Major rework of the network code, splitting out the speech recognition aspects and creating separate threads for read and write. This improves handling of network connection closure by the other end and other network connectivity errors. Add some unit tests for the connection code.
This commit is contained in:
parent
923d0bf967
commit
d7c430f588
|
@ -8,6 +8,14 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
9D05BAAA27DFDE6300D9CC4B /* GSConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D05BAA927DFDE6300D9CC4B /* GSConnection.swift */; };
|
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 */; };
|
9D5155F326A1EF7B0075EBC7 /* ListenerGSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D5155F226A1EF7B0075EBC7 /* ListenerGSApp.swift */; };
|
||||||
9D5155F726A1EF7C0075EBC7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D5155F626A1EF7C0075EBC7 /* Assets.xcassets */; };
|
9D5155F726A1EF7C0075EBC7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D5155F626A1EF7C0075EBC7 /* Assets.xcassets */; };
|
||||||
9D5155FA26A1EF7C0075EBC7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D5155F926A1EF7C0075EBC7 /* Preview Assets.xcassets */; };
|
9D5155FA26A1EF7C0075EBC7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D5155F926A1EF7C0075EBC7 /* Preview Assets.xcassets */; };
|
||||||
|
@ -51,6 +59,8 @@
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
9D05BAA927DFDE6300D9CC4B /* GSConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GSConnection.swift; sourceTree = "<group>"; };
|
9D05BAA927DFDE6300D9CC4B /* GSConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GSConnection.swift; sourceTree = "<group>"; };
|
||||||
9D0DC15826F2E47A007EB92D /* ListenerGS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ListenerGS.entitlements; sourceTree = "<group>"; };
|
9D0DC15826F2E47A007EB92D /* ListenerGS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ListenerGS.entitlements; sourceTree = "<group>"; };
|
||||||
|
9D2A6D1D27E235E400DF3D85 /* GSServerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GSServerMock.swift; sourceTree = "<group>"; };
|
||||||
|
9D2A6D2627E24BD600DF3D85 /* SpeechForwarderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeechForwarderMock.swift; sourceTree = "<group>"; };
|
||||||
9D5155EF26A1EF7B0075EBC7 /* ListenerGS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ListenerGS.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
9D5155F226A1EF7B0075EBC7 /* ListenerGSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListenerGSApp.swift; sourceTree = "<group>"; };
|
||||||
9D5155F626A1EF7C0075EBC7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
9D5155F626A1EF7C0075EBC7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
@ -166,6 +176,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
9D51560426A1EF7C0075EBC7 /* ListenerGSTests.swift */,
|
9D51560426A1EF7C0075EBC7 /* ListenerGSTests.swift */,
|
||||||
|
9D2A6D1D27E235E400DF3D85 /* GSServerMock.swift */,
|
||||||
|
9D2A6D2627E24BD600DF3D85 /* SpeechForwarderMock.swift */,
|
||||||
9D51560626A1EF7C0075EBC7 /* Info.plist */,
|
9D51560626A1EF7C0075EBC7 /* Info.plist */,
|
||||||
);
|
);
|
||||||
path = ListenerGSTests;
|
path = ListenerGSTests;
|
||||||
|
@ -388,7 +400,15 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
9D51560526A1EF7C0075EBC7 /* ListenerGSTests.swift in Sources */,
|
||||||
|
9D2A6D2327E236E400DF3D85 /* ytcpsocket.c in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -590,6 +610,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_ENTITLEMENTS = "";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = VD9FGCW36C;
|
DEVELOPMENT_TEAM = VD9FGCW36C;
|
||||||
INFOPLIST_FILE = ListenerGSTests/Info.plist;
|
INFOPLIST_FILE = ListenerGSTests/Info.plist;
|
||||||
|
@ -612,6 +633,7 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_ENTITLEMENTS = "";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = VD9FGCW36C;
|
DEVELOPMENT_TEAM = VD9FGCW36C;
|
||||||
INFOPLIST_FILE = ListenerGSTests/Info.plist;
|
INFOPLIST_FILE = ListenerGSTests/Info.plist;
|
||||||
|
|
|
@ -42,7 +42,7 @@ extension GSConnectionState: CustomStringConvertible
|
||||||
|
|
||||||
protocol SpeechForwarderProtocol
|
protocol SpeechForwarderProtocol
|
||||||
{
|
{
|
||||||
func startListening() -> Bool
|
func startListening(connection: GSConnection) -> Bool
|
||||||
func stopListening()
|
func stopListening()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,11 +53,12 @@ class GSConnection : ObservableObject {
|
||||||
|
|
||||||
var speechForwarder : SpeechForwarderProtocol?
|
var speechForwarder : SpeechForwarderProtocol?
|
||||||
|
|
||||||
let LISTEN_STATE_MSG = 1
|
static let LISTEN_STATE_MSG = 1
|
||||||
let LISTEN_TEXT_MSG = 2
|
static let LISTEN_TEXT_MSG = 2
|
||||||
let LISTEN_SEND_MORE = 3
|
static let LISTEN_SEND_MORE = 3
|
||||||
|
|
||||||
|
static let port = 19026
|
||||||
|
|
||||||
let port = 19026
|
|
||||||
private var destination = ""
|
private var destination = ""
|
||||||
private var client: TCPClient?
|
private var client: TCPClient?
|
||||||
|
|
||||||
|
@ -65,14 +66,15 @@ class GSConnection : ObservableObject {
|
||||||
|
|
||||||
private let readQueue = OperationQueue()
|
private let readQueue = OperationQueue()
|
||||||
private let writeQueue = OperationQueue()
|
private let writeQueue = OperationQueue()
|
||||||
|
private var mainQueue = OperationQueue.main
|
||||||
|
|
||||||
private var condition = NSCondition()
|
private var condition = NSCondition()
|
||||||
private var stopListeningFlag = false
|
|
||||||
private var canSend = true
|
private var canSend = true
|
||||||
|
|
||||||
func changeState(newState : GSConnectionState)
|
private func changeState(newState : GSConnectionState)
|
||||||
{
|
{
|
||||||
if (state == newState) {
|
let oldState = state
|
||||||
|
if (oldState == newState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,24 +82,24 @@ class GSConnection : ObservableObject {
|
||||||
switch (newState)
|
switch (newState)
|
||||||
{
|
{
|
||||||
case .disconnected:
|
case .disconnected:
|
||||||
legalTransition = ((state == .connected) || (state == .connecting))
|
legalTransition = ((oldState == .connected) || (oldState == .connecting) || (oldState == .stoplistening))
|
||||||
|
|
||||||
case .connecting:
|
case .connecting:
|
||||||
legalTransition = (state == .disconnected)
|
legalTransition = (oldState == .disconnected)
|
||||||
|
|
||||||
case .connected:
|
case .connected:
|
||||||
legalTransition = ((state == .connecting) || (state == .listening) || (state == .stoplistening))
|
legalTransition = ((oldState == .connecting) || (oldState == .listening) || (oldState == .stoplistening))
|
||||||
|
|
||||||
case .listening:
|
case .listening:
|
||||||
legalTransition = (state == .connected)
|
legalTransition = (oldState == .connected)
|
||||||
|
|
||||||
case .stoplistening:
|
case .stoplistening:
|
||||||
legalTransition = ((state == .connected) || (state == .listening))
|
legalTransition = ((oldState == .connected) || (oldState == .listening))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!legalTransition) {
|
if (!legalTransition) {
|
||||||
logger.error("Illegal requested 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 \(self.state) to \(newState)")
|
errorOccurred(title: "Bad State Change", message: "Illegal state transition from \(oldState) to \(newState)")
|
||||||
} else {
|
} else {
|
||||||
state = newState
|
state = newState
|
||||||
}
|
}
|
||||||
|
@ -105,7 +107,7 @@ class GSConnection : ObservableObject {
|
||||||
|
|
||||||
func errorOccurred(title: String, message : String)
|
func errorOccurred(title: String, message : String)
|
||||||
{
|
{
|
||||||
OperationQueue.main.addOperation {
|
mainQueue.addOperation {
|
||||||
self.errorMessage = GSConnectionErrorMessage(title: title, message: message)
|
self.errorMessage = GSConnectionErrorMessage(title: title, message: message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,19 +125,19 @@ class GSConnection : ObservableObject {
|
||||||
|
|
||||||
private func doConnect() {
|
private func doConnect() {
|
||||||
logger.debug("Attempting to connect to \(self.destination)")
|
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 {
|
guard let client = client else {
|
||||||
OperationQueue.main.addOperation { self.connectionFailed() }
|
mainQueue.addOperation { self.connectionFailed() }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch client.connect(timeout: 10) {
|
switch client.connect(timeout: 10) {
|
||||||
case .success:
|
case .success:
|
||||||
OperationQueue.main.addOperation { self.connectionSuccessful() }
|
mainQueue.addOperation { self.connectionSuccessful() }
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
client.close()
|
client.close()
|
||||||
self.client = nil
|
self.client = nil
|
||||||
logger.error("Failed to connect to \(self.destination): \(String(describing: error))")
|
logger.error("Failed to connect to \(self.destination): \(String(describing: error))")
|
||||||
OperationQueue.main.addOperation { self.connectionFailed() }
|
mainQueue.addOperation { self.connectionFailed() }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,10 +145,15 @@ class GSConnection : ObservableObject {
|
||||||
guard let byteArray = client.read(2) else {
|
guard let byteArray = client.read(2) else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (byteArray.count != 2) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
let data = Data(byteArray)
|
let data = Data(byteArray)
|
||||||
do {
|
do {
|
||||||
let unpacked = try unpack("<h", data)
|
let unpacked = try unpack("<h", data)
|
||||||
if (unpacked[0] as? Int == LISTEN_SEND_MORE) {
|
if (unpacked[0] as? Int == GSConnection.LISTEN_SEND_MORE) {
|
||||||
condition.lock()
|
condition.lock()
|
||||||
canSend = true
|
canSend = true
|
||||||
condition.broadcast()
|
condition.broadcast()
|
||||||
|
@ -166,7 +173,7 @@ class GSConnection : ObservableObject {
|
||||||
|
|
||||||
client.close()
|
client.close()
|
||||||
self.client = nil
|
self.client = nil
|
||||||
OperationQueue.main.addOperation { self.disconnect() }
|
mainQueue.addOperation { self.disconnect() }
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect(destination : String) {
|
func connect(destination : String) {
|
||||||
|
@ -193,6 +200,9 @@ class GSConnection : ObservableObject {
|
||||||
}
|
}
|
||||||
condition.broadcast()
|
condition.broadcast()
|
||||||
condition.unlock()
|
condition.unlock()
|
||||||
|
|
||||||
|
waitForWriteQueue()
|
||||||
|
waitForReadQueue()
|
||||||
self.changeState(newState:.disconnected)
|
self.changeState(newState:.disconnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +223,7 @@ class GSConnection : ObservableObject {
|
||||||
private func sendListenMsg(isListening: Bool) -> Bool {
|
private func sendListenMsg(isListening: Bool) -> Bool {
|
||||||
guard let client = client else { return false }
|
guard let client = client else { return false }
|
||||||
|
|
||||||
switch (client.send(data: pack("<hh", [LISTEN_STATE_MSG, isListening ? 1 : 0]))) {
|
switch (client.send(data: pack("<hh", [GSConnection.LISTEN_STATE_MSG, isListening ? 1 : 0]))) {
|
||||||
case .success:
|
case .success:
|
||||||
break
|
break
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
|
@ -232,9 +242,9 @@ class GSConnection : ObservableObject {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
OperationQueue.main.addOperation {
|
self.mainQueue.addOperation {
|
||||||
self.changeState(newState: .listening)
|
self.changeState(newState: .listening)
|
||||||
if (!speechForwarder.startListening()) {
|
if (!speechForwarder.startListening(connection: self)) {
|
||||||
self.logger.error("Unable to start listening")
|
self.logger.error("Unable to start listening")
|
||||||
self.errorOccurred(title: "Speech Error", message: "Unable to start listening for speech")
|
self.errorOccurred(title: "Speech Error", message: "Unable to start listening for speech")
|
||||||
self.stopListening()
|
self.stopListening()
|
||||||
|
@ -247,7 +257,7 @@ class GSConnection : ObservableObject {
|
||||||
|
|
||||||
_ = self.sendListenMsg(isListening: false)
|
_ = self.sendListenMsg(isListening: false)
|
||||||
|
|
||||||
OperationQueue.main.addOperation {
|
self.mainQueue.addOperation {
|
||||||
if (self.state == .stoplistening) {
|
if (self.state == .stoplistening) {
|
||||||
self.changeState(newState: .connected)
|
self.changeState(newState: .connected)
|
||||||
}
|
}
|
||||||
|
@ -318,29 +328,51 @@ class GSConnection : ObservableObject {
|
||||||
let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringBuiltInEncodings.macRoman.rawValue))
|
let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringBuiltInEncodings.macRoman.rawValue))
|
||||||
let encoding = String.Encoding(rawValue: nsEnc) // String.Encoding
|
let encoding = String.Encoding(rawValue: nsEnc) // String.Encoding
|
||||||
if let bytes = stringToSend.data(using: encoding) {
|
if let bytes = stringToSend.data(using: encoding) {
|
||||||
switch (client.send(data: pack("<hh", [LISTEN_TEXT_MSG, bytes.count]))) {
|
switch (client.send(data: pack("<hh", [GSConnection.LISTEN_TEXT_MSG, bytes.count]))) {
|
||||||
case .success:
|
case .success:
|
||||||
switch (client.send(data: bytes)) {
|
switch (client.send(data: bytes)) {
|
||||||
case .success:
|
case .success:
|
||||||
logger.debug("Sent text \"\(stringToSend)\"")
|
logger.debug("Sent text \"\(stringToSend)\"")
|
||||||
break
|
break
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
OperationQueue.main.addOperation {
|
mainQueue.addOperation {
|
||||||
self.errorOccurred(title: "Write Error", message: "Unable to send text to the GS")
|
self.errorOccurred(title: "Write Error", message: "Unable to send text to the GS")
|
||||||
self.stopListening()
|
self.disconnect()
|
||||||
}
|
}
|
||||||
logger.error("Failed to send text: \(String(describing: error))")
|
logger.error("Failed to send text: \(String(describing: error))")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
OperationQueue.main.addOperation {
|
mainQueue.addOperation {
|
||||||
self.errorOccurred(title: "Write Error", message: "Unable to send text to the GS")
|
self.errorOccurred(title: "Write Error", message: "Unable to send text to the GS")
|
||||||
self.stopListening()
|
self.disconnect()
|
||||||
}
|
}
|
||||||
logger.error("Failed to send text: \(String(describing: error))")
|
logger.error("Failed to send text: \(String(describing: error))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setMainQueueForTest() {
|
||||||
|
mainQueue = OperationQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForMain() {
|
||||||
|
mainQueue.waitUntilAllOperationsAreFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForReadQueue() {
|
||||||
|
readQueue.waitUntilAllOperationsAreFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForWriteQueue() {
|
||||||
|
writeQueue.waitUntilAllOperationsAreFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForAllQueues() {
|
||||||
|
waitForWriteQueue()
|
||||||
|
waitForReadQueue()
|
||||||
|
waitForMain()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ struct GSView: View {
|
||||||
|
|
||||||
case .connected:
|
case .connected:
|
||||||
Button("\(Image(systemName: "ear.and.waveform")) Listen and Send Text") {
|
Button("\(Image(systemName: "ear.and.waveform")) Listen and Send Text") {
|
||||||
connection.listen(speechForwarder: SpeechForwarder(connection: connection))
|
connection.listen(speechForwarder: SpeechForwarder())
|
||||||
}
|
}
|
||||||
.buttonStyle(GSButtonStyle())
|
.buttonStyle(GSButtonStyle())
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>483</string>
|
<string>548</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.utilities</string>
|
<string>public.app-category.utilities</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|
|
@ -11,8 +11,6 @@ import Speech
|
||||||
|
|
||||||
class SpeechForwarder : SpeechForwarderProtocol {
|
class SpeechForwarder : SpeechForwarderProtocol {
|
||||||
|
|
||||||
private var connection : GSConnection
|
|
||||||
|
|
||||||
private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: Locale.preferredLanguages[0]))!
|
private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: Locale.preferredLanguages[0]))!
|
||||||
|
|
||||||
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
|
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
|
||||||
|
@ -23,11 +21,7 @@ class SpeechForwarder : SpeechForwarderProtocol {
|
||||||
|
|
||||||
private let logger = Logger()
|
private let logger = Logger()
|
||||||
|
|
||||||
init(connection : GSConnection) {
|
func startListening(connection : GSConnection) -> Bool {
|
||||||
self.connection = connection
|
|
||||||
}
|
|
||||||
|
|
||||||
func startListening() -> Bool {
|
|
||||||
SFSpeechRecognizer.requestAuthorization { authStatus in
|
SFSpeechRecognizer.requestAuthorization { authStatus in
|
||||||
OperationQueue.main.addOperation {
|
OperationQueue.main.addOperation {
|
||||||
switch authStatus {
|
switch authStatus {
|
||||||
|
@ -35,16 +29,16 @@ class SpeechForwarder : SpeechForwarderProtocol {
|
||||||
break
|
break
|
||||||
|
|
||||||
case .denied, .restricted, .notDetermined:
|
case .denied, .restricted, .notDetermined:
|
||||||
self.connection.stopListening()
|
connection.stopListening()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
self.connection.stopListening()
|
connection.stopListening()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try startRecording()
|
try startRecording(connection: connection)
|
||||||
logger.debug("Started listening")
|
logger.debug("Started listening")
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
@ -64,7 +58,7 @@ class SpeechForwarder : SpeechForwarderProtocol {
|
||||||
recognitionTask = nil
|
recognitionTask = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startRecording() throws {
|
private func startRecording(connection : GSConnection) throws {
|
||||||
|
|
||||||
// Cancel the previous task if it's running.
|
// Cancel the previous task if it's running.
|
||||||
recognitionTask?.cancel()
|
recognitionTask?.cancel()
|
||||||
|
@ -95,18 +89,17 @@ class SpeechForwarder : SpeechForwarderProtocol {
|
||||||
|
|
||||||
if let result = result {
|
if let result = result {
|
||||||
// Update the text view with the results.
|
// 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
|
isFinal = result.isFinal
|
||||||
}
|
}
|
||||||
|
|
||||||
if error != nil {
|
if error != nil {
|
||||||
self.logger.error("Error from recognizer: \(String(describing: error))")
|
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 {
|
if error != nil || isFinal {
|
||||||
OperationQueue.main.addOperation {
|
OperationQueue.main.addOperation {
|
||||||
self.connection.stopListening()
|
connection.stopListening()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("<hh", data)
|
||||||
|
if (unpacked[0] as? Int != GSConnection.LISTEN_STATE_MSG) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (unpacked[1] as? Int != (isListening ? 1 : 0)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendMore() -> Bool {
|
||||||
|
guard let client = client else { return false }
|
||||||
|
|
||||||
|
let result = client.send(data: pack("<h", [GSConnection.LISTEN_SEND_MORE]))
|
||||||
|
return result.isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendMoreBad() -> Bool {
|
||||||
|
guard let client = client else { return false }
|
||||||
|
|
||||||
|
let result = client.send(data: pack("<h", [6502]))
|
||||||
|
return result.isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func getText() -> 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("<hh", headerData)
|
||||||
|
if (unpacked[0] as? Int != GSConnection.LISTEN_TEXT_MSG) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
textLength = unpacked[1] as! Int
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textLength == 0) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let bodyByteArray = client.read(textLength) else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyByteArray.count != textLength) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
let bodyData = Data(bodyByteArray)
|
||||||
|
let nsEnc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringBuiltInEncodings.macRoman.rawValue))
|
||||||
|
let encoding = String.Encoding(rawValue: nsEnc) // String.Encoding
|
||||||
|
let result = String(data:bodyData, encoding: encoding)
|
||||||
|
guard let result = result else { return "" }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDisconnect() -> Bool {
|
||||||
|
guard let client = client else { return false }
|
||||||
|
|
||||||
|
guard let headerByteArray = client.read(1) else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (headerByteArray.count == 0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,17 +17,335 @@ class ListenerGSTests: XCTestCase {
|
||||||
override func tearDownWithError() throws {
|
override func tearDownWithError() throws {
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
}
|
}
|
||||||
|
|
||||||
func testExample() throws {
|
func waitForConnection(connection: GSConnection) {
|
||||||
// This is an example of a functional test case.
|
for _ in (1...1000) {
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
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 {
|
func testPerformanceExample() throws {
|
||||||
// This is an example of a performance test case.
|
// This is an example of a performance test case.
|
||||||
self.measure {
|
self.measure {
|
||||||
// Put the code you want to measure the time of here.
|
// Put the code you want to measure the time of here.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func testExample() throws {
|
func testExample() throws {
|
||||||
// UI tests must launch the application that they test.
|
// UI tests must launch the application that they test.
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
|
@ -39,4 +40,5 @@ class ListenerGSUITests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue