win32api/common_api_test.go

1479 lines
41 KiB
Go
Raw Normal View History

//go:build windows
package win32api
import (
"bytes"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"testing"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
const (
processHelperEnv = "WIN32API_TEST_HELPER_PROCESS"
processHelperModeEnv = "WIN32API_TEST_HELPER_MODE"
processHelperExitEnv = "WIN32API_TEST_HELPER_EXIT_CODE"
cmdCoverageEnv = "WIN32API_RUN_CMD_INTEGRATION"
processModeExit = "exit"
processModeSleep = "sleep"
processWaitTime = 30 * time.Second
)
func TestProcessHelper(t *testing.T) {
if os.Getenv(processHelperEnv) != "1" {
return
}
switch os.Getenv(processHelperModeEnv) {
case processModeExit:
code, err := strconv.Atoi(os.Getenv(processHelperExitEnv))
if err != nil {
os.Exit(2)
}
os.Exit(code)
case processModeSleep:
time.Sleep(processWaitTime)
os.Exit(0)
default:
os.Exit(3)
}
}
func helperProcessEnvList(mode string, exitCode int) []string {
base := make([]string, 0, len(os.Environ())+3)
for _, entry := range os.Environ() {
if strings.HasPrefix(entry, processHelperEnv+"=") ||
strings.HasPrefix(entry, processHelperModeEnv+"=") ||
strings.HasPrefix(entry, processHelperExitEnv+"=") {
continue
}
base = append(base, entry)
}
base = append(base,
processHelperEnv+"=1",
processHelperModeEnv+"="+mode,
processHelperExitEnv+"="+strconv.Itoa(exitCode),
)
return base
}
func configureHelperProcess(t *testing.T, mode string, exitCode int) (string, string) {
t.Helper()
restore := map[string]*string{}
for _, key := range []string{processHelperEnv, processHelperModeEnv, processHelperExitEnv} {
if value, ok := os.LookupEnv(key); ok {
v := value
restore[key] = &v
} else {
restore[key] = nil
}
}
for _, entry := range helperProcessEnvList(mode, exitCode) {
parts := strings.SplitN(entry, "=", 2)
if len(parts) != 2 {
continue
}
if parts[0] == processHelperEnv || parts[0] == processHelperModeEnv || parts[0] == processHelperExitEnv {
if err := os.Setenv(parts[0], parts[1]); err != nil {
t.Fatalf("Setenv(%s) failed: %v", parts[0], err)
}
}
}
t.Cleanup(func() {
for key, value := range restore {
if value == nil {
_ = os.Unsetenv(key)
continue
}
_ = os.Setenv(key, *value)
}
})
exe, err := os.Executable()
if err != nil {
t.Fatalf("Executable failed: %v", err)
}
return exe, windows.EscapeArg(exe) + " -test.run=^TestProcessHelper$"
}
func requireCmdCoverage(t *testing.T) {
t.Helper()
if os.Getenv(cmdCoverageEnv) != "1" {
t.Skipf("set %s=1 to run cmd.exe integration coverage", cmdCoverageEnv)
}
}
func TestGetCurrentProcessId(t *testing.T) {
pid := GetCurrentProcessId()
if pid == 0 {
t.Fatal("GetCurrentProcessId returned 0")
}
if int(pid) != os.Getpid() {
t.Fatalf("GetCurrentProcessId mismatch: got=%d want=%d", pid, os.Getpid())
}
}
func TestGetCurrentThreadId(t *testing.T) {
tid := GetCurrentThreadId()
if tid == 0 {
t.Fatal("GetCurrentThreadId returned 0")
}
}
func TestGetComputerName(t *testing.T) {
name, err := GetComputerName()
if err != nil {
t.Fatalf("GetComputerName failed: %v", err)
}
name = strings.TrimSpace(name)
if name == "" {
t.Fatal("GetComputerName returned empty string")
}
}
func TestGetUserName(t *testing.T) {
name, err := GetUserName()
if err != nil {
t.Fatalf("GetUserName failed: %v", err)
}
name = strings.TrimSpace(name)
if name == "" {
t.Fatal("GetUserName returned empty string")
}
}
func TestGetTempPath(t *testing.T) {
tempPath, err := GetTempPath()
if err != nil {
t.Fatalf("GetTempPath failed: %v", err)
}
tempPath = strings.TrimSpace(tempPath)
if tempPath == "" {
t.Fatal("GetTempPath returned empty string")
}
cleaned := filepath.Clean(tempPath)
if _, err := os.Stat(cleaned); err != nil {
t.Fatalf("GetTempPath returned path not found: %q err=%v", cleaned, err)
}
}
func TestGetSystemDirectoryAndGetWindowsDirectory(t *testing.T) {
systemDir, err := GetSystemDirectory()
if err != nil {
t.Fatalf("GetSystemDirectory failed: %v", err)
}
if strings.TrimSpace(systemDir) == "" {
t.Fatal("GetSystemDirectory returned empty path")
}
if _, err := os.Stat(filepath.Clean(systemDir)); err != nil {
t.Fatalf("GetSystemDirectory path not found: %q err=%v", systemDir, err)
}
windowsDir, err := GetWindowsDirectory()
if err != nil {
t.Fatalf("GetWindowsDirectory failed: %v", err)
}
if strings.TrimSpace(windowsDir) == "" {
t.Fatal("GetWindowsDirectory returned empty path")
}
if _, err := os.Stat(filepath.Clean(windowsDir)); err != nil {
t.Fatalf("GetWindowsDirectory path not found: %q err=%v", windowsDir, err)
}
}
func TestOpenProcessAndGetExitCodeWait(t *testing.T) {
h, err := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION|SYNCHRONIZE, false, DWORD(os.Getpid()))
if err != nil {
t.Fatalf("OpenProcess failed: %v", err)
}
defer func() {
_ = CloseHandle(h)
}()
code, err := GetExitCodeProcess(h)
if err != nil {
t.Fatalf("GetExitCodeProcess failed: %v", err)
}
if code != STILL_ACTIVE {
t.Fatalf("GetExitCodeProcess should return STILL_ACTIVE(%d), got %d", STILL_ACTIVE, code)
}
wait, err := WaitForSingleObject(h, 0)
if err != nil {
t.Fatalf("WaitForSingleObject failed: %v", err)
}
if wait != WAIT_TIMEOUT {
t.Fatalf("WaitForSingleObject should return WAIT_TIMEOUT(%d), got %d", WAIT_TIMEOUT, wait)
}
}
func TestProcess32FirstNextContainsSelf(t *testing.T) {
snapshot, err := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if err != nil {
t.Fatalf("CreateToolhelp32Snapshot failed: %v", err)
}
defer func() {
_ = CloseHandle(snapshot)
}()
var entry PROCESSENTRY32
entry.DwSize = Ulong(unsafe.Sizeof(entry))
if err := Process32First(snapshot, &entry); err != nil {
t.Fatalf("Process32First failed: %v", err)
}
foundSelf := false
count := 0
for {
count++
if DWORD(uint32(entry.Th32ProcessID)) == DWORD(os.Getpid()) {
foundSelf = true
}
entry.DwSize = Ulong(unsafe.Sizeof(entry))
err = Process32Next(snapshot, &entry)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == ERROR_NO_MORE_FILES {
break
}
if err == syscall.EINVAL {
break
}
t.Fatalf("Process32Next failed: %v", err)
}
}
if count == 0 {
t.Fatal("process snapshot is empty")
}
if !foundSelf {
t.Fatalf("current process pid=%d not found in process snapshot", os.Getpid())
}
}
func TestEnumerateProcessesHelper(t *testing.T) {
processes, err := EnumerateProcesses()
if err != nil {
t.Fatalf("EnumerateProcesses failed: %v", err)
}
if len(processes) == 0 {
t.Fatal("EnumerateProcesses returned empty result")
}
selfExe, _ := os.Executable()
selfBase := filepath.Base(selfExe)
foundSelf := false
for _, entry := range processes {
if entry.ExeFile() == "" {
t.Fatal("EnumerateProcesses returned process with empty ExeFile")
}
if selfBase != "" && strings.EqualFold(entry.ExeFile(), selfBase) {
foundSelf = true
}
}
if selfBase != "" && !foundSelf {
t.Fatalf("current executable %q not found in EnumerateProcesses result", selfBase)
}
}
func TestThread32FirstNextContainsSelf(t *testing.T) {
snapshot, err := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)
if err != nil {
t.Fatalf("CreateToolhelp32Snapshot(THREAD) failed: %v", err)
}
defer func() {
_ = CloseHandle(snapshot)
}()
var entry THREADENTRY32
entry.DwSize = DWORD(unsafe.Sizeof(entry))
if err := Thread32First(snapshot, &entry); err != nil {
t.Fatalf("Thread32First failed: %v", err)
}
currentPID := DWORD(os.Getpid())
currentTID := GetCurrentThreadId()
foundProcessThread := false
foundCurrentThread := false
count := 0
for {
count++
if entry.Th32OwnerProcessID == currentPID {
foundProcessThread = true
if entry.Th32ThreadID == currentTID {
foundCurrentThread = true
}
}
entry.DwSize = DWORD(unsafe.Sizeof(entry))
err = Thread32Next(snapshot, &entry)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == ERROR_NO_MORE_FILES {
break
}
if err == syscall.EINVAL {
break
}
t.Fatalf("Thread32Next failed: %v", err)
}
}
if count == 0 {
t.Fatal("thread snapshot is empty")
}
if !foundProcessThread {
t.Fatalf("no thread found for current process pid=%d", currentPID)
}
if !foundCurrentThread {
t.Fatalf("current thread tid=%d not found in thread snapshot", currentTID)
}
}
func TestEnumerateThreadsHelper(t *testing.T) {
threads, err := EnumerateThreads(DWORD(os.Getpid()))
if err != nil {
t.Fatalf("EnumerateThreads failed: %v", err)
}
if len(threads) == 0 {
t.Fatal("EnumerateThreads returned empty result")
}
currentTID := GetCurrentThreadId()
foundCurrentThread := false
for _, entry := range threads {
if entry.Th32OwnerProcessID != DWORD(os.Getpid()) {
t.Fatalf("EnumerateThreads returned thread from another process: got=%d want=%d", entry.Th32OwnerProcessID, os.Getpid())
}
if entry.Th32ThreadID == currentTID {
foundCurrentThread = true
}
}
if !foundCurrentThread {
t.Fatalf("current thread tid=%d not found in EnumerateThreads result", currentTID)
}
}
func TestModule32FirstNextContainsSelf(t *testing.T) {
snapshot, err := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE|TH32CS_SNAPMODULE32, DWORD(os.Getpid()))
if err != nil {
t.Fatalf("CreateToolhelp32Snapshot(MODULE) failed: %v", err)
}
defer func() {
_ = CloseHandle(snapshot)
}()
var entry MODULEENTRY32W
entry.DwSize = DWORD(unsafe.Sizeof(entry))
if err := Module32First(snapshot, &entry); err != nil {
t.Fatalf("Module32First failed: %v", err)
}
selfExe, _ := os.Executable()
selfBase := strings.ToLower(filepath.Base(selfExe))
foundSelf := false
count := 0
for {
count++
exePath := syscall.UTF16ToString(entry.SzExePath[:])
if selfBase != "" && strings.EqualFold(filepath.Base(exePath), selfBase) {
foundSelf = true
}
entry.DwSize = DWORD(unsafe.Sizeof(entry))
err = Module32Next(snapshot, &entry)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == ERROR_NO_MORE_FILES {
break
}
if err == syscall.EINVAL {
break
}
t.Fatalf("Module32Next failed: %v", err)
}
}
if count == 0 {
t.Fatal("module snapshot is empty")
}
if selfBase != "" && !foundSelf {
t.Fatalf("current executable module %q not found in module snapshot", selfBase)
}
}
func TestEnumerateModulesHelper(t *testing.T) {
modules, err := EnumerateModules(DWORD(os.Getpid()))
if err != nil {
t.Fatalf("EnumerateModules failed: %v", err)
}
if len(modules) == 0 {
t.Fatal("EnumerateModules returned empty result")
}
selfExe, _ := os.Executable()
selfBase := filepath.Base(selfExe)
foundSelf := false
for _, entry := range modules {
if entry.ModuleName() == "" {
t.Fatal("EnumerateModules returned module with empty ModuleName")
}
if entry.ExePath() == "" {
t.Fatal("EnumerateModules returned module with empty ExePath")
}
if selfBase != "" && strings.EqualFold(filepath.Base(entry.ExePath()), selfBase) {
foundSelf = true
}
}
if selfBase != "" && !foundSelf {
t.Fatalf("current executable module %q not found in EnumerateModules result", selfBase)
}
}
func TestOpenThreadCurrentThread(t *testing.T) {
tid := GetCurrentThreadId()
if tid == 0 {
t.Fatal("GetCurrentThreadId returned 0")
}
threadHandle, err := OpenThread(THREAD_QUERY_INFORMATION, false, tid)
if err != nil {
t.Fatalf("OpenThread failed: %v", err)
}
defer func() {
_ = CloseHandle(threadHandle)
}()
}
func TestSuspendResumeInvalidThreadHandle(t *testing.T) {
if _, err := SuspendThread(0); err == nil {
t.Fatal("SuspendThread should fail for invalid handle")
}
if _, err := ResumeThread(0); err == nil {
t.Fatal("ResumeThread should fail for invalid handle")
}
}
func TestQueryFullProcessImageName(t *testing.T) {
h, err := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, DWORD(os.Getpid()))
if err != nil {
t.Fatalf("OpenProcess failed: %v", err)
}
defer func() {
_ = CloseHandle(h)
}()
path, err := QueryFullProcessImageName(h, 0)
if err != nil {
t.Fatalf("QueryFullProcessImageName failed: %v", err)
}
if strings.TrimSpace(path) == "" {
t.Fatal("QueryFullProcessImageName returned empty path")
}
if _, err := os.Stat(filepath.Clean(path)); err != nil {
t.Fatalf("QueryFullProcessImageName path not found: %q err=%v", path, err)
}
}
func TestCreateRemoteThreadSelfExitThread(t *testing.T) {
process, err := OpenProcess(
PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE|SYNCHRONIZE,
false,
DWORD(os.Getpid()),
)
if err != nil {
t.Fatalf("OpenProcess failed: %v", err)
}
defer func() {
_ = CloseHandle(process)
}()
exitThreadProc, err := getProcAddr("kernel32.dll", "ExitThread")
if err != nil {
t.Fatalf("getProcAddr(ExitThread) failed: %v", err)
}
var threadID DWORD
threadHandle, err := CreateRemoteThread(process, nil, 0, exitThreadProc, 0, 0, &threadID)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && (errno == syscall.ERROR_ACCESS_DENIED || errno == syscall.Errno(1314)) {
t.Skipf("CreateRemoteThread requires higher privilege in current context: %v", err)
}
t.Fatalf("CreateRemoteThread failed: %v", err)
}
defer func() {
_ = CloseHandle(threadHandle)
}()
if threadID == 0 {
t.Fatal("CreateRemoteThread returned thread id 0")
}
wait, err := WaitForSingleObject(threadHandle, 5000)
if err != nil {
t.Fatalf("WaitForSingleObject(thread) failed: %v", err)
}
if wait != WAIT_OBJECT_0 {
t.Fatalf("WaitForSingleObject(thread) mismatch: got=%d want=%d", wait, WAIT_OBJECT_0)
}
}
func TestGetSetThreadContextInvalidArgs(t *testing.T) {
if err := GetThreadContext(0, nil); err == nil {
t.Fatal("GetThreadContext should fail on nil context pointer")
}
if err := SetThreadContext(0, nil); err == nil {
t.Fatal("SetThreadContext should fail on nil context pointer")
}
}
func TestGetSetThreadContextSuspendedProcess(t *testing.T) {
if runtime.GOARCH != "amd64" {
t.Skip("GetThreadContext success path test is amd64-only")
}
app, cmdLine := configureHelperProcess(t, processModeExit, 0)
si := StartupInfo{Cb: uint32(unsafe.Sizeof(StartupInfo{}))}
pi := ProcessInformation{}
if err := CreateProcess(
app,
cmdLine,
nil,
nil,
false,
CREATE_NO_WINDOW|CREATE_SUSPENDED,
0,
"",
&si,
&pi,
); err != nil {
t.Fatalf("CreateProcess(CREATE_SUSPENDED) failed: %v", err)
}
defer func() {
if pi.Thread != 0 {
_ = CloseHandle(pi.Thread)
}
if pi.Process != 0 {
_ = TerminateProcess(pi.Process, 0)
_ = CloseHandle(pi.Process)
}
}()
ctx := AMD64_CONTEXT{ContextFlags: CONTEXT_CONTROL}
if err := GetThreadContext(pi.Thread, unsafe.Pointer(&ctx)); err != nil {
t.Fatalf("GetThreadContext failed: %v", err)
}
if ctx.Rip == 0 || ctx.Rsp == 0 {
t.Fatalf("GetThreadContext returned empty control registers: RIP=%#x RSP=%#x", ctx.Rip, ctx.Rsp)
}
if err := SetThreadContext(pi.Thread, unsafe.Pointer(&ctx)); err != nil {
t.Fatalf("SetThreadContext failed: %v", err)
}
if _, err := ResumeThread(pi.Thread); err != nil {
t.Fatalf("ResumeThread failed: %v", err)
}
wait, err := WaitForSingleObject(pi.Process, 5000)
if err != nil {
t.Fatalf("WaitForSingleObject(process) failed: %v", err)
}
if wait != WAIT_OBJECT_0 {
t.Fatalf("WaitForSingleObject(process) mismatch: got=%d want=%d", wait, WAIT_OBJECT_0)
}
}
func TestDebugActiveProcessAttachWaitContinueStop(t *testing.T) {
app, cmdLine := configureHelperProcess(t, processModeSleep, 0)
si := StartupInfo{Cb: uint32(unsafe.Sizeof(StartupInfo{}))}
pi := ProcessInformation{}
if err := CreateProcess(
app,
cmdLine,
nil,
nil,
false,
CREATE_NO_WINDOW,
0,
"",
&si,
&pi,
); err != nil {
t.Fatalf("CreateProcess failed: %v", err)
}
defer func() {
if pi.Thread != 0 {
_ = CloseHandle(pi.Thread)
}
if pi.Process != 0 {
_ = TerminateProcess(pi.Process, 0)
_ = CloseHandle(pi.Process)
}
}()
if err := DebugActiveProcess(DWORD(pi.ProcessId)); err != nil {
if errno, ok := err.(syscall.Errno); ok && (errno == syscall.ERROR_ACCESS_DENIED || errno == syscall.Errno(1314)) {
t.Skipf("DebugActiveProcess requires higher privilege in current context: %v", err)
}
t.Fatalf("DebugActiveProcess failed: %v", err)
}
eventBuffer := make([]byte, 1024)
eventInfo, err := WaitForDebugEventInfo(eventBuffer, 5000)
if err != nil {
if stopErr := DebugActiveProcessStop(DWORD(pi.ProcessId)); stopErr != nil {
t.Logf("DebugActiveProcessStop after wait failure: %v", stopErr)
}
t.Fatalf("WaitForDebugEvent failed: %v", err)
}
if eventInfo.Header.DwProcessId == 0 || eventInfo.Header.DwThreadId == 0 {
t.Fatalf("invalid debug event header: process=%d thread=%d", eventInfo.Header.DwProcessId, eventInfo.Header.DwThreadId)
}
if eventInfo.CodeName == "" {
t.Fatal("WaitForDebugEventInfo returned empty code name")
}
if err := ContinueDebugEvent(eventInfo.Header.DwProcessId, eventInfo.Header.DwThreadId, DBG_CONTINUE); err != nil {
t.Fatalf("ContinueDebugEvent failed: %v", err)
}
if err := DebugActiveProcessStop(DWORD(pi.ProcessId)); err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.Errno(87) {
t.Logf("DebugActiveProcessStop got ERROR_INVALID_PARAMETER (process might already exit): %v", err)
} else {
t.Fatalf("DebugActiveProcessStop failed: %v", err)
}
}
}
func TestWaitForDebugEventAndContinueInvalidArgs(t *testing.T) {
if err := WaitForDebugEvent(nil, 0); err == nil {
t.Fatal("WaitForDebugEvent should fail on nil event pointer")
}
if _, err := WaitForDebugEventInfo(nil, 0); err == nil {
t.Fatal("WaitForDebugEventInfo should fail on empty buffer")
}
if err := ContinueDebugEvent(0, 0, DBG_CONTINUE); err == nil {
t.Fatal("ContinueDebugEvent should fail on invalid process/thread id")
}
}
func TestDecodeDebugEvent(t *testing.T) {
raw := make([]byte, int(unsafe.Sizeof(DEBUG_EVENT_HEADER{})))
header := (*DEBUG_EVENT_HEADER)(unsafe.Pointer(&raw[0]))
header.DwDebugEventCode = CREATE_PROCESS_DEBUG_EVENT
header.DwProcessId = 123
header.DwThreadId = 456
info, err := DecodeDebugEvent(raw)
if err != nil {
t.Fatalf("DecodeDebugEvent failed: %v", err)
}
if info.Header.DwProcessId != 123 || info.Header.DwThreadId != 456 {
t.Fatalf("DecodeDebugEvent header mismatch: got process=%d thread=%d", info.Header.DwProcessId, info.Header.DwThreadId)
}
if info.CodeName != "CREATE_PROCESS_DEBUG_EVENT" {
t.Fatalf("DecodeDebugEvent code name mismatch: got=%q", info.CodeName)
}
if info.String() != "CREATE_PROCESS_DEBUG_EVENT" {
t.Fatalf("DebugEventInfo.String mismatch: got=%q", info.String())
}
}
func TestProcessIdToSessionIdCurrentProcess(t *testing.T) {
sessionID, err := ProcessIdToSessionId(DWORD(os.Getpid()))
if err != nil {
t.Fatalf("ProcessIdToSessionId failed: %v", err)
}
if sessionID == WTS_CURRENT_SESSION {
t.Fatalf("ProcessIdToSessionId returned invalid sentinel value: %d", sessionID)
}
currentSessionID, err := CurrentProcessSessionID()
if err != nil {
t.Fatalf("CurrentProcessSessionID failed: %v", err)
}
if currentSessionID != sessionID {
t.Fatalf("session id mismatch: ProcessIdToSessionId=%d CurrentProcessSessionID=%d", sessionID, currentSessionID)
}
}
func TestWTSQuerySessionInformationCurrentProcess(t *testing.T) {
sessionID, err := CurrentProcessSessionID()
if err != nil {
t.Fatalf("CurrentProcessSessionID failed: %v", err)
}
gotSessionID, err := WTSQuerySessionDWORD(0, sessionID, WTSSessionId)
if err != nil {
t.Fatalf("WTSQuerySessionDWORD(WTSSessionId) failed: %v", err)
}
if gotSessionID != sessionID {
t.Fatalf("session id mismatch from WTSQuerySessionDWORD: got=%d want=%d", gotSessionID, sessionID)
}
details, err := GetSessionDetails(0, sessionID)
if err != nil {
t.Fatalf("GetSessionDetails failed: %v", err)
}
if details.SessionID != sessionID {
t.Fatalf("GetSessionDetails.SessionID mismatch: got=%d want=%d", details.SessionID, sessionID)
}
if details.ConnectState < WTSActive || details.ConnectState > WTSInit {
t.Fatalf("unexpected session connect state: %d", details.ConnectState)
}
t.Logf(
"session details: session=%d user=%q domain=%q winStation=%q connectState=%d remote=%v",
details.SessionID,
details.UserName,
details.DomainName,
details.WinStationName,
details.ConnectState,
details.IsRemote,
)
}
func TestWin32ScalarTypeSizes(t *testing.T) {
if got := unsafe.Sizeof(WTS_CONNECTSTATE_CLASS(0)); got != 4 {
t.Fatalf("WTS_CONNECTSTATE_CLASS size mismatch: got=%d want=4", got)
}
if got := unsafe.Sizeof(WTS_INFO_CLASS(0)); got != 4 {
t.Fatalf("WTS_INFO_CLASS size mismatch: got=%d want=4", got)
}
if got := unsafe.Sizeof(TOKEN_TYPE(0)); got != 4 {
t.Fatalf("TOKEN_TYPE size mismatch: got=%d want=4", got)
}
if got := unsafe.Sizeof(SECURITY_IMPERSONATION_LEVEL(0)); got != 4 {
t.Fatalf("SECURITY_IMPERSONATION_LEVEL size mismatch: got=%d want=4", got)
}
if got := unsafe.Sizeof(SW(0)); got != 4 {
t.Fatalf("SW size mismatch: got=%d want=4", got)
}
}
func TestWTSSessionInfoLayout(t *testing.T) {
wantNameOffset := uintptr(4)
wantStateOffset := uintptr(8)
wantSize := uintptr(12)
if unsafe.Sizeof(uintptr(0)) == 8 {
wantNameOffset = 8
wantStateOffset = 16
wantSize = 24
}
if got := unsafe.Sizeof(WTS_SESSION_INFO{}); got != wantSize {
t.Fatalf("WTS_SESSION_INFO size mismatch: got=%d want=%d", got, wantSize)
}
if got := unsafe.Offsetof(WTS_SESSION_INFO{}.WinStationName); got != wantNameOffset {
t.Fatalf("WTS_SESSION_INFO.WinStationName offset mismatch: got=%d want=%d", got, wantNameOffset)
}
if got := unsafe.Offsetof(WTS_SESSION_INFO{}.State); got != wantStateOffset {
t.Fatalf("WTS_SESSION_INFO.State offset mismatch: got=%d want=%d", got, wantStateOffset)
}
}
func TestShellExecuteInfoLayout(t *testing.T) {
wantNShowOffset := uintptr(28)
wantHProcessOffset := uintptr(52)
wantSize := uintptr(56)
if unsafe.Sizeof(uintptr(0)) == 8 {
wantNShowOffset = 48
wantHProcessOffset = 104
wantSize = 112
}
if got := unsafe.Sizeof(SHELLEXECUTEINFOW{}.NShow); got != 4 {
t.Fatalf("SHELLEXECUTEINFOW.NShow size mismatch: got=%d want=4", got)
}
if got := unsafe.Offsetof(SHELLEXECUTEINFOW{}.NShow); got != wantNShowOffset {
t.Fatalf("SHELLEXECUTEINFOW.NShow offset mismatch: got=%d want=%d", got, wantNShowOffset)
}
if got := unsafe.Offsetof(SHELLEXECUTEINFOW{}.HProcess); got != wantHProcessOffset {
t.Fatalf("SHELLEXECUTEINFOW.HProcess offset mismatch: got=%d want=%d", got, wantHProcessOffset)
}
if got := unsafe.Sizeof(SHELLEXECUTEINFOW{}); got != wantSize {
t.Fatalf("SHELLEXECUTEINFOW size mismatch: got=%d want=%d", got, wantSize)
}
}
func TestFileIDDescriptorLayout(t *testing.T) {
if got := unsafe.Sizeof(FILE_ID_TYPE(0)); got != 4 {
t.Fatalf("FILE_ID_TYPE size mismatch: got=%d want=4", got)
}
if got := unsafe.Offsetof(FILE_ID_DESCRIPTOR{}.FileId); got != 8 {
t.Fatalf("FILE_ID_DESCRIPTOR.FileId offset mismatch: got=%d want=8", got)
}
if got := unsafe.Sizeof(FILE_ID_DESCRIPTOR{}); got != 24 {
t.Fatalf("FILE_ID_DESCRIPTOR size mismatch: got=%d want=24", got)
}
}
func TestEnumerateSessionsAndActiveSessionID(t *testing.T) {
currentSessionID, err := CurrentProcessSessionID()
if err != nil {
t.Fatalf("CurrentProcessSessionID failed: %v", err)
}
sessions, err := EnumerateSessions()
if err != nil {
t.Fatalf("EnumerateSessions failed: %v", err)
}
if len(sessions) == 0 {
t.Fatal("EnumerateSessions returned empty result")
}
foundCurrent := false
activeIDs := make(map[DWORD]bool)
for _, session := range sessions {
if session.SessionID == currentSessionID {
foundCurrent = true
}
if session.State < WTSActive || session.State > WTSInit {
t.Fatalf("unexpected session state %d for session %d", session.State, session.SessionID)
}
if session.State == WTSActive {
activeIDs[session.SessionID] = true
}
}
if !foundCurrent {
t.Fatalf("current session id %d not found in EnumerateSessions result", currentSessionID)
}
activeSessionID, err := ActiveSessionID()
if err != nil {
t.Fatalf("ActiveSessionID failed: %v", err)
}
if activeSessionID == WTS_CURRENT_SESSION {
t.Fatalf("ActiveSessionID returned invalid sentinel value: %d", activeSessionID)
}
if len(activeIDs) > 0 && !activeIDs[activeSessionID] {
t.Fatalf("ActiveSessionID=%d not present in active session set %v", activeSessionID, activeIDs)
}
}
func TestVirtualAllocReadWriteProtectFreeSelf(t *testing.T) {
process, err := OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_QUERY_INFORMATION, false, DWORD(os.Getpid()))
if err != nil {
t.Fatalf("OpenProcess failed: %v", err)
}
defer func() {
_ = CloseHandle(process)
}()
const allocSize = uintptr(4096)
addr, err := VirtualAllocEx(process, 0, allocSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
if err != nil {
t.Fatalf("VirtualAllocEx failed: %v", err)
}
defer func() {
_ = VirtualFreeEx(process, addr, 0, MEM_RELEASE)
}()
payload := []byte("win32api-memory-rw")
var written uintptr
if err := WriteProcessMemory(process, addr, payload, &written); err != nil {
t.Fatalf("WriteProcessMemory failed: %v", err)
}
if written != uintptr(len(payload)) {
t.Fatalf("WriteProcessMemory written mismatch: got=%d want=%d", written, len(payload))
}
readBuf := make([]byte, len(payload))
var read uintptr
if err := ReadProcessMemory(process, addr, readBuf, &read); err != nil {
t.Fatalf("ReadProcessMemory failed: %v", err)
}
if read != uintptr(len(payload)) {
t.Fatalf("ReadProcessMemory read mismatch: got=%d want=%d", read, len(payload))
}
if string(readBuf) != string(payload) {
t.Fatalf("ReadProcessMemory content mismatch: got=%q want=%q", string(readBuf), string(payload))
}
var oldProtect DWORD
if err := VirtualProtectEx(process, addr, uintptr(len(payload)), PAGE_READONLY, &oldProtect); err != nil {
t.Fatalf("VirtualProtectEx(PAGE_READONLY) failed: %v", err)
}
var restoreOld DWORD
if err := VirtualProtectEx(process, addr, uintptr(len(payload)), oldProtect, &restoreOld); err != nil {
t.Fatalf("VirtualProtectEx(restore) failed: %v", err)
}
}
func TestVirtualQueryExSelfAllocatedRegion(t *testing.T) {
process, err := OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_QUERY_INFORMATION, false, DWORD(os.Getpid()))
if err != nil {
t.Fatalf("OpenProcess failed: %v", err)
}
defer func() {
_ = CloseHandle(process)
}()
const allocSize = uintptr(4096)
addr, err := VirtualAllocEx(process, 0, allocSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE)
if err != nil {
t.Fatalf("VirtualAllocEx failed: %v", err)
}
defer func() {
_ = VirtualFreeEx(process, addr, 0, MEM_RELEASE)
}()
var mbi MEMORY_BASIC_INFORMATION
readLen, err := VirtualQueryEx(process, addr, &mbi, uintptr(unsafe.Sizeof(mbi)))
if err != nil {
t.Fatalf("VirtualQueryEx failed: %v", err)
}
if readLen == 0 {
t.Fatal("VirtualQueryEx returned 0 bytes")
}
if mbi.RegionSize == 0 {
t.Fatal("VirtualQueryEx returned RegionSize=0")
}
if mbi.State != MEM_COMMIT {
t.Fatalf("VirtualQueryEx state mismatch: got=%#x want=%#x", mbi.State, MEM_COMMIT)
}
if addr < mbi.BaseAddress || addr-mbi.BaseAddress >= mbi.RegionSize {
t.Fatalf("allocated address %#x is outside queried region [%#x, %#x)", addr, mbi.BaseAddress, mbi.BaseAddress+mbi.RegionSize)
}
}
func TestGetFileAttributesAndGetFullPathName(t *testing.T) {
tmpDir := t.TempDir()
relative := filepath.Join(tmpDir, ".", "attrs.txt")
if err := os.WriteFile(relative, []byte("ok"), 0644); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
attrs, err := GetFileAttributes(relative)
if err != nil {
t.Fatalf("GetFileAttributes failed: %v", err)
}
if attrs == INVALID_FILE_ATTRIBUTES {
t.Fatalf("GetFileAttributes returned INVALID_FILE_ATTRIBUTES(%d)", INVALID_FILE_ATTRIBUTES)
}
full, err := GetFullPathName(relative)
if err != nil {
t.Fatalf("GetFullPathName failed: %v", err)
}
if full == "" {
t.Fatal("GetFullPathName returned empty string")
}
if !strings.EqualFold(filepath.Clean(full), filepath.Clean(relative)) {
t.Fatalf("GetFullPathName mismatch: got=%q want=%q", filepath.Clean(full), filepath.Clean(relative))
}
}
func TestMoveFileEx(t *testing.T) {
tmpDir := t.TempDir()
src := filepath.Join(tmpDir, "src.txt")
dst := filepath.Join(tmpDir, "dst.txt")
if err := os.WriteFile(src, []byte("move-me"), 0644); err != nil {
t.Fatalf("WriteFile src failed: %v", err)
}
if err := os.WriteFile(dst, []byte("existing"), 0644); err != nil {
t.Fatalf("WriteFile dst failed: %v", err)
}
if err := MoveFileEx(src, dst, MOVEFILE_REPLACE_EXISTING); err != nil {
t.Fatalf("MoveFileEx failed: %v", err)
}
if _, err := os.Stat(src); !os.IsNotExist(err) {
t.Fatalf("source should be gone after MoveFileEx, stat err=%v", err)
}
data, err := os.ReadFile(dst)
if err != nil {
t.Fatalf("ReadFile dst failed: %v", err)
}
if string(data) != "move-me" {
t.Fatalf("unexpected dst content after MoveFileEx: %q", string(data))
}
}
func TestGetLastErrorAndFormatMessage(t *testing.T) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
missing := filepath.Join(t.TempDir(), "missing.txt")
delErr := DeleteFile(missing)
if delErr == nil {
t.Fatal("DeleteFile should fail for missing file")
}
code := GetLastError()
if code == 0 {
// In Go, runtime/syscall interaction may clear the thread-local last-error
// before we query it again; fall back to the direct syscall error.
if errno, ok := delErr.(syscall.Errno); ok {
code = DWORD(errno)
}
}
if code == 0 {
code = DWORD(syscall.ERROR_FILE_NOT_FOUND)
}
if errno, ok := delErr.(syscall.Errno); ok && code != DWORD(errno) {
t.Logf("GetLastError differs from direct syscall errno: last=%d errno=%d", code, errno)
if code == 0 {
code = DWORD(errno)
}
}
msg, err := FormatMessage(code)
if err != nil {
t.Fatalf("FormatMessage failed: %v", err)
}
if strings.TrimSpace(msg) == "" {
t.Fatal("FormatMessage returned empty message")
}
}
func TestRtlMoveMemoryIgnoresStaleLastError(t *testing.T) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := SetLastError(DWORD(syscall.ERROR_ACCESS_DENIED)); err != nil {
t.Fatalf("SetLastError failed: %v", err)
}
src := []byte("copy-ok")
dst := make([]byte, len(src))
if err := RtlMoveMemory(unsafe.Pointer(&dst[0]), unsafe.Pointer(&src[0]), uintptr(len(src))); err != nil {
t.Fatalf("RtlMoveMemory failed with stale last error: %v", err)
}
if !bytes.Equal(dst, src) {
t.Fatalf("RtlMoveMemory copy mismatch: got=%q want=%q", string(dst), string(src))
}
}
func TestCountClipboardFormatsEmptyClipboard(t *testing.T) {
if err := OpenClipboard(0); err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
t.Skipf("OpenClipboard access denied in current context: %v", err)
}
t.Fatalf("OpenClipboard failed: %v", err)
}
defer func() {
if err := CloseClipboard(); err != nil {
t.Fatalf("CloseClipboard failed: %v", err)
}
}()
if err := EmptyClipboard(); err != nil {
t.Fatalf("EmptyClipboard failed: %v", err)
}
count, err := CountClipboardFormats()
if err != nil {
t.Fatalf("CountClipboardFormats failed: %v", err)
}
if count != 0 {
t.Fatalf("CountClipboardFormats = %d, want 0", count)
}
}
func TestGetClipboardOwnerEmptyClipboard(t *testing.T) {
if err := OpenClipboard(0); err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
t.Skipf("OpenClipboard access denied in current context: %v", err)
}
t.Fatalf("OpenClipboard failed: %v", err)
}
defer func() {
if err := CloseClipboard(); err != nil {
t.Fatalf("CloseClipboard failed: %v", err)
}
}()
if err := EmptyClipboard(); err != nil {
t.Fatalf("EmptyClipboard failed: %v", err)
}
owner, err := GetClipboardOwner()
if err != nil {
t.Fatalf("GetClipboardOwner failed: %v", err)
}
if owner != 0 {
t.Fatalf("GetClipboardOwner = %#x, want 0", owner)
}
}
func TestCreateFileReadWriteCopyDelete(t *testing.T) {
tmpDir := t.TempDir()
src := filepath.Join(tmpDir, "io_src.txt")
dst := filepath.Join(tmpDir, "io_dst.txt")
h, err := CreateFile(
src,
GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
nil,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0,
)
if err != nil {
t.Fatalf("CreateFile write failed: %v", err)
}
defer func() {
_ = CloseHandle(h)
}()
payload := []byte("hello-win32api")
var written DWORD
if err := WriteFile(h, payload, &written, nil); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
if int(written) != len(payload) {
t.Fatalf("WriteFile bytes mismatch: got=%d want=%d", written, len(payload))
}
if err := CloseHandle(h); err != nil {
t.Fatalf("CloseHandle write file failed: %v", err)
}
h = 0
rh, err := CreateFile(
src,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0,
)
if err != nil {
t.Fatalf("CreateFile read failed: %v", err)
}
defer func() {
_ = CloseHandle(rh)
}()
buf := make([]byte, 64)
var read DWORD
if err := ReadFile(rh, buf, &read, nil); err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if string(buf[:read]) != string(payload) {
t.Fatalf("ReadFile content mismatch: got=%q want=%q", string(buf[:read]), string(payload))
}
if err := CopyFile(src, dst, false); err != nil {
t.Fatalf("CopyFile failed: %v", err)
}
copied, err := os.ReadFile(dst)
if err != nil {
t.Fatalf("Read copied file failed: %v", err)
}
if string(copied) != string(payload) {
t.Fatalf("CopyFile content mismatch: got=%q want=%q", string(copied), string(payload))
}
if err := DeleteFile(dst); err != nil {
t.Fatalf("DeleteFile dst failed: %v", err)
}
if err := DeleteFile(src); err != nil {
t.Fatalf("DeleteFile src failed: %v", err)
}
if _, err := os.Stat(dst); !os.IsNotExist(err) {
t.Fatalf("dst should not exist, stat err=%v", err)
}
if _, err := os.Stat(src); !os.IsNotExist(err) {
t.Fatalf("src should not exist, stat err=%v", err)
}
}
func TestWaitForMultipleObjects(t *testing.T) {
ev1, err := CreateEventW(nil, true, true, nil)
if err != nil {
t.Fatalf("CreateEventW ev1 failed: %v", err)
}
defer func() {
_ = CloseHandle(ev1)
}()
ev2, err := CreateEventW(nil, true, false, nil)
if err != nil {
t.Fatalf("CreateEventW ev2 failed: %v", err)
}
defer func() {
_ = CloseHandle(ev2)
}()
waitAny, err := WaitForMultipleObjects([]HANDLE{ev1, ev2}, false, 0)
if err != nil {
t.Fatalf("WaitForMultipleObjects(wait any) failed: %v", err)
}
if waitAny != WAIT_OBJECT_0 {
t.Fatalf("WaitForMultipleObjects(wait any) mismatch: got=%d want=%d", waitAny, WAIT_OBJECT_0)
}
waitAll, err := WaitForMultipleObjects([]HANDLE{ev1, ev2}, true, 0)
if err != nil {
t.Fatalf("WaitForMultipleObjects(wait all) failed: %v", err)
}
if waitAll != WAIT_TIMEOUT {
t.Fatalf("WaitForMultipleObjects(wait all) mismatch: got=%d want=%d", waitAll, WAIT_TIMEOUT)
}
}
func TestOpenFileByIdInvalidHandleFails(t *testing.T) {
desc := FILE_ID_DESCRIPTOR{
DwSize: DWORD(unsafe.Sizeof(FILE_ID_DESCRIPTOR{})),
Type: FileIdType,
FileId: 1,
}
h, err := OpenFileById(0, &desc, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, nil, FILE_ATTRIBUTE_NORMAL)
if err == nil {
if h != 0 && h != HANDLE(syscall.InvalidHandle) {
_ = CloseHandle(h)
}
t.Fatalf("OpenFileById with invalid volume handle returned nil error and handle=%#x", h)
}
if h != HANDLE(syscall.InvalidHandle) {
t.Fatalf("OpenFileById invalid handle result=%#x, want INVALID_HANDLE_VALUE", h)
}
}
func TestGlobalUnlockZeroReturnCanMeanSuccess(t *testing.T) {
mem, err := GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, 16)
if err != nil {
t.Fatalf("GlobalAlloc failed: %v", err)
}
defer func() {
_ = GlobalFree(mem)
}()
ptr, err := GlobalLock(mem)
if err != nil {
t.Fatalf("GlobalLock failed: %v", err)
}
if ptr == nil {
t.Fatal("GlobalLock returned nil pointer")
}
if err := GlobalUnlock(mem); err != nil {
t.Fatalf("GlobalUnlock should succeed when lock count reaches zero: %v", err)
}
}
func TestOpenProcessTokenLookupPrivilegeAdjustRevert(t *testing.T) {
processHandle, err := syscall.GetCurrentProcess()
if err != nil {
t.Fatalf("GetCurrentProcess failed: %v", err)
}
var token TOKEN
if err := OpenProcessToken(HANDLE(processHandle), TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, &token); err != nil {
t.Fatalf("OpenProcessToken failed: %v", err)
}
defer func() {
_ = CloseHandle(HANDLE(token))
}()
var luid LUID
if err := LookupPrivilegeValue("", SE_CHANGE_NOTIFY_NAME, &luid); err != nil {
t.Fatalf("LookupPrivilegeValue failed: %v", err)
}
tp := TOKEN_PRIVILEGES{
PrivilegeCount: 1,
Privileges: [1]LUID_AND_ATTRIBUTES{
{
Luid: luid,
Attributes: SE_PRIVILEGE_ENABLED,
},
},
}
if err := AdjustTokenPrivileges(token, false, &tp, 0, nil, nil); err != nil {
t.Fatalf("AdjustTokenPrivileges failed: %v", err)
}
if err := RevertToSelf(); err != nil {
t.Fatalf("RevertToSelf failed: %v", err)
}
}
func TestIsCurrentProcessElevated(t *testing.T) {
elevated, err := IsCurrentProcessElevated()
if err != nil {
t.Fatalf("IsCurrentProcessElevated failed: %v", err)
}
t.Logf("current process elevated: %v", elevated)
}
func TestIsCurrentUserInAdminGroup(t *testing.T) {
isAdmin, err := IsCurrentUserInAdminGroup()
if err != nil {
t.Fatalf("IsCurrentUserInAdminGroup failed: %v", err)
}
t.Logf("current user in administrators group: %v", isAdmin)
}
func TestCreateProcessAsUserCreationFlagsTypeCoversDWORD(t *testing.T) {
var flags DWORD = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE
if flags&CREATE_NO_WINDOW == 0 {
t.Fatal("CREATE_NO_WINDOW should be preserved in DWORD creation flags")
}
}
func TestCreateProcess(t *testing.T) {
app, cmdLine := configureHelperProcess(t, processModeExit, 0)
si := StartupInfo{
Cb: uint32(unsafe.Sizeof(StartupInfo{})),
}
pi := ProcessInformation{}
if err := CreateProcess(
app,
cmdLine,
nil,
nil,
false,
CREATE_NO_WINDOW,
0,
"",
&si,
&pi,
); err != nil {
t.Fatalf("CreateProcess failed: %v", err)
}
defer func() {
if pi.Thread != 0 {
_ = CloseHandle(pi.Thread)
}
if pi.Process != 0 {
_ = CloseHandle(pi.Process)
}
}()
wait, err := WaitForSingleObject(pi.Process, 5000)
if err != nil {
t.Fatalf("WaitForSingleObject failed: %v", err)
}
if wait != WAIT_OBJECT_0 {
t.Fatalf("WaitForSingleObject mismatch: got=%d want=%d", wait, WAIT_OBJECT_0)
}
code, err := GetExitCodeProcess(pi.Process)
if err != nil {
t.Fatalf("GetExitCodeProcess failed: %v", err)
}
if code != 0 {
t.Fatalf("CreateProcess exit code mismatch: got=%d want=0", code)
}
}
func TestCreateProcessCmdIntegration(t *testing.T) {
requireCmdCoverage(t)
si := StartupInfo{
Cb: uint32(unsafe.Sizeof(StartupInfo{})),
}
pi := ProcessInformation{}
if err := CreateProcess(
"",
"cmd.exe /C exit 0",
nil,
nil,
false,
CREATE_NO_WINDOW,
0,
"",
&si,
&pi,
); err != nil {
t.Fatalf("CreateProcess(cmd.exe) failed: %v", err)
}
defer func() {
if pi.Thread != 0 {
_ = CloseHandle(pi.Thread)
}
if pi.Process != 0 {
_ = CloseHandle(pi.Process)
}
}()
wait, err := WaitForSingleObject(pi.Process, 5000)
if err != nil {
t.Fatalf("WaitForSingleObject(cmd.exe) failed: %v", err)
}
if wait != WAIT_OBJECT_0 {
t.Fatalf("WaitForSingleObject mismatch: got=%d want=%d", wait, WAIT_OBJECT_0)
}
}
func TestCreateProcessWithToken(t *testing.T) {
app, cmdLine := configureHelperProcess(t, processModeExit, 0)
processHandle, err := syscall.GetCurrentProcess()
if err != nil {
t.Fatalf("GetCurrentProcess failed: %v", err)
}
var token TOKEN
desired := TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY
if err := OpenProcessToken(HANDLE(processHandle), desired, &token); err != nil {
t.Fatalf("OpenProcessToken for CreateProcessWithToken failed: %v", err)
}
defer func() {
_ = CloseHandle(HANDLE(token))
}()
si := StartupInfo{
Cb: uint32(unsafe.Sizeof(StartupInfo{})),
}
pi := ProcessInformation{}
err = CreateProcessWithToken(
token,
0,
app,
cmdLine,
CREATE_NO_WINDOW,
0,
"",
&si,
&pi,
)
if err != nil {
if errno, ok := err.(syscall.Errno); ok {
if errno == 1314 || errno == syscall.ERROR_ACCESS_DENIED {
t.Skipf("CreateProcessWithToken requires extra privilege in current context: %v", err)
}
}
t.Fatalf("CreateProcessWithToken failed: %v", err)
}
defer func() {
if pi.Thread != 0 {
_ = CloseHandle(pi.Thread)
}
if pi.Process != 0 {
_ = CloseHandle(pi.Process)
}
}()
wait, err := WaitForSingleObject(pi.Process, 5000)
if err != nil {
t.Fatalf("WaitForSingleObject(CreateProcessWithToken) failed: %v", err)
}
if wait != WAIT_OBJECT_0 {
t.Fatalf("CreateProcessWithToken wait mismatch: got=%d want=%d", wait, WAIT_OBJECT_0)
}
code, err := GetExitCodeProcess(pi.Process)
if err != nil {
t.Fatalf("GetExitCodeProcess(CreateProcessWithToken) failed: %v", err)
}
if code != 0 {
t.Fatalf("CreateProcessWithToken exit code mismatch: got=%d want=0", code)
}
}
func TestShellExecuteExRejectsInvalidStruct(t *testing.T) {
var info SHELLEXECUTEINFOW
if err := ShellExecuteEx(&info); err == nil {
t.Fatal("expected ShellExecuteEx to reject zero-value struct")
}
}
func TestCreateEnvironmentBlockRejectsNilOutput(t *testing.T) {
err := CreateEnvironmentBlock(nil, 0, 0)
if err == nil {
t.Fatal("expected CreateEnvironmentBlock to reject nil output pointer")
}
}