diff --git a/SourceKitStressTester/Sources/Common/Message.swift b/SourceKitStressTester/Sources/Common/Message.swift index be30852..7773b88 100644 --- a/SourceKitStressTester/Sources/Common/Message.swift +++ b/SourceKitStressTester/Sources/Common/Message.swift @@ -76,9 +76,14 @@ public enum SourceKitError: Error { } } -public enum SourceKitErrorReason: String, Codable { - case errorResponse, errorTypeInResponse, errorDeserializingSyntaxTree, - sourceAndSyntaxTreeMismatch, missingExpectedResult, errorWritingModule +public enum SourceKitErrorReason: Codable { + case errorResponse + case errorTypeInResponse + case errorDeserializingSyntaxTree + case sourceAndSyntaxTreeMismatch + case missingExpectedResult + case duplicateResult(name: String) + case errorWritingModule } public enum RequestInfo { @@ -477,6 +482,8 @@ extension SourceKitErrorReason: CustomStringConvertible { return "SourceKit returned a syntax tree that doesn't match the expected source" case .missingExpectedResult: return "SourceKit returned a response that didn't contain the expected result" + case .duplicateResult(name: let name): + return "SourceKit returned a response that contained the result '\(name)' twice" case .errorWritingModule: return "Error while writing out module" } diff --git a/SourceKitStressTester/Sources/StressTester/SourceKitDocument.swift b/SourceKitStressTester/Sources/StressTester/SourceKitDocument.swift index 0a1062e..487d41c 100644 --- a/SourceKitStressTester/Sources/StressTester/SourceKitDocument.swift +++ b/SourceKitStressTester/Sources/StressTester/SourceKitDocument.swift @@ -425,21 +425,45 @@ class SourceKitDocument { } private func checkExpectedCompletionResult(_ expected: ExpectedResult, in response: SourceKitdResponse, info: RequestInfo) throws { + /// Struct that defines a code completion result to determine if there are duplicate results. + struct CodeCompletionResultSpec: Hashable { + let description: String + let type: String + let usr: String + + init(_ result: SourceKitdResponse.Dictionary) { + self.description = result.getString(.key_Description) + self.type = result.getString(.key_TypeName) + self.usr = result.getString(.key_AssociatedUSRs) + } + } let matcher = CompletionMatcher(for: expected) var found = false + var foundDuplicate: CodeCompletionResultSpec? = nil + var seenResults = Set() response.value.getArray(.key_Results).enumerate { (_, item) -> Bool in let result = item.getDictionary() found = matcher.match(result.getString(.key_Name), ignoreArgLabels: shouldIgnoreArgs(of: expected, for: result)) + let resultSpec = CodeCompletionResultSpec(result) + if foundDuplicate == nil, !seenResults.insert(resultSpec).inserted { + foundDuplicate = resultSpec + } return !found } - if !found { + lazy var truncatedResponseText = { // FIXME: code completion responses can be huge, truncate them for now. let maxSize = 25_000 var responseText = response.description if responseText.count > maxSize { responseText = responseText.prefix(maxSize) + "[truncated]" } - throw SourceKitError.failed(.missingExpectedResult, request: info, response: responseText.trimmingCharacters(in: .newlines)) + return responseText + }() + if !found { + throw SourceKitError.failed(.missingExpectedResult, request: info, response: truncatedResponseText.trimmingCharacters(in: .newlines)) + } + if let foundDuplicate = foundDuplicate { + throw SourceKitError.failed(.duplicateResult(name: foundDuplicate.description), request: info, response: truncatedResponseText.trimmingCharacters(in: .newlines)) } }