Merge pull request #23 from danramteke/develop

Tests pass using swift package manager on Linux and Mac, using Swift 4.0.2
This commit is contained in:
1024jp 2017-11-08 08:51:42 +09:00 committed by GitHub
commit fc9fb73ff4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 150 additions and 115 deletions

View File

@ -1,6 +1,15 @@
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "Gzip",
dependencies: [.Package(url: "https://github.com/1024jp/zlib.git", versions: Version(0, 0, 0)..<Version(1, 0, 0))]
products: [
.library(name: "Gzip", targets: ["Gzip"])
],
targets: [
.target(name: "Gzip", dependencies: ["system-zlib"]),
.target(name: "system-zlib"),
.testTarget(name: "GzipTests", dependencies: ["Gzip"])
]
)

View File

@ -4,19 +4,19 @@
/*
The MIT License (MIT)
© 2014-2017 1024jp <wolfrosch.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -27,34 +27,39 @@
*/
import Foundation
#if os(OSX)
import zlib
#elseif os(Linux)
import zlibLinux
#endif
/**
Compression level whose rawValue is based on the zlib's constants.
*/
public struct CompressionLevel: RawRepresentable {
/// Compression level in the range of `0` (no compression) to `9` (maximum compression).
public let rawValue: Int32
public static let noCompression = CompressionLevel(Z_NO_COMPRESSION)
public static let bestSpeed = CompressionLevel(Z_BEST_SPEED)
public static let bestCompression = CompressionLevel(Z_BEST_COMPRESSION)
public static let defaultCompression = CompressionLevel(Z_DEFAULT_COMPRESSION)
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init(_ rawValue: Int32) {
self.rawValue = rawValue
}
}
@ -63,67 +68,67 @@ public struct CompressionLevel: RawRepresentable {
*/
public struct GzipError: Swift.Error {
// cf. http://www.zlib.net/manual.html
public enum Kind {
/**
The stream structure was inconsistent.
- underlying zlib error: `Z_STREAM_ERROR` (-2)
*/
case stream
/**
The input data was corrupted (input stream not conforming to the zlib format or incorrect check value).
- underlying zlib error: `Z_DATA_ERROR` (-3)
*/
case data
/**
There was not enough memory.
- underlying zlib error: `Z_MEM_ERROR` (-4)
*/
case memory
/**
No progress is possible or there was not enough room in the output buffer.
- underlying zlib error: `Z_BUF_ERROR` (-5)
*/
case buffer
/**
The zlib library version is incompatible with the version assumed by the caller.
- underlying zlib error: `Z_VERSION_ERROR` (-6)
*/
case version
/**
An unknown error occurred.
- parameter code: return error by zlib
*/
case unknown(code: Int)
}
/// Error kind.
public let kind: Kind
/// Returned message by zlib.
public let message: String
internal init(code: Int32, msg: UnsafePointer<CChar>?) {
self.message = {
guard let msg = msg, let message = String(validatingUTF8: msg) else {
return "Unknown gzip error"
}
return message
}()
self.kind = {
switch code {
case Z_STREAM_ERROR:
@ -141,46 +146,46 @@ public struct GzipError: Swift.Error {
}
}()
}
public var localizedDescription: String {
return self.message
}
}
extension Data {
/**
Whether the data is compressed in gzip format.
*/
public var isGzipped: Bool {
return self.starts(with: [0x1f, 0x8b]) // check magic number
}
/**
Create a new `Data` object by compressing the receiver using zlib.
Throws an error if compression failed.
- parameters:
- level: Compression level.
- throws: `GzipError`
- returns: Gzip-compressed `Data` object.
*/
public func gzipped(level: CompressionLevel = .defaultCompression) throws -> Data {
guard !self.isEmpty else {
return Data()
}
var stream = self.createZStream()
var status: Int32
status = deflateInit2_(&stream, level.rawValue, Z_DEFLATED, MAX_WBITS + 16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY, ZLIB_VERSION, Int32(DataSize.stream))
guard status == Z_OK else {
@ -188,109 +193,109 @@ extension Data {
// Z_VERSION_ERROR The zlib library version is incompatible with the version assumed by the caller.
// Z_MEM_ERROR There was not enough memory.
// Z_STREAM_ERROR A parameter is invalid.
throw GzipError(code: status, msg: stream.msg)
}
var data = Data(capacity: DataSize.chunk)
while stream.avail_out == 0 {
if Int(stream.total_out) >= data.count {
data.count += DataSize.chunk
}
data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Bytef>) in
stream.next_out = bytes.advanced(by: Int(stream.total_out))
}
stream.avail_out = uInt(data.count) - uInt(stream.total_out)
deflate(&stream, Z_FINISH)
}
deflateEnd(&stream)
data.count = Int(stream.total_out)
return data
}
/**
Create a new `Data` object by decompressing the receiver using zlib.
Throws an error if decompression failed.
- throws: `GzipError`
- returns: Gzip-decompressed `Data` object.
*/
public func gunzipped() throws -> Data {
guard !self.isEmpty else {
return Data()
}
var stream = self.createZStream()
var status: Int32
status = inflateInit2_(&stream, MAX_WBITS + 32, ZLIB_VERSION, Int32(DataSize.stream))
guard status == Z_OK else {
// inflateInit2 returns:
// Z_VERSION_ERROR The zlib library version is incompatible with the version assumed by the caller.
// Z_MEM_ERROR There was not enough memory.
// Z_STREAM_ERROR A parameters are invalid.
throw GzipError(code: status, msg: stream.msg)
}
var data = Data(capacity: self.count * 2)
repeat {
if Int(stream.total_out) >= data.count {
data.count += self.count / 2
}
data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Bytef>) in
stream.next_out = bytes.advanced(by: Int(stream.total_out))
}
stream.avail_out = uInt(data.count) - uInt(stream.total_out)
status = inflate(&stream, Z_SYNC_FLUSH)
} while status == Z_OK
guard inflateEnd(&stream) == Z_OK && status == Z_STREAM_END else {
// inflate returns:
// Z_DATA_ERROR The input data was corrupted (input stream not conforming to the zlib format or incorrect check value).
// Z_STREAM_ERROR The stream structure was inconsistent (for example if next_in or next_out was NULL).
// Z_MEM_ERROR There was not enough memory.
// Z_BUF_ERROR No progress is possible or there was not enough room in the output buffer when Z_FINISH is used.
throw GzipError(code: status, msg: stream.msg)
}
data.count = Int(stream.total_out)
return data
}
private func createZStream() -> z_stream {
var stream = z_stream()
self.withUnsafeBytes { (bytes: UnsafePointer<Bytef>) in
stream.next_in = UnsafeMutablePointer<Bytef>(mutating: bytes)
}
stream.avail_in = uint(self.count)
return stream
}
}
private struct DataSize {
static let chunk = 2 ^ 14
static let stream = MemoryLayout<z_stream>.size
private init() { }
}

View File

@ -0,0 +1 @@
// intentionally empty

View File

@ -0,0 +1,6 @@
module zlibLinux [system] {
header "/usr/include/zlib.h"
header "/usr/include/zconf.h"
link "z"
export *
}

View File

@ -32,40 +32,47 @@ import XCTest
import Gzip
class GzipTests: XCTestCase {
static let allTests = [
("testGzip", GzipTests.testGZip),
("testZeroLength", GzipTests.testZeroLength),
("testWrongUngzip", GzipTests.testWrongUngzip),
("testCompressionLevel", GzipTests.testCompressionLevel),
("testFileDecompression", GzipTests.testFileDecompression)
]
func testGZip() {
let testSentence = "foo"
let data = testSentence.data(using: .utf8)!
let gzipped = try! data.gzipped()
let uncompressed = try! gzipped.gunzipped()
let uncompressedSentence = String(data: uncompressed, encoding: .utf8)
XCTAssertNotEqual(gzipped, data)
XCTAssertEqual(uncompressedSentence, testSentence)
XCTAssertTrue(gzipped.isGzipped)
XCTAssertFalse(data.isGzipped)
XCTAssertFalse(uncompressed.isGzipped)
}
func testZeroLength() {
let zeroLengthData = Data()
XCTAssertEqual(try! zeroLengthData.gzipped(), zeroLengthData)
XCTAssertEqual(try! zeroLengthData.gunzipped(), zeroLengthData)
XCTAssertFalse(zeroLengthData.isGzipped)
}
func testWrongUngzip() {
// data not compressed
let data = "testString".data(using: .utf8)!
var uncompressed: Data?
do {
uncompressed = try data.gunzipped()
@ -82,46 +89,53 @@ class GzipTests: XCTestCase {
}
XCTAssertNil(uncompressed)
}
func testCompressionLevel() {
let data = String.lorem(length: 100_000).data(using: .utf8)!
XCTAssertGreaterThan(try! data.gzipped(level: .bestSpeed).count,
try! data.gzipped(level: .bestCompression).count)
}
func testFileDecompression() {
let bundle = Bundle(for: type(of: self))
guard let url = bundle.url(forResource: "test.txt", withExtension: "gz") else { return }
let url = URL(fileURLWithPath: "./Tests/test.txt.gz")
let data = try! Data(contentsOf: url)
let uncompressed = try! data.gunzipped()
XCTAssertEqual(String(data: uncompressed, encoding: .utf8), "test")
}
}
private extension String {
/// Generate random letters string for test.
static func lorem(length: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var string = ""
for _ in 0..<length {
let rand = Int(arc4random_uniform(UInt32(letters.count)))
let index = letters.index(letters.startIndex, offsetBy: rand)
let character = letters[index]
string.append(character)
}
return string
func random(_ upperBound: Int) -> Int {
#if os(Linux)
srandom(UInt32(time(nil)))
return Int(random(upperBound))
#else
return Int(arc4random_uniform(UInt32(upperBound)))
#endif
}
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var string = ""
for _ in 0..<length {
let rand = random(letters.count)
let index = letters.index(letters.startIndex, offsetBy: rand)
let character = letters[index]
string.append(character)
}
return string
}
}