补充原生控制台回归并保存并发修复前检查点

This commit is contained in:
xuans 2026-09-21 01:15:51 +08:00
parent 325928ee1f
commit e08a4a47d7

View File

@ -0,0 +1,148 @@
//go:build windows
package adminconsole
import (
"context"
"errors"
"os"
"os/exec"
"strings"
"syscall"
"testing"
"time"
"unicode/utf16"
"unsafe"
"golang.org/x/sys/windows"
)
// 这是项目原生平台回归测试,不替代提升权限管道和生产交互验收。
// 子进程使用隐藏的独立控制台,避免读取或修改开发者正在使用的终端。
func TestWindowsTerminalNative(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
command := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestWindowsTerminalNativeChild$", "-test.v", "-test.timeout=15s")
command.Env = append(os.Environ(), "SCERT_TEST_NATIVE_CONSOLE=1")
command.SysProcAttr = &syscall.SysProcAttr{CreationFlags: windows.CREATE_NEW_CONSOLE, HideWindow: true}
output, err := command.CombinedOutput()
if err != nil {
t.Fatalf("原生控制台回归失败:%v\n%s", err, output)
}
if !strings.Contains(string(output), "PASS: TestWindowsTerminalNativeChild") {
t.Fatalf("原生控制台子测试没有运行:%s", output)
}
}
func TestWindowsTerminalNativeChild(t *testing.T) {
if os.Getenv("SCERT_TEST_NATIVE_CONSOLE") != "1" {
t.Skip("仅由独立隐藏控制台中的子进程执行")
}
input, err := os.OpenFile("CONIN$", os.O_RDWR, 0)
if err != nil {
t.Fatal(err)
}
defer input.Close()
handle := windows.Handle(input.Fd())
var original uint32
if err := windows.GetConsoleMode(handle, &original); err != nil {
t.Fatal(err)
}
if err := readConsoleInput.Find(); err != nil {
t.Fatalf("系统缺少所需的非阻塞控制台接口:%v", err)
}
flush := windows.NewLazySystemDLL("kernel32.dll").NewProc("FlushConsoleInputBuffer")
write := windows.NewLazySystemDLL("kernel32.dll").NewProc("WriteConsoleInputW")
screen := &windowsTerminal{input: handle, output: os.Stdout}
cases := []struct {
name string
keys string
want string
hidden bool
cancel bool
deadline bool
wantErr error
}{
{name: "visible", keys: "1\r", want: "1"},
{name: "secret", keys: "Native123\r", want: "Native123", hidden: true},
{name: "backspace", keys: "abc\bD\r", want: "abD", hidden: true},
{name: "surrogate", keys: "a\U0001F600\bb\r", want: "ab", hidden: true},
{name: "escape", keys: "\x1b", hidden: true, wantErr: context.Canceled},
{name: "interrupt", keys: "\x03", hidden: true, wantErr: context.Canceled},
{name: "cancel", hidden: true, cancel: true, wantErr: context.Canceled},
{name: "deadline", hidden: true, deadline: true, wantErr: context.DeadlineExceeded},
{name: "overflow", keys: strings.Repeat("a", 4097), hidden: true, wantErr: ErrInvalidRequest},
}
for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
if ok, _, err := flush.Call(uintptr(handle)); ok == 0 {
t.Fatal(err)
}
if err := windows.SetConsoleMode(handle, original|windows.ENABLE_ECHO_INPUT|windows.ENABLE_LINE_INPUT); err != nil {
t.Fatal(err)
}
var before uint32
if err := windows.GetConsoleMode(handle, &before); err != nil {
t.Fatal(err)
}
limit := 2 * time.Second
if test.deadline {
limit = 250 * time.Millisecond
}
ctx, cancel := context.WithTimeout(context.Background(), limit)
defer cancel()
type result struct {
value string
err error
}
done := make(chan result, 1)
go func() {
value, err := screen.Read(ctx, "原生控制台测试", test.hidden)
done <- result{value, err}
}()
// 等待实际关闭回显后再输入,防止测试本身制造输入时序竞争。
for {
var active uint32
if err := windows.GetConsoleMode(handle, &active); err != nil {
t.Fatal(err)
}
if active&windows.ENABLE_ECHO_INPUT == 0 {
break
}
select {
case result := <-done:
t.Fatalf("输入模式建立前读取已结束:%v", result.err)
case <-ctx.Done():
t.Fatal("未及时关闭控制台回显")
case <-time.After(time.Millisecond):
}
}
if test.cancel {
cancel()
} else if test.keys != "" {
units := utf16.Encode([]rune(test.keys))
records := make([]consoleRecord, len(units))
for index, unit := range units {
records[index] = consoleRecord{Type: 1, Down: 1, Repeat: 1, Character: unit}
}
var count uint32
ok, _, err := write.Call(uintptr(handle), uintptr(unsafe.Pointer(&records[0])), uintptr(len(records)), uintptr(unsafe.Pointer(&count)))
if ok == 0 || count != uint32(len(records)) {
t.Fatalf("写入原生控制台事件失败:%d %v", count, err)
}
}
select {
case result := <-done:
if !errors.Is(result.err, test.wantErr) || result.value != test.want {
t.Fatalf("读取结果不符:长度=%d,错误=%v", len(result.value), result.err)
}
case <-time.After(3 * time.Second):
t.Fatal("取消后控制台读取仍然阻塞")
}
var after uint32
if err := windows.GetConsoleMode(handle, &after); err != nil || after != before {
t.Fatalf("控制台模式未恢复:before=%x after=%x error=%v", before, after, err)
}
})
}
}