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:
2026-08-24 22:43:14 +03:00
co-authored by Claude Opus 5
parent 03afaf3083
commit f88b1615e7
4 changed files with 88 additions and 8 deletions
+16
View File
@@ -62,6 +62,22 @@ sim_build() { # $1 = win|mac
sim_build win
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 "Итого: $COUNT проверок, провалено $FAILED"
[ "$FAILED" -eq 0 ]
+56
View File
@@ -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.