Skip to content

Commit

Permalink
Put XCTest parameters into a config struct (#507)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmissmann authored Nov 8, 2024
1 parent 6bc43a1 commit 4cd5d9f
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 109 deletions.
124 changes: 52 additions & 72 deletions ios/testmanagerd/xcuitestrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,95 +221,75 @@ const (

const testBundleSuffix = "UITests.xctrunner"

func RunXCUITest(bundleID string, testRunnerBundleID string, xctestConfigName string, device ios.DeviceEntry, env map[string]interface{}, testsToRun []string, testsToSkip []string, testListener *TestListener, isXCTest bool) ([]TestSuite, error) {
// FIXME: this is redundant code, getting the app list twice and creating the appinfos twice
// just to generate the xctestConfigFileName. Should be cleaned up at some point.
installationProxy, err := installationproxy.New(device)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUITest: cannot connect to installation proxy: %w", err)
}
defer installationProxy.Close()

if testRunnerBundleID == "" {
testRunnerBundleID = bundleID + testBundleSuffix
}

apps, err := installationProxy.BrowseUserApps()
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUITest: cannot browse user apps: %w", err)
}

if bundleID != "" && xctestConfigName == "" {
info, err := getappInfo(bundleID, apps)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUITest: cannot get app information: %w", err)
}

xctestConfigName = info.bundleName + "UITests.xctest"
}

return RunXCUIWithBundleIdsCtx(context.TODO(), bundleID, testRunnerBundleID, xctestConfigName, device, nil, env, testsToRun, testsToSkip, testListener, isXCTest)
// TestConfig specifies the parameters of a test execution
type TestConfig struct {
// The identifier of the app under test
BundleId string
// The identifier of the test runner. For unit tests (non-UI tests) this is also the
// app under test (BundleId can be left empty) as the .xctest bundle is packaged into the app under test
TestRunnerBundleId string
// XctestConfigName is the name of the
XctestConfigName string
// Env is passed as environment variables to the test runner
Env map[string]any
// Args are passed to the test runner as launch arguments
Args []string
// TestsToRun specifies a list of tests that should be executed. All other tests are ignored. To execute all tests
// pass nil.
// The format of the values is {PRODUCT_MODULE_NAME}.{CLASS}/{METHOD} where {PRODUCT_MODULE_NAME} and {METHOD} are
// optional. If {METHOD} is omitted, all tests of {CLASS} are executed
TestsToRun []string
// TestsToSkip specifies a list of tests that should be skipped. See TestsToRun for the format
TestsToSkip []string
// XcTest needs to be set to true if the TestRunnerBundleId is a unit test and not a UI test
XcTest bool
// The device on which the test is executed
Device ios.DeviceEntry
// The listener for receiving results
Listener *TestListener
}

func RunXCUIWithBundleIdsCtx(
ctx context.Context,
bundleID string,
testRunnerBundleID string,
xctestConfigFileName string,
device ios.DeviceEntry,
args []string,
env map[string]interface{},
testsToRun []string,
testsToSkip []string,
testListener *TestListener,
isXCTest bool,
) ([]TestSuite, error) {
version, err := ios.GetProductVersion(device)
func RunTestWithConfig(ctx context.Context, testConfig TestConfig) ([]TestSuite, error) {
if len(testConfig.TestRunnerBundleId) == 0 {
return nil, fmt.Errorf("RunTestWithConfig: testConfig.TestRunnerBundleId can not be empty")
}
version, err := ios.GetProductVersion(testConfig.Device)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsCtx: cannot determine iOS version: %w", err)
}

if version.LessThan(ios.IOS14()) {
log.Debugf("iOS version: %s detected, running with ios11 support", version)
return runXCUIWithBundleIdsXcode11Ctx(ctx, bundleID, testRunnerBundleID, xctestConfigFileName, device, args, env, testsToRun, testsToSkip, testListener, isXCTest, version)
return runXCUIWithBundleIdsXcode11Ctx(ctx, testConfig, version)
}

if version.LessThan(ios.IOS17()) {
log.Debugf("iOS version: %s detected, running with ios14 support", version)
return runXUITestWithBundleIdsXcode12Ctx(ctx, bundleID, testRunnerBundleID, xctestConfigFileName, device, args, env, testsToRun, testsToSkip, testListener, isXCTest, version)
return runXUITestWithBundleIdsXcode12Ctx(ctx, testConfig, version)
}

log.Debugf("iOS version: %s detected, running with ios17 support", version)
return runXUITestWithBundleIdsXcode15Ctx(ctx, bundleID, testRunnerBundleID, xctestConfigFileName, device, args, env, testsToRun, testsToSkip, testListener, isXCTest, version)
return runXUITestWithBundleIdsXcode15Ctx(ctx, testConfig, version)
}

func runXUITestWithBundleIdsXcode15Ctx(
ctx context.Context,
bundleID string,
testRunnerBundleID string,
xctestConfigFileName string,
device ios.DeviceEntry,
args []string,
env map[string]interface{},
testsToRun []string,
testsToSkip []string,
testListener *TestListener,
isXCTest bool,
config TestConfig,
version *semver.Version,
) ([]TestSuite, error) {
conn1, err := dtx.NewTunnelConnection(device, testmanagerdiOS17)
conn1, err := dtx.NewTunnelConnection(config.Device, testmanagerdiOS17)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot create a tunnel connection to testmanagerd: %w", err)
}
defer conn1.Close()

conn2, err := dtx.NewTunnelConnection(device, testmanagerdiOS17)
conn2, err := dtx.NewTunnelConnection(config.Device, testmanagerdiOS17)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot create a tunnel connection to testmanagerd: %w", err)
}
defer conn2.Close()

installationProxy, err := installationproxy.New(device)
installationProxy, err := installationproxy.New(config.Device)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot connect to installation proxy: %w", err)
}
Expand All @@ -319,7 +299,7 @@ func runXUITestWithBundleIdsXcode15Ctx(
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot browse user apps: %w", err)
}

testAppInfo, err := getappInfo(testRunnerBundleID, apps)
testAppInfo, err := getappInfo(config.TestRunnerBundleId, apps)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot get test app information: %w", err)
}
Expand All @@ -328,8 +308,8 @@ func runXUITestWithBundleIdsXcode15Ctx(
testApp: testAppInfo,
}

if bundleID != "" {
appInfo, err := getappInfo(bundleID, apps)
if config.BundleId != "" {
appInfo, err := getappInfo(config.BundleId, apps)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot get app information: %w", err)
}
Expand All @@ -338,8 +318,8 @@ func runXUITestWithBundleIdsXcode15Ctx(
}

testSessionID := uuid.New()
testconfig := createTestConfig(info, testSessionID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest, version)
ideDaemonProxy1 := newDtxProxyWithConfig(conn1, testconfig, testListener)
testconfig := createTestConfig(info, testSessionID, config.XctestConfigName, config.TestsToRun, config.TestsToSkip, config.XcTest, version)
ideDaemonProxy1 := newDtxProxyWithConfig(conn1, testconfig, config.Listener)

localCaps := nskeyedarchiver.XCTCapabilities{CapabilitiesDictionary: map[string]interface{}{
"XCTIssue capability": uint64(1),
Expand All @@ -359,26 +339,26 @@ func runXUITestWithBundleIdsXcode15Ctx(
}
log.WithField("receivedCaps", receivedCaps).Info("got capabilities")

appserviceConn, err := appservice.New(device)
appserviceConn, err := appservice.New(config.Device)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot connect to app service: %w", err)
}
defer appserviceConn.Close()

testRunnerLaunch, err := startTestRunner17(device, appserviceConn, "", testRunnerBundleID, strings.ToUpper(testSessionID.String()), info.testApp.path+"/PlugIns/"+xctestConfigFileName, args, env, isXCTest)
testRunnerLaunch, err := startTestRunner17(appserviceConn, config.TestRunnerBundleId, strings.ToUpper(testSessionID.String()), info.testApp.path+"/PlugIns/"+config.XctestConfigName, config.Args, config.Env, config.XcTest)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot start test runner: %w", err)
}

defer testRunnerLaunch.Close()
go func() {
_, err := io.Copy(testListener.logWriter, testRunnerLaunch)
_, err := io.Copy(config.Listener.logWriter, testRunnerLaunch)
if err != nil {
log.Warn("copying stdout failed", log.WithError(err))
}
}()

ideDaemonProxy2 := newDtxProxyWithConfig(conn2, testconfig, testListener)
ideDaemonProxy2 := newDtxProxyWithConfig(conn2, testconfig, config.Listener)
caps, err := ideDaemonProxy2.daemonConnection.initiateControlSessionWithCapabilities(nskeyedarchiver.XCTCapabilities{CapabilitiesDictionary: map[string]interface{}{}})
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot initiate a control session with capabilities: %w", err)
Expand All @@ -404,16 +384,16 @@ func runXUITestWithBundleIdsXcode15Ctx(
if !errors.Is(conn1.Err(), dtx.ErrConnectionClosed) {
log.WithError(conn1.Err()).Error("conn1 closed unexpectedly")
}
testListener.FinishWithError(errors.New("lost connection to testmanagerd. the test-runner may have been killed"))
config.Listener.FinishWithError(errors.New("lost connection to testmanagerd. the test-runner may have been killed"))
break
case <-conn2.Closed():
log.Debug("conn2 closed")
if !errors.Is(conn2.Err(), dtx.ErrConnectionClosed) {
log.WithError(conn2.Err()).Error("conn2 closed unexpectedly")
}
testListener.FinishWithError(errors.New("lost connection to testmanagerd. the test-runner may have been killed"))
config.Listener.FinishWithError(errors.New("lost connection to testmanagerd. the test-runner may have been killed"))
break
case <-testListener.Done():
case <-config.Listener.Done():
break
case <-ctx.Done():
break
Expand All @@ -428,7 +408,7 @@ func runXUITestWithBundleIdsXcode15Ctx(

log.Debugf("Done running test")

return testListener.TestSuites, testListener.err
return config.Listener.TestSuites, config.Listener.err
}

type processKiller interface {
Expand All @@ -446,7 +426,7 @@ func killTestRunner(killer processKiller, pid int) error {
return nil
}

func startTestRunner17(device ios.DeviceEntry, appserviceConn *appservice.Connection, xctestConfigPath string, bundleID string, sessionIdentifier string, testBundlePath string, testArgs []string, testEnv map[string]interface{}, isXCTest bool) (appservice.LaunchedAppWithStdIo, error) {
func startTestRunner17(appserviceConn *appservice.Connection, bundleID string, sessionIdentifier string, testBundlePath string, testArgs []string, testEnv map[string]interface{}, isXCTest bool) (appservice.LaunchedAppWithStdIo, error) {
args := []interface{}{}
for _, arg := range testArgs {
args = append(args, arg)
Expand Down
30 changes: 10 additions & 20 deletions ios/testmanagerd/xcuitestrunner_11.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,37 @@ import (
"maps"

"github.com/Masterminds/semver"
"github.com/danielpaulus/go-ios/ios"
dtx "github.com/danielpaulus/go-ios/ios/dtx_codec"
"github.com/danielpaulus/go-ios/ios/instruments"
log "github.com/sirupsen/logrus"
)

func runXCUIWithBundleIdsXcode11Ctx(
ctx context.Context,
bundleID string,
testRunnerBundleID string,
xctestConfigFileName string,
device ios.DeviceEntry,
args []string,
env map[string]interface{},
testsToRun []string,
testsToSkip []string,
testListener *TestListener,
isXCTest bool,
config TestConfig,
version *semver.Version,
) ([]TestSuite, error) {
log.Debugf("set up xcuitest")
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest, version)
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(config.Device, config.BundleId, config.TestRunnerBundleId, config.XctestConfigName, config.TestsToRun, config.TestsToSkip, config.XcTest, version)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsXcode11Ctx: cannot create test config: %w", err)
}
log.Debugf("test session setup ok")
conn, err := dtx.NewUsbmuxdConnection(device, testmanagerd)
conn, err := dtx.NewUsbmuxdConnection(config.Device, testmanagerd)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsXcode11Ctx: cannot create a usbmuxd connection to testmanagerd: %w", err)
}
defer conn.Close()

ideDaemonProxy := newDtxProxyWithConfig(conn, testConfig, testListener)
ideDaemonProxy := newDtxProxyWithConfig(conn, testConfig, config.Listener)

conn2, err := dtx.NewUsbmuxdConnection(device, testmanagerd)
conn2, err := dtx.NewUsbmuxdConnection(config.Device, testmanagerd)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsXcode11Ctx: cannot create a usbmuxd connection to testmanagerd: %w", err)
}
defer conn2.Close()
log.Debug("connections ready")
ideDaemonProxy2 := newDtxProxyWithConfig(conn2, testConfig, testListener)
ideDaemonProxy2 := newDtxProxyWithConfig(conn2, testConfig, config.Listener)
ideDaemonProxy2.ideInterface.testConfig = testConfig
// TODO: fixme
protocolVersion := uint64(25)
Expand All @@ -55,13 +45,13 @@ func runXCUIWithBundleIdsXcode11Ctx(
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsXcode11Ctx: cannot initiate a test session: %w", err)
}

pControl, err := instruments.NewProcessControl(device)
pControl, err := instruments.NewProcessControl(config.Device)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsXcode11Ctx: cannot connect to process control: %w", err)
}
defer pControl.Close()

pid, err := startTestRunner11(pControl, xctestConfigPath, testRunnerBundleID, testSessionId.String(), testInfo.testApp.path+"/PlugIns/"+xctestConfigFileName, args, env)
pid, err := startTestRunner11(pControl, xctestConfigPath, config.TestRunnerBundleId, testSessionId.String(), testInfo.testApp.path+"/PlugIns/"+config.XctestConfigName, config.Args, config.Env)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsXcode11Ctx: cannot start the test runner: %w", err)
}
Expand Down Expand Up @@ -93,7 +83,7 @@ func runXCUIWithBundleIdsXcode11Ctx(
log.WithError(conn2.Err()).Error("conn2 closed unexpectedly")
}
break
case <-testListener.Done():
case <-config.Listener.Done():
break
case <-ctx.Done():
break
Expand All @@ -108,7 +98,7 @@ func runXCUIWithBundleIdsXcode11Ctx(

log.Debugf("Done running test")

return testListener.TestSuites, testListener.err
return config.Listener.TestSuites, config.Listener.err
}

func startTestRunner11(pControl *instruments.ProcessControl, xctestConfigPath string, bundleID string,
Expand Down
22 changes: 10 additions & 12 deletions ios/testmanagerd/xcuitestrunner_12.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,34 @@ import (
"time"

"github.com/Masterminds/semver"
"github.com/danielpaulus/go-ios/ios"
dtx "github.com/danielpaulus/go-ios/ios/dtx_codec"
"github.com/danielpaulus/go-ios/ios/instruments"
"github.com/danielpaulus/go-ios/ios/nskeyedarchiver"
log "github.com/sirupsen/logrus"
)

func runXUITestWithBundleIdsXcode12Ctx(ctx context.Context, bundleID string, testRunnerBundleID string, xctestConfigFileName string,
device ios.DeviceEntry, args []string, env map[string]interface{}, testsToRun []string, testsToSkip []string, testListener *TestListener, isXCTest bool, version *semver.Version,
func runXUITestWithBundleIdsXcode12Ctx(ctx context.Context, config TestConfig, version *semver.Version,
) ([]TestSuite, error) {
conn, err := dtx.NewUsbmuxdConnection(device, testmanagerdiOS14)
conn, err := dtx.NewUsbmuxdConnection(config.Device, testmanagerdiOS14)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot create a usbmuxd connection to testmanagerd: %w", err)
}

testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest, version)
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(config.Device, config.BundleId, config.TestRunnerBundleId, config.XctestConfigName, config.TestsToRun, config.TestsToSkip, config.XcTest, version)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot setup test config: %w", err)
}
defer conn.Close()

ideDaemonProxy := newDtxProxyWithConfig(conn, testConfig, testListener)
ideDaemonProxy := newDtxProxyWithConfig(conn, testConfig, config.Listener)

conn2, err := dtx.NewUsbmuxdConnection(device, testmanagerdiOS14)
conn2, err := dtx.NewUsbmuxdConnection(config.Device, testmanagerdiOS14)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot create a usbmuxd connection to testmanagerd: %w", err)
}
defer conn2.Close()
log.Debug("connections ready")
ideDaemonProxy2 := newDtxProxyWithConfig(conn2, testConfig, testListener)
ideDaemonProxy2 := newDtxProxyWithConfig(conn2, testConfig, config.Listener)
ideDaemonProxy2.ideInterface.testConfig = testConfig
caps, err := ideDaemonProxy.daemonConnection.initiateControlSessionWithCapabilities(nskeyedarchiver.XCTCapabilities{})
if err != nil {
Expand All @@ -54,13 +52,13 @@ func runXUITestWithBundleIdsXcode12Ctx(ctx context.Context, bundleID string, tes
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot initiate a session with identifier and capabilities: %w", err)
}
log.Debug(caps2)
pControl, err := instruments.NewProcessControl(device)
pControl, err := instruments.NewProcessControl(config.Device)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot connect to process control: %w", err)
}
defer pControl.Close()

pid, err := startTestRunner12(pControl, xctestConfigPath, testRunnerBundleID, testSessionId.String(), testInfo.testApp.path+"/PlugIns/"+xctestConfigFileName, args, env)
pid, err := startTestRunner12(pControl, xctestConfigPath, config.TestRunnerBundleId, testSessionId.String(), testInfo.testApp.path+"/PlugIns/"+config.XctestConfigName, config.Args, config.Env)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot start test runner: %w", err)
}
Expand Down Expand Up @@ -90,7 +88,7 @@ func runXUITestWithBundleIdsXcode12Ctx(ctx context.Context, bundleID string, tes
log.WithError(conn2.Err()).Error("conn2 closed unexpectedly")
}
break
case <-testListener.Done():
case <-config.Listener.Done():
break
case <-ctx.Done():
break
Expand All @@ -105,7 +103,7 @@ func runXUITestWithBundleIdsXcode12Ctx(ctx context.Context, bundleID string, tes

log.Debugf("Done running test")

return testListener.TestSuites, testListener.err
return config.Listener.TestSuites, config.Listener.err
}

func startTestRunner12(pControl *instruments.ProcessControl, xctestConfigPath string, bundleID string,
Expand Down
Loading

0 comments on commit 4cd5d9f

Please sign in to comment.