Skip to content

Commit ec9f8f0

Browse files
committed
Add io arguments to runtests instead of relying on IOCapture.
1 parent 5e6ec5f commit ec9f8f0

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
@@ -782,19 +787,23 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
782787
fake
783788
elseif isa(resp, RemoteException) &&
784789
isa(resp.captured.ex, Test.TestSetException)
785-
println("Worker $(resp.pid) failed running test $(testname):")
786-
Base.showerror(stdout, resp.captured)
787-
println()
790+
println(io_ctx.stderr, "Worker $(resp.pid) failed running test $(testname):")
791+
Base.showerror(io_ctx.stderr, resp.captured)
792+
println(io_ctx.stderr)
793+
788794
fake = Test.DefaultTestSet(testname)
789-
for i in 1:resp.captured.ex.pass
790-
Test.record(fake, Test.Pass(:test, nothing, nothing, nothing, nothing))
791-
end
792-
for i in 1:resp.captured.ex.broken
793-
Test.record(fake, Test.Broken(:test, nothing))
794-
end
795-
for t in resp.captured.ex.errors_and_fails
796-
Test.record(fake, t)
795+
c = IOCapture.capture() do
796+
for i in 1:resp.captured.ex.pass
797+
Test.record(fake, Test.Pass(:test, nothing, nothing, nothing, nothing))
798+
end
799+
for i in 1:resp.captured.ex.broken
800+
Test.record(fake, Test.Broken(:test, nothing))
801+
end
802+
for t in resp.captured.ex.errors_and_fails
803+
Test.record(fake, t)
804+
end
797805
end
806+
print(io_ctx.stdout, c.output)
798807
fake
799808
else
800809
if !isa(resp, Exception)
@@ -805,7 +814,10 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
805814
# the test runner itself had some problem, so we may have hit a segfault,
806815
# deserialization errors or something similar. Record this testset as Errored.
807816
fake = Test.DefaultTestSet(testname)
808-
Test.record(fake, Test.Error(:nontest_error, testname, nothing, Base.ExceptionStack([(exception = resp, backtrace = [])]), LineNumberNode(1)))
817+
c = IOCapture.capture() do
818+
Test.record(fake, Test.Error(:nontest_error, testname, nothing, Base.ExceptionStack([(exception = resp, backtrace = [])]), LineNumberNode(1)))
819+
end
820+
print(io_ctx.stdout, c.output)
809821
fake
810822
end
811823

@@ -826,20 +838,37 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
826838
for test in [tests; collect(keys(running_tests))]
827839
(test in completed_tests) && continue
828840
fake = Test.DefaultTestSet(test)
829-
Test.record(fake, Test.Error(:test_interrupted, test, nothing, Base.ExceptionStack([(exception = "skipped", backtrace = [])]), LineNumberNode(1)))
841+
c = IOCapture.capture() do
842+
Test.record(fake, Test.Error(:test_interrupted, test, nothing, Base.ExceptionStack([(exception = "skipped", backtrace = [])]), LineNumberNode(1)))
843+
end
844+
print(io_ctx.stdout, c.output)
830845
with_testset(fake) do
831846
Test.record(o_ts, fake)
832847
end
833848
end
834849
end
835-
println()
836-
Test.print_test_results(o_ts, 1)
850+
println(io_ctx.stdout)
851+
if VERSION >= v"1.13.0-DEV.1033"
852+
Test.print_test_results(io_ctx.stdout, o_ts, 1)
853+
else
854+
c = IOCapture.capture() do
855+
Test.print_test_results(o_ts, 1)
856+
end
857+
print(io_ctx.stdout, c.output)
858+
end
837859
if (VERSION >= v"1.13.0-DEV.1037" && !Test.anynonpass(o_ts)) ||
838860
(VERSION < v"1.13.0-DEV.1037" && !o_ts.anynonpass)
839-
println(" \033[32;1mSUCCESS\033[0m")
861+
println(io_ctx.stdout, " \033[32;1mSUCCESS\033[0m")
840862
else
841-
println(" \033[31;1mFAILURE\033[0m\n")
842-
Test.print_test_errors(o_ts)
863+
println(io_ctx.stderr, " \033[31;1mFAILURE\033[0m\n")
864+
if VERSION >= v"1.13.0-DEV.1033"
865+
Test.print_test_errors(io_ctx.stdout, o_ts)
866+
else
867+
c = IOCapture.capture() do
868+
Test.print_test_errors(o_ts)
869+
end
870+
print(io_ctx.stdout, c.output)
871+
end
843872
throw(Test.FallbackTestSetException("Test run finished with errors"))
844873
end
845874
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)