-
Notifications
You must be signed in to change notification settings - Fork 103
/
Copy pathFileHandleTests.swift
280 lines (254 loc) · 8.33 KB
/
FileHandleTests.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//
@testable @_spi(Experimental) import Testing
private import _TestingInternals
#if !SWT_NO_FILE_IO
// NOTE: we don't run these tests on iOS (etc.) because processes on those
// platforms are sandboxed and do not have arbitrary filesystem access.
#if os(macOS) || os(Linux) || os(FreeBSD) || os(Android) || os(Windows)
@Suite("FileHandle Tests")
struct FileHandleTests {
// FileHandle is non-copyable, so it cannot yet be used as a test parameter.
func canGet(_ fileHandle: borrowing FileHandle) {
// This test function doesn't really do much other than check that the
// standard I/O files can be accessed.
fileHandle.withUnsafeCFILEHandle { fileHandle in
#expect(EOF != feof(fileHandle))
}
}
@Test("Can get stdout")
func canGetStdout() {
canGet(.stdout)
}
@Test("Can get stderr")
func canGetStderr() {
canGet(.stderr)
}
@Test("Can get file descriptor")
func fileDescriptor() throws {
let fileHandle = try FileHandle.temporary()
try fileHandle.withUnsafePOSIXFileDescriptor { fd in
try #require(fd != nil)
}
}
#if !os(Windows) // Windows does not like invalid file descriptors.
@Test("Init from invalid file descriptor")
func invalidFileDescriptor() throws {
#expect(throws: CError.self) {
_ = try FileHandle(unsafePOSIXFileDescriptor: -1, mode: "")
}
}
#endif
#if os(Windows)
@Test("Can get Windows file HANDLE")
func fileHANDLE() throws {
let fileHandle = try FileHandle.temporary()
try fileHandle.withUnsafeWindowsHANDLE { handle in
try #require(handle != nil)
}
}
#endif
#if SWT_TARGET_OS_APPLE
@Test("close() function")
func closeFunction() async throws {
try await confirmation("File handle closed") { closed in
let fileHandle = try fileHandleForCloseMonitoring(with: closed)
fileHandle.close()
}
}
#endif
@Test("Can write to a file")
func canWrite() throws {
try withTemporaryPath { path in
let fileHandle = try FileHandle(forWritingAtPath: path)
try fileHandle.write([0, 1, 2, 3, 4, 5])
try fileHandle.write("Hello world!")
}
}
#if !SWT_NO_EXIT_TESTS
@Test("Writing requires contiguous storage")
func writeIsContiguous() async {
await #expect(exitsWith: .failure) {
let fileHandle = try FileHandle.null(mode: "wb")
try fileHandle.write([1, 2, 3, 4, 5].lazy.filter { $0 == 1 })
}
}
#endif
@Test("Can read from a file")
func canRead() throws {
let bytes: [UInt8] = (0 ..< 8192).map { _ in
UInt8.random(in: .min ... .max)
}
try withTemporaryPath { path in
do {
let fileHandle = try FileHandle(forWritingAtPath: path)
try fileHandle.write(bytes)
}
let fileHandle = try FileHandle(forReadingAtPath: path)
let bytes2 = try fileHandle.readToEnd()
#expect(bytes == bytes2)
}
}
@Test("Cannot write bytes to a read-only file")
func cannotWriteBytesToReadOnlyFile() throws {
let fileHandle = try FileHandle.null(mode: "rb")
#expect(throws: CError.self) {
try fileHandle.write([0, 1, 2, 3, 4, 5])
}
}
@Test("Cannot write string to a read-only file")
func cannotWriteStringToReadOnlyFile() throws {
let fileHandle = try FileHandle.null(mode: "rb")
#expect(throws: CError.self) {
try fileHandle.write("Impossible!")
}
}
#if !os(Windows)
// Disabled on Windows because the equivalent of /dev/tty, CON, redirects
// to stdout, but stdout may be any type of file, not just a TTY.
@Test("Can recognize opened TTY")
func isTTY() throws {
#if os(Windows)
let fileHandle = try FileHandle(forWritingAtPath: "CON")
#else
let oldTERM = Environment.variable(named: "TERM")
Environment.setVariable("xterm", named: "TERM")
defer {
Environment.setVariable(oldTERM, named: "TERM")
}
var primary: CInt = 0
var secondary: CInt = 0
try #require(0 == openpty(&primary, &secondary, nil, nil, nil))
close(secondary)
let file = try #require(fdopen(primary, "wb"))
#endif
let fileHandle = FileHandle(unsafeCFILEHandle: file, closeWhenDone: true)
#expect(Bool(fileHandle.isTTY))
}
#endif
@Test("Can recognize opened pipe")
func isPipe() throws {
let pipe = try FileHandle.Pipe()
#expect(pipe.readEnd.isPipe as Bool)
#expect(pipe.writeEnd.isPipe as Bool)
}
#if SWT_TARGET_OS_APPLE
@Test("Can close ends of a pipe")
func closeEndsOfPipe() async throws {
try await confirmation("File handle closed", expectedCount: 2) { closed in
var pipe1 = try FileHandle.Pipe()
pipe1.readEnd = try fileHandleForCloseMonitoring(with: closed)
_ = pipe1.closeReadEnd()
var pipe2 = try FileHandle.Pipe()
pipe2.writeEnd = try fileHandleForCloseMonitoring(with: closed)
_ = pipe2.closeWriteEnd()
}
}
#endif
@Test("/dev/null is not a TTY or pipe")
func devNull() throws {
let fileHandle = try FileHandle.null(mode: "wb")
#expect(!Bool(fileHandle.isTTY))
#expect(!Bool(fileHandle.isPipe))
}
#if !os(Windows)
// Disabled on Windows because it does not have the equivalent of
// fmemopen(), so there is no need for this test.
@Test("fmemopen()'ed file is not a TTY or pipe")
func fmemopenedFile() throws {
let file = try #require(fmemopen(nil, 1, "wb+"))
let fileHandle = FileHandle(unsafeCFILEHandle: file, closeWhenDone: true)
#expect(!Bool(fileHandle.isTTY))
#expect(!Bool(fileHandle.isPipe))
}
#endif
}
// MARK: - Fixtures
func withTemporaryPath<R>(_ body: (_ path: String) throws -> R) throws -> R {
// NOTE: we are not trying to test mkstemp() here. We are trying to test the
// capacity of FileHandle to open a file for reading or writing and we need a
// temporary file to write to.
#if os(Windows)
let path = try String(unsafeUninitializedCapacity: 1024) { buffer in
try #require(0 == tmpnam_s(buffer.baseAddress!, buffer.count))
return strnlen(buffer.baseAddress!, buffer.count)
}
#else
let path = appendPathComponent("file_named_\(UInt64.random(in: 0 ..< .max))", to: try temporaryDirectory())
#endif
defer {
_ = remove(path)
}
return try body(path)
}
extension FileHandle {
static func temporary() throws -> FileHandle {
#if os(Windows)
let tmpFile: SWT_FILEHandle = try {
var file: SWT_FILEHandle?
try #require(0 == tmpfile_s(&file))
return file!
}()
#else
let tmpFile = try #require(tmpfile())
#endif
return FileHandle(unsafeCFILEHandle: tmpFile, closeWhenDone: true)
}
static func null(mode: String) throws -> FileHandle {
#if os(Windows)
try FileHandle(atPath: "NUL", mode: mode)
#else
try FileHandle(atPath: "/dev/null", mode: mode)
#endif
}
}
#endif
func temporaryDirectory() throws -> String {
#if SWT_TARGET_OS_APPLE
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(PATH_MAX)) { buffer in
if 0 != confstr(_CS_DARWIN_USER_TEMP_DIR, buffer.baseAddress, buffer.count) {
return String(cString: buffer.baseAddress!)
}
return try #require(Environment.variable(named: "TMPDIR"))
}
#elseif os(Linux) || os(FreeBSD)
"/tmp"
#elseif os(Android)
Environment.variable(named: "TMPDIR") ?? "/data/local/tmp"
#elseif os(Windows)
try withUnsafeTemporaryAllocation(of: wchar_t.self, capacity: Int(MAX_PATH + 1)) { buffer in
// NOTE: GetTempPath2W() was introduced in Windows 10 Build 20348.
if 0 == GetTempPathW(DWORD(buffer.count), buffer.baseAddress) {
throw Win32Error(rawValue: GetLastError())
}
return try #require(String.decodeCString(buffer.baseAddress, as: UTF16.self)?.result)
}
#endif
}
#if SWT_TARGET_OS_APPLE
func fileHandleForCloseMonitoring(with confirmation: Confirmation) throws -> FileHandle {
let context = Unmanaged.passRetained(confirmation as AnyObject).toOpaque()
let file = try #require(
funopen(
context,
{ _, _, _ in 0 },
nil,
nil,
{ context in
let confirmation = Unmanaged<AnyObject>.fromOpaque(context!).takeRetainedValue() as! Confirmation
confirmation()
return 0
}
) as SWT_FILEHandle?
)
return FileHandle(unsafeCFILEHandle: file, closeWhenDone: false)
}
#endif
#endif