github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/test_common.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 //go:build !production 5 // +build !production 6 7 package libkb 8 9 import ( 10 "crypto/rand" 11 "encoding/hex" 12 "fmt" 13 "os" 14 "path" 15 "path/filepath" 16 "runtime" 17 "strings" 18 "sync" 19 "time" 20 21 "golang.org/x/net/context" 22 "golang.org/x/sync/errgroup" 23 24 "github.com/keybase/client/go/gregor" 25 "github.com/keybase/client/go/logger" 26 "github.com/keybase/client/go/protocol/gregor1" 27 keybase1 "github.com/keybase/client/go/protocol/keybase1" 28 "github.com/stretchr/testify/require" 29 ) 30 31 // TestConfig tracks libkb config during a test 32 type TestConfig struct { 33 configFileName string 34 } 35 36 func (c *TestConfig) GetConfigFileName() string { return c.configFileName } 37 38 // TestingTB is a copy of the exported parts of testing.TB. We define 39 // this in order to avoid pulling in the "testing" package in exported 40 // code. 41 type TestingTB interface { 42 Error(args ...interface{}) 43 Errorf(format string, args ...interface{}) 44 Fail() 45 FailNow() 46 Failed() bool 47 Fatal(args ...interface{}) 48 Fatalf(format string, args ...interface{}) 49 Log(args ...interface{}) 50 Logf(format string, args ...interface{}) 51 Name() string 52 Skip(args ...interface{}) 53 SkipNow() 54 Skipf(format string, args ...interface{}) 55 Skipped() bool 56 Helper() 57 } 58 59 func MakeThinGlobalContextForTesting(t TestingTB) *GlobalContext { 60 g := NewGlobalContext().Init() 61 g.Log = logger.NewTestLogger(t) 62 return g 63 } 64 65 func makeLogGetter(t TestingTB) func() logger.Logger { 66 return func() logger.Logger { return logger.NewTestLogger(t) } 67 } 68 69 func (c *TestConfig) CleanTest() { 70 if c.configFileName != "" { 71 os.Remove(c.configFileName) 72 } 73 } 74 75 // TestOutput is a mock interface for capturing and testing output 76 type TestOutput struct { 77 expected string 78 t TestingTB 79 called *bool 80 } 81 82 func NewTestOutput(e string, t TestingTB, c *bool) TestOutput { 83 return TestOutput{e, t, c} 84 } 85 86 func (to TestOutput) Write(p []byte) (n int, err error) { 87 output := string(p) 88 if to.expected != output { 89 to.t.Errorf("Expected output %s, got %s", to.expected, output) 90 } 91 *to.called = true 92 return len(p), nil 93 } 94 95 type TestContext struct { 96 G *GlobalContext 97 PrevGlobal *GlobalContext 98 Tp *TestParameters 99 // TODO: Rename this to TB. 100 T TestingTB 101 eg *errgroup.Group 102 cleanupCh chan struct{} 103 origLog logger.Logger 104 } 105 106 func (tc *TestContext) Cleanup() { 107 // stop the background logger 108 close(tc.cleanupCh) 109 err := tc.eg.Wait() 110 require.NoError(tc.T, err) 111 112 tc.G.Log.Debug("global context shutdown:") 113 mctx := NewMetaContextForTest(*tc) 114 err = tc.G.Shutdown(mctx) // could error due to missing pid file 115 if err != nil { 116 tc.G.Log.Warning("tc.G.Shutdown failed: %s", err) 117 } 118 if len(tc.Tp.Home) > 0 { 119 tc.G.Log.Debug("clearing stored secrets:") 120 err := tc.ClearAllStoredSecrets() 121 tc.G.Log.Debug("cleaning up %s", tc.Tp.Home) 122 os.RemoveAll(tc.Tp.Home) 123 require.NoError(tc.T, err) 124 } 125 tc.G.Log.Debug("cleanup complete") 126 127 // Don't use the test logger anymore, since it's now out of scope 128 tc.G.Log = tc.origLog 129 } 130 131 func (tc *TestContext) Logout() error { 132 return NewMetaContextForTest(*tc).LogoutKillSecrets() 133 } 134 135 func (tc TestContext) MoveGpgKeyringTo(dst TestContext) error { 136 137 mv := func(f string) (err error) { 138 return os.Rename(path.Join(tc.Tp.GPGHome, f), filepath.Join(dst.Tp.GPGHome, f)) 139 } 140 141 if err := mv("secring.gpg"); err != nil { 142 return err 143 } 144 return mv("pubring.gpg") 145 } 146 147 func (tc *TestContext) GenerateGPGKeyring(ids ...string) error { 148 tc.T.Logf("generating gpg keyring in %s", tc.Tp.GPGHome) 149 fsk, err := os.Create(path.Join(tc.Tp.GPGHome, "secring.gpg")) 150 if err != nil { 151 return err 152 } 153 defer fsk.Close() 154 fpk, err := os.Create(path.Join(tc.Tp.GPGHome, "pubring.gpg")) 155 if err != nil { 156 return err 157 } 158 defer fpk.Close() 159 160 for _, id := range ids { 161 bundle, err := tc.MakePGPKey(id) 162 if err != nil { 163 return err 164 } 165 166 err = bundle.Entity.SerializePrivate(fsk, nil) 167 if err != nil { 168 return err 169 } 170 171 err = bundle.Entity.Serialize(fpk) 172 if err != nil { 173 return err 174 } 175 } 176 177 return nil 178 } 179 180 func (tc *TestContext) MakePGPKey(id string) (*PGPKeyBundle, error) { 181 arg := PGPGenArg{ 182 PrimaryBits: 1024, 183 SubkeyBits: 1024, 184 PGPUids: []string{id}, 185 } 186 err := arg.Init() 187 if err != nil { 188 return nil, err 189 } 190 err = arg.CreatePGPIDs() 191 if err != nil { 192 return nil, err 193 } 194 return GeneratePGPKeyBundle(tc.G, arg, tc.G.UI.GetLogUI()) 195 } 196 197 // SimulatServiceRestart simulates a shutdown and restart (for client 198 // state). Used by tests that need to clear out cached login state 199 // without logging out. 200 func (tc *TestContext) SimulateServiceRestart() { 201 tc.G.simulateServiceRestart() 202 } 203 204 func (tc TestContext) ClearAllStoredSecrets() error { 205 m := NewMetaContextForTest(tc) 206 usernames, err := tc.G.GetUsersWithStoredSecrets(m.Ctx()) 207 if err != nil { 208 return err 209 } 210 for _, username := range usernames { 211 nu := NewNormalizedUsername(username) 212 err = ClearStoredSecret(m, nu) 213 if err != nil { 214 return err 215 } 216 } 217 return nil 218 } 219 220 func (tc TestContext) Context() context.Context { return WithLogTag(context.Background(), "TST") } 221 func (tc TestContext) MetaContext() MetaContext { return NewMetaContextForTest(tc) } 222 223 var setupTestMu sync.Mutex 224 225 func setupTestContext(tb TestingTB, name string, tcPrev *TestContext) (tc TestContext, err error) { 226 setupTestMu.Lock() 227 defer setupTestMu.Unlock() 228 tc.Tp = &TestParameters{ 229 SecretStorePrimingDisabled: true, 230 } 231 232 g := NewGlobalContext() 233 234 // In debugging mode, dump all log, don't use the test logger. 235 // We only use the environment variable to discover debug mode 236 tc.origLog = g.Log 237 if val, _ := getEnvBool("KEYBASE_DEBUG"); !val { 238 g.Log = logger.NewTestLogger(tb) 239 } 240 241 buf := make([]byte, 5) 242 if _, err = rand.Read(buf); err != nil { 243 return 244 } 245 // Uniquify name, since multiple tests may use the same name. 246 develName := fmt.Sprintf("%s_%s", name, hex.EncodeToString(buf)) 247 248 g.Init() 249 g.Log.Debug("SetupTest %s", develName) 250 251 // Set up our testing parameters. We might add others later on 252 if tcPrev != nil { 253 tc.Tp = tcPrev.Tp 254 } else if tc.Tp.Home, err = os.MkdirTemp(os.TempDir(), develName); err != nil { 255 return 256 } 257 258 g.Log.Debug("SetupTest home directory: %s", tc.Tp.Home) 259 260 // might as well be the same directory... 261 tc.Tp.GPGHome = tc.Tp.Home 262 tc.Tp.GPGOptions = []string{"--homedir=" + tc.Tp.GPGHome} 263 264 tc.Tp.Debug = false 265 tc.Tp.Devel = true 266 tc.Tp.DevelName = develName 267 tc.Tp.DevelPrefix = name 268 269 g.Env.Test = tc.Tp 270 271 // SecretStoreFile needs test home directory 272 g.secretStoreMu.Lock() 273 m := NewMetaContextTODO(g) 274 g.secretStore = NewSecretStoreLocked(m) 275 g.secretStoreMu.Unlock() 276 277 err = g.ConfigureLogging(nil) 278 if err != nil { 279 return TestContext{}, err 280 } 281 282 if err = g.ConfigureAPI(); err != nil { 283 return 284 } 285 286 // use stub engine for external api 287 g.XAPI = NewStubAPIEngine(g) 288 289 if err = g.ConfigureConfig(); err != nil { 290 return 291 } 292 if err = g.ConfigureTimers(); err != nil { 293 return 294 } 295 if err = g.ConfigureCaches(); err != nil { 296 return 297 } 298 if err = g.ConfigureMerkleClient(); err != nil { 299 return 300 } 301 g.UI = &nullui{gctx: g} 302 if err = g.UI.Configure(); err != nil { 303 return 304 } 305 if err = g.ConfigureKeyring(); err != nil { 306 return 307 } 308 309 g.GregorState = &FakeGregorState{} 310 g.SetUIDMapper(NewTestUIDMapper(g.GetUPAKLoader())) 311 tc.G = g 312 tc.T = tb 313 314 // Periodically log in the background until `Cleanup` is called. Tests that 315 // forget to call this will panic because of logging after the test 316 // completes. 317 cleanupCh := make(chan struct{}) 318 tc.cleanupCh = cleanupCh 319 tc.eg = &errgroup.Group{} 320 tc.eg.Go(func() error { 321 log := g.Log.CloneWithAddedDepth(1) 322 log.Debug("TestContext bg loop starting up") 323 for { 324 select { 325 case <-cleanupCh: 326 log.Debug("TestContext bg loop shutting down") 327 return nil 328 case <-time.After(time.Second): 329 log.Debug("TestContext bg loop not cleaned up yet") 330 } 331 } 332 }) 333 334 return 335 } 336 337 // The depth argument is now ignored. 338 func SetupTest(tb TestingTB, name string, depth int) (tc TestContext) { 339 var err error 340 tc, err = setupTestContext(tb, name, nil) 341 if err != nil { 342 tb.Fatal(err) 343 } 344 if os.Getenv("KEYBASE_LOG_SETUPTEST_FUNCS") != "" { 345 depth := 0 346 // Walk up the stackframe looking for the function that starts with "Test". 347 for { 348 pc, file, line, ok := runtime.Caller(depth) 349 if ok { 350 fn := runtime.FuncForPC(pc) 351 fnName := filepath.Base(fn.Name()) 352 if !strings.Contains(fnName, ".Test") { 353 // Not the right frame. Bump depth and loop again. 354 depth++ 355 continue 356 } 357 // This is the right frame. 358 fmt.Fprintf(os.Stderr, "- SetupTest %s %s:%d\n", filepath.Base(fn.Name()), filepath.Base(file), line) 359 } else { 360 // We've walked off the end of the stack without finding what we were looking for. 361 fmt.Fprintf(os.Stderr, "- SetupTest FAILED TO GET STACKFRAME") 362 } 363 break 364 } 365 } 366 367 AddEnvironmentFeatureForTest(tc, EnvironmentFeatureAllowHighSkips) 368 // If journeycards are disabled, this may be helpful to get tests to pass: 369 // AddEnvironmentFeatureForTest(tc, FeatureJourneycard) 370 // AddEnvironmentFeatureForTest(tc, FeatureJourneycard) 371 372 return tc 373 } 374 375 func (tc *TestContext) SetRuntimeDir(s string) { 376 tc.Tp.RuntimeDir = s 377 tc.G.Env.Test.RuntimeDir = s 378 } 379 380 func (tc TestContext) Clone() (ret TestContext) { 381 var err error 382 ret, err = setupTestContext(tc.T, "", &tc) 383 if err != nil { 384 tc.T.Fatal(err) 385 } 386 return ret 387 } 388 389 type nullui struct { 390 gctx *GlobalContext 391 } 392 393 func (n *nullui) Printf(f string, args ...interface{}) (int, error) { 394 return fmt.Printf(f, args...) 395 } 396 397 func (n *nullui) PrintfStderr(f string, args ...interface{}) (int, error) { 398 return fmt.Fprintf(os.Stderr, f, args...) 399 } 400 401 func (n *nullui) PrintfUnescaped(f string, args ...interface{}) (int, error) { 402 return fmt.Printf(f, args...) 403 } 404 405 func (n *nullui) GetDumbOutputUI() DumbOutputUI { 406 return n 407 } 408 409 func (n *nullui) GetIdentifyUI() IdentifyUI { 410 return nil 411 } 412 func (n *nullui) GetIdentifyTrackUI() IdentifyUI { 413 return nil 414 } 415 func (n *nullui) GetLoginUI() LoginUI { 416 return nil 417 } 418 func (n *nullui) GetTerminalUI() TerminalUI { 419 return nil 420 } 421 func (n *nullui) GetSecretUI() SecretUI { 422 return nil 423 } 424 func (n *nullui) GetProveUI() ProveUI { 425 return nil 426 } 427 func (n *nullui) GetGPGUI() GPGUI { 428 return nil 429 } 430 func (n *nullui) GetLogUI() LogUI { 431 return n.gctx.Log 432 } 433 func (n *nullui) GetPgpUI() PgpUI { 434 return nil 435 } 436 func (n *nullui) GetProvisionUI(KexRole) ProvisionUI { 437 return nil 438 } 439 func (n *nullui) Prompt(string, bool, Checker) (string, error) { 440 return "", nil 441 } 442 func (n *nullui) PromptForConfirmation(prompt string) error { 443 return nil 444 } 445 func (n *nullui) Configure() error { 446 return nil 447 } 448 func (n *nullui) Shutdown() error { 449 return nil 450 } 451 452 type TestSecretUI struct { 453 Passphrase string 454 StoreSecret bool 455 CalledGetPassphrase bool 456 } 457 458 func (t *TestSecretUI) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (keybase1.GetPassphraseRes, error) { 459 t.CalledGetPassphrase = true 460 return keybase1.GetPassphraseRes{ 461 Passphrase: t.Passphrase, 462 StoreSecret: t.StoreSecret, 463 }, nil 464 } 465 466 type TestCancelSecretUI struct { 467 CallCount int 468 } 469 470 func (t *TestCancelSecretUI) GetPassphrase(_ keybase1.GUIEntryArg, _ *keybase1.SecretEntryArg) (keybase1.GetPassphraseRes, error) { 471 t.CallCount++ 472 return keybase1.GetPassphraseRes{}, InputCanceledError{} 473 } 474 475 type TestCountSecretUI struct { 476 Passphrase string 477 StoreSecret bool 478 CallCount int 479 } 480 481 func (t *TestCountSecretUI) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (keybase1.GetPassphraseRes, error) { 482 t.CallCount++ 483 return keybase1.GetPassphraseRes{ 484 Passphrase: t.Passphrase, 485 StoreSecret: t.StoreSecret, 486 }, nil 487 } 488 489 type TestLoginUI struct { 490 Username string 491 RevokeBackup bool 492 CalledGetEmailOrUsername int 493 ResetAccount keybase1.ResetPromptResponse 494 PassphraseRecovery bool 495 } 496 497 var _ LoginUI = (*TestLoginUI)(nil) 498 499 func (t *TestLoginUI) GetEmailOrUsername(_ context.Context, _ int) (string, error) { 500 t.CalledGetEmailOrUsername++ 501 return t.Username, nil 502 } 503 504 func (t *TestLoginUI) PromptRevokePaperKeys(_ context.Context, arg keybase1.PromptRevokePaperKeysArg) (bool, error) { 505 return t.RevokeBackup, nil 506 } 507 508 func (t *TestLoginUI) DisplayPaperKeyPhrase(_ context.Context, arg keybase1.DisplayPaperKeyPhraseArg) error { 509 return nil 510 } 511 512 func (t *TestLoginUI) DisplayPrimaryPaperKey(_ context.Context, arg keybase1.DisplayPrimaryPaperKeyArg) error { 513 return nil 514 } 515 516 func (t *TestLoginUI) PromptResetAccount(_ context.Context, arg keybase1.PromptResetAccountArg) (keybase1.ResetPromptResponse, error) { 517 return t.ResetAccount, nil 518 } 519 520 func (t *TestLoginUI) DisplayResetProgress(_ context.Context, arg keybase1.DisplayResetProgressArg) error { 521 return nil 522 } 523 524 func (t *TestLoginUI) ExplainDeviceRecovery(_ context.Context, arg keybase1.ExplainDeviceRecoveryArg) error { 525 return nil 526 } 527 528 func (t *TestLoginUI) PromptPassphraseRecovery(_ context.Context, arg keybase1.PromptPassphraseRecoveryArg) (bool, error) { 529 return t.PassphraseRecovery, nil 530 } 531 532 func (t *TestLoginUI) ChooseDeviceToRecoverWith(_ context.Context, arg keybase1.ChooseDeviceToRecoverWithArg) (keybase1.DeviceID, error) { 533 return "", nil 534 } 535 536 func (t *TestLoginUI) DisplayResetMessage(_ context.Context, arg keybase1.DisplayResetMessageArg) error { 537 return nil 538 } 539 540 type TestLoginCancelUI struct { 541 TestLoginUI 542 } 543 544 func (t *TestLoginCancelUI) GetEmailOrUsername(_ context.Context, _ int) (string, error) { 545 return "", InputCanceledError{} 546 } 547 548 type FakeGregorState struct { 549 dismissedIDs []gregor.MsgID 550 } 551 552 var _ GregorState = (*FakeGregorState)(nil) 553 554 func (f *FakeGregorState) State(_ context.Context) (gregor.State, error) { 555 return gregor1.State{}, nil 556 } 557 558 func (f *FakeGregorState) UpdateCategory(ctx context.Context, cat string, body []byte, 559 dtime gregor1.TimeOrOffset) (gregor1.MsgID, error) { 560 return gregor1.MsgID{}, nil 561 } 562 563 func (f *FakeGregorState) InjectItem(ctx context.Context, cat string, body []byte, dtime gregor1.TimeOrOffset) (gregor1.MsgID, error) { 564 return gregor1.MsgID{}, nil 565 } 566 567 func (f *FakeGregorState) DismissItem(_ context.Context, cli gregor1.IncomingInterface, id gregor.MsgID) error { 568 f.dismissedIDs = append(f.dismissedIDs, id) 569 return nil 570 } 571 572 func (f *FakeGregorState) LocalDismissItem(ctx context.Context, id gregor.MsgID) error { 573 return nil 574 } 575 576 func (f *FakeGregorState) PeekDismissedIDs() []gregor.MsgID { 577 return f.dismissedIDs 578 } 579 580 func (f *FakeGregorState) DismissCategory(ctx context.Context, cat gregor1.Category) error { 581 return nil 582 } 583 584 type TestUIDMapper struct { 585 ul UPAKLoader 586 } 587 588 func NewTestUIDMapper(ul UPAKLoader) TestUIDMapper { 589 return TestUIDMapper{ 590 ul: ul, 591 } 592 } 593 594 func (t TestUIDMapper) ClearUIDFullName(_ context.Context, _ UIDMapperContext, _ keybase1.UID) error { 595 return nil 596 } 597 598 func (t TestUIDMapper) ClearUIDAtEldestSeqno(_ context.Context, _ UIDMapperContext, _ keybase1.UID, _ keybase1.Seqno) error { 599 return nil 600 } 601 602 func (t TestUIDMapper) CheckUIDAgainstUsername(uid keybase1.UID, un NormalizedUsername) bool { 603 return true 604 } 605 606 func (t TestUIDMapper) MapHardcodedUsernameToUID(un NormalizedUsername) keybase1.UID { 607 if un.String() == "max" { 608 return keybase1.UID("dbb165b7879fe7b1174df73bed0b9500") 609 } 610 return keybase1.UID("") 611 } 612 613 func (t TestUIDMapper) InformOfEldestSeqno(ctx context.Context, g UIDMapperContext, uv keybase1.UserVersion) (bool, error) { 614 return true, nil 615 } 616 617 func (t TestUIDMapper) MapUIDsToUsernamePackages(ctx context.Context, g UIDMapperContext, uids []keybase1.UID, fullNameFreshness time.Duration, networkTimeBudget time.Duration, forceNetworkForFullNames bool) ([]UsernamePackage, error) { 618 var res []UsernamePackage 619 for _, uid := range uids { 620 name, err := t.ul.LookupUsernameUPAK(ctx, uid) 621 if err != nil { 622 return nil, err 623 } 624 res = append(res, UsernamePackage{NormalizedUsername: name}) 625 } 626 return res, nil 627 } 628 629 func (t TestUIDMapper) SetTestingNoCachingMode(enabled bool) { 630 631 } 632 633 func (t TestUIDMapper) MapUIDsToUsernamePackagesOffline(ctx context.Context, g UIDMapperContext, uids []keybase1.UID, fullNameFreshness time.Duration) ([]UsernamePackage, error) { 634 // Just call MapUIDsToUsernamePackages. TestUIDMapper does not respect 635 // freshness, network budget, nor forceNetwork arguments. 636 return t.MapUIDsToUsernamePackages(ctx, g, uids, fullNameFreshness, 0, true) 637 } 638 639 func NewMetaContextForTest(tc TestContext) MetaContext { 640 return NewMetaContextBackground(tc.G).WithLogTag("TST") 641 } 642 643 func NewMetaContextForTestWithLogUI(tc TestContext) MetaContext { 644 return NewMetaContextForTest(tc).WithUIs(UIs{ 645 LogUI: tc.G.UI.GetLogUI(), 646 }) 647 } 648 649 func CreateClonedDevice(tc TestContext, m MetaContext) { 650 runAndGetDeviceCloneState := func() DeviceCloneState { 651 _, _, err := UpdateDeviceCloneState(m) 652 require.NoError(tc.T, err) 653 d, err := GetDeviceCloneState(m) 654 require.NoError(tc.T, err) 655 return d 656 } 657 // setup: perform two runs, and then manually persist the earlier 658 // prior token to simulate a subsequent run by a cloned device 659 d0 := runAndGetDeviceCloneState() 660 runAndGetDeviceCloneState() 661 err := SetDeviceCloneState(m, d0) 662 require.NoError(tc.T, err) 663 664 d := runAndGetDeviceCloneState() 665 require.True(tc.T, d.IsClone()) 666 } 667 668 func AddEnvironmentFeatureForTest(tc TestContext, feature Feature) { 669 tc.Tp.EnvironmentFeatureFlags = append(tc.Tp.EnvironmentFeatureFlags, feature) 670 } 671 672 func RemoveEnvironmentFeatureForTest(tp *TestParameters, feature Feature) { 673 var flags FeatureFlags 674 for _, flag := range tp.EnvironmentFeatureFlags { 675 if flag != feature { 676 flags = append(flags, flag) 677 } 678 } 679 tp.EnvironmentFeatureFlags = flags 680 } 681 682 // newSecretStoreLockedForTests is a simple function to create 683 // SecretStoreLocked for the purposes of unit tests outside of libkb package 684 // which need finer control over how secret store is configured. 685 // 686 // Omitting dataDir argument will create memory-only secret store, similar to 687 // how disabling "remember passphrase" would work. 688 func newSecretStoreLockedForTests(m MetaContext, dataDir string) *SecretStoreLocked { 689 var disk SecretStoreAll 690 mem := NewSecretStoreMem() 691 if dataDir != "" { 692 disk = NewSecretStoreFile(dataDir) 693 } 694 695 return &SecretStoreLocked{ 696 mem: mem, 697 disk: disk, 698 } 699 } 700 701 func ReplaceSecretStoreForTests(tc TestContext, dataDir string) { 702 g := tc.G 703 g.secretStoreMu.Lock() 704 g.secretStore = newSecretStoreLockedForTests(NewMetaContextForTest(tc), dataDir) 705 g.secretStoreMu.Unlock() 706 } 707 708 func CreateReadOnlySecretStoreDir(tc TestContext) (string, func()) { 709 td, err := os.MkdirTemp("", "ss") 710 require.NoError(tc.T, err) 711 712 // Change mode of test dir to read-only so secret store on this dir can 713 // fail. 714 fi, err := os.Stat(td) 715 require.NoError(tc.T, err) 716 oldMode := fi.Mode() 717 _ = os.Chmod(td, 0400) 718 719 cleanup := func() { 720 _ = os.Chmod(td, oldMode) 721 if err := os.RemoveAll(td); err != nil { 722 tc.T.Log(err) 723 } 724 } 725 726 return td, cleanup 727 }