Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update codec coverage #245

Merged
merged 18 commits into from
Dec 10, 2024
26 changes: 26 additions & 0 deletions Codec/Sources/Codec/JamEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ private class EncodeContext: Encoder {
}
}

fileprivate func encodeOptional(_ value: Encodable) throws {
let mirror = Mirror(reflecting: value)
if let someValue = mirror.children.first?.value as? Encodable {
data.append(UInt8(1)) // Encode presence flag
try encode(someValue) // Encode the unwrapped value
} else {
data.append(UInt8(0)) // Encode absence flag
}
}

fileprivate func encode(_ value: some Encodable) throws {
if let value = value as? Data {
encodeData(value, lengthPrefix: true)
Expand All @@ -94,6 +104,8 @@ private class EncodeContext: Encoder {
encodeData(value.data, lengthPrefix: false)
} else if let value = value as? [Encodable] {
try encodeArray(value)
} else if Mirror(reflecting: value).displayStyle == .optional {
try encodeOptional(value)
} else {
try value.encode(to: self)
}
Expand Down Expand Up @@ -489,6 +501,20 @@ private struct JamSingleValueEncodingContainer: SingleValueEncodingContainer {
encoder.encodeInt(value)
}

mutating func encode(_: Double) throws {
throw EncodingError.invalidValue(
Double.self,
EncodingError.Context(codingPath: codingPath, debugDescription: "Double is not supported")
)
}

mutating func encode(_: Float) throws {
throw EncodingError.invalidValue(
Float.self,
EncodingError.Context(codingPath: codingPath, debugDescription: "Float is not supported")
)
}

mutating func encode(_ value: some Encodable) throws {
try encoder.encode(value)
}
Expand Down
236 changes: 236 additions & 0 deletions Codec/Tests/CodecTests/DecoderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import Foundation
import Testing

@testable import Codec

struct DecoderTests {
@Test func decodeOverflowLength() throws {
let overflowLength = UInt64.max
let lengthData = Data([241]) + withUnsafeBytes(of: overflowLength) { Data($0) }
let data = Data(repeating: 0, count: 8)
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(Data.self, from: lengthData + data)
}
}

@Test func decodeUnsupportedType() throws {
struct UnsupportedType: Codable {}
let unsupportedEncodedData = Data([0])
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(UnsupportedType.self, from: unsupportedEncodedData)
}
}

@Test func decodeCorruptedString() throws {
let invalidUTF8Data = Data([0x02, 0xC3, 0x28])
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(String.self, from: invalidUTF8Data)
}
}

@Test func decodeCorruptedNumericData() throws {
let corruptedNumericData = Data([0xFF, 0xFF, 0xFF, 0xFF])
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(Int.self, from: corruptedNumericData)
}
}

@Test func decodeIncorrectEncoding() throws {
let incorrectEncodedData = Data([255, 255, 255])
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(Int.self, from: incorrectEncodedData)
}
}

@Test func decodeInvalidKeyedContainer() throws {
let invalidKeyedData = Data([1, 1, 0, 0, 0, 0, 0, 0, 0])
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode([String: Int].self, from: invalidKeyedData)
}
}

@Test func decodeInvalidUnkeyedContainer() throws {
let invalidUnkeyedData = Data([5, 104, 101, 108])
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode([Int].self, from: invalidUnkeyedData)
}
struct UnkeyedDouble: Codable {
var doubleValue: Double
}
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(UnkeyedDouble.self, from: invalidUnkeyedData)
}
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(UnkeyedDouble?.self, from: invalidUnkeyedData)
}
struct UnkeyedFloat: Codable {
var floatValue: Float
}
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(UnkeyedFloat?.self, from: invalidUnkeyedData)
}
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(UnkeyedFloat.self, from: invalidUnkeyedData)
}
}

@Test func decodeCorruptedNestedStructure() throws {
let corruptedEncodedData = Data([2, 3, 0, 1, 2])
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode([String: [Int]].self, from: corruptedEncodedData)
}
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode([String: [Int]]?.self, from: corruptedEncodedData)
}
}

@Test func decodeEmptyDataForArray() throws {
let emptyData = Data()
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode([Int].self, from: emptyData)
}
}

@Test func decodeUnsupportedArrayFormat() throws {
let unsupportedArrayFormat = Data([5, 0, 0, 0, 0, 0, 0, 0])
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode([Int].self, from: unsupportedArrayFormat)
}
}

@Test func decodeLargeArray() throws {
let maxLength = 0xFFFF_FFFF
let encoded = try JamEncoder.encode(maxLength)
var data = Data()
data.append(contentsOf: encoded)
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode([Int].self, from: Data(data + Data(repeating: 0, count: maxLength)))
}
}

@Test func decodeInvalidInt() throws {
let encoded16 = try JamEncoder.encode(0xFFFF_FFFF)
#expect(throws: Error.self) {
_ = try JamDecoder.decode(UInt16.self, from: encoded16)
}

let maxLength: UInt64 = 0x1_0000_0000
let encoded = try JamEncoder.encode(maxLength)
#expect(encoded.count == 8)
let decoded = try JamDecoder.decode(UInt64.self, from: encoded)
#expect(decoded == maxLength)
let lengthData = Data([241, 0, 0, 0, 0, 0, 0, 0])
let data = Data(repeating: 0, count: Int(maxLength))
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode(Data.self, from: lengthData + data)
}
#expect(throws: DecodingError.self) {
_ = try JamDecoder.decode([UInt8].self, from: lengthData + data)
}
}

@Test func decodeData() throws {
let encodedData = Data([3, 0, 1, 2])
let decoded = try JamDecoder.decode(Data.self, from: encodedData)

#expect(decoded == Data([0, 1, 2]))
}

@Test func decodeBool() throws {
let encodedTrue = Data([1])
let encodedFalse = Data([0])

let decodedTrue = try JamDecoder.decode(Bool.self, from: encodedTrue)
let decodedFalse = try JamDecoder.decode(Bool.self, from: encodedFalse)

#expect(decodedTrue == true)
#expect(decodedFalse == false)
}

@Test func decodeString() throws {
let encoded = Data([5, 104, 101, 108, 108, 111])
let decoded = try JamDecoder.decode(String.self, from: encoded)
#expect(decoded == "hello")

#expect(throws: Error.self) {
_ = try JamDecoder.decode(String.self, from: Data([6, 104, 101, 108, 108, 111]))
}
}

@Test func decodeArray() throws {
let encoded = Data([3, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0])
let decoded = try JamDecoder.decode([Int].self, from: encoded)

#expect(decoded == [1, 2, 3])
}

@Test func decodeInt() throws {
let encoded = Data([1, 0, 0, 0, 0, 0, 0, 0])
let decoded = try JamDecoder.decode(Int.self, from: encoded)

#expect(decoded == 1)
}

@Test func decodeOptional() throws {
let encodedSome = Data([1, 1, 0, 0, 0, 0, 0, 0, 0])
let encodedNone = Data([0])

let decodedSome = try JamDecoder.decode(Int?.self, from: encodedSome)
let decodedNone = try JamDecoder.decode(Int?.self, from: encodedNone)

#expect(decodedSome == .some(1))
#expect(decodedNone == .none)
}

@Test func decodeFixedWidthInteger() throws {
var encodedInt8 = Data([251])
let encodedUInt64 = Data([21, 205, 91, 7, 0, 0, 0, 0])

let decodedInt8 = try JamDecoder.decode(Int8.self, from: encodedInt8)
let decodedUInt64 = try JamDecoder.decode(UInt64.self, from: encodedUInt64)

#expect(decodedInt8 == -5)
#expect(decodedUInt64 == 123_456_789)
#expect(throws: Error.self) {
_ = try encodedInt8.read(length: 8)
}
}

@Test func decodeInvalidData() throws {
let invalidEncodedData = Data([0, 0, 0, 123])
#expect(throws: Error.self) {
_ = try JamDecoder.decode(Int8.self, from: invalidEncodedData)
}
#expect(throws: Error.self) {
_ = try JamDecoder.decode(Double.self, from: Data())
}
#expect(throws: Error.self) {
_ = try JamDecoder.decode(Float.self, from: Data())
}
#expect(throws: Error.self) {
_ = try JamDecoder.decode(Int?.self, from: Data())
}
#expect(throws: Error.self) {
_ = try JamDecoder.decode(Int?.self, from: Data([2]))
}
#expect(throws: Error.self) {
_ = try JamDecoder.decode(String.self, from: Data([1, 2, 3]))
}
#expect(throws: Error.self) {
_ = try JamDecoder.decode([Int].self, from: Data([21, 205, 91, 7, 0, 0, 0, 0]))
}
#expect(throws: Error.self) {
_ = try JamDecoder.decode([Data].self, from: Data([21, 205, 91, 7, 0, 0, 0, 0]))
}
#expect(throws: Error.self) {
_ = try JamDecoder.decode(Data.self, from: Data([21, 205, 91, 7, 0, 0, 0, 0]))
}
}

@Test func decodeEmptyString() throws {
let invalidEncodedData = Data()
#expect(throws: Error.self) {
_ = try JamDecoder.decode(String.self, from: invalidEncodedData)
}
}
}
92 changes: 92 additions & 0 deletions Codec/Tests/CodecTests/EncodeSizeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import Foundation
import Testing

@testable import Codec

extension Int: EncodedSize, @retroactive Error {
public var encodedSize: Int {
MemoryLayout<Int>.size
}

public static var encodeedSizeHint: Int? {
MemoryLayout<Int>.size
}
}

struct EncodeSizeTests {
@Test
func encodeFixedWidthInteger() throws {
#expect(Int(42).encodedSize == MemoryLayout<Int>.size)
#expect(Int8(-5).encodedSize == MemoryLayout<Int8>.size)
#expect(UInt32(123_456).encodedSize == MemoryLayout<UInt32>.size)
#expect(Int.encodeedSizeHint == MemoryLayout<Int>.size)
#expect(Int8.encodeedSizeHint == MemoryLayout<Int8>.size)
#expect(UInt32.encodeedSizeHint == MemoryLayout<UInt32>.size)
}

@Test
func encodeBool() throws {
#expect(true.encodedSize == 1)
#expect(false.encodedSize == 1)
#expect(Bool.encodeedSizeHint == 1)
}

@Test
func encodeStringAndData() throws {
#expect("test".encodedSize == 4)
#expect("".encodedSize == 0)
#expect(Data([0x01, 0x02, 0x03]).encodedSize == 4)
#expect(Data().encodedSize == 1)
#expect(String.encodeedSizeHint == nil)
#expect(Data.encodeedSizeHint == nil)
}

@Test
func encodeArrayAndSet() throws {
let intArray = [1, 2, 3]
let emptyArray: [Int] = []
let intSet: Set<Int> = [4, 5, 6]
let emptySet: Set<Int> = []

#expect(intArray.encodedSize == UInt32(3).variableEncodingLength() + 3 * MemoryLayout<Int>.size)
#expect(emptyArray.encodedSize == UInt32(0).variableEncodingLength())
#expect(intSet.encodedSize >= UInt32(3).variableEncodingLength())
#expect(emptySet.encodedSize == UInt32(0).variableEncodingLength())
#expect([Int].encodeedSizeHint == nil)
#expect(Set<Int>.encodeedSizeHint == nil)
}

@Test
func encodeDictionary() throws {
let dict: [Int: String] = [1: "one", 2: "two"]
let emptyDict: [Int: String] = [:]

let expectedSize = UInt32(2).variableEncodingLength() +
1.encodedSize + "one".encodedSize +
1.encodedSize + "two".encodedSize

#expect(dict.encodedSize == expectedSize)
#expect(emptyDict.encodedSize == UInt32(0).variableEncodingLength())
#expect([Int: String].encodeedSizeHint == nil)
}

@Test
func encodeOptional() throws {
let someValue: Int? = 42
let noneValue: Int? = nil

#expect(someValue.encodedSize == 1 + MemoryLayout<Int>.size)
#expect(noneValue.encodedSize == 1)
#expect(Int?.encodeedSizeHint == nil)
}

@Test
func encodeResult() throws {
let successResult: Result<String, Int> = .success("OK")
let failureResult: Result<String, Int> = .failure(404)

#expect(successResult.encodedSize == 1 + "OK".encodedSize)
#expect(failureResult.encodedSize == 1 + MemoryLayout<Int>.size)
#expect(Result<String, Int>.encodeedSizeHint == nil)
}
}
Loading