Skip to content

Commit a8958af

Browse files
committed
Add io arguments to runtests instead of relying on IOCapture.
1 parent fc0b40a commit a8958af

File tree

4 files changed

+132
-90
lines changed

4 files changed

+132
-90
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ version = "0.1.2"
66
[deps]
77
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
88
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
9+
IOCapture = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
910
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
1011
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
1112
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
@@ -14,6 +15,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1415
[compat]
1516
Dates = "1"
1617
Distributed = "1"
18+
IOCapture = "0.2.5"
1719
Printf = "1"
1820
REPL = "1"
1921
Random = "1"

src/ParallelTestRunner.jl

Lines changed: 83 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ using Printf: @sprintf
99
using Base.Filesystem: path_separator
1010
import Test
1111
import Random
12+
import IOCapture
1213

1314
#Always set the max rss so that if tests add large global variables (which they do) we don't make the GC's life too hard
1415
if Sys.WORD_SIZE == 64
@@ -82,7 +83,8 @@ end
8283
#
8384

8485
struct TestIOContext
85-
io::IO
86+
stdout::IO
87+
stderr::IO
8688
lock::ReentrantLock
8789
name_align::Int
8890
elapsed_align::Int
@@ -92,28 +94,28 @@ struct TestIOContext
9294
rss_align::Int
9395
end
9496

95-
function test_IOContext(::Type{TestRecord}, io::IO, lock::ReentrantLock, name_align::Int)
97+
function test_IOContext(::Type{TestRecord}, stdout::IO, stderr::IO, lock::ReentrantLock, name_align::Int)
9698
elapsed_align = textwidth("Time (s)")
9799
gc_align = textwidth("GC (s)")
98100
percent_align = textwidth("GC %")
99101
alloc_align = textwidth("Alloc (MB)")
100102
rss_align = textwidth("RSS (MB)")
101103

102104
return TestIOContext(
103-
io, lock, name_align, elapsed_align, gc_align, percent_align,
105+
stdout, stderr, lock, name_align, elapsed_align, gc_align, percent_align,
104106
alloc_align, rss_align
105107
)
106108
end
107109

108110
function print_header(::Type{TestRecord}, ctx::TestIOContext, testgroupheader, workerheader)
109111
lock(ctx.lock)
110112
try
111-
printstyled(ctx.io, " "^(ctx.name_align + textwidth(testgroupheader) - 3), " | ")
112-
printstyled(ctx.io, " | ---------------- CPU ---------------- |\n", color = :white)
113-
printstyled(ctx.io, testgroupheader, color = :white)
114-
printstyled(ctx.io, lpad(workerheader, ctx.name_align - textwidth(testgroupheader) + 1), " | ", color = :white)
115-
printstyled(ctx.io, "Time (s) | GC (s) | GC % | Alloc (MB) | RSS (MB) |\n", color = :white)
116-
flush(ctx.io)
113+
printstyled(ctx.stdout, " "^(ctx.name_align + textwidth(testgroupheader) - 3), " | ")
114+
printstyled(ctx.stdout, " | ---------------- CPU ---------------- |\n", color = :white)
115+
printstyled(ctx.stdout, testgroupheader, color = :white)
116+
printstyled(ctx.stdout, lpad(workerheader, ctx.name_align - textwidth(testgroupheader) + 1), " | ", color = :white)
117+
printstyled(ctx.stdout, "Time (s) | GC (s) | GC % | Alloc (MB) | RSS (MB) |\n", color = :white)
118+
flush(ctx.stdout)
117119
finally
118120
unlock(ctx.lock)
119121
end
@@ -122,12 +124,13 @@ end
122124
function print_test_started(::Type{TestRecord}, wrkr, test, ctx::TestIOContext)
123125
lock(ctx.lock)
124126
try
125-
printstyled(test, color = :white)
127+
printstyled(ctx.stdout, test, color = :white)
126128
printstyled(
129+
ctx.stdout,
127130
lpad("($wrkr)", ctx.name_align - textwidth(test) + 1, " "), " |",
128131
" "^ctx.elapsed_align, "started at $(now())\n", color = :white
129132
)
130-
flush(ctx.io)
133+
flush(ctx.stdout)
131134
finally
132135
unlock(ctx.lock)
133136
end
@@ -136,22 +139,22 @@ end
136139
function print_test_finished(test, wrkr, record::TestRecord, ctx::TestIOContext)
137140
lock(ctx.lock)
138141
try
139-
printstyled(ctx.io, test, color = :white)
140-
printstyled(ctx.io, lpad("($wrkr)", ctx.name_align - textwidth(test) + 1, " "), " | ", color = :white)
142+
printstyled(ctx.stdout, test, color = :white)
143+
printstyled(ctx.stdout, lpad("($wrkr)", ctx.name_align - textwidth(test) + 1, " "), " | ", color = :white)
141144
time_str = @sprintf("%7.2f", record.time)
142-
printstyled(ctx.io, lpad(time_str, ctx.elapsed_align, " "), " | ", color = :white)
145+
printstyled(ctx.stdout, lpad(time_str, ctx.elapsed_align, " "), " | ", color = :white)
143146

144147
gc_str = @sprintf("%5.2f", record.gctime)
145-
printstyled(ctx.io, lpad(gc_str, ctx.gc_align, " "), " | ", color = :white)
148+
printstyled(ctx.stdout, lpad(gc_str, ctx.gc_align, " "), " | ", color = :white)
146149
percent_str = @sprintf("%4.1f", 100 * record.gctime / record.time)
147-
printstyled(ctx.io, lpad(percent_str, ctx.percent_align, " "), " | ", color = :white)
150+
printstyled(ctx.stdout, lpad(percent_str, ctx.percent_align, " "), " | ", color = :white)
148151
alloc_str = @sprintf("%5.2f", record.bytes / 2^20)
149-
printstyled(ctx.io, lpad(alloc_str, ctx.alloc_align, " "), " | ", color = :white)
152+
printstyled(ctx.stdout, lpad(alloc_str, ctx.alloc_align, " "), " | ", color = :white)
150153

151154
rss_str = @sprintf("%5.2f", record.rss / 2^20)
152-
printstyled(ctx.io, lpad(rss_str, ctx.rss_align, " "), " |\n", color = :white)
155+
printstyled(ctx.stdout, lpad(rss_str, ctx.rss_align, " "), " |\n", color = :white)
153156

154-
flush(ctx.io)
157+
flush(ctx.stdout)
155158
finally
156159
unlock(ctx.lock)
157160
end
@@ -160,13 +163,14 @@ end
160163
function print_test_errorred(::Type{TestRecord}, wrkr, test, ctx::TestIOContext)
161164
lock(ctx.lock)
162165
try
163-
printstyled(test, color = :red)
166+
printstyled(ctx.stderr, test, color = :red)
164167
printstyled(
168+
ctx.stderr,
165169
lpad("($wrkr)", ctx.name_align - textwidth(test) + 1, " "), " |",
166170
" "^ctx.elapsed_align, " failed at $(now())\n", color = :red
167171
)
168172

169-
flush(ctx.io)
173+
flush(ctx.stderr)
170174
finally
171175
unlock(ctx.lock)
172176
end
@@ -318,6 +322,7 @@ Several keyword arguments are also supported:
318322
- `--quickfail`: Stop the entire test run as soon as any test fails
319323
- `--jobs=N`: Use N worker processes (default: based on CPU threads and available memory)
320324
- `TESTS...`: Filter tests by name, matched using `startswith`
325+
- `stdout` and `stderr`: I/O streams to write to (default: `Base.stdout` and `Base.stderr`)
321326
322327
## Behavior
323328
@@ -352,7 +357,7 @@ issues during long test runs. The memory limit is set based on system architectu
352357
"""
353358
function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
354359
custom_tests::Dict{String, Expr}=Dict{String, Expr}(), init_code = :(),
355-
test_worker = Returns(nothing))
360+
test_worker = Returns(nothing), stdout = Base.stdout, stderr = Base.stderr)
356361
#
357362
# set-up
358363
#
@@ -453,7 +458,7 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
453458
if !set_jobs
454459
jobs = default_njobs()
455460
end
456-
@info "Running $jobs tests in parallel. If this is too many, specify the `--jobs=N` argument to the tests, or set the `JULIA_CPU_THREADS` environment variable."
461+
println(stdout, "Running $jobs tests in parallel. If this is too many, specify the `--jobs=N` argument to the tests, or set the `JULIA_CPU_THREADS` environment variable.")
457462

458463
# add workers
459464
addworkers(min(jobs, length(tests)))
@@ -490,7 +495,7 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
490495
while !done
491496
c = read(term, Char)
492497
if c == '\x3'
493-
println("\nCaught interrupt, stopping...")
498+
println(stderr, "\nCaught interrupt, stopping...")
494499
stop_work()
495500
break
496501
end
@@ -526,26 +531,26 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
526531
stderr.lock = print_lock
527532
end
528533

529-
io_ctx = test_IOContext(RecordType, stdout, print_lock, name_align)
534+
io_ctx = test_IOContext(RecordType, stdout, stderr, print_lock, name_align)
530535
print_header(RecordType, io_ctx, testgroupheader, workerheader)
531536

532537
status_lines_visible = Ref(0)
533538
function clear_status()
534539
if status_lines_visible[] > 0
535540
for i in 1:status_lines_visible[]
536-
width = displaysize(stdout)[2]
537-
print(stdout, "\r", " "^width, "\r")
541+
width = displaysize(io_ctx.stdout)[2]
542+
print(io_ctx.stdout, "\r", " "^width, "\r")
538543
if i < status_lines_visible[]
539-
print(stdout, "\033[1A") # Move up
544+
print(io_ctx.stdout, "\033[1A") # Move up
540545
end
541546
end
542-
print(stdout, "\r")
547+
print(io_ctx.stdout, "\r")
543548
status_lines_visible[] = 0
544549
end
545550
end
546551
function update_status()
547552
# only draw the status bar on actual terminals
548-
stdout isa Base.TTY || return
553+
io_ctx.stdout isa Base.TTY || return
549554

550555
# only draw if we have something to show
551556
isempty(running_tests) && return
@@ -562,7 +567,7 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
562567
end
563568
line2 = "Running: " * join(status_parts, ", ")
564569
## truncate
565-
max_width = displaysize(stdout)[2]
570+
max_width = displaysize(io_ctx.stdout)[2]
566571
if length(line2) > max_width
567572
line2 = line2[1:max_width-3] * "..."
568573
end
@@ -580,10 +585,10 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
580585

581586
# display
582587
clear_status()
583-
println(stdout, line1)
584-
println(stdout, line2)
585-
print(stdout, line3)
586-
flush(stdout)
588+
println(io_ctx.stdout, line1)
589+
println(io_ctx.stdout, line2)
590+
print(io_ctx.stdout, line3)
591+
flush(io_ctx.stdout)
587592
status_lines_visible[] = 3
588593
end
589594

@@ -721,7 +726,7 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
721726
try
722727
while true
723728
if any(istaskfailed, tasks)
724-
println("\nCaught an error, stopping...")
729+
println(io_ctx.stderr, "\nCaught an error, stopping...")
725730
break
726731
elseif done || (isempty(tests) && isempty(running_tests))
727732
break
@@ -784,19 +789,23 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
784789
fake
785790
elseif isa(resp, RemoteException) &&
786791
isa(resp.captured.ex, Test.TestSetException)
787-
println("Worker $(resp.pid) failed running test $(testname):")
788-
Base.showerror(stdout, resp.captured)
789-
println()
792+
println(io_ctx.stderr, "Worker $(resp.pid) failed running test $(testname):")
793+
Base.showerror(io_ctx.stderr, resp.captured)
794+
println(io_ctx.stderr)
795+
790796
fake = Test.DefaultTestSet(testname)
791-
for i in 1:resp.captured.ex.pass
792-
Test.record(fake, Test.Pass(:test, nothing, nothing, nothing, nothing))
793-
end
794-
for i in 1:resp.captured.ex.broken
795-
Test.record(fake, Test.Broken(:test, nothing))
796-
end
797-
for t in resp.captured.ex.errors_and_fails
798-
Test.record(fake, t)
797+
c = IOCapture.capture() do
798+
for i in 1:resp.captured.ex.pass
799+
Test.record(fake, Test.Pass(:test, nothing, nothing, nothing, nothing))
800+
end
801+
for i in 1:resp.captured.ex.broken
802+
Test.record(fake, Test.Broken(:test, nothing))
803+
end
804+
for t in resp.captured.ex.errors_and_fails
805+
Test.record(fake, t)
806+
end
799807
end
808+
print(io_ctx.stdout, c.output)
800809
fake
801810
else
802811
if !isa(resp, Exception)
@@ -807,7 +816,10 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
807816
# the test runner itself had some problem, so we may have hit a segfault,
808817
# deserialization errors or something similar. Record this testset as Errored.
809818
fake = Test.DefaultTestSet(testname)
810-
Test.record(fake, Test.Error(:nontest_error, testname, nothing, Base.ExceptionStack([(exception = resp, backtrace = [])]), LineNumberNode(1)))
819+
c = IOCapture.capture() do
820+
Test.record(fake, Test.Error(:nontest_error, testname, nothing, Base.ExceptionStack([(exception = resp, backtrace = [])]), LineNumberNode(1)))
821+
end
822+
print(io_ctx.stdout, c.output)
811823
fake
812824
end
813825

@@ -828,20 +840,37 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
828840
for test in [tests; collect(keys(running_tests))]
829841
(test in completed_tests) && continue
830842
fake = Test.DefaultTestSet(test)
831-
Test.record(fake, Test.Error(:test_interrupted, test, nothing, Base.ExceptionStack([(exception = "skipped", backtrace = [])]), LineNumberNode(1)))
843+
c = IOCapture.capture() do
844+
Test.record(fake, Test.Error(:test_interrupted, test, nothing, Base.ExceptionStack([(exception = "skipped", backtrace = [])]), LineNumberNode(1)))
845+
end
846+
print(io_ctx.stdout, c.output)
832847
with_testset(fake) do
833848
Test.record(o_ts, fake)
834849
end
835850
end
836851
end
837-
println()
838-
Test.print_test_results(o_ts, 1)
852+
println(io_ctx.stdout)
853+
if VERSION >= v"1.13.0-DEV.1033"
854+
Test.print_test_results(io_ctx.stdout, o_ts, 1)
855+
else
856+
c = IOCapture.capture() do
857+
Test.print_test_results(o_ts, 1)
858+
end
859+
print(io_ctx.stdout, c.output)
860+
end
839861
if (VERSION >= v"1.13.0-DEV.1037" && !Test.anynonpass(o_ts)) ||
840862
(VERSION < v"1.13.0-DEV.1037" && !o_ts.anynonpass)
841-
println(" \033[32;1mSUCCESS\033[0m")
863+
println(io_ctx.stdout, " \033[32;1mSUCCESS\033[0m")
842864
else
843-
println(" \033[31;1mFAILURE\033[0m\n")
844-
Test.print_test_errors(o_ts)
865+
println(io_ctx.stderr, " \033[31;1mFAILURE\033[0m\n")
866+
if VERSION >= v"1.13.0-DEV.1033"
867+
Test.print_test_errors(io_ctx.stdout, o_ts)
868+
else
869+
c = IOCapture.capture() do
870+
Test.print_test_errors(o_ts)
871+
end
872+
print(io_ctx.stdout, c.output)
873+
end
845874
throw(Test.FallbackTestSetException("Test run finished with errors"))
846875
end
847876
return nothing

test/Project.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
[deps]
2-
IOCapture = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
32
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

0 commit comments

Comments
 (0)