github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbtest/kbtest.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  package kbtest
     5  
     6  import (
     7  	"crypto/rand"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"os"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"golang.org/x/net/context"
    18  
    19  	"github.com/keybase/client/go/engine"
    20  	"github.com/keybase/client/go/kex2"
    21  	"github.com/keybase/client/go/libkb"
    22  	"github.com/keybase/client/go/protocol/keybase1"
    23  )
    24  
    25  const testInviteCode = "202020202020202020202020"
    26  
    27  const DefaultDeviceName = "my device"
    28  
    29  type FakeUser struct {
    30  	Username    string
    31  	Email       string
    32  	Passphrase  string
    33  	User        *libkb.User
    34  	EldestSeqno keybase1.Seqno
    35  }
    36  
    37  func NewFakeUser(prefix string) (*FakeUser, error) {
    38  	buf := make([]byte, 5)
    39  	if _, err := rand.Read(buf); err != nil {
    40  		return nil, err
    41  	}
    42  	username := fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(buf))
    43  	email := fmt.Sprintf("%s@noemail.keybase.io", username)
    44  	buf = make([]byte, 12)
    45  	if _, err := rand.Read(buf); err != nil {
    46  		return nil, err
    47  	}
    48  	passphrase := hex.EncodeToString(buf)
    49  	return &FakeUser{username, email, passphrase, nil, keybase1.Seqno(1)}, nil
    50  }
    51  
    52  func (fu *FakeUser) NewSecretUI() *libkb.TestSecretUI {
    53  	return &libkb.TestSecretUI{Passphrase: fu.Passphrase}
    54  }
    55  
    56  func (fu *FakeUser) GetUID() keybase1.UID {
    57  	return libkb.UsernameToUID(fu.Username)
    58  }
    59  
    60  func (fu FakeUser) NormalizedUsername() libkb.NormalizedUsername {
    61  	return libkb.NewNormalizedUsername(fu.Username)
    62  }
    63  
    64  func (fu *FakeUser) GetUserVersion() keybase1.UserVersion {
    65  	return keybase1.UserVersion{
    66  		Uid:         fu.GetUID(),
    67  		EldestSeqno: fu.EldestSeqno,
    68  	}
    69  }
    70  
    71  func (fu *FakeUser) Login(g *libkb.GlobalContext) error {
    72  	uis := libkb.UIs{
    73  		ProvisionUI: &TestProvisionUI{},
    74  		LogUI:       g.UI.GetLogUI(),
    75  		GPGUI:       &GPGTestUI{},
    76  		SecretUI:    fu.NewSecretUI(),
    77  		LoginUI:     &libkb.TestLoginUI{Username: fu.Username},
    78  	}
    79  	li := engine.NewLogin(g, keybase1.DeviceTypeV2_DESKTOP, fu.Username, keybase1.ClientType_CLI)
    80  	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
    81  	return engine.RunEngine2(m, li)
    82  }
    83  
    84  func LogoutAndLoginAs(tc libkb.TestContext, fu *FakeUser) {
    85  	Logout(tc)
    86  	err := fu.Login(tc.G)
    87  	require.NoError(tc.T, err)
    88  }
    89  
    90  func CreateAndSignupFakeUser(prefix string, g *libkb.GlobalContext) (*FakeUser, error) {
    91  	return createAndSignupFakeUser(prefix, g, true /* skipPaper */, keybase1.DeviceType_DESKTOP, false /* randomPW */)
    92  }
    93  
    94  func CreateAndSignupFakeUserPaper(prefix string, g *libkb.GlobalContext) (*FakeUser, error) {
    95  	return createAndSignupFakeUser(prefix, g, false /* skipPaper */, keybase1.DeviceType_DESKTOP, false /* randomPW */)
    96  }
    97  
    98  func CreateAndSignupFakeUserMobile(prefix string, g *libkb.GlobalContext) (*FakeUser, error) {
    99  	return createAndSignupFakeUser(prefix, g, true /* skipPaper */, keybase1.DeviceType_MOBILE, false /* randomPW */)
   100  }
   101  
   102  func CreateAndSignupFakeUserRandomPW(prefix string, g *libkb.GlobalContext) (*FakeUser, error) {
   103  	return createAndSignupFakeUser(prefix, g, true /* skipPaper */, keybase1.DeviceType_DESKTOP, true /* randomPW */)
   104  }
   105  
   106  func createAndSignupFakeUser(prefix string, g *libkb.GlobalContext, skipPaper bool, deviceType keybase1.DeviceType, randomPW bool) (*FakeUser, error) {
   107  	fu, err := NewFakeUser(prefix)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	if randomPW {
   112  		fu.Passphrase = ""
   113  	}
   114  	arg := engine.SignupEngineRunArg{
   115  		Username:                 fu.Username,
   116  		Email:                    fu.Email,
   117  		InviteCode:               testInviteCode,
   118  		Passphrase:               fu.Passphrase,
   119  		DeviceName:               DefaultDeviceName,
   120  		DeviceType:               deviceType,
   121  		SkipGPG:                  true,
   122  		SkipMail:                 true,
   123  		SkipPaper:                skipPaper,
   124  		GenerateRandomPassphrase: randomPW,
   125  		StoreSecret:              true,
   126  	}
   127  	uis := libkb.UIs{
   128  		LogUI:    g.UI.GetLogUI(),
   129  		GPGUI:    &GPGTestUI{},
   130  		SecretUI: fu.NewSecretUI(),
   131  		LoginUI:  &libkb.TestLoginUI{Username: fu.Username},
   132  	}
   133  	s := engine.NewSignupEngine(g, &arg)
   134  	m := libkb.NewMetaContextTODO(g).WithUIs(uis)
   135  	if err := engine.RunEngine2(m, s); err != nil {
   136  		return nil, err
   137  	}
   138  	fu.User, err = libkb.LoadUser(libkb.NewLoadUserByNameArg(g, fu.Username))
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	return fu, nil
   143  }
   144  
   145  func TCreateFakeUser(tc libkb.TestContext) *FakeUser {
   146  	Logout(tc)
   147  	// Not randomPW because these user's can't logout without setting a PW,
   148  	// and we do a lot of "switching" (logout-login) between users in tests.
   149  	user, err := createAndSignupFakeUser(tc.Tp.DevelPrefix, tc.G, true /* skipPaper */, keybase1.DeviceType_DESKTOP, false /* randomPW */)
   150  	require.NoError(tc.T, err)
   151  	return user
   152  }
   153  
   154  func GetContactSettings(tc libkb.TestContext, u *FakeUser) (ret keybase1.ContactSettings) {
   155  	m := libkb.NewMetaContextForTest(tc)
   156  	ret, err := libkb.GetContactSettings(m)
   157  	require.NoError(tc.T, err)
   158  	tc.T.Logf("Got contact settings for user %s", u.Username)
   159  	return ret
   160  }
   161  
   162  func SetContactSettings(tc libkb.TestContext, u *FakeUser, arg keybase1.ContactSettings) {
   163  	m := libkb.NewMetaContextForTest(tc)
   164  	err := libkb.SetContactSettings(m, arg)
   165  	require.NoError(tc.T, err)
   166  	tc.T.Logf("Set contact settings for user %s", u.Username)
   167  }
   168  
   169  // copied from engine/common_test.go
   170  func ResetAccount(tc libkb.TestContext, u *FakeUser) {
   171  	m := libkb.NewMetaContextForTest(tc)
   172  	err := libkb.ResetAccount(m, u.NormalizedUsername(), u.Passphrase)
   173  	require.NoError(tc.T, err)
   174  	tc.T.Logf("Account reset for user %s", u.Username)
   175  	Logout(tc)
   176  }
   177  
   178  func DeleteAccount(tc libkb.TestContext, u *FakeUser) {
   179  	m := libkb.NewMetaContextForTest(tc)
   180  	err := libkb.DeleteAccount(m, u.NormalizedUsername(), &u.Passphrase)
   181  	require.NoError(tc.T, err)
   182  	tc.T.Logf("Account deleted for user %s", u.Username)
   183  	Logout(tc)
   184  }
   185  
   186  // copied from engine/common_test.go
   187  func Logout(tc libkb.TestContext) {
   188  	mctx := libkb.NewMetaContextForTest(tc)
   189  	if err := mctx.LogoutKillSecrets(); err != nil {
   190  		tc.T.Fatalf("logout error: %s", err)
   191  	}
   192  }
   193  
   194  // summarized from engine/revoke_test.go
   195  func RotatePaper(tc libkb.TestContext, u *FakeUser) {
   196  	uis := libkb.UIs{
   197  		LogUI:    tc.G.UI.GetLogUI(),
   198  		LoginUI:  &libkb.TestLoginUI{},
   199  		SecretUI: &libkb.TestSecretUI{},
   200  	}
   201  	eng := engine.NewPaperKey(tc.G)
   202  	m := libkb.NewMetaContextForTest(tc).WithUIs(uis)
   203  	err := engine.RunEngine2(m, eng)
   204  	require.NoError(tc.T, err)
   205  
   206  	arg := libkb.NewLoadUserByNameArg(tc.G, u.Username).WithPublicKeyOptional()
   207  	user, err := libkb.LoadUser(arg)
   208  	require.NoError(tc.T, err)
   209  
   210  	activeDevices := make([]*libkb.Device, 0)
   211  	for _, device := range user.GetComputedKeyFamily().GetAllDevices() {
   212  		if device.Status != nil && *device.Status == libkb.DeviceStatusActive {
   213  			activeDevices = append(activeDevices, device.Device)
   214  		}
   215  	}
   216  
   217  	var revokeDevice *libkb.Device
   218  	for _, device := range activeDevices {
   219  		if device.Type == keybase1.DeviceTypeV2_PAPER {
   220  			revokeDevice = device
   221  		}
   222  	}
   223  	require.NotNil(tc.T, revokeDevice, "no paper key found to revoke")
   224  
   225  	revokeEngine := engine.NewRevokeDeviceEngine(tc.G, engine.RevokeDeviceEngineArgs{ID: revokeDevice.ID})
   226  	uis = libkb.UIs{
   227  		LogUI:    tc.G.UI.GetLogUI(),
   228  		SecretUI: u.NewSecretUI(),
   229  	}
   230  	m = libkb.NewMetaContextForTest(tc).WithUIs(uis)
   231  	err = engine.RunEngine2(m, revokeEngine)
   232  	require.NoError(tc.T, err)
   233  }
   234  
   235  func AssertProvisioned(tc libkb.TestContext) error {
   236  	if !tc.G.ActiveDevice.Valid() {
   237  		return libkb.LoginRequiredError{}
   238  	}
   239  	return nil
   240  }
   241  
   242  func FakeSalt() []byte {
   243  	return []byte("fakeSALTfakeSALT")
   244  }
   245  
   246  // Provision a new device (in context tcY) from the active (and logged in) device in test context tcX.
   247  // This was adapted from engine/kex2_test.go
   248  // Note that it uses Errorf in goroutines, so if it fails
   249  // the test will not fail until later.
   250  // `tcX` is a TestContext where device X (the provisioner) is already provisioned and logged in.
   251  // this function will provision a new device Y inside `tcY`
   252  // `newDeviceType` is keybase1.DeviceTypeV2_MOBILE or keybase1.DeviceTypeV2_DESKTOP.
   253  func ProvisionNewDeviceKex(tcX *libkb.TestContext, tcY *libkb.TestContext, userX *FakeUser, newDeviceType keybase1.DeviceTypeV2) {
   254  	// tcX is the device X (provisioner) context:
   255  	// tcX should already have been logged in.
   256  	t := tcX.T
   257  
   258  	var secretX kex2.Secret
   259  	if _, err := rand.Read(secretX[:]); err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  	var secretY kex2.Secret
   264  	if _, err := rand.Read(secretY[:]); err != nil {
   265  		t.Fatal(err)
   266  	}
   267  
   268  	var wg sync.WaitGroup
   269  
   270  	// start provisionee
   271  	wg.Add(1)
   272  	go func() {
   273  		defer wg.Done()
   274  		err := (func() error {
   275  			uis := libkb.UIs{
   276  				ProvisionUI: &TestProvisionUI{SecretCh: make(chan kex2.Secret, 1)},
   277  			}
   278  			m := libkb.NewMetaContextForTest(*tcY).WithUIs(uis).WithNewProvisionalLoginContext()
   279  			deviceID, err := libkb.NewDeviceID()
   280  			if err != nil {
   281  				return err
   282  			}
   283  			suffix, err := libkb.RandBytes(5)
   284  			if err != nil {
   285  				return err
   286  			}
   287  			dname := fmt.Sprintf("device_%x", suffix)
   288  			device := &libkb.Device{
   289  				ID:          deviceID,
   290  				Description: &dname,
   291  				Type:        newDeviceType,
   292  			}
   293  			provisionee := engine.NewKex2Provisionee(tcY.G, device, secretY, userX.GetUID(), FakeSalt())
   294  			return engine.RunEngine2(m, provisionee)
   295  		})()
   296  		require.NoError(t, err, "kex2 provisionee")
   297  	}()
   298  
   299  	// start provisioner
   300  	wg.Add(1)
   301  	go func() {
   302  		defer wg.Done()
   303  		uis := libkb.UIs{
   304  			SecretUI:    userX.NewSecretUI(),
   305  			ProvisionUI: &TestProvisionUI{},
   306  		}
   307  		provisioner := engine.NewKex2Provisioner(tcX.G, secretX, nil)
   308  		go provisioner.AddSecret(secretY)
   309  		m := libkb.NewMetaContextForTest(*tcX).WithUIs(uis)
   310  		if err := engine.RunEngine2(m, provisioner); err != nil {
   311  			require.NoErrorf(t, err, "provisioner error")
   312  			return
   313  		}
   314  	}()
   315  
   316  	wg.Wait()
   317  }
   318  
   319  type TestProvisionUI struct {
   320  	SecretCh   chan kex2.Secret
   321  	DeviceType keybase1.DeviceTypeV2
   322  }
   323  
   324  func (u *TestProvisionUI) ChooseProvisioningMethod(_ context.Context, _ keybase1.ChooseProvisioningMethodArg) (keybase1.ProvisionMethod, error) {
   325  	panic("ChooseProvisioningMethod deprecated")
   326  }
   327  
   328  func (u *TestProvisionUI) ChooseGPGMethod(_ context.Context, _ keybase1.ChooseGPGMethodArg) (keybase1.GPGMethod, error) {
   329  	return keybase1.GPGMethod_GPG_NONE, nil
   330  }
   331  
   332  func (u *TestProvisionUI) SwitchToGPGSignOK(ctx context.Context, arg keybase1.SwitchToGPGSignOKArg) (bool, error) {
   333  	return true, nil
   334  }
   335  
   336  func (u *TestProvisionUI) ChooseDevice(_ context.Context, arg keybase1.ChooseDeviceArg) (keybase1.DeviceID, error) {
   337  	for _, d := range arg.Devices {
   338  		if d.Type == u.DeviceType {
   339  			return d.DeviceID, nil
   340  		}
   341  	}
   342  	return "", nil
   343  }
   344  
   345  func (u *TestProvisionUI) ChooseDeviceType(_ context.Context, _ keybase1.ChooseDeviceTypeArg) (keybase1.DeviceType, error) {
   346  	return keybase1.DeviceType_DESKTOP, nil
   347  }
   348  
   349  func (u *TestProvisionUI) DisplayAndPromptSecret(_ context.Context, arg keybase1.DisplayAndPromptSecretArg) (keybase1.SecretResponse, error) {
   350  	var ks kex2.Secret
   351  	copy(ks[:], arg.Secret)
   352  	u.SecretCh <- ks
   353  	var sr keybase1.SecretResponse
   354  	return sr, nil
   355  }
   356  
   357  func (u *TestProvisionUI) PromptNewDeviceName(_ context.Context, arg keybase1.PromptNewDeviceNameArg) (string, error) {
   358  	return libkb.RandString("device", 5)
   359  }
   360  
   361  func (u *TestProvisionUI) DisplaySecretExchanged(_ context.Context, _ int) error {
   362  	return nil
   363  }
   364  
   365  func (u *TestProvisionUI) ProvisioneeSuccess(_ context.Context, _ keybase1.ProvisioneeSuccessArg) error {
   366  	return nil
   367  }
   368  
   369  func (u *TestProvisionUI) ProvisionerSuccess(_ context.Context, _ keybase1.ProvisionerSuccessArg) error {
   370  	return nil
   371  }
   372  
   373  type TeamNotifyListener struct {
   374  	libkb.NoopNotifyListener
   375  	changeByIDCh   chan keybase1.TeamChangedByIDArg
   376  	changeByNameCh chan keybase1.TeamChangedByNameArg
   377  }
   378  
   379  var _ libkb.NotifyListener = (*TeamNotifyListener)(nil)
   380  
   381  func (n *TeamNotifyListener) TeamChangedByID(teamID keybase1.TeamID, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource) {
   382  	n.changeByIDCh <- keybase1.TeamChangedByIDArg{
   383  		TeamID:            teamID,
   384  		LatestSeqno:       latestSeqno,
   385  		ImplicitTeam:      implicitTeam,
   386  		Changes:           changes,
   387  		LatestHiddenSeqno: latestHiddenSeqno,
   388  	}
   389  }
   390  func (n *TeamNotifyListener) TeamChangedByName(teamName string, latestSeqno keybase1.Seqno, implicitTeam bool, changes keybase1.TeamChangeSet, latestHiddenSeqno keybase1.Seqno, source keybase1.TeamChangedSource) {
   391  	n.changeByNameCh <- keybase1.TeamChangedByNameArg{
   392  		TeamName:          teamName,
   393  		LatestSeqno:       latestSeqno,
   394  		ImplicitTeam:      implicitTeam,
   395  		Changes:           changes,
   396  		LatestHiddenSeqno: latestHiddenSeqno,
   397  	}
   398  }
   399  
   400  func NewTeamNotifyListener() *TeamNotifyListener {
   401  	return &TeamNotifyListener{
   402  		changeByIDCh:   make(chan keybase1.TeamChangedByIDArg, 10),
   403  		changeByNameCh: make(chan keybase1.TeamChangedByNameArg, 10),
   404  	}
   405  }
   406  
   407  func CheckTeamMiscNotifications(tc libkb.TestContext, notifications *TeamNotifyListener) {
   408  	changeByID := false
   409  	changeByName := false
   410  	for {
   411  		select {
   412  		case arg := <-notifications.changeByIDCh:
   413  			changeByID = arg.Changes.Misc
   414  		case arg := <-notifications.changeByNameCh:
   415  			changeByName = arg.Changes.Misc
   416  		case <-time.After(500 * time.Millisecond * libkb.CITimeMultiplier(tc.G)):
   417  			tc.T.Fatal("no notification on teamSetSettings")
   418  		}
   419  		if changeByID && changeByName {
   420  			return
   421  		}
   422  	}
   423  }
   424  
   425  type fakeIdentifyUI struct {
   426  	*engine.LoopbackIdentifyUI
   427  }
   428  
   429  func (l *fakeIdentifyUI) Confirm(_ libkb.MetaContext, o *keybase1.IdentifyOutcome) (keybase1.ConfirmResult, error) {
   430  	return keybase1.ConfirmResult{IdentityConfirmed: true, RemoteConfirmed: true}, nil
   431  }
   432  
   433  func newFakeIdentifyUI(g *libkb.GlobalContext) *fakeIdentifyUI {
   434  	var tb *keybase1.IdentifyTrackBreaks
   435  	return &fakeIdentifyUI{
   436  		engine.NewLoopbackIdentifyUI(g, &tb),
   437  	}
   438  }
   439  
   440  func RunTrack(tc libkb.TestContext, fu *FakeUser, username string) (them *libkb.User, err error) {
   441  	sv := keybase1.SigVersion(2)
   442  	return RunTrackWithOptions(tc, fu, username, keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv}, fu.NewSecretUI(), false)
   443  }
   444  
   445  func RunTrackWithOptions(tc libkb.TestContext, fu *FakeUser, username string, options keybase1.TrackOptions, secretUI libkb.SecretUI, forceRemoteCheck bool) (them *libkb.User, err error) {
   446  	idUI := newFakeIdentifyUI(tc.G)
   447  
   448  	arg := &engine.TrackEngineArg{
   449  		UserAssertion:    username,
   450  		Options:          options,
   451  		ForceRemoteCheck: forceRemoteCheck,
   452  	}
   453  	uis := libkb.UIs{
   454  		LogUI:      tc.G.UI.GetLogUI(),
   455  		IdentifyUI: idUI,
   456  		SecretUI:   secretUI,
   457  	}
   458  	eng := engine.NewTrackEngine(tc.G, arg)
   459  	m := libkb.NewMetaContextForTest(tc).WithUIs(uis)
   460  	err = engine.RunEngine2(m, eng)
   461  	them = eng.User()
   462  	return them, err
   463  }
   464  
   465  // GenerateTestPhoneNumber generates a random phone number in US with 555 area
   466  // code. It passes serverside "strict phone number" checker test, and it's
   467  // considered `possible`, but not `valid` by libphonenumber.
   468  func GenerateTestPhoneNumber() string {
   469  	ret := make([]byte, 7)
   470  	_, err := rand.Read(ret)
   471  	if err != nil {
   472  		panic(err)
   473  	}
   474  	for i := range ret {
   475  		ret[i] = "0123456789"[int(ret[i])%10]
   476  	}
   477  	return fmt.Sprintf("1555%s", string(ret))
   478  }
   479  
   480  func GenerateRandomEmailAddress() keybase1.EmailAddress {
   481  	buf := make([]byte, 5)
   482  	_, err := rand.Read(buf)
   483  	if err != nil {
   484  		panic(err)
   485  	}
   486  	email := fmt.Sprintf("%s@example.org", hex.EncodeToString(buf))
   487  	return keybase1.EmailAddress(email)
   488  }
   489  
   490  type getCodeResponse struct {
   491  	libkb.AppStatusEmbed
   492  	VerificationCode string `json:"verification_code"`
   493  }
   494  
   495  func GetPhoneVerificationCode(mctx libkb.MetaContext, phoneNumber keybase1.PhoneNumber) (code string, err error) {
   496  	arg := libkb.APIArg{
   497  		Endpoint:    "test/phone_number_code",
   498  		SessionType: libkb.APISessionTypeREQUIRED,
   499  		Args: libkb.HTTPArgs{
   500  			"phone_number": libkb.S{Val: phoneNumber.String()},
   501  		},
   502  	}
   503  	var resp getCodeResponse
   504  	err = mctx.G().API.GetDecode(mctx, arg, &resp)
   505  	if err != nil {
   506  		return "", err
   507  	}
   508  	return resp.VerificationCode, nil
   509  }
   510  
   511  func VerifyEmailAuto(mctx libkb.MetaContext, email keybase1.EmailAddress) error {
   512  	arg := libkb.APIArg{
   513  		Endpoint:    "test/verify_email_auto",
   514  		SessionType: libkb.APISessionTypeREQUIRED,
   515  		Args: libkb.HTTPArgs{
   516  			"email": libkb.S{Val: string(email)},
   517  		},
   518  	}
   519  	_, err := mctx.G().API.Post(mctx, arg)
   520  	return err
   521  }
   522  
   523  func EditProfile(mctx libkb.MetaContext, arg keybase1.ProfileEditArg) error {
   524  	eng := engine.NewProfileEdit(mctx.G(), arg)
   525  	return engine.RunEngine2(mctx, eng)
   526  }
   527  
   528  func RunningInCI() bool {
   529  	x := os.Getenv("KEYBASE_RUN_CI")
   530  	return len(x) > 0 && x != "0" && x[0] != byte('n')
   531  }
   532  
   533  func SkipTestOnNonMasterCI(t *testing.T, reason string) {
   534  	if RunningInCI() && os.Getenv("BRANCH_NAME") != "master" {
   535  		t.Skipf("skip test on non-master CI run: %v", reason)
   536  	}
   537  }
   538  
   539  // CORE-10146
   540  func SkipIconRemoteTest() bool {
   541  	return RunningInCI()
   542  }