diff --git a/ListenerGS.xcodeproj/project.pbxproj b/ListenerGS.xcodeproj/project.pbxproj index c7a0fd5..a1ad5d7 100644 --- a/ListenerGS.xcodeproj/project.pbxproj +++ b/ListenerGS.xcodeproj/project.pbxproj @@ -8,6 +8,14 @@ /* Begin PBXBuildFile section */ 9D05BAAA27DFDE6300D9CC4B /* GSConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D05BAA927DFDE6300D9CC4B /* GSConnection.swift */; }; + 9D2A6D1E27E235E400DF3D85 /* GSServerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2A6D1D27E235E400DF3D85 /* GSServerMock.swift */; }; + 9D2A6D1F27E236D500DF3D85 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D51564C26A36B410075EBC7 /* Result.swift */; }; + 9D2A6D2027E236D800DF3D85 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D51565026A36B410075EBC7 /* Socket.swift */; }; + 9D2A6D2127E236DC00DF3D85 /* TCPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D51565126A36B410075EBC7 /* TCPClient.swift */; }; + 9D2A6D2227E236E000DF3D85 /* UDPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D51564F26A36B410075EBC7 /* UDPClient.swift */; }; + 9D2A6D2327E236E400DF3D85 /* ytcpsocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 9D51564E26A36B410075EBC7 /* ytcpsocket.c */; }; + 9D2A6D2427E236FD00DF3D85 /* yudpsocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 9D51564D26A36B410075EBC7 /* yudpsocket.c */; }; + 9D2A6D2727E24BD600DF3D85 /* SpeechForwarderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2A6D2627E24BD600DF3D85 /* SpeechForwarderMock.swift */; }; 9D5155F326A1EF7B0075EBC7 /* ListenerGSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D5155F226A1EF7B0075EBC7 /* ListenerGSApp.swift */; }; 9D5155F726A1EF7C0075EBC7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D5155F626A1EF7C0075EBC7 /* Assets.xcassets */; }; 9D5155FA26A1EF7C0075EBC7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D5155F926A1EF7C0075EBC7 /* Preview Assets.xcassets */; }; @@ -51,6 +59,8 @@ /* Begin PBXFileReference section */ 9D05BAA927DFDE6300D9CC4B /* GSConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GSConnection.swift; sourceTree = ""; }; 9D0DC15826F2E47A007EB92D /* ListenerGS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ListenerGS.entitlements; sourceTree = ""; }; + 9D2A6D1D27E235E400DF3D85 /* GSServerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GSServerMock.swift; sourceTree = ""; }; + 9D2A6D2627E24BD600DF3D85 /* SpeechForwarderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeechForwarderMock.swift; sourceTree = ""; }; 9D5155EF26A1EF7B0075EBC7 /* ListenerGS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ListenerGS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9D5155F226A1EF7B0075EBC7 /* ListenerGSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListenerGSApp.swift; sourceTree = ""; }; 9D5155F626A1EF7C0075EBC7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -166,6 +176,8 @@ isa = PBXGroup; children = ( 9D51560426A1EF7C0075EBC7 /* ListenerGSTests.swift */, + 9D2A6D1D27E235E400DF3D85 /* GSServerMock.swift */, + 9D2A6D2627E24BD600DF3D85 /* SpeechForwarderMock.swift */, 9D51560626A1EF7C0075EBC7 /* Info.plist */, ); path = ListenerGSTests; @@ -388,7 +400,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9D2A6D2427E236FD00DF3D85 /* yudpsocket.c in Sources */, + 9D2A6D2227E236E000DF3D85 /* UDPClient.swift in Sources */, + 9D2A6D2027E236D800DF3D85 /* Socket.swift in Sources */, + 9D2A6D2727E24BD600DF3D85 /* SpeechForwarderMock.swift in Sources */, + 9D2A6D2127E236DC00DF3D85 /* TCPClient.swift in Sources */, + 9D2A6D1F27E236D500DF3D85 /* Result.swift in Sources */, + 9D2A6D1E27E235E400DF3D85 /* GSServerMock.swift in Sources */, 9D51560526A1EF7C0075EBC7 /* ListenerGSTests.swift in Sources */, + 9D2A6D2327E236E400DF3D85 /* ytcpsocket.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -590,6 +610,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = VD9FGCW36C; INFOPLIST_FILE = ListenerGSTests/Info.plist; @@ -612,6 +633,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = VD9FGCW36C; INFOPLIST_FILE = ListenerGSTests/Info.plist; diff --git a/ListenerGS/GSConnection.swift b/ListenerGS/GSConnection.swift index 01398b2..cd131df 100644 --- a/ListenerGS/GSConnection.swift +++ b/ListenerGS/GSConnection.swift @@ -42,7 +42,7 @@ extension GSConnectionState: CustomStringConvertible protocol SpeechForwarderProtocol { - func startListening() -> Bool + func startListening(connection: GSConnection) -> Bool func stopListening() } @@ -53,11 +53,12 @@ class GSConnection : ObservableObject { var speechForwarder : SpeechForwarderProtocol? - let LISTEN_STATE_MSG = 1 - let LISTEN_TEXT_MSG = 2 - let LISTEN_SEND_MORE = 3 + static let LISTEN_STATE_MSG = 1 + static let LISTEN_TEXT_MSG = 2 + static let LISTEN_SEND_MORE = 3 + + static let port = 19026 - let port = 19026 private var destination = "" private var client: TCPClient? @@ -65,14 +66,15 @@ class GSConnection : ObservableObject { private let readQueue = OperationQueue() private let writeQueue = OperationQueue() + private var mainQueue = OperationQueue.main private var condition = NSCondition() - private var stopListeningFlag = false private var canSend = true - func changeState(newState : GSConnectionState) + private func changeState(newState : GSConnectionState) { - if (state == newState) { + let oldState = state + if (oldState == newState) { return; } @@ -80,24 +82,24 @@ class GSConnection : ObservableObject { switch (newState) { case .disconnected: - legalTransition = ((state == .connected) || (state == .connecting)) + legalTransition = ((oldState == .connected) || (oldState == .connecting) || (oldState == .stoplistening)) case .connecting: - legalTransition = (state == .disconnected) + legalTransition = (oldState == .disconnected) case .connected: - legalTransition = ((state == .connecting) || (state == .listening) || (state == .stoplistening)) + legalTransition = ((oldState == .connecting) || (oldState == .listening) || (oldState == .stoplistening)) case .listening: - legalTransition = (state == .connected) + legalTransition = (oldState == .connected) case .stoplistening: - legalTransition = ((state == .connected) || (state == .listening)) + legalTransition = ((oldState == .connected) || (oldState == .listening)) } if (!legalTransition) { - logger.error("Illegal requested state transition from \(self.state) to \(newState)") - errorOccurred(title: "Bad State Change", message: "Illegal state transition from \(self.state) to \(newState)") + logger.error("Illegal requested state transition from \(oldState) to \(newState)") + errorOccurred(title: "Bad State Change", message: "Illegal state transition from \(oldState) to \(newState)") } else { state = newState } @@ -105,7 +107,7 @@ class GSConnection : ObservableObject { func errorOccurred(title: String, message : String) { - OperationQueue.main.addOperation { + mainQueue.addOperation { self.errorMessage = GSConnectionErrorMessage(title: title, message: message) } } @@ -123,19 +125,19 @@ class GSConnection : ObservableObject { private func doConnect() { logger.debug("Attempting to connect to \(self.destination)") - client = TCPClient(address: destination, port: Int32(port)) + client = TCPClient(address: destination, port: Int32(GSConnection.port)) guard let client = client else { - OperationQueue.main.addOperation { self.connectionFailed() } + mainQueue.addOperation { self.connectionFailed() } return } switch client.connect(timeout: 10) { case .success: - OperationQueue.main.addOperation { self.connectionSuccessful() } + mainQueue.addOperation { self.connectionSuccessful() } case .failure(let error): client.close() self.client = nil logger.error("Failed to connect to \(self.destination): \(String(describing: error))") - OperationQueue.main.addOperation { self.connectionFailed() } + mainQueue.addOperation { self.connectionFailed() } return } @@ -143,10 +145,15 @@ class GSConnection : ObservableObject { guard let byteArray = client.read(2) else { break } + + if (byteArray.count != 2) { + break + } + let data = Data(byteArray) do { let unpacked = try unpack(" Bool { guard let client = client else { return false } - switch (client.send(data: pack("CFBundleShortVersionString 1.0 CFBundleVersion - 483 + 548 LSApplicationCategoryType public.app-category.utilities LSRequiresIPhoneOS diff --git a/ListenerGS/SpeechForwarder.swift b/ListenerGS/SpeechForwarder.swift index c4d5337..2abe2af 100644 --- a/ListenerGS/SpeechForwarder.swift +++ b/ListenerGS/SpeechForwarder.swift @@ -11,8 +11,6 @@ import Speech class SpeechForwarder : SpeechForwarderProtocol { - private var connection : GSConnection - private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: Locale.preferredLanguages[0]))! private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? @@ -23,11 +21,7 @@ class SpeechForwarder : SpeechForwarderProtocol { private let logger = Logger() - init(connection : GSConnection) { - self.connection = connection - } - - func startListening() -> Bool { + func startListening(connection : GSConnection) -> Bool { SFSpeechRecognizer.requestAuthorization { authStatus in OperationQueue.main.addOperation { switch authStatus { @@ -35,16 +29,16 @@ class SpeechForwarder : SpeechForwarderProtocol { break case .denied, .restricted, .notDetermined: - self.connection.stopListening() + connection.stopListening() default: - self.connection.stopListening() + connection.stopListening() } } } do { - try startRecording() + try startRecording(connection: connection) logger.debug("Started listening") } catch { @@ -64,7 +58,7 @@ class SpeechForwarder : SpeechForwarderProtocol { recognitionTask = nil } - private func startRecording() throws { + private func startRecording(connection : GSConnection) throws { // Cancel the previous task if it's running. recognitionTask?.cancel() @@ -95,18 +89,17 @@ class SpeechForwarder : SpeechForwarderProtocol { if let result = result { // Update the text view with the results. - OperationQueue.main.addOperation { self.connection.set(text: result.bestTranscription.formattedString) } + OperationQueue.main.addOperation { connection.set(text: result.bestTranscription.formattedString) } isFinal = result.isFinal } if error != nil { self.logger.error("Error from recognizer: \(String(describing: error))") - self.connection.errorOccurred(title: "Recognizer Error", message: "Speech recognizer failed with an error") } if error != nil || isFinal { OperationQueue.main.addOperation { - self.connection.stopListening() + connection.stopListening() } } } diff --git a/ListenerGSTests/GSServerMock.swift b/ListenerGSTests/GSServerMock.swift new file mode 100644 index 0000000..f30af17 --- /dev/null +++ b/ListenerGSTests/GSServerMock.swift @@ -0,0 +1,133 @@ +// +// GSServerMock.swift +// ListenerGSTests +// +// Created by Jeremy Rand on 2022-03-16. +// + +import Foundation +@testable import ListenerGS + +class GSServerMock { + private let server = TCPServer(address: "127.0.0.1", port: Int32(GSConnection.port)) + private var client : TCPClient? + + deinit { + server.close() + disconnect() + } + + func accept() -> Bool { + let result = server.listen() + if (!result.isSuccess) { + return false + } + client = server.accept(timeout: 10) + return (client != nil) + } + + func hasClient() -> Bool { + return client != nil + } + + func disconnect() { + if let client = client { + client.close() + self.client = nil + } + } + + func getListenState(isListening : Bool) -> Bool { + guard let client = client else { return false } + guard let byteArray = client.read(4) else { + return false + } + + if (byteArray.count != 4) { + return false + } + + let data = Data(byteArray) + do { + let unpacked = try unpack(" Bool { + guard let client = client else { return false } + + let result = client.send(data: pack(" Bool { + guard let client = client else { return false } + + let result = client.send(data: pack(" String { + guard let client = client else { return "" } + + guard let headerByteArray = client.read(4) else { + return "" + } + + if (headerByteArray.count != 4) { + return "" + } + + let headerData = Data(headerByteArray) + var textLength = 0 + do { + let unpacked = try unpack(" Bool { + guard let client = client else { return false } + + guard let headerByteArray = client.read(1) else { + return true + } + + return (headerByteArray.count == 0) + } +} diff --git a/ListenerGSTests/ListenerGSTests.swift b/ListenerGSTests/ListenerGSTests.swift index c441a6c..b2ec27a 100644 --- a/ListenerGSTests/ListenerGSTests.swift +++ b/ListenerGSTests/ListenerGSTests.swift @@ -17,17 +17,335 @@ class ListenerGSTests: XCTestCase { override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. + + func waitForConnection(connection: GSConnection) { + for _ in (1...1000) { + if (connection.state != .connecting) { + return + } + usleep(10000) + } } + + func testNoConnection() throws { + let connection = GSConnection() + connection.setMainQueueForTest() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + connection.connect(destination: "127.0.0.1") + connection.waitForReadQueue() + connection.waitForMain() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNotNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + } + + func testNormalPath() throws { + let connection = GSConnection() + connection.setMainQueueForTest() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let server = GSServerMock() + + connection.connect(destination: "127.0.0.1") + XCTAssertEqual(connection.state, .connecting) + XCTAssert(server.accept()) + + XCTAssert(server.hasClient()) + connection.waitForMain() + waitForConnection(connection: connection) + + XCTAssertEqual(connection.state, .connected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let speechForwarder = SpeechForwarderMock() + XCTAssert(!speechForwarder.isListening) + + connection.listen(speechForwarder: speechForwarder) + XCTAssert(server.getListenState(isListening: true)) + connection.waitForMain() + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + connection.set(text: "Hello, world!") + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, world!") + + XCTAssertEqual(server.getText(), "Hello, world!") + + connection.set(text: "Rewrite everything...") + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Rewrite everything...") + + connection.set(text: "Hello, everyone!") + connection.stopListening() + + XCTAssert(!speechForwarder.isListening) + XCTAssertEqual(connection.state, .stoplistening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, everyone!") + + XCTAssert(server.sendMore()) + XCTAssertEqual(server.getText(), "\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}everyone!") + + connection.waitForWriteQueue() + connection.waitForMain() + XCTAssert(server.getListenState(isListening: false)) + + XCTAssertEqual(connection.state, .connected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, everyone!") + + server.disconnect() + connection.waitForReadQueue() + connection.waitForMain() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, everyone!") + } + + func testDisconnectWhileListening() throws { + let connection = GSConnection() + connection.setMainQueueForTest() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let server = GSServerMock() + + connection.connect(destination: "127.0.0.1") + XCTAssertEqual(connection.state, .connecting) + XCTAssert(server.accept()) + + XCTAssert(server.hasClient()) + connection.waitForMain() + + waitForConnection(connection: connection) + XCTAssertEqual(connection.state, .connected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let speechForwarder = SpeechForwarderMock() + XCTAssert(!speechForwarder.isListening) + + connection.listen(speechForwarder: speechForwarder) + XCTAssert(server.getListenState(isListening: true)) + connection.waitForMain() + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + connection.set(text: "Hello, world!") + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, world!") + + XCTAssertEqual(server.getText(), "Hello, world!") + + connection.set(text: "Rewrite everything...") + connection.disconnect() + connection.waitForAllQueues() + + XCTAssert(!speechForwarder.isListening) + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Rewrite everything...") + + XCTAssert(server.getDisconnect()) + } + + func testBadSendMore() throws { + let connection = GSConnection() + connection.setMainQueueForTest() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let server = GSServerMock() + + connection.connect(destination: "127.0.0.1") + XCTAssertEqual(connection.state, .connecting) + XCTAssert(server.accept()) + + XCTAssert(server.hasClient()) + connection.waitForMain() + + waitForConnection(connection: connection) + XCTAssertEqual(connection.state, .connected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let speechForwarder = SpeechForwarderMock() + XCTAssert(!speechForwarder.isListening) + + connection.listen(speechForwarder: speechForwarder) + XCTAssert(server.getListenState(isListening: true)) + connection.waitForMain() + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + connection.set(text: "Hello, world!") + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, world!") + + XCTAssertEqual(server.getText(), "Hello, world!") + + connection.set(text: "Rewrite everything...") + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Rewrite everything...") + + connection.set(text: "Hello, everyone!") + connection.stopListening() + + XCTAssert(!speechForwarder.isListening) + XCTAssertEqual(connection.state, .stoplistening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, everyone!") + + XCTAssert(server.sendMoreBad()) + + connection.waitForAllQueues() + XCTAssert(server.getDisconnect()) + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNotNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, everyone!") + } + + func testServerDisconnect() throws { + let connection = GSConnection() + connection.setMainQueueForTest() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let server = GSServerMock() + + connection.connect(destination: "127.0.0.1") + XCTAssertEqual(connection.state, .connecting) + XCTAssert(server.accept()) + + XCTAssert(server.hasClient()) + connection.waitForMain() + + waitForConnection(connection: connection) + XCTAssertEqual(connection.state, .connected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + server.disconnect() + connection.waitForReadQueue() + connection.waitForMain() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + } + + func testServerDisconnectionWhileListening() throws { + let connection = GSConnection() + connection.setMainQueueForTest() + + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let server = GSServerMock() + + connection.connect(destination: "127.0.0.1") + XCTAssertEqual(connection.state, .connecting) + XCTAssert(server.accept()) + + XCTAssert(server.hasClient()) + connection.waitForMain() + + waitForConnection(connection: connection) + XCTAssertEqual(connection.state, .connected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + let speechForwarder = SpeechForwarderMock() + XCTAssert(!speechForwarder.isListening) + + connection.listen(speechForwarder: speechForwarder) + XCTAssert(server.getListenState(isListening: true)) + connection.waitForMain() + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "") + + connection.set(text: "Hello, world!") + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, world!") + + XCTAssertEqual(server.getText(), "Hello, world!") + + connection.set(text: "Rewrite everything...") + + XCTAssert(speechForwarder.isListening) + XCTAssertEqual(connection.state, .listening) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Rewrite everything...") + + connection.set(text: "Hello, everyone!") + server.disconnect() + connection.waitForAllQueues() + + XCTAssert(!speechForwarder.isListening) + XCTAssertEqual(connection.state, .disconnected) + XCTAssertNil(connection.errorMessage) + XCTAssertEqual(connection.textHeard, "Hello, everyone!") + } + + // Other tests: + // - Connection deconstruction - When I remove the reference to the connection, it stays up. I think self has references because of closures that are still running in the queues. + /* func testPerformanceExample() throws { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } + */ } diff --git a/ListenerGSTests/SpeechForwarderMock.swift b/ListenerGSTests/SpeechForwarderMock.swift new file mode 100644 index 0000000..be44449 --- /dev/null +++ b/ListenerGSTests/SpeechForwarderMock.swift @@ -0,0 +1,25 @@ +// +// SpeechForwarderMock.swift +// ListenerGSTests +// +// Created by Jeremy Rand on 2022-03-16. +// + +import Foundation +@testable import ListenerGS + +class SpeechForwarderMock : SpeechForwarderProtocol { + var isListening = false + var startListeningResult = true + + func startListening(connection: GSConnection) -> Bool { + isListening = startListeningResult + return startListeningResult + } + + func stopListening() { + assert(isListening) + isListening = false + } + +} diff --git a/ListenerGSUITests/ListenerGSUITests.swift b/ListenerGSUITests/ListenerGSUITests.swift index 5b56706..ebbe5ae 100644 --- a/ListenerGSUITests/ListenerGSUITests.swift +++ b/ListenerGSUITests/ListenerGSUITests.swift @@ -22,6 +22,7 @@ class ListenerGSUITests: XCTestCase { // Put teardown code here. This method is called after the invocation of each test method in the class. } + /* func testExample() throws { // UI tests must launch the application that they test. let app = XCUIApplication() @@ -39,4 +40,5 @@ class ListenerGSUITests: XCTestCase { } } } + */ }