]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.24] os/user: user random name for the test user account
authorqmuntal <quimmuntal@gmail.com>
Thu, 24 Jul 2025 13:38:35 +0000 (15:38 +0200)
committerGopher Robot <gobot@golang.org>
Wed, 30 Jul 2025 18:52:37 +0000 (11:52 -0700)
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.

For #73523.
Fixes #74760.

Cq-Include-Trybots: luci.golang.try:go1.24-windows-amd64-longtest
Change-Id: Ib2283a888437420502b1c11d876c975f5af4bc03
Reviewed-on: https://go-review.googlesource.com/c/go/+/690175
Auto-Submit: Quim Muntal <quimmuntal@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
(cherry picked from commit 374e3be2eb9b546ef1340f750e343c15a8f87dde)
Reviewed-on: https://go-review.googlesource.com/c/go/+/690556
Reviewed-by: Mark Freeman <mark@golang.org>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>

src/os/user/user_windows_test.go

index c71503372e01073fd8e4f16c4c4458f3b2b6c051..c8ee10cbb9fcd811d8d8e92d90e9d04caba80e13 100644 (file)
@@ -7,6 +7,7 @@ package user
 import (
        "crypto/rand"
        "encoding/base64"
+       "encoding/binary"
        "errors"
        "fmt"
        "internal/syscall/windows"
@@ -15,11 +16,92 @@ import (
        "os/exec"
        "runtime"
        "strconv"
+       "strings"
        "syscall"
        "testing"
+       "unicode"
+       "unicode/utf8"
        "unsafe"
 )
 
+// addUserAccount creates a local user account.
+// It returns the name and password of the new account.
+// Multiple programs or goroutines calling addUserAccount simultaneously will not choose the same directory.
+func addUserAccount(t *testing.T) (name, password string) {
+       t.TempDir()
+       pattern := t.Name()
+       // Windows limits the user name to 20 characters,
+       // leave space for a 4 digits random suffix.
+       const maxNameLen, suffixLen = 20, 4
+       pattern = pattern[:min(len(pattern), maxNameLen-suffixLen)]
+       // Drop unusual characters from the account name.
+       mapper := func(r rune) rune {
+               if r < utf8.RuneSelf {
+                       if '0' <= r && r <= '9' ||
+                               'a' <= r && r <= 'z' ||
+                               'A' <= r && r <= 'Z' {
+                               return r
+                       }
+               } else if unicode.IsLetter(r) || unicode.IsNumber(r) {
+                       return r
+               }
+               return -1
+       }
+       pattern = strings.Map(mapper, pattern)
+
+       // Generate a long random password.
+       var pwd [33]byte
+       rand.Read(pwd[:])
+       // Add special chars to ensure it satisfies password requirements.
+       password = base64.StdEncoding.EncodeToString(pwd[:]) + "_-As@!%*(1)4#2"
+       password16, err := syscall.UTF16PtrFromString(password)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       try := 0
+       for {
+               // Calculate a random suffix to append to the user name.
+               var suffix [2]byte
+               rand.Read(suffix[:])
+               suffixStr := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(suffix[:])), 10)
+               name := pattern + suffixStr[:min(len(suffixStr), suffixLen)]
+               name16, err := syscall.UTF16PtrFromString(name)
+               if err != nil {
+                       t.Fatal(err)
+               }
+               // Create user.
+               userInfo := windows.UserInfo1{
+                       Name:     name16,
+                       Password: password16,
+                       Priv:     windows.USER_PRIV_USER,
+               }
+               err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
+               if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
+                       t.Skip("skipping test; don't have permission to create user")
+               }
+               // If the user already exists, try again with a different name.
+               if errors.Is(err, windows.NERR_UserExists) {
+                       if try++; try < 1000 {
+                               t.Log("user already exists, trying again with a different name")
+                               continue
+                       }
+               }
+               if err != nil {
+                       t.Fatalf("NetUserAdd failed: %v", err)
+               }
+               // Delete the user when the test is done.
+               t.Cleanup(func() {
+                       if err := windows.NetUserDel(nil, name16); err != nil {
+                               if !errors.Is(err, windows.NERR_UserNotFound) {
+                                       t.Fatal(err)
+                               }
+                       }
+               })
+               return name, password
+       }
+}
+
 // windowsTestAccount creates a test user and returns a token for that user.
 // If the user already exists, it will be deleted and recreated.
 // The caller is responsible for closing the token.
@@ -31,47 +113,15 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
                // See https://dev.go/issue/70396.
                t.Skip("skipping non-hermetic test outside of Go builders")
        }
-       const testUserName = "GoStdTestUser01"
-       var password [33]byte
-       rand.Read(password[:])
-       // Add special chars to ensure it satisfies password requirements.
-       pwd := base64.StdEncoding.EncodeToString(password[:]) + "_-As@!%*(1)4#2"
-       name, err := syscall.UTF16PtrFromString(testUserName)
+       name, password := addUserAccount(t)
+       name16, err := syscall.UTF16PtrFromString(name)
        if err != nil {
                t.Fatal(err)
        }
-       pwd16, err := syscall.UTF16PtrFromString(pwd)
+       pwd16, err := syscall.UTF16PtrFromString(password)
        if err != nil {
                t.Fatal(err)
        }
-       userInfo := windows.UserInfo1{
-               Name:     name,
-               Password: pwd16,
-               Priv:     windows.USER_PRIV_USER,
-       }
-       // Create user.
-       err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
-       if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
-               t.Skip("skipping test; don't have permission to create user")
-       }
-       if errors.Is(err, windows.NERR_UserExists) {
-               // User already exists, delete and recreate.
-               if err = windows.NetUserDel(nil, name); err != nil {
-                       t.Fatal(err)
-               }
-               if err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil); err != nil {
-                       t.Fatal(err)
-               }
-       } else if err != nil {
-               t.Fatal(err)
-       }
-       t.Cleanup(func() {
-               if err = windows.NetUserDel(nil, name); err != nil {
-                       if !errors.Is(err, windows.NERR_UserNotFound) {
-                               t.Fatal(err)
-                       }
-               }
-       })
        domain, err := syscall.UTF16PtrFromString(".")
        if err != nil {
                t.Fatal(err)
@@ -79,13 +129,13 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
        const LOGON32_PROVIDER_DEFAULT = 0
        const LOGON32_LOGON_INTERACTIVE = 2
        var token syscall.Token
-       if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
+       if err = windows.LogonUser(name16, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
                t.Fatal(err)
        }
        t.Cleanup(func() {
                token.Close()
        })
-       usr, err := Lookup(testUserName)
+       usr, err := Lookup(name)
        if err != nil {
                t.Fatal(err)
        }