Files
2026-06-13 16:22:23 +08:00

368 lines
16 KiB
Swift

// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
import XCTest
import SWCompression
class TarWriterTests: XCTestCase {
private static let tempDir = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent("TestSWCompression-" + UUID().uuidString, isDirectory: true)
class override func setUp() {
do {
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true, attributes: nil)
} catch let error {
fatalError("TarWriterTests.setUp(): unable to create temporary directory: \(error)")
}
}
class override func tearDown() {
do {
try FileManager.default.removeItem(at: tempDir)
} catch let error {
fatalError("TarWriterTests.tearDown(): unable to remove temporary directory: \(error)")
}
}
private static func generateContainerData(_ entries: [TarEntry], format: TarContainer.Format = .pax) throws -> Data {
let tempFileUrl = tempDir.appendingPathComponent(UUID().uuidString, isDirectory: false)
try "".write(to: tempFileUrl, atomically: true, encoding: .utf8)
let handle = try FileHandle(forWritingTo: tempFileUrl)
var writer = TarWriter(fileHandle: handle, force: format)
for entry in entries {
try writer.append(entry)
}
try writer.finalize()
try handle.close()
return try Data(contentsOf: tempFileUrl)
}
func test1() throws {
var info = TarEntryInfo(name: "file.txt", type: .regular)
info.ownerUserName = "timofeysolomko"
info.ownerGroupName = "staff"
info.ownerID = 501
info.groupID = 20
info.permissions = Permissions(rawValue: 420)
// We have to convert time interval to int, since tar can't store fractional timestamps, so we lose in accuracy.
let intTimeInterval = Int(Date().timeIntervalSince1970)
let date = Date(timeIntervalSince1970: Double(intTimeInterval))
info.modificationTime = date
info.creationTime = date
info.accessTime = date
info.comment = "comment"
let data = Data("Hello, World!\n".utf8)
let entry = TarEntry(info: info, data: data)
let containerData = try TarWriterTests.generateContainerData([entry])
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .pax)
let newEntries = try TarContainer.open(container: containerData)
XCTAssertEqual(newEntries.count, 1)
XCTAssertEqual(newEntries[0].info.name, "file.txt")
XCTAssertEqual(newEntries[0].info.type, .regular)
XCTAssertEqual(newEntries[0].info.size, 14)
XCTAssertEqual(newEntries[0].info.ownerUserName, "timofeysolomko")
XCTAssertEqual(newEntries[0].info.ownerGroupName, "staff")
XCTAssertEqual(newEntries[0].info.ownerID, 501)
XCTAssertEqual(newEntries[0].info.groupID, 20)
XCTAssertEqual(newEntries[0].info.permissions, Permissions(rawValue: 420))
XCTAssertEqual(newEntries[0].info.modificationTime, date)
XCTAssertEqual(newEntries[0].info.creationTime, date)
XCTAssertEqual(newEntries[0].info.accessTime, date)
XCTAssertEqual(newEntries[0].info.comment, "comment")
XCTAssertEqual(newEntries[0].data, data)
}
func test2() throws {
let dict = [
"SWCompression/Tests/TAR": "value",
"key": "valuevaluevaluevaluevaluevaluevaluevaluevaluevaluevaluevaluevaluevaluevaluevaluevaluevalue22"
]
var info = TarEntryInfo(name: "symbolic-link", type: .symbolicLink)
info.accessTime = Date(timeIntervalSince1970: 1)
info.creationTime = Date(timeIntervalSince1970: 2)
info.modificationTime = Date(timeIntervalSince1970: 0)
info.permissions = Permissions(rawValue: 420)
info.permissions?.insert(.executeOwner)
info.ownerID = 250
info.groupID = 250
info.ownerUserName = "testUserName"
info.ownerGroupName = "testGroupName"
info.deviceMajorNumber = 1
info.deviceMinorNumber = 2
info.charset = "UTF-8"
info.comment = "some comment..."
info.linkName = "file"
info.unknownExtendedHeaderRecords = dict
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry])
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .pax)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, "symbolic-link")
XCTAssertEqual(newInfo.type, .symbolicLink)
XCTAssertEqual(newInfo.permissions?.rawValue, 484)
XCTAssertEqual(newInfo.ownerID, 250)
XCTAssertEqual(newInfo.groupID, 250)
XCTAssertEqual(newInfo.size, 0)
XCTAssertEqual(newInfo.modificationTime?.timeIntervalSince1970, 0)
XCTAssertEqual(newInfo.linkName, "file")
XCTAssertEqual(newInfo.ownerUserName, "testUserName")
XCTAssertEqual(newInfo.ownerGroupName, "testGroupName")
XCTAssertEqual(newInfo.deviceMajorNumber, 1)
XCTAssertEqual(newInfo.deviceMinorNumber, 2)
XCTAssertEqual(newInfo.accessTime?.timeIntervalSince1970, 1)
XCTAssertEqual(newInfo.creationTime?.timeIntervalSince1970, 2)
XCTAssertEqual(newInfo.charset, "UTF-8")
XCTAssertEqual(newInfo.comment, "some comment...")
XCTAssertEqual(newInfo.unknownExtendedHeaderRecords, dict)
}
func testLongName() throws {
var info = TarEntryInfo(name: "", type: .regular)
info.name = "path/to/"
info.name.append(String(repeating: "readme/", count: 15))
info.name.append("readme.txt")
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry])
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .pax)
let newInfo = try TarContainer.open(container: containerData)[0].info
// This name should fit into ustar format using "prefix" field
XCTAssertEqual(newInfo.name, info.name)
}
func testVeryLongName() throws {
var info = TarEntryInfo(name: "", type: .regular)
info.name = "path/to/"
info.name.append(String(repeating: "readme/", count: 25))
info.name.append("readme.txt")
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry])
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .pax)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, info.name)
}
func testLongDirectoryName() throws {
// Tests what happens to the filename's trailing slash when "prefix" field is used.
var info = TarEntryInfo(name: "", type: .regular)
info.name = "path/to/"
info.name.append(String(repeating: "readme/", count: 15))
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry])
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .pax)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, info.name)
}
func testUnicode() throws {
let date = Date(timeIntervalSince1970: 1300000)
var info = TarEntryInfo(name: "ссылка", type: .symbolicLink)
info.accessTime = date
info.creationTime = date
info.modificationTime = date
info.permissions = Permissions(rawValue: 420)
info.ownerID = 501
info.groupID = 20
info.ownerUserName = "timofeysolomko"
info.ownerGroupName = "staff"
info.deviceMajorNumber = 1
info.deviceMinorNumber = 2
info.comment = "комментарий"
info.linkName = "путь/к/файлу"
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry])
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .pax)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, "ссылка")
XCTAssertEqual(newInfo.type, .symbolicLink)
XCTAssertEqual(newInfo.permissions?.rawValue, 420)
XCTAssertEqual(newInfo.ownerID, 501)
XCTAssertEqual(newInfo.groupID, 20)
XCTAssertEqual(newInfo.size, 0)
XCTAssertEqual(newInfo.modificationTime?.timeIntervalSince1970, 1300000)
XCTAssertEqual(newInfo.linkName, "путь/к/файлу")
XCTAssertEqual(newInfo.ownerUserName, "timofeysolomko")
XCTAssertEqual(newInfo.ownerGroupName, "staff")
XCTAssertEqual(newInfo.accessTime?.timeIntervalSince1970, 1300000)
XCTAssertEqual(newInfo.creationTime?.timeIntervalSince1970, 1300000)
XCTAssertEqual(newInfo.comment, "комментарий")
}
func testUstar() throws {
// This set of settings should result in the container which uses only ustar TAR format features.
let date = Date(timeIntervalSince1970: 1300000)
var info = TarEntryInfo(name: "file.txt", type: .regular)
info.permissions = Permissions(rawValue: 420)
info.ownerID = 501
info.groupID = 20
info.modificationTime = date
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry], format: .ustar)
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .ustar)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, "file.txt")
XCTAssertEqual(newInfo.type, .regular)
XCTAssertEqual(newInfo.permissions?.rawValue, 420)
XCTAssertEqual(newInfo.ownerID, 501)
XCTAssertEqual(newInfo.groupID, 20)
XCTAssertEqual(newInfo.size, 0)
XCTAssertEqual(newInfo.modificationTime?.timeIntervalSince1970, 1300000)
XCTAssertEqual(newInfo.linkName, "")
XCTAssertEqual(newInfo.ownerUserName, "")
XCTAssertEqual(newInfo.ownerGroupName, "")
XCTAssertNil(newInfo.accessTime)
XCTAssertNil(newInfo.creationTime)
XCTAssertNil(newInfo.comment)
}
func testNegativeMtime() throws {
let date = Date(timeIntervalSince1970: -1300000)
var info = TarEntryInfo(name: "file.txt", type: .regular)
info.modificationTime = date
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry])
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .pax)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, "file.txt")
XCTAssertEqual(newInfo.type, .regular)
XCTAssertEqual(newInfo.size, 0)
XCTAssertEqual(newInfo.modificationTime?.timeIntervalSince1970, -1300000)
XCTAssertEqual(newInfo.linkName, "")
XCTAssertEqual(newInfo.ownerUserName, "")
XCTAssertEqual(newInfo.ownerGroupName, "")
XCTAssertNil(newInfo.permissions)
XCTAssertNil(newInfo.ownerID)
XCTAssertNil(newInfo.groupID)
XCTAssertNil(newInfo.accessTime)
XCTAssertNil(newInfo.creationTime)
XCTAssertNil(newInfo.comment)
}
func testBigUid() throws {
// Int.max tests that base-256 encoding of integer fields works in the edge case.
for uid in [(1 << 32) - 1, Int.max] {
var info = TarEntryInfo(name: "file.txt", type: .regular)
info.ownerID = uid
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry])
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .pax)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, "file.txt")
XCTAssertEqual(newInfo.type, .regular)
XCTAssertEqual(newInfo.size, 0)
XCTAssertEqual(newInfo.ownerID, uid)
XCTAssertEqual(newInfo.linkName, "")
XCTAssertEqual(newInfo.ownerUserName, "")
XCTAssertEqual(newInfo.ownerGroupName, "")
XCTAssertNil(newInfo.permissions)
XCTAssertNil(newInfo.groupID)
XCTAssertNil(newInfo.accessTime)
XCTAssertNil(newInfo.creationTime)
XCTAssertNil(newInfo.modificationTime)
XCTAssertNil(newInfo.comment)
}
}
func testGnuLongName() throws {
var info = TarEntryInfo(name: "", type: .regular)
info.name = "path/to/"
info.name.append(String(repeating: "name/", count: 25))
info.name.append("name.txt")
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry], format: .gnu)
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .gnu)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, info.name)
}
func testGnuLongLinkName() throws {
var info = TarEntryInfo(name: "", type: .symbolicLink)
info.name = "link"
info.linkName = "path/to/"
info.linkName.append(String(repeating: "name/", count: 25))
info.linkName.append("name.txt")
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry], format: .gnu)
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .gnu)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, info.name)
}
func testGnuBothLongNames() throws {
var info = TarEntryInfo(name: "", type: .symbolicLink)
info.name = "path/to/"
info.name.append(String(repeating: "name/", count: 25))
info.name.append("name.txt")
info.linkName = "path/to/"
info.linkName.append(String(repeating: "link/", count: 25))
info.linkName.append("link.txt")
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry], format: .gnu)
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .gnu)
let newInfo = try TarContainer.open(container: containerData)[0].info
XCTAssertEqual(newInfo.name, info.name)
}
func testGnuTimes() throws {
var info = TarEntryInfo(name: "dir", type: .directory)
info.ownerUserName = "tsolomko"
info.ownerGroupName = "staff"
info.ownerID = 501
info.groupID = 20
info.permissions = Permissions(rawValue: 420)
// We have to convert time interval to int, since tar can't store fractional timestamps, so we lose in accuracy.
let intTimeInterval = Int(Date().timeIntervalSince1970)
let date = Date(timeIntervalSince1970: Double(intTimeInterval))
info.modificationTime = date
info.creationTime = date
info.accessTime = date
let entry = TarEntry(info: info, data: Data())
let containerData = try TarWriterTests.generateContainerData([entry], format: .gnu)
XCTAssertEqual(try TarContainer.formatOf(container: containerData), .gnu)
let newEntries = try TarContainer.open(container: containerData)
XCTAssertEqual(newEntries.count, 1)
XCTAssertEqual(newEntries[0].info.name, "dir")
XCTAssertEqual(newEntries[0].info.type, .directory)
XCTAssertEqual(newEntries[0].info.size, 0)
XCTAssertEqual(newEntries[0].info.ownerUserName, "tsolomko")
XCTAssertEqual(newEntries[0].info.ownerGroupName, "staff")
XCTAssertEqual(newEntries[0].info.ownerID, 501)
XCTAssertEqual(newEntries[0].info.groupID, 20)
XCTAssertEqual(newEntries[0].info.permissions, Permissions(rawValue: 420))
XCTAssertEqual(newEntries[0].info.modificationTime, date)
XCTAssertEqual(newEntries[0].info.creationTime, date)
XCTAssertEqual(newEntries[0].info.accessTime, date)
XCTAssertNil(newEntries[0].info.comment)
}
}