@@ -22,6 +22,7 @@ import (
2222 "os/exec"
2323 "path/filepath"
2424 "strings"
25+ "sync"
2526 "testing"
2627 "time"
2728
@@ -44,8 +45,22 @@ type Harness struct {
4445 // Mux serves the native HomeCloud API (register routes with Router).
4546 Mux * http.ServeMux
4647 Router * httpx.Router
47- // Audit records every audited call (action, resource).
48- Audit []string
48+ // Audit records every audited call (action, resource); read it with AuditLog.
49+ Audit []string
50+ auditMu sync.Mutex
51+ // barrier orders a test's setup writes (fields set on services after New)
52+ // before the requests that read them; the race detector can't see that
53+ // ordering through the AWS CLI subprocess.
54+ barrier sync.Mutex
55+ }
56+
57+ func (h * Harness ) sync () { h .barrier .Lock (); h .barrier .Unlock () }
58+
59+ // AuditLog returns a copy of the audited calls so far.
60+ func (h * Harness ) AuditLog () []string {
61+ h .auditMu .Lock ()
62+ defer h .auditMu .Unlock ()
63+ return append ([]string (nil ), h .Audit ... )
4964}
5065
5166// New starts an endpoint with IAM (root user) and STS.
@@ -65,12 +80,15 @@ func New(t *testing.T) *Harness {
6580 im .RegisterAWS ()
6681 h := & Harness {Env : env , IAM : im , Secrets : sec , AccessKeyID : boot .AccessKeyID , SecretKey : boot .SecretKey , Mux : http .NewServeMux ()}
6782 audit := func (p * httpx.Principal , action , resource string , r * http.Request , status int , took time.Duration ) {
83+ h .auditMu .Lock ()
6884 h .Audit = append (h .Audit , action + " " + resource )
85+ h .auditMu .Unlock ()
6986 }
7087 h .Router = & httpx.Router {Mux : h .Mux , Auth : im , Account : env .AccountID , Audit : audit }
7188 im .Routes (h .Router )
7289 aws := & awsapi.Handler {Creds : im , Account : env .AccountID , Audit : audit }
7390 srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
91+ h .sync ()
7492 if awsapi .Match (r ) && ! strings .HasPrefix (r .Header .Get ("Authorization" ), "Bearer " ) {
7593 aws .ServeHTTP (w , r )
7694 return
@@ -130,6 +148,7 @@ func (h *Harness) AWSErr(t *testing.T, args ...string) (string, error) {
130148// AWSAs runs the AWS CLI with the given credentials.
131149func (h * Harness ) AWSAs (t * testing.T , akid , secret , token string , args ... string ) (string , error ) {
132150 t .Helper ()
151+ h .sync ()
133152 cmd := exec .Command (awsBin (t ), append ([]string {"--output" , "json" }, args ... )... )
134153 cmd .Env = h .Environ (akid , secret , token )
135154 var out bytes.Buffer
@@ -173,6 +192,7 @@ func (h *Harness) Python(t *testing.T, script string) string {
173192 if err := os .WriteFile (f , []byte (src ), 0o600 ); err != nil {
174193 t .Fatal (err )
175194 }
195+ h .sync ()
176196 cmd := exec .Command (python (t ), f )
177197 cmd .Env = h .Environ (h .AccessKeyID , h .SecretKey , "" )
178198 var out , errb bytes.Buffer
@@ -215,6 +235,7 @@ func (h *Harness) nativeAsRoot(t *testing.T, method, path string, body any) []by
215235 } else {
216236 rd = bytes .NewReader (nil )
217237 }
238+ h .sync ()
218239 req , _ := http .NewRequest (method , h .URL + path , rd )
219240 req .Header .Set ("Authorization" , "Bearer " + h .AccessKeyID + ":" + h .SecretKey )
220241 req .Header .Set ("Content-Type" , "application/json" )
0 commit comments