Files
ipsw/internal/xcode/devicesupport_test.go

189 lines
6.2 KiB
Go

package xcode
import (
"os"
"path/filepath"
"testing"
)
// writeDSC creates a fake DeviceSupport dump under xcodeDir for the given
// platform/device dir, placing main + subcache files in the cache dir. It
// returns the path to the main cache file that the locator should return.
func writeDSC(t *testing.T, xcodeDir, platformDir, deviceDir string, withSymbols bool) string {
t.Helper()
return writeDSCAt(t, xcodeDir, platformDir, deviceDir, deviceSupportSystemCacheDir, withSymbols)
}
func writeDSCAt(t *testing.T, xcodeDir, platformDir, deviceDir, cacheRel string, withSymbols bool) string {
t.Helper()
root := filepath.Join(xcodeDir, platformDir, deviceDir)
if withSymbols {
root = filepath.Join(root, "Symbols")
}
cacheDir := filepath.Join(root, cacheRel)
if err := os.MkdirAll(cacheDir, 0o755); err != nil {
t.Fatalf("mkdir %s: %v", cacheDir, err)
}
main := filepath.Join(cacheDir, "dyld_shared_cache_arm64e")
for _, name := range []string{
"dyld_shared_cache_arm64e",
"dyld_shared_cache_arm64e.01",
"dyld_shared_cache_arm64e.symbols",
} {
if err := os.WriteFile(filepath.Join(cacheDir, name), []byte("x"), 0o644); err != nil {
t.Fatalf("write %s: %v", name, err)
}
}
return main
}
func TestFindDeviceSupportDSCsCryptexLayout(t *testing.T) {
t.Parallel()
xcodeDir := t.TempDir()
want := writeDSCAt(t, xcodeDir, "iOS DeviceSupport", "iPhone12,1 26.5 (23F77)",
deviceSupportCryptexCacheDir, true)
got, err := findDeviceSupport(xcodeDir, "iPhone12,1", "26.5", "23F77")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got.DSCs) != 1 || got.DSCs[0] != want {
t.Fatalf("DSCs = %v, want [%s]", got.DSCs, want)
}
}
func TestFindDeviceSupportDSCsModernLayout(t *testing.T) {
t.Parallel()
xcodeDir := t.TempDir()
want := writeDSC(t, xcodeDir, "iOS DeviceSupport", "iPhone12,1 26.5 (23F77)", true)
got, err := findDeviceSupport(xcodeDir, "iPhone12,1", "26.5", "23F77")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got.DSCs) != 1 || got.DSCs[0] != want {
t.Fatalf("DSCs = %v, want [%s]", got.DSCs, want)
}
}
func TestFindDeviceSupportDSCsLegacyLayout(t *testing.T) {
t.Parallel()
xcodeDir := t.TempDir()
// Older Xcode: no device prefix, no Symbols subdir.
want := writeDSC(t, xcodeDir, "iOS DeviceSupport", "14.5 (18E5154f)", false)
got, err := findDeviceSupport(xcodeDir, "iPhone13,3", "14.5", "18E5154f")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got.DSCs) != 1 || got.DSCs[0] != want {
t.Fatalf("DSCs = %v, want [%s]", got.DSCs, want)
}
}
func TestFindDeviceSupportDSCsDisambiguatesByProduct(t *testing.T) {
t.Parallel()
xcodeDir := t.TempDir()
writeDSC(t, xcodeDir, "iOS DeviceSupport", "iPhone14,2 23F77 (23F77)", true)
want := writeDSC(t, xcodeDir, "iOS DeviceSupport", "iPhone12,1 26.5 (23F77)", true)
got, err := findDeviceSupport(xcodeDir, "iPhone12,1", "26.5", "23F77")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.DSCs[0] != want {
t.Fatalf("DSCs[0] = %s, want %s (product should disambiguate)", got.DSCs[0], want)
}
}
func TestFindDeviceSupportDSCsNoBuild(t *testing.T) {
t.Parallel()
if _, err := findDeviceSupport(t.TempDir(), "iPhone12,1", "26.5", ""); err == nil {
t.Fatal("expected error for empty build, got nil")
}
}
func TestFindDeviceSupportDSCsNoMatch(t *testing.T) {
t.Parallel()
xcodeDir := t.TempDir()
writeDSC(t, xcodeDir, "iOS DeviceSupport", "iPhone12,1 26.5 (23F77)", true)
if _, err := findDeviceSupport(xcodeDir, "iPhone12,1", "26.4", "23E224"); err == nil {
t.Fatal("expected error for unknown build, got nil")
}
}
func TestFindDeviceSupportDSCsResolvesHome(t *testing.T) {
// not parallel: mutates HOME via t.Setenv
home := t.TempDir()
t.Setenv("HOME", home)
want := writeDSC(t, filepath.Join(home, "Library", "Developer", "Xcode"), "iOS DeviceSupport", "iPhone12,1 26.5 (23F77)", true)
got, err := FindDeviceSupport("iPhone12,1", "26.5", "23F77")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got.DSCs) != 1 || got.DSCs[0] != want {
t.Fatalf("DSCs = %v, want [%s]", got.DSCs, want)
}
}
func TestFindDeviceSupportDSCsDirWithoutCache(t *testing.T) {
t.Parallel()
xcodeDir := t.TempDir()
// Empty dump: no dyld_shared_cache and no extracted dylibs.
if err := os.MkdirAll(filepath.Join(xcodeDir, "iOS DeviceSupport", "iPhone12,1 26.5 (23F77)", "Symbols"), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if _, err := findDeviceSupport(xcodeDir, "iPhone12,1", "26.5", "23F77"); err == nil {
t.Fatal("expected error when dump has neither DSC nor dylibs, got nil")
}
}
func TestFindDeviceSupportLooseDylibsOnly(t *testing.T) {
t.Parallel()
xcodeDir := t.TempDir()
// Modern Xcode: extracted dylibs under Symbols/System, NO dyld_shared_cache.
symbols := filepath.Join(xcodeDir, "iOS DeviceSupport", "iPhone18,4 26.4.2 (23E261)", "Symbols")
fw := filepath.Join(symbols, "System", "Library", "Frameworks", "Foundation.framework")
if err := os.MkdirAll(fw, 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(filepath.Join(fw, "Foundation"), []byte("x"), 0o644); err != nil {
t.Fatalf("write: %v", err)
}
got, err := findDeviceSupport(xcodeDir, "iPhone18,4", "26.4.2", "23E261")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got.DSCs) != 0 {
t.Fatalf("DSCs = %v, want empty (loose-dylib dump)", got.DSCs)
}
if got.Symbols != symbols {
t.Fatalf("Symbols = %q, want %q", got.Symbols, symbols)
}
}
func TestFindDeviceSupportLooseDylibsUsrOnly(t *testing.T) {
t.Parallel()
xcodeDir := t.TempDir()
// Dump with extracted dylibs only under usr/ (no System/), no dyld_shared_cache.
symbols := filepath.Join(xcodeDir, "iOS DeviceSupport", "iPhone18,4 26.4.2 (23E261)", "Symbols")
libDir := filepath.Join(symbols, "usr", "lib", "system")
if err := os.MkdirAll(libDir, 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(filepath.Join(libDir, "libsystem_kernel.dylib"), []byte("x"), 0o644); err != nil {
t.Fatalf("write: %v", err)
}
got, err := findDeviceSupport(xcodeDir, "iPhone18,4", "26.4.2", "23E261")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got.DSCs) != 0 || got.Symbols != symbols {
t.Fatalf("got DSCs=%v Symbols=%q, want empty DSCs and Symbols=%q", got.DSCs, got.Symbols, symbols)
}
}