Swift 5: How to test Data(bytesNoCopy:count:deallocator:)?


(Gwendal Roué) #1

Hello,

I want to test that a blob loaded from SQLite has not been copied:

// Test the Row.dataNoCopy(atIndex:) method.
// It should not copy the blob buffer loaded by SQLite.
let rows = try Row.fetchCursor(db, "SELECT * FROM datas")
while let row = try rows.next() {
    // The pointer to the blob loaded by SQLite (using C API)
    let sqlitePointer: UnsafeRawPointer? = sqlite3_column_blob(row.sqliteStatement, 0)
    
    // Load that blob into a Data value with the tested method.
    // This internally calls Data(bytesNoCopy:count:deallocator:)
    // with the same blob buffer as above.
    let data: Data = row.dataNoCopy(atIndex: 0)!
    data.withUnsafeBytes { buffer in
        // Test that data has not been copied
        XCTAssertEqual(buffer.baseAddress, sqlitePointer)
    }
}

This test fails, with Swift 5, with the following error:

XCTAssertEqual failed: ("Optional(0x00007ffeefbfc760)")
is not equal to ("Optional(0x0000000103022200)")

With the Swift 4.2 compiler (Xcode 10.1), the test passes.

Do I write my test in a bad way, or is it a regression in the Foundation library shipped with the latest Swift 5 snapshot (06/02/2019)?

Actually, I really can't manage to see Data(bytesNoCopy:count:deallocator:) in action:

let data1 = Data([0])
data1.withUnsafeBytes { buffer1 in
    let data2 = Data(
        bytesNoCopy: UnsafeMutableRawPointer(mutating: buffer1.baseAddress!),
        count: buffer1.count,
        deallocator: .none)

    data2.withUnsafeBytes { buffer2 in
        // Unexpectedly (?) fails
        assert(buffer1.baseAddress == buffer2.baseAddress)
    }
}

let nsData1 = NSData(data: data1)
let nsData2 = NSData(
    bytesNoCopy: UnsafeMutableRawPointer(mutating: nsData1.bytes),
    length: nsData1.count,
    freeWhenDone: false)
// Expectedly passes
assert(nsData1.bytes == nsData2.bytes)

(Ben Rimmington) #2

Usually the "NoCopy" label is only a hint. The internal _Representation can store an InlineData with up to 14 bytes (64-bit arch) or 6 bytes (32-bit arch). Your last example should succeed with let data1 = Data(repeating: 0, count: 15).


(Gwendal Roué) #3

Your prediction is 100% accurate, Ben, thank you! 15 is indeed the threshold that triggers the assertions to pass. :clap:

I've learned something today. Now I do have to test that the library actually helps users spare memory: I'll test with bigger data chunks guaranteed to be somewhere on the heap. Thank you again!