From f9a9acf6510cef6a5b51d39db49df02152df5489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Mon, 28 Oct 2024 08:23:38 +0100 Subject: [PATCH] Rewrite of BinaryInteger+Random.swift tests (#108) (#110). --- Sources/TestKit2/Utilities+Integers.swift | 37 ++ .../BinaryInteger+Random.swift | 367 +++++++++++------- 2 files changed, 269 insertions(+), 135 deletions(-) diff --git a/Sources/TestKit2/Utilities+Integers.swift b/Sources/TestKit2/Utilities+Integers.swift index 7df7bc67..b7f0fbc0 100644 --- a/Sources/TestKit2/Utilities+Integers.swift +++ b/Sources/TestKit2/Utilities+Integers.swift @@ -9,6 +9,43 @@ import CoreKit +//*============================================================================* +// MARK: * Utilities x Integers +//*============================================================================* + +extension BinaryInteger { + + //=------------------------------------------------------------------------= + // MARK: Initializers + //=------------------------------------------------------------------------= + + @inlinable public static func minLikeSystemsInteger(size: IX) -> Optional { + guard !size.isZero, Count(size) <= Self.size else { + return nil + } + + if Self.isSigned { + return Self(repeating: Bit.one).up(Count(size - 1)) + + } else { + return Self.zero + } + } + + @inlinable public static func maxLikeSystemsInteger(size: IX) -> Optional { + guard !size.isZero, Count(size) <= Self.size else { + return nil + } + + if Self.isSigned { + return Self(repeating: Bit.one).up(Count(size - 1)).toggled() + + } else { + return Self(repeating: Bit.one).up(Count(((size)))).toggled() + } + } +} + //*============================================================================* // MARK: * Utilities x Integers x Edgy //*============================================================================* diff --git a/Tests/UltimathnumTests/BinaryInteger+Random.swift b/Tests/UltimathnumTests/BinaryInteger+Random.swift index 841c4ab1..2323af7d 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Random.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Random.swift @@ -8,61 +8,151 @@ //=----------------------------------------------------------------------------= import CoreKit -import DoubleIntKit -import InfiniIntKit import RandomIntKit -import TestKit +import TestKit2 //*============================================================================* // MARK: * Binary Integer x Random //*============================================================================* -final class BinaryIntegerTestsOnRandom: XCTestCase { +@Suite struct BinaryIntegerTestsOnRandom { //=------------------------------------------------------------------------= - // MARK: Tests x Range + // MARK: Tests x Bit Index //=------------------------------------------------------------------------= - /// - Note: The bounds may be infinite, but not their distance. - func testRandomInRange() { - func whereIs(_ type: T.Type, randomness: consuming FuzzerInt) where T: BinaryInteger { - let min = Esque.min - let max = Esque.bot - - let eigth: T = T(1 + Esque.bot / 8) - let small: Range = (T.isSigned ? -4..<3 : 0..<8) - let large: Range = (min + eigth)..<(max - eigth) + @Test( + "BinaryInteger/random: through bit index", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsBinaryInteger, fuzzers + ) func randomThroughBitIndex(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + let size = IX(size: T.self) ?? 128 + for index in 0 ..< size { + let limit = index + (T.isSigned ? 1 : 2) + let index = Shift(Count(index)) + + while true { + let random = T.random(through: index) + let entropy = random.entropy() + try #require(entropy >= Count(00001)) + try #require(entropy <= Count(limit)) + if entropy == Count(limit) { break } + } + + while true { + let random = T.random(through: index, using: &randomness) + let entropy = random.entropy() + try #require(!random.isInfinite) + try #require(entropy >= Count(00001)) + try #require(entropy <= Count(limit)) + if entropy == Count(limit) { break } + } + } + } + } + + @Test( + "BinaryInteger/random: through bit index has known bounds", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsBinaryInteger, fuzzers + ) func randomThroughBitIndexHasKnownBounds(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + try require(Shift(Count(0)), T.isSigned ? -001...000 : 000...001) + try require(Shift(Count(1)), T.isSigned ? -002...001 : 000...003) + try require(Shift(Count(2)), T.isSigned ? -004...003 : 000...007) + try require(Shift(Count(3)), T.isSigned ? -008...007 : 000...015) + try require(Shift(Count(4)), T.isSigned ? -016...015 : 000...031) + try require(Shift(Count(5)), T.isSigned ? -032...031 : 000...063) + try require(Shift(Count(6)), T.isSigned ? -064...063 : 000...127) + try require(Shift(Count(7)), T.isSigned ? -128...127 : 000...255) - func check(_ range: Range) { - let r0 = T.random(in: range) - let r1 = T.random(in: range, using: &randomness) + func require(_ index: Shift, _ expectation: ClosedRange) throws { + let middle = T.isSigned ? T.zero : (expectation.upperBound / 2 + 1) - for random: Optional in [r0, r1] { - Test().yay(random == nil ? range.isEmpty : range.contains(random!)) + var min = false + var mid = false + var max = false + + while !(min && mid && max) { + let random = T.random(through: index, using: &randomness) + guard expectation.contains(random) else { break } + if random == expectation.lowerBound { min = true } + if random == ((((((((middle)))))))) { mid = true } + if random == expectation.upperBound { max = true } } + + try #require(min && mid && max) } + } + } + + //=------------------------------------------------------------------------= + // MARK: Tests x Range + //=------------------------------------------------------------------------= + + @Test( + "BinaryInteger/random: in range", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsBinaryInteger, fuzzers + ) func randomInRange(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + let size = IX(size: T.self) ?? 256 + let min: T = T.minLikeSystemsInteger(size: size)! + let max: T = T.maxLikeSystemsInteger(size: size)! + + let eight: T = T((max.magnitude() + min.magnitude()) / 8 + 1) + let small: Range = (T.isSigned ? -4..<3 : 0..<8) + let large: Range = (min + eight)..<(max - eight) for min: T in small { - for max: T in (min) ..< (small).upperBound { - check(( min) ..< ( max)) - check(( ~max) ..< (~min)) + for max: T in min..) throws { + let a = T.random(in: range) + let b = T.random(in: range, using: &randomness) + + for x in [a, b] { + try #require(x == nil ? range.isEmpty : range.contains(x!)) + } } - } - - for type in typesAsBinaryInteger { - whereIs(type, randomness: fuzzer) } } - func testRandomInRangeHasKnownBounds() { - func whereIs(_ type: T.Type, randomness: consuming FuzzerInt) where T: BinaryInteger { - func check(_ expectation: Range) { + @Test( + "BinaryInteger/random: in range has known bounds", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsBinaryInteger, fuzzers + ) func randomInRangeHasKnownBounds(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for index: IX in 0 ... 7 { + let min = T.random(through: Shift(Count(index)), using: &randomness) + let max = T.random(through: Shift(Count(index)), using: &randomness) + try require(min <= max ? min..) throws { guard !expectation.isEmpty else { return } let last: T = expectation.upperBound - 1 @@ -76,18 +166,8 @@ final class BinaryIntegerTestsOnRandom: XCTestCase { if random == (((((((((last))))))))) { max = true } } - Test().yay(min && max) + try #require(min && max) } - - for index: IX in 0 ... 7 { - let min = T.random(through: Shift(Count(index)), using: &randomness) - let max = T.random(through: Shift(Count(index)), using: &randomness) - check(min <= max ? min..(_ type: T.Type, randomness: consuming FuzzerInt) where T: BinaryInteger { - let min = Esque.min - let max = Esque.bot + @Test( + "BinaryInteger/random: in closed range", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsBinaryInteger, fuzzers + ) func randomInClosedRange(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + let size = IX(size: T.self) ?? 256 + let min: T = T.minLikeSystemsInteger(size: size)! + let max: T = T.maxLikeSystemsInteger(size: size)! - let eigth: T = T(1 + Esque.bot / 8) + let eight: T = T((max.magnitude() + min.magnitude()) / 8 + 1) let small: ClosedRange = (T.isSigned ? -4...3 : 0...8) - let large: ClosedRange = (min + eigth)...(max - eigth) + let large: ClosedRange = (min + eight)...(max - eight) - func check(_ range: ClosedRange) { - let r0 = T.random(in: range) - let r1 = T.random(in: range, using: &randomness) - - for random: T in [r0, r1] { - Test().yay(range.contains(random)) + for min: T in small { + for max: T in min...small.upperBound { + try require(( min)...( max)) + try require((~max)...(~min)) } } - for min: T in small { - for max: T in (min) ... (small).upperBound { - check(( min) ... ( max)) - check(( ~max) ... (~min)) - } + for _ in 0 ..< 16 { + try require(( large.lowerBound)...( large.upperBound)) + try require((~large.upperBound)...(~large.lowerBound)) } - for _ in IX.zero ..< 16 { - check(( large.lowerBound) ... ( large.upperBound)) - check((~large.upperBound) ... (~large.lowerBound)) + func require(_ range: ClosedRange) throws { + try #require(range.contains(T.random(in: range))) + try #require(range.contains(T.random(in: range, using: &randomness))) } } - - for type in typesAsBinaryInteger { - whereIs(type, randomness: fuzzer) - } } - func testRandomInClosedRangeHasKnownBounds() { - func whereIs(_ type: T.Type, randomness: consuming FuzzerInt) where T: BinaryInteger { - func check(_ expectation: ClosedRange) { + @Test( + "BinaryInteger/random: in closed range has known bounds", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsBinaryInteger, fuzzers + ) func randomInClosedRangeHasKnownBounds(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for index: IX in 0 ... 7 { + let min = T.random(through: Shift(Count(index)), using: &randomness) + let max = T.random(through: Shift(Count(index)), using: &randomness) + try require(min <= max ? min...max : max...min) + } + + func require(_ expectation: ClosedRange) throws { var min = false var max = false @@ -145,88 +237,93 @@ final class BinaryIntegerTestsOnRandom: XCTestCase { if random == expectation.upperBound { max = true } } - Test().yay(min && max) - } - - for index: IX in 0 ... 7 { - let min = T.random(through: Shift(Count(index)), using: &randomness) - let max = T.random(through: Shift(Count(index)), using: &randomness) - check(min <= max ? min...max : max...min) + try #require(min && max) } } - - for type in typesAsBinaryInteger { - whereIs(type, randomness: fuzzer) - } } +} + +//*============================================================================* +// MARK: * Binary Integer x Random x Byte +//*============================================================================* + +@Suite struct BinaryIntegerTestsOnRandomAsByte { //=------------------------------------------------------------------------= - // MARK: Tests x Bit Index + // MARK: Tests //=------------------------------------------------------------------------= - func testRandomThroughBitIndex() { - func whereIs(_ type: T.Type, randomness: consuming FuzzerInt) where T: BinaryInteger { - for index in 0 ..< (T.isArbitrary ? 128 : IX(size: T.self)!) { - let index = Shift(Count(index)) - let limit = IX(raw: index.value) + (T.isSigned ? 1 : 2) + @Test( + "BinaryInteger/random/byte: random hits all values", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsCoreIntegerAsByte, randomnesses + ) func randomAsByteHitsAllValues(type: any SystemsInteger.Type, randomness: any Randomness) throws { + try whereIs(type, randomness) + + func whereIs(_ type: T.Type, _ randomness: consuming some Randomness) throws where T: SystemsInteger { + var matches: Set = [] + matches.reserveCapacity(T.all.count) + + while matches.count != T.all.count { + matches.insert(T.random(using: &randomness)) + } + + try #require(matches.sorted() == Array(T.all)) + } + } + + @Test( + "BinaryInteger/random/byte: random hits all values in range", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsCoreIntegerAsByte, randomnesses + ) func randomAsByteHitsAllValuesInRange(type: any SystemsInteger.Type, randomness: any Randomness) throws { + try whereIs(type, randomness) + + func whereIs(_ type: T.Type, _ randomness: consuming some Randomness) throws where T: SystemsInteger { + var matches: Set = [] + matches.reserveCapacity(T.all.count) + + for _ in 0 ..< 8 { + let a = T.random(using: &randomness) + let b = T.random(using: &randomness) + let r = a <= b ? a..= Count(00001)) - Test().yay(entropy <= Count(limit)) - Test().nay(random.isInfinite) - guard entropy != Count(limit) else { break } + while matches.count != r.count { + matches.insert(T.random(in: r, using: &randomness)!) } - while true { - let random = T.random(through: index, using: &randomness) - let entropy = random.entropy() - Test().yay(entropy >= Count(00001)) - Test().yay(entropy <= Count(limit)) - Test().nay(random.isInfinite) - guard entropy != Count(limit) else { break } - } + try #require(matches.sorted() == Array(r)) + matches.removeAll(keepingCapacity: true) } } - - for type in typesAsBinaryInteger { - whereIs(type, randomness: fuzzer) - } } - func testRandomThroughBitIndexHasKnownBounds() { - func whereIs(_ type: T.Type, randomness: consuming FuzzerInt) where T: BinaryInteger { - func check(_ index: Shift, _ expectation: ClosedRange) { - let middle = T.isSigned ? T.zero : ((expectation.upperBound / 2) + 1) - - var min = false - var mid = false - var max = false + @Test( + "BinaryInteger/random/byte: random hits all values in closed range", + Tag.List.tags(.generic, .random), + TimeLimitTrait.timeLimit(.minutes(3)), + arguments: typesAsCoreIntegerAsByte, randomnesses + ) func randomAsByteHitsAllValuesInClosedRange(type: any SystemsInteger.Type, randomness: any Randomness) throws { + try whereIs(type, randomness) + + func whereIs(_ type: T.Type, _ randomness: consuming some Randomness) throws where T: SystemsInteger { + var matches: Set = [] + matches.reserveCapacity(T.all.count) + + for _ in 0 ..< 8 { + let a = T.random(using: &randomness) + let b = T.random(using: &randomness) + let r = a <= b ? a...b : b...a - while !(min && mid && max) { - let random = T.random(through: index, using: &randomness) - guard expectation.contains(random) else { break } - if random == expectation.lowerBound { min = true } - if random == ((((((((middle)))))))) { mid = true } - if random == expectation.upperBound { max = true } + while matches.count != r.count { + matches.insert(T.random(in: r, using: &randomness)) } - Test().yay(min && mid && max) + try #require(matches.sorted() == Array(r)) + matches.removeAll(keepingCapacity: true) } - - check(Shift(Count(0)), T.isSigned ? -001 ... 000 : 000 ... 001) - check(Shift(Count(1)), T.isSigned ? -002 ... 001 : 000 ... 003) - check(Shift(Count(2)), T.isSigned ? -004 ... 003 : 000 ... 007) - check(Shift(Count(3)), T.isSigned ? -008 ... 007 : 000 ... 015) - check(Shift(Count(4)), T.isSigned ? -016 ... 015 : 000 ... 031) - check(Shift(Count(5)), T.isSigned ? -032 ... 031 : 000 ... 063) - check(Shift(Count(6)), T.isSigned ? -064 ... 063 : 000 ... 127) - check(Shift(Count(7)), T.isSigned ? -128 ... 127 : 000 ... 255) - } - - for type in typesAsBinaryInteger { - whereIs(type, randomness: fuzzer) } } }