From f88b1615e794cb56639d6956108409b712898805 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 24 Aug 2026 22:43:14 +0300 Subject: [PATCH] =?UTF-8?q?fix(serial):=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=20SerValid=20=E2=80=94=20=D0=BF=D0=BB=D0=B0?= =?UTF-8?q?=D1=82=D1=84=D0=BE=D1=80=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9,?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=BB=D1=8C=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B8?= =?UTF-8?q?=D1=82=20=D1=80=D0=B0=D0=B7=D0=BD=D0=BE=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ноль на 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 Claude-Session: https://claude.ai/code/session_01Bkwwyj7xVRrqnSVEseTRfV --- SerialPort.pas | 18 ++++++--- test/platform/run.sh | 16 ++++++++ test/platform/win_probe/winprobe.pas | 56 ++++++++++++++++++++++++++++ test/serial/README.md | 6 ++- 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 test/platform/win_probe/winprobe.pas diff --git a/SerialPort.pas b/SerialPort.pas index 3279251..7368415 100644 --- a/SerialPort.pas +++ b/SerialPort.pas @@ -424,14 +424,20 @@ begin end; function SerValid(Handle: TSerialHandle): Boolean; +// ★Ноль значит РАЗНОЕ на разных системах, и одной проверкой тут не обойтись. begin - // ★Ноль — ЗАКОННЫЙ дескриптор на Unix: если стандартный ввод закрыт (демон, - // запуск из службы), fpOpen отдаст именно 0, и объявлять такой порт ошибкой - // нельзя — он не только не заработает, но и не закроется, потому что - // SerClose тоже смотрит сюда. Признак неудачи ровно один и на всех - // платформах: SER_INVALID_HANDLE. Ноль, который RTL под Windows отдаёт при - // отказе, переводится в него внутри SerOpen. +{$IFDEF WINDOWS} + // Ядро Windows нулевой хендл не выдаёт никогда: ноль здесь — либо отказ RTL + // (SerOpen переводит его в SER_INVALID_HANDLE), либо просто неинициализированное + // поле. Отвергаем, чтобы контракт был верен и для значения, пришедшего извне. + Result := (Handle <> SER_INVALID_HANDLE) and (Handle <> 0); +{$ELSE} + // А на Unix ноль ЗАКОНЕН: при закрытом стандартном вводе (демон, запуск из + // службы) fpOpen отдаст именно его. Объявить такой порт ошибкой значит не + // только потерять рабочий порт, но и не закрыть дескриптор — SerClose + // смотрит сюда же. Признак неудачи один: SER_INVALID_HANDLE. Result := Handle <> SER_INVALID_HANDLE; +{$ENDIF} end; function SerDefaultPortName(Index: Integer): string; diff --git a/test/platform/run.sh b/test/platform/run.sh index fdde5ed..e34d79e 100755 --- a/test/platform/run.sh +++ b/test/platform/run.sh @@ -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 ] diff --git a/test/platform/win_probe/winprobe.pas b/test/platform/win_probe/winprobe.pas new file mode 100644 index 0000000..64ca3a2 --- /dev/null +++ b/test/platform/win_probe/winprobe.pas @@ -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. diff --git a/test/serial/README.md b/test/serial/README.md index b4ac347..c124c31 100644 --- a/test/serial/README.md +++ b/test/serial/README.md @@ -47,5 +47,7 @@ test/serial/run.sh псевдотерминале не работает. Это остаётся на живой порт с железкой — там же проверяется и телеграфный ключ на `CWKeyer`. -Компиляцию платформенных ветвей (Windows, macOS) проверяет соседний стенд -`test/platform`. +Платформенные ветви (Windows, macOS) проверяет соседний стенд `test/platform`: +компиляцию обеих, а windows-ветку ещё и прогоном на заглушке модуля `Serial` — +перевод нулевого хендла RTL в `SER_INVALID_HANDLE`, ответ `SerValid` (там ноль +негоден, в отличие от Unix) и правило имени `COM10+`.