|
4 | 4 | import polars as pl |
5 | 5 | import pytest |
6 | 6 |
|
7 | | -from diffly.testing import assert_collection_equal |
| 7 | +from diffly.comparison import DataFrameComparison |
| 8 | +from diffly.testing import ( |
| 9 | + CollectionComparisonAssertionError, |
| 10 | + assert_collection_equal, |
| 11 | +) |
8 | 12 |
|
9 | 13 | pytest.importorskip("dataframely", reason="requires dataframely") |
10 | 14 | import dataframely as dy |
@@ -37,89 +41,103 @@ class Quux(dy.Collection): |
37 | 41 | bar: dy.LazyFrame[Bar] |
38 | 42 |
|
39 | 43 |
|
40 | | -def test_identical() -> None: |
| 44 | +@pytest.fixture |
| 45 | +def matching() -> pl.DataFrame: |
| 46 | + return pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}) |
| 47 | + |
| 48 | + |
| 49 | +@pytest.fixture |
| 50 | +def mismatching() -> pl.DataFrame: |
| 51 | + return pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 4.0]}) |
| 52 | + |
| 53 | + |
| 54 | +def test_identical(matching: pl.DataFrame) -> None: |
41 | 55 | qux = Qux.validate( |
42 | 56 | { |
43 | | - "foo": Foo.validate( |
44 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
45 | | - ), |
46 | | - "bar": Bar.validate( |
47 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
48 | | - ), |
| 57 | + "foo": Foo.validate(matching, cast=True), |
| 58 | + "bar": Bar.validate(matching, cast=True), |
49 | 59 | } |
50 | 60 | ) |
51 | 61 | assert_collection_equal(qux, qux) |
52 | 62 |
|
53 | 63 |
|
54 | | -def test_different_types() -> None: |
| 64 | +def test_different_types(matching: pl.DataFrame) -> None: |
55 | 65 | qux = Qux.validate( |
56 | 66 | { |
57 | | - "foo": Foo.validate( |
58 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
59 | | - ), |
60 | | - "bar": Bar.validate( |
61 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
62 | | - ), |
| 67 | + "foo": Foo.validate(matching, cast=True), |
| 68 | + "bar": Bar.validate(matching, cast=True), |
63 | 69 | } |
64 | 70 | ) |
65 | 71 | quux = Quux.validate(qux.to_dict()) |
66 | 72 | with pytest.raises(AssertionError, match="The collection definitions do not match"): |
67 | 73 | assert_collection_equal(qux, quux) |
68 | 74 |
|
69 | 75 |
|
70 | | -def test_missing_member() -> None: |
| 76 | +def test_missing_member(matching: pl.DataFrame) -> None: |
71 | 77 | qux1 = Qux.validate( |
72 | 78 | { |
73 | | - "foo": Foo.validate( |
74 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
75 | | - ), |
76 | | - "bar": Bar.validate( |
77 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
78 | | - ), |
79 | | - "baz": Baz.validate( |
80 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
81 | | - ), |
| 79 | + "foo": Foo.validate(matching, cast=True), |
| 80 | + "bar": Bar.validate(matching, cast=True), |
| 81 | + "baz": Baz.validate(matching, cast=True), |
82 | 82 | } |
83 | 83 | ) |
84 | 84 | qux2 = Qux.validate( |
85 | 85 | { |
86 | | - "foo": Foo.validate( |
87 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
88 | | - ), |
89 | | - "bar": Bar.validate( |
90 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
91 | | - ), |
| 86 | + "foo": Foo.validate(matching, cast=True), |
| 87 | + "bar": Bar.validate(matching, cast=True), |
92 | 88 | } |
93 | 89 | ) |
94 | 90 | with pytest.raises(AssertionError, match="The collections have different members"): |
95 | 91 | assert_collection_equal(qux1, qux2) |
96 | 92 |
|
97 | 93 |
|
98 | | -def test_unequal_members() -> None: |
| 94 | +def test_unequal_members(matching: pl.DataFrame, mismatching: pl.DataFrame) -> None: |
99 | 95 | qux1 = Qux.validate( |
100 | 96 | { |
101 | | - "foo": Foo.validate( |
102 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
103 | | - ), |
104 | | - "bar": Bar.validate( |
105 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 3.0]}), cast=True |
106 | | - ), |
| 97 | + "foo": Foo.validate(matching, cast=True), |
| 98 | + "bar": Bar.validate(matching, cast=True), |
107 | 99 | } |
108 | 100 | ) |
109 | 101 | qux2 = Qux.validate( |
110 | 102 | { |
111 | | - "foo": Foo.validate( |
112 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 4.0]}), cast=True |
113 | | - ), |
114 | | - "bar": Bar.validate( |
115 | | - pl.DataFrame({"index": [1, 2, 3], "value": [1.0, 2.0, 4.0]}), cast=True |
116 | | - ), |
| 103 | + "foo": Foo.validate(mismatching, cast=True), |
| 104 | + "bar": Bar.validate(mismatching, cast=True), |
117 | 105 | } |
118 | 106 | ) |
119 | 107 | with pytest.raises(AssertionError, match="The following members are not equal"): |
120 | 108 | assert_collection_equal(qux1, qux2) |
121 | 109 |
|
122 | 110 |
|
| 111 | +def test_error_exposes_comparisons( |
| 112 | + matching: pl.DataFrame, mismatching: pl.DataFrame |
| 113 | +) -> None: |
| 114 | + # Arrange |
| 115 | + qux1 = Qux.validate( |
| 116 | + { |
| 117 | + "foo": Foo.validate(matching, cast=True), |
| 118 | + "bar": Bar.validate(matching, cast=True), |
| 119 | + } |
| 120 | + ) |
| 121 | + qux2 = Qux.validate( |
| 122 | + { |
| 123 | + "foo": Foo.validate(mismatching, cast=True), |
| 124 | + "bar": Bar.validate(matching, cast=True), |
| 125 | + } |
| 126 | + ) |
| 127 | + |
| 128 | + # Act |
| 129 | + with pytest.raises(CollectionComparisonAssertionError) as exc_info: |
| 130 | + assert_collection_equal(qux1, qux2) |
| 131 | + |
| 132 | + # Assert |
| 133 | + assert isinstance(exc_info.value, CollectionComparisonAssertionError) |
| 134 | + # Only the failing member is exposed, and its comparison is usable. |
| 135 | + comparisons = exc_info.value.comparisons |
| 136 | + assert set(comparisons) == {"foo"} |
| 137 | + assert isinstance(comparisons["foo"], DataFrameComparison) |
| 138 | + assert comparisons["foo"].fraction_same("value") == pytest.approx(2 / 3) |
| 139 | + |
| 140 | + |
123 | 141 | def test_no_primary_key() -> None: |
124 | 142 | no_pk_schema = create_schema("NoPKSchema", {"a": dy.Integer(nullable=True)}) |
125 | 143 | collection = create_collection("Test", {"first": Foo, "second": no_pk_schema}) |
|
0 commit comments