Skip to content

Commit 5cd58f3

Browse files
committed
[WIP] implement ADB tests
1 parent 71c1f67 commit 5cd58f3

File tree

6 files changed

+252
-13
lines changed

6 files changed

+252
-13
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ fastlane/test_output
9090

9191
iOSInjectionProject/
9292

93-
.DS_Store
93+
.DS_Store

MiniSim.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
76059BF52AD4361C0008D38B /* SetupPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76059BF42AD4361C0008D38B /* SetupPreferences.swift */; };
2020
76059BF72AD449DC0008D38B /* OnboardingHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76059BF62AD449DC0008D38B /* OnboardingHeader.swift */; };
2121
76059BF92AD558C30008D38B /* SetupItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76059BF82AD558C30008D38B /* SetupItemView.swift */; };
22+
760DEACE2B0DFB6600253576 /* ShellStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 760DEACD2B0DFB6600253576 /* ShellStub.swift */; };
2223
7610992D2A3F95850067885A /* MiniSim.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 7610992C2A3F95850067885A /* MiniSim.sdef */; };
2324
7610992F2A3F95D90067885A /* NSScriptCommand+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7610992E2A3F95D90067885A /* NSScriptCommand+utils.swift */; };
2425
7625140B2992B46D0060A225 /* Pasteboard+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7625140A2992B46D0060A225 /* Pasteboard+utils.swift */; };
@@ -78,6 +79,8 @@
7879
76BF0AD92C8CB3E6003BE568 /* AcknowList in Frameworks */ = {isa = PBXBuildFile; productRef = 76BF0AD82C8CB3E6003BE568 /* AcknowList */; };
7980
76BF0ADB2C8CB4CD003BE568 /* Package.resolved in Resources */ = {isa = PBXBuildFile; fileRef = 76BF0ADA2C8CB4CD003BE568 /* Package.resolved */; };
8081
76C1396A2C849A3F006CD80C /* MenuIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76C139692C849A3F006CD80C /* MenuIcons.swift */; };
82+
76B70F822B0D50FE009D87A4 /* ADBTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B70F812B0D50FE009D87A4 /* ADBTests.swift */; };
83+
76B70F842B0D5AB4009D87A4 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B70F832B0D5AB4009D87A4 /* Shell.swift */; };
8184
76E4451229D4391000039025 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E4451129D4391000039025 /* Onboarding.swift */; };
8285
76E4451429D4403F00039025 /* NSNotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E4451329D4403F00039025 /* NSNotificationName.swift */; };
8386
76F04A11298A5AE000BF9CA3 /* ADB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F04A10298A5AE000BF9CA3 /* ADB.swift */; };
@@ -115,6 +118,7 @@
115118
76059BF42AD4361C0008D38B /* SetupPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupPreferences.swift; sourceTree = "<group>"; };
116119
76059BF62AD449DC0008D38B /* OnboardingHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingHeader.swift; sourceTree = "<group>"; };
117120
76059BF82AD558C30008D38B /* SetupItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupItemView.swift; sourceTree = "<group>"; };
121+
760DEACD2B0DFB6600253576 /* ShellStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellStub.swift; sourceTree = "<group>"; };
118122
7610992C2A3F95850067885A /* MiniSim.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = MiniSim.sdef; sourceTree = "<group>"; };
119123
7610992E2A3F95D90067885A /* NSScriptCommand+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScriptCommand+utils.swift"; sourceTree = "<group>"; };
120124
7625140A2992B46D0060A225 /* Pasteboard+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pasteboard+utils.swift"; sourceTree = "<group>"; };
@@ -171,6 +175,8 @@
171175
76B70F7D2B0D361A009D87A4 /* UserDefaultsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsTests.swift; sourceTree = "<group>"; };
172176
76BF0ADA2C8CB4CD003BE568 /* Package.resolved */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Package.resolved; path = MiniSim.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; sourceTree = SOURCE_ROOT; };
173177
76C139692C849A3F006CD80C /* MenuIcons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuIcons.swift; sourceTree = "<group>"; };
178+
76B70F812B0D50FE009D87A4 /* ADBTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADBTests.swift; sourceTree = "<group>"; };
179+
76B70F832B0D5AB4009D87A4 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
174180
76E4451129D4391000039025 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = "<group>"; };
175181
76E4451329D4403F00039025 /* NSNotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSNotificationName.swift; sourceTree = "<group>"; };
176182
76F04A10298A5AE000BF9CA3 /* ADB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADB.swift; sourceTree = "<group>"; };
@@ -230,6 +236,14 @@
230236
path = Terminal;
231237
sourceTree = "<group>";
232238
};
239+
760DEACC2B0DFB5B00253576 /* Mocks */ = {
240+
isa = PBXGroup;
241+
children = (
242+
760DEACD2B0DFB6600253576 /* ShellStub.swift */,
243+
);
244+
path = Mocks;
245+
sourceTree = "<group>";
246+
};
233247
762CF1E12981DDD400099999 /* Extensions */ = {
234248
isa = PBXGroup;
235249
children = (
@@ -297,6 +311,7 @@
297311
7645D4BD2982A1B100019227 /* DeviceService.swift */,
298312
7699511C2C845B1900462287 /* DeviceParser.swift */,
299313
76F04A10298A5AE000BF9CA3 /* ADB.swift */,
314+
76B70F832B0D5AB4009D87A4 /* Shell.swift */,
300315
);
301316
path = Service;
302317
sourceTree = "<group>";
@@ -396,8 +411,10 @@
396411
76B70F752B0D359D009D87A4 /* MiniSimTests */ = {
397412
isa = PBXGroup;
398413
children = (
414+
760DEACC2B0DFB5B00253576 /* Mocks */,
399415
76B70F7D2B0D361A009D87A4 /* UserDefaultsTests.swift */,
400416
7699511E2C845CBA00462287 /* DeviceParserTests.swift */,
417+
76B70F812B0D50FE009D87A4 /* ADBTests.swift */,
401418
);
402419
path = MiniSimTests;
403420
sourceTree = "<group>";
@@ -619,6 +636,7 @@
619636
7630B2752986D52900D8B57D /* NSAlert+showError.swift in Sources */,
620637
4AFACC742AD730BE00EC369F /* SubMenuItem.swift in Sources */,
621638
7630B25E2984339100D8B57D /* MainMenuActions.swift in Sources */,
639+
76B70F842B0D5AB4009D87A4 /* Shell.swift in Sources */,
622640
76AC9AF62A0EA82C00864A8B /* CustomCommands.swift in Sources */,
623641
76489D5C29BFCA330070EF03 /* OnboardingItem.swift in Sources */,
624642
7645D5012982E6FA00019227 /* main.swift in Sources */,
@@ -657,6 +675,8 @@
657675
files = (
658676
7699511F2C845CBA00462287 /* DeviceParserTests.swift in Sources */,
659677
76B70F7E2B0D361A009D87A4 /* UserDefaultsTests.swift in Sources */,
678+
760DEACE2B0DFB6600253576 /* ShellStub.swift in Sources */,
679+
76B70F822B0D50FE009D87A4 /* ADBTests.swift in Sources */,
660680
);
661681
runOnlyForDeploymentPostprocessing = 0;
662682
};

MiniSim/Service/Adb.swift

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,24 @@
66
//
77

88
import Foundation
9-
import ShellOut
109

1110
protocol ADBProtocol {
11+
static var shell: ShellProtocol.Type { get set }
12+
1213
static func getAdbPath() throws -> String
1314
static func getEmulatorPath() throws -> String
14-
static func getAdbId(for deviceName: String, adbPath: String) throws -> String
15-
static func checkAndroidHome(path: String) throws -> Bool
16-
static func isAccesibilityOn(deviceId: String, adbPath: String) -> Bool
15+
static func getAdbId(for deviceName: String) throws -> String
16+
static func checkAndroidHome(
17+
path: String,
18+
fileManager: FileManager
19+
) throws -> Bool
20+
static func isAccesibilityOn(deviceId: String) -> Bool
21+
static func toggleAccesibility(deviceId: String)
1722
}
1823

1924
final class ADB: ADBProtocol {
25+
static var shell: ShellProtocol.Type = Shell.self
26+
2027
static let talkbackOn = "com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService"
2128
static let talkbackOff = "com.android.talkback/com.google.android.marvin.talkback.TalkBackService"
2229

@@ -55,13 +62,16 @@ final class ADB: ADBProtocol {
5562
/**
5663
Checks if passed path exists and points to `ANDROID_HOME`.
5764
*/
58-
@discardableResult static func checkAndroidHome(path: String) throws -> Bool {
59-
if !FileManager.default.fileExists(atPath: path) {
65+
@discardableResult static func checkAndroidHome(
66+
path: String,
67+
fileManager: FileManager = .default
68+
) throws -> Bool {
69+
if !fileManager.fileExists(atPath: path) {
6070
throw AndroidHomeError.pathNotFound
6171
}
6272

6373
do {
64-
try shellOut(to: "\(path)" + Paths.emulator.rawValue, arguments: ["-list-avds"])
74+
try shell.execute(command: "\(path)" + Paths.emulator.rawValue, arguments: ["-list-avds"])
6575
} catch {
6676
throw AndroidHomeError.pathNotCorrect
6777
}
@@ -72,14 +82,20 @@ final class ADB: ADBProtocol {
7282
try getAndroidHome() + Paths.emulator.rawValue
7383
}
7484

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

7990
for line in splitted {
8091
let device = line.match("^emulator-[0-9]+")
8192
guard let deviceId = device.first?.first else { continue }
82-
let output = try? shellOut(to: "\(adbPath) -s \(deviceId) emu avd name").components(separatedBy: "\n")
93+
94+
let output = try? shell.execute(
95+
command: "\(adbPath) -s \(deviceId) emu avd name"
96+
)
97+
.components(separatedBy: "\n")
98+
8399
if let name = output?.first {
84100
let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
85101
let trimmedDeviceName = deviceName.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -91,9 +107,12 @@ final class ADB: ADBProtocol {
91107
throw DeviceError.deviceNotFound
92108
}
93109

94-
static func isAccesibilityOn(deviceId: String, adbPath: String) -> Bool {
110+
static func isAccesibilityOn(deviceId: String) -> Bool {
111+
guard let adbPath = try? Self.getAdbPath() else {
112+
return false
113+
}
95114
let shellCommand = "\(adbPath) -s \(deviceId) shell settings get secure enabled_accessibility_services"
96-
guard let result = try? shellOut(to: [shellCommand]) else {
115+
guard let result = try? shell.execute(command: shellCommand) else {
97116
return false
98117
}
99118

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

104123
return false
105124
}
125+
126+
static func toggleAccesibility(deviceId: String) {
127+
guard let adbPath = try? Self.getAdbPath() else {
128+
return
129+
}
130+
let a11yIsEnabled = Self.isAccesibilityOn(deviceId: deviceId)
131+
let value = a11yIsEnabled ? ADB.talkbackOff : ADB.talkbackOn
132+
let shellCmd = "\(adbPath) -s \(deviceId) shell settings put secure enabled_accessibility_services \(value)"
133+
134+
// Ignore the error if toggling a11y fails.
135+
_ = try? shell.execute(command: shellCmd)
136+
}
106137
}

MiniSim/Service/Shell.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Foundation
2+
import ShellOut
3+
4+
protocol ShellProtocol {
5+
@discardableResult static func execute(
6+
command: String,
7+
arguments: [String],
8+
atPath: String
9+
) throws -> String
10+
}
11+
12+
extension ShellProtocol {
13+
@discardableResult static func execute(
14+
command: String,
15+
arguments: [String] = [],
16+
atPath: String = "."
17+
) throws -> String {
18+
try execute(command: command, arguments: arguments, atPath: atPath)
19+
}
20+
}
21+
22+
final class Shell: ShellProtocol {
23+
@discardableResult static func execute(
24+
command: String,
25+
arguments: [String] = [],
26+
atPath: String = "."
27+
) throws -> String {
28+
try shellOut(
29+
to: command,
30+
arguments: arguments,
31+
at: atPath
32+
)
33+
}
34+
}

MiniSimTests/ADBTests.swift

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import XCTest
2+
3+
@testable import MiniSim
4+
5+
class FileManagerStub: FileManager {
6+
override func fileExists(atPath path: String) -> Bool {
7+
true
8+
}
9+
}
10+
11+
class FileManagerEmptyStub: FileManager {
12+
override func fileExists(atPath path: String) -> Bool {
13+
false
14+
}
15+
}
16+
17+
final class ADBTests: XCTestCase {
18+
let savedAndroidHome = UserDefaults.standard.androidHome
19+
let defaultHomePath = "/Users/\(NSUserName())/Library/Android/sdk"
20+
21+
override func setUp() {
22+
ADB.shell = ShellStub.self
23+
UserDefaults.standard.removeObject(forKey: UserDefaults.Keys.androidHome)
24+
}
25+
26+
override func tearDown() {
27+
UserDefaults.standard.androidHome = savedAndroidHome
28+
ShellStub.tearDown()
29+
}
30+
31+
func testGetAndroidHome() throws {
32+
let androidHome = try ADB.getAndroidHome()
33+
34+
XCTAssertEqual(androidHome, defaultHomePath)
35+
36+
UserDefaults.standard.androidHome = "customAndroidHome"
37+
let customAndroidHome = try ADB.getAndroidHome()
38+
39+
XCTAssertEqual(
40+
customAndroidHome,
41+
"customAndroidHome",
42+
"Setting custom androidHome overrides default one"
43+
)
44+
}
45+
46+
func testCheckAndroidHome() throws {
47+
let output = try ADB.checkAndroidHome(
48+
path: defaultHomePath,
49+
fileManager: FileManagerStub()
50+
)
51+
XCTAssertEqual(output, true)
52+
XCTAssertEqual(ShellStub.lastExecutedCommand, defaultHomePath + "/emulator/emulator")
53+
XCTAssertEqual(ShellStub.lastPassedArguments, ["-list-avds"])
54+
55+
XCTAssertThrowsError(
56+
try ADB.checkAndroidHome(
57+
path: defaultHomePath,
58+
fileManager: FileManagerEmptyStub()
59+
)
60+
)
61+
}
62+
63+
func testGetUtilPaths() throws {
64+
let adbPath = try ADB.getAdbPath()
65+
let avdPath = try ADB.getAvdPath()
66+
67+
XCTAssertEqual(
68+
adbPath,
69+
defaultHomePath + "/platform-tools/adb"
70+
)
71+
XCTAssertEqual(
72+
avdPath,
73+
defaultHomePath + "/cmdline-tools/latest/bin/avdmanager"
74+
)
75+
}
76+
77+
func testGetAdbId() throws {
78+
ShellStub.mockedExecute = { command, _, _ in
79+
if command.contains("devices") {
80+
return """
81+
List of devices attached
82+
emulator-5554 device
83+
emulator-5556 device
84+
"""
85+
}
86+
87+
if command.contains("avd name") {
88+
return """
89+
Pixel_XL_API_32
90+
OK
91+
"""
92+
}
93+
94+
return ""
95+
}
96+
let adbId = try ADB.getAdbId(for: "Pixel_XL_API_32")
97+
98+
XCTAssertEqual(adbId, "emulator-5554")
99+
100+
XCTAssertThrowsError(
101+
try ADB.getAdbId(for: "Pixel_Not_Found")
102+
)
103+
}
104+
105+
func testIsAccesibilityOn() throws {
106+
var isA11yOn: Bool
107+
isA11yOn = ADB.isAccesibilityOn(deviceId: "emulator-5544")
108+
XCTAssertFalse(isA11yOn)
109+
110+
ShellStub.mockedExecute = { _, _, _ in
111+
"com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService"
112+
}
113+
isA11yOn = ADB.isAccesibilityOn(deviceId: "emulator-5544")
114+
XCTAssertTrue(isA11yOn)
115+
}
116+
117+
func testToggle11y() {
118+
UserDefaults.standard.androidHome = "adbPath"
119+
let expectedCommand = """
120+
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
121+
"""
122+
123+
ADB.toggleAccesibility(deviceId: "emulator-5544")
124+
XCTAssertEqual(ShellStub.lastExecutedCommand, expectedCommand)
125+
XCTAssertEqual(ShellStub.lastPassedArguments, [])
126+
}
127+
}

MiniSimTests/Mocks/ShellStub.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Foundation
2+
3+
@testable import MiniSim
4+
5+
class ShellStub: ShellProtocol {
6+
private(set) static var lastExecutedCommand: String = ""
7+
private(set) static var lastPassedArguments: [String] = []
8+
private(set) static var lastPassedPath: String = ""
9+
static var mockedExecute: ((_ command: String, _ arguments: [String], _ atPath: String) -> String)?
10+
11+
static func execute(command: String, arguments: [String], atPath: String) throws -> String {
12+
lastExecutedCommand = command
13+
lastPassedArguments = arguments
14+
lastPassedPath = atPath
15+
if let mockedExecute {
16+
return mockedExecute(command, arguments, atPath)
17+
}
18+
return ""
19+
}
20+
21+
static func tearDown() {
22+
lastExecutedCommand = ""
23+
lastPassedArguments = []
24+
lastPassedPath = ""
25+
mockedExecute = nil
26+
}
27+
}

0 commit comments

Comments
 (0)