Skip to content

Commit

Permalink
Implementing blocking test runs on iOS17
Browse files Browse the repository at this point in the history
  • Loading branch information
diegoperini committed Dec 20, 2023
1 parent a7e8249 commit f4128a6
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 91 deletions.
2 changes: 0 additions & 2 deletions ios/appservice/appservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ func buildAppLaunchPayload(deviceId string, bundleId string, args []interface{},
panic(err)
}

env["TERM"] = "xterm-256color"

return buildRequest(deviceId, "com.apple.coredevice.feature.launchapplication", map[string]interface{}{
"applicationSpecifier": map[string]interface{}{
"bundleIdentifier": map[string]interface{}{
Expand Down
125 changes: 77 additions & 48 deletions ios/testmanagerd/xcuitestrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ type ProxyDispatcher struct {
testRunnerReadyWithCapabilities dtx.MethodWithResponse
dtxConnection *dtx.Connection
id string
closeChannel chan interface{}
closedChannel chan interface{}
}

func (p ProxyDispatcher) Dispatch(m dtx.Message) {
Expand All @@ -202,7 +204,10 @@ func (p ProxyDispatcher) Dispatch(m dtx.Message) {
p.dtxConnection.Send(messageBytes)
case "_XCT_didFinishExecutingTestPlan":
log.Info("_XCT_didFinishExecutingTestPlan received. Closing test.")
CloseXCUITestRunner()
p.DispatchClose()
case "_XCT_didFailToBootstrapWithError:":
log.Info("_XCT_didFailToBootstrapWithError received. Closing test.")
p.DispatchClose()
default:
log.WithFields(log.Fields{"sel": method}).Infof("device called local method")
}
Expand All @@ -213,10 +218,24 @@ func (p ProxyDispatcher) Dispatch(m dtx.Message) {
log.Tracef("dispatcher received: %s", m.String())
}

func (p *ProxyDispatcher) DispatchClose() error {
var signal interface{}
go func() {
p.closeChannel <- signal
log.Debug("DIEGO")
}()
select {
case <-p.closedChannel:
return nil
case <-time.After(10 * time.Second):
return fmt.Errorf("Failed closing, exiting due to timeout")
}
}

func newDtxProxyWithConfig(dtxConnection *dtx.Connection, testConfig nskeyedarchiver.XCTestConfiguration) dtxproxy {
testBundleReadyChannel := make(chan dtx.Message, 1)
//(xide XCTestManager_IDEInterface)
proxyDispatcher := ProxyDispatcher{testBundleReadyChannel: testBundleReadyChannel, dtxConnection: dtxConnection, testRunnerReadyWithCapabilities: testRunnerReadyWithCapabilitiesConfig(testConfig)}
proxyDispatcher := ProxyDispatcher{testBundleReadyChannel: testBundleReadyChannel, dtxConnection: dtxConnection, testRunnerReadyWithCapabilities: testRunnerReadyWithCapabilitiesConfig(testConfig), closeChannel: make(chan interface{}, 1), closedChannel: make(chan interface{}, 1)}
IDEDaemonProxy := dtxConnection.RequestChannelIdentifier(ideToDaemonProxyChannelName, proxyDispatcher)
ideInterface := XCTestManager_IDEInterface{IDEDaemonProxy: IDEDaemonProxy, testConfig: testConfig, testBundleReadyChannel: testBundleReadyChannel}

Expand Down Expand Up @@ -263,13 +282,17 @@ func RunXCUITest(bundleID string, testRunnerBundleID string, xctestConfigName st
xctestConfigName = info.targetAppBundleName + "UITests.xctest"
}

return RunXCUIWithBundleIdsCtx(nil, bundleID, testRunnerBundleID, xctestConfigName, device, nil, env)
_, err = RunXCUIWithBundleIdsCtx(nil, bundleID, testRunnerBundleID, xctestConfigName, device, nil, env)
return err
}

var (
closeChan = make(chan interface{})
closedChan = make(chan interface{})
)
type TestRunner struct {
proxyDispatcher ProxyDispatcher
}

func (t *TestRunner) Close() error {
return t.proxyDispatcher.DispatchClose()
}

func RunXCUIWithBundleIdsCtx(
ctx context.Context,
Expand All @@ -279,10 +302,10 @@ func RunXCUIWithBundleIdsCtx(
device ios.DeviceEntry,
wdaargs []string,
wdaenv []string,
) error {
) (*TestRunner, error) {
version, err := ios.GetProductVersion(device)
if err != nil {
return err
return nil, err
}

if version.LessThan(ios.IOS14()) {
Expand All @@ -296,46 +319,36 @@ func RunXCUIWithBundleIdsCtx(
}

log.Debugf("iOS version: %s detected, running with ios17 support", version)
return runXUITestWithBundleIdsXcode15Ctx(bundleID, testRunnerBundleID, xctestConfigFileName, device)
}

func CloseXCUITestRunner() error {
var signal interface{}
go func() { closeChan <- signal }()
select {
case <-closedChan:
return nil
case <-time.After(5 * time.Second):
return fmt.Errorf("Failed closing, exiting due to timeout")
}
return runXUITestWithBundleIdsXcode15Ctx(ctx, bundleID, testRunnerBundleID, xctestConfigFileName, device)
}

func runXUITestWithBundleIdsXcode15Ctx(
ctx context.Context,
bundleID string,
testRunnerBundleID string,
xctestConfigFileName string,
device ios.DeviceEntry,
) error {
) (*TestRunner, error) {
conn1, err := dtx.NewTunnelConnection(device, testmanagerdiOS17)
if err != nil {
return err
return nil, err
}
defer conn1.Close()

conn2, err := dtx.NewTunnelConnection(device, testmanagerdiOS17)
if err != nil {
return err
return nil, err
}
defer conn2.Close()

installationProxy, err := installationproxy.New(device)
if err != nil {
return err
return nil, err
}
defer installationProxy.Close()
apps, err := installationProxy.BrowseUserApps()
if err != nil {
return err
return nil, err
}

info, err := getAppInfos(bundleID, testRunnerBundleID, apps)
Expand All @@ -348,17 +361,17 @@ func runXUITestWithBundleIdsXcode15Ctx(

proto, err := ideDaemonProxy1.daemonConnection.initiateSessionWithIdentifier(testSessionID, 29)
if err != nil {
return err
return nil, err
}
log.WithField("proto", proto).Info("got capabilities")

appserviceConn, err := appservice.New(device)
if err != nil {
return err
return nil, err
}
defer appserviceConn.Close()

pid, err := startTestRunner17(device, appserviceConn, "", testRunnerBundleID, strings.ToUpper(testSessionID.String()), info.testrunnerAppPath+"/PlugIns/"+xctestConfigFileName, []string{}, []string{})
pid, err := startTestRunner17(device, appserviceConn, "", testRunnerBundleID, strings.ToUpper(testSessionID.String()), info.testrunnerAppPath+"/PlugIns/"+xctestConfigFileName)

localCaps := nskeyedarchiver.XCTCapabilities{CapabilitiesDictionary: map[string]interface{}{
"XCTIssue capability": uint64(1),
Expand All @@ -376,37 +389,61 @@ func runXUITestWithBundleIdsXcode15Ctx(
ideDaemonProxy2 := newDtxProxyWithConfig(conn2, testconfig)
caps, err := ideDaemonProxy1.daemonConnection.initiateControlSessionWithCapabilities(localCaps)
if err != nil {
return err
return nil, err
}
log.WithField("caps", caps).Info("got capabilities")
authorized, err := ideDaemonProxy2.daemonConnection.authorizeTestSessionWithProcessID(pid)
if err != nil {
return err
return nil, err
}
log.WithField("authorized", authorized).Info("authorized")

err = ideDaemonProxy2.daemonConnection.initiateControlSession(pid, proto)
if err != nil {
return err
return nil, err
}
log.Debug("control session initiated")

ideInterfaceChannel := ideDaemonProxy1.dtxConnection.ForChannelRequest(ProxyDispatcher{id: "dtxproxy:XCTestDriverInterface:XCTestManager_IDEInterface"})
proxyDispatcher := ProxyDispatcher{id: "dtxproxy:XCTestDriverInterface:XCTestManager_IDEInterface", closeChannel: make(chan interface{}, 1), closedChannel: make(chan interface{}, 1)}
ideInterfaceChannel := ideDaemonProxy1.dtxConnection.ForChannelRequest(proxyDispatcher)

err = ideDaemonProxy1.daemonConnection.startExecutingTestPlanWithProtocolVersion(ideInterfaceChannel, proto)

time.Sleep(60 * time.Second)
if ctx != nil {
select {
case <-ctx.Done():
log.Infof("Killing test runner with pid %d ...", pid)
err = appserviceConn.KillProcess(int(pid))
if err != nil {
return nil, err
}
log.Info("Test runner killed with success")
}
return &TestRunner{proxyDispatcher: proxyDispatcher}, nil
}

return nil
<-proxyDispatcher.closeChannel
log.Debugf("Killing test runner with pid %d ...", pid)
err = appserviceConn.KillProcess(int(pid))
if err != nil {
return nil, err
}
log.Debugf("Test runner killed with success")
var signal interface{}
proxyDispatcher.closedChannel <- signal

return &TestRunner{proxyDispatcher: proxyDispatcher}, nil
}

func startTestRunner17(device ios.DeviceEntry, appserviceConn *appservice.Connection, xctestConfigPath string, bundleID string,
sessionIdentifier string, testBundlePath string, wdaargs []string, wdaenv []string,
func startTestRunner17(
device ios.DeviceEntry,
appserviceConn *appservice.Connection,
xctestConfigPath string,
bundleID string,
sessionIdentifier string,
testBundlePath string,
) (uint64, error) {
args := []interface{}{}
for _, arg := range wdaargs {
args = append(args, arg)
}

env := map[string]interface{}{
"CA_ASSERT_MAIN_THREAD_TRANSACTIONS": "0",
Expand All @@ -425,14 +462,6 @@ func startTestRunner17(device ios.DeviceEntry, appserviceConn *appservice.Connec
"XCTestSessionIdentifier": strings.ToUpper(sessionIdentifier),
}

for _, entrystring := range wdaenv {
entry := strings.Split(entrystring, "=")
key := entry[0]
value := entry[1]
env[key] = value
log.Debugf("adding extra env %s=%s", key, value)
}

opts := map[string]interface{}{
"ActivateSuspended": uint64(1),
"StartSuspendedKey": uint64(0),
Expand Down
39 changes: 20 additions & 19 deletions ios/testmanagerd/xcuitestrunner_11.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ func RunXCUIWithBundleIdsXcode11Ctx(
device ios.DeviceEntry,
args []string,
env []string,
) error {
) (*TestRunner, error) {
log.Debugf("set up xcuitest")
testSessionId, xctestConfigPath, testConfig, testInfo, err := setupXcuiTest(device, bundleID, testRunnerBundleID, xctestConfigFileName)
if err != nil {
return err
return nil, err
}
log.Debugf("test session setup ok")
conn, err := dtx.NewUsbmuxdConnection(device, testmanagerd)
if err != nil {
return err
return nil, err
}
defer conn.Close()
ideDaemonProxy := newDtxProxyWithConfig(conn, testConfig)

conn2, err := dtx.NewUsbmuxdConnection(device, testmanagerd)
if err != nil {
return err
return nil, err
}
defer conn2.Close()
log.Debug("connections ready")
Expand All @@ -44,27 +44,28 @@ func RunXCUIWithBundleIdsXcode11Ctx(
protocolVersion := uint64(25)
_, err = ideDaemonProxy.daemonConnection.initiateSessionWithIdentifier(testSessionId, protocolVersion)
if err != nil {
return err
return nil, err
}

pControl, err := instruments.NewProcessControl(device)
if err != nil {
return err
return nil, err
}
defer pControl.Close()

pid, err := startTestRunner11(pControl, xctestConfigPath, testRunnerBundleID, testSessionId.String(), testInfo.testrunnerAppPath+"/PlugIns/"+xctestConfigFileName, args, env)
if err != nil {
return err
return nil, err
}
log.Debugf("Runner started with pid:%d, waiting for testBundleReady", pid)

err = ideDaemonProxy2.daemonConnection.initiateControlSession(pid, protocolVersion)
if err != nil {
return err
return nil, err
}
log.Debugf("control session initiated")
ideInterfaceChannel := ideDaemonProxy.dtxConnection.ForChannelRequest(ProxyDispatcher{id: "emty"})
proxyDispatcher := ProxyDispatcher{id: "emty", closeChannel: make(chan interface{}), closedChannel: make(chan interface{})}
ideInterfaceChannel := ideDaemonProxy.dtxConnection.ForChannelRequest(proxyDispatcher)

log.Debug("start executing testplan")
err = ideDaemonProxy2.daemonConnection.startExecutingTestPlanWithProtocolVersion(ideInterfaceChannel, 25)
Expand All @@ -74,26 +75,26 @@ func RunXCUIWithBundleIdsXcode11Ctx(
if ctx != nil {
select {
case <-ctx.Done():
log.Infof("Killing WebDriverAgent with pid %d ...", pid)
log.Infof("Killing test runner with pid %d ...", pid)
err = pControl.KillProcess(pid)
if err != nil {
return err
return nil, err
}
log.Info("WDA killed with success")
log.Info("Test runner killed with success")
}
return nil
return &TestRunner{proxyDispatcher: proxyDispatcher}, nil
}
log.Debugf("done starting test")
<-closeChan
log.Infof("Killing WebDriverAgent with pid %d ...", pid)
<-proxyDispatcher.closeChannel
log.Infof("Killing test runner with pid %d ...", pid)
err = pControl.KillProcess(pid)
if err != nil {
return err
return nil, err
}
log.Info("WDA killed with success")
log.Info("Test runner killed with success")
var signal interface{}
closedChan <- signal
return nil
proxyDispatcher.closedChannel <- signal
return &TestRunner{proxyDispatcher: proxyDispatcher}, nil
}

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

0 comments on commit f4128a6

Please sign in to comment.