• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

curtd / TestingUtilities.jl / 20152273962

12 Dec 2025 12:43AM UTC coverage: 73.719% (-15.1%) from 88.777%
20152273962

Pull #40

github

web-flow
Merge d283b854f into 0100e235b
Pull Request #40: deps: Set `PrettyTables@3`

0 of 5 new or added lines in 1 file covered. (0.0%)

174 existing lines in 3 files now uncovered.

993 of 1347 relevant lines covered (73.72%)

69.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/ext/TestingUtilitiesDataFramesExt.jl
1
module TestingUtilitiesDataFramesExt 
2
    using TestingUtilities, DataFrames, PrettyTables 
3

4
    using PrettyTables: TextHighlighter
5

6
    const pretty_table_kwarg_keys = (:alignment, :backend, :cell_alignment, :cell_first_line_only, :compact_printing, :formatters, :header, :header_alignment, :header_cell_alignment, :limit_printing, :max_num_of_columns, :max_num_of_rows, :renderer, :row_labels, :row_label_alignment, :row_label_column_title, :row_number_column_title, :show_header, :show_row_number, :show_subheader, :title, :title_alignment)
7
    const pretty_table_kwarg_keys_text = (pretty_table_kwarg_keys..., :alignment_anchor_fallback, :alignment_anchor_fallback_override, :alignment_anchor_regex, :autowrap, :body_hlines, :body_hlines_format, :columns_width, :crop, :Crop_subheader, :continuation_row_alignment, :display_size, :ellipsis_line_skip, :equal_columns_width, :highlighters, :hlines, :linebreaks, :maximum_columns_width, :minimum_columns_width, :newline_at_end, :overwrite, :reserved_display_lines, :row_number_alignment, :show_omitted_cell_summary, :tf, :title_autowrap, :title_same_width_as_table, :vcrop_mode, :vlines, :border_crayon, :header_crayon, :omitted_cell_summary_crayon, :row_label_crayon, :row_label_header_crayon, :row_number_header_crayon, :subheader_crayon, :text_crayon, :title_crayon)
8

UNCOV
9
    function crayon_from_style(; matching::Bool)
×
UNCOV
10
        if matching 
×
UNCOV
11
            style = TestingUtilities.show_diff_matching_style
×
UNCOV
12
            if isempty(style)
×
13
                style = TestingUtilities._show_diff_matching_style_default()
×
14
            end
15
        else 
UNCOV
16
            style = TestingUtilities.show_diff_differing_style
×
UNCOV
17
            if isempty(style)
×
18
                style = TestingUtilities._show_diff_differing_style_default()
×
19
            end
20
        end
UNCOV
21
        kwargs = []
×
UNCOV
22
        keys = first.(style)
×
UNCOV
23
        index = findfirst(==(:color), keys)
×
UNCOV
24
        if !isnothing(index) && (val = last(style[index]); val isa Symbol || val isa Integer || val isa NTuple{3,Integer} || val isa UInt32)
×
UNCOV
25
            push!(kwargs, :foreground => val)
×
26
        end
UNCOV
27
        for f in fieldnames(Crayon)
×
UNCOV
28
            index = findfirst(==(f), keys)
×
UNCOV
29
            if !isnothing(index) && (val = last(style[index]); val isa Bool)
×
UNCOV
30
                push!(kwargs, f => val)
×
31
            end
32
        end
UNCOV
33
        return Crayon(; kwargs...)
×
34
    end
35

36
    struct TruncatedValue end 
NEW
37
    PrettyTables._compact_type_str(::Type{TruncatedValue}) = ""
×
38

UNCOV
39
    function show_truncated_df(io::IO, df::AbstractDataFrame; max_num_rows_cols::Tuple{Int,Int} = TestingUtilities.show_df_max_nrows_ncols[], kwargs...)
×
UNCOV
40
        max_num_of_rows, max_num_of_columns = max.(1, max_num_rows_cols)
×
UNCOV
41
        num_rows = nrow(df)
×
UNCOV
42
        num_cols = ncol(df)
×
UNCOV
43
        truncate_to_rows = min(num_rows, max_num_of_rows)
×
UNCOV
44
        truncate_to_cols = min(num_cols, max_num_of_columns)
×
45

UNCOV
46
        row_indices = 1:(truncate_to_rows + (num_rows > max_num_of_rows ? 1 : 0))
×
UNCOV
47
        col_indices = 1:(truncate_to_cols + (num_cols > max_num_of_columns ? 1 : 0))
×
48

UNCOV
49
        df_to_show = df[row_indices, col_indices]
×
UNCOV
50
        if num_cols > max_num_of_columns
×
UNCOV
51
            rename!(df_to_show, (truncate_to_cols+1) => Symbol("…"))
×
UNCOV
52
            df_to_show[!, Symbol("…")] = [TruncatedValue() for _ in row_indices]
×
53
        end
54

UNCOV
55
        if haskey(kwargs, :formatters)
×
UNCOV
56
            _formatter = kwargs[:formatters]
×
57
        else
UNCOV
58
            _formatter = (v,i,j) -> string(v) 
×
59
        end
UNCOV
60
        if haskey(kwargs, :highlighters)
×
UNCOV
61
            _highlighters = kwargs[:highlighters]
×
UNCOV
62
            highlighter_f = function(data, i, j)
×
UNCOV
63
                ((i == truncate_to_rows + 1) || (j == truncate_to_cols + 1)) && return false 
×
UNCOV
64
                return _highlighters.f(data, i, j)
×
65
            end
UNCOV
66
            highlighter_fd = _highlighters.fd 
×
NEW
67
            highlighters = TextHighlighter(highlighter_f, highlighter_fd)
×
68
        else
NEW
69
            highlighters = TextHighlighter((data,i,j) -> false, crayon"white")
×
70
        end
UNCOV
71
        formatters = function(v,i,j)
×
UNCOV
72
            if i == truncate_to_rows + 1
×
UNCOV
73
                if j ≤ truncate_to_cols
×
UNCOV
74
                    return "⋮"
×
75
                else
UNCOV
76
                    return ""
×
77
                end
UNCOV
78
            elseif i == 1
×
UNCOV
79
                if j == truncate_to_cols + 1
×
UNCOV
80
                    return "⋯"
×
81
                else
UNCOV
82
                    return _formatter(v,i,j)
×
83
                end
UNCOV
84
            elseif j == truncate_to_cols + 1
×
UNCOV
85
                return ""
×
86
            else
UNCOV
87
                return _formatter(v,i,j)
×
88
            end
89
        end
NEW
90
        return pretty_table(io, df_to_show; (k => v for (k, v) in pairs(kwargs) if k ∈ pretty_table_kwarg_keys_text)..., highlighters=[highlighters], formatters=[formatters])
×
91
    end
92

UNCOV
93
    function TestingUtilities.show_diff(::TestingUtilities.StructTypeCat, ctx::IOContext, expected::AbstractDataFrame, result::AbstractDataFrame; expected_name="expected", result_name="result", max_num_rows_cols::Tuple{Int,Int} = TestingUtilities.show_diff_df_max_nrows_ncols[], results_printer::Union{TestingUtilities.TestResultsPrinter, Nothing}=nothing, differing_cols_only::Bool=false, kwargs...)
×
UNCOV
94
        has_colour = get(ctx, :color, false)
×
UNCOV
95
        expected_names = propertynames(expected)
×
UNCOV
96
        result_names = propertynames(result)
×
UNCOV
97
        not_in_result_names = setdiff(expected_names, result_names)
×
UNCOV
98
        not_in_expected_names = setdiff(result_names, expected_names)
×
UNCOV
99
        sym_diff = union(not_in_result_names, not_in_expected_names)
×
UNCOV
100
        if isempty(sym_diff)
×
UNCOV
101
            expected_nrows = nrow(expected)
×
UNCOV
102
            result_nrows = nrow(result)
×
UNCOV
103
            if expected_nrows == result_nrows 
×
UNCOV
104
                max_num_of_rows, max_num_of_columns = max.(1, max_num_rows_cols)
×
UNCOV
105
                max_num_of_columns_plus_headers = max_num_of_columns+2
×
UNCOV
106
                differing_rows = Int[]
×
UNCOV
107
                differing_cols = Set{Symbol}()
×
UNCOV
108
                for (row_num,(row_expected, row_result)) in enumerate(zip(eachrow(expected), eachrow(result)))
×
UNCOV
109
                    if !isequal(row_expected, row_result)
×
UNCOV
110
                        push!(differing_rows, row_num)
×
UNCOV
111
                        for col in expected_names 
×
UNCOV
112
                            if !isequal(row_expected[col], row_result[col])
×
UNCOV
113
                                push!(differing_cols, col)
×
114
                            end
115
                        end
116
                    end
UNCOV
117
                    length(differing_rows) == max_num_of_rows+1 && break 
×
118
                end
UNCOV
119
                if length(differing_rows) == max_num_of_rows+1
×
UNCOV
120
                    pop!(differing_rows)
×
UNCOV
121
                    has_more_differing_rows = true
×
122
                else
UNCOV
123
                    has_more_differing_rows = false 
×
124
                end
125

126
                # If there are more agreeing columns than number of columns we can display, or the first `max_num_of_columns` we can display are agreeing columns, only show differing columns
UNCOV
127
                agreeing_columns = setdiff(expected_names, differing_cols)
×
UNCOV
128
                if length(agreeing_columns) ≥ max_num_of_columns || length(Int[i for (i, col) in enumerate(expected_names) if col in agreeing_columns]) ≥ max_num_of_columns
×
UNCOV
129
                    differing_cols_only = true
×
130
                end
UNCOV
131
                n_differing_rows = length(differing_rows)
×
UNCOV
132
                difference_dfs = DataFrame[]
×
UNCOV
133
                if differing_cols_only
×
UNCOV
134
                    for row_num in differing_rows 
×
UNCOV
135
                        difference_df = DataFrame()
×
UNCOV
136
                        difference_df[!,:row_num] = [row_num, nothing]
×
UNCOV
137
                        difference_df[!,:df] = [expected_name, result_name]
×
UNCOV
138
                        for col in expected_names 
×
UNCOV
139
                            if !isequal(expected[row_num, col], result[row_num, col])
×
UNCOV
140
                                difference_df[!, col] = [expected[row_num, col], result[row_num, col]]
×
141
                            end
142
                        end
UNCOV
143
                        push!(difference_dfs, difference_df)
×
144
                    end
145
                else
UNCOV
146
                    difference_df = DataFrame()
×
UNCOV
147
                    difference_df[!,:row_num] = vec(vcat(differing_rows', repeat([nothing],1, n_differing_rows)))
×
UNCOV
148
                    difference_df[!,:df] = repeat([expected_name, result_name], length(differing_rows))
×
UNCOV
149
                    for col in expected_names 
×
UNCOV
150
                        difference_df[!, col] = vec(vcat(reshape(expected[differing_rows, col], (1, n_differing_rows)), reshape(result[differing_rows, col], (1, n_differing_rows))))
×
151
                    end
UNCOV
152
                    push!(difference_dfs, difference_df)
×
153
                end
154
               
UNCOV
155
                matching_crayon = crayon_from_style(; matching=true)
×
UNCOV
156
                differing_crayon = crayon_from_style(; matching=false)
×
157
                 
UNCOV
158
                highlight_diff = function(h, data, i, j)
×
UNCOV
159
                    if mod(i, 2) == 1
×
UNCOV
160
                        if isequal(data[i,j], data[i+1,j])
×
UNCOV
161
                            return matching_crayon
×
162
                        else
UNCOV
163
                            return differing_crayon
×
164
                        end
165
                    else
UNCOV
166
                        return highlight_diff(h, data, i-1, j)
×
167
                    end
168
                end
169

NEW
170
                highlighters = TextHighlighter((data,i,j) -> j > 2, highlight_diff)
×
UNCOV
171
                formatters = (v, i, j) -> j == 1 && isnothing(v) ? "" : v
×
UNCOV
172
                println(ctx, "Reason: Mismatched values")
×
UNCOV
173
                for df in difference_dfs
×
UNCOV
174
                    show_truncated_df(ctx, df; highlighters, formatters, max_num_rows_cols=(max_num_of_rows, max_num_of_columns_plus_headers))
×
175
                end
UNCOV
176
                if length(difference_dfs) == n_differing_rows && has_more_differing_rows
×
UNCOV
177
                    println(ctx, "⋮ ⋮ ⋮")
×
178
                end
179
            else
UNCOV
180
                println(ctx, "Reason: `nrow($expected_name) != nrow($result_name)`")
×
UNCOV
181
                p = TestingUtilities.PrintAligned("`nrow($expected_name)`", "`nrow($result_name)`"; separator=" = ")
×
UNCOV
182
                p(ctx, 1)
×
UNCOV
183
                println(ctx, expected_nrows)
×
UNCOV
184
                p(ctx, 2)
×
UNCOV
185
                println(ctx, result_nrows)
×
186
            end
187
        else 
UNCOV
188
            println(ctx, "Reason: `propertynames($expected_name) != propertynames($result_name)`")
×
UNCOV
189
            common_columns = [name for name in expected_names if name in result_names]
×
190
            
UNCOV
191
            p = TestingUtilities.PrintAligned("`propertynames($expected_name)`", "`propertynames($result_name)`"; separator=" = ")
×
UNCOV
192
            p(ctx, 1)
×
UNCOV
193
            print(ctx, "{")
×
194
          
UNCOV
195
            TestingUtilities.show_maybe_styled(ctx, join([repr(s) for s in common_columns], ", "); has_colour, is_matching=true)
×
196
          
UNCOV
197
            if !isempty(common_columns) && !isempty(not_in_result_names)
×
UNCOV
198
                print(ctx, ", ")
×
199
            end
UNCOV
200
            TestingUtilities.show_maybe_styled(ctx, join( [repr(s) for s in not_in_result_names], ", "); has_colour, is_matching=false)
×
UNCOV
201
            println(ctx, "}")
×
202

UNCOV
203
            p(ctx, 2)
×
UNCOV
204
            print(ctx, "{")
×
UNCOV
205
            TestingUtilities.show_maybe_styled(ctx, join([repr(s) for s in common_columns], ", "); has_colour, is_matching=true)
×
UNCOV
206
            if !isempty(common_columns) && !isempty(not_in_expected_names)
×
UNCOV
207
                print(ctx, ", ")
×
208
            end
UNCOV
209
            TestingUtilities.show_maybe_styled(ctx, join([repr(s) for s in not_in_expected_names], ", "); has_colour, is_matching=false)
×
UNCOV
210
            println(ctx, "}")
×
211
        end
UNCOV
212
        return true
×
213
    end
214

UNCOV
215
    TestingUtilities.show_value(ctx::IOContext, value::AbstractDataFrame; max_num_rows_cols::Tuple{Int,Int} =  TestingUtilities.show_df_max_nrows_ncols[], kwargs...) = show_truncated_df(ctx, value; max_num_rows_cols, kwargs...)
×
216

UNCOV
217
    TestingUtilities.should_print_differing_fields_header(::Type{<:AbstractDataFrame}) = false
×
218
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc