github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }