Skip to content

Commit

Permalink
feat: implement device parser tests (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski authored Sep 1, 2024
1 parent ceb85f7 commit abe4b6e
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 37 deletions.
8 changes: 8 additions & 0 deletions MiniSim.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
767F713229D574EF004159A6 /* UNUserNotificationCenter+showNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 767F713129D574EF004159A6 /* UNUserNotificationCenter+showNotification.swift */; };
7684FAAF29D202F500230BB0 /* AndroidHomeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7684FAAE29D202F500230BB0 /* AndroidHomeError.swift */; };
768F8EC829954C8A00DFBCDB /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 768F8EC729954C8A00DFBCDB /* Sparkle */; };
7699511D2C845B1900462287 /* DeviceParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7699511C2C845B1900462287 /* DeviceParser.swift */; };
7699511F2C845CBA00462287 /* DeviceParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7699511E2C845CBA00462287 /* DeviceParserTests.swift */; };
76AC9AF62A0EA82C00864A8B /* CustomCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76AC9AF52A0EA82C00864A8B /* CustomCommands.swift */; };
76AC9AF92A0EB50800864A8B /* SymbolPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 76AC9AF82A0EB50800864A8B /* SymbolPicker */; };
76B70F7E2B0D361A009D87A4 /* UserDefaultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B70F7D2B0D361A009D87A4 /* UserDefaultsTests.swift */; };
Expand Down Expand Up @@ -158,6 +160,8 @@
767F713129D574EF004159A6 /* UNUserNotificationCenter+showNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNUserNotificationCenter+showNotification.swift"; sourceTree = "<group>"; };
7684FAAE29D202F500230BB0 /* AndroidHomeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AndroidHomeError.swift; sourceTree = "<group>"; };
768F8ECC2995575B00DFBCDB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
7699511C2C845B1900462287 /* DeviceParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceParser.swift; sourceTree = "<group>"; };
7699511E2C845CBA00462287 /* DeviceParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceParserTests.swift; sourceTree = "<group>"; };
76AC9AF52A0EA82C00864A8B /* CustomCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCommands.swift; sourceTree = "<group>"; };
76B70F742B0D359D009D87A4 /* MiniSimTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MiniSimTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
76B70F7D2B0D361A009D87A4 /* UserDefaultsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -281,6 +285,7 @@
55CDB0762B1B6D06002418D7 /* Terminal */,
76F269832A2A375900424BDA /* CustomErrors */,
7645D4BD2982A1B100019227 /* DeviceService.swift */,
7699511C2C845B1900462287 /* DeviceParser.swift */,
76F04A10298A5AE000BF9CA3 /* ADB.swift */,
);
path = Service;
Expand Down Expand Up @@ -381,6 +386,7 @@
isa = PBXGroup;
children = (
76B70F7D2B0D361A009D87A4 /* UserDefaultsTests.swift */,
7699511E2C845CBA00462287 /* DeviceParserTests.swift */,
);
path = MiniSimTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -584,6 +590,7 @@
7677999829C25894009030F8 /* OnboardingPager.swift in Sources */,
52B363EC2AEC10A3006F515C /* ParametersTableForm.swift in Sources */,
7630B2682985C4CF00D8B57D /* About.swift in Sources */,
7699511D2C845B1900462287 /* DeviceParser.swift in Sources */,
76630F0C29BDD0C000FB64F9 /* Devices.swift in Sources */,
767C761F29B26ED3009B9AEC /* AccessibilityElement.swift in Sources */,
7610992F2A3F95D90067885A /* NSScriptCommand+utils.swift in Sources */,
Expand Down Expand Up @@ -632,6 +639,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7699511F2C845CBA00462287 /* DeviceParserTests.swift in Sources */,
76B70F7E2B0D361A009D87A4 /* UserDefaultsTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
70 changes: 70 additions & 0 deletions MiniSim/Service/DeviceParser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Foundation

enum DeviceParserType {
case iosSimulator
case androidEmulator
}

protocol DeviceParser {
func parse(_ input: String) -> [Device]
}

class DeviceParserFactory {
func getParser(_ type: DeviceParserType) -> DeviceParser {
switch type {
case .iosSimulator:
return IOSSimulatorParser()
case .androidEmulator:
return AndroidEmulatorParser()
}
}
}

class IOSSimulatorParser: DeviceParser {
func parse(_ input: String) -> [Device] {
let lines = input.components(separatedBy: .newlines)
var devices: [Device] = []
let currentOSIdx = 1
let deviceNameIdx = 1
let identifierIdx = 4
let deviceStateIdx = 5
var osVersion = ""

lines.forEach { line in
if let currentOs = line.match("-- (.*?) --").first, !currentOs.isEmpty {
osVersion = currentOs[currentOSIdx]
}
if let device = line.match("(.*?) (\\(([0-9.]+)\\) )?\\(([0-9A-F-]+)\\) (\\(.*?)\\)").first {
devices.append(
Device(
name: device[deviceNameIdx].trimmingCharacters(in: .whitespacesAndNewlines),
version: osVersion,
identifier: device[identifierIdx],
booted: device[deviceStateIdx].contains("Booted"),
platform: .ios
)
)
}
}
return devices
}
}

class AndroidEmulatorParser: DeviceParser {
let adb: ADBProtocol.Type

required init(adb: ADBProtocol.Type = ADB.self) {
self.adb = adb
}

func parse(_ input: String) -> [Device] {
guard let adbPath = try? adb.getAdbPath() else { return [] }
let deviceNames = input.components(separatedBy: .newlines)
return deviceNames
.filter { !$0.isEmpty && !$0.contains("Storing crashdata") }
.compactMap { deviceName in
let adbId = try? adb.getAdbId(for: deviceName, adbPath: adbPath)
return Device(name: deviceName, identifier: adbId, booted: adbId != nil, platform: .android)
}
}
}
39 changes: 2 additions & 37 deletions MiniSim/Service/DeviceService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,32 +215,6 @@ class DeviceService: DeviceServiceProtocol {

// MARK: iOS Methods
extension DeviceService {
private static func parseIOSDevices(result: [String]) -> [Device] {
var devices: [Device] = []
let currentOSIdx = 1
let deviceNameIdx = 1
let identifierIdx = 4
let deviceStateIdx = 5
var osVersion = ""
result.forEach { line in
if let currentOs = line.match("-- (.*?) --").first, !currentOs.isEmpty {
osVersion = currentOs[currentOSIdx]
}
if let device = line.match("(.*?) (\\(([0-9.]+)\\) )?\\(([0-9A-F-]+)\\) (\\(.*?)\\)").first {
devices.append(
Device(
name: device[deviceNameIdx].trimmingCharacters(in: .whitespacesAndNewlines),
version: osVersion,
identifier: device[identifierIdx],
booted: device[deviceStateIdx].contains("Booted"),
platform: .ios
)
)
}
}
return devices
}

static func clearDerivedData(
completionQueue: DispatchQueue = .main,
completion: @escaping (String, Error?) -> Void
Expand Down Expand Up @@ -268,9 +242,7 @@ extension DeviceService {
to: ProcessPaths.xcrun.rawValue,
arguments: ["simctl", "list", "devices", "available"]
)
let splitted = output.components(separatedBy: "\n")

return parseIOSDevices(result: splitted)
return DeviceParserFactory().getParser(.iosSimulator).parse(output)
}

static func launchSimulatorApp(uuid: String) throws {
Expand Down Expand Up @@ -391,16 +363,9 @@ extension DeviceService {
Thread.assertBackgroundThread()

let emulatorPath = try ADB.getEmulatorPath()
let adbPath = try ADB.getAdbPath()
let output = try shellOut(to: emulatorPath, arguments: ["-list-avds"])
let splitted = output.components(separatedBy: "\n")

return splitted
.filter { !$0.isEmpty && !$0.contains(crashDataError) }
.map { deviceName in
let adbId = try? ADB.getAdbId(for: deviceName, adbPath: adbPath)
return Device(name: deviceName, identifier: adbId, booted: adbId != nil, platform: .android)
}
return DeviceParserFactory().getParser(.androidEmulator).parse(output)
}

static func toggleA11y(device: Device) throws {
Expand Down
169 changes: 169 additions & 0 deletions MiniSimTests/DeviceParserTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
@testable import MiniSim
import XCTest

// Mock ADB class for testing
class ADB: ADBProtocol {
static func getEmulatorPath() throws -> String {
""
}

static func checkAndroidHome(path: String) throws -> Bool {
true
}

static func isAccesibilityOn(deviceId: String, adbPath: String) -> Bool {
false
}

static func getAdbPath() throws -> String {
"/mock/adb/path"
}

static func getAdbId(for deviceName: String, adbPath: String) throws -> String {
if deviceName == "Nexus_5X_API_28" {
throw NSError(domain: "ADBError", code: 1, userInfo: nil)
}
return "mock_adb_id_for_\(deviceName)"
}
}

class DeviceParserTests: XCTestCase {
func testDeviceParserFactory() {
let iosParser = DeviceParserFactory().getParser(.iosSimulator)
XCTAssertTrue(iosParser is IOSSimulatorParser)

let androidParser = DeviceParserFactory().getParser(.androidEmulator)
XCTAssertTrue(androidParser is AndroidEmulatorParser)
}

func testIOSSimulatorParser() {
let parser = IOSSimulatorParser()
let input = """
== Devices ==
-- iOS 17.5 --
iPhone SE (3rd generation) (957C8A2F-4C12-4732-A4E9-37F8FDD35E3B) (Booted)
iPhone 15 (7B8464FF-956F-405B-B357-8ED4689E5177) (Shutdown)
iPhone 15 Plus (37A0352D-849D-463B-B513-D23ED0113A87) (Booted)
iPhone 15 Pro (9536A75B-5B77-40D8-B96D-925A60E5C0ED) (Shutdown)
iPhone 15 Pro Max (7ADF6567-9F08-42A4-A709-2460879038A7) (Shutdown)
iPad (10th generation) (D923D804-5E6C-4039-9095-294F7EE2EF3C) (Shutdown)
iPad mini (6th generation) (FD14D0FA-7D9A-4107-B73F-B137B7B61515) (Shutdown)
iPad Air 11-inch (M2) (67454794-F54F-43DB-868E-7798B32599D9) (Shutdown)
iPad Air 13-inch (M2) (2BF9FCDF-EF46-4340-BF90-5DA59AA9F55C) (Shutdown)
iPad Pro 11-inch (M4) (7EF89937-90BE-41E5-BD53-03BA6050D63F) (Shutdown)
iPad Pro 13-inch (M4) (0F74471B-3D0C-4EDA-9D65-E6A430217294) (Shutdown)
-- visionOS 1.2 --
Apple Vision Pro (CD50D0C6-D8F6-424E-B1C2-1C288EDBBD79) (Shutdown)
-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-15-5 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-16-1 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-17-0 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-17-2 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-17-4 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.tvOS-15-4 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.tvOS-16-1 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.watchOS-8-5 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.xrOS-1-0 --
-- Unavailable: com.apple.CoreSimulator.SimRuntime.xrOS-1-1 --
"""

let devices = parser.parse(input)

XCTAssertEqual(devices.count, 12)

XCTAssertEqual(devices[0].name, "iPhone SE (3rd generation)")
XCTAssertEqual(devices[0].version, "iOS 17.5")
XCTAssertEqual(devices[0].identifier, "957C8A2F-4C12-4732-A4E9-37F8FDD35E3B")
XCTAssertTrue(devices[0].booted)
XCTAssertEqual(devices[0].platform, .ios)

XCTAssertEqual(devices[1].name, "iPhone 15")
XCTAssertEqual(devices[1].version, "iOS 17.5")
XCTAssertEqual(devices[1].identifier, "7B8464FF-956F-405B-B357-8ED4689E5177")
XCTAssertFalse(devices[1].booted)
XCTAssertEqual(devices[1].platform, .ios)

XCTAssertEqual(devices[2].name, "iPhone 15 Plus")
XCTAssertEqual(devices[2].version, "iOS 17.5")
XCTAssertEqual(devices[2].identifier, "37A0352D-849D-463B-B513-D23ED0113A87")
XCTAssertTrue(devices[2].booted)
XCTAssertEqual(devices[2].platform, .ios)
}

func testAndroidEmulatorParser() {
let parser = AndroidEmulatorParser(adb: ADB.self)
let input = """
Pixel_3a_API_30_x86
Pixel_4_API_29
Nexus_5X_API_28
"""

let devices = parser.parse(input)

XCTAssertEqual(devices.count, 3)

XCTAssertEqual(devices[0].name, "Pixel_3a_API_30_x86")
XCTAssertEqual(devices[0].identifier, "mock_adb_id_for_Pixel_3a_API_30_x86")
XCTAssertTrue(devices[0].booted)
XCTAssertEqual(devices[0].platform, .android)

XCTAssertEqual(devices[1].name, "Pixel_4_API_29")
XCTAssertEqual(devices[1].identifier, "mock_adb_id_for_Pixel_4_API_29")
XCTAssertTrue(devices[1].booted)
XCTAssertEqual(devices[1].platform, .android)

XCTAssertEqual(devices[2].name, "Nexus_5X_API_28")
XCTAssertEqual(devices[2].identifier, nil)
XCTAssertFalse(devices[2].booted)
XCTAssertEqual(devices[2].platform, .android)
}

func filtersOutEmulatorCrashData() {
let parser = AndroidEmulatorParser(adb: ADB.self)
let input = """
Pixel_3a_API_30_x86
INFO | Storing crashdata in: /tmp/android-test/emu-crash-34.1.20.db, detection is enabled for process: 58515
"""

let devices = parser.parse(input)

XCTAssertEqual(devices.count, 1)

XCTAssertEqual(devices[0].name, "Pixel_3a_API_30_x86")
XCTAssertEqual(devices[0].identifier, "mock_adb_id_for_Pixel_3a_API_30_x86")
XCTAssertTrue(devices[0].booted)
XCTAssertEqual(devices[0].platform, .android)

XCTAssertNil(devices.first(where: { $0.name.contains("crashdata") }))

Check warning on line 136 in MiniSimTests/DeviceParserTests.swift

View workflow job for this annotation

GitHub Actions / build

Trailing Closure Violation: Trailing closure syntax should be used whenever possible (trailing_closure)
}

func testAndroidEmulatorParserWithADBFailure() {
class FailingADB: ADBProtocol {
static func getEmulatorPath() throws -> String {
""
}

static func checkAndroidHome(path: String) throws -> Bool {
true
}

static func isAccesibilityOn(deviceId: String, adbPath: String) -> Bool {
false
}

static func getAdbPath() throws -> String {
throw NSError(domain: "ADBError", code: 1, userInfo: nil)
}

static func getAdbId(for deviceName: String, adbPath: String) throws -> String {
throw NSError(domain: "ADBError", code: 2, userInfo: nil)
}
}

let parser = AndroidEmulatorParser(adb: FailingADB.self)
let input = "Pixel_3a_API_30_x86"

let devices = parser.parse(input)

XCTAssertTrue(devices.isEmpty)
}
}

0 comments on commit abe4b6e

Please sign in to comment.