Skip to content

Commit 89cbe52

Browse files
Add Support for XCTest (#464)
Provide the ability to run an XCTEST by passing the flag --xctest to ios runtest Test the ability to run a XCUITEST on a Real Device Test the ability to run a XCTEST on a Real Device by the default, if the flag --xctest is not provided we will run XCUITEST. so to be able to run an XCTEST we need the flag to be present. Thanks bahrimootaz! --------- Co-authored-by: David Missmann <[email protected]>
1 parent bc47af1 commit 89cbe52

File tree

6 files changed

+37
-27
lines changed

6 files changed

+37
-27
lines changed

ios/nskeyedarchiver/archiver_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestArchiveSlice(t *testing.T) {
3636
// TODO currently only partially decoding XCTestConfig is supported, fix later
3737
func TestXCTestconfig(t *testing.T) {
3838
uuid := uuid.New()
39-
config := nskeyedarchiver.NewXCTestConfiguration("productmodulename", uuid, "targetAppBundle", "targetAppPath", "testBundleUrl", nil, nil)
39+
config := nskeyedarchiver.NewXCTestConfiguration("productmodulename", uuid, "targetAppBundle", "targetAppPath", "testBundleUrl", nil, nil, false)
4040
result, err := nskeyedarchiver.ArchiveXML(config)
4141
if err != nil {
4242
log.Error(err)

ios/nskeyedarchiver/objectivec_classes.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func NewXCTestConfiguration(
8383
testBundleURL string,
8484
testsToRun []string,
8585
testsToSkip []string,
86+
isXCTest bool,
8687
) XCTestConfiguration {
8788
contents := map[string]interface{}{}
8889

@@ -126,7 +127,7 @@ func NewXCTestConfiguration(
126127
contents["emitOSLogs"] = false
127128
// contents["formatVersion"] = 2
128129
contents["gatherLocalizableStringsData"] = false
129-
contents["initializeForUITesting"] = true
130+
contents["initializeForUITesting"] = !isXCTest
130131
contents["maximumTestExecutionTimeAllowance"] = plist.UID(0)
131132
contents["randomExecutionOrderingSeed"] = plist.UID(0)
132133
contents["reportActivities"] = true

ios/testmanagerd/xcuitestrunner.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ const (
219219

220220
const testBundleSuffix = "UITests.xctrunner"
221221

222-
func RunXCUITest(bundleID string, testRunnerBundleID string, xctestConfigName string, device ios.DeviceEntry, env []string, testsToRun []string, testsToSkip []string, testListener *TestListener) ([]TestSuite, error) {
222+
func RunXCUITest(bundleID string, testRunnerBundleID string, xctestConfigName string, device ios.DeviceEntry, env []string, testsToRun []string, testsToSkip []string, testListener *TestListener, isXCTest bool) ([]TestSuite, error) {
223223
// FIXME: this is redundant code, getting the app list twice and creating the appinfos twice
224224
// just to generate the xctestConfigFileName. Should be cleaned up at some point.
225225
installationProxy, err := installationproxy.New(device)
@@ -246,7 +246,7 @@ func RunXCUITest(bundleID string, testRunnerBundleID string, xctestConfigName st
246246
xctestConfigName = info.bundleName + "UITests.xctest"
247247
}
248248

249-
return RunXCUIWithBundleIdsCtx(context.TODO(), bundleID, testRunnerBundleID, xctestConfigName, device, nil, env, testsToRun, testsToSkip, testListener)
249+
return RunXCUIWithBundleIdsCtx(context.TODO(), bundleID, testRunnerBundleID, xctestConfigName, device, nil, env, testsToRun, testsToSkip, testListener, isXCTest)
250250
}
251251

252252
func RunXCUIWithBundleIdsCtx(
@@ -260,6 +260,7 @@ func RunXCUIWithBundleIdsCtx(
260260
testsToRun []string,
261261
testsToSkip []string,
262262
testListener *TestListener,
263+
isXCTest bool,
263264
) ([]TestSuite, error) {
264265
version, err := ios.GetProductVersion(device)
265266
if err != nil {
@@ -268,16 +269,16 @@ func RunXCUIWithBundleIdsCtx(
268269

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

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

279280
log.Debugf("iOS version: %s detected, running with ios17 support", version)
280-
return runXUITestWithBundleIdsXcode15Ctx(ctx, bundleID, testRunnerBundleID, xctestConfigFileName, device, args, env, testsToRun, testsToSkip, testListener)
281+
return runXUITestWithBundleIdsXcode15Ctx(ctx, bundleID, testRunnerBundleID, xctestConfigFileName, device, args, env, testsToRun, testsToSkip, testListener, isXCTest)
281282
}
282283

283284
func runXUITestWithBundleIdsXcode15Ctx(
@@ -291,6 +292,7 @@ func runXUITestWithBundleIdsXcode15Ctx(
291292
testsToRun []string,
292293
testsToSkip []string,
293294
testListener *TestListener,
295+
isXCTest bool,
294296
) ([]TestSuite, error) {
295297
conn1, err := dtx.NewTunnelConnection(device, testmanagerdiOS17)
296298
if err != nil {
@@ -333,7 +335,7 @@ func runXUITestWithBundleIdsXcode15Ctx(
333335
}
334336

335337
testSessionID := uuid.New()
336-
testconfig := createTestConfig(info, testSessionID, xctestConfigFileName, testsToRun, testsToSkip)
338+
testconfig := createTestConfig(info, testSessionID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
337339
ideDaemonProxy1 := newDtxProxyWithConfig(conn1, testconfig, testListener)
338340

339341
localCaps := nskeyedarchiver.XCTCapabilities{CapabilitiesDictionary: map[string]interface{}{
@@ -360,7 +362,7 @@ func runXUITestWithBundleIdsXcode15Ctx(
360362
}
361363
defer appserviceConn.Close()
362364

363-
testRunnerLaunch, err := startTestRunner17(device, appserviceConn, "", testRunnerBundleID, strings.ToUpper(testSessionID.String()), info.testApp.path+"/PlugIns/"+xctestConfigFileName, args, env)
365+
testRunnerLaunch, err := startTestRunner17(device, appserviceConn, "", testRunnerBundleID, strings.ToUpper(testSessionID.String()), info.testApp.path+"/PlugIns/"+xctestConfigFileName, args, env, isXCTest)
364366
if err != nil {
365367
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot start test runner: %w", err)
366368
}
@@ -441,16 +443,21 @@ func killTestRunner(killer processKiller, pid int) error {
441443
return nil
442444
}
443445

444-
func startTestRunner17(device ios.DeviceEntry, appserviceConn *appservice.Connection, xctestConfigPath string, bundleID string, sessionIdentifier string, testBundlePath string, testArgs []string, testEnv []string) (appservice.LaunchedAppWithStdIo, error) {
446+
func startTestRunner17(device ios.DeviceEntry, appserviceConn *appservice.Connection, xctestConfigPath string, bundleID string, sessionIdentifier string, testBundlePath string, testArgs []string, testEnv []string, isXCTest bool) (appservice.LaunchedAppWithStdIo, error) {
445447
args := []interface{}{}
446448
for _, arg := range testArgs {
447449
args = append(args, arg)
448450
}
449451

452+
libraries := "/Developer/usr/lib/libMainThreadChecker.dylib"
453+
if isXCTest {
454+
libraries += ":/System/Developer/usr/lib/libXCTestBundleInject.dylib"
455+
}
456+
450457
env := map[string]interface{}{
451458
"CA_ASSERT_MAIN_THREAD_TRANSACTIONS": "0",
452459
"CA_DEBUG_TRANSACTIONS": "0",
453-
"DYLD_INSERT_LIBRARIES": "/Developer/usr/lib/libMainThreadChecker.dylib",
460+
"DYLD_INSERT_LIBRARIES": libraries,
454461
"DYLD_FRAMEWORK_PATH": "/System/Developer/Library/Frameworks",
455462
"DYLD_LIBRARY_PATH": "/System/Developer/usr/lib",
456463

@@ -492,7 +499,7 @@ func startTestRunner17(device ios.DeviceEntry, appserviceConn *appservice.Connec
492499
return appLaunch, nil
493500
}
494501

495-
func setupXcuiTest(device ios.DeviceEntry, bundleID string, testRunnerBundleID string, xctestConfigFileName string, testsToRun []string, testsToSkip []string) (uuid.UUID, string, nskeyedarchiver.XCTestConfiguration, testInfo, error) {
502+
func setupXcuiTest(device ios.DeviceEntry, bundleID string, testRunnerBundleID string, xctestConfigFileName string, testsToRun []string, testsToSkip []string, isXCTest bool) (uuid.UUID, string, nskeyedarchiver.XCTestConfiguration, testInfo, error) {
496503
testSessionID := uuid.New()
497504
installationProxy, err := installationproxy.New(device)
498505
if err != nil {
@@ -530,21 +537,21 @@ func setupXcuiTest(device ios.DeviceEntry, bundleID string, testRunnerBundleID s
530537
return uuid.UUID{}, "", nskeyedarchiver.XCTestConfiguration{}, testInfo{}, err
531538
}
532539
log.Debugf("creating test config")
533-
testConfigPath, testConfig, err := createTestConfigOnDevice(testSessionID, info, houseArrestService, xctestConfigFileName, testsToRun, testsToSkip)
540+
testConfigPath, testConfig, err := createTestConfigOnDevice(testSessionID, info, houseArrestService, xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
534541
if err != nil {
535542
return uuid.UUID{}, "", nskeyedarchiver.XCTestConfiguration{}, testInfo{}, err
536543
}
537544

538545
return testSessionID, testConfigPath, testConfig, info, nil
539546
}
540547

541-
func createTestConfigOnDevice(testSessionID uuid.UUID, info testInfo, houseArrestService *house_arrest.Connection, xctestConfigFileName string, testsToRun []string, testsToSkip []string) (string, nskeyedarchiver.XCTestConfiguration, error) {
548+
func createTestConfigOnDevice(testSessionID uuid.UUID, info testInfo, houseArrestService *house_arrest.Connection, xctestConfigFileName string, testsToRun []string, testsToSkip []string, isXCTest bool) (string, nskeyedarchiver.XCTestConfiguration, error) {
542549
relativeXcTestConfigPath := path.Join("tmp", testSessionID.String()+".xctestconfiguration")
543550
xctestConfigPath := path.Join(info.testApp.homePath, relativeXcTestConfigPath)
544551

545552
testBundleURL := path.Join(info.testApp.path, "PlugIns", xctestConfigFileName)
546553

547-
config := nskeyedarchiver.NewXCTestConfiguration(info.targetApp.bundleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, testBundleURL, testsToRun, testsToSkip)
554+
config := nskeyedarchiver.NewXCTestConfiguration(info.targetApp.bundleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, testBundleURL, testsToRun, testsToSkip, isXCTest)
548555
result, err := nskeyedarchiver.ArchiveXML(config)
549556
if err != nil {
550557
return "", nskeyedarchiver.XCTestConfiguration{}, err
@@ -554,13 +561,13 @@ func createTestConfigOnDevice(testSessionID uuid.UUID, info testInfo, houseArres
554561
if err != nil {
555562
return "", nskeyedarchiver.XCTestConfiguration{}, err
556563
}
557-
return xctestConfigPath, nskeyedarchiver.NewXCTestConfiguration(info.targetApp.bundleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, testBundleURL, testsToRun, testsToSkip), nil
564+
return xctestConfigPath, nskeyedarchiver.NewXCTestConfiguration(info.targetApp.bundleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, testBundleURL, testsToRun, testsToSkip, isXCTest), nil
558565
}
559566

560-
func createTestConfig(info testInfo, testSessionID uuid.UUID, xctestConfigFileName string, testsToRun []string, testsToSkip []string) nskeyedarchiver.XCTestConfiguration {
567+
func createTestConfig(info testInfo, testSessionID uuid.UUID, xctestConfigFileName string, testsToRun []string, testsToSkip []string, isXCTest bool) nskeyedarchiver.XCTestConfiguration {
561568
// the default value for this generated by Xcode is the target name, and the same name is used for the '.xctest' bundle name per default
562569
productModuleName := strings.ReplaceAll(xctestConfigFileName, ".xctest", "")
563-
return nskeyedarchiver.NewXCTestConfiguration(productModuleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, "PlugIns/"+xctestConfigFileName, testsToRun, testsToSkip)
570+
return nskeyedarchiver.NewXCTestConfiguration(productModuleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, "PlugIns/"+xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
564571
}
565572

566573
type testInfo struct {

ios/testmanagerd/xcuitestrunner_11.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ func runXCUIWithBundleIdsXcode11Ctx(
2222
testsToRun []string,
2323
testsToSkip []string,
2424
testListener *TestListener,
25+
isXCTest bool,
2526
) ([]TestSuite, error) {
2627
log.Debugf("set up xcuitest")
27-
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip)
28+
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
2829
if err != nil {
2930
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsXcode11Ctx: cannot create test config: %w", err)
3031
}

ios/testmanagerd/xcuitestrunner_12.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import (
1414
)
1515

1616
func runXUITestWithBundleIdsXcode12Ctx(ctx context.Context, bundleID string, testRunnerBundleID string, xctestConfigFileName string,
17-
device ios.DeviceEntry, args []string, env []string, testsToRun []string, testsToSkip []string, testListener *TestListener,
17+
device ios.DeviceEntry, args []string, env []string, testsToRun []string, testsToSkip []string, testListener *TestListener, isXCTest bool,
1818
) ([]TestSuite, error) {
1919
conn, err := dtx.NewUsbmuxdConnection(device, testmanagerdiOS14)
2020
if err != nil {
2121
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot create a usbmuxd connection to testmanagerd: %w", err)
2222
}
2323

24-
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip)
24+
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
2525
if err != nil {
2626
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot setup test config: %w", err)
2727
}

main.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ Usage:
7474
ios info [display | lockdown] [options]
7575
ios image list [options]
7676
ios image mount [--path=<imagepath>] [options]
77-
ios image unmount [options]
7877
ios image auto [--basedir=<where_dev_images_are_stored>] [options]
7978
ios syslog [options]
8079
ios screenshot [options] [--output=<outfile>] [--stream] [--port=<port>]
@@ -111,7 +110,7 @@ Usage:
111110
ios apps [--system] [--all] [--list] [--filesharing] [options]
112111
ios launch <bundleID> [--wait] [--kill-existing] [options]
113112
ios kill (<bundleID> | --pid=<processID> | --process=<processName>) [options]
114-
ios runtest [--bundle-id=<bundleid>] [--test-runner-bundle-id=<testrunnerbundleid>] [--xctest-config=<xctestconfig>] [--log-output=<file>] [--test-to-run=<tests>]... [--test-to-skip=<tests>]... [--env=<e>]... [options]
113+
ios runtest [--bundle-id=<bundleid>] [--test-runner-bundle-id=<testrunnerbundleid>] [--xctest-config=<xctestconfig>] [--log-output=<file>] [--xctest] [--test-to-run=<tests>]... [--test-to-skip=<tests>]... [--env=<e>]... [options]
115114
ios runwda [--bundleid=<bundleid>] [--testrunnerbundleid=<testbundleid>] [--xctestconfig=<xctestconfig>] [--log-output=<file>] [--arg=<a>]... [--env=<e>]... [options]
116115
ios ax [--font=<fontSize>] [options]
117116
ios debug [options] [--stop-at-entry] <app_path>
@@ -222,7 +221,7 @@ The commands work as following:
222221
ios apps [--system] [--all] [--list] [--filesharing] Retrieves a list of installed applications. --system prints out preinstalled system apps. --all prints all apps, including system, user, and hidden apps. --list only prints bundle ID, bundle name and version number. --filesharing only prints apps which enable documents sharing.
223222
ios launch <bundleID> [--wait] [--kill-existing] [options] Launch app with the bundleID on the device. Get your bundle ID from the apps command. --wait keeps the connection open if you want logs.
224223
ios kill (<bundleID> | --pid=<processID> | --process=<processName>) [options] Kill app with the specified bundleID, process id, or process name on the device.
225-
ios runtest [--bundle-id=<bundleid>] [--test-runner-bundle-id=<testbundleid>] [--xctest-config=<xctestconfig>] [--log-output=<file>] [--test-to-run=<tests>]... [--test-to-skip=<tests>]... [--env=<e>]... [options] Run a XCUITest. If you provide only bundle-id go-ios will try to dynamically create test-runner-bundle-id and xctest-config.
224+
ios runtest [--bundle-id=<bundleid>] [--test-runner-bundle-id=<testbundleid>] [--xctest-config=<xctestconfig>] [--log-output=<file>] [--xctest] [--test-to-run=<tests>]... [--test-to-skip=<tests>]... [--env=<e>]... [options] Run a XCUITest. If you provide only bundle-id go-ios will try to dynamically create test-runner-bundle-id and xctest-config.
226225
> If you provide '-' as log output, it prints resuts to stdout.
227226
> To be able to filter for tests to run or skip, use one argument per test selector. Example: runtest --test-to-run=(TestTarget.)TestClass/testMethod --test-to-run=(TestTarget.)TestClass/testMethod (the value for 'TestTarget' is optional)
228227
> The method name can also be omitted and in this case all tests of the specified class are run
@@ -907,6 +906,8 @@ The commands work as following:
907906
rawTestlog, rawTestlogErr := arguments.String("--log-output")
908907
env := arguments["--env"].([]string)
909908

909+
isXCTest, _ := arguments.Bool("--xctest")
910+
910911
if rawTestlogErr == nil {
911912
var writer *os.File = os.Stdout
912913
if rawTestlog != "-" {
@@ -916,14 +917,14 @@ The commands work as following:
916917
}
917918
defer writer.Close()
918919

919-
testResults, err := testmanagerd.RunXCUITest(bundleID, testRunnerBundleId, xctestConfig, device, env, testsToRun, testsToSkip, testmanagerd.NewTestListener(writer, writer, os.TempDir()))
920+
testResults, err := testmanagerd.RunXCUITest(bundleID, testRunnerBundleId, xctestConfig, device, env, testsToRun, testsToSkip, testmanagerd.NewTestListener(writer, writer, os.TempDir()), isXCTest)
920921
if err != nil {
921922
log.WithFields(log.Fields{"error": err}).Info("Failed running Xcuitest")
922923
}
923924

924925
log.Info(fmt.Printf("%+v", testResults))
925926
} else {
926-
_, err := testmanagerd.RunXCUITest(bundleID, testRunnerBundleId, xctestConfig, device, env, testsToRun, testsToSkip, testmanagerd.NewTestListener(io.Discard, io.Discard, os.TempDir()))
927+
_, err := testmanagerd.RunXCUITest(bundleID, testRunnerBundleId, xctestConfig, device, env, testsToRun, testsToSkip, testmanagerd.NewTestListener(io.Discard, io.Discard, os.TempDir()), isXCTest)
927928
if err != nil {
928929
log.WithFields(log.Fields{"error": err}).Info("Failed running Xcuitest")
929930
}
@@ -1214,7 +1215,7 @@ func runWdaCommand(device ios.DeviceEntry, arguments docopt.Opts) bool {
12141215
defer close(errorChannel)
12151216
ctx, stopWda := context.WithCancel(context.Background())
12161217
go func() {
1217-
_, err := testmanagerd.RunXCUIWithBundleIdsCtx(ctx, bundleID, testbundleID, xctestconfig, device, wdaargs, wdaenv, nil, nil, testmanagerd.NewTestListener(writer, writer, os.TempDir()))
1218+
_, err := testmanagerd.RunXCUIWithBundleIdsCtx(ctx, bundleID, testbundleID, xctestconfig, device, wdaargs, wdaenv, nil, nil, testmanagerd.NewTestListener(writer, writer, os.TempDir()), false)
12181219
if err != nil {
12191220
errorChannel <- err
12201221
}

0 commit comments

Comments
 (0)