Skip to content

Commit 374e3be

Browse files
qmuntalgopherbot
authored andcommitted
os/user: user random name for the test user account
TestImpersonated and TestGroupIdsTestUser are flaky due to sporadic failures when creating the test user account when running the tests from different processes at the same time. This flakiness can be fixed by using a random name for the test user account. Fixes #73523 Fixes #74727 Fixes #74728 Fixes #74729 Fixes #74745 Fixes #74751 Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-longtest Change-Id: Ib2283a888437420502b1c11d876c975f5af4bc03 Reviewed-on: https://go-review.googlesource.com/c/go/+/690175 Auto-Submit: Quim Muntal <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> TryBot-Bypass: Dmitri Shuralyov <[email protected]>
1 parent 1aa1546 commit 374e3be

File tree

1 file changed

+87
-37
lines changed

1 file changed

+87
-37
lines changed

src/os/user/user_windows_test.go

Lines changed: 87 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package user
77
import (
88
"crypto/rand"
99
"encoding/base64"
10+
"encoding/binary"
1011
"errors"
1112
"fmt"
1213
"internal/syscall/windows"
@@ -16,11 +17,92 @@ import (
1617
"runtime"
1718
"slices"
1819
"strconv"
20+
"strings"
1921
"syscall"
2022
"testing"
23+
"unicode"
24+
"unicode/utf8"
2125
"unsafe"
2226
)
2327

28+
// addUserAccount creates a local user account.
29+
// It returns the name and password of the new account.
30+
// Multiple programs or goroutines calling addUserAccount simultaneously will not choose the same directory.
31+
func addUserAccount(t *testing.T) (name, password string) {
32+
t.TempDir()
33+
pattern := t.Name()
34+
// Windows limits the user name to 20 characters,
35+
// leave space for a 4 digits random suffix.
36+
const maxNameLen, suffixLen = 20, 4
37+
pattern = pattern[:min(len(pattern), maxNameLen-suffixLen)]
38+
// Drop unusual characters from the account name.
39+
mapper := func(r rune) rune {
40+
if r < utf8.RuneSelf {
41+
if '0' <= r && r <= '9' ||
42+
'a' <= r && r <= 'z' ||
43+
'A' <= r && r <= 'Z' {
44+
return r
45+
}
46+
} else if unicode.IsLetter(r) || unicode.IsNumber(r) {
47+
return r
48+
}
49+
return -1
50+
}
51+
pattern = strings.Map(mapper, pattern)
52+
53+
// Generate a long random password.
54+
var pwd [33]byte
55+
rand.Read(pwd[:])
56+
// Add special chars to ensure it satisfies password requirements.
57+
password = base64.StdEncoding.EncodeToString(pwd[:]) + "_-As@!%*(1)4#2"
58+
password16, err := syscall.UTF16PtrFromString(password)
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
63+
try := 0
64+
for {
65+
// Calculate a random suffix to append to the user name.
66+
var suffix [2]byte
67+
rand.Read(suffix[:])
68+
suffixStr := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(suffix[:])), 10)
69+
name := pattern + suffixStr[:min(len(suffixStr), suffixLen)]
70+
name16, err := syscall.UTF16PtrFromString(name)
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
// Create user.
75+
userInfo := windows.UserInfo1{
76+
Name: name16,
77+
Password: password16,
78+
Priv: windows.USER_PRIV_USER,
79+
}
80+
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
81+
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
82+
t.Skip("skipping test; don't have permission to create user")
83+
}
84+
// If the user already exists, try again with a different name.
85+
if errors.Is(err, windows.NERR_UserExists) {
86+
if try++; try < 1000 {
87+
t.Log("user already exists, trying again with a different name")
88+
continue
89+
}
90+
}
91+
if err != nil {
92+
t.Fatalf("NetUserAdd failed: %v", err)
93+
}
94+
// Delete the user when the test is done.
95+
t.Cleanup(func() {
96+
if err := windows.NetUserDel(nil, name16); err != nil {
97+
if !errors.Is(err, windows.NERR_UserNotFound) {
98+
t.Fatal(err)
99+
}
100+
}
101+
})
102+
return name, password
103+
}
104+
}
105+
24106
// windowsTestAccount creates a test user and returns a token for that user.
25107
// If the user already exists, it will be deleted and recreated.
26108
// The caller is responsible for closing the token.
@@ -32,61 +114,29 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
32114
// See https://dev.go/issue/70396.
33115
t.Skip("skipping non-hermetic test outside of Go builders")
34116
}
35-
const testUserName = "GoStdTestUser01"
36-
var password [33]byte
37-
rand.Read(password[:])
38-
// Add special chars to ensure it satisfies password requirements.
39-
pwd := base64.StdEncoding.EncodeToString(password[:]) + "_-As@!%*(1)4#2"
40-
name, err := syscall.UTF16PtrFromString(testUserName)
117+
name, password := addUserAccount(t)
118+
name16, err := syscall.UTF16PtrFromString(name)
41119
if err != nil {
42120
t.Fatal(err)
43121
}
44-
pwd16, err := syscall.UTF16PtrFromString(pwd)
122+
pwd16, err := syscall.UTF16PtrFromString(password)
45123
if err != nil {
46124
t.Fatal(err)
47125
}
48-
userInfo := windows.UserInfo1{
49-
Name: name,
50-
Password: pwd16,
51-
Priv: windows.USER_PRIV_USER,
52-
}
53-
// Create user.
54-
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
55-
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
56-
t.Skip("skipping test; don't have permission to create user")
57-
}
58-
if errors.Is(err, windows.NERR_UserExists) {
59-
// User already exists, delete and recreate.
60-
if err = windows.NetUserDel(nil, name); err != nil {
61-
t.Fatal(err)
62-
}
63-
if err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil); err != nil {
64-
t.Fatal(err)
65-
}
66-
} else if err != nil {
67-
t.Fatal(err)
68-
}
69-
t.Cleanup(func() {
70-
if err = windows.NetUserDel(nil, name); err != nil {
71-
if !errors.Is(err, windows.NERR_UserNotFound) {
72-
t.Fatal(err)
73-
}
74-
}
75-
})
76126
domain, err := syscall.UTF16PtrFromString(".")
77127
if err != nil {
78128
t.Fatal(err)
79129
}
80130
const LOGON32_PROVIDER_DEFAULT = 0
81131
const LOGON32_LOGON_INTERACTIVE = 2
82132
var token syscall.Token
83-
if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
133+
if err = windows.LogonUser(name16, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
84134
t.Fatal(err)
85135
}
86136
t.Cleanup(func() {
87137
token.Close()
88138
})
89-
usr, err := Lookup(testUserName)
139+
usr, err := Lookup(name)
90140
if err != nil {
91141
t.Fatal(err)
92142
}

0 commit comments

Comments
 (0)