Skip to content

Commit

Permalink
Add XCTAssertBytesEqual and XCTAssertBytesFromHexEqual method to new …
Browse files Browse the repository at this point in the history
…package and library XCTAssertBytesEqual. Expose BytesMutation as a library.

Appending info to README
  • Loading branch information
Sajjon committed Sep 11, 2022
1 parent 2d2775a commit 7def790
Show file tree
Hide file tree
Showing 7 changed files with 382 additions and 43 deletions.
19 changes: 16 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ let package = Package(
name: "BytePattern",
targets: ["BytePattern"]
),
.library(
name: "XCTAssertBytesEqual",
targets: ["XCTAssertBytesEqual"]
),
.library(
name: "BytePattern",
targets: ["BytePattern"]
),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
Expand All @@ -25,6 +33,12 @@ let package = Package(
.product(name: "Algorithms", package: "swift-algorithms"),
]
),
.target(
name: "XCTAssertBytesEqual",
dependencies: [
"BytePattern"
]
),
.target(
name: "BytesMutation",
dependencies: [
Expand All @@ -35,9 +49,8 @@ let package = Package(
.testTarget(
name: "BytePatternTests",
dependencies: [
"BytePattern",
"BytesMutation",
.product(name: "Algorithms", package: "swift-algorithms"),
"XCTAssertBytesEqual",
"BytesMutation"
]
),
]
Expand Down
107 changes: 106 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# BytePatternFinder
# BytePattern


A **linear time** byte pattern finder, useful to discover that two bytes seqences are almost identical, probably they are but during construction of one of them the developer has accidently either reversed the sequence, or they originate from integers, which might accidently use the wrong endianess, or a combination of both.
Expand Down Expand Up @@ -104,3 +104,108 @@ finder.find(
rhs: "edda ebfe 2143",
) // `sameIf([.swapEndianessOfUInt16sFromBytes, .reverseOrderOfUInt16sFromBytes, .reversedHex])`
```

# XCTAssertBytesEqual
A small test util package which enables you to conveniently compare byte sequences using the `BytePatternFinder`. It contains some `XCTAssert` like methods, but specially tailored for byte sequence comparision.


## XCTAssertBytesEqual


```swift
// This test will fail.
// Assertion failure message is:
// "Expected bytes in LHS to equal RHS, but they are not, however, they resemble each other with according to byte pattern: sameIf([swapEndianessOfUInt16sFromBytes])."
func test_data_failing() throws {
try XCTAssertBytesEqual(
Data(hex: "ab12 cd34"),
Data(hex: "12ab 34cd"),
"An optional message goes here."
)
}
```

You can change the behaviour of the test to pass for non-`identical` patterns found - but will still fail for no pattern found of course, by passing `passOnPatternNonIdentical: true`.

```swift
func test_data_passing() throws {
try XCTAssertBytesEqual(
Data(hex: "ab12 cd34"),
Data(hex: "12ab 34cd"),
"An optional message goes here.",
passOnPatternNonIdentical: true
)
}
```

You can also opt in to interrupt on non-identical patterns found by passing `haltOnPatternNonIdentical: true`:

```swift
func test_data_passing_halting() throws {
try XCTAssertBytesEqual(
Data(hex: "ab12 cd34"),
Data(hex: "12ab 34cd"),
"An optional message goes here.",
passOnPatternNonIdentical: true,
haltOnPatternNonIdentical: true
)
}
```

You can also globally change default value of `passOnPatternNonIdentical` and `haltOnPatternNonIdentical` by setting these properties on global type `DefaultXCTAssertBytesEqualParameters`. A good place to do this is in the `setUp()` method of your test class.


```swift
override func setUp() {
super.setUp()
DefaultXCTAssertBytesEqualParameters.passOnPatternNonIdentical = true
DefaultXCTAssertBytesEqualParameters.haltOnPatternNonIdentical = true
}

func test_data_passing_halting_defaulParamsUsed() throws {
try XCTAssertBytesEqual(
Data(hex: "ab12 cd34"),
Data(hex: "12ab 34cd")
)
}
```


## XCTAssertBytesFromHexEqual

The examples above can be simplified by used of `XCTAssertBytesFromHexEqual`, which will fail with error if you're passing in invalid hexadecimal strings.


```swift
func test_nonIdentical_but_passing() {
XCTAssertBytesFromHexEqual(
"ab12 cd34",
"12ab 34cd",
passOnPatternNonIdentical: true
)
}
```

# BytesMutation

This small package allows you to perform mutation on any byte sequence conforming to `ContiguousBytes` and which names mirror those of `BytePattern`.

```swift
public extension ContiguousBytes {
func reversed() -> [UInt8]

func reversedHex() -> [UInt8]

func reverseOrderOfUInt16sFromBytes() -> [UInt8]

func reverseOrderOfUInt32sFromBytes() -> [UInt8]

func reverseOrderOfUInt64sFromBytes() -> [UInt8]

func swapEndianessOfUInt16sFromBytes() -> [UInt8]

func swapEndianessOfUInt32sFromBytes() -> [UInt8]

func swapEndianessOfUInt64sFromBytes() -> [UInt8]
}
```
16 changes: 8 additions & 8 deletions Sources/BytesMutation/ContigiousBytes+Mutate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ private extension ContiguousBytes {
}
}

func _asSegmentsOfUIntButReversedOrder<I: FixedWidthInteger>(uIntType _: I.Type) -> [UInt8] {
func _reverseOrderOfUInt<I: FixedWidthInteger>(type _: I.Type) -> [UInt8] {
into(I.self)!
.reversed()
.flatMap { $0.bigEndian.bytes }
}

func _asSegmentsOfUIntButEndianessSwapped<I: FixedWidthInteger>(uIntType _: I.Type) -> [UInt8] {
func _swapEndianessOfUInt<I: FixedWidthInteger>(type _: I.Type) -> [UInt8] {
into(I.self)!
.flatMap { $0.littleEndian.bytes }
}
Expand All @@ -56,26 +56,26 @@ public extension ContiguousBytes {
}

func reverseOrderOfUInt16sFromBytes() -> [UInt8] {
_asSegmentsOfUIntButReversedOrder(uIntType: UInt16.self)
_reverseOrderOfUInt(type: UInt16.self)
}

func reverseOrderOfUInt32sFromBytes() -> [UInt8] {
_asSegmentsOfUIntButReversedOrder(uIntType: UInt32.self)
_reverseOrderOfUInt(type: UInt32.self)
}

func reverseOrderOfUInt64sFromBytes() -> [UInt8] {
_asSegmentsOfUIntButReversedOrder(uIntType: UInt64.self)
_reverseOrderOfUInt(type: UInt64.self)
}

func swapEndianessOfUInt16sFromBytes() -> [UInt8] {
_asSegmentsOfUIntButEndianessSwapped(uIntType: UInt16.self)
_swapEndianessOfUInt(type: UInt16.self)
}

func swapEndianessOfUInt32sFromBytes() -> [UInt8] {
_asSegmentsOfUIntButEndianessSwapped(uIntType: UInt32.self)
_swapEndianessOfUInt(type: UInt32.self)
}

func swapEndianessOfUInt64sFromBytes() -> [UInt8] {
_asSegmentsOfUIntButEndianessSwapped(uIntType: UInt64.self)
_swapEndianessOfUInt(type: UInt64.self)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// File.swift
//
//
// Created by Alexander Cyon on 2022-09-11.
//

import Foundation

/// A global set of values that will be used in the abscense of arguments passed to
/// `XCTAssertBytesEqual` and `XCTAssertBytesFromHexEqual`. These
/// bools can be set by you if you want to have the same behaviour across many
/// tests, and thus omit this parameter when calling this function.
public enum DefaultXCTAssertBytesEqualParameters {

/// Used in the abscenve of the `passOnPatternNonIdentical` parameter
/// passed to test assert function `XCTAssertBytesEqual` or
/// `XCTAssertBytesFromHexEqual`.
public static var passOnPatternNonIdentical = false

/// Used in the abscenve of the `haltOnPatternNonIdentical` parameter
/// passed to test assert function `XCTAssertBytesEqual` or
/// `XCTAssertBytesFromHexEqual`.
public static var haltOnPatternNonIdentical = false
}
87 changes: 87 additions & 0 deletions Sources/XCTAssertBytesEqual/XCTAssertBytesEqual.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// File.swift
//
//
// Created by Alexander Cyon on 2022-09-11.
//

import Foundation
import XCTest
import BytePattern

/// A comprehensive compare of LHS and RHS byte sequences
/// which uses BytePatternFinder to find any byte pattern between
/// them. The sequences might be identical, completely different
/// or would have been identical if some mutation would be done,
/// or in other words, you might have accidently done a mutation
/// or otherwise incorrect initialization of the data, this comparission
/// will help inform you of this.
///
/// - Parameters:
/// - lhs: Some sequence of bytes to compare with `RHS`.
/// - rhs: Some sequence of bytes to compare with`LHS`.
/// - message: An optional description of the assertion, for inclusion in test results.
/// - passOnPatternNonIdentical: An optional boolean value indicating that
/// this test assertion should count as "pass"/"success" even if `LHS` and `RHS`
/// are not identical, but similar according to a `BytePattern`. If `nil`
/// is passed in, the static `passOnPatternNonIdentical` value in the global
/// type `DefaultXCTAssertBytesEqualParameters` will be used, this bool
/// has both getter and a setter, so you can override this default value if you want to
/// have the same behaviour across many tests, and thus omit this parameter when
/// calling this function.
/// - haltOnPatternNonIdentical: An optional boolean value indicating that
/// when a non `identical`byte pattern was found when for `LHS` and `RHS`
/// the test should halt execution and debug print the pattern This is useful when you
/// want to highlight any non-identical pattern for a specifc test you care about. If `nil`
/// is passed in, the static `haltOnPatternNonIdentical` value in the global
/// type `DefaultXCTAssertBytesEqualParameters` will be used, this bool
/// has both getter and a setter, so you can override this default value if you want to
/// have the same behaviour across many tests, and thus omit this parameter when
/// calling this function.
/// - file: The file where the failure occurs. The default is the filename of the
/// test case where you call this function.
/// - line: The line number where the failure occurs. The default is the line
/// number where you call this function.
/// - Returns: A pattern of bytes found when comparing `LHS` with `RHS`.
@discardableResult
public func XCTAssertBytesEqual(
_ lhs: some ContiguousBytes,
_ rhs: some ContiguousBytes,
_ message: String? = nil,
passOnPatternNonIdentical: Bool? = nil,
haltOnPatternNonIdentical: Bool? = nil,
file: StaticString = #file,
line: UInt = #line
) -> BytePattern? {

let passOnPatternNonIdentical = passOnPatternNonIdentical ?? DefaultXCTAssertBytesEqualParameters.passOnPatternNonIdentical
let haltOnPatternNonIdentical = haltOnPatternNonIdentical ?? DefaultXCTAssertBytesEqualParameters.haltOnPatternNonIdentical

let bytePatternFinder = BytePatternFinder()
if let pattern = bytePatternFinder.find(lhs: lhs, rhs: rhs) {
if pattern == .identical {
// All good
} else {
let msg = "Expected bytes in LHS to equal RHS, but they are not, however, they resemble each other with according to byte pattern: \(String(describing: pattern)).\(message.map { " \($0)." } ?? "")"
if haltOnPatternNonIdentical {
debugPrint(msg)
raise(SIGINT)
}
if !passOnPatternNonIdentical {
XCTFail(
msg,
file: file,
line: line
)
}
}
return pattern
} else {
XCTFail(
"Expected bytes in LHS to equal RHS, but they are different.\(message.map { " \($0)." } ?? "")",
file: file,
line: line
)
return nil
}
}
Loading

0 comments on commit 7def790

Please sign in to comment.