From f21f36c701e25a1232b7a6db90ecae765f43c4db Mon Sep 17 00:00:00 2001 From: Charlotte Tortorella Date: Mon, 30 Jan 2017 22:51:05 +1100 Subject: [PATCH] Add decodeFile(_:) --- .../xcschemes/BencodeKit.xcscheme | 3 +- Sources/Bencode.swift | 26 ++++--- Sources/Types/Dictionary.swift | 2 +- Sources/Types/Integer.swift | 2 +- Sources/Types/List.swift | 4 +- Sources/Types/String.swift | 2 +- Tests/Tests.swift | 67 +++++++++++++------ 7 files changed, 72 insertions(+), 34 deletions(-) diff --git a/BencodeKit.xcodeproj/xcshareddata/xcschemes/BencodeKit.xcscheme b/BencodeKit.xcodeproj/xcshareddata/xcschemes/BencodeKit.xcscheme index 074dcdf..e508efd 100644 --- a/BencodeKit.xcodeproj/xcshareddata/xcschemes/BencodeKit.xcscheme +++ b/BencodeKit.xcodeproj/xcshareddata/xcschemes/BencodeKit.xcscheme @@ -26,7 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> diff --git a/Sources/Bencode.swift b/Sources/Bencode.swift index 9abf9f0..63afeed 100644 --- a/Sources/Bencode.swift +++ b/Sources/Bencode.swift @@ -8,6 +8,7 @@ public enum BencodingError: Error { case emptyString + case nonExistentFile(String) case unexpectedCharacter(Character, Data.Index) case negativeZeroEncountered(Data.Index) case invalidStringLength(Data.Index) @@ -20,6 +21,7 @@ public enum BencodingError: Error { case nonStringDictionaryKey(Data.Index) case nonAsciiDictionaryKey(Data.Index) case unterminatedDictionary(Data.Index) + case nonAsciiString } public indirect enum Bencode: Equatable { @@ -32,10 +34,8 @@ public indirect enum Bencode: Equatable { self = .integer(integer) } - init?(_ string: String) { - guard let value = string.data(using: .ascii) else { - return nil - } + init(_ string: String) throws { + guard let value = string.data(using: .ascii) else { throw BencodingError.nonAsciiString } self = .bytes(value) } @@ -51,16 +51,17 @@ public indirect enum Bencode: Equatable { self = .dictionary(dictionary) } - var description: String { + func stringRepresentation() throws -> String { switch self { case .integer(let integer): return "i\(String(integer))e" case .bytes(let bytes): - return "\(bytes.count):\(String(bytes: bytes, encoding: .ascii) ?? "")" + guard let string = String(bytes: bytes, encoding: .ascii) else { throw BencodingError.nonAsciiString } + return "\(string.characters.count):\(string)" case .list(let list): - return "l\(list.map({ $0.description }).joined())e" + return try "l\(list.map({ try $0.stringRepresentation() }).joined())e" case .dictionary(let dictionary): - return "d\(dictionary.map({ "\($0.characters.count):\($0)\($1.description)" }).joined())e" + return try "d\(dictionary.map({ "\($0.characters.count):\($0)\(try $1.stringRepresentation())" }).joined())e" } } } @@ -71,6 +72,15 @@ public extension Bencode { } } +public extension Bencode { + public static func decodeFile(atPath path: String) throws -> Bencode { + guard let data = FileManager.default.contents(atPath: path) else { + throw BencodingError.nonExistentFile(path) + } + return try decode(data) + } +} + public extension Bencode { public func encoded() -> Data { switch self { diff --git a/Sources/Types/Dictionary.swift b/Sources/Types/Dictionary.swift index af7f71c..240b443 100644 --- a/Sources/Types/Dictionary.swift +++ b/Sources/Types/Dictionary.swift @@ -6,7 +6,7 @@ // Copyright © 2017 Monadic Consulting. All rights reserved. // -func bdecodeDictionary(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) { +internal func bdecodeDictionary(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) { guard data[index] == "d" else { throw BencodingError.invalidDictionary(index) } diff --git a/Sources/Types/Integer.swift b/Sources/Types/Integer.swift index 672fccc..095ba66 100644 --- a/Sources/Types/Integer.swift +++ b/Sources/Types/Integer.swift @@ -6,7 +6,7 @@ // Copyright © 2017 Monadic Consulting. All rights reserved. // -func bdecodeInteger(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) { +internal func bdecodeInteger(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) { guard data[index] == "i" else { throw BencodingError.invalidInteger(index) } diff --git a/Sources/Types/List.swift b/Sources/Types/List.swift index 296f0c2..c00839d 100644 --- a/Sources/Types/List.swift +++ b/Sources/Types/List.swift @@ -6,14 +6,14 @@ // Copyright © 2017 Monadic Consulting. All rights reserved. // -func bdecodeList(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) { +internal func bdecodeList(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) { guard data[index] == "l" else { throw BencodingError.invalidList(index) } var currentIndex = data.index(after: index) guard currentIndex != data.endIndex else { - throw BencodingError.unterminatedDictionary(index) + throw BencodingError.unterminatedList(index) } var values: [Bencode] = [] diff --git a/Sources/Types/String.swift b/Sources/Types/String.swift index c7e5c13..e816d6a 100644 --- a/Sources/Types/String.swift +++ b/Sources/Types/String.swift @@ -15,7 +15,7 @@ internal func bdecodeString(_ data: Data, _ index: Data.Index) throws -> (match: let restIndex = data.index(after: colonIndex) let lengthString = String(bytes: data.subdata(in: Range(uncheckedBounds: (index, colonIndex))), encoding: .ascii) - guard let length = lengthString.flatMap({UInt($0)}).map({ Int($0) }) else { + guard let length = lengthString.flatMap({ UInt($0) }).map({ Int($0) }) else { throw BencodingError.invalidStringLength(index) } diff --git a/Tests/Tests.swift b/Tests/Tests.swift index f85e619..bb384d1 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -12,37 +12,50 @@ import XCTest class BencodeKitTests: XCTestCase { + func testDecoding() { + expectException("") + expectException("k") + _ = Bencode("hello".asciiData) + XCTAssertNotEqual(Bencode(0), try! Bencode("4:null")) + expectException(try Bencode.decodeFile(atPath: "")) + } + func testStringParsing() { - let data = "12:123456789123".data(using: .ascii)! - let (match, _) = try! bdecodeString(data, data.startIndex) - print(match.description) -// testParsing("12:123456789123", .bytes("123456789123")) + testParsing("12:123456789123", try! Bencode("123456789123")) - expectException("-13:40") - expectException("-13:40") - expectException("-13:40") - expectException("-13:40") + expectException("-13:40", bdecodeString) expectException("4:lkd") + expectException("4e:lkd") + expectException("i0e", bdecodeString) } func testListParsing() { - testParsing("li-123456789e4:nulle", Bencode([.integer(-123456789), Bencode("null")!])) + testParsing("li-123456789e4:nulle", try! Bencode([.integer(-123456789), Bencode("null")])) compareToReencoding("li-123456789e4:nulle") + expectException("l") + expectException("li0e") + expectException("i0e", bdecodeList) } func testDictionaryParsing() { + _ = Bencode([("", Bencode(0))]) testParsing("d4:nulli-123456789e2:hilee", .dictionary([("null", .integer(-123456789)), ("hi", .list([]))])) + testParsing("de", .dictionary([])) compareToReencoding("d4:nulli-123456789e2:hilee") + compareToReencoding("de") + compareToReencoding("d5:hello5:theree") expectException("d") + expectException("di0e4:nulle") + expectException("d4:nulli0e") + expectException("i0e", bdecodeDictionary) } func testIntegerParsing() { - let data = "i-123456789e".data(using: .ascii)! - print(try! bdecodeInteger(data, data.startIndex)) - + _ = Bencode(0) testParsing("i-123456789e", .integer(-123456789)) - + compareToReencoding("i-123456789e") + expectException("de", bdecodeInteger) expectException("ie") expectException("i-0e") expectException("ioe") @@ -52,31 +65,45 @@ class BencodeKitTests: XCTestCase { } func testTorrentFiles() { - Bundle(for: type(of: self)) - .paths(forResourcesOfType: "torrent", inDirectory: "Torrents") + let filePaths = Bundle(for: type(of: self)).paths(forResourcesOfType: "torrent", inDirectory: "Torrents") + filePaths .flatMap(FileManager.default.contents) .forEach { encoded in let decoded = try! Bencode.decode(encoded) - print(decoded.description) let reEncoded = decoded.encoded() XCTAssertEqual(encoded, reEncoded) + _ = try! decoded.stringRepresentation() + } + filePaths + .map { try! Bencode.decodeFile(atPath: $0) } + .forEach { decoded in + _ = try! decoded.stringRepresentation() } } } func compareToReencoding(_ param: String) { let data = param.asciiData - let returnedData = try! Bencode.decode(data).encoded() - XCTAssertEqual(returnedData, data) + let decoded = try! Bencode.decode(data) + XCTAssertEqual(data, decoded.encoded()) + XCTAssertEqual(param, try! decoded.stringRepresentation()) } func testParsing(_ param: String, _ compareTo: Bencode) { XCTAssertEqual(try! Bencode.decode(param.asciiData), compareTo) } -func expectException(_ param: String) { +func expectException(_ param: String, _ f: (Data, Data.Index) throws -> (Bencode, Data.Index) = { data, index in try (Bencode.decode(data), 0) }) { + do { + let data = param.asciiData + _ = try f(data, data.startIndex) + XCTFail() + } catch {} +} + +func expectException(_ f: @autoclosure () throws -> T) { do { - _ = try Bencode.decode(param.asciiData) + _ = try f() XCTFail() } catch {} }