Skip to content

Commit cd0feec

Browse files
Mal Millermmiller-max
authored andcommitted
Allow and test for custom TestSets in pkg tests
1 parent c4ecb80 commit cd0feec

File tree

23 files changed

+473
-79
lines changed

23 files changed

+473
-79
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "TestReports"
22
uuid = "dcd651b4-b50a-5b6b-8f22-87e9f253a252"
3-
version = "0.5.1"
3+
version = "0.5.2"
44

55
[deps]
66
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"

docs/src/manual.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ julia> TestReports.test("MyPackage")
2323
It is intended that it `runtests.jl` will not need to be changed to generate
2424
a report (unless [properties are being added](#Adding-Properties)).
2525

26-
This does assume, however, that `DefaultTestSet`s are being used. In the case of
26+
This does assume, however, that no custom `TestSet`s are being used. In the case of
2727
custom `TestSet`s, please see the [discussion](#Custom-TestSet-Types) below.
2828

2929
The typical use in a CI process would be:
@@ -210,9 +210,20 @@ However at a minimum, for a custom `TestSet` type to work with `TestReports` it
210210
- Push itself onto its parent when finishing, if it is not at the top level
211211
- Have `description` and `results` fields as per a `DefaultTestSet`
212212

213-
Testsuite properties cannot be added to a `TestSet` that is not a `ReportingTestSet`,
214-
(i.e. any `TestSet` that has the type specified to be something other than a
215-
`ReportingTestSet`, or that inherits a specified type from a parent). If no types
216-
are specified, `TestReports.test` will ensure that all child `TestSet`s inherit
217-
the `ReportingTestSet` type.
213+
The following information in a JUnit XML relies on the functionality of `ReportingTestSet`s
214+
but can be added to your own custom `TestSet` as described in the table.
215+
216+
|Information|Description|
217+
|---|---|
218+
| testcase time | This is extracted from a `ReportingResult` by the `TestReports.time_taken` function. For standard `Result`s, rather than `ReportingResult`s, this function returns `Dates.Millisecond(0)`. This function can be extended for other custom `Result` types.|
219+
| testsuite time| This is extracted from a `TestSet` by the `TestReports.time_taken` function, which can be extended for custom `TestSet`s. If not extended, the `AbstractTestSet` method will be used and the value defaults to `Dates.Millisecond(0)`. |
220+
| testsuite timestamp| This is extracted from a `TestSet` by the `TestReports.start_time` function, which can be extended for custom `TestSet`s. If not extended, the `AbstractTestSet` method will be used and the value defaults to `Dates.now()`. |
221+
| testsuite hostname| This is extracted from a `TestSet` by the `TestReports.hostname` function, which can be extended for custom `TestSet`s. If not extended, the `AbstractTestSet` method will be used and the value defaults to `gethostname()`. |
222+
| testsuite properties| This is extracted from a `TestSet` by the `TestReports.properties` function, which can be extended for custom `TestSet`s. If not extended, the `AbstractTestSet` method will be used and the value defaults to `nothing`. |
223+
224+
For further details on extending these fuctions, see the docstrings in [TestSets](@ref).
225+
226+
The [source code of `TestReports`](https://github.com/JuliaTesting/TestReports.jl/blob/master/src/testsets.jl) can be used as a starting point for including this behaviour in your custom `TestSet`s.
227+
228+
If no `TestSet` types are specified (as per the standard `Test` approach), `TestSet` functionality will ensure that all child `TestSet`s inherit the `ReportingTestSet` type.
218229

src/TestReports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Test: Result, Fail, Broken, Pass, Error, scrub_backtrace
99

1010
export ReportingTestSet, any_problems, report, recordproperty
1111

12+
include("v1_compat.jl")
1213
include("./testsets.jl")
1314
include("to_xml.jl")
1415
include("runner.jl")

src/recordproperty.jl

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ Adds a property to a testset with `name` and `value` that will in turn be added
77
to the `<properties>` node of the corresponding testsuite in the JUnit XML.
88
99
Multiple properties can be added to one testset, but if the same property is set on
10-
both parent and child testsets, the value in the parent testset takes precedence over
11-
that in the child.
10+
both parent and child testsets, the value in the child testset takes precedence over
11+
that in the parent.
1212
1313
The suggested use of this function is to place it inside a testset with unspecified type
1414
(see Examples). This will ensure that `Pkg.test` is unnaffected, but that the properties
15-
are added to the report when `TestReports.test` is used. This is because `TestReports`
16-
wraps package tests in a `ReportingTestSet`, and the function only adds a property when
17-
it is within a `ReportingTestSet`.
15+
are added to the report when `TestReports.test` is used. This is because properties are
16+
only added when the `Testset` type has a `TestReports.properties` method defined, as does
17+
the `ReportingTestSet` used by `TestReports`. `TestReports.properties` can be extended
18+
for custom `TestSet`s.
1819
19-
If a child testset is a `ReportingTestSet` but its parent isn't, the property should
20+
If a child testset has this method defined but its parent doesn't, the property should
2021
be in the report when `TestReport.test` is used, assuming that the parent testset
2122
type doesn't do anything to affect the reporting behaviour. However this is not tested
2223
functionality.
@@ -37,10 +38,14 @@ using TestReports
3738
@test 2==2
3839
end
3940
```
41+
42+
See also: [`properties`](@ref)
4043
"""
4144
function recordproperty(name::String, val)
42-
if get_testset() isa ReportingTestSet
43-
if haskey(get_testset().properties, name)
45+
properties_dict = properties(get_testset())
46+
if !isnothing(properties_dict)
47+
!isa(properties_dict, AbstractDict) && throw(PkgTestError("TestReports.properties method for custom testset must return a dictionary."))
48+
if haskey(properties_dict, name)
4449
throw(PkgTestError("Property $name already set and can't be set again in the same testset"))
4550
else
4651
get_testset().properties["$name"] = val

src/testsets.jl

Lines changed: 107 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using Dates
22
import Test: Result
33

4+
####################
5+
# Type Definitions #
6+
####################
47
"""
58
ReportingResult{T}
69
@@ -17,6 +20,16 @@ end
1720
Base.:(==)(r1::ReportingResult, r2::ReportingResult) = r1.result == r2.result
1821
Base.hash(f::ReportingResult, h::UInt) = hash(f.result, h)
1922

23+
"""
24+
time_taken(result::ReportingResult)
25+
time_taken(result::Result)
26+
27+
For a `ReportingResult`, return the time taken for the test to run.
28+
For a `Result`, return Dates.Millisecond(0).
29+
"""
30+
time_taken(result::ReportingResult) = result.time_taken
31+
time_taken(result::Result) = Dates.Millisecond(0)
32+
2033
"""
2134
ReportingTestSet
2235
@@ -87,8 +100,76 @@ function finish(ts::ReportingTestSet)
87100
flatten_results!(ts)
88101
end
89102

90-
#############
103+
#################################
104+
# Accessing and setting methods #
105+
#################################
106+
"""
107+
properties(ts::ReportingTestSet)
108+
properties(ts::AbstractTestSet)
109+
110+
Get the properties dictionary of a `ReportingTestSet`, returns
111+
nothing for an `AbstractTestSet`. Can be extended for custom
112+
`TestSet`s, and must return either a `Dict` or `nothing`.
113+
"""
114+
properties(ts::ReportingTestSet) = ts.properties
115+
properties(ts::AbstractTestSet) = nothing
116+
117+
"""
118+
start_time(ts::ReportingTestSet)
119+
start_time(ts::AbstractTestSet)
120+
121+
Get the start time of a `ReportingTestSet`, returns `Dates.now()`
122+
for an `AbstractTestSet`. Can be extended for custom `TestSet`s,
123+
must return a `DateTime`.
124+
"""
125+
start_time(ts::ReportingTestSet) = ts.start_time
126+
start_time(ts::AbstractTestSet) = Dates.now()
127+
128+
"""
129+
time_taken(ts::ReportingTestSet)
130+
time_taken(ts::AbstractTestSet)
131+
132+
Get the time taken of a `ReportingTestSet`, returns `Dates.Millisecond(0)`
133+
for an `AbstractTestSet`. Can be extended for custom `TestSet`s, must return
134+
a `Dates.Millisecond`.
135+
"""
136+
time_taken(ts::ReportingTestSet) = ts.time_taken
137+
time_taken(ts::AbstractTestSet) = Dates.Millisecond(0)
138+
139+
"""
140+
hostname(ts::ReportingTestSet)
141+
hostname(ts::AbstractTestSet)
142+
143+
Get the hostname of a `ReportingTestSet`, returns `gethostname()`
144+
for an `AbstractTestSet`. Can be extended for custom `TestSet`s,
145+
must return a `string`.
146+
"""
147+
hostname(ts::ReportingTestSet) = ts.hostname
148+
hostname(ts::AbstractTestSet) = gethostname()
149+
150+
"""
151+
set_time_taken!(ts::ReportingTestSet, time_taken)
152+
set_time_taken!(ts::AbstractTestSet, time_taken)
153+
154+
Sets the time taken field of a `ReportingTestSet`. This is used when flattening
155+
`ReportingTestSet`s for report generation and an be extended for custom `TestSet`s.
156+
"""
157+
set_time_taken!(ts::ReportingTestSet, time_taken::Millisecond) = ts.time_taken = time_taken
158+
set_time_taken!(ts::AbstractTestSet, time_taken::Millisecond) = nothing
159+
160+
"""
161+
set_start_time!(ts::ReportingTestSet, start_time)
162+
set_start_time!(ts::AbstractTestSet, start_time)
163+
164+
Sets the start time field of a `ReportingTestSet`. This is used when flattening
165+
`ReportingTestSet`s for report generation and an be extended for custom `TestSet`s.
166+
"""
167+
set_start_time!(ts::ReportingTestSet, start_time::DateTime) = ts.start_time = start_time
168+
set_start_time!(ts::AbstractTestSet, start_time::DateTime) = nothing
91169

170+
############
171+
# Checking #
172+
############
92173
"""
93174
any_problems(ts)
94175
@@ -104,9 +185,9 @@ any_problems(::Fail) = true
104185
any_problems(::Broken) = false
105186
any_problems(::Error) = true
106187

107-
######################################
108-
# result flattening
109-
188+
#####################
189+
# Tesult flattening #
190+
#####################
110191

111192
"""
112193
flatten_results!(ts::AbstractTestSet)
@@ -179,35 +260,34 @@ _flatten_results!(rs::Result) = [rs]
179260

180261
"""
181262
update_testset_properties!(childts::AbstractTestSet, ts::AbstractTestSet)
182-
update_testset_properties!(childts::ReportingTestSet, ts::ReportingTestSet)
183263
184264
Adds properties of `ts` to `childts`. If any properties being added already exist in
185265
`childts`, a warning is displayed and the value in `ts` is overwritten.
186266
187-
If `ts` and\\or `childts` is not a `ReportingTestSet`, this is handled in the
188-
`AbstractTestSet` method:
189-
- If `ts` is not a `ReportingTestSet`, it has no properties to add to `childts`
267+
If the types of `ts` and\\or `childts` do not a method defined for `TestReports.properties`,
268+
this is handled as follows:
269+
- If method not defined for `typeof(ts)`, it has no properties to add to `childts`
190270
and therefore nothing happens.
191-
- If `childts` is not a `ReportingTestSet` and `ts` has properties, then a warning
271+
- If method not defined for `typeof(chidlts)` and `ts` has properties, then a warning
192272
is shown.
273+
274+
See also: [`properties`](@ref)
193275
"""
194276
function update_testset_properties!(childts::AbstractTestSet, ts::AbstractTestSet)
195-
if !isa(childts, ReportingTestSet) && isa(ts, ReportingTestSet) && !isempty(ts.properties)
196-
@warn "Properties of testset $(ts.description) can not be added to child testset $(childts.description) as it is not a ReportingTestSet."
197-
end
198-
# No need to check if childts is ReportingTestSet and ts isn't, as if this is the case
199-
# ts has no properties to apply to childts.
200-
return childts
201-
end
202-
function update_testset_properties!(childts::ReportingTestSet, ts::ReportingTestSet)
203-
parent_keys = keys(ts.properties)
204-
child_keys = keys(childts.properties)
205-
# Loop through keys so that warnings can be issued for any duplicates
206-
for key in parent_keys
207-
if key in child_keys
208-
@warn "Property $key in testest $(ts.description) overwritten by child testset $(childts.description)"
209-
else
210-
childts.properties[key] = ts.properties[key]
277+
if isnothing(properties(childts)) && !isnothing(properties(ts)) && !isempty(properties(ts))
278+
@warn "Properties of testset $(ts.description) can not be added to child testset $(childts.description) as it does not have a TestReports.properties method defined."
279+
# No need to check if childts is has properties defined and ts doesn't as if this is the case
280+
# ts has no properties to add to that of childts.
281+
elseif !isnothing(properties(ts))
282+
parent_keys = keys(properties(ts))
283+
child_keys = keys(properties(childts))
284+
# Loop through keys so that warnings can be issued for any duplicates
285+
for key in parent_keys
286+
if key in child_keys
287+
@warn "Property $key in testest $(ts.description) overwritten by child testset $(childts.description)"
288+
else
289+
properties(childts)[key] = properties(ts)[key]
290+
end
211291
end
212292
end
213293
return childts
@@ -231,8 +311,8 @@ function handle_top_level_results!(ts::AbstractTestSet)
231311
ts.results = AbstractTestSet[]
232312
ts_nested = ReportingTestSet("Top level tests")
233313
ts_nested.results = original_results[isa_Result]
234-
ts_nested.time_taken = sum(x -> x.time_taken, ts_nested.results)
235-
ts_nested.start_time = ts.start_time
314+
set_time_taken!(ts_nested, sum(x -> time_taken(x)::Millisecond, ts_nested.results))
315+
set_start_time!(ts_nested, start_time(ts)::DateTime)
236316
push!(ts.results, ts_nested)
237317
append!(ts.results, original_results[.!isa_Result])
238318
end

0 commit comments

Comments
 (0)