Skip to content

Commit 90cafe9

Browse files
authored
Merge pull request #32 from benlangmuir/test-infrastructure-rebased
Add infrastructure for testing IndexStoreDB
2 parents 14e86f7 + b57be17 commit 90cafe9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3702
-239
lines changed

Package.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ let package = Package(
1111
.library(
1212
name: "IndexStoreDB_CXX",
1313
targets: ["IndexStoreDB_Index"]),
14+
.library(
15+
name: "ISDBTestSupport",
16+
targets: ["ISDBTestSupport"]),
17+
.executable(
18+
name: "tibs",
19+
targets: ["tibs"])
1420
],
1521
dependencies: [],
1622
targets: [
@@ -23,7 +29,28 @@ let package = Package(
2329

2430
.testTarget(
2531
name: "IndexStoreDBTests",
26-
dependencies: ["IndexStoreDB"]),
32+
dependencies: ["IndexStoreDB", "ISDBTestSupport"]),
33+
34+
// MARK: Swift Test Infrastructure
35+
36+
// The Test Index Build System (tibs) library.
37+
.target(
38+
name: "ISDBTibs",
39+
dependencies: []),
40+
41+
.testTarget(
42+
name: "ISDBTibsTests",
43+
dependencies: ["ISDBTibs"]),
44+
45+
// Commandline tool for working with tibs projects.
46+
.target(
47+
name: "tibs",
48+
dependencies: ["ISDBTibs"]),
49+
50+
// Test support library, built on top of tibs.
51+
.target(
52+
name: "ISDBTestSupport",
53+
dependencies: ["IndexStoreDB", "ISDBTibs"]),
2754

2855
// MARK: C++ interface
2956

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
/// Reads and caches file contents by URL.
16+
///
17+
/// Use `cache.get(url)` to read a file, or get its cached contents. The contents can be overridden
18+
/// or removed from the cache by calling `cache.set(url, to: "new contents")`
19+
public final class SourceFileCache {
20+
var cache: [URL: String] = [:]
21+
22+
public init(_ cache: [URL: String] = [:]) {
23+
self.cache = cache
24+
}
25+
26+
/// Read the contents of `file`, or retrieve them from the cache if available.
27+
///
28+
/// * parameter file: The file to read.
29+
/// * returns: The file contents as a String.
30+
/// * throws: If there are any errors reading the file.
31+
public func get(_ file: URL) throws -> String {
32+
if let content = cache[file] {
33+
return content
34+
}
35+
let content = try String(contentsOfFile: file.path, encoding: .utf8)
36+
cache[file] = content
37+
return content
38+
}
39+
40+
/// Set the cached contents of `file` to `content`.
41+
///
42+
/// * parameters
43+
/// * file: The file to read.
44+
/// * content: The new file content.
45+
public func set(_ file: URL, to content: String?) {
46+
cache[file] = content
47+
}
48+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
import IndexStoreDB
15+
16+
/// A source location (file:line:column) in a test project, for use with the TestLocationScanner.
17+
public struct TestLocation: Hashable {
18+
19+
/// The path/url of the source file.
20+
public var url: URL
21+
22+
/// The one-based line number.
23+
public var line: Int
24+
25+
/// The one-based column number.
26+
///
27+
/// FIXME: define utf8 vs. utf16 column index.
28+
public var column: Int
29+
30+
public init(url: URL, line: Int, column: Int) {
31+
self.url = url
32+
self.line = line
33+
self.column = column
34+
}
35+
}
36+
37+
extension TestLocation: Comparable {
38+
public static func <(a: TestLocation, b: TestLocation) -> Bool {
39+
return (a.url.path, a.line, a.column) < (b.url.path, b.line, b.column)
40+
}
41+
}
42+
43+
extension SymbolLocation {
44+
45+
/// Constructs a SymbolLocation from a TestLocation, using a non-system path by default.
46+
public init(_ loc: TestLocation, isSystem: Bool = false) {
47+
self.init(
48+
path: loc.url.path,
49+
isSystem: isSystem,
50+
line: loc.line,
51+
utf8Column: loc.column)
52+
}
53+
}
54+
55+
extension Symbol {
56+
57+
/// Returns a SymbolOccurrence with the given location and roles.
58+
public func at(_ location: TestLocation, roles: SymbolRole) -> SymbolOccurrence {
59+
return self.at(SymbolLocation(location), roles: roles)
60+
}
61+
}
62+
63+
extension TestLocation: CustomStringConvertible {
64+
public var description: String { "\(url.path):\(line):\(column)" }
65+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
/// A builder object for scanning source files for TestLocations specified in /*inline comments*/.
16+
///
17+
/// The scanner searches source files for inline comments /*foo*/ and builds up a mapping from name
18+
/// (the contents of the inline comment) to the location in the source file that it was found.
19+
///
20+
/// For example:
21+
///
22+
/// ```
23+
/// var scanner = TestLocationScanner()
24+
/// scanner.scan("""
25+
/// func /*foo:def*/foo() {}
26+
/// """, url: myURL)
27+
/// scanner.result == ["foo:def": TestLocation(url: myURL, line: 1, column: 17)]
28+
/// ```
29+
public struct TestLocationScanner {
30+
31+
/// The result of the scan (so far), mapping name to test location.
32+
public var result: [String: TestLocation] = [:]
33+
34+
public init() {}
35+
36+
public enum Error: Swift.Error {
37+
case noSuchFileOrDirectory(URL)
38+
39+
/// The sources contained a `/*/*nested*/*/` inline comment, which is not supported.
40+
case nestedComment(TestLocation)
41+
42+
/// The same test location name was used in multiple places.
43+
case duplicateKey(String, TestLocation, TestLocation)
44+
}
45+
46+
public mutating func scan(_ str: String, url: URL) throws {
47+
if str.count < 4 {
48+
return
49+
}
50+
51+
enum State {
52+
/// Outside any comment.
53+
case normal(prev: Character)
54+
55+
/// Inside a comment. The payload contains the previous character and the index of the first
56+
/// character after the '*' (i.e. the start of the comment body).
57+
///
58+
/// bodyStart
59+
/// |
60+
/// /*XXX*/
61+
/// ^^^
62+
case comment(bodyStart: String.Index, prev: Character)
63+
}
64+
65+
var state = State.normal(prev: "_")
66+
var i = str.startIndex
67+
var line = 1
68+
var column = 1
69+
70+
while i != str.endIndex {
71+
let c = str[i]
72+
73+
switch (state, c) {
74+
case (.normal("/"), "*"):
75+
state = .comment(bodyStart: str.index(after: i), prev: "_")
76+
case (.normal(_), _):
77+
state = .normal(prev: c)
78+
79+
case (.comment(let start, "*"), "/"):
80+
let name = String(str[start..<str.index(before: i)])
81+
let loc = TestLocation(url: url, line: line, column: column + 1)
82+
if let prevLoc = result.updateValue(loc, forKey: name) {
83+
throw Error.duplicateKey(name, prevLoc, loc)
84+
}
85+
state = .normal(prev: "_")
86+
87+
case (.comment(_, "/"), "*"):
88+
throw Error.nestedComment(TestLocation(url: url, line: line, column: column))
89+
90+
case (.comment(let start, _), _):
91+
state = .comment(bodyStart: start, prev: c)
92+
}
93+
94+
if c == "\n" {
95+
line += 1
96+
column = 1
97+
} else {
98+
column += 1
99+
}
100+
101+
i = str.index(after: i)
102+
}
103+
}
104+
105+
public mutating func scan(file: URL, sourceCache: SourceFileCache) throws {
106+
let content = try sourceCache.get(file)
107+
try scan(content, url: file)
108+
}
109+
110+
public mutating func scan(rootDirectory: URL, sourceCache: SourceFileCache) throws {
111+
let fm = FileManager.default
112+
113+
guard let generator = fm.enumerator(at: rootDirectory, includingPropertiesForKeys: []) else {
114+
throw Error.noSuchFileOrDirectory(rootDirectory)
115+
}
116+
117+
while let url = generator.nextObject() as? URL {
118+
if isSourceFileExtension(url.pathExtension) {
119+
try scan(file: url, sourceCache: sourceCache)
120+
}
121+
}
122+
}
123+
}
124+
125+
/// Scans `rootDirectory` for test locations, returning a mapping from name to location.
126+
///
127+
/// See TestLocationScanner.
128+
public func scanLocations(
129+
rootDirectory: URL,
130+
sourceCache: SourceFileCache
131+
) throws -> [String: TestLocation] {
132+
var scanner = TestLocationScanner()
133+
try scanner.scan(rootDirectory: rootDirectory, sourceCache: sourceCache)
134+
return scanner.result
135+
}
136+
137+
func isSourceFileExtension(_ ext: String) -> Bool {
138+
switch ext {
139+
case "swift", "c", "cpp", "m", "mm", "h", "hpp":
140+
return true
141+
default:
142+
return false
143+
}
144+
}

0 commit comments

Comments
 (0)