mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
refactor(ioscompanion): deliver COMPANION_PORT via TEST_RUNNER_ env
This commit is contained in:
1 parent
4bdb0e8934
commit
71019843ff
2 files changed
+8
-108
No files matched your search
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -92,9 +91,9 @@ func VerifyDeviceSigning() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// realSpawnDeviceRunner regenerates the runner project, builds it for the device
|
// realSpawnDeviceRunner regenerates the runner project, builds it for the device
|
||||||
// (skipping when the cached build matches the current sources), injects the
|
// (skipping when the cached build matches the current sources), and spawns the
|
||||||
// session port into the device xctestrun, and spawns the test session that hosts
|
// test session that hosts the runner on the device, passing the session port via
|
||||||
// the runner on the device. address carries the host loopback port, reused as
|
// TEST_RUNNER_COMPANION_PORT. address carries the host loopback port, reused as
|
||||||
// the device-side COMPANION_PORT.
|
// the device-side COMPANION_PORT.
|
||||||
func (d *Driver) realSpawnDeviceRunner(ctx context.Context, address string) (*exec.Cmd, error) {
|
func (d *Driver) realSpawnDeviceRunner(ctx context.Context, address string) (*exec.Cmd, error) {
|
||||||
_, port, err := net.SplitHostPort(address)
|
_, port, err := net.SplitHostPort(address)
|
||||||
@@ -123,9 +122,6 @@ func (d *Driver) realSpawnDeviceRunner(ctx context.Context, address string) (*ex
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := injectCompanionPort(ctx, xctestrunPath, port); err != nil {
|
|
||||||
return nil, fmt.Errorf("inject companion port: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logPath := filepath.Join(derivedDataPath, "device-session-"+port+".log")
|
logPath := filepath.Join(derivedDataPath, "device-session-"+port+".log")
|
||||||
logFile, err := os.Create(logPath)
|
logFile, err := os.Create(logPath)
|
||||||
@@ -139,11 +135,15 @@ func (d *Driver) realSpawnDeviceRunner(ctx context.Context, address string) (*ex
|
|||||||
command.Stderr = logFile
|
command.Stderr = logFile
|
||||||
// Minimal environment: the session can echo its environment into the run
|
// Minimal environment: the session can echo its environment into the run
|
||||||
// log, so secrets in the parent environment must not reach it. Signing is
|
// log, so secrets in the parent environment must not reach it. Signing is
|
||||||
// passed by flag (a key-file path), not env.
|
// passed by flag (a key-file path), not env. xcodebuild forwards any
|
||||||
|
// TEST_RUNNER_-prefixed var into the runner with the prefix stripped, so the
|
||||||
|
// non-secret COMPANION_PORT reaches the device that way instead of a plist
|
||||||
|
// patch.
|
||||||
command.Env = []string{
|
command.Env = []string{
|
||||||
"HOME=" + os.Getenv("HOME"),
|
"HOME=" + os.Getenv("HOME"),
|
||||||
"PATH=/usr/bin:/bin",
|
"PATH=/usr/bin:/bin",
|
||||||
"TMPDIR=" + os.TempDir(),
|
"TMPDIR=" + os.TempDir(),
|
||||||
|
"TEST_RUNNER_COMPANION_PORT=" + port,
|
||||||
}
|
}
|
||||||
command.Cancel = func() error { return command.Process.Signal(syscall.SIGTERM) }
|
command.Cancel = func() error { return command.Process.Signal(syscall.SIGTERM) }
|
||||||
command.WaitDelay = shutdownGrace
|
command.WaitDelay = shutdownGrace
|
||||||
@@ -255,69 +255,6 @@ func locateDeviceXctestrun(derivedDataPath string) (string, error) {
|
|||||||
return "", fmt.Errorf("no xctestrun under %s", products)
|
return "", fmt.Errorf("no xctestrun under %s", products)
|
||||||
}
|
}
|
||||||
|
|
||||||
// injectCompanionPort sets COMPANION_PORT in the runner's environment inside the
|
|
||||||
// device xctestrun. The device xctestrun has no port placeholder and its
|
|
||||||
// test-target dict name embeds the SDK, so the name is parsed from the plist and
|
|
||||||
// the key is set (created when absent).
|
|
||||||
func injectCompanionPort(ctx context.Context, xctestrunPath, port string) error {
|
|
||||||
jsonBytes, err := plistAsJSON(ctx, xctestrunPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
target, err := testTargetNameFromJSON(jsonBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
keyPath := ":" + target + ":EnvironmentVariables:COMPANION_PORT"
|
|
||||||
// Set updates an existing key; when the key is absent (the common device
|
|
||||||
// case) Set fails and Add creates it. Running both, ignoring Set's failure,
|
|
||||||
// makes the injection idempotent across reruns against a cached build.
|
|
||||||
_ = exec.CommandContext(ctx, "/usr/libexec/PlistBuddy", "-c", "Set "+keyPath+" "+port, xctestrunPath).Run()
|
|
||||||
if out, err := exec.CommandContext(ctx, "/usr/libexec/PlistBuddy",
|
|
||||||
"-c", "Add "+keyPath+" string "+port, xctestrunPath).CombinedOutput(); err != nil {
|
|
||||||
// Add fails when the key already exists, which means Set above succeeded.
|
|
||||||
if !strings.Contains(string(out), "Entry Already Exists") {
|
|
||||||
return fmt.Errorf("PlistBuddy: %w: %s", err, strings.TrimSpace(string(out)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// plistAsJSON converts a plist to JSON via plutil so it can be parsed without a
|
|
||||||
// plist library.
|
|
||||||
func plistAsJSON(ctx context.Context, plistPath string) ([]byte, error) {
|
|
||||||
out, err := exec.CommandContext(ctx, "plutil", "-convert", "json", "-o", "-", plistPath).Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("plutil convert %s: %w", plistPath, err)
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// testTargetNameFromJSON returns the single test-target dict name in a parsed
|
|
||||||
// xctestrun: the lone top-level key that is not the metadata entry.
|
|
||||||
func testTargetNameFromJSON(data []byte) (string, error) {
|
|
||||||
var top map[string]json.RawMessage
|
|
||||||
if err := json.Unmarshal(data, &top); err != nil {
|
|
||||||
return "", fmt.Errorf("parse xctestrun json: %w", err)
|
|
||||||
}
|
|
||||||
var names []string
|
|
||||||
for key := range top {
|
|
||||||
if key == "__xctestrun_metadata__" || strings.HasPrefix(key, "CodeCoverageBuildableInfos") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, key)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
switch len(names) {
|
|
||||||
case 1:
|
|
||||||
return names[0], nil
|
|
||||||
case 0:
|
|
||||||
return "", fmt.Errorf("xctestrun has no test-target dict")
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("xctestrun has multiple test-target dicts: %s", strings.Join(names, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sourceHash digests the runner sources and project spec so a source edit
|
// sourceHash digests the runner sources and project spec so a source edit
|
||||||
// invalidates the cached device build. Mirrors the runnerassets checksum reuse.
|
// invalidates the cached device build. Mirrors the runnerassets checksum reuse.
|
||||||
func sourceHash(companionDir string) (string, error) {
|
func sourceHash(companionDir string) (string, error) {
|
||||||
|
|||||||
@@ -7,43 +7,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTestTargetNameFromJSON(t *testing.T) {
|
|
||||||
// A device xctestrun-as-json: one test-target dict plus the metadata entry.
|
|
||||||
data := []byte(`{
|
|
||||||
"CompanionRunnerUITests": {"EnvironmentVariables": {}},
|
|
||||||
"__xctestrun_metadata__": {"FormatVersion": 1}
|
|
||||||
}`)
|
|
||||||
name, err := testTargetNameFromJSON(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if name != "CompanionRunnerUITests" {
|
|
||||||
t.Fatalf("name = %q, want CompanionRunnerUITests", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTestTargetNameFromJSON_IgnoresCodeCoverageInfo(t *testing.T) {
|
|
||||||
data := []byte(`{
|
|
||||||
"CompanionRunnerUITests": {},
|
|
||||||
"CodeCoverageBuildableInfos": {},
|
|
||||||
"__xctestrun_metadata__": {}
|
|
||||||
}`)
|
|
||||||
name, err := testTargetNameFromJSON(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if name != "CompanionRunnerUITests" {
|
|
||||||
t.Fatalf("name = %q, want CompanionRunnerUITests", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTestTargetNameFromJSON_MultipleTargetsError(t *testing.T) {
|
|
||||||
data := []byte(`{"A": {}, "B": {}, "__xctestrun_metadata__": {}}`)
|
|
||||||
if _, err := testTargetNameFromJSON(data); err == nil {
|
|
||||||
t.Fatal("multiple test-target dicts must error so the wrong dict is never patched")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadSigningCredentialsReportsMissing(t *testing.T) {
|
func TestReadSigningCredentialsReportsMissing(t *testing.T) {
|
||||||
for _, key := range []string{envTeam, envTeamFallback, envAuthKeyPath, envAuthKeyID, envAuthIssuer} {
|
for _, key := range []string{envTeam, envTeamFallback, envAuthKeyPath, envAuthKeyID, envAuthIssuer} {
|
||||||
t.Setenv(key, "")
|
t.Setenv(key, "")
|
||||||
|
|||||||
Reference in new issue
Block a user