Files

366 lines
16 KiB
Swift

// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
import XCTest
import SWCompression
class TarTests: XCTestCase {
private static let testType: String = "tar"
func testBadFile_short() {
XCTAssertThrowsError(try TarContainer.open(container: Data([0, 1, 2])))
}
func testBadFile_invalid() throws {
// This is potentially a misleading test, since there is no way to guarantee that a file is not a TAR container.
// We use randomly generated data, since the 0-filled data is processed as an empty container.
let testData = try Constants.data(forAnswer: "test7")
XCTAssertThrowsError(try TarContainer.open(container: testData))
}
func test() throws {
let testData = try Constants.data(forTest: "test", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .ustar)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].info.name, "test5.answer")
XCTAssertEqual(entries[0].info.size, 0)
XCTAssertEqual(entries[0].info.type, .regular)
XCTAssertEqual(entries[0].info.ownerID, 501)
XCTAssertEqual(entries[0].info.groupID, 20)
XCTAssertEqual(entries[0].info.ownerUserName, "timofeysolomko")
XCTAssertEqual(entries[0].info.ownerGroupName, "staff")
XCTAssertEqual(entries[0].info.permissions, Permissions(rawValue: 420))
XCTAssertNil(entries[0].info.comment)
XCTAssertEqual(entries[0].data, Data())
}
func testPax() throws {
let testData = try Constants.data(forTest: "full_test", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .pax)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 5)
for entry in entries {
let name = entry.info.name.components(separatedBy: ".")[0]
let answerData = try Constants.data(forAnswer: name)
XCTAssertEqual(entry.data, answerData)
XCTAssertEqual(entry.info.type, .regular)
XCTAssertEqual(entry.info.ownerUserName, "tsolomko")
XCTAssertEqual(entry.info.ownerGroupName, "tsolomko")
XCTAssertEqual(entry.info.ownerID, 1001)
XCTAssertEqual(entry.info.groupID, 1001)
XCTAssertEqual(entry.info.permissions, Permissions(rawValue: 436))
XCTAssertNil(entry.info.comment)
// Checking times' values is a bit difficult since they are extremely precise.
XCTAssertNotNil(entry.info.modificationTime)
XCTAssertNotNil(entry.info.accessTime)
XCTAssertNotNil(entry.info.creationTime)
}
}
func testPaxRecordNewline() throws {
// In this test we check the handling of a PAX header record with a newline character inside a record value.
let testData = try Constants.data(forTest: "test_pax_record_newline", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .pax)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries.first?.info.name, "test_file")
XCTAssertEqual(entries.first?.data, Data("Hello, weird pax record value!".utf8))
XCTAssertEqual(entries.first?.info.type, .regular)
XCTAssertEqual(entries.first?.info.size, 30)
XCTAssertNil(entries.first?.info.accessTime)
XCTAssertNil(entries.first?.info.creationTime)
XCTAssertEqual(entries.first?.info.modificationTime, Date(timeIntervalSince1970: 1666443016))
XCTAssertEqual(entries.first?.info.permissions, Permissions(rawValue: 420))
XCTAssertEqual(entries.first?.info.ownerID, 501)
XCTAssertEqual(entries.first?.info.groupID, 20)
XCTAssertEqual(entries.first?.info.ownerUserName, "tsolomko")
XCTAssertEqual(entries.first?.info.ownerGroupName, "staff")
XCTAssertEqual(entries.first?.info.deviceMajorNumber, 0)
XCTAssertEqual(entries.first?.info.deviceMinorNumber, 0)
XCTAssertNil(entries.first?.info.charset)
XCTAssertNil(entries.first?.info.comment)
XCTAssertEqual(entries.first?.info.linkName, "")
XCTAssertEqual(entries.first?.info.unknownExtendedHeaderRecords?.count, 3)
XCTAssertEqual(entries.first?.info.unknownExtendedHeaderRecords?["normal1"], "test_record_value1")
XCTAssertEqual(entries.first?.info.unknownExtendedHeaderRecords?["newline"], "test1\ntest2")
XCTAssertEqual(entries.first?.info.unknownExtendedHeaderRecords?["normal2"], "test_record_value2")
}
func testFormats() throws {
let formatTestNames = ["test_gnu", "test_oldgnu", "test_pax", "test_ustar", "test_v7"]
let answerData = try Constants.data(forAnswer: "test1")
for testName in formatTestNames {
let testData = try Constants.data(forTest: testName, withType: TarTests.testType)
if testName == "test_gnu" {
XCTAssertEqual(try TarContainer.formatOf(container: testData), .gnu)
} else if testName == "test_oldgnu" {
XCTAssertEqual(try TarContainer.formatOf(container: testData), .gnu)
} else if testName == "test_pax" {
XCTAssertEqual(try TarContainer.formatOf(container: testData), .pax)
} else if testName == "test_ustar" {
XCTAssertEqual(try TarContainer.formatOf(container: testData), .ustar)
} else if testName == "test_v7" {
XCTAssertEqual(try TarContainer.formatOf(container: testData), .prePosix)
}
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].info.name, "test1.answer")
XCTAssertEqual(entries[0].info.size, 14)
XCTAssertEqual(entries[0].info.type, .regular)
XCTAssertEqual(entries[0].data, answerData)
}
}
func testLongNames() throws {
let formatTestNames = ["long_test_gnu", "long_test_oldgnu", "long_test_pax"]
for testName in formatTestNames {
let testData = try Constants.data(forTest: testName, withType: TarTests.testType)
if testName == "long_test_gnu" {
XCTAssertEqual(try TarContainer.formatOf(container: testData), .gnu)
} else if testName == "long_test_oldgnu" {
XCTAssertEqual(try TarContainer.formatOf(container: testData), .gnu)
} else if testName == "long_test_pax" {
XCTAssertEqual(try TarContainer.formatOf(container: testData), .pax)
}
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 6)
}
}
func testWinContainer() throws {
let testData = try Constants.data(forTest: "test_win", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .ustar)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 2)
XCTAssertEqual(entries[0].info.name, "dir/")
XCTAssertEqual(entries[0].info.type, .directory)
XCTAssertEqual(entries[0].info.size, 0)
XCTAssertEqual(entries[0].info.ownerUserName, "")
XCTAssertEqual(entries[0].info.ownerGroupName, "")
XCTAssertEqual(entries[0].info.ownerID, 0)
XCTAssertEqual(entries[0].info.groupID, 0)
XCTAssertEqual(entries[0].info.permissions, Permissions(rawValue: 511))
XCTAssertNil(entries[0].info.comment)
XCTAssertEqual(entries[0].data, nil)
XCTAssertEqual(entries[1].info.name, "text_win.txt")
XCTAssertEqual(entries[1].info.type, .regular)
XCTAssertEqual(entries[1].info.size, 15)
XCTAssertEqual(entries[1].info.ownerUserName, "")
XCTAssertEqual(entries[1].info.ownerGroupName, "")
XCTAssertEqual(entries[1].info.ownerID, 0)
XCTAssertEqual(entries[1].info.groupID, 0)
XCTAssertEqual(entries[1].info.permissions, Permissions(rawValue: 511))
XCTAssertNil(entries[1].info.comment)
XCTAssertEqual(entries[1].data, "Hello, Windows!".data(using: .utf8))
}
func testEmptyFile() throws {
let testData = try Constants.data(forTest: "test_empty_file", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .ustar)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].info.name, "empty_file")
XCTAssertEqual(entries[0].info.type, .regular)
XCTAssertEqual(entries[0].info.size, 0)
XCTAssertEqual(entries[0].info.ownerID, 501)
XCTAssertEqual(entries[0].info.groupID, 20)
XCTAssertEqual(entries[0].info.ownerUserName, "timofeysolomko")
XCTAssertEqual(entries[0].info.ownerGroupName, "staff")
XCTAssertEqual(entries[0].info.permissions, Permissions(rawValue: 420))
XCTAssertNil(entries[0].info.comment)
XCTAssertEqual(entries[0].data, Data())
}
func testEmptyDirectory() throws {
let testData = try Constants.data(forTest: "test_empty_dir", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .ustar)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].info.name, "empty_dir/")
XCTAssertEqual(entries[0].info.type, .directory)
XCTAssertEqual(entries[0].info.size, 0)
XCTAssertEqual(entries[0].info.ownerID, 501)
XCTAssertEqual(entries[0].info.groupID, 20)
XCTAssertEqual(entries[0].info.ownerUserName, "timofeysolomko")
XCTAssertEqual(entries[0].info.ownerGroupName, "staff")
XCTAssertEqual(entries[0].info.permissions, Permissions(rawValue: 493))
XCTAssertNil(entries[0].info.comment)
XCTAssertEqual(entries[0].data, nil)
}
func testOnlyDirectoryHeader() throws {
// This tests the correct handling of the situation when there is nothing in the container but one basic header,
// even no EOF marker (two blocks of zeros).
let testData = try Constants.data(forTest: "test_only_dir_header", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .ustar)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].info.name, "empty_dir/")
XCTAssertEqual(entries[0].info.type, .directory)
XCTAssertEqual(entries[0].info.size, 0)
XCTAssertEqual(entries[0].info.ownerID, 501)
XCTAssertEqual(entries[0].info.groupID, 20)
XCTAssertEqual(entries[0].info.ownerUserName, "timofeysolomko")
XCTAssertEqual(entries[0].info.ownerGroupName, "staff")
XCTAssertEqual(entries[0].info.permissions, Permissions(rawValue: 493))
XCTAssertNil(entries[0].info.comment)
XCTAssertNil(entries[0].data)
}
func testEmptyContainer() throws {
let testData = try Constants.data(forTest: "test_empty_cont", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .prePosix)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.isEmpty, true)
}
func testBigContainer() throws {
let testData = try Constants.data(forTest: "SWCompressionSourceCode", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .ustar)
_ = try TarContainer.info(container: testData)
_ = try TarContainer.open(container: testData)
}
func testUnicodeUstar() throws {
let testData = try Constants.data(forTest: "test_unicode_ustar", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .ustar)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].info.name, "текстовый файл.answer")
XCTAssertEqual(entries[0].info.type, .regular)
XCTAssertEqual(entries[0].info.ownerID, 501)
XCTAssertEqual(entries[0].info.groupID, 20)
XCTAssertEqual(entries[0].info.ownerUserName, "timofeysolomko")
XCTAssertEqual(entries[0].info.ownerGroupName, "staff")
XCTAssertEqual(entries[0].info.permissions, Permissions(rawValue: 420))
XCTAssertNil(entries[0].info.comment)
XCTAssertEqual(entries[0].data, Constants.текстовыйФайлData)
}
func testUnicodePax() throws {
let testData = try Constants.data(forTest: "test_unicode_pax", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .pax)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].info.name, "текстовый файл.answer")
XCTAssertEqual(entries[0].info.type, .regular)
XCTAssertEqual(entries[0].info.ownerID, 501)
XCTAssertEqual(entries[0].info.groupID, 20)
XCTAssertEqual(entries[0].info.ownerUserName, "timofeysolomko")
XCTAssertEqual(entries[0].info.ownerGroupName, "staff")
XCTAssertEqual(entries[0].info.permissions, Permissions(rawValue: 420))
XCTAssertNil(entries[0].info.comment)
XCTAssertEqual(entries[0].data, Constants.текстовыйФайлData)
}
func testGnuIncrementalFormat() throws {
let testData = try Constants.data(forTest: "test_gnu_inc_format", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .gnu)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 3)
for entry in entries {
XCTAssertEqual(entry.info.ownerID, 501)
XCTAssertEqual(entry.info.groupID, 20)
XCTAssertEqual(entry.info.ownerUserName, "timofeysolomko")
XCTAssertEqual(entry.info.ownerGroupName, "staff")
XCTAssertNotNil(entry.info.accessTime)
XCTAssertNotNil(entry.info.creationTime)
}
}
func testBigNumField() throws {
// This file is truncated because of its size (8.6 GB): it doesn't contain any actual file data.
let testData = try Constants.data(forTest: "test_big_num_field", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .gnu)
let entries = try TarContainer.info(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].name, "rands")
XCTAssertEqual(entries[0].type, .regular)
XCTAssertEqual(entries[0].size, 8600000000)
XCTAssertEqual(entries[0].ownerUserName, "timofeysolomko")
XCTAssertEqual(entries[0].ownerGroupName, "staff")
XCTAssertEqual(entries[0].ownerID, 501)
XCTAssertEqual(entries[0].groupID, 20)
XCTAssertEqual(entries[0].permissions, Permissions(rawValue: 420))
XCTAssertNil(entries[0].comment)
}
func testNegativeMtime() throws {
let testData = try Constants.data(forTest: "test_negative_mtime", withType: TarTests.testType)
XCTAssertEqual(try TarContainer.formatOf(container: testData), .gnu)
let entries = try TarContainer.open(container: testData)
XCTAssertEqual(entries.count, 1)
XCTAssertEqual(entries[0].info.name, "file")
XCTAssertEqual(entries[0].info.type, .regular)
XCTAssertEqual(entries[0].info.size, 27)
XCTAssertEqual(entries[0].info.ownerID, 501)
XCTAssertEqual(entries[0].info.groupID, 20)
XCTAssertEqual(entries[0].info.ownerUserName, "timofeysolomko")
XCTAssertEqual(entries[0].info.ownerGroupName, "staff")
XCTAssertEqual(entries[0].info.permissions, Permissions(rawValue: 420))
XCTAssertEqual(entries[0].info.modificationTime, Date(timeIntervalSince1970: -313006414))
XCTAssertNil(entries[0].info.comment)
XCTAssertEqual(entries[0].data, "File with negative mtime.\n\n".data(using: .utf8))
}
}