From 45315ca25bd3aca6ec131d020cb1bf6d1670e09e Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 2 Oct 2025 23:19:31 +0800 Subject: [PATCH 1/2] Update CompareValuesCompatibilityTests --- .../CompareValuesCompatibilityTests.swift | 163 +++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/Tests/OpenAttributeGraphCompatibilityTests/CompareValuesCompatibilityTests.swift b/Tests/OpenAttributeGraphCompatibilityTests/CompareValuesCompatibilityTests.swift index 29afdf26..35fc9886 100644 --- a/Tests/OpenAttributeGraphCompatibilityTests/CompareValuesCompatibilityTests.swift +++ b/Tests/OpenAttributeGraphCompatibilityTests/CompareValuesCompatibilityTests.swift @@ -33,7 +33,7 @@ struct CompareValuesCompatibilityTests { } @Test - func testStructCompare() throws { + func structCompare() throws { struct A1 { var a: Int var b: Bool @@ -59,4 +59,165 @@ struct CompareValuesCompatibilityTests { } } } + + + // Below is the graph to show the expected behavior of compareValues with different modes and types + // ┌──────────────────┬────────────────────┬──────────────────┬──────────────────┐ + // │ │ mode │ Layout not ready │ Layout ready │ + // ├──────────────────┼────────────────────┼──────────────────┼──────────────────┤ + // │ │ bitwise │ bitwise │ bitwise │ + // │ PODEquatable │ equatableUnlessPOD │ bitwise │ bitwise │ + // │ │ equatableAlways │ bitwise │ equatable │ + // ├──────────────────┼────────────────────┼──────────────────┼──────────────────┤ + // │ │ bitwise │ bitwise │ bitwise │ + // │ NonPODEquatable │ equatableUnlessPOD │ bitwise │ equatable │ + // │ │ equatableAlways │ bitwise │ equatable │ + // └──────────────────┴────────────────────┴──────────────────┴──────────────────┘ + + struct POD { + var id: Int + } + + struct PODEquatable: Equatable { + var id: Int + var v1: Int + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.id == rhs.id && (lhs.v1 - rhs.v1) % 2 == 0 + } + } + + struct PODEquatableTrue: Equatable { + var id: Int + + static func == (lhs: Self, rhs: Self) -> Bool { + true + } + } + + struct PODEquatableFalse: Equatable { + var id: Int + + static func == (lhs: Self, rhs: Self) -> Bool { + false + } + } + + struct NonPODEquatable: Equatable { + init(id: Int, v1: Int) { + self.id = id + self.m = M(v: v1) + } + + var id: Int + var v1: Int { m.v } + + class M: Equatable { + var v: Int + init(v: Int) { self.v = v } + + static func == (lhs: M, rhs: M) -> Bool { + lhs.v == rhs.v + } + } + var m: M + + static func == (lhs: NonPODEquatable, rhs: NonPODEquatable) -> Bool { + lhs.id == rhs.id && (lhs.v1 - rhs.v1) % 2 == 0 + } + } + + @Test + func bitwizeMode() async throws { + let mode = ComparisonMode.bitwise + #expect(compareValues(POD(id: 1), POD(id: 1), mode: mode) == true) + #expect(compareValues(POD(id: 1), POD(id: 2), mode: mode) == false) + #expect(compareValues(POD(id: 1), POD(id: 3), mode: mode) == false) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 2), mode: mode) == true) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 4), mode: mode) == false) + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 1), mode: mode) == true) + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 2), mode: mode) == false) + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 1), mode: mode) == true) + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 2), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 2), mode: mode) == false, "bitwize compare will fail since M is a class") + } + + @Test + func equatableUnlessPODMode() async throws { + let mode = ComparisonMode.equatableUnlessPOD + // layout is not ready, use bitwise copmare + #expect(compareValues(POD(id: 1), POD(id: 1), mode: mode) == true) + #expect(compareValues(POD(id: 1), POD(id: 2), mode: mode) == false) + #expect(compareValues(POD(id: 1), POD(id: 3), mode: mode) == false) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 2), mode: mode) == true) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 4), mode: mode) == false) + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 1), mode: mode) == true) + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 2), mode: mode) == false) + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 1), mode: mode) == true) + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 2), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 2), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 4), mode: mode) == false) + + try await Task.sleep(for: .seconds(1)) + + // layout is ready, use Equatable copmare only for non POD type when avaiable + #expect(compareValues(POD(id: 1), POD(id: 1), mode: mode) == true) + #expect(compareValues(POD(id: 1), POD(id: 2), mode: mode) == false) + #expect(compareValues(POD(id: 1), POD(id: 3), mode: mode) == false) + + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 2), mode: mode) == true) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 4), mode: mode) == false, "POD type, use bitwise compare") + + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 1), mode: mode) == true) + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 2), mode: mode) == false, "POD type, use bitwise compare") + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 1), mode: mode) == true, "POD type, use bitwise compare") + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 2), mode: mode) == false) + + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 2), mode: mode) == true, "Non POD type, use Equatable compare") + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 4), mode: mode) == true, "Non POD type, use Equatable compare") + } + + @Test + func equatableAlwaysMode() async throws { + let mode = ComparisonMode.equatableAlways + // When layout is not ready, the same as bitwize + #expect(compareValues(POD(id: 1), POD(id: 1), mode: mode) == true) + #expect(compareValues(POD(id: 1), POD(id: 2), mode: mode) == false) + #expect(compareValues(POD(id: 1), POD(id: 3), mode: mode) == false) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 2), mode: mode) == true) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 4), mode: mode) == false) + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 1), mode: mode) == true) + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 2), mode: mode) == false) + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 1), mode: mode) == true) + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 2), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 2), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 4), mode: mode) == false) + + try await Task.sleep(for: .seconds(1)) + + // layout is ready, Equatable is used when avaiable + #expect(compareValues(POD(id: 1), POD(id: 1), mode: mode) == true) + #expect(compareValues(POD(id: 1), POD(id: 2), mode: mode) == false) + #expect(compareValues(POD(id: 1), POD(id: 3), mode: mode) == false) + + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 2), mode: mode) == true) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(PODEquatable(id: 1, v1: 2), PODEquatable(id: 1, v1: 4), mode: mode) == true, "use Equatable compare") + + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 1), mode: mode) == true) + #expect(compareValues(PODEquatableTrue(id: 1), PODEquatableTrue(id: 2), mode: mode) == true, "use Equatable compare") + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 1), mode: mode) == false, "use Equatable compare") + #expect(compareValues(PODEquatableFalse(id: 1), PODEquatableFalse(id: 2), mode: mode) == false) + + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 2), mode: mode) == true, "use Equatable compare") + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 3), mode: mode) == false) + #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 4), mode: mode) == true, "use Equatable compare") + } } From a743288e8df492d03fe1182274fdebfa25f481ed Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 2 Oct 2025 23:59:07 +0800 Subject: [PATCH 2/2] Fix iOS build issue and add new test case --- .../CompareValuesCompatibilityTests.swift | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/Tests/OpenAttributeGraphCompatibilityTests/CompareValuesCompatibilityTests.swift b/Tests/OpenAttributeGraphCompatibilityTests/CompareValuesCompatibilityTests.swift index 35fc9886..bc173e8b 100644 --- a/Tests/OpenAttributeGraphCompatibilityTests/CompareValuesCompatibilityTests.swift +++ b/Tests/OpenAttributeGraphCompatibilityTests/CompareValuesCompatibilityTests.swift @@ -106,27 +106,57 @@ struct CompareValuesCompatibilityTests { struct NonPODEquatable: Equatable { init(id: Int, v1: Int) { self.id = id - self.m = M(v: v1) + self.wrapper = Wrapper(v: v1) } var id: Int - var v1: Int { m.v } + var v1: Int { wrapper.v } - class M: Equatable { + class Wrapper: Equatable { var v: Int init(v: Int) { self.v = v } - static func == (lhs: M, rhs: M) -> Bool { + static func == (lhs: Wrapper, rhs: Wrapper) -> Bool { lhs.v == rhs.v } } - var m: M + private var wrapper: Wrapper static func == (lhs: NonPODEquatable, rhs: NonPODEquatable) -> Bool { lhs.id == rhs.id && (lhs.v1 - rhs.v1) % 2 == 0 } } + struct NonPODEquatableTrue: Equatable { + init() { + self.wrapper = Wrapper() + } + + private var wrapper: Wrapper + + class Wrapper {} + + static func == (lhs: NonPODEquatableTrue, rhs: NonPODEquatableTrue) -> Bool { + true + } + } + + struct NonPODEquatableFalse: Equatable { + init() { + self.wrapper = Wrapper.shared + } + + private var wrapper: Wrapper + + class Wrapper { + static let shared = Wrapper() + } + + static func == (lhs: NonPODEquatableFalse, rhs: NonPODEquatableFalse) -> Bool { + false + } + } + @Test func bitwizeMode() async throws { let mode = ComparisonMode.bitwise @@ -160,8 +190,10 @@ struct CompareValuesCompatibilityTests { #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 2), mode: mode) == false) #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 3), mode: mode) == false) #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 4), mode: mode) == false) + #expect(compareValues(NonPODEquatableTrue(), NonPODEquatableTrue(), mode: mode) == false) + #expect(compareValues(NonPODEquatableFalse(), NonPODEquatableFalse(), mode: mode) == true) - try await Task.sleep(for: .seconds(1)) + try await Task.sleep(nanoseconds: 1_000_000_000) // layout is ready, use Equatable copmare only for non POD type when avaiable #expect(compareValues(POD(id: 1), POD(id: 1), mode: mode) == true) @@ -180,6 +212,9 @@ struct CompareValuesCompatibilityTests { #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 2), mode: mode) == true, "Non POD type, use Equatable compare") #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 3), mode: mode) == false) #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 4), mode: mode) == true, "Non POD type, use Equatable compare") + + #expect(compareValues(NonPODEquatableTrue(), NonPODEquatableTrue(), mode: mode) == true) + #expect(compareValues(NonPODEquatableFalse(), NonPODEquatableFalse(), mode: mode) == false) } @Test @@ -200,7 +235,7 @@ struct CompareValuesCompatibilityTests { #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 3), mode: mode) == false) #expect(compareValues(NonPODEquatable(id: 1, v1: 2), NonPODEquatable(id: 1, v1: 4), mode: mode) == false) - try await Task.sleep(for: .seconds(1)) + try await Task.sleep(nanoseconds: 1_000_000_000) // layout is ready, Equatable is used when avaiable #expect(compareValues(POD(id: 1), POD(id: 1), mode: mode) == true)