-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathTestExtractionPlugin_example.swift
executable file
·135 lines (112 loc) · 4.15 KB
/
TestExtractionPlugin_example.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/swift sh
// swiftlint:disable all
import Foundation
import SourceKittenFramework // jpsim/SourceKitten ~> 0.1
// struct TestCase: Codable {
// var name: String
// var suite: String
// }
//
// struct TestExtractionInput: Codable {
// var candidates: Array<URL>
// var device: Device
// }
//
// struct Device: Codable {
// var name: String
// var version: String
// }
//
struct TestExtractionPlugin {
func handle(_ input: TestExtractionInput, pluginData _: String?) -> [TestCase] {
do {
let parser = XCTestFileParser()
let extractionTestCases = try parser.extractTestCases(from: input.candidates)
return extractionTestCases.map { TestCase(name: $0.name, suite: $0.suite) }
} catch {
print(error.localizedDescription)
return []
}
}
}
struct ExtractionTestCase {
let name: String
let suite: String
let types: Set<String>
}
struct KittenElement: Codable, Equatable, Hashable {
let stage: String?
let accessibility: String?
let types: [KittenElement]?
let name: String?
let kind: String?
let subElements: [KittenElement]?
enum CodingKeys: String, CodingKey {
case stage = "key.diagnostic_stage"
case accessibility = "key.accessibility"
case types = "key.inheritedtypes"
case name = "key.name"
case kind = "key.kind"
case subElements = "key.substructure"
}
var isType: Bool {
true
// return kind == "source.lang.swift.decl.function.method.instance" ||
// kind == "source.lang.swift.decl.function.protocol.instance"
}
var isTestMethod: Bool {
name?.hasPrefix("test") == true &&
name?.hasSuffix("()") == true &&
accessibility != "source.lang.swift.accessibility.private" &&
kind == "source.lang.swift.decl.function.method.instance"
}
var isOpenClass: Bool {
subElements?.count != 0 &&
accessibility != "source.lang.swift.accessibility.private" &&
kind == "source.lang.swift.decl.class"
}
func conforms(candidates: [KittenElement]) -> Set<String> {
var items = candidates
guard var currentInherits = types else { return [] }
var result = Set(currentInherits)
loop: repeat {
for item in items {
if currentInherits.map(\.name).contains(item.name) {
currentInherits = item.types ?? []
result = result.union(currentInherits)
items.removeAll { $0 == item }
continue loop
}
}
break
} while true
result = result.union(currentInherits)
return Set(result.compactMap(\.name))
}
}
struct XCTestFileParser {
func extractTestCases(from urls: [URL]) throws -> [ExtractionTestCase] {
var result = [ExtractionTestCase]()
for url in urls {
guard let file = File(path: url.path) else { fatalError("File `\(url.path)` does not exists") }
let structure = try Structure(file: file).description
guard let structureData = structure.data(using: .utf8) else { fatalError("Failed parsing `\(url.path)` source file") }
let parsed = try JSONDecoder().decode(KittenElement.self, from: structureData)
guard let types = parsed.subElements?.filter(\.isOpenClass) else {
return [] // no testing classes found
}
let testClasses = types.filter { $0.conforms(candidates: types).contains("XCTestCase") }
let testCases: [[ExtractionTestCase]] = testClasses.compactMap {
guard let suite = $0.name,
let methods = $0.subElements?.filter(\.isTestMethod)
else {
return nil
}
let testCaseTypes = $0.conforms(candidates: types)
return methods.map { ExtractionTestCase(name: $0.name!.replacingOccurrences(of: "()", with: ""), suite: suite, types: testCaseTypes) }
}
result += testCases.flatMap { $0 }
}
return result
}
}