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

curtd / TestingUtilities.jl / 20152465503

12 Dec 2025 12:52AM UTC coverage: 88.134% (-0.6%) from 88.777%
20152465503

Pull #40

github

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

8 of 8 new or added lines in 2 files covered. (100.0%)

10 existing lines in 4 files now uncovered.

1233 of 1399 relevant lines covered (88.13%)

140.0 hits per line

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

73.94
/src/show_diff/common.jl
1
function cpad(s, width::Int, p::AbstractChar=' ')
135✔
2
    if s isa AbstractString 
174✔
3
        t = s 
66✔
4
    else
5
        t = repr(s)
24✔
6
    end
7
    tw = textwidth(t)
90✔
8
    if tw < width 
90✔
9
        l = ceil(Int, (width-tw)/2)
90✔
10
        r = floor(Int, (width-tw)/2)
90✔
11
        return p^l * t * p^r
90✔
12
    elseif tw > width
×
13
        return t[1:width-3]*"..."
×
14
    else
15
        return t 
×
16
    end
17
end
18

19
function _show_maybe_styled(io, val; has_colour::Bool=false, is_matching::Bool)
416✔
20
    if has_colour 
208✔
21
        if is_matching
66✔
22
            style = NamedTuple(show_diff_matching_style)
36✔
23
        else
24
            style = NamedTuple(show_diff_differing_style)
30✔
25
        end
26
        printstyled(io, val; style...)
132✔
27
    else
28
        print(io, val)
142✔
29
    end
30
    return nothing
208✔
31
end
32

33
show_maybe_styled(io, val; has_colour::Bool=false, is_matching::Bool) = _show_maybe_styled(io, val; has_colour, is_matching)
408✔
34

35
function show_maybe_styled(io, val::AbstractVector; has_colour::Bool=false, is_matching::Bool, max_entries::Int=length(val))
8✔
36
    if length(val) > max_entries 
4✔
37
        ks = floor(Int, max_entries / 2)
×
38
        ke = ceil(Int, max_entries / 2)
×
39
        _show_maybe_styled(io, '['; has_colour, is_matching)
×
40
        for (c, i) in enumerate(eachindex(val))
×
41
            _show_maybe_styled(io, val[i]; has_colour, is_matching)
×
42
            _show_maybe_styled(io, ", "; has_colour, is_matching)
×
43
            c ≥ ks && break
×
UNCOV
44
        end
×
45
        _show_maybe_styled(io, "..., "; has_colour, is_matching)
×
46
        vals = Vector{eltype(val)}(undef, ke)
×
47
        for (c, i) in enumerate(Iterators.reverse(eachindex(val)))
×
48
            vals[c] = val[i]
×
49
            c ≥ ke && break
×
UNCOV
50
        end
×
51
        for (i, v) in enumerate(Iterators.reverse(vals))
×
52
            _show_maybe_styled(io, v; has_colour, is_matching)
×
53
            if i != ke
×
54
                _show_maybe_styled(io, ", "; has_colour, is_matching)
×
55
            end
UNCOV
56
        end
×
57
        _show_maybe_styled(io, ']'; has_colour, is_matching)
×
58
    else
59
        _show_maybe_styled(io, val; has_colour, is_matching)
4✔
60
    end
61
end
62

63
function print_spaces(io::IO, num_spaces::Int) 
88✔
64
    if num_spaces > 0
176✔
65
        print(io, repeat(' ', num_spaces))
50✔
66
    end
67
end
68

69
function print_aligned(io::IO, str::AbstractString, width, max_width, separator, justify::Symbol)
304✔
70
    if justify == :left
304✔
71
        print(io, str)
176✔
72
        print_spaces(io, max_width-width)
176✔
73
    elseif justify == :right 
128✔
74
        print_spaces(io, max_width-width)
×
75
        print(io, str)
×
76
    else
77
        print(io, str)
128✔
78
    end
79
    print(io, separator)
304✔
80
end
81

82
struct PrintAligned 
83
    header_strs::Vector{String}
84
    widths::Vector{Int}
85
    max_width::Int
86
    separator::String 
87
    justify::Symbol
88
    function PrintAligned(header_strs::Vector{String}; separator::String=" = ", justify::Symbol=:left)
304✔
89
        widths = textwidth.(header_strs)
152✔
90
        max_width = maximum(widths)
228✔
91
        return new(header_strs, widths, max_width, separator, justify) 
152✔
92
    end
93
end
94
PrintAligned(header_strs::String...; kwargs...) = PrintAligned(collect(header_strs); kwargs...)
456✔
95

96
function (p::PrintAligned)(io::IO, i::Int)
152✔
97
    (i < 0 || i > length(p.header_strs)) && throw(BoundsError(p.header_strs, i))
304✔
98
    print_aligned(io, p.header_strs[i], p.widths[i], p.max_width, p.separator, p.justify)
304✔
99
    return nothing
304✔
100
end
101
Base.eachindex(p::PrintAligned) = eachindex(p.header_strs)
44✔
102

103
should_ignore_struct_type((@nospecialize x)) = false 
21✔
104
should_print_differing_fields_header((@nospecialize x)) = true
4✔
105

106
for T in (String, AbstractDict, AbstractVector, AbstractSet, Dates.TimeType, Dates.AbstractDateTime, Dates.Period)
107
    @eval should_ignore_struct_type(::Type{<:$T}) = true
4✔
108
    @eval should_print_differing_fields_header(::Type{<:$T}) = false
6✔
109
end
110

111
function append_type_str(header, type_str::String)
132✔
112
    if !isempty(type_str)
264✔
113
        return string(header)*"::$type_str"
88✔
114
    else
115
        return string(header)
176✔
116
    end
117
end
118

119
function header_and_type(header, type_str::String) 
167✔
120
    append_type_str(_show_name_str(header), type_str)
370✔
121
end
122

123
abstract type AbstractTypeCategory end 
124
struct TypeTypeCat <: AbstractTypeCategory end 
4✔
125
struct StructTypeCat <: AbstractTypeCategory end 
262✔
126
struct TupleTypeCat <: AbstractTypeCategory end 
6✔
127
struct NamedTupleTypeCat <: AbstractTypeCategory end
12✔
128
struct VectorTypeCat <: AbstractTypeCategory end 
12✔
129
struct DictTypeCat <: AbstractTypeCategory end 
4✔
UNCOV
130
struct SetTypeCat <: AbstractTypeCategory end 
×
131
struct GenericTypeCat <: AbstractTypeCategory end 
156✔
132

133
typecat_description(::TypeTypeCat) = "Type"
×
134
typecat_description(::StructTypeCat) = "Struct"
1✔
135
typecat_description(::TupleTypeCat) = "Tuple"
×
136
typecat_description(::NamedTupleTypeCat) = "NamedTuple"
×
137
typecat_description(::VectorTypeCat) = "Vector"
×
138
typecat_description(::DictTypeCat) = "Dict"
×
139
typecat_description(::SetTypeCat) = "Set"
×
140
typecat_description(::GenericTypeCat) = "generic value"
1✔
141

142
function type_category(::Type{T}) where {T}
178✔
143
    if T <: Type
356✔
144
        return TypeTypeCat()
4✔
145
    elseif T <: AbstractVector 
352✔
146
        return VectorTypeCat()
12✔
147
    elseif T <: Tuple
340✔
148
        return TupleTypeCat()
6✔
149
    elseif T <: NamedTuple 
334✔
150
        return NamedTupleTypeCat()
12✔
151
    elseif T <: AbstractDict
322✔
152
        return DictTypeCat()
4✔
153
    elseif T <: AbstractSet
318✔
154
        return SetTypeCat()
×
155
    elseif isstructtype(T)
364✔
156
        return StructTypeCat()
162✔
157
    else
158
        return GenericTypeCat()
156✔
159
    end
160
end
161

162
function show_differing_fieldnames(ctx::IOContext, expected_fields, results_fields; expected_name="expected", expected_type_str::String="", result_name="result", result_type_str::String="")
×
163
    has_colour = get(ctx, :color, false)::Bool
×
164
    expected_header = "fieldnames($(append_type_str(lstrip(string(expected_name)), expected_type_str)))"
×
165
    result_header = "fieldnames($(append_type_str(lstrip(string(result_name)), result_type_str)))"
×
166
    println(ctx, "Reason: `", expected_header, " != ", result_header ,'`')
×
167
    p = PrintAligned(expected_header, result_header; separator=" = ")
×
168
    matching = intersect(expected_fields, results_fields)
×
169
    for (i, fields) in enumerate((expected_fields, results_fields))
×
170
        p(ctx, i)
×
171
        print(ctx, '(')
×
172
        for field in fields
×
173
            show_maybe_styled(ctx, ":$field"; has_colour=has_colour, is_matching=field in matching)
×
UNCOV
174
        end
×
175
        println(ctx, ')')
×
UNCOV
176
    end
×
177
    return nothing
×
178
end
179

180
function show_diff(expected, result; io=stderr, compact::Bool=true, kwargs...)
222✔
181
    return show_diff(IOContext(io, :compact=>compact), expected, result; kwargs...)
148✔
182
end
183

184
function show_diff_header(value, value_name, value_type_str::String; show_type_str::Bool=false)
502✔
185
    if show_type_str && isempty(value_type_str)
264✔
186
        value_type_str = string(typeof(value))
40✔
187
    end
188
    return header_and_type(value_name, value_type_str)
370✔
189
end
190

191
function show_diff_mismatched_type_cat(ctx::IOContext, expected_type_category, expected, expected_type, result_type_category, result, result_type; expected_name, result_name)
4✔
192
    has_colour = get(ctx, :color, false)::Bool
4✔
193
    expected_header = show_diff_header(expected, expected_name, string(expected_type); show_type_str=true)
2✔
194
    result_header = show_diff_header(result, result_name, string(result_type); show_type_str=true)
2✔
195
    println(ctx, "Reason: Mismatched type categories")
2✔
196
    show_maybe_styled(ctx, "$expected_header is a $(typecat_description(expected_type_category)), but $result_header is a $(typecat_description(result_type_category))"; has_colour=has_colour, is_matching=false)
2✔
197
    println(ctx)
2✔
198
end
199

200
function show_diff(ctx::IOContext, expected, result; expected_name="expected", result_name="result", kwargs...)
193✔
201
    expected_type = typeof(expected)
128✔
202
    result_type = typeof(result)
128✔
203
    expected_typecat = type_category(expected_type)
128✔
204
    result_typecat = type_category(result_type)
128✔
205
    if expected_typecat == result_typecat
128✔
206
        return show_diff(expected_typecat, ctx, expected, result; expected_name=expected_name, result_name=result_name, kwargs...)
126✔
207
    else
208
        return show_diff_mismatched_type_cat(ctx, expected_typecat, expected, expected_type, result_typecat, result, result_type; expected_name=expected_name, result_name=result_name)
2✔
209
    end
210
end
211

212
function show_diff_generic(ctx::IOContext, expected, result; expected_name="expected", expected_type_str::String="", result_name="result", result_type_str::String="", show_type_str::Bool=false, justify_headers::Symbol=:left, kwargs...)
172✔
213
    expected_header = show_diff_header(expected, expected_name, expected_type_str; show_type_str=show_type_str)
132✔
214
    result_header = show_diff_header(result, result_name, result_type_str; show_type_str=show_type_str)
94✔
215
   
216
    p = PrintAligned(expected_header, result_header; separator=" = ", justify=justify_headers)
129✔
217
    for (i, value) in enumerate((expected, result))
88✔
218
        p(ctx, i)
172✔
219
        show_value(ctx, value; kwargs...)
174✔
220
    end
258✔
221
    
222
    return nothing
86✔
223
end
224

225
show_diff(::AbstractTypeCategory, ctx::IOContext, expected, result; expected_name="expected", result_name="result", kwargs...) = show_diff_generic(ctx::IOContext, expected, result; expected_name=expected_name, result_name=result_name, justify_headers=:none, kwargs...)
128✔
226

227
function show_diff(::VectorTypeCat, ctx::IOContext, expected, result; expected_name="expected", result_name="result", max_show_differing_entries::Int=10, max_per_col_width::Int=((displaysize(ctx)[2]*3) ÷ 10), kwargs...)
12✔
228
    has_colour = get(ctx, :color, false)::Bool
12✔
229
    expected_indices = eachindex(expected)
6✔
230
    result_indices = eachindex(result)
6✔
231
    if expected_indices != result_indices
6✔
232
        expected_diff_result = setdiff(expected_indices, result_indices)
2✔
233
        result_diff_expected = setdiff(result_indices, expected_indices)
2✔
234

235
        println(ctx, "Reason: `eachindex($expected_name) != eachindex($result_name)`")
2✔
236

237
        p = PrintAligned("`eachindex($expected_name) ⧵ eachindex($result_name)`", "`eachindex($result_name) ⧵ eachindex($expected_name)`"; separator=" = ")
3✔
238
        p(ctx, 1)
2✔
239

240
        show_maybe_styled(ctx, expected_diff_result; has_colour, is_matching=false, max_entries=max_show_differing_entries)
2✔
241
        println(ctx)
2✔
242
        p(ctx, 2)
2✔
243
        show_maybe_styled(ctx, result_diff_expected; has_colour, is_matching=false, max_entries=max_show_differing_entries)
2✔
244
        println(ctx)
2✔
245
        return nothing
2✔
246
    end
247
    differing_indices = keytype(expected)[ i for i in expected_indices if !isequal(expected[i], result[i]) ]
4✔
248
    n_diff = length(differing_indices)
4✔
249

250
    ks = floor(Int, min(n_diff, max_show_differing_entries) / 2)
4✔
251
    ke = ceil(Int, min(n_diff, max_show_differing_entries) / 2)
4✔
252

253
    index_diffs = vcat(differing_indices[1:ks], differing_indices[end-ke+1:end])
6✔
254
    expected_diffs = repr.(getindex.(Ref(expected), index_diffs))
4✔
255
    result_diffs = repr.(getindex.(Ref(result), index_diffs))
4✔
256

257

258
    max_col1_width = max( min.(vcat([textwidth("index")], textwidth.(repr.(index_diffs))), max_per_col_width)...) + 2
8✔
259
    max_col2_width = max( min.(vcat([textwidth(string(expected_name))], textwidth.(expected_diffs)), max_per_col_width)...) + 2
8✔
260
    max_col3_width = max( min.(vcat([textwidth(string(expected_name))], textwidth.(result_diffs)), max_per_col_width)...) + 2
8✔
261
   
262
    println(ctx, cpad("index", max_col1_width), "|", cpad(string(expected_name), max_col2_width), "|", cpad(string(result_name), max_col3_width))
4✔
263

264
    for i in 1:length(index_diffs)
4✔
265
        println(ctx, cpad(index_diffs[i], max_col1_width), "|", cpad(expected_diffs[i], max_col2_width), "|", cpad(result_diffs[i], max_col3_width))
24✔
266
        if i == ks && n_diff > max_show_differing_entries            
24✔
267
            println(ctx, cpad("⋮", max_col1_width), "|", cpad("⋮", max_col2_width), "|", cpad("⋮", max_col3_width))    
2✔
268
        end
269
    end
44✔
270
    return nothing
4✔
271
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