Compare commits
15 Commits
166
...
current_fi
| Author | SHA1 | Date | |
|---|---|---|---|
| dce9dd41a4 | |||
| 31f92e89d9 | |||
| 13f1a2c5fd | |||
| 345c81166e | |||
| 7713ff7573 | |||
| fe95dbff3d | |||
| 001ade4b20 | |||
| 8427827247 | |||
| 3e69aab967 | |||
| b0c1667523 | |||
| 29a191be56 | |||
| 22eb4ce243 | |||
| 4635f00a20 | |||
| 1955f2c98f | |||
| ba9a54959e |
Binary file not shown.
@@ -1,9 +1,9 @@
|
||||
********************************************************************************
|
||||
TMS320C2000 Hex Converter v5.2.7
|
||||
TMS320C2000 Hex Converter v22.6.0
|
||||
********************************************************************************
|
||||
|
||||
INPUT FILE NAME: <c:\Temp\166-sifu-build-vq3o6iha\UKSSTMS320F28335.out>
|
||||
OUTPUT FORMAT: Binary
|
||||
INPUT FILE NAME: <K:\git_project\28335_periph\Bin\UKSSTMS320F28335.out>
|
||||
OUTPUT FORMAT: Intel
|
||||
|
||||
PHYSICAL MEMORY PARAMETERS
|
||||
Default data width : 16
|
||||
@@ -12,22 +12,22 @@ PHYSICAL MEMORY PARAMETERS
|
||||
|
||||
BOOT LOADER PARAMETERS
|
||||
Table Type: SERIAL PORT (SCI 8 bit Mode)
|
||||
Entry Point: 0x0000b56a
|
||||
Entry Point: 0x0000b7bc
|
||||
|
||||
|
||||
OUTPUT TRANSLATION MAP
|
||||
--------------------------------------------------------------------------------
|
||||
00000000..003fffff Page=0 Memory Width=8 ROM Width=8
|
||||
00000000..ffffffff Page=0 Memory Width=8 ROM Width=8
|
||||
--------------------------------------------------------------------------------
|
||||
OUTPUT FILES: c:\Temp\166-sifu-build-vq3o6iha\UKSSTMS320F28335.bin [b0..b7]
|
||||
OUTPUT FILES: K:\git_project\28335_periph\UKSSTMS320F28335\Debug\.identity-release-018w5cqu\.firmware-7iwzourf\UKSSTMS320F28335.hex [b0..b7]
|
||||
|
||||
CONTENTS: 00000000..000075d5 BOOT TABLE
|
||||
.cinit : dest=0000b717 size=00000262 width=00000002
|
||||
.text : dest=00008000 size=00003717 width=00000002
|
||||
.econst : dest=0000c000 size=00000112 width=00000002
|
||||
.switch : dest=0000c112 size=00000048 width=00000002
|
||||
CONTENTS: 00000000..00007aa3 BOOT TABLE
|
||||
.cinit : dest=0000b969 size=0x26b width=00000002
|
||||
.text : dest=00008000 size=0x3969 width=00000002
|
||||
.econst : dest=0000c000 size=0x11e width=00000002
|
||||
.switch : dest=0000c11e size=0x48 width=00000002
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
00000000..003fffff Page=1 Memory Width=8 ROM Width=8 "*DEFAULT PAGE 1*"
|
||||
00000000..ffffffff Page=1 Memory Width=8 ROM Width=8 "*DEFAULT PAGE 1*"
|
||||
--------------------------------------------------------------------------------
|
||||
NO CONTENTS
|
||||
|
||||
Binary file not shown.
@@ -129,3 +129,4 @@ void test_rs_live(RS_DATA *rs_arr);
|
||||
#endif
|
||||
|
||||
#endif /* _RS485 */
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/* Отличающийся код обратного порядка выбирает ветку перестановки двух показаний в pulto.c. */
|
||||
#define REVERSE 1
|
||||
|
||||
/* Сохраняет выбранное в обеих ветках исполнение 165 с прямым порядком индикаторов; перенос функций не меняет аппаратные калибровки. */
|
||||
/* Выбирает исполнение 166: соответствующие калибровки и обратный порядок индикаторов задаются условными блоками ниже. */
|
||||
#define BALSAM 165
|
||||
|
||||
#if BALSAM == 166
|
||||
|
||||
@@ -207,8 +207,8 @@ void RS_RX_Handler(RS_DATA *rs_arr)
|
||||
{
|
||||
if(rs_arr->pRS_RecvPtr<(unsigned int *)Rec_Bloc_Begin || rs_arr->pRS_RecvPtr>(unsigned int *)Rec_Bloc_End)
|
||||
{
|
||||
rs_arr->pRS_RecvPtr = (unsigned int *)Rec_Bloc_Begin; // На программу надейса, а сам не плошай
|
||||
rs_arr->pRecvPtr = (unsigned int *)Rec_Bloc_Begin; // На программу надейса, а сам не плошай
|
||||
rs_arr->pRS_RecvPtr = (unsigned int *)Rec_Bloc_Begin; // На программу надейса, а сам не плошай
|
||||
rs_arr->pRecvPtr = (unsigned int *)Rec_Bloc_Begin; // На программу надейса, а сам не плошай
|
||||
}
|
||||
if(rs_arr->RS_PrevCmd != CMD_INITLOAD) continue; // Мы здесь оказались по какой-то чудовищной ошибке
|
||||
|
||||
@@ -351,15 +351,15 @@ int RS_Send(RS_DATA *rs_arr,unsigned int *pBuf,unsigned long len)
|
||||
if(len > 1)
|
||||
{
|
||||
enableUARTIntW(rs_arr); /* Разрешаем прерываниа по передаче */
|
||||
SCI_send(rs_arr, *pBuf); // Передаем второй байт по прерыванию
|
||||
SCI_send(rs_arr, *pBuf); // Передаем второй байт по прерыванию
|
||||
}
|
||||
else
|
||||
{
|
||||
SCI_send(rs_arr, *pBuf); // Передаем второй байт по прерыванию
|
||||
RS_Wait4OK(rs_arr); /* Дожидаемса ухода без прерываниа */
|
||||
for (i=0; i <= TIME_WAIT_RS_BYTE_OUT; i++){} /* Пауза дла PC */
|
||||
RS_SetBitMode(rs_arr,9); /* Обратно в 9-бит режим */
|
||||
RS_Line_to_receive(rs_arr); /* режим приема RS485 */
|
||||
RS_Wait4OK(rs_arr); /* Дожидаемса ухода без прерываниа */
|
||||
for (i=0; i <= TIME_WAIT_RS_BYTE_OUT; i++){} /* Пауза дла PC */
|
||||
RS_SetBitMode(rs_arr,9); /* Обратно в 9-бит режим */
|
||||
RS_Line_to_receive(rs_arr); /* режим приема RS485 */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ void load(RS_DATA *rs_arr)
|
||||
else
|
||||
{
|
||||
rs_arr->BS_LoadOK = false;
|
||||
RS_Line_to_receive(rs_arr); // режим приема RS485
|
||||
RS_Line_to_receive(rs_arr); // режим приема RS485
|
||||
RS_SetBitMode(rs_arr,9);
|
||||
} }
|
||||
|
||||
@@ -297,10 +297,10 @@ void upload(RS_DATA *rs_arr)
|
||||
crc = get_crc_16( crc, rs_arr->buffer, 2);
|
||||
crc = get_crc_16b( crc, (unsigned int *)Address, Length);
|
||||
|
||||
RS_Send(rs_arr,rs_arr->buffer, 1); // <=2 байт по флагу
|
||||
RS_Send(rs_arr,rs_arr->buffer, 1); // <=2 байт по флагу
|
||||
|
||||
rs_arr->buffer[0] = CMD_UPLOAD;
|
||||
RS_Send(rs_arr,rs_arr->buffer, 1); // <=2 байт по флагу
|
||||
RS_Send(rs_arr,rs_arr->buffer, 1); // <=2 байт по флагу
|
||||
|
||||
RS_Wait4OK(rs_arr);
|
||||
RS_BSend(rs_arr,(unsigned int*)Address, Length);
|
||||
|
||||
@@ -35,3 +35,4 @@ void set_cntrl_addr (int cntrl_addr,int cntrl_addr_for_all)
|
||||
CNTRL_ADDR = cntrl_addr;
|
||||
ADDR_FOR_ALL = cntrl_addr_for_all;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,8 @@ void InitCan(int Port, int DevNum)
|
||||
ECanMOTORegs->MOTO0 = 550000;
|
||||
ECanMOTORegs->MOTO1 = 550000;
|
||||
|
||||
ECanRegs->CANTOC.all = 0; // До первой передачи отключает контроль mailbox: по SPRUI07, 13.8.18.3, сброс TOC снимает TOS и MTOF без записи в CANTOS.
|
||||
ECanRegs->CANTOC.all = 1;
|
||||
ECanRegs->CANTOS.all = 0; // clear all time-out flags
|
||||
ECanRegs->CANTSC = 0; // clear time-out counter
|
||||
|
||||
ECanShadow.CANGIM.all = 0;
|
||||
|
||||
@@ -20,27 +20,6 @@
|
||||
int digits[16] = {63,6,91,79,102,109,125,7,127,111,64,0,0,0,121,0};
|
||||
int readr[5] = {0x08,0x0C,0x10,0x00,0x04};
|
||||
|
||||
/* Ограничивает настройки таймингов компилятором TI C2000; модель на ПК не использует эти директивы. */
|
||||
#if defined(__TI_COMPILER_VERSION__)
|
||||
/* Сохраняет отдельный вызов переключения CLOCK: встраивание при -O3 сокращало удержание DATA после фронта. */
|
||||
#pragma FUNC_CANNOT_INLINE(DCLK)
|
||||
/* Возвращает генерацию DCLK как в образе от 06.10.2026: уровень 1 и приоритет скорости 3 сохраняют прежнюю последовательность инструкций GPIO. */
|
||||
#pragma FUNCTION_OPTIONS(DCLK, "--opt_level=1 --opt_for_speed=3")
|
||||
/* Сохраняет отдельный вызов переключения DATA, чтобы длительность импульсов не зависела от встраивания в передатчик. */
|
||||
#pragma FUNC_CANNOT_INLINE(DOUT)
|
||||
/* Возвращает генерацию DOUT с прежними параметрами -O1 и --opt_for_speed=3; значения взяты из старой конфигурации CCS. */
|
||||
#pragma FUNCTION_OPTIONS(DOUT, "--opt_level=1 --opt_for_speed=3")
|
||||
/* Не позволяет встроить разделитель пакетов в цикл передачи и изменить интервалы между переключениями линий. */
|
||||
#pragma FUNC_CANNOT_INLINE(RESET)
|
||||
/* Сохраняет прежнюю последовательность вызовов GPIO и задержек разделителя, используя настройки старого образа. */
|
||||
#pragma FUNCTION_OPTIONS(RESET, "--opt_level=1 --opt_for_speed=3")
|
||||
/* Сохраняет отдельный вызов передачи бита; протокол использует порядок и интервалы фронтов CLOCK и DATA. */
|
||||
#pragma FUNC_CANNOT_INLINE(SENDBIT)
|
||||
/* Закрепляет для передачи бита настройки из 167: -O1 и --opt_for_speed=3 сохраняют интервалы GPIO независимо от общего уровня оптимизации проекта. */
|
||||
#pragma FUNCTION_OPTIONS(SENDBIT, "--opt_level=1 --opt_for_speed=3")
|
||||
/* Завершает блок настроек TI, не изменяя формат пакетов и существующие счётчики задержек. */
|
||||
#endif
|
||||
|
||||
void DCLK(int x)
|
||||
{
|
||||
if(x) GpioDataRegs.GPASET.bit.GPIO6=1;
|
||||
@@ -109,7 +88,7 @@ void kanal_Send(int adr, long dat, int dot)
|
||||
long Word,data,aliq_part,dg[4];
|
||||
int i,j,bit,byt,addr,sgn=0,punkt=0,aliq_len=0,full_len, isdot=0;
|
||||
|
||||
if(adr>1) // Адреса каналов выше 1 передают готовую битовую маску ламп; каналы 0 и 1 требуют преобразования числа в сегменты.
|
||||
if(adr>1) // Лампочки
|
||||
{
|
||||
Word =dat;
|
||||
}
|
||||
@@ -160,7 +139,7 @@ void kanal_Send(int adr, long dat, int dot)
|
||||
for(i=3;i>0;i--)
|
||||
{
|
||||
if((dg[i]==0)&&(i!=dot))
|
||||
dg[i]=0xF; // Индекс 0xF таблицы digits задаёт пустой символ, скрывая ведущий ноль вне позиции десятичной точки.
|
||||
dg[i]=0xF; // Это значит пусто
|
||||
else break;
|
||||
}
|
||||
|
||||
@@ -169,7 +148,7 @@ void kanal_Send(int adr, long dat, int dot)
|
||||
{
|
||||
if( (dg[i]==0xF)||(i==3))
|
||||
{
|
||||
dg[i]=0xA; // Индекс 0xA таблицы digits задаёт знак минуса в свободном разряде отрицательного числа.
|
||||
dg[i]=0xA; // Это значит минус
|
||||
break;
|
||||
} }
|
||||
|
||||
|
||||
@@ -812,3 +812,4 @@ void Power_count(int chan)
|
||||
if(!WAKE)
|
||||
reset_errs(chan,error);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ interrupt void cpu_timer1_isr_PULT(void)
|
||||
|
||||
EALLOW;
|
||||
CpuTimer1.InterruptCount++;
|
||||
IER &= (MINT13 & ~M_INT13); // Сохраняет только ранее разрешённые источники по таблице TI и запрещает повторный Timer1, чтобы передача DATA/CLOCK завершилась без вложенного вызова.
|
||||
IER |= MINT13; // Set "global" priority
|
||||
EINT;
|
||||
EDIS; // This is needed to disable write to EALLOW protected registers
|
||||
|
||||
|
||||
@@ -4,17 +4,6 @@
|
||||
`K:\git_project\28335_periph\UKSSTMS320F28335\.cproject`.
|
||||
Для рабочей сборки используйте конфигурацию **Debug**.
|
||||
|
||||
В локальную ветку `166` перенесены функции из `current_firmware_166`:
|
||||
телеметрия CAN по изменению с heartbeat, логгер v4, паспорт прошивки,
|
||||
блочное чтение EEPROM и проверяемый выпуск BIN. Сохранены локальные
|
||||
настройки таймингов DATA/CLOCK в `kanal.c`, запрет повторного входа Timer1
|
||||
в `pulto.c` и отключение контроля CAN timeout до первой передачи.
|
||||
Выбор `BALSAM=165`, существовавший в обеих ветках, сохранён: перенос
|
||||
функций не меняет аппаратные калибровки и порядок индикаторов.
|
||||
|
||||
Образы в `Bin` при переносе исходников не заменялись. Перед прошивкой
|
||||
платы нужна новая полная сборка: прежние HEX/OUT не содержат этого переноса.
|
||||
|
||||
## Реальные пути на этом компьютере
|
||||
|
||||
| Назначение | Полный путь |
|
||||
|
||||
77
tests/can_scheduler/run_test.py
Normal file
77
tests/can_scheduler/run_test.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from pathlib import Path # Проверяет исходники подготовленной прошивки и отдельно исходную версию для чувствительности регрессии.
|
||||
import re # Выделяет настоящие тела C-функций без комментариев.
|
||||
import subprocess # Компилирует и выполняет поведенческую проверку C-кода.
|
||||
import os # Добавляет библиотеки MinGW только в окружение дочерних процессов.
|
||||
import tempfile # Изолирует сгенерированные файлы GCC от исходников прошивки.
|
||||
import sys # Позволяет выбрать старую версию планировщика для воспроизведения ошибки.
|
||||
stage = Path(__file__).resolve().parents[2] # Все результаты проверки сохраняются в каталоге этой конкретной правки.
|
||||
root = Path(sys.argv[1]) if len(sys.argv) > 1 else stage # По умолчанию проверяет подготовленное исправление.
|
||||
def clean(path): # Комментарии не участвуют в поиске вложенных блоков алгоритма.
|
||||
return re.sub(r'/\*.*?\*/|//[^\n]*', '', path.read_bytes().decode('utf-8', errors='replace'), flags=re.S) # Замена старых комментариев не меняет ASCII-код алгоритма.
|
||||
def block(source, start): # Возвращает выбранный блок вместе со всеми вложенными условиями.
|
||||
cursor = source.index('{', start) + 1 # Начинает счётчик после открывающей скобки выбранного определения.
|
||||
depth = 1 # Отслеживает незакрытый внешний блок.
|
||||
while depth: # Завершает поиск только на парной внешней закрывающей скобке.
|
||||
depth += (source[cursor] == '{') - (source[cursor] == '}') # Учитывает все вложенные ветви production-кода.
|
||||
cursor += 1 # Включает последнюю закрывающую скобку в извлечённый фрагмент.
|
||||
return source[start:cursor] # Не поддерживает отдельную копию проверяемого алгоритма.
|
||||
ecan = clean(root / 'Source/Internal/ecan.c') # Передатчик проверяется в той же версии, что выбранный планировщик.
|
||||
functions = [] # Модель включает штатную отправку и действующий обработчик тайм-аутов.
|
||||
for name in ('CAN_send', 'CANa_reset_err'): # Оба алгоритма должны сохранять согласованный жизненный цикл mailbox.
|
||||
match = re.search(r'(?:interrupt\s+)?(?:void|int)\s+' + name + r'\([^;{}]*\)\s*\{', ecan) # Поддерживает старую void-версию и исправленную int-версию функции отправки.
|
||||
assert match, name # Отсутствие функции делает проверку неуспешной.
|
||||
functions.append(block(ecan, match.start())) # Использует реальное тело, включая все аппаратные условия отказа.
|
||||
main = clean(root / 'Source/Internal/main.c') # Планировщик извлекается непосредственно из фонового цикла платы.
|
||||
scheduler = block(main, main.index('if(CanGO)')) # Изолирует только обслуживание очередных периодов CAN.
|
||||
model = (stage / 'tests/can_timeout/model.inc').read_text(encoding='utf-8') # Повторно использует уже проверенную модель регистров eCAN.
|
||||
prefix = model.split('/* PRODUCTION */')[0] # Типы регистров остаются общими для проверки тайм-аутов и очереди.
|
||||
helpers = model[model.index('#define CHECK'):model.index('int main(void)')] # Проверки условий и сброс модели совпадают с существующим тестом.
|
||||
body = r'''
|
||||
static int CanGO, modbus[131]; /* Запас двух слов повторяет поведение трёхсловного пакета на верхней границе банка. */
|
||||
static unsigned int cancount[2], cancell[2], Maska[2][8]; /* Два потока и восемь 16-битных масок повторяют main и Init_packMask. */
|
||||
#define Cancount (modbus+0x60) /* R96 и R97 задают периоды быстрого и медленного потоков. */
|
||||
static void step(void) { int i, mask; /* Локальные переменные соответствуют исходному main.c. */
|
||||
/* SCHEDULER */
|
||||
} /* Один вызов обслуживает очередной базовый интервал CANPOWSE. */
|
||||
static void prepare(unsigned fast, unsigned slow) { /* Задаёт именно карту задатчика, без обращения к физической CAN-шине. */
|
||||
reset_model(); memset(modbus,0,sizeof(modbus)); memset(cancount,0,sizeof(cancount)); memset(cancell,0,sizeof(cancell)); memset(Maska,0,sizeof(Maska)); /* Начинает независимый сценарий. */
|
||||
Maska[0][1]=1; Maska[1][0]=15; Maska[1][6]=0x1FF; Maska[1][7]=0xE000; /* R16, R0–R3, параметры R96–R104 и R125–R127 соответствуют Init_packMask(dsk_EPLT). */
|
||||
Cancount[0]=(int)fast; Cancount[1]=(int)slow; modbus[0]=9; modbus[1]=12; /* Использует периоды сценария и задание со снимка пользователя. */
|
||||
} /* Завершает подготовку начального состояния. */
|
||||
static void tick(void) { CanGO=1; step(); } /* Таймер даёт ровно один базовый интервал планировщика. */
|
||||
static void complete(void) { ECanaRegs.CANTRS.all=0; ECanaRegs.CANTRR.all=0; ECanaRegs.CANTA.all=1; } /* Имитирует аппаратное завершение предыдущего кадра до следующего интервала. */
|
||||
int main(void) { /* Проверяет именно потерю числового фрагмента при одновременном запуске двух потоков. */
|
||||
unsigned n, scenario, seen[128], address; /* Счётчики наблюдают кадры, действительно поставленные в MBOX0. */
|
||||
const unsigned periods[][2]={{10,50},{10,101},{10,8},{12,50}}; /* Проверяет текущие defaults, прежний делитель и несовпадающие периоды из EEPROM. */
|
||||
prepare(10,50); cancount[0]=9; cancount[1]=49; tick(); /* Воспроизводит совпадение быстрого и медленного сроков отправки. */
|
||||
CHECK(((ECanaMboxes.MBOX0.MDH.all>>16)&0x1FFF)==16); /* Быстрый поток успел поставить кнопки в штатный mailbox. */
|
||||
CHECK(cancell[1]==0 && cancount[1]==50); /* Занятость сохраняет неподтверждённый адрес R0 и готовность повторной попытки. */
|
||||
complete(); tick(); /* Следующий свободный интервал должен доставить отложенное задание. */
|
||||
CHECK(((ECanaMboxes.MBOX0.MDH.all>>16)&0x1FFF)==0 && cancell[1]==3 && cancount[1]==0); /* Адрес продвигается только после принятого mailbox кадра R0–R2. */
|
||||
CHECK((ECanaMboxes.MBOX0.MDH.all&0xFFFF)==9 && (ECanaMboxes.MBOX0.MDL.all>>16)==12); /* В кадре находятся реальные R0/R1 платы, а не значения из GUI. */
|
||||
prepare(10,50); ECanaRegs.CANTRS.all=1; wait=1; /* Длительно занятый mailbox не должен вызывать переполнение счётчиков. */
|
||||
for(n=0;n<100000;n++) tick(); /* Сто тысяч интервалов превышают полный диапазон 16-битного счётчика C28x. */
|
||||
CHECK(cancount[0]==10 && cancount[1]==50 && cancell[1]==0); /* Оба периода насыщаются, не теряя неподтверждённое задание. */
|
||||
complete(); tick(); complete(); tick(); /* Последовательно освобождает mailbox для обоих ожидающих потоков. */
|
||||
CHECK(((ECanaMboxes.MBOX0.MDH.all>>16)&0x1FFF)==0); /* После восстановления шины повторяется прежде отложенный R0. */
|
||||
for(scenario=0;scenario<sizeof(periods)/sizeof(periods[0]);scenario++) { /* Проверяет устойчивость при разных параметрах EEPROM. */
|
||||
prepare(periods[scenario][0],periods[scenario][1]); memset(seen,0,sizeof(seen)); /* Сбрасывает статистику каждого сценария. */
|
||||
for(n=0;n<3000;n++) { /* Одна минута модельного времени при базовом интервале 20 мс. */
|
||||
complete(); tick(); /* Предыдущий кадр завершается, затем выполняется настоящий планировщик. */
|
||||
if(ECanaRegs.CANTRS.all&1) { address=(ECanaMboxes.MBOX0.MDH.all>>16)&0x1FFF; CHECK(address<128); seen[address]++; } /* Учитывает только фактически поставленные в передатчик адреса. */
|
||||
} /* Обновления R16 не должны вытеснять числовую индикацию из медленного потока. */
|
||||
CHECK(seen[0]>0 && seen[3]>0 && seen[16]>0 && seen[99]>0 && seen[125]>0); /* Все группы из пользовательского журнала, включая пропавший R0, достигают mailbox. */
|
||||
} /* Завершает матрицу периодов передачи. */
|
||||
prepare(0,0); tick(); CHECK(ECanaRegs.CANTRS.all==0); /* Нулевые периоды продолжают отключать рассылку без изменения настроек EEPROM. */
|
||||
puts("PASS scheduler: R0 retried after collision, counters saturated, all VPU groups sent for 4 period pairs"); /* Указывает программные гарантии без заявления об испытании на плате. */
|
||||
return 0; /* Все сценарии завершены успешно. */
|
||||
} /* Завершает поведенческую проверку. */
|
||||
''' # C-модель исполняет извлечённый production-код вместо упрощённого пересказа алгоритма.
|
||||
code = prefix + '\n'.join(functions) + '\n' + helpers + body.replace('/* SCHEDULER */', scheduler) # Отделяет директиву CHECK переводом строки после последней извлечённой функции.
|
||||
compiler = Path('C:/msys64/mingw64/bin/gcc.exe') # Использует уже установленный компилятор без загрузок и установки зависимостей.
|
||||
environment = dict(os.environ, PATH=str(compiler.parent)+os.pathsep+os.environ.get('PATH','')) # Подключает DLL компилятора только дочерней проверке.
|
||||
temporary = tempfile.TemporaryDirectory(prefix='can-scheduler-') # Удаляет только временные результаты проверки.
|
||||
cpath, exe = Path(temporary.name) / 'scheduler-test.c', Path(temporary.name) / 'scheduler-test.exe' # Артефакты проверки не смешиваются с образом для микроконтроллера.
|
||||
cpath.write_text(code, encoding='utf-8') # Сохраняет исходник модели с русскими комментариями в UTF-8.
|
||||
subprocess.run([str(compiler),'-std=c99','-O3','-Wall','-Wextra','-Wno-unused-parameter','-Werror',*(['-Wno-error=sign-compare'] if root != stage else []),str(cpath),'-o',str(exe)],env=environment,check=True) # Исправленный код требует чистой сборки; старый допускает только известное предупреждение знака для проверки самой потери R0.
|
||||
subprocess.run([str(exe)],env=environment,check=True,timeout=10) # Бесконечное ожидание или пропущенный R0 немедленно проваливают регрессию.
|
||||
@@ -1,58 +1,116 @@
|
||||
#include <stdint.h> /* Задаёт размеры 16- и 32-битных типов C28x в хостовой модели. */
|
||||
|
||||
#include <stdio.h> /* Печатает конкретное нарушенное условие и итог сценариев. */
|
||||
|
||||
#include <string.h> /* Обнуляет модель между независимыми сценариями. */
|
||||
|
||||
typedef uint16_t Uint16; /* Воспроизводит тип масок PIE, используемый обработчиком TI. */
|
||||
|
||||
typedef uint32_t Uint32; /* Воспроизводит упаковку двух технологических слов в MDH/MDL. */
|
||||
|
||||
#define interrupt /* Убирает только аппаратный атрибут функции при компиляции на ПК. */
|
||||
|
||||
#define EALLOW ((void)0) /* Модель не воспроизводит механизм защиты регистров ядра. */
|
||||
|
||||
#define EDIS ((void)0) /* Защита EALLOW проверяется сборкой настоящего модуля под TI. */
|
||||
|
||||
#define EINT ((void)0) /* Входы ISR вызывает сценарий, не поток операционной системы. */
|
||||
|
||||
#define DINT ((void)0) /* Отключение прерываний не требует имитации планировщика на ПК. */
|
||||
|
||||
#define M_INT9 0x0100U /* Маска CPU INT9 соответствует девятой группе PIE. */
|
||||
|
||||
#define MINT9 0x0100U /* Для проверки жизненного цикла сохраняет разрешение группы CAN. */
|
||||
|
||||
#define MG96 0x00DFU /* Исключает повторную вложенную обработку INTx6 той же группы. */
|
||||
|
||||
struct REG { uint32_t all; }; /* Представляет 32-битные регистры eCAN без зависимости от размеров long на хосте. */
|
||||
|
||||
struct ECAN_REGS { struct REG CANTA, CANAA, CANTRR, CANTRS, CANTOC, CANTOS; uint32_t CANTSC; }; /* Даёт рабочим функциям реальные имена используемых регистров. */
|
||||
|
||||
static volatile struct ECAN_REGS ECanaRegs; /* Позволяет наблюдать все записи production-функций при оптимизации O3. */
|
||||
|
||||
struct ECAN_MBOXES { struct { struct REG MDH, MDL; } MBOX0; }; /* Моделирует только штатный передатчик, который проверяет этот тест. */
|
||||
|
||||
static volatile struct ECAN_MBOXES ECanaMboxes; /* Содержимое mailbox остаётся доступным для проверок неизменности занятого кадра. */
|
||||
|
||||
static struct { struct { Uint16 all; } PIEIER9; union { unsigned all; struct { unsigned reserved:8, ACK9:1; } bit; } PIEACK; } PieCtrlRegs; /* Связывает запись всей маски PIEACK с битом подтверждения девятой группы в позиции 8. */
|
||||
|
||||
static Uint16 IER; /* Представляет маску разрешённых прерываний ядра. */
|
||||
|
||||
static int wait, CanTimeOutErrorTR; /* Повторяет состояние штатного передатчика и счётчик обнаруженных тайм-аутов. */
|
||||
|
||||
static void led1_toggle(void) {} /* Светодиод передачи не влияет на завершение аппаратной транзакции. */
|
||||
|
||||
/* PRODUCTION */
|
||||
|
||||
#define CHECK(condition) do { if (!(condition)) { printf("FAIL line %d: %s\n", __LINE__, #condition); return 1; } } while (0) /* Не отключает проверку условий при оптимизации или NDEBUG. */
|
||||
|
||||
static void reset_model(void) { /* Начинает сценарий без остаточных запросов и подтверждений предыдущего теста. */
|
||||
|
||||
memset((void *)&ECanaRegs, 0, sizeof(ECanaRegs)); /* Сбрасывает моделируемые аппаратные регистры CAN. */
|
||||
|
||||
memset((void *)&ECanaMboxes, 0, sizeof(ECanaMboxes)); /* Убирает ранее переданное содержимое mailbox. */
|
||||
|
||||
memset(&PieCtrlRegs, 0, sizeof(PieCtrlRegs)); /* Исключает ложное подтверждение PIE из предыдущего сценария. */
|
||||
|
||||
PieCtrlRegs.PIEIER9.all = 0x0030U; IER = M_INT9; /* Разрешает оба CAN-прерывания для проверки восстановления маски ISR. */
|
||||
|
||||
wait = 0; CanTimeOutErrorTR = 0; /* Повторяет начальные значения production-переменных. */
|
||||
|
||||
} /* Завершает начальную установку независимого сценария. */
|
||||
|
||||
int main(void) { /* Проверяет потерю ACK, фантомное прерывание, занятый mailbox и восстановление отправки. */
|
||||
|
||||
int words[128] = {0}; unsigned calls; /* Хранит данные кнопок и ограничивает имитацию повторных входов ISR. */
|
||||
|
||||
reset_model(); ECanaRegs.CANTOC.all = 1; ECanaRegs.CANTOS.all = 1; ECanaRegs.CANTRS.all = 5; wait = 1; /* Тайм-аут относится к mailbox 0, пока служебный mailbox 2 тоже занят. */
|
||||
|
||||
for (calls = 0; calls < 4 && ECanaRegs.CANTOS.all; calls++) { /* Повторяет IRQ, пока источник не снят, с явной защитой от зависания старого кода. */
|
||||
|
||||
CANa_reset_err(); /* Выполняет настоящий обработчик проекта, включая отмену TX и подтверждение PIE. */
|
||||
|
||||
ECanaRegs.CANTOS.all &= ECanaRegs.CANTOC.all; /* По SPRUI07 13.8.18.3 сброс TOC аппаратно снимает TOS и MTOF; одна запись CANTRR этого не гарантирует. */
|
||||
|
||||
} /* Проверяет, что обработчик освобождает процессор после первого тайм-аута. */
|
||||
|
||||
CHECK(calls == 1 && ECanaRegs.CANTOS.all == 0 && CanTimeOutErrorTR == 1); /* Повторные входы без снятия источника означают блокирование основного цикла. */
|
||||
|
||||
CHECK(ECanaRegs.CANTRR.all == 1 && ECanaRegs.CANTRS.all == 5); /* Отмена запрошена только для штатного TX; модель намеренно ещё не завершила аппаратную отмену. */
|
||||
|
||||
CHECK(PieCtrlRegs.PIEIER9.all == 0x0030U && PieCtrlRegs.PIEACK.bit.ACK9 == 1); /* ISR сохраняет прежние разрешения обоих каналов CAN и подтверждает свою группу. */
|
||||
|
||||
words[16] = 8; ECanaMboxes.MBOX0.MDH.all = 0x12345678UL; /* Подготавливает новое состояние кнопки при ещё занятом предыдущем кадре. */
|
||||
|
||||
CAN_send(0, words, 16); CHECK(ECanaMboxes.MBOX0.MDH.all == 0x12345678UL); /* Не допускает перезаписи данных до завершения отмены. */
|
||||
|
||||
ECanaRegs.CANTRS.all = 0; ECanaRegs.CANTRR.all = 0; ECanaRegs.CANAA.all = 1; ECanaRegs.CANTSC = 999; /* Имитирует аппаратное завершение отмены и подтверждение CANAA0. */
|
||||
|
||||
CAN_send(0, words, 16); /* Проверяет повторный запуск тем же реальным передатчиком после исчезновения тайм-аута. */
|
||||
|
||||
CHECK(ECanaRegs.CANTOC.all == 1 && ECanaRegs.CANTSC == 0 && ECanaRegs.CANTRS.all == 1); /* Новая передача вновь получает собственный отсчёт тайм-аута. */
|
||||
|
||||
CHECK(ECanaMboxes.MBOX0.MDH.all == 0xE0100008UL && ECanaMboxes.MBOX0.MDL.all == 0); /* После восстановления передаются свежие данные R16, а не старый нулевой кадр. */
|
||||
|
||||
reset_model(); ECanaRegs.CANTOC.all = 1; ECanaRegs.CANTRS.all = 1; /* Фантомное PIE-прерывание может прийти, когда начат следующий кадр без тайм-аута. */
|
||||
|
||||
CANa_reset_err(); CHECK(ECanaRegs.CANTRR.all == 0 && CanTimeOutErrorTR == 0 && ECanaRegs.CANTOC.all == 1); /* Не отменяет исправную передачу при уже снятом TOS0. */
|
||||
|
||||
reset_model(); ECanaRegs.CANTOC.all = 1; ECanaRegs.CANTOS.all = 1; /* Передача успела завершиться, но сценарий сохраняет зафиксированный флаг тайм-аута. */
|
||||
|
||||
CANa_reset_err(); CHECK(ECanaRegs.CANTRR.all == 0 && ECanaRegs.CANTOC.all == 0); /* Снимает контроль, не отменяя уже свободный mailbox. */
|
||||
|
||||
reset_model(); ECanaRegs.CANTRR.all = 1; ECanaMboxes.MBOX0.MDH.all = 0x11223344UL; /* Аппаратная отмена ещё активна, даже если CANTRS уже снят. */
|
||||
|
||||
CAN_send(0, words, 16); CHECK(ECanaMboxes.MBOX0.MDH.all == 0x11223344UL && ECanaRegs.CANTRS.all == 0); /* Не запускает повторный кадр до снятия CANTRR0. */
|
||||
|
||||
reset_model(); wait = 1; ECanaRegs.CANTA.all = 1; /* Проверяет обычную передачу после успешного подтверждения предыдущего кадра. */
|
||||
|
||||
CAN_send(0, words, 16); CHECK(ECanaRegs.CANTRS.all == 1 && ECanaRegs.CANTOC.all == 1); /* Исправление не блокирует штатный обмен при наличии CAN ACK. */
|
||||
|
||||
puts("PASS CAN timeout: IRQ released, phantom ignored, busy mailbox preserved, TX recovered"); /* Сообщает только о проверенных сценариях программной модели. */
|
||||
|
||||
return 0; /* Подтверждает прохождение всех проверок оптимизированного рабочего кода. */
|
||||
|
||||
} /* Завершает тест без доступа к реальному USB-CAN или питанию платы. */
|
||||
|
||||
|
||||
@@ -1,36 +1,62 @@
|
||||
"""Проверяет тайм-аут штатного CAN на настоящих функциях проекта 166.""" # Модель не заменяет испытание аппаратного eCAN.
|
||||
|
||||
import os # Передаёт пути библиотек GCC только дочерним процессам.
|
||||
|
||||
from pathlib import Path # Находит исходники независимо от рабочего каталога запуска.
|
||||
|
||||
import re # Удаляет комментарии и выделяет тела проверяемых функций.
|
||||
|
||||
import subprocess # Запускает компилятор и проверяет поведенческий результат модели.
|
||||
|
||||
import tempfile # Изолирует сгенерированные файлы от образов прошивки пользователя.
|
||||
|
||||
import sys # Позволяет проверить отдельно сохранённую старую версию ecan.c.
|
||||
|
||||
|
||||
|
||||
root = Path(__file__).resolve().parents[2] # Корень проекта содержит рабочую реализацию eCAN.
|
||||
|
||||
source_path = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "Source/Internal/ecan.c" # Дополнительный путь нужен для проверки чувствительности к старой ошибке.
|
||||
|
||||
source = re.sub(r"/\*.*?\*/|//[^\n]*", "", source_path.read_text(encoding="utf-8"), flags=re.S) # Комментарии со скобками не участвуют в выделении функций.
|
||||
if '#include "can_telemetry_impl.h"' in source: # Новая реализация отправителя вынесена в заголовок и требует модели с подтверждённым кэшем и временем Timer1.
|
||||
import runpy # Запускает поведенческую проверку действующего передатчика вместо извлечения отсутствующей функции из ecan.c.
|
||||
sys.argv = [str(root / "tests/can_telemetry/run_test.py")] # Не передаёт путь отдельного ecan.c как ошибочный корень проекта новому тесту.
|
||||
runpy.run_path(sys.argv[0], run_name="__main__") # Проверяет аппаратный и программный тайм-ауты, отмену TX, ACK и секундную паузу на production-коде.
|
||||
raise SystemExit(0) # Старую модель ниже сохраняет только для явно выбранных снимков прежнего передатчика.
|
||||
|
||||
|
||||
|
||||
def function(name): # Извлекает текущий алгоритм вместо поддержания отдельной копии в тесте.
|
||||
match = re.search(r"(?:interrupt\s+)?void\s+" + name + r"\([^;{}]*\)\s*\{", source) # Отличает определение от вызова и объявления.
|
||||
|
||||
match = re.search(r"(?:interrupt\s+)?(?:void|int)\s+" + name + r"\([^;{}]*\)\s*\{", source) # Отличает определение от вызова и объявления.
|
||||
|
||||
assert match is not None, name # Исчезновение функции должно останавливать проверку.
|
||||
|
||||
end, depth = match.end(), 1 # Начинает поиск после открывающей скобки тела.
|
||||
|
||||
while depth: # Учитывает вложенные условия до закрытия внешнего блока.
|
||||
|
||||
depth += (source[end] == "{") - (source[end] == "}") # Считает только скобки кода без комментариев.
|
||||
|
||||
end += 1 # Включает найденную закрывающую скобку в результат.
|
||||
|
||||
return source[match.start():end] # Сохраняет все реальные обращения к регистрам и условия.
|
||||
|
||||
|
||||
|
||||
model = Path(__file__).with_name("model.inc").read_text(encoding="utf-8") # Загружает минимальное аппаратное окружение и сценарии потери ACK.
|
||||
|
||||
model = model.replace("/* PRODUCTION */", function("CAN_send") + "\n" + function("CANa_reset_err")) # Проверяет передачу и прерывание в одном жизненном цикле.
|
||||
|
||||
compiler = os.environ.get("CC", "C:/msys64/mingw64/bin/gcc.exe") # Использует установленный GCC, сохраняя возможность явно выбрать другой.
|
||||
|
||||
environment = dict(os.environ) # Не меняет окружение приложения и проекта CCS.
|
||||
|
||||
environment["PATH"] = str(Path(compiler).parent) + os.pathsep + environment.get("PATH", "") # Делает библиотеки MinGW доступными тестовой программе.
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="can-timeout-") as directory: # Удаляет только собственные временные файлы после завершения проверки.
|
||||
|
||||
c_path, exe_path = Path(directory) / "test.c", Path(directory) / "test.exe" # Разделяет исходник модели и исполняемый результат.
|
||||
|
||||
c_path.write_text(model, encoding="utf-8") # Сохраняет русские пояснения модели в требуемой кодировке.
|
||||
|
||||
subprocess.run([compiler, "-std=c99", "-O3", "-Wall", "-Wextra", "-Wno-unused-parameter", "-Werror", str(c_path), "-o", str(exe_path)], env=environment, check=True) # Проверяет реальные функции под оптимизацией и без скрытых предупреждений.
|
||||
|
||||
subprocess.run([str(exe_path)], env=environment, check=True, timeout=10) # Зависание и нарушенное условие делают сценарий неуспешным.
|
||||
|
||||
|
||||
@@ -28,41 +28,56 @@ names = ('dsk_COMM', 'dsk_BKSD', 'dsk_BKST', 'dsk_EPLT', 'dsk_SHKF', 'dsk_LOAD',
|
||||
defines = '\n'.join(re.search(r'^#define\s+' + name + r'\s+\d+', package, re.M).group() for name in names) # Не дублирует номера плат в тестовой карте.
|
||||
measure_source = clean(measure) # Выбирает проверяемую версию Init_packMask.
|
||||
mask_function = block(measure_source, measure_source.index('void Init_packMask(')) # Состав быстрой и медленной рассылки полностью берётся из прошивки.
|
||||
ecan = clean(root / 'Source/Internal/ecan.c') # Реальный ISR завершает ту же передачу, что и новый сервис телеметрии.
|
||||
match = re.search(r'interrupt\s+void\s+CANa_reset_err\(void\)\s*\{', ecan) # Находит определение обработчика ошибок вместо его прототипа.
|
||||
assert match is not None # Исчезновение обработчика должно проваливать проверку.
|
||||
model = (root / 'tests/can_telemetry/model.c').read_text(encoding='utf-8').split('int main(void)')[0] # Повторно использует аппаратуру, ACK и журнал кадров общей модели нового передатчика.
|
||||
model = model.replace('/* TIMEOUT_ISR */', block(ecan, match.start())) # Вставляет реальное тело обработчика без отдельной копии его алгоритма.
|
||||
model += '\n' + defines + '\nstatic int Kurrent, tpl_cans; /* Настройки требуются ветвям настоящей Init_packMask. */\n' + mask_function # Берёт реальные номера плат, потоков и состав масок из текущей прошивки.
|
||||
model += r'''
|
||||
int main(void) { /* Измеряет свежесть R3 при реальной карте пульта и разных настройках потоков EEPROM. */
|
||||
const unsigned periods[][2] = {{2,101},{10,101},{12,50},{2,0}}; /* Проверяет прежние настройки, включая отключённый медленный поток. */
|
||||
unsigned scenario, tick, cursor, received, last, gap, worst; /* Наблюдает переданные кадры и разрыв между обновлениями R3. */
|
||||
for (scenario = 0; scenario < sizeof(periods)/sizeof(periods[0]); ++scenario) { /* Проверяет независимые запуски платы. */
|
||||
prepare(8U, dsk_EPLT); Init_packMask(); /* Строит настоящую карту задатчика поверх сброшенной модели CAN. */
|
||||
CHECK((Maska[m_FAST][0] & 15U) == 15U && !(Maska[m_SLOW][0] & 15U)); /* R0–R3 остаются в быстром потоке при отключённом медленном. */
|
||||
CHECK(Maska[m_FAST][1] == 1U && Maska[m_SLOW][6] == 0x1FFU && Maska[m_SLOW][7] == 0xE000U); /* Кнопки и параметры сохраняют состав исходной карты. */
|
||||
Cancount[0] = periods[scenario][0]; Cancount[1] = periods[scenario][1]; /* Ненулевое значение теперь включает обновления по изменению и таймеру. */
|
||||
cursor = received = last = worst = 0U; /* Учитывает также начальное ожидание первого подтверждения. */
|
||||
for (tick = 1U; tick <= 5000U; ++tick) { /* Пять секунд включают heartbeat и обязательные повторы неизменных данных. */
|
||||
CpuTimer1.InterruptCount = tick; modbus[3] = tick; CHECK(pump() == 0); /* Меняет R3 каждую миллисекунду и выдаёт готовую очередь через настоящие CAN_send и ACK. */
|
||||
for (; cursor < count; ++cursor) { /* Анализирует только новые кадры mailbox. */
|
||||
if (address_at(cursor) != 3U) continue; /* R3 начинает второй трёхсловный фрагмент действительной маски. */
|
||||
CHECK((frames_hi[cursor] & 0xFFFFU) == tick); /* Требует свежее значение вместо старого снимка. */
|
||||
gap = tick - last; if (gap > worst) worst = gap; last = tick; ++received; /* Измеряет худший интервал получения актуального R3. */
|
||||
} /* Завершает анализ кадров текущего момента. */
|
||||
} /* Приоритет heartbeat обрабатывается общей моделью передатчика. */
|
||||
CHECK(received > 0U && worst <= CANPOWSE && tick - 1U - last <= CANPOWSE); /* При немедленном ACK интервал ограничен шагом сканирования 20 мс независимо от slow. */
|
||||
CHECK(count_address(CAN_HEARTBEAT_ADDR) >= 4U); /* Обновление R3 не должно вытеснять секундный heartbeat. */
|
||||
printf("PASS VPU fast=%u slow=%u: R3 max=%u ms, updates=%u\n", periods[scenario][0], periods[scenario][1], worst, received); /* Выводит измеренный интервал и число свежих кадров. */
|
||||
} /* Завершает матрицу настроек EEPROM. */
|
||||
return 0; /* Подтверждает программную модель без заявления об аппаратном испытании. */
|
||||
} /* Завершает проверку обратной связи R3. */
|
||||
''' # Использует production-маски и production-сервис вместо удалённого блока if(CanGO).
|
||||
compiler = Path(os.environ.get('CC', 'C:/msys64/mingw64/bin/gcc.exe')) # Использует установленный GCC без изменения системных настроек.
|
||||
environment = dict(os.environ, PATH=str(compiler.parent) + os.pathsep + os.environ.get('PATH', '')) # Добавляет runtime только дочерним процессам.
|
||||
with tempfile.TemporaryDirectory(prefix='vpu-r3-rx-') as directory: # Отделяет временные результаты от образов пользователя.
|
||||
source, executable = Path(directory) / 'test.c', Path(directory) / 'test.exe' # Разделяет исходник и исполняемый результат.
|
||||
source.write_text(model, encoding='utf-8') # Сохраняет русские пояснения в UTF-8.
|
||||
subprocess.run([str(compiler), '-std=c99', '-O3', '-Wall', '-Wextra', '-Werror', '-I' + str(root / 'Source/Internal/Include'), str(source), '-o', str(executable)], env=environment, check=True) # Компилирует настоящий заголовок при строгих предупреждениях.
|
||||
subprocess.run([str(executable)], env=environment, check=True, timeout=10) # Нарушение задержки или зависание проваливает проверку.
|
||||
main = clean(root / 'Source/Internal/main.c') # Последовательность передачи берётся из текущего рабочего main.c.
|
||||
scheduler = block(main, main.index('if(CanGO)')) # Проверяет настоящий учёт занятого mailbox и продвижение адресов.
|
||||
base_tick = int(re.search(r'^#define\s+CANPOWSE\s+(\d+)', clean(root / 'Source/Internal/Include/measure.h'), re.M).group(1)) # При READY_FREQ=1000 Гц делитель CANPOWSE равен числу миллисекунд базового шага.
|
||||
model = r'''
|
||||
#include <stdio.h> /* Выводит измеренные моделью интервалы подтверждения. */
|
||||
#include <string.h> /* Сбрасывает состояние перед каждым сочетанием периодов EEPROM. */
|
||||
/* DEFINES */
|
||||
static int Desk, Kurrent, tpl_cans, CanGO, modbus[131], busy; /* Запас слов повторяет трёхсловное чтение у границы R127. */
|
||||
static unsigned int Maska[2][8], cancount[2], cancell[2], now, received_at, max_gap, received_count; /* Каждое состояние соответствует маскам, очередям и статистике RX. */
|
||||
#define Cancount (modbus+0x60) /* R96/R97 задают периоды fast/slow в базовых интервалах CANPOWSE. */
|
||||
#define CHECK(condition) do { if (!(condition)) { printf("FAIL line %d: %s\n", __LINE__, #condition); return 1; } } while (0) /* Ошибка остаётся видимой и при включённой оптимизации. */
|
||||
static int CAN_send(int port, int data[], int address) { /* Моделирует только принятие кадра свободным mailbox, не аппаратный ACK. */
|
||||
(void)port; /* Номер порта не влияет на порядок технологических кадров. */
|
||||
if(busy) return 0; /* Второй поток в том же тике должен повторить попытку позднее. */
|
||||
busy=1; /* Один передатчик одновременно принимает только один кадр. */
|
||||
if(address<=3 && address+2>=3) { /* R3 передаётся и отдельным кадром, и внутри трёхсловного фрагмента. */
|
||||
if(data[3] != (int)now) return 0; /* Проверяет передачу свежего слова, изменённого перед текущим шагом. */
|
||||
if(now-received_at>max_gap) max_gap=now-received_at; /* Наблюдает худшее ожидание следующего реального кадра с R3. */
|
||||
received_at=now; received_count++; /* Фиксирует только принятый передатчиком регистр. */
|
||||
} /* Завершает наблюдение очередного фрагмента. */
|
||||
return 1; /* Успешная постановка позволяет настоящему main продвинуть адрес. */
|
||||
} /* Модель намеренно не заменяет проверку физического CAN на плате. */
|
||||
/* MASK */
|
||||
static void step(void) { int i, mask; /* Локальные переменные повторяют контекст фонового цикла main.c. */
|
||||
/* SCHEDULER */
|
||||
} /* Выполняет один базовый интервал настоящего планировщика. */
|
||||
int main(void) { /* Покрывает штатные периоды 166 и варианты из EEPROM, включая отключённый медленный поток. */
|
||||
const unsigned periods[][2]={{2,101},{10,101},{12,50},{2,0}}; /* Периоды заданы в тиках CANPOWSE: 2 — штатный быстрый делитель VPU-166, 101 — медленный. */
|
||||
unsigned scenario, tick; /* Сценарии независимы, каждый охватывает сто секунд модельного времени. */
|
||||
Desk=dsk_EPLT; Init_packMask(); /* Строит реальную карту задатчика выбранной версии прошивки. */
|
||||
CHECK((Maska[m_FAST][0]&15)==15 && !(Maska[m_SLOW][0]&15)); /* R0–R3 должны подтверждаться быстрым потоком независимо от периода параметров. */
|
||||
CHECK(Maska[m_FAST][1]==1 && Maska[m_SLOW][6]==0x1FF && Maska[m_SLOW][7]==0xE000); /* Кнопки R16 и медленные параметры R96–R104/R125–R127 сохраняют состав. */
|
||||
for(scenario=0;scenario<sizeof(periods)/sizeof(periods[0]);scenario++) { /* Проверяет передачу R3 при разных сохранённых настройках платы. */
|
||||
memset(cancount,0,sizeof(cancount)); memset(cancell,0,sizeof(cancell)); /* Начинает каждый сценарий с первого адреса и нулевого срока. */
|
||||
received_at=max_gap=received_count=now=0; /* Старые измерения не сокращают наблюдаемую задержку нового сценария. */
|
||||
Cancount[0]=(int)periods[scenario][0]; Cancount[1]=(int)periods[scenario][1]; /* Использует заданные периоды без сброса EEPROM к defaults. */
|
||||
for(tick=1;tick<=5000;tick++) { /* Пять тысяч шагов по 20 мс включают много полных обходов медленной очереди. */
|
||||
now=tick; modbus[3]=(int)tick; busy=0; CanGO=1; step(); /* Меняет R3 перед каждым шагом и завершает предыдущую аппаратную передачу. */
|
||||
} /* В статистику входят начальное ожидание и все последующие интервалы. */
|
||||
CHECK(received_count>0 && max_gap<=3*(periods[scenario][0]+1)); /* Три быстрых кадра R0–R2/R3–R5/R16–R18 ограничивают ожидание независимо от slow. */
|
||||
printf("PASS fast=%u slow=%u: R3 max=%u ms, updates=%u\n",periods[scenario][0],periods[scenario][1],max_gap*BASE_TICK,received_count); /* Переводит тики в миллисекунды базового таймера платы. */
|
||||
} /* Все сочетания периодов обязаны давать частое подтверждение R3. */
|
||||
return 0; /* Успех означает прохождение модели, а не испытание физического устройства. */
|
||||
} /* Завершает регрессию задержки обратной связи VPU. */
|
||||
''' # Вставки ниже исполняют текущий код прошивки, а не его переписанную модель.
|
||||
model = model.replace('/* DEFINES */', defines + f'\n#define BASE_TICK {base_tick} /* Длительность базового шага в миллисекундах при таймере 1000 Гц. */').replace('/* MASK */', mask_function).replace('/* SCHEDULER */', scheduler) # Связывает реальные маски и планировщик с моделью одного занятого передатчика.
|
||||
compiler = Path(os.environ.get('CC', 'C:/msys64/mingw64/bin/gcc.exe')) # Использует установленный GCC без загрузки инструментов.
|
||||
environment = dict(os.environ, PATH=str(compiler.parent) + os.pathsep + os.environ.get('PATH', '')) # Делает библиотеки компилятора доступными только дочерним программам.
|
||||
with tempfile.TemporaryDirectory(prefix='vpu-r3-rx-') as directory: # Удаляет только собственные временные результаты после проверки.
|
||||
source, executable = Path(directory) / 'test.c', Path(directory) / 'test.exe' # Разделяет C-код проверки и исполняемую программу.
|
||||
source.write_text(model, encoding='utf-8') # Все создаваемые исходники и русские комментарии сохраняются в UTF-8.
|
||||
subprocess.run([str(compiler), '-std=c99', '-O3', '-Wall', '-Wextra', '-Werror', str(source), '-o', str(executable)], env=environment, check=True) # Требует чистой компиляции настоящих алгоритмов при оптимизации.
|
||||
subprocess.run([str(executable)], env=environment, check=True, timeout=10) # Зависание либо нарушение ограничения задержки проваливает регрессию.
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
/* Проверяет удержание TESTLAMP и выбор пакетов при уже включённых рабочих лампах. */
|
||||
#include <stdio.h> /* Выводит конкретные нарушенные условия и итог сценария. */
|
||||
#include <string.h> /* Обнуляет регистровое пространство перед началом проверки. */
|
||||
#define interrupt /* Убирает аппаратный атрибут ISR при компиляции модели на ПК. */
|
||||
typedef float FILTERBAT; /* Позволяет подключить production-заголовок с интерфейсами фильтров. */
|
||||
#include "measure.h" /* Получает настоящие READY_FREQ, BLINK_TIME, CANPOWSE и maximum_bright. */
|
||||
#include "package.h" /* Получает настоящие адреса TESTLAMP, яркости, ламп и типов плат. */
|
||||
#include "DSP2833x_SWPrioritizedIsrLevels.h" /* Использует реальную вычисляемую маску приоритетов TI MINT13. */
|
||||
typedef union { /* Моделирует слово Modbus размером хостового int, сохраняя номера битов production-псевдонимов. */
|
||||
unsigned int all; /* Обеспечивает запись целого слова по тому же смещению массива. */
|
||||
struct { /* Размещает шестнадцать технологических битов в младшей части слова. */
|
||||
unsigned int bit0:1, bit1:1, bit2:1, bit3:1, bit4:1, bit5:1, bit6:1, bit7:1; /* Сохраняет позиции кнопок и команд 0..7. */
|
||||
unsigned int bit8:1, bit9:1, bitA:1, bitB:1, bitC:1, bitD:1, bitE:1, bitF:1; /* Сохраняет позиции повторителей входов и старших команд 8..15. */
|
||||
} bit; /* Предоставляет production-псевдонимам доступ к полям регистра. */
|
||||
} WORDE; /* Завершает хостовое представление одного регистра Modbus. */
|
||||
struct gpio_bits { /* Предоставляет входы всех ветвей настоящей get_Buttons, без моделирования электрических свойств. */
|
||||
unsigned GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO8, GPIO9, GPIO10, GPIO11; /* Представляет дискретные входы нескольких типов плат. */
|
||||
unsigned GPIO20, GPIO21, GPIO22, GPIO23, GPIO24, GPIO25, GPIO26, GPIO27; /* Представляет перемычки и вход TESTLAMP GPIO26 пульта. */
|
||||
unsigned GPIO48, GPIO49, GPIO50, GPIO51, GPIO52, GPIO53; /* Представляет входы банка B, читаемые get_Buttons. */
|
||||
}; /* Завершает перечень моделируемых входов. */
|
||||
struct gpio_reg { struct gpio_bits bit; }; /* Даёт регистрам GPADAT и GPBDAT production-форму обращения. */
|
||||
static struct { struct gpio_reg GPADAT, GPBDAT; } GpioDataRegs; /* Хранит управляемые сценарием уровни входов. */
|
||||
static struct { unsigned long all; } DigErr; /* Принимает диагностическую маску посторонней ветви SHKF. */
|
||||
static struct { unsigned long InterruptCount; } CpuTimer1; /* Учитывает вызовы настоящего обработчика пульта. */
|
||||
int modbus[129], Desk; /* Предоставляет технологические регистры и выбирает ветвь пульта. */
|
||||
long INcount0; /* Повторяет тип накопителя в peripher.c проекта 166 и параметр long * функции er_anal, чтобы модель проверяла настоящий фильтр без несовместимых указателей. */
|
||||
unsigned int CanPowse, CanGO; /* Хранит счётчик тактов выдачи CAN и признак готовности очередной передачи. */
|
||||
unsigned long isMask, Lonely; /* Хранит тестовую маску и возраст последней связи. */
|
||||
int isNumb, quaLamp = 6; /* Сохраняет тестовые цифры и число ламп из pulto.c. */
|
||||
static unsigned int IER, mask_at_eint, entry_mask, failures, sends, nested_entries, timer_pending; /* Отслеживает разрешения прерываний, пакетные вызовы и возможность вложенного Timer1. */
|
||||
static unsigned long lamp_word; /* Запоминает последний пакет шести сигнальных ламп канала 2. */
|
||||
static long digit_word; /* Сохраняет последнее значение одного из двух семисегментных индикаторов в текущем такте. */
|
||||
static int digit_dot, digit_sends; /* Запоминает режим форматирования цифр и наличие числового пакета в текущем такте. */
|
||||
#define M_INT13 0x1000U /* Повторяет бит 12 INT13 из DSP2833x_Device.h для модели регистра IER. */
|
||||
#define EALLOW ((void)0) /* Не моделирует защиту аппаратных регистров, поскольку она не влияет на рассматриваемые ветви. */
|
||||
#define EDIS ((void)0) /* Завершает заглушку защищённой записи. */
|
||||
#define EINT (mask_at_eint = IER) /* Снимает реально применённую маску в момент разрешения вложенных прерываний. */
|
||||
#define ServiceDog() ((void)0) /* Исключает аппаратный сторожевой таймер из проверки индикации. */
|
||||
#define toggle_ONLINE() ((void)0) /* Не моделирует электрический сигнал готовности ПЛИС. */
|
||||
#define toggle_RES_OUT_1() ((void)0) /* Исключает резервный GPIO из логики выбора TESTLAMP. */
|
||||
#define toggle_RES_OUT_2() ((void)0) /* Исключает второй резервный GPIO из пакетной проверки. */
|
||||
void cpu_timer1_isr_PULT(void); /* Позволяет заглушке передачи смоделировать повторное аппаратное событие Timer1. */
|
||||
static void kanal_Send(int adr, long dat, int dot) /* Записывает передаваемые данные, не подменяя проверяемый выбор режима. */
|
||||
{ /* Начинает обработку одного production-вызова передачи. */
|
||||
sends++; /* Позволяет обнаружить дополнительные вызовы передачи при повторном входе в ISR. */
|
||||
if (timer_pending && (IER & M_INT13)) { /* Моделирует новый такт Timer1 во время незаконченной передачи, только когда собственное прерывание ошибочно разрешено. */
|
||||
timer_pending = 0; /* Ограничивает искусственное событие одним вложенным вызовом. */
|
||||
nested_entries++; /* Регистрирует перемешивание обработки двух тактов. */
|
||||
cpu_timer1_isr_PULT(); /* Запускает настоящую production-функцию повторно при неверной маске. */
|
||||
} /* Завершает моделирование потенциального повторного входа. */
|
||||
if (adr == 2) lamp_word = (unsigned long)dat; /* Сохраняет именно канал сигнальных ламп, обновляемый в каждом такте. */
|
||||
if (adr < 2) { /* Записывает также передачу цифр, чтобы обнаружить переход семисегментного индикатора в рабочий режим при провале кнопки. */
|
||||
digit_word = dat; /* Сохраняет переданное production-функцией числовое значение. */
|
||||
digit_dot = dot; /* Различает тест четырёх одинаковых цифр и обычное форматирование измерений. */
|
||||
digit_sends++; /* Отмечает присутствие числового пакета в текущем такте. */
|
||||
} /* Завершает фиксацию передачи на семисегментный индикатор. */
|
||||
} /* Завершает заглушку аппаратной передачи. */
|
||||
/* HANDLERS */
|
||||
static void check(int condition, const char *name) /* Накапливает ошибки, позволяя проверить все последствия одного дефекта. */
|
||||
{ /* Начинает регистрацию одного требования. */
|
||||
if (!condition) { /* Выводит только нарушенные условия. */
|
||||
printf("FAIL %s\n", name); /* Даёт мутационному тесту устойчивое имя обнаруженного дефекта. */
|
||||
failures++; /* Делает код завершения ненулевым при любой регрессии. */
|
||||
} /* Завершает обработку ошибки. */
|
||||
} /* Завершает проверку одного требования. */
|
||||
static void tick(void) /* Моделирует сохранение и восстановление IER на границе аппаратного ISR. */
|
||||
{ /* Начинает один такт индикации. */
|
||||
IER = entry_mask; /* Возвращает исходные разрешения источников перед новым обработчиком. */
|
||||
sends = 0; /* Отделяет вызовы передачи текущего такта от предыдущих. */
|
||||
digit_sends = 0; /* Не принимает пакет предыдущего такта за обновление цифр в текущем. */
|
||||
cpu_timer1_isr_PULT(); /* Выполняет настоящий алгоритм выбора режима, PWM и пакетов. */
|
||||
check(mask_at_eint == (entry_mask & MINT13 & ~M_INT13), "interrupt mask"); /* Проверяет отсутствие собственного Timer1 и запрет включения ранее выключенных источников. */
|
||||
check(sends >= 1 && sends <= 2, "packet count"); /* Требует штатный объём одного такта: пакет ламп и не более одного пакета очередного индикатора. */
|
||||
} /* Завершает один такт индикации. */
|
||||
int main(void) /* Проверяет двухсекундное удержание по Timer1 и защиту стробов DATA/CLOCK от повторного входа. */
|
||||
{ /* Начинает независимые сценарии физической кнопки, удалённой команды и яркости. */
|
||||
int i, pwm_on = 0; /* Считает такты удержания и включённые фазы десятишагового PWM. */
|
||||
memset(modbus, 0, sizeof(modbus)); /* Очищает команды и входы перед запуском production-функций. */
|
||||
check(sizeof(WORDE) == sizeof(modbus[0]), "register layout"); /* Подтверждает совместимость битовых псевдонимов с массивом хостовой модели. */
|
||||
Desk = dsk_EPLT; modbus[2] = 1; Bright[0] = maximum_bright; /* Выбирает пульт и заранее включает рабочую лампу с полной яркостью. */
|
||||
entry_mask = M_INT13 | (1U << 0) | (1U << 13); /* Проверяет Timer1 вместе с ранее разрешёнными источниками разных приоритетов. */
|
||||
tick(); check(lamp_word == 1, "normal lamp before test"); /* Проверяет рабочую индикацию до начала удержания. */
|
||||
GpioDataRegs.GPADAT.bit.GPIO26 = 1; /* Нажатие TESTLAMP представлено высоким уровнем входа платы. */
|
||||
for (i = 0; i < 4000; i++) get_Buttons(); /* Частый main не должен ускорять выдержку, которую теперь считает только Timer1. */
|
||||
check(!bTestLamp, "polling independent"); /* Доказывает независимость порога от числа опросов главного цикла. */
|
||||
for (i = 0; i < 2 * READY_FREQ - 1; i++) { tick(); check(!bTestLamp, "short test"); } /* Даже 1999 тактов при 1000 Гц ещё не разрешают местный тест. */
|
||||
tick(); check(bTestLamp, "test activation"); /* Двухтысячный такт включает тест точно после двух секунд удержания. */
|
||||
for (i = 0; i < 600; i++) { /* Проверяет непрерывный тест дольше периода BLINK_TIME=500 без потери защиты стробов. */
|
||||
timer_pending = 1; tick(); /* Имитирует новый такт во время передачи, когда прежний ISR ещё не завершился. */
|
||||
check(bTestLamp && lamp_word == isMask, "held test"); /* Требует передачу тестовой фазы вместо исходной рабочей маски. */
|
||||
check(!digit_sends || (digit_word == isNumb && digit_dot == 4), "test digit packet"); /* Проверяет тестовый формат числового индикатора. */
|
||||
check(nested_entries == 0, "nested transmission"); timer_pending = 0; /* Единственный владелец DATA/CLOCK исключает перемешивание пакетов. */
|
||||
} /* Завершает длительное удержание физической кнопки. */
|
||||
GpioDataRegs.GPADAT.bit.GPIO26 = 0; tick(); /* Отпускание сбрасывает местный тест в следующем такте Timer1. */
|
||||
check(!bTestLamp && lamp_word == 1, "release"); /* Возвращает заранее включённую рабочую лампу без дополнительной задержки. */
|
||||
for (i = 0; i < 3; i++) { /* Отдельные короткие нажатия суммарно превышают две секунды, но не должны включать тест. */
|
||||
int held; GpioDataRegs.GPADAT.bit.GPIO26 = 1; /* Начинает очередное независимое нажатие. */
|
||||
for (held = 0; held < READY_FREQ; held++) tick(); /* Удерживает вход одну секунду, то есть меньше порога. */
|
||||
check(!bTestLamp, "separate presses"); /* Не позволяет сложить длительности разных нажатий. */
|
||||
GpioDataRegs.GPADAT.bit.GPIO26 = 0; tick(); /* Сбрасывает накопленную выдержку отпусканием. */
|
||||
} /* Завершает сценарий повторных коротких нажатий. */
|
||||
cTestLamp = 1; tick(); check(lamp_word == isMask, "remote test"); /* Команда Modbus сохраняет немедленный независимый запуск теста. */
|
||||
cTestLamp = 0; Bright[0] = 6; /* Возвращает рабочий режим с яркостью шесть тактов из десяти. */
|
||||
for (i = 0; i < 10; i++) { tick(); check(lamp_word <= 1, "normal PWM packet"); pwm_on += lamp_word == 1; } /* Измеряет полный период реального алгоритма яркости после теста. */
|
||||
check(pwm_on == 6, "normal PWM duty"); /* Подтверждает сохранение настроенной скважности рабочего сигнала. */
|
||||
printf("%s TESTLAMP: 2-second hold, main independent, single ISR, release, remote command, PWM\n", failures ? "FAIL" : "PASS"); /* Указывает проверенные программные свойства без заявления об аппаратном испытании. */
|
||||
return failures ? 1 : 0; /* Любое нарушенное условие завершает процесс проверки с ошибкой. */
|
||||
} /* Завершает проверку кнопки и защищённой передачи индикации. */
|
||||
@@ -1,51 +0,0 @@
|
||||
"""Проверяет TESTLAMP на настоящих функциях прошивки без подключения платы."""
|
||||
import argparse # Позволяет использовать доступный GCC для модели аппаратного окружения.
|
||||
import os # Передаёт компилятору PATH с его вспомогательными программами и библиотеками.
|
||||
from pathlib import Path # Разрешает исходники и модель относительно каталога этого теста.
|
||||
import re # Извлекает функции, убирая комментарии, которые могут содержать примеры скобок.
|
||||
import subprocess # Компилирует и запускает независимые проверки с контролем результата.
|
||||
import tempfile # Удаляет только временные исходники и исполняемые файлы данного запуска.
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2] # Получает корень репозитория из расположения tests/testlamp.
|
||||
|
||||
def source(name): # Читает production-модуль без изменения исходного файла.
|
||||
data = (ROOT / "Source/Internal" / name).read_bytes() # Читает исходник без изменения его кодировки: в 166 ещё есть модули Windows-1251.
|
||||
try: # Сначала использует UTF-8 для перенесённых модулей индикации.
|
||||
text = data.decode("utf-8") # Строго декодирует обновлённые исходники без потери русских комментариев.
|
||||
except UnicodeDecodeError: # Поддерживает старый measure.c, не изменяя его ради хостового теста.
|
||||
text = data.decode("cp1251") # Читает исходную Windows-1251 только при невозможности декодирования UTF-8.
|
||||
return re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S) # Убирает комментарии перед разбором вложенных блоков.
|
||||
|
||||
def function(text, name): # Выбирает определение функции вместо копии её алгоритма в тесте.
|
||||
match = re.search(r"(?:interrupt\s+)?(?:void|int)\s+" + name + r"\([^;{}]*\)\s*\{", text) # Отличает тело функции от прототипов и вызовов.
|
||||
assert match is not None, name # Завершает проверку, если проверяемая production-функция исчезла.
|
||||
end, depth = match.end(), 1 # Учитывает найденную открывающую скобку внешнего блока.
|
||||
while depth: # Читает все вложенные ветви до конца определения.
|
||||
depth += (text[end] == "{") - (text[end] == "}") # Находит парную закрывающую скобку.
|
||||
end += 1 # Включает текущий символ в извлекаемый фрагмент.
|
||||
return text[match.start():end] # Сохраняет порядок операторов настоящей функции.
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__) # Объясняет назначение отдельного запуска проверки.
|
||||
parser.add_argument("--cc", default="C:/msys64/mingw64/bin/gcc.exe", help="Путь к GCC") # Повторяет установленный в этом проекте хостовый компилятор.
|
||||
args = parser.parse_args() # Разбирает параметры до формирования временных файлов.
|
||||
button = function(source("peripher.c"), "get_Buttons") # Проверяет именно публикацию отфильтрованной кнопки в Modbus.
|
||||
pult = source("pulto.c") # Берёт текущую обработку индикации, включая реальные ветви теста и яркости.
|
||||
body = "\n".join((function(source("measure.c"), "er_anal"), button, function(pult, "what_is"), function(pult, "cpu_timer1_isr_PULT"))) # Собирает функции, участвующие в переключении режима и передаче масок.
|
||||
before, after = Path(__file__).with_name("model.inc").read_text(encoding="utf-8").split("/* HANDLERS */") # Помещает production-функции между аппаратными заглушками и поведенческими сценариями.
|
||||
old_button, count_button = re.subn(r"test_lamp_hold_ticks >= 2U \* \(unsigned int\)READY_FREQ", "test_lamp_hold_ticks >= 1U", body) # Мутация включает TESTLAMP после одного такта вместо двух секунд и должна нарушить сценарий короткого нажатия.
|
||||
old_mask, count_mask = re.subn(r"IER\s*&=\s*\(MINT13\s*&\s*~M_INT13\);", "IER |= MINT13;", body) # Восстанавливает ошибочное разрешение прерываний в отдельном временном варианте.
|
||||
assert count_button == count_mask == 1, "Не найдены единственные места исправлений TESTLAMP" # Не позволяет мутации незаметно проверять неизменённый код.
|
||||
environment = dict(os.environ) # Сохраняет окружение основного процесса.
|
||||
environment["PATH"] = str(Path(args.cc).resolve().parent) + os.pathsep + environment.get("PATH", "") # Делает DLL и вспомогательные программы GCC доступными дочерним процессам.
|
||||
with tempfile.TemporaryDirectory(prefix="testlamp-") as directory: # Изолирует артефакты каждой проверки от дерева прошивки.
|
||||
for name, functions, failure in (("production", body, None), ("old_button", old_button, "FAIL short test"), ("old_mask", old_mask, "FAIL interrupt mask")): # Проверяет выдержку кнопки и чувствительность к повторному разрешению Timer1 во время передачи индикации.
|
||||
c_path, exe_path = Path(directory) / (name + ".c"), Path(directory) / (name + ".exe") # Разделяет временные исходники и программы трёх вариантов.
|
||||
c_path.write_text(before + functions + after, encoding="utf-8") # Сохраняет модель и русские пояснения в UTF-8.
|
||||
subprocess.run([args.cc, "-std=c99", "-O3", "-fno-strict-aliasing", "-Werror=implicit-function-declaration", "-I", str(ROOT / "Source/Internal/Include"), "-I", str(ROOT / "Source/External/v120/DSP2833x_common/include"), str(c_path), "-o", str(exe_path)], env=environment, check=True) # Сохраняет адреса production-псевдонимов регистров и использует реальную таблицу приоритетов TI.
|
||||
result = subprocess.run([str(exe_path)], env=environment, capture_output=True, text=True, timeout=20) # Ограничивает зависание и сохраняет конкретные нарушенные условия.
|
||||
if failure is None: # Рабочая версия обязана пройти все сценарии.
|
||||
print(result.stdout, end="") # Показывает результат поведенческих проверок текущей прошивки.
|
||||
assert result.returncode == 0, result.stdout + result.stderr # Возвращает ошибку при любой регрессии TESTLAMP или рабочего режима.
|
||||
else: # Старая ошибка должна воспроизводимо нарушить соответствующее требование.
|
||||
assert result.returncode == 1 and failure in result.stdout, result.stdout + result.stderr # Доказывает, что тест действительно обнаруживает возвращённый дефект.
|
||||
print("PASS mutation:", name, "rejected") # Сообщает об ожидаемом провале временного варианта со старой ошибкой.
|
||||
Reference in New Issue
Block a user