File: FileHandleTests.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (230 lines) | stat: -rw-r--r-- 6,976 bytes parent folder | download
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
//
// 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 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(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)
  @Test("Can get Windows file HANDLE")
  func fileHANDLE() throws {
    let fileHandle = try FileHandle.temporary()
    try fileHandle.withUnsafeWindowsHANDLE { handle in
      try #require(handle != nil)
    }
  }
#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!")
    }
  }

  @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 {
#if os(Windows)
    var rHandle: HANDLE?
    var wHandle: HANDLE?
    try #require(CreatePipe(&rHandle, &wHandle, nil, 0))
    if let rHandle {
      CloseHandle(rHandle)
    }
    let fdWrite = _open_osfhandle(intptr_t(bitPattern: wHandle), 0)
    let file = try #require(_fdopen(fdWrite, "wb"))
#else
    var fds: [CInt] = [-1, -1]
    try #require(0 == pipe(&fds))
    try #require(fds[1] >= 0)
    close(fds[0])
    let file = try #require(fdopen(fds[1], "wb"))
#endif
    let fileHandle = FileHandle(unsafeCFILEHandle: file, closeWhenDone: true)
    #expect(Bool(fileHandle.isPipe))
  }

  @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)
  "/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
}

#endif