mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
fix(serial): контракт SerValid — платформенный, ноль значит разное
Ноль на Unix и на Windows означает не одно и то же, и одной проверкой тут не обойтись. На Unix это законный дескриптор (при закрытом стандартном вводе fpOpen отдаёт именно его), а ядро Windows нулевой хендл не выдаёт никогда — там ноль это либо отказ RTL, либо неинициализированное поле. Внутренний путь был безопасен и раньше (SerOpen переводит ноль RTL в SER_INVALID_HANDLE, свои поля инициализируются им же), но публичный контракт для значения, пришедшего извне, оказывался неверным. Теперь проверка разведена по платформам. ★Стенд научился ПРОГОНЯТЬ windows-ветку, а не только компилировать её. Заглушка модуля Serial — чистый Паскаль, поэтому ветку можно собрать и запустить прямо на Linux: test/platform/win_probe проверяет перевод нулевого хендла в SER_INVALID_HANDLE, ответ SerValid и правило имени COM10+. До сих пор у windows-пути обёртки не было никакого поведенческого покрытия вовсе. Негативный контроль: если убрать windows-ветку из SerValid, прогон падает на «★нулевой хендл под Windows негоден». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bkwwyj7xVRrqnSVEseTRfV
This commit is contained in:
+12
-6
@@ -424,14 +424,20 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
function SerValid(Handle: TSerialHandle): Boolean;
|
function SerValid(Handle: TSerialHandle): Boolean;
|
||||||
|
// ★Ноль значит РАЗНОЕ на разных системах, и одной проверкой тут не обойтись.
|
||||||
begin
|
begin
|
||||||
// ★Ноль — ЗАКОННЫЙ дескриптор на Unix: если стандартный ввод закрыт (демон,
|
{$IFDEF WINDOWS}
|
||||||
// запуск из службы), fpOpen отдаст именно 0, и объявлять такой порт ошибкой
|
// Ядро Windows нулевой хендл не выдаёт никогда: ноль здесь — либо отказ RTL
|
||||||
// нельзя — он не только не заработает, но и не закроется, потому что
|
// (SerOpen переводит его в SER_INVALID_HANDLE), либо просто неинициализированное
|
||||||
// SerClose тоже смотрит сюда. Признак неудачи ровно один и на всех
|
// поле. Отвергаем, чтобы контракт был верен и для значения, пришедшего извне.
|
||||||
// платформах: SER_INVALID_HANDLE. Ноль, который RTL под Windows отдаёт при
|
Result := (Handle <> SER_INVALID_HANDLE) and (Handle <> 0);
|
||||||
// отказе, переводится в него внутри SerOpen.
|
{$ELSE}
|
||||||
|
// А на Unix ноль ЗАКОНЕН: при закрытом стандартном вводе (демон, запуск из
|
||||||
|
// службы) fpOpen отдаст именно его. Объявить такой порт ошибкой значит не
|
||||||
|
// только потерять рабочий порт, но и не закрыть дескриптор — SerClose
|
||||||
|
// смотрит сюда же. Признак неудачи один: SER_INVALID_HANDLE.
|
||||||
Result := Handle <> SER_INVALID_HANDLE;
|
Result := Handle <> SER_INVALID_HANDLE;
|
||||||
|
{$ENDIF}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function SerDefaultPortName(Index: Integer): string;
|
function SerDefaultPortName(Index: Integer): string;
|
||||||
|
|||||||
@@ -62,6 +62,22 @@ sim_build() { # $1 = win|mac
|
|||||||
sim_build win
|
sim_build win
|
||||||
sim_build mac
|
sim_build mac
|
||||||
|
|
||||||
|
# ── Прогон WINDOWS-ветки, а не только её компиляция ──────────────────────────
|
||||||
|
# ★Заглушка модуля Serial — чистый Паскаль, поэтому ветку можно не только
|
||||||
|
# собрать, но и ЗАПУСТИТЬ: проверяются перевод нулевого хендла RTL в
|
||||||
|
# SER_INVALID_HANDLE, ответ SerValid (под Windows ноль негоден — в отличие от
|
||||||
|
# Unix) и правило имени COM10+.
|
||||||
|
cp win_probe/winprobe.pas "$WORK/win/"
|
||||||
|
COUNT=$((COUNT + 1))
|
||||||
|
if ( cd "$WORK/win" && fpc -B -Mobjfpc -dHEADLESS -dSIMWIN -Fu. -FU. \
|
||||||
|
-o./winprobe winprobe.pas >"$WORK/win/probe.log" 2>&1 && ./winprobe ); then
|
||||||
|
echo " ok SerialPort.pas: ветка win ведёт себя как надо (прогон на заглушке)"
|
||||||
|
else
|
||||||
|
echo " FAIL SerialPort.pas: прогон windows-ветки провалился"
|
||||||
|
cat "$WORK/win/probe.log"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Итого: $COUNT проверок, провалено $FAILED"
|
echo "Итого: $COUNT проверок, провалено $FAILED"
|
||||||
[ "$FAILED" -eq 0 ]
|
[ "$FAILED" -eq 0 ]
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
program winprobe;
|
||||||
|
|
||||||
|
{
|
||||||
|
Прогон WINDOWS-ветки SerialPort.pas на Linux.
|
||||||
|
|
||||||
|
★Компиляции мало. Соседняя проверка (-Cn) ловит синтаксис и разрешение имён,
|
||||||
|
но не поведение, а под Windows у обёртки своя логика: перевод нулевого хендла
|
||||||
|
RTL в SER_INVALID_HANDLE и свой ответ SerValid (ядро Windows нулевой хендл не
|
||||||
|
выдаёт никогда, значит ноль там негоден — в отличие от Unix, где это законный
|
||||||
|
дескриптор при закрытом стандартном вводе). Всё это чистая логика поверх
|
||||||
|
заглушки модуля Serial, поэтому её можно и нужно прогнать здесь.
|
||||||
|
}
|
||||||
|
|
||||||
|
{$MODE Delphi}
|
||||||
|
|
||||||
|
uses
|
||||||
|
SysUtils, SerialPort;
|
||||||
|
|
||||||
|
var
|
||||||
|
Passed, Failed: Integer;
|
||||||
|
|
||||||
|
procedure Check(const Name: string; Cond: Boolean; const Extra: string = '');
|
||||||
|
begin
|
||||||
|
if Cond then
|
||||||
|
begin
|
||||||
|
Inc(Passed);
|
||||||
|
WriteLn(' ok ', Name);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Inc(Failed);
|
||||||
|
if Extra <> '' then WriteLn(' FAIL ', Name, ' ', Extra)
|
||||||
|
else WriteLn(' FAIL ', Name);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Passed := 0;
|
||||||
|
Failed := 0;
|
||||||
|
WriteLn(' -- прогон WINDOWS-ветки SerialPort на заглушке');
|
||||||
|
|
||||||
|
// Заглушка Serial.SerOpen всегда отдаёт ноль — то есть «не открылось» по
|
||||||
|
// соглашению RTL под Windows. Обёртка обязана перевести это в свой признак.
|
||||||
|
Check('★отказ RTL (ноль) переводится в SER_INVALID_HANDLE',
|
||||||
|
SerOpen('COM1') = SER_INVALID_HANDLE, IntToStr(SerOpen('COM1')));
|
||||||
|
Check('★нулевой хендл под Windows негоден', not SerValid(0));
|
||||||
|
Check('SER_INVALID_HANDLE негоден', not SerValid(SER_INVALID_HANDLE));
|
||||||
|
Check('обычный хендл годен', SerValid(5));
|
||||||
|
|
||||||
|
Check('COM1 остаётся коротким', SerWinDeviceName('COM1') = 'COM1');
|
||||||
|
Check('COM10 получает префикс', SerWinDeviceName('COM10') = '\\.\COM10');
|
||||||
|
|
||||||
|
WriteLn(Format(' -- ветка windows: %d проверок, провалено %d',
|
||||||
|
[Passed + Failed, Failed]));
|
||||||
|
if Failed > 0 then Halt(1);
|
||||||
|
end.
|
||||||
@@ -47,5 +47,7 @@ test/serial/run.sh
|
|||||||
псевдотерминале не работает. Это остаётся на живой порт с железкой — там же
|
псевдотерминале не работает. Это остаётся на живой порт с железкой — там же
|
||||||
проверяется и телеграфный ключ на `CWKeyer`.
|
проверяется и телеграфный ключ на `CWKeyer`.
|
||||||
|
|
||||||
Компиляцию платформенных ветвей (Windows, macOS) проверяет соседний стенд
|
Платформенные ветви (Windows, macOS) проверяет соседний стенд `test/platform`:
|
||||||
`test/platform`.
|
компиляцию обеих, а windows-ветку ещё и прогоном на заглушке модуля `Serial` —
|
||||||
|
перевод нулевого хендла RTL в `SER_INVALID_HANDLE`, ответ `SerValid` (там ноль
|
||||||
|
негоден, в отличие от Unix) и правило имени `COM10+`.
|
||||||
|
|||||||
Reference in New Issue
Block a user