Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for XCTest #464

Merged
merged 7 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ios/nskeyedarchiver/archiver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestArchiveSlice(t *testing.T) {
// TODO currently only partially decoding XCTestConfig is supported, fix later
func TestXCTestconfig(t *testing.T) {
uuid := uuid.New()
config := nskeyedarchiver.NewXCTestConfiguration("productmodulename", uuid, "targetAppBundle", "targetAppPath", "testBundleUrl", nil, nil)
config := nskeyedarchiver.NewXCTestConfiguration("productmodulename", uuid, "targetAppBundle", "targetAppPath", "testBundleUrl", nil, nil, false)
result, err := nskeyedarchiver.ArchiveXML(config)
if err != nil {
log.Error(err)
Expand Down
3 changes: 2 additions & 1 deletion ios/nskeyedarchiver/objectivec_classes.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func NewXCTestConfiguration(
testBundleURL string,
testsToRun []string,
testsToSkip []string,
isXCTest bool,
) XCTestConfiguration {
contents := map[string]interface{}{}

Expand Down Expand Up @@ -126,7 +127,7 @@ func NewXCTestConfiguration(
contents["emitOSLogs"] = false
// contents["formatVersion"] = 2
contents["gatherLocalizableStringsData"] = false
contents["initializeForUITesting"] = true
contents["initializeForUITesting"] = !isXCTest
contents["maximumTestExecutionTimeAllowance"] = plist.UID(0)
contents["randomExecutionOrderingSeed"] = plist.UID(0)
contents["reportActivities"] = true
Expand Down
39 changes: 23 additions & 16 deletions ios/testmanagerd/xcuitestrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ const (

const testBundleSuffix = "UITests.xctrunner"

func RunXCUITest(bundleID string, testRunnerBundleID string, xctestConfigName string, device ios.DeviceEntry, env []string, testsToRun []string, testsToSkip []string, testListener *TestListener) ([]TestSuite, error) {
func RunXCUITest(bundleID string, testRunnerBundleID string, xctestConfigName string, device ios.DeviceEntry, env []string, 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)
Expand All @@ -246,7 +246,7 @@ func RunXCUITest(bundleID string, testRunnerBundleID string, xctestConfigName st
xctestConfigName = info.bundleName + "UITests.xctest"
}

return RunXCUIWithBundleIdsCtx(context.TODO(), bundleID, testRunnerBundleID, xctestConfigName, device, nil, env, testsToRun, testsToSkip, testListener)
return RunXCUIWithBundleIdsCtx(context.TODO(), bundleID, testRunnerBundleID, xctestConfigName, device, nil, env, testsToRun, testsToSkip, testListener, isXCTest)
}

func RunXCUIWithBundleIdsCtx(
Expand All @@ -260,6 +260,7 @@ func RunXCUIWithBundleIdsCtx(
testsToRun []string,
testsToSkip []string,
testListener *TestListener,
isXCTest bool,
) ([]TestSuite, error) {
version, err := ios.GetProductVersion(device)
if err != nil {
Expand All @@ -268,16 +269,16 @@ func RunXCUIWithBundleIdsCtx(

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)
return runXCUIWithBundleIdsXcode11Ctx(ctx, bundleID, testRunnerBundleID, xctestConfigFileName, device, args, env, testsToRun, testsToSkip, testListener, isXCTest)
}

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)
return runXUITestWithBundleIdsXcode12Ctx(ctx, bundleID, testRunnerBundleID, xctestConfigFileName, device, args, env, testsToRun, testsToSkip, testListener, isXCTest)
}

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

func runXUITestWithBundleIdsXcode15Ctx(
Expand All @@ -291,6 +292,7 @@ func runXUITestWithBundleIdsXcode15Ctx(
testsToRun []string,
testsToSkip []string,
testListener *TestListener,
isXCTest bool,
) ([]TestSuite, error) {
conn1, err := dtx.NewTunnelConnection(device, testmanagerdiOS17)
if err != nil {
Expand Down Expand Up @@ -333,7 +335,7 @@ func runXUITestWithBundleIdsXcode15Ctx(
}

testSessionID := uuid.New()
testconfig := createTestConfig(info, testSessionID, xctestConfigFileName, testsToRun, testsToSkip)
testconfig := createTestConfig(info, testSessionID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
ideDaemonProxy1 := newDtxProxyWithConfig(conn1, testconfig, testListener)

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

testRunnerLaunch, err := startTestRunner17(device, appserviceConn, "", testRunnerBundleID, strings.ToUpper(testSessionID.String()), info.testApp.path+"/PlugIns/"+xctestConfigFileName, args, env)
testRunnerLaunch, err := startTestRunner17(device, appserviceConn, "", testRunnerBundleID, strings.ToUpper(testSessionID.String()), info.testApp.path+"/PlugIns/"+xctestConfigFileName, args, env, isXCTest)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("runXUITestWithBundleIdsXcode15Ctx: cannot start test runner: %w", err)
}
Expand Down Expand Up @@ -441,16 +443,21 @@ 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 []string) (appservice.LaunchedAppWithStdIo, error) {
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) {
args := []interface{}{}
for _, arg := range testArgs {
args = append(args, arg)
}

libraries := "/Developer/usr/lib/libMainThreadChecker.dylib"
if isXCTest {
libraries += ":/System/Developer/usr/lib/libXCTestBundleInject.dylib"
}

env := map[string]interface{}{
"CA_ASSERT_MAIN_THREAD_TRANSACTIONS": "0",
"CA_DEBUG_TRANSACTIONS": "0",
"DYLD_INSERT_LIBRARIES": "/Developer/usr/lib/libMainThreadChecker.dylib",
"DYLD_INSERT_LIBRARIES": libraries,
"DYLD_FRAMEWORK_PATH": "/System/Developer/Library/Frameworks",
"DYLD_LIBRARY_PATH": "/System/Developer/usr/lib",

Expand Down Expand Up @@ -492,7 +499,7 @@ func startTestRunner17(device ios.DeviceEntry, appserviceConn *appservice.Connec
return appLaunch, nil
}

func setupXcuiTest(device ios.DeviceEntry, bundleID string, testRunnerBundleID string, xctestConfigFileName string, testsToRun []string, testsToSkip []string) (uuid.UUID, string, nskeyedarchiver.XCTestConfiguration, testInfo, error) {
func setupXcuiTest(device ios.DeviceEntry, bundleID string, testRunnerBundleID string, xctestConfigFileName string, testsToRun []string, testsToSkip []string, isXCTest bool) (uuid.UUID, string, nskeyedarchiver.XCTestConfiguration, testInfo, error) {
testSessionID := uuid.New()
installationProxy, err := installationproxy.New(device)
if err != nil {
Expand Down Expand Up @@ -530,21 +537,21 @@ func setupXcuiTest(device ios.DeviceEntry, bundleID string, testRunnerBundleID s
return uuid.UUID{}, "", nskeyedarchiver.XCTestConfiguration{}, testInfo{}, err
}
log.Debugf("creating test config")
testConfigPath, testConfig, err := createTestConfigOnDevice(testSessionID, info, houseArrestService, xctestConfigFileName, testsToRun, testsToSkip)
testConfigPath, testConfig, err := createTestConfigOnDevice(testSessionID, info, houseArrestService, xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
if err != nil {
return uuid.UUID{}, "", nskeyedarchiver.XCTestConfiguration{}, testInfo{}, err
}

return testSessionID, testConfigPath, testConfig, info, nil
}

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

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

config := nskeyedarchiver.NewXCTestConfiguration(info.targetApp.bundleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, testBundleURL, testsToRun, testsToSkip)
config := nskeyedarchiver.NewXCTestConfiguration(info.targetApp.bundleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, testBundleURL, testsToRun, testsToSkip, isXCTest)
result, err := nskeyedarchiver.ArchiveXML(config)
if err != nil {
return "", nskeyedarchiver.XCTestConfiguration{}, err
Expand All @@ -554,13 +561,13 @@ func createTestConfigOnDevice(testSessionID uuid.UUID, info testInfo, houseArres
if err != nil {
return "", nskeyedarchiver.XCTestConfiguration{}, err
}
return xctestConfigPath, nskeyedarchiver.NewXCTestConfiguration(info.targetApp.bundleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, testBundleURL, testsToRun, testsToSkip), nil
return xctestConfigPath, nskeyedarchiver.NewXCTestConfiguration(info.targetApp.bundleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, testBundleURL, testsToRun, testsToSkip, isXCTest), nil
}

func createTestConfig(info testInfo, testSessionID uuid.UUID, xctestConfigFileName string, testsToRun []string, testsToSkip []string) nskeyedarchiver.XCTestConfiguration {
func createTestConfig(info testInfo, testSessionID uuid.UUID, xctestConfigFileName string, testsToRun []string, testsToSkip []string, isXCTest bool) nskeyedarchiver.XCTestConfiguration {
// 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
productModuleName := strings.ReplaceAll(xctestConfigFileName, ".xctest", "")
return nskeyedarchiver.NewXCTestConfiguration(productModuleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, "PlugIns/"+xctestConfigFileName, testsToRun, testsToSkip)
return nskeyedarchiver.NewXCTestConfiguration(productModuleName, testSessionID, info.targetApp.bundleID, info.targetApp.path, "PlugIns/"+xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
}

type testInfo struct {
Expand Down
3 changes: 2 additions & 1 deletion ios/testmanagerd/xcuitestrunner_11.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ func runXCUIWithBundleIdsXcode11Ctx(
testsToRun []string,
testsToSkip []string,
testListener *TestListener,
isXCTest bool,
) ([]TestSuite, error) {
log.Debugf("set up xcuitest")
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip)
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXCUIWithBundleIdsXcode11Ctx: cannot create test config: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions ios/testmanagerd/xcuitestrunner_12.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import (
)

func runXUITestWithBundleIdsXcode12Ctx(ctx context.Context, bundleID string, testRunnerBundleID string, xctestConfigFileName string,
device ios.DeviceEntry, args []string, env []string, testsToRun []string, testsToSkip []string, testListener *TestListener,
device ios.DeviceEntry, args []string, env []string, testsToRun []string, testsToSkip []string, testListener *TestListener, isXCTest bool,
) ([]TestSuite, error) {
conn, err := dtx.NewUsbmuxdConnection(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)
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName, testsToRun, testsToSkip, isXCTest)
if err != nil {
return make([]TestSuite, 0), fmt.Errorf("RunXUITestWithBundleIdsXcode12Ctx: cannot setup test config: %w", err)
}
Expand Down
13 changes: 7 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ Usage:
ios info [display | lockdown] [options]
ios image list [options]
ios image mount [--path=<imagepath>] [options]
ios image unmount [options]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this has been removed by mistake 😢

ios image auto [--basedir=<where_dev_images_are_stored>] [options]
ios syslog [options]
ios screenshot [options] [--output=<outfile>] [--stream] [--port=<port>]
Expand Down Expand Up @@ -111,7 +110,7 @@ Usage:
ios apps [--system] [--all] [--list] [--filesharing] [options]
ios launch <bundleID> [--wait] [--kill-existing] [options]
ios kill (<bundleID> | --pid=<processID> | --process=<processName>) [options]
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]
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]
ios runwda [--bundleid=<bundleid>] [--testrunnerbundleid=<testbundleid>] [--xctestconfig=<xctestconfig>] [--log-output=<file>] [--arg=<a>]... [--env=<e>]... [options]
ios ax [--font=<fontSize>] [options]
ios debug [options] [--stop-at-entry] <app_path>
Expand Down Expand Up @@ -222,7 +221,7 @@ The commands work as following:
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.
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.
ios kill (<bundleID> | --pid=<processID> | --process=<processName>) [options] Kill app with the specified bundleID, process id, or process name on the device.
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.
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.
> If you provide '-' as log output, it prints resuts to stdout.
> 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)
> The method name can also be omitted and in this case all tests of the specified class are run
Expand Down Expand Up @@ -907,6 +906,8 @@ The commands work as following:
rawTestlog, rawTestlogErr := arguments.String("--log-output")
env := arguments["--env"].([]string)

isXCTest, _ := arguments.Bool("--xctest")

if rawTestlogErr == nil {
var writer *os.File = os.Stdout
if rawTestlog != "-" {
Expand All @@ -916,14 +917,14 @@ The commands work as following:
}
defer writer.Close()

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

log.Info(fmt.Printf("%+v", testResults))
} else {
_, err := testmanagerd.RunXCUITest(bundleID, testRunnerBundleId, xctestConfig, device, env, testsToRun, testsToSkip, testmanagerd.NewTestListener(io.Discard, io.Discard, os.TempDir()))
_, err := testmanagerd.RunXCUITest(bundleID, testRunnerBundleId, xctestConfig, device, env, testsToRun, testsToSkip, testmanagerd.NewTestListener(io.Discard, io.Discard, os.TempDir()), isXCTest)
if err != nil {
log.WithFields(log.Fields{"error": err}).Info("Failed running Xcuitest")
}
Expand Down Expand Up @@ -1214,7 +1215,7 @@ func runWdaCommand(device ios.DeviceEntry, arguments docopt.Opts) bool {
defer close(errorChannel)
ctx, stopWda := context.WithCancel(context.Background())
go func() {
_, err := testmanagerd.RunXCUIWithBundleIdsCtx(ctx, bundleID, testbundleID, xctestconfig, device, wdaargs, wdaenv, nil, nil, testmanagerd.NewTestListener(writer, writer, os.TempDir()))
_, err := testmanagerd.RunXCUIWithBundleIdsCtx(ctx, bundleID, testbundleID, xctestconfig, device, wdaargs, wdaenv, nil, nil, testmanagerd.NewTestListener(writer, writer, os.TempDir()), false)
if err != nil {
errorChannel <- err
}
Expand Down
Loading