Skip to content

Commit

Permalink
[WIP] implement ADB tests
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski committed Sep 8, 2024
1 parent 71c1f67 commit 5cd58f3
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,4 @@ fastlane/test_output

iOSInjectionProject/

.DS_Store
.DS_Store
20 changes: 20 additions & 0 deletions MiniSim.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
76059BF52AD4361C0008D38B /* SetupPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76059BF42AD4361C0008D38B /* SetupPreferences.swift */; };
76059BF72AD449DC0008D38B /* OnboardingHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76059BF62AD449DC0008D38B /* OnboardingHeader.swift */; };
76059BF92AD558C30008D38B /* SetupItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76059BF82AD558C30008D38B /* SetupItemView.swift */; };
760DEACE2B0DFB6600253576 /* ShellStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 760DEACD2B0DFB6600253576 /* ShellStub.swift */; };
7610992D2A3F95850067885A /* MiniSim.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 7610992C2A3F95850067885A /* MiniSim.sdef */; };
7610992F2A3F95D90067885A /* NSScriptCommand+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7610992E2A3F95D90067885A /* NSScriptCommand+utils.swift */; };
7625140B2992B46D0060A225 /* Pasteboard+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7625140A2992B46D0060A225 /* Pasteboard+utils.swift */; };
Expand Down Expand Up @@ -78,6 +79,8 @@
76BF0AD92C8CB3E6003BE568 /* AcknowList in Frameworks */ = {isa = PBXBuildFile; productRef = 76BF0AD82C8CB3E6003BE568 /* AcknowList */; };
76BF0ADB2C8CB4CD003BE568 /* Package.resolved in Resources */ = {isa = PBXBuildFile; fileRef = 76BF0ADA2C8CB4CD003BE568 /* Package.resolved */; };
76C1396A2C849A3F006CD80C /* MenuIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76C139692C849A3F006CD80C /* MenuIcons.swift */; };
76B70F822B0D50FE009D87A4 /* ADBTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B70F812B0D50FE009D87A4 /* ADBTests.swift */; };
76B70F842B0D5AB4009D87A4 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B70F832B0D5AB4009D87A4 /* Shell.swift */; };
76E4451229D4391000039025 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E4451129D4391000039025 /* Onboarding.swift */; };
76E4451429D4403F00039025 /* NSNotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E4451329D4403F00039025 /* NSNotificationName.swift */; };
76F04A11298A5AE000BF9CA3 /* ADB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F04A10298A5AE000BF9CA3 /* ADB.swift */; };
Expand Down Expand Up @@ -115,6 +118,7 @@
76059BF42AD4361C0008D38B /* SetupPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupPreferences.swift; sourceTree = "<group>"; };
76059BF62AD449DC0008D38B /* OnboardingHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingHeader.swift; sourceTree = "<group>"; };
76059BF82AD558C30008D38B /* SetupItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupItemView.swift; sourceTree = "<group>"; };
760DEACD2B0DFB6600253576 /* ShellStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellStub.swift; sourceTree = "<group>"; };
7610992C2A3F95850067885A /* MiniSim.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = MiniSim.sdef; sourceTree = "<group>"; };
7610992E2A3F95D90067885A /* NSScriptCommand+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScriptCommand+utils.swift"; sourceTree = "<group>"; };
7625140A2992B46D0060A225 /* Pasteboard+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pasteboard+utils.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -171,6 +175,8 @@
76B70F7D2B0D361A009D87A4 /* UserDefaultsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsTests.swift; sourceTree = "<group>"; };
76BF0ADA2C8CB4CD003BE568 /* Package.resolved */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Package.resolved; path = MiniSim.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; sourceTree = SOURCE_ROOT; };
76C139692C849A3F006CD80C /* MenuIcons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuIcons.swift; sourceTree = "<group>"; };
76B70F812B0D50FE009D87A4 /* ADBTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADBTests.swift; sourceTree = "<group>"; };
76B70F832B0D5AB4009D87A4 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
76E4451129D4391000039025 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = "<group>"; };
76E4451329D4403F00039025 /* NSNotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSNotificationName.swift; sourceTree = "<group>"; };
76F04A10298A5AE000BF9CA3 /* ADB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADB.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -230,6 +236,14 @@
path = Terminal;
sourceTree = "<group>";
};
760DEACC2B0DFB5B00253576 /* Mocks */ = {
isa = PBXGroup;
children = (
760DEACD2B0DFB6600253576 /* ShellStub.swift */,
);
path = Mocks;
sourceTree = "<group>";
};
762CF1E12981DDD400099999 /* Extensions */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -297,6 +311,7 @@
7645D4BD2982A1B100019227 /* DeviceService.swift */,
7699511C2C845B1900462287 /* DeviceParser.swift */,
76F04A10298A5AE000BF9CA3 /* ADB.swift */,
76B70F832B0D5AB4009D87A4 /* Shell.swift */,
);
path = Service;
sourceTree = "<group>";
Expand Down Expand Up @@ -396,8 +411,10 @@
76B70F752B0D359D009D87A4 /* MiniSimTests */ = {
isa = PBXGroup;
children = (
760DEACC2B0DFB5B00253576 /* Mocks */,
76B70F7D2B0D361A009D87A4 /* UserDefaultsTests.swift */,
7699511E2C845CBA00462287 /* DeviceParserTests.swift */,
76B70F812B0D50FE009D87A4 /* ADBTests.swift */,
);
path = MiniSimTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -619,6 +636,7 @@
7630B2752986D52900D8B57D /* NSAlert+showError.swift in Sources */,
4AFACC742AD730BE00EC369F /* SubMenuItem.swift in Sources */,
7630B25E2984339100D8B57D /* MainMenuActions.swift in Sources */,
76B70F842B0D5AB4009D87A4 /* Shell.swift in Sources */,
76AC9AF62A0EA82C00864A8B /* CustomCommands.swift in Sources */,
76489D5C29BFCA330070EF03 /* OnboardingItem.swift in Sources */,
7645D5012982E6FA00019227 /* main.swift in Sources */,
Expand Down Expand Up @@ -657,6 +675,8 @@
files = (
7699511F2C845CBA00462287 /* DeviceParserTests.swift in Sources */,
76B70F7E2B0D361A009D87A4 /* UserDefaultsTests.swift in Sources */,
760DEACE2B0DFB6600253576 /* ShellStub.swift in Sources */,
76B70F822B0D50FE009D87A4 /* ADBTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
55 changes: 43 additions & 12 deletions MiniSim/Service/Adb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@
//

import Foundation
import ShellOut

protocol ADBProtocol {
static var shell: ShellProtocol.Type { get set }

static func getAdbPath() throws -> String
static func getEmulatorPath() throws -> String
static func getAdbId(for deviceName: String, adbPath: String) throws -> String
static func checkAndroidHome(path: String) throws -> Bool
static func isAccesibilityOn(deviceId: String, adbPath: String) -> Bool
static func getAdbId(for deviceName: String) throws -> String
static func checkAndroidHome(
path: String,
fileManager: FileManager
) throws -> Bool
static func isAccesibilityOn(deviceId: String) -> Bool
static func toggleAccesibility(deviceId: String)
}

final class ADB: ADBProtocol {
static var shell: ShellProtocol.Type = Shell.self

static let talkbackOn = "com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService"
static let talkbackOff = "com.android.talkback/com.google.android.marvin.talkback.TalkBackService"

Expand Down Expand Up @@ -55,13 +62,16 @@ final class ADB: ADBProtocol {
/**
Checks if passed path exists and points to `ANDROID_HOME`.
*/
@discardableResult static func checkAndroidHome(path: String) throws -> Bool {
if !FileManager.default.fileExists(atPath: path) {
@discardableResult static func checkAndroidHome(
path: String,
fileManager: FileManager = .default
) throws -> Bool {
if !fileManager.fileExists(atPath: path) {
throw AndroidHomeError.pathNotFound
}

do {
try shellOut(to: "\(path)" + Paths.emulator.rawValue, arguments: ["-list-avds"])
try shell.execute(command: "\(path)" + Paths.emulator.rawValue, arguments: ["-list-avds"])
} catch {
throw AndroidHomeError.pathNotCorrect
}
Expand All @@ -72,14 +82,20 @@ final class ADB: ADBProtocol {
try getAndroidHome() + Paths.emulator.rawValue
}

static func getAdbId(for deviceName: String, adbPath: String) throws -> String {
let onlineDevices = try shellOut(to: "\(adbPath) devices")
static func getAdbId(for deviceName: String) throws -> String {
let adbPath = try Self.getAdbPath()
let onlineDevices = try shell.execute(command: "\(adbPath) devices")
let splitted = onlineDevices.components(separatedBy: "\n")

for line in splitted {
let device = line.match("^emulator-[0-9]+")
guard let deviceId = device.first?.first else { continue }
let output = try? shellOut(to: "\(adbPath) -s \(deviceId) emu avd name").components(separatedBy: "\n")

let output = try? shell.execute(
command: "\(adbPath) -s \(deviceId) emu avd name"
)
.components(separatedBy: "\n")

if let name = output?.first {
let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedDeviceName = deviceName.trimmingCharacters(in: .whitespacesAndNewlines)
Expand All @@ -91,9 +107,12 @@ final class ADB: ADBProtocol {
throw DeviceError.deviceNotFound
}

static func isAccesibilityOn(deviceId: String, adbPath: String) -> Bool {
static func isAccesibilityOn(deviceId: String) -> Bool {
guard let adbPath = try? Self.getAdbPath() else {
return false
}
let shellCommand = "\(adbPath) -s \(deviceId) shell settings get secure enabled_accessibility_services"
guard let result = try? shellOut(to: [shellCommand]) else {
guard let result = try? shell.execute(command: shellCommand) else {
return false
}

Expand All @@ -103,4 +122,16 @@ final class ADB: ADBProtocol {

return false
}

static func toggleAccesibility(deviceId: String) {
guard let adbPath = try? Self.getAdbPath() else {
return
}
let a11yIsEnabled = Self.isAccesibilityOn(deviceId: deviceId)
let value = a11yIsEnabled ? ADB.talkbackOff : ADB.talkbackOn
let shellCmd = "\(adbPath) -s \(deviceId) shell settings put secure enabled_accessibility_services \(value)"

// Ignore the error if toggling a11y fails.
_ = try? shell.execute(command: shellCmd)
}
}
34 changes: 34 additions & 0 deletions MiniSim/Service/Shell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Foundation
import ShellOut

protocol ShellProtocol {
@discardableResult static func execute(
command: String,
arguments: [String],
atPath: String
) throws -> String
}

extension ShellProtocol {
@discardableResult static func execute(
command: String,
arguments: [String] = [],
atPath: String = "."
) throws -> String {
try execute(command: command, arguments: arguments, atPath: atPath)
}
}

final class Shell: ShellProtocol {
@discardableResult static func execute(
command: String,
arguments: [String] = [],
atPath: String = "."
) throws -> String {
try shellOut(
to: command,
arguments: arguments,
at: atPath
)
}
}
127 changes: 127 additions & 0 deletions MiniSimTests/ADBTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import XCTest

@testable import MiniSim

class FileManagerStub: FileManager {
override func fileExists(atPath path: String) -> Bool {
true
}
}

class FileManagerEmptyStub: FileManager {
override func fileExists(atPath path: String) -> Bool {
false
}
}

final class ADBTests: XCTestCase {
let savedAndroidHome = UserDefaults.standard.androidHome
let defaultHomePath = "/Users/\(NSUserName())/Library/Android/sdk"

override func setUp() {
ADB.shell = ShellStub.self
UserDefaults.standard.removeObject(forKey: UserDefaults.Keys.androidHome)
}

override func tearDown() {
UserDefaults.standard.androidHome = savedAndroidHome
ShellStub.tearDown()
}

func testGetAndroidHome() throws {
let androidHome = try ADB.getAndroidHome()

XCTAssertEqual(androidHome, defaultHomePath)

UserDefaults.standard.androidHome = "customAndroidHome"
let customAndroidHome = try ADB.getAndroidHome()

XCTAssertEqual(
customAndroidHome,
"customAndroidHome",
"Setting custom androidHome overrides default one"
)
}

func testCheckAndroidHome() throws {
let output = try ADB.checkAndroidHome(
path: defaultHomePath,
fileManager: FileManagerStub()
)
XCTAssertEqual(output, true)
XCTAssertEqual(ShellStub.lastExecutedCommand, defaultHomePath + "/emulator/emulator")
XCTAssertEqual(ShellStub.lastPassedArguments, ["-list-avds"])

XCTAssertThrowsError(
try ADB.checkAndroidHome(
path: defaultHomePath,
fileManager: FileManagerEmptyStub()
)
)
}

func testGetUtilPaths() throws {
let adbPath = try ADB.getAdbPath()
let avdPath = try ADB.getAvdPath()

XCTAssertEqual(
adbPath,
defaultHomePath + "/platform-tools/adb"
)
XCTAssertEqual(
avdPath,
defaultHomePath + "/cmdline-tools/latest/bin/avdmanager"
)
}

func testGetAdbId() throws {
ShellStub.mockedExecute = { command, _, _ in
if command.contains("devices") {
return """
List of devices attached
emulator-5554 device
emulator-5556 device
"""
}

if command.contains("avd name") {
return """
Pixel_XL_API_32
OK
"""
}

return ""
}
let adbId = try ADB.getAdbId(for: "Pixel_XL_API_32")

XCTAssertEqual(adbId, "emulator-5554")

XCTAssertThrowsError(
try ADB.getAdbId(for: "Pixel_Not_Found")
)
}

func testIsAccesibilityOn() throws {
var isA11yOn: Bool
isA11yOn = ADB.isAccesibilityOn(deviceId: "emulator-5544")
XCTAssertFalse(isA11yOn)

ShellStub.mockedExecute = { _, _, _ in
"com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService"
}
isA11yOn = ADB.isAccesibilityOn(deviceId: "emulator-5544")
XCTAssertTrue(isA11yOn)
}

func testToggle11y() {
UserDefaults.standard.androidHome = "adbPath"
let expectedCommand = """
adbPath/platform-tools/adb -s emulator-5544 shell settings put secure enabled_accessibility_services com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService
"""

ADB.toggleAccesibility(deviceId: "emulator-5544")
XCTAssertEqual(ShellStub.lastExecutedCommand, expectedCommand)
XCTAssertEqual(ShellStub.lastPassedArguments, [])
}
}
27 changes: 27 additions & 0 deletions MiniSimTests/Mocks/ShellStub.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation

@testable import MiniSim

class ShellStub: ShellProtocol {
private(set) static var lastExecutedCommand: String = ""
private(set) static var lastPassedArguments: [String] = []
private(set) static var lastPassedPath: String = ""
static var mockedExecute: ((_ command: String, _ arguments: [String], _ atPath: String) -> String)?

static func execute(command: String, arguments: [String], atPath: String) throws -> String {
lastExecutedCommand = command
lastPassedArguments = arguments
lastPassedPath = atPath
if let mockedExecute {
return mockedExecute(command, arguments, atPath)
}
return ""
}

static func tearDown() {
lastExecutedCommand = ""
lastPassedArguments = []
lastPassedPath = ""
mockedExecute = nil
}
}

0 comments on commit 5cd58f3

Please sign in to comment.