Skip to content

Commit 8310ae6

Browse files
committed
Add tests for new FileHandle API. Add utilities. Fix NSError comparison.
1 parent dc7b4b7 commit 8310ae6

File tree

4 files changed

+299
-14
lines changed

4 files changed

+299
-14
lines changed

Foundation.xcodeproj/project.pbxproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2762,7 +2762,7 @@
27622762
MTL_ENABLE_DEBUG_INFO = YES;
27632763
ONLY_ACTIVE_ARCH = YES;
27642764
SDKROOT = macosx;
2765-
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT DEBUG";
2765+
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "FOUNDATION_XCTEST NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT DEBUG";
27662766
VERSIONING_SYSTEM = "apple-generic";
27672767
VERSION_INFO_PREFIX = "";
27682768
};

Foundation/NSError.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,10 @@ open class NSError : NSObject, NSCopying, NSSecureCoding, NSCoding {
170170
override open func isEqual(_ object: Any?) -> Bool {
171171
// Pulled from NSObject itself; this works on all platforms.
172172
guard let obj = object as? NSError else { return false }
173-
return obj === self
173+
guard obj.domain == self.domain && obj.code == self.code else { return false }
174+
175+
// NSDictionaries are comparable, and that's the actual equality ObjC Foundation cares about.
176+
return (self.userInfo as NSDictionary) == (obj.userInfo as NSDictionary)
174177
}
175178
}
176179

TestFoundation/TestFileHandle.swift

+263-12
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,255 @@
11
// This source file is part of the Swift.org open source project
22
//
3-
// Copyright (c) 2016, 2018 Apple Inc. and the Swift project authors
3+
// Copyright (c) 2016, 2018, 2019 Apple Inc. and the Swift project authors
44
// Licensed under Apache License v2.0 with Runtime Library Exception
55
//
66
// See https://swift.org/LICENSE.txt for license information
77
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

1010
class TestFileHandle : XCTestCase {
11-
static var allTests : [(String, (TestFileHandle) -> () throws -> ())] {
12-
return [
13-
("test_constants", test_constants),
14-
("test_nullDevice", test_nullDevice),
15-
("test_truncateFile", test_truncateFile)
16-
]
11+
var allHandles: [FileHandle] = []
12+
var allTemporaryFileURLs: [URL] = []
13+
14+
let content: Data = {
15+
return """
16+
CHAPTER I.
17+
18+
The Author gives some account of himself and family--His first
19+
inducements to travel--He is shipwrecked, and swims for his life--Gets
20+
safe on shore in the country of Lilliput--Is made a prisoner, and
21+
carried up the country
22+
23+
CHAPTER II.
24+
25+
The emperor of Lilliput, attended by several of the nobility, comes to
26+
see the Author in his confinement--The emperor's person and habits
27+
described--Learned men appointed to teach the Author their language--He
28+
gains favor by his mild disposition--His pockets are searched, and his
29+
sword and pistols taken from him
30+
31+
CHAPTER III.
32+
33+
The Author diverts the emperor, and his nobility of both sexes, in a
34+
very uncommon manner--The diversions of the court of Lilliput
35+
described--The Author has his liberty granted him upon certain
36+
conditions
37+
38+
CHAPTER IV.
39+
40+
Mildendo, the metropolis of Lilliput, described, together with the
41+
emperor's palace--A conversation between the Author and a principal
42+
secretary concerning the affairs of that empire--The Author's offers to
43+
serve the emperor in his wars
44+
45+
CHAPTER V.
46+
47+
The Author, by an extraordinary stratagem, prevents an invasion--A high
48+
title of honor is conferred upon him--Ambassadors arrive from the
49+
emperor of Blefuscu, and sue for peace
50+
""".data(using: .utf8)!
51+
}()
52+
53+
func createFileHandle() -> FileHandle {
54+
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString)
55+
56+
expectDoesNotThrow({ try content.write(to: url) }, "Couldn't write file at \(url.path) for testing")
57+
58+
var fh: FileHandle?
59+
expectDoesNotThrow({ fh = try FileHandle(forReadingFrom: url) }, "Couldn't create file handle.")
60+
61+
allHandles.append(fh!)
62+
allTemporaryFileURLs.append(url)
63+
return fh!
64+
}
65+
66+
func createFileHandleForSeekErrors() -> FileHandle {
67+
var fds: [Int32] = [-1, -1]
68+
fds.withUnsafeMutableBufferPointer { (pointer) -> Void in
69+
pipe(pointer.baseAddress)
70+
}
71+
72+
close(fds[1])
73+
74+
let fh = FileHandle(fileDescriptor: fds[0], closeOnDealloc: true)
75+
allHandles.append(fh)
76+
return fh
77+
}
78+
79+
let seekError = NSError(domain: NSCocoaErrorDomain, code: NSFileReadUnknownError, userInfo: [ NSUnderlyingErrorKey: NSError(domain: NSPOSIXErrorDomain, code: Int(ESPIPE), userInfo: [:])])
80+
81+
func createFileHandleForReadErrors() -> FileHandle {
82+
// Create a file handle where calling read returns -1.
83+
// Accomplish this by creating one for a directory.
84+
let fd = open(".", O_RDONLY)
85+
expectTrue(fd > 0, "We must be able to open a fd to the current directory (.)")
86+
let fh = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
87+
allHandles.append(fh)
88+
return fh
89+
}
90+
91+
let readError = NSError(domain: NSCocoaErrorDomain, code: NSFileReadUnknownError, userInfo: [ NSUnderlyingErrorKey: NSError(domain: NSPOSIXErrorDomain, code: Int(EISDIR), userInfo: [:])])
92+
93+
override func tearDown() {
94+
for handle in allHandles {
95+
print("Closing \(handle)")
96+
try? handle.close()
97+
}
98+
99+
for url in allTemporaryFileURLs {
100+
print("Deleting \(url)")
101+
try? FileManager.default.removeItem(at: url)
102+
}
103+
104+
allHandles = []
105+
allTemporaryFileURLs = []
106+
}
107+
108+
func testHandleCreationAndCleanup() {
109+
_ = createFileHandle()
110+
_ = createFileHandleForSeekErrors()
111+
_ = createFileHandleForReadErrors()
112+
}
113+
114+
func testReadUpToCount() {
115+
let handle = createFileHandle()
116+
117+
// Zero:
118+
expectDoesNotThrow({
119+
let zeroData = try handle.read(upToCount: 0)
120+
expectEqual(zeroData, nil, "Data should be nil")
121+
}, "Must not throw while reading zero data")
122+
123+
// Max:
124+
expectDoesNotThrow({
125+
let maxData = try handle.read(upToCount: Int.max)
126+
expectEqual(maxData, content, "Data should be equal to the content")
127+
}, "Must not throw while reading Int.max data")
128+
129+
// EOF:
130+
expectDoesNotThrow({
131+
let eof = try handle.read(upToCount: Int.max)
132+
expectEqual(eof, nil, "EOF should return nil")
133+
}, "Must not throw while reading EOF")
134+
135+
// One byte at a time
136+
let onesHandle = createFileHandle()
137+
expectDoesNotThrow({
138+
for index in content.indices {
139+
let oneByteData = try onesHandle.read(upToCount: 1)
140+
let expected = content[index ..< content.index(after: index)]
141+
expectEqual(oneByteData, expected, "Read incorrect data at index \(index)")
142+
}
143+
}, "Must not throw while reading one byte at a time")
144+
145+
// EOF:
146+
expectDoesNotThrow({
147+
let eof = try handle.read(upToCount: 1)
148+
expectEqual(eof, nil, "EOF should return nil")
149+
}, "Must not throw while reading one-byte-at-a-time EOF")
150+
151+
// Errors:
152+
expectThrows(readError, {
153+
_ = try createFileHandleForReadErrors().read(upToCount: 1)
154+
}, "Must throw when encountering a read error")
155+
}
156+
157+
func testReadToEnd() {
158+
let handle = createFileHandle()
159+
160+
// To end:
161+
expectDoesNotThrow({
162+
let maxData = try handle.readToEnd()
163+
expectEqual(maxData, content, "Data to end should equal what was written out")
164+
}, "Must not throw while reading to end")
165+
166+
// EOF:
167+
expectDoesNotThrow({
168+
let eof = try handle.readToEnd()
169+
expectEqual(eof, nil, "EOF should return nil")
170+
}, "Must not throw while reading EOF")
171+
172+
// Errors:
173+
expectThrows(readError, {
174+
_ = try createFileHandleForReadErrors().readToEnd()
175+
}, "Must throw when encountering a read error")
176+
}
177+
178+
func testOffset() {
179+
// One byte at a time:
180+
let handle = createFileHandle()
181+
var offset: UInt64 = 0
182+
183+
for index in content.indices {
184+
expectDoesNotThrow({ offset = try handle.offset() }, "Reading the offset must not throw")
185+
expectEqual(offset, UInt64(index), "The offset must match")
186+
expectDoesNotThrow({ _ = try handle.read(upToCount: 1) }, "Advancing by reading must not throw")
187+
}
188+
189+
expectDoesNotThrow({ offset = try handle.offset() }, "Reading the offset at EOF must not throw")
190+
expectEqual(offset, UInt64(content.count), "The offset at EOF must be at the end")
191+
192+
// Error:
193+
expectThrows(seekError, {
194+
_ = try createFileHandleForSeekErrors().offset()
195+
}, "Must throw when encountering a seek error")
196+
}
197+
198+
func createPipe() -> Pipe {
199+
let pipe = Pipe()
200+
allHandles.append(pipe.fileHandleForWriting)
201+
allHandles.append(pipe.fileHandleForReading)
202+
return pipe
203+
}
204+
205+
func performWriteTest<T: DataProtocol>(with data: T, expecting expectation: Data? = nil) {
206+
let pipe = createPipe()
207+
let writer = pipe.fileHandleForWriting
208+
let reader = pipe.fileHandleForReading
209+
210+
expectDoesNotThrow({ try writer.write(contentsOf: data) }, "Writing must succeed")
211+
expectDoesNotThrow({
212+
expectEqual(try reader.read(upToCount: data.count), expectation ?? content, "The content must be the same")
213+
}, "Reading must succeed")
214+
}
215+
216+
func testWritingWithData() {
217+
performWriteTest(with: content)
218+
}
219+
220+
func testWritingWithBuffer() {
221+
content.withUnsafeBytes { (buffer) in
222+
performWriteTest(with: buffer)
223+
}
224+
}
225+
226+
func testWritingWithMultiregionData() {
227+
var expectation = Data()
228+
expectation.append(content)
229+
expectation.append(content)
230+
expectation.append(content)
231+
expectation.append(content)
232+
233+
content.withUnsafeBytes { (buffer) in
234+
let data1 = DispatchData(bytes: buffer)
235+
let data2 = DispatchData(bytes: buffer)
236+
237+
var multiregion1: DispatchData = .empty
238+
multiregion1.append(data1)
239+
multiregion1.append(data2)
240+
241+
var multiregion2: DispatchData = .empty
242+
multiregion2.append(data1)
243+
multiregion2.append(data2)
244+
245+
var longMultiregion: DispatchData = .empty
246+
longMultiregion.append(multiregion1)
247+
longMultiregion.append(multiregion2)
248+
249+
expectTrue(longMultiregion.regions.count > 0, "The multiregion data must be actually composed of multiple regions")
250+
251+
performWriteTest(with: longMultiregion, expecting: expectation)
252+
}
17253
}
18254

19255
func test_constants() {
@@ -32,10 +268,10 @@ class TestFileHandle : XCTestCase {
32268
XCTAssertEqual(fh.readData(ofLength: 15).count, 0)
33269
fh.synchronizeFile()
34270

35-
fh.write(Data(bytes: [1,2]))
271+
fh.write(Data([1,2]))
36272
fh.seek(toFileOffset: 0)
37273
XCTAssertEqual(fh.availableData.count, 0)
38-
fh.write(Data(bytes: [1,2]))
274+
fh.write(Data([1,2]))
39275
fh.seek(toFileOffset: 0)
40276
XCTAssertEqual(fh.readDataToEndOfFile().count, 0)
41277
}
@@ -51,13 +287,13 @@ class TestFileHandle : XCTestCase {
51287
fh.truncateFile(atOffset: 100)
52288
XCTAssertEqual(fh.offsetInFile, 100)
53289

54-
fh.write(Data(bytes: [1, 2]))
290+
fh.write(Data([1, 2]))
55291
XCTAssertEqual(fh.offsetInFile, 102)
56292

57293
fh.seek(toFileOffset: 4)
58294
XCTAssertEqual(fh.offsetInFile, 4)
59295

60-
(0..<20).forEach { fh.write(Data(bytes: [$0])) }
296+
(0..<20).forEach { fh.write(Data([$0])) }
61297
XCTAssertEqual(fh.offsetInFile, 24)
62298

63299
fh.seekToEndOfFile()
@@ -71,7 +307,22 @@ class TestFileHandle : XCTestCase {
71307

72308
let data = fh.readDataToEndOfFile()
73309
XCTAssertEqual(data.count, 10)
74-
XCTAssertEqual(data, Data(bytes: [0, 0, 0, 0, 0, 1, 2, 3, 4, 5]))
310+
XCTAssertEqual(data, Data([0, 0, 0, 0, 0, 1, 2, 3, 4, 5]))
75311
}
76312
}
313+
314+
static var allTests : [(String, (TestFileHandle) -> () throws -> ())] {
315+
return [
316+
("testHandleCreationAndCleanup", testHandleCreationAndCleanup),
317+
("testReadUpToCount", testReadUpToCount),
318+
("testReadToEnd", testReadToEnd),
319+
("testOffset", testOffset),
320+
("testWritingWithData", testWritingWithData),
321+
("testWritingWithBuffer", testWritingWithBuffer),
322+
("testWritingWithMultiregionData", testWritingWithMultiregionData),
323+
("test_constants", test_constants),
324+
("test_nullDevice", test_nullDevice),
325+
("test_truncateFile", test_truncateFile)
326+
]
327+
}
77328
}

TestFoundation/Utilities.swift

+31
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,34 @@ extension Optional {
128128
}
129129
}
130130
}
131+
132+
// Shims for StdlibUnittest:
133+
// These allow for test code to be written targeting the overlay and then ported to s-c-f, or vice versa.
134+
// You can use the FOUNDATION_XCTEST compilation condition to distinguish between tests running in XCTest
135+
// or in StdlibUnittest.
136+
137+
func expectThrows<Error: Swift.Error & Equatable>(_ expectedError: Error, _ test: () throws -> Void, _ message: @autoclosure () -> String = "") {
138+
var caught = false
139+
do {
140+
try test()
141+
} catch let error as Error {
142+
caught = true
143+
XCTAssertEqual(error, expectedError, message())
144+
} catch {
145+
caught = true
146+
XCTFail("Incorrect error thrown: \(error) -- \(message())")
147+
}
148+
XCTAssert(caught, "No error thrown -- \(message())")
149+
}
150+
151+
func expectDoesNotThrow(_ test: () throws -> Void, _ message: @autoclosure () -> String = "") {
152+
XCTAssertNoThrow(try test(), message())
153+
}
154+
155+
func expectTrue(_ actual: Bool, _ message: @autoclosure () -> String = "") {
156+
XCTAssertTrue(actual, message())
157+
}
158+
159+
func expectEqual<T: Equatable>(_ expected: T, _ actual: T, _ message: @autoclosure () -> String = "") {
160+
XCTAssertEqual(expected, actual, message())
161+
}

0 commit comments

Comments
 (0)