Skip to content

Convert Data/DataIO Tests to swift-testing #1363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 92 additions & 180 deletions Tests/FoundationEssentialsTests/DataIOTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,63 @@
//
//===----------------------------------------------------------------------===//

#if canImport(TestSupport)
import TestSupport
#endif

#if canImport(Glibc)
@preconcurrency import Glibc
#endif
import Testing

#if FOUNDATION_FRAMEWORK
@testable import Foundation
#else
@testable import FoundationEssentials
#endif // FOUNDATION_FRAMEWORK

class DataIOTests : XCTestCase {
private func generateTestData(count: Int = 16_777_216) -> Data {
// Set a few bytes so we're sure to not be all zeros
let buf = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: count)
for i in 0..<15 {
for j in 0..<128 {
buf[j * 1024 + i] = UInt8.random(in: 1..<42)
}
}

return Data(bytesNoCopy: buf.baseAddress!, count: count, deallocator: .custom({ ptr, _ in
ptr.deallocate()
}))
}

@Suite("Data I/O")
private final class DataIOTests {

// MARK: - Helpers

func testURL() -> URL {
// Generate a random file name
URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("testfile-\(UUID().uuidString)")
}
let url: URL

func generateTestData(count: Int = 16_777_216) -> Data {
let memory = malloc(count)!
let ptr = memory.bindMemory(to: UInt8.self, capacity: count)

// Set a few bytes so we're sure to not be all zeros
let buf = UnsafeMutableBufferPointer(start: ptr, count: count)
for i in 0..<15 {
for j in 0..<128 {
buf[j * 1024 + i] = UInt8.random(in: 1..<42)
}
}

return Data(bytesNoCopy: ptr, count: count, deallocator: .free)
init() {
// Generate a random file name
url = URL.temporaryDirectory.appendingPathComponent("testfile-\(UUID().uuidString)")
}

func writeAndVerifyTestData(to url: URL, writeOptions: Data.WritingOptions = [], readOptions: Data.ReadingOptions = []) throws {
func writeAndVerifyTestData(to url: URL, writeOptions: Data.WritingOptions = [], readOptions: Data.ReadingOptions = [], sourceLocation: SourceLocation = #_sourceLocation) throws {
let data = generateTestData()
try data.write(to: url, options: writeOptions)
let readData = try Data(contentsOf: url, options: readOptions)
XCTAssertEqual(data, readData)
#expect(data == readData, sourceLocation: sourceLocation)
}

func cleanup(at url: URL) {
deinit {
do {
try FileManager.default.removeItem(at: url)
} catch {
// Ignore
}
}


// MARK: - Tests

func test_basicReadWrite() throws {
let url = testURL()
@Test func basicReadWrite() throws {
try writeAndVerifyTestData(to: url)
cleanup(at: url)
}

func test_slicedReadWrite() throws {
@Test func slicedReadWrite() throws {
// Be sure to use progress reporting so we get tests of the chunking
let url = testURL()
let data = generateTestData()
let slice = data[data.startIndex.advanced(by: 1 * 1024 * 1024)..<data.startIndex.advanced(by: 8 * 1024 * 1024)]

Expand All @@ -87,76 +79,57 @@ class DataIOTests : XCTestCase {
p.resignCurrent()
#endif
let readData = try Data(contentsOf: url, options: [])
XCTAssertEqual(readData, slice)
cleanup(at: url)
#expect(readData == slice)
}

#if !os(WASI)
// Atomic writing is a very different code path
func test_readWriteAtomic() throws {
#if os(WASI)
try XCTSkip("atomic writing is not supported on WASI")
#else
let url = testURL()
@Test func readWriteAtomic() throws {
// Perform an atomic write to a file that does not exist
try writeAndVerifyTestData(to: url, writeOptions: [.atomic])

// Perform an atomic write to a file that already exists
try writeAndVerifyTestData(to: url, writeOptions: [.atomic])

cleanup(at: url)
#endif
}
#endif

func test_readWriteMapped() throws {
let url = testURL()
@Test func readWriteMapped() throws {
try writeAndVerifyTestData(to: url, readOptions: [.mappedIfSafe])

cleanup(at: url)
}

func test_writeFailure() throws {
let url = testURL()

@Test func writeFailure() throws {
let data = Data()
try data.write(to: url)

#if FOUNDATION_FRAMEWORK
XCTAssertThrowsError(try data.write(to: url, options: [.withoutOverwriting])) { e in
XCTAssertEqual((e as NSError).code, NSFileWriteFileExistsError)

#expect {
try data.write(to: url, options: [.withoutOverwriting])
} throws: {
($0 as? CocoaError)?.code == .fileWriteFileExists
}
#else
XCTAssertThrowsError(try data.write(to: url, options: [.withoutOverwriting]))
#endif

cleanup(at: url)
try FileManager.default.removeItem(at: url)

// Make sure clearing the error condition allows the write to succeed
try data.write(to: url, options: [.withoutOverwriting])

cleanup(at: url)
}

#if FOUNDATION_FRAMEWORK
// Progress is currently stubbed out for FoundationPreview
func test_writeWithProgress() throws {
let url = testURL()

@Test func writeWithProgress() throws {
let p = Progress(totalUnitCount: 1)
p.becomeCurrent(withPendingUnitCount: 1)
try writeAndVerifyTestData(to: url)
p.resignCurrent()

XCTAssertEqual(p.completedUnitCount, 1)
XCTAssertEqual(p.fractionCompleted, 1.0, accuracy: 0.1)
cleanup(at: url)
#expect(p.completedUnitCount == 1)
#expect(abs(p.fractionCompleted - 1.0) <= 0.1)
}
#endif

#if FOUNDATION_FRAMEWORK
func test_writeWithAttributes() throws {
@Test func writeWithAttributes() throws {
let writeData = generateTestData()

let url = testURL()
// Data doesn't have a direct API to write with attributes, but our I/O code has it. Use it via @testable interface here.

let writeAttrs: [String : Data] = [FileAttributeKey.hfsCreatorCode.rawValue : "abcd".data(using: .ascii)!]
Expand All @@ -166,146 +139,85 @@ class DataIOTests : XCTestCase {
var readAttrs: [String : Data] = [:]
let readData = try readDataFromFile(path: .url(url), reportProgress: false, options: [], attributesToRead: [FileAttributeKey.hfsCreatorCode.rawValue], attributes: &readAttrs)

XCTAssertEqual(writeData, readData)
XCTAssertEqual(writeAttrs, readAttrs)

cleanup(at: url)
#expect(writeData == readData)
#expect(writeAttrs == readAttrs)
}
#endif

func test_emptyFile() throws {
@Test func emptyFile() throws {
let data = Data()
let url = testURL()
try data.write(to: url)
let read = try Data(contentsOf: url, options: [])
XCTAssertEqual(data, read)

cleanup(at: url)
#expect(data == read)
}

#if FOUNDATION_FRAMEWORK
// String(contentsOf:) is not available outside the framework yet
@available(*, deprecated)
func test_emptyFileString() {
@Test func emptyFileString() throws {
let data = Data()
let url = testURL()

do {
try data.write(to: url)
let readString = try String(contentsOf: url)
XCTAssertEqual(readString, "")

let readStringWithEncoding = try String(contentsOf: url, encoding: String._Encoding.utf8)
XCTAssertEqual(readStringWithEncoding, "")

cleanup(at: url)
} catch {
XCTFail("Could not read file: \(error)")
}
}
#endif

func test_largeFile() throws {
#if !os(watchOS)
// More than 2 GB
let size = 0x80010000
let url = testURL()

let data = generateTestData(count: size)

try data.write(to: url)
let read = try! Data(contentsOf: url, options: .mappedIfSafe)

// No need to compare the contents, but do compare the size
XCTAssertEqual(data.count, read.count)
let readString = try String(contentsOf: url)
#expect(readString == "")

#if FOUNDATION_FRAMEWORK
// Try the NSData path
let readNS = try! NSData(contentsOf: url, options: .mappedIfSafe) as Data
XCTAssertEqual(data.count, readNS.count)
#endif

cleanup(at: url)
#endif // !os(watchOS)
let readStringWithEncoding = try String(contentsOf: url, encoding: .utf8)
#expect(readStringWithEncoding == "")
}
#endif

func test_writeToSpecialFile() throws {
#if !os(Linux) && !os(Windows)
throw XCTSkip("This test is only supported on Linux and Windows")
#else
#if os(Linux) || os(Windows)
@Test
#else
@Test(.disabled("This test is not applicable on this platform"))
#endif
func writeToSpecialFile() throws {
#if os(Windows)
let path = URL(filePath: "CON", directoryHint: .notDirectory)
#else
let path = URL(filePath: "/dev/stdout", directoryHint: .notDirectory)
#endif
XCTAssertNoThrow(try Data("Output to STDOUT\n".utf8).write(to: path))
#endif
#expect(throws: Never.self) {
try Data("Output to STDOUT\n".utf8).write(to: path)
}
}

func test_zeroSizeFile() throws {
#if !os(Linux) && !os(Android)
throw XCTSkip("This test is only applicable on Linux")
#else
#if os(Linux) || os(Android)
@Test
#else
@Test(.disabled("This test is not applicable on this platform"))
#endif
func zeroSizeFile() throws {
// Some files in /proc report a file size of 0 bytes via a stat call
// Ensure that these files can still be read despite appearing to be empty
let maps = try String(contentsOfFile: "/proc/self/maps", encoding: String._Encoding.utf8)
XCTAssertFalse(maps.isEmpty)
#endif
let maps = try String(contentsOfFile: "/proc/self/maps", encoding: .utf8)
#expect(!maps.isEmpty)
}
}

// MARK: - String Path Tests
func testStringDeletingLastPathComponent() {
XCTAssertEqual("/a/b/c".deletingLastPathComponent(), "/a/b")
XCTAssertEqual("".deletingLastPathComponent(), "")
XCTAssertEqual("/".deletingLastPathComponent(), "/")
XCTAssertEqual("q".deletingLastPathComponent(), "")
XCTAssertEqual("/aaa".deletingLastPathComponent(), "/")
XCTAssertEqual("/aaa/".deletingLastPathComponent(), "/")
XCTAssertEqual("/a/b/c/".deletingLastPathComponent(), "/a/b")
XCTAssertEqual("hello".deletingLastPathComponent(), "")
XCTAssertEqual("hello/".deletingLastPathComponent(), "")
XCTAssertEqual("/hello/".deletingLastPathComponent(), "/")
XCTAssertEqual("hello///".deletingLastPathComponent(), "")
XCTAssertEqual("a/".deletingLastPathComponent(), "")
XCTAssertEqual("a/b".deletingLastPathComponent(), "a")
XCTAssertEqual("a/b/".deletingLastPathComponent(), "a")
XCTAssertEqual("a//b//".deletingLastPathComponent(), "a")
}

func testAppendingPathComponent() {
let comp = "test"
XCTAssertEqual("/a/b/c".appendingPathComponent(comp), "/a/b/c/test")
XCTAssertEqual("".appendingPathComponent(comp), "test")
XCTAssertEqual("/".appendingPathComponent(comp), "/test")
XCTAssertEqual("q".appendingPathComponent(comp), "q/test")
XCTAssertEqual("/aaa".appendingPathComponent(comp), "/aaa/test")
XCTAssertEqual("/a/b/c/".appendingPathComponent(comp), "/a/b/c/test")
XCTAssertEqual("hello".appendingPathComponent(comp), "hello/test")
XCTAssertEqual("hello/".appendingPathComponent(comp), "hello/test")
extension LargeDataTests {
// This test is placed in the LargeDataTests suite since it allocates an extremely large amount of memory for some devices
#if !os(watchOS)
@Test func readLargeFile() throws {
let url = URL.temporaryDirectory.appendingPathComponent("testfile-\(UUID().uuidString)")
defer { try? FileManager.default.removeItem(at: url) }
// More than 2 GB
let size = 0x80010000

XCTAssertEqual("hello/".appendingPathComponent("/test"), "hello/test")
XCTAssertEqual("hello".appendingPathComponent("/test"), "hello/test")
XCTAssertEqual("hello///".appendingPathComponent("///test"), "hello/test")
XCTAssertEqual("hello".appendingPathComponent("test/"), "hello/test")
XCTAssertEqual("hello".appendingPathComponent("test/test2"), "hello/test/test2")
XCTAssertEqual("hello".appendingPathComponent("test/test2/"), "hello/test/test2")
XCTAssertEqual("hello".appendingPathComponent("test///test2/"), "hello/test/test2")
XCTAssertEqual("hello".appendingPathComponent("/"), "hello")
XCTAssertEqual("//".appendingPathComponent("/"), "/")
XCTAssertEqual("".appendingPathComponent(""), "")
}

func testStringLastPathComponent() {
XCTAssertEqual("/a/b/c".lastPathComponent, "c")
XCTAssertEqual("".lastPathComponent, "")
XCTAssertEqual("/".lastPathComponent, "/")
XCTAssertEqual("q".lastPathComponent, "q")
XCTAssertEqual("/aaa".lastPathComponent, "aaa")
XCTAssertEqual("/a/b/c/".lastPathComponent, "c")
XCTAssertEqual("hello".lastPathComponent, "hello")
XCTAssertEqual("hello/".lastPathComponent, "hello")
XCTAssertEqual("hello///".lastPathComponent, "hello")
XCTAssertEqual("//a//".lastPathComponent, "a")
let data = generateTestData(count: size)

try data.write(to: url)
let read = try Data(contentsOf: url, options: .mappedIfSafe)

// No need to compare the contents, but do compare the size
#expect(data.count == read.count)

#if FOUNDATION_FRAMEWORK
// Try the NSData path
let readNS = try NSData(contentsOf: url, options: .mappedIfSafe) as Data
#expect(data.count == readNS.count)
#endif
}
#endif
}

Loading
Loading