diff --git a/Networking/Tests/NetworkingTests/PeerTests.swift b/Networking/Tests/NetworkingTests/PeerTests.swift index 96bcaceb..555113d0 100644 --- a/Networking/Tests/NetworkingTests/PeerTests.swift +++ b/Networking/Tests/NetworkingTests/PeerTests.swift @@ -137,11 +137,12 @@ struct PeerTests { presistentStreamHandler: MockPresentStreamHandler(), ephemeralStreamHandler: MockEphemeralStreamHandler(), serverSettings: .defaultSettings, - clientSettings: .defaultSettings + clientSettings: .defaultSettings, + peerSettings: PeerSettings(maxBuilderConnections: 3) ) ) - // Create 30 peer nodes - for _ in 0 ..< 30 { + // Create 5 peer nodes + for _ in 0 ..< 5 { let handler = MockPresentStreamHandler() handlers.append(handler) let peer = try Peer( @@ -160,20 +161,20 @@ struct PeerTests { } // Make some connections - for i in 0 ..< 30 { + for i in 0 ..< 5 { let peer = peers[i] let con = try peer.connect(to: centerPeer.listenAddress(), role: .builder) try await con.ready() } - // Simulate close connections 5~8s - try? await Task.sleep(for: .milliseconds(8000)) - centerPeer.broadcast(kind: .uniqueA, message: .init(kind: .uniqueA, data: Data("connection rotation strategy".utf8))) + // Simulate close connections 1~3s try? await Task.sleep(for: .milliseconds(1000)) + centerPeer.broadcast(kind: .uniqueA, message: .init(kind: .uniqueA, data: Data("connection rotation strategy".utf8))) + try? await Task.sleep(for: .milliseconds(100)) var receivedCount = 0 for handler in handlers { receivedCount += await handler.receivedData.count } - #expect(receivedCount == PeerSettings.defaultSettings.maxBuilderConnections) + #expect(receivedCount == 3) } @Test diff --git a/Utils/Sources/Utils/JSON.swift b/Utils/Sources/Utils/JSON.swift index f8d3be8f..e18bf595 100644 --- a/Utils/Sources/Utils/JSON.swift +++ b/Utils/Sources/Utils/JSON.swift @@ -12,7 +12,7 @@ import Foundation -public indirect enum JSON: Codable, Equatable { +public indirect enum JSON: Codable, Equatable, Sendable { case dictionary([String: JSON]) case array([JSON]) case string(String) @@ -155,11 +155,6 @@ extension JSON { stringValue = value.description } - init(_ value: String) { - intValue = nil - stringValue = value - } - init?(intValue: Int) { self.init(intValue) } diff --git a/Utils/Sources/Utils/Merklization/MMR.swift b/Utils/Sources/Utils/Merklization/MMR.swift index 87b2e8c0..ec459130 100644 --- a/Utils/Sources/Utils/Merklization/MMR.swift +++ b/Utils/Sources/Utils/Merklization/MMR.swift @@ -1,6 +1,5 @@ import Codec -// TODO: add tests // Merkle Mountain Range public struct MMR: Sendable, Equatable, Codable { public var peaks: [Data32?] diff --git a/Utils/Sources/Utils/Merklization/Merklization.swift b/Utils/Sources/Utils/Merklization/Merklization.swift index d069955b..dcfdce44 100644 --- a/Utils/Sources/Utils/Merklization/Merklization.swift +++ b/Utils/Sources/Utils/Merklization/Merklization.swift @@ -1,6 +1,5 @@ import Foundation -// TODO: add tests public enum Merklization { // roundup of half private static func half(_ i: Int) -> Int { diff --git a/Utils/Sources/Utils/SortedContainer/SortedUniqueArray.swift b/Utils/Sources/Utils/SortedContainer/SortedUniqueArray.swift index 3e15cda2..205c07dc 100644 --- a/Utils/Sources/Utils/SortedContainer/SortedUniqueArray.swift +++ b/Utils/Sources/Utils/SortedContainer/SortedUniqueArray.swift @@ -1,4 +1,3 @@ -// TODO: add tests public struct SortedUniqueArray: SortedContainer { public private(set) var array: [T] @@ -35,7 +34,7 @@ public struct SortedUniqueArray: SortedContainer { var begin = 0 for element in other.array { let idx = insertIndex(element, begin: begin) - if idx > array.count || array[idx] != element { + if idx == array.count || array[idx] != element { array.insert(element, at: idx) } begin = idx + 1 diff --git a/Utils/Sources/Utils/debugCheck.swift b/Utils/Sources/Utils/debugCheck.swift index e9d1d302..18fc40bc 100644 --- a/Utils/Sources/Utils/debugCheck.swift +++ b/Utils/Sources/Utils/debugCheck.swift @@ -1,38 +1,38 @@ import Foundation -public enum AssertError: Error { - case assertionFailed +public enum DebugCheckError: Error { + case assertionFailed(String, file: StaticString, line: UInt) + case unexpectedError(Error, file: StaticString, line: UInt) } public func debugCheck( _ condition: @autoclosure () throws -> Bool, file: StaticString = #file, line: UInt = #line -) { +) throws { #if DEBUG_ASSERT - let res = Result { try condition() } - switch res { - case let .success(res): - if !res { - fatalError(file: file, line: line) + let result = Result { try condition() } + switch result { + case let .success(isValid): + if !isValid { + throw DebugCheckError.assertionFailed("Assertion failed", file: file, line: line) } - case let .failure(err): - fatalError("\(err)", file: file, line: line) + case let .failure(error): + throw DebugCheckError.unexpectedError(error, file: file, line: line) } #endif } public func debugCheck( - _ condition: @autoclosure () async throws -> Bool, file: StaticString = #file, - line: UInt = #line -) async { + _ condition: @autoclosure () async throws -> Bool, file: StaticString = #file, line: UInt = #line +) async throws { #if DEBUG_ASSERT - let res = await Result { try await condition() } - switch res { - case let .success(res): - if !res { - fatalError(file: file, line: line) + let result = await Result { try await condition() } + switch result { + case let .success(isValid): + if !isValid { + throw DebugCheckError.assertionFailed("Assertion failed", file: file, line: line) } - case let .failure(err): - fatalError("\(err)", file: file, line: line) + case let .failure(error): + throw DebugCheckError.unexpectedError(error, file: file, line: line) } #endif } diff --git a/Utils/Tests/UtilsTests/ConfigLimitedSizeArrayTest.swift b/Utils/Tests/UtilsTests/ConfigLimitedSizeArrayTest.swift index 70519112..4d62ecc8 100644 --- a/Utils/Tests/UtilsTests/ConfigLimitedSizeArrayTest.swift +++ b/Utils/Tests/UtilsTests/ConfigLimitedSizeArrayTest.swift @@ -4,6 +4,14 @@ import Testing @testable import Utils +struct MinLengthNegated: ReadInt { + typealias TConfig = Int + + static func read(config _: Int) -> Int { + -1 + } +} + struct MinLength3: ReadInt { typealias TConfig = Int @@ -20,13 +28,24 @@ struct MaxLength5: ReadInt { } } +struct MaxLength8: ReadInt { + typealias TConfig = Int + + static func read(config _: Int) -> Int { + 8 + } +} + struct ConfigLimitedSizeArrayTests { @Test func initWithDefaultValue() throws { let config = 0 let defaultValue = 1 - let array = try ConfigLimitedSizeArray(config: config, defaultValue: defaultValue) + var array = try ConfigLimitedSizeArray(config: config, defaultValue: defaultValue) #expect(array.array == [1, 1, 1]) #expect(array.count == 3) + #expect(array[0] == 1) + array[0] = 0 + #expect(array[0] != 1) } @Test func initWithArrayWithinBounds() throws { @@ -141,4 +160,45 @@ struct ConfigLimitedSizeArrayTests { let decoded = try JamDecoder.decode(ConfigLimitedSizeArray.self, from: encoded, withConfig: config) #expect(decoded == array) } + + @Test func throwLength() throws { + #expect(throws: Error.self) { + _ = try ConfigLimitedSizeArray(config: 0, array: [1, 2, 3]) + } + #expect(throws: Error.self) { + _ = try ConfigLimitedSizeArray(config: 0, array: [1, 2, 3]) + } + } + + @Test func randomAccessCollection() throws { + let value = try ConfigLimitedSizeArray(config: 0, array: [1, 2, 3, 4, 5, 6, 7, 8]) + #expect(value.startIndex == 0) + #expect(value.endIndex == 8) + + var idx = value.startIndex + value.formIndex(after: &idx) + #expect(idx == 1) + + value.formIndex(before: &idx) + #expect(idx == 0) + + let dist = value.distance(from: 0, to: 7) + #expect(dist == 7) + + let indexForward = value.index(0, offsetBy: 3) + #expect(indexForward == 3) + + let indexWithinLimit = value.index(0, offsetBy: 3, limitedBy: 5) + #expect(indexWithinLimit == 3) + #expect(value.index(after: indexWithinLimit!) == 4) + #expect(value.index(before: indexWithinLimit!) == 2) + #expect(value.index(from: indexWithinLimit!) == 3) + } + + @Test func description() throws { + let config = 0 + let array = try ConfigLimitedSizeArray(config: config, array: [1, 2, 3, 4, 5]) + #expect(array.description == "[1, 2, 3, 4, 5]") + #expect(array.debugDescription == "ConfigLimitedSizeArray([1, 2, 3, 4, 5])") + } } diff --git a/Utils/Tests/UtilsTests/ConfigSizeBitStringTests.swift b/Utils/Tests/UtilsTests/ConfigSizeBitStringTests.swift index 074c57de..abbda86a 100644 --- a/Utils/Tests/UtilsTests/ConfigSizeBitStringTests.swift +++ b/Utils/Tests/UtilsTests/ConfigSizeBitStringTests.swift @@ -113,4 +113,44 @@ struct ConfigSizeBitStringTests { value[19] = true #expect(value == value3) } + + @Test func encodedSizeTests() throws { + let data = Data([0b1011_0101, 0b1100_0101, 0b0000_0110]) + let length = 20 + let value = try ConfigSizeBitString(config: length, data: data) + + #expect(value.encodedSize == data.count) + #expect(ConfigSizeBitString.encodeedSizeHint == nil) + } + + @Test func randomAccessCollection() throws { + var value = ConfigSizeBitString(config: 8) + let result = value.withPtr { ptr in + ptr.reduce(0, +) + } + #expect(result == 0) + + #expect(value.startIndex == 0) + #expect(value.endIndex == 8) + + value[7] = true + let collected = Array(value) + #expect(collected == [false, false, false, false, false, false, false, true]) + + var idx = value.startIndex + value.formIndex(after: &idx) + #expect(idx == 1) + + value.formIndex(before: &idx) + #expect(idx == 0) + + let dist = value.distance(from: 0, to: 7) + #expect(dist == 7) + + let indexForward = value.index(0, offsetBy: 3) + #expect(indexForward == 3) + + let indexWithinLimit = value.index(0, offsetBy: 3, limitedBy: 5) + #expect(indexWithinLimit == 3) + } } diff --git a/Utils/Tests/UtilsTests/ConstValueTests.swift b/Utils/Tests/UtilsTests/ConstValueTests.swift new file mode 100644 index 00000000..d440ead0 --- /dev/null +++ b/Utils/Tests/UtilsTests/ConstValueTests.swift @@ -0,0 +1,22 @@ +import Testing + +@testable import Utils + +struct ConstIntTests { + @Test + func constIntValues() { + #expect(ConstInt0.value == 0) + #expect(ConstInt1.value == 1) + #expect(ConstInt2.value == 2) + #expect(ConstInt3.value == 3) + #expect(ConstIntMax.value == Int.max) + #expect(ConstInt32.value == 32) + #expect(ConstInt48.value == 48) + #expect(ConstInt64.value == 64) + #expect(ConstUInt96.value == 96) + #expect(ConstUInt128.value == 128) + #expect(ConstUInt144.value == 144) + #expect(ConstUInt384.value == 384) + #expect(ConstUInt784.value == 784) + } +} diff --git a/Utils/Tests/UtilsTests/DebugCheckTests.swift b/Utils/Tests/UtilsTests/DebugCheckTests.swift new file mode 100644 index 00000000..23af9a06 --- /dev/null +++ b/Utils/Tests/UtilsTests/DebugCheckTests.swift @@ -0,0 +1,33 @@ +import Foundation +import Testing + +@testable import Utils + +struct DebugCheckTests { + func awaitThrow(_ expression: () async throws -> some Any) async throws -> Bool { + _ = try await expression() + return true + } + + func doesThrow(_ expression: () throws -> some Any) throws -> Bool { + _ = try expression() + return true + } + + @Test + func testDebugCheck() async throws { + try #expect(doesThrow { + try debugCheck(1 + 1 == 2) + } == true) + #expect(throws: DebugCheckError.self) { + try debugCheck(1 + 1 == 3) + } + try await #expect(awaitThrow { + try await debugCheck(1 + 1 == 2) + } == true) + + await #expect(throws: DebugCheckError.self) { + try await debugCheck(1 + 1 == 3) + } + } +} diff --git a/Utils/Tests/UtilsTests/EitherTests.swift b/Utils/Tests/UtilsTests/EitherTests.swift new file mode 100644 index 00000000..b9d8424e --- /dev/null +++ b/Utils/Tests/UtilsTests/EitherTests.swift @@ -0,0 +1,156 @@ +import Codec +import Foundation +import Testing + +@testable import Utils + +struct EitherTests { + struct EncodedString: EncodedSize { + let value: String + + var encodedSize: Int { + value.utf8.count + } + + static var encodeedSizeHint: Int? { + nil + } + } + + struct EncodedInt: EncodedSize { + let value: Int + + var encodedSize: Int { + MemoryLayout.size + } + + static var encodeedSizeHint: Int? { + MemoryLayout.size + } + } + + typealias MyEither = Either + typealias MyIntEither = Either + + @Test(arguments: [ + Data([0x02]), + ]) + func throwsOnUnknownVariant(data: Data) { + #expect(throws: Error.self) { + _ = try JamDecoder.decode(Either.self, from: data) + } + } + + @Test + func encodedSizeForLeft() { + let either = MyEither.left(EncodedString(value: "Hi")) + #expect(either.encodedSize == 3) + } + + @Test + func encodedSizeForRight() { + let either = MyEither.right(EncodedInt(value: 42)) + #expect(either.encodedSize == 9) + } + + @Test + func encodeedSizeHint() { + let hint = MyEither.encodeedSizeHint + #expect(hint == nil) + let IntHint = MyIntEither.encodeedSizeHint + #expect(IntHint == MemoryLayout.size * 2 + 1) + } + + @Test(arguments: [ + Either.left("Hello"), + Either.right(42), + ]) + func testAccessors(either: Either) { + if let left = either.left { + #expect(left == "Hello") + #expect(either.right == nil) + } else if let right = either.right { + #expect(right == 42) + #expect(either.left == nil) + } + } + + @Test(arguments: [ + (Either.left("Test"), Either.left("Test"), true), + (Either.right(100), Either.right(100), true), + (Either.left("A"), Either.right(100), false), + (Either.right(42), Either.right(100), false) + ]) + func equality(lhs: Either, rhs: Either, expected: Bool) { + #expect((lhs == rhs) == expected) + } + + @Test(arguments: [ + Either.left("Left Value"), + Either.right(123) + ]) + func JSONCoding(either: Either) throws { + let encoder = JSONEncoder() + let data = try encoder.encode(either) + let decoder = JSONDecoder() + let decoded = try decoder.decode(Either.self, from: data) + #expect(decoded == either) + } + + @Test(arguments: [ + Either.left("Jam Codec Left"), + Either.right(456), + ]) + func JamCoding(either: Either) throws { + let encoded = try JamEncoder.encode(either) + let decoder = JamDecoder(data: encoded) + let decoded = try decoder.decode(Either.self) + #expect(decoded == either) + } + + @Test(arguments: [ + (Either.left("Describe Me"), "Left(Describe Me)"), + (Either.right(789), "Right(789)"), + ]) + func description(either: Either, expected: String) { + #expect(either.description == expected) + } + + @Test(arguments: [ + Either.left(42), + Either.right("hello"), + ]) + func maybeEitherInit(either: Either) { + let maybe = MaybeEither(either) + #expect(maybe.value == either) + } + + @Test(arguments: [ + 42, + -1, + 1000, + ]) + func maybeEitherInitLeft(left: Int) { + let maybe = MaybeEither(left: left) + #expect(maybe.value == .left(left)) + } + + @Test(arguments: [ + "hello", + "world", + "", + ]) + func maybeEitherInitRight(right: String) { + let maybe = MaybeEither(right: right) + #expect(maybe.value == .right(right)) + } + + @Test(arguments: [ + Either.left(42), + Either.right(99), + ]) + func testMaybeEitherUnwrapped(either: Either) { + let maybe = MaybeEither(either) + #expect(maybe.unwrapped == (either == .left(42) ? 42 : 99)) + } +} diff --git a/Utils/Tests/UtilsTests/Extensions/CollectionTests.swift b/Utils/Tests/UtilsTests/Extensions/CollectionTests.swift new file mode 100644 index 00000000..34e04fbc --- /dev/null +++ b/Utils/Tests/UtilsTests/Extensions/CollectionTests.swift @@ -0,0 +1,89 @@ +import Testing + +@testable import Utils + +struct CollectionUtilsTests { + @Test func atMethods() throws { + let array = [1, 2, 3, 4, 5] + + let result1 = try array.at(relative: 2...) + #expect(result1 == [3, 4, 5]) + + let result2 = try array.at(relative: ..<3) + #expect(result2 == [1, 2, 3]) + + let result3 = try array.at(relative: ..<0) + #expect(result3.isEmpty) + + #expect(throws: Error.self) { + _ = try array.at(relative: ..<6) + } + + let result4 = try array.at(relative: ...3) + #expect(result4 == [1, 2, 3, 4]) + + let result5 = try array.at(relative: ...0) + #expect(result5 == [1]) + #expect(throws: Error.self) { + _ = try array.at(0 ..< 6) + } + } + + @Test func safeIndexAccess() throws { + let array = [10, 20, 30, 40, 50] + + #expect(array[safe: 0] == 10) + #expect(array[safe: 4] == 50) + #expect(array[safe: 5] == nil) + #expect(array[safe: -1] == nil) + } + + @Test func safeRangeAccess() throws { + let array = [10, 20, 30, 40, 50] + + #expect(array[safe: 0 ..< 2] == [10, 20]) + #expect(array[safeRelative: 3 ..< 6] == nil) + #expect(array[safe: 2 ..< 2] == []) + } + + @Test func indexAccess() throws { + let array = [10, 20, 30, 40, 50] + + let element = try array.at(2) + #expect(element == 30) + #expect(throws: Error.self) { + _ = try array.at(5) + } + } + + @Test func relativeIndexAccess() throws { + let array = [10, 20, 30, 40, 50] + #expect(array[relative: 1] == 20) + } + + @Test func relativeRangeAccess() throws { + let array = [10, 20, 30, 40, 50] + + #expect(array[relative: 0 ..< 2] == [10, 20]) + #expect(array[relative: 3 ..< 5] == [40, 50]) + #expect(array[relative: 1...] == [20, 30, 40, 50]) + #expect(array[relative: ...4] == [10, 20, 30, 40, 50]) + } + + @Test func safeRelativeIndexAccess() throws { + let array = [10, 20, 30, 40, 50] + + #expect(array[safeRelative: 1] == 20) + #expect(array[safeRelative: -1] == nil) + #expect(array[safeRelative: 10] == nil) + } + + @Test func atRelativeIndexAccess() throws { + let array = [10, 20, 30, 40, 50] + let element = try array.at(relative: 2) + #expect(element == 30) + #expect(throws: Error.self) { + _ = try array.at(relative: 10) + } + } +} diff --git a/Utils/Tests/UtilsTests/JSONTests.swift b/Utils/Tests/UtilsTests/JSONTests.swift new file mode 100644 index 00000000..31c20b00 --- /dev/null +++ b/Utils/Tests/UtilsTests/JSONTests.swift @@ -0,0 +1,119 @@ +import Foundation +import Testing + +@testable import Utils + +struct JSONTests { + @Test(arguments: [ + "{\"key\":\"value\"}", + "[\"a\",1]", + "\"test\"", + "42", + "true", + "null", + ]) + func debugDescription(jsonString: String) throws { + let decoder = JSONDecoder() + let json = try decoder.decode(JSON.self, from: jsonString.data(using: .utf8)!) + let debugDescription = json.debugDescription + let parsedJSON = try decoder.decode(JSON.self, from: debugDescription.data(using: .utf8)!) + #expect(json == parsedJSON) + } + + @Test(arguments: [ + ("{\"key\":\"value\"}", JSON.dictionary(["key": .string("value")])), + ("[\"a\",1]", JSON.array([.string("a"), .number(1)])), + ("\"test\"", JSON.string("test")), + ("42", JSON.number(42)), + ("true", JSON.boolean(true)), + ("null", JSON.null), + ]) + func decoding(jsonString: String, expectedJSON: JSON) throws { + let decoder = JSONDecoder() + let json = try decoder.decode(JSON.self, from: jsonString.data(using: .utf8)!) + #expect(json == expectedJSON) + } + + @Test(arguments: [ + JSON.dictionary(["key": .string("value")]), + JSON.array([.string("a"), .number(1)]), + JSON.string("test"), + JSON.number(42), + JSON.boolean(true), + JSON.null, + ]) + func propertyAccess(json: JSON) throws { + let isNil: [Bool] = [ + json.dictionary == nil, + json.array == nil, + json.string == nil, + json.number == nil, + json.bool == nil, + ] + + let expected: [Bool] = switch json { + case .dictionary: [false, true, true, true, true] + case .array: [true, false, true, true, true] + case .string: [true, true, false, true, true] + case .number: [true, true, true, false, true] + case .boolean: [true, true, true, true, false] + case .null: [true, true, true, true, true] + } + + #expect(isNil == expected) + } + + @Test(arguments: [ + (123, "123"), // Valid integer + (-456, "-456"), // Negative integer + (0, "0"), // Zero + ]) + func integerKeyFromInt(value: Int, expectedString: String) throws { + let key = JSON.IntegerKey(value) + #expect(key.intValue == value) + #expect(key.stringValue == expectedString) + } + + @Test(arguments: [ + ("123", 123), // Valid string + ("-456", -456), // Negative integer string + ("invalid", nil), // Non-numeric string + ]) + func integerKeyFromString(value: String, expectedInt: Int?) throws { + let key = JSON.IntegerKey(stringValue: value) + if let expected = expectedInt { + #expect(key!.intValue == expected) + #expect(key!.stringValue == value) + } else { + #expect(key == nil) // Ensure `nil` for invalid strings + } + } + + @Test(arguments: [ + (123, JSON.number(123.0)), // Integer to JSON + (-42, JSON.number(-42.0)), // Negative integer to JSON + (0, JSON.number(0.0)), // Zero to JSON + ]) + func binaryIntegerToJSON(value: Int, expectedJSON: JSON) throws { + let json = value.json + #expect(json == expectedJSON) + } + + @Test(arguments: [ + ("Hello, world!", JSON.string("Hello, world!")), // Simple string + ("", JSON.string("")), // Empty string + ]) + func stringToJSON(value: String, expectedJSON: JSON) throws { + let json = value.json + #expect(json == expectedJSON) + } + + @Test(arguments: [ + (true, JSON.boolean(true)), // Boolean true to JSON + (false, JSON.boolean(false)), // Boolean false to JSON + ]) + func boolToJSON(value: Bool, expectedJSON: JSON) throws { + let json = value.json + #expect(json == expectedJSON) + } +} diff --git a/Utils/Tests/UtilsTests/LimitedSizeArrayTest.swift b/Utils/Tests/UtilsTests/LimitedSizeArrayTest.swift index f7730349..5858f804 100644 --- a/Utils/Tests/UtilsTests/LimitedSizeArrayTest.swift +++ b/Utils/Tests/UtilsTests/LimitedSizeArrayTest.swift @@ -4,19 +4,19 @@ import Testing @testable import Utils -struct ConstInt5: ConstInt { - static let value = 5 -} +struct LimitedSizeArrayTests { + struct ConstInt5: ConstInt { + static let value = 5 + } -struct ConstInt10: ConstInt { - static let value = 10 -} + struct ConstInt10: ConstInt { + static let value = 10 + } -struct ConstInt0: ConstInt { - static let value = 0 -} + struct ConstInt0: ConstInt { + static let value = 0 + } -struct LimitedSizeArrayTests { @Test func initWithDefaultValue() throws { let defaultValue = 1 let array = LimitedSizeArray(defaultValue: defaultValue) @@ -67,4 +67,52 @@ struct LimitedSizeArrayTests { #expect(decoded == array) } + + @Test func encodedSize() throws { + struct FixedEncodedSizeType: EncodedSize { + var encodedSize: Int { 4 } + static var encodeedSizeHint: Int? { 4 } + } + + let array: LimitedSizeArray = .init( + Array(repeating: FixedEncodedSizeType(), count: 5) + ) + + #expect(array.encodedSize == 20) // 5 elements * 4 bytes each + let array1: LimitedSizeArray = .init( + Array(repeating: FixedEncodedSizeType(), count: 10) + ) + #expect(array1.encodedSize == 41) + #expect(LimitedSizeArray.encodeedSizeHint == 20) + #expect(LimitedSizeArray.encodeedSizeHint == nil) + } + + @Test func randomAccessCollection() throws { + var array: LimitedSizeArray = [1, 2, 3, 4, 5] + + #expect(array.startIndex == 0) + #expect(array.endIndex == 5) + #expect(array[array.startIndex] == 1) + #expect(array[array.index(array.startIndex, offsetBy: 2)] == 3) + + var iteratorIndex = array.startIndex + array.formIndex(after: &iteratorIndex) + #expect(iteratorIndex == 1) + array.formIndex(before: &iteratorIndex) + #expect(iteratorIndex == 0) + #expect(array.index(after: iteratorIndex) == array.index(before: iteratorIndex) + 2) + array[iteratorIndex] = 9 + #expect(array[iteratorIndex] == 9) + #expect(array.index(from: iteratorIndex) == 0) + } + + @Test func randomAccessCollectionIndexOutOfBounds() throws { + let array: LimitedSizeArray = [1, 2, 3, 4, 5] + + // Ensure accessing out of bounds throws as expected + let index = array.startIndex + let outOfBounds = array.index(array.endIndex, offsetBy: 1, limitedBy: array.endIndex) + #expect(outOfBounds == nil) + #expect(index.distance(to: array.endIndex) == array.count) + } } diff --git a/Utils/Tests/UtilsTests/Merklization/MerklizationTests.swift b/Utils/Tests/UtilsTests/Merklization/MerklizationTests.swift new file mode 100644 index 00000000..b57c706c --- /dev/null +++ b/Utils/Tests/UtilsTests/Merklization/MerklizationTests.swift @@ -0,0 +1,116 @@ +import Codec +import Foundation +import Testing + +@testable import Utils + +extension Blake2b256 { + public static func hash(_ inputs: String...) -> Data32 { + var hasher = Blake2b256() + for input in inputs { + hasher.update(Data(input.utf8)) + } + return hasher.finalize() + } +} + +struct MerklizationTests { + @Test + func testHash() throws { + var mmr = MMR([]) + let emptyHash = try JamEncoder.encode(mmr).keccakHash() + #expect(mmr.hash() == emptyHash) + } + + @Test + func binaryMerklize() { + let input: [Data] = [ + Data("node1".utf8), + Data("node2".utf8), + Data("node3".utf8), + ] + let result = Merklization.binaryMerklize(input) + print("result = \(result)") + let expected = Blake2b256.hash("node", + Blake2b256.hash("node", + Data("node1".utf8), + Data("node2".utf8)), + Data("node3".utf8)) + #expect(result == expected) + } + + @Test + func trace() { + let input: [Data] = [ + Data("node1".utf8), + Data("node2".utf8), + Data("node3".utf8), + Data("node4".utf8), + ] + let index = 2 + let result = Merklization.trace(input, index: index) + let expected: [Either] = [ + .right(Blake2b256.hash("node", Data("node3".utf8), Data("node4".utf8))), + .left(Data("node1".utf8)), + .left(Data("node2".utf8)), + ] + #expect(result == expected) + } + + @Test + func constantDepthMerklize() { + let input: [Data] = [ + Data("node1".utf8), + Data("node2".utf8), + Data("node3".utf8), + Data("node4".utf8), + ] + let result = Merklization.constantDepthMerklize(input) + + let expected = Blake2b256.hash("node", + Blake2b256.hash("node", Blake2b256.hash("leaf", "node1"), Blake2b256.hash("leaf", "node2")), + Blake2b256.hash("node", Blake2b256.hash("leaf", "node3"), Blake2b256.hash("leaf", "node4"))) + #expect(result == expected) + } + + @Test + func generateJustification() { + let input: [Data] = [ + Data("node1".utf8), + Data("node2".utf8), + Data("node3".utf8), + Data("node4".utf8), + ] + let index = 2 + let result = Merklization.generateJustification(input, index: index) + + let expected: [Data32] = [ + Blake2b256.hash("node", Blake2b256.hash("leaf", "node3"), Blake2b256.hash("leaf", "node4")), + ] + + #expect(result.first == expected.first) + } + + @Test + func emptyInput() { + let emptyInput: [Data] = [] + + let binaryResult = Merklization.binaryMerklize(emptyInput) + let constantDepthResult = Merklization.constantDepthMerklize(emptyInput) + let justificationResult = Merklization.generateJustification(emptyInput, index: 0) + + #expect(binaryResult == Data32()) + #expect(constantDepthResult == Data32()) + #expect(justificationResult.isEmpty) + } + + @Test + func singleElementInput() { + let singleInput: [Data] = [Data("single".utf8)] + let binaryResult = Merklization.binaryMerklize(singleInput) + let constantDepthResult = Merklization.constantDepthMerklize(singleInput) + let expectedHash = Blake2b256.hash("single") + #expect(binaryResult == expectedHash) + #expect(constantDepthResult == Blake2b256.hash("leaf", "single")) + } +} diff --git a/Utils/Tests/UtilsTests/SaturatingNumberTests.swift b/Utils/Tests/UtilsTests/SaturatingNumberTests.swift index 176cd1d7..7a508446 100644 --- a/Utils/Tests/UtilsTests/SaturatingNumberTests.swift +++ b/Utils/Tests/UtilsTests/SaturatingNumberTests.swift @@ -4,6 +4,42 @@ import Testing @testable import Utils struct SaturatingNumberTests { + @Test func testMoreAssignment() { + var gas = Gas(100) + gas += Gas(2) + #expect(gas == Gas(102)) + gas = Gas(100) + gas -= Gas(50) + #expect(gas == Gas(50)) + gas = Gas(100) + gas *= Gas(2) + #expect(gas == Gas(200)) + gas = Gas(200) + gas /= Gas(2) + #expect(gas == Gas(100)) + gas = Gas(200) + gas %= Gas(2) + #expect(gas == Gas(0)) + gas = Gas(200) + #expect(gas / 2 == Gas(100)) + gas = Gas(100) + gas -= 50 + #expect(gas == Gas(50)) + gas = Gas(100) + gas *= 2 + #expect(gas == Gas(200)) + gas = Gas(200) + gas /= 2 + #expect(gas == Gas(100)) + gas = Gas(200) + gas %= 2 + #expect(gas == Gas(0)) + gas = Gas(100) + gas += 2 + #expect(gas == Gas(102)) + #expect(gas % 2 == Gas(0)) + } + @Test func testAdditionWithNoOverflow() { let gas1 = Gas(100) let gas2 = Gas(200) @@ -106,4 +142,18 @@ struct SaturatingNumberTests { #expect(gas2 % gas1 == Gas(0)) #expect(gas1 % gas2 == Gas(100)) } + + @Test func testEncodedSize() { + let gas = Gas(100) + #expect(gas.encodedSize == MemoryLayout.size) + } + + @Test func testEncodeedSizeHint() { + #expect(Gas.encodeedSizeHint == MemoryLayout.size) + } + + @Test func testDescription() { + let gas = Gas(100) + #expect(gas.description == "100") + } } diff --git a/Utils/Tests/UtilsTests/SortedUniqueArrayTests.swift b/Utils/Tests/UtilsTests/SortedUniqueArrayTests.swift new file mode 100644 index 00000000..ef4fab32 --- /dev/null +++ b/Utils/Tests/UtilsTests/SortedUniqueArrayTests.swift @@ -0,0 +1,61 @@ +import Foundation +import Testing + +@testable import Utils + +struct SortedUniqueArrayTests { + @Test + func initialization() throws { + let array = SortedUniqueArray([3, 1, 2, 3, 5, 4, 2]) + #expect(array.array == [1, 2, 3, 4, 5]) + + let sortedArray = try? SortedUniqueArray(sorted: [1, 2, 3, 4, 5]) + #expect(sortedArray?.array == [1, 2, 3, 4, 5]) + + let invalidSortedArray = (try? SortedUniqueArray(sorted: [1, 1, 2, 3])) == nil + #expect(invalidSortedArray) + + let emptyArray = SortedUniqueArray() + #expect(emptyArray.array.isEmpty) + } + + @Test + func insertion() throws { + var array = SortedUniqueArray([1, 3, 5]) + + array.insert(2) + #expect(array.array == [1, 2, 3, 5]) + + array.insert(3) + #expect(array.array == [1, 2, 3, 5]) + } + + @Test + func removal() throws { + var array = SortedUniqueArray([1, 2, 3, 4, 5]) + + array.remove(at: 2) + #expect(array.array == [1, 2, 4, 5]) + + array.removeAll() + #expect(array.array.isEmpty) + } + + @Test + func encodingDecoding() throws { + let array = SortedUniqueArray([1, 2, 3, 4, 5]) + let encodedData = try JSONEncoder().encode(array) + let decodedArray = try JSONDecoder().decode(SortedUniqueArray.self, from: encodedData) + + #expect(decodedArray.array == [1, 2, 3, 4, 5]) + } + + @Test + func appending() throws { + var array = SortedUniqueArray([1, 3, 5]) + let otherArray = SortedUniqueArray([2, 4]) + + array.append(contentsOf: otherArray) + #expect(array.array == [1, 2, 3, 4, 5]) + } +} diff --git a/Utils/Tests/UtilsTests/ThreadSafeContainerTests.swift b/Utils/Tests/UtilsTests/ThreadSafeContainerTests.swift new file mode 100644 index 00000000..2e98826c --- /dev/null +++ b/Utils/Tests/UtilsTests/ThreadSafeContainerTests.swift @@ -0,0 +1,15 @@ +import Testing + +@testable import Utils + +struct ThreadSafeContainerTests { + @Test func exchangeValue() throws { + let container = ThreadSafeContainer(10) + let oldValue = container.exchange(20) + #expect(oldValue == 10) + #expect(container.value == 20) + let previousValue = container.exchange(30) + #expect(previousValue == 20) + #expect(container.value == 30) + } +}