PlutoTest.jl
This notebook introduces visual testing:
3.046681
2
3
4
5
6
7
Tests have time-travel functionality built in! Click on the tests above.
Time travel
This notebook contains visual debugging:
UndefVarError: plot not defined
Here is what happened, the most recent locations are first:
(You need Pluto#main to run this notebook)
1
3
1
3
1
4
missing440.988274
0.510641
0.438907
0.534822
0.926395
0.983752
0.529208
0.448898
0.650767
0.283051
0.536844
0.128828
0.905674
0.516557
0.580888
0.780972
0.429731
0.55104
0.0579891
0.438915
0.155332
0.818163
0.195875
0.628077
0.149449
0.533657
0.0258216
0.343407
0.774534
0.679764
0.667581
0.0114866
0.10014
0.927642
0.0275688
0.640854
0.725443
0.8124
0.705226
0.00638059
2
0.410669
0.579182
0.518272
0.685746
truetrue4418truetruetruetruetruetruetruetruefalsefalse1.0
1.41421
4.47214
5.0
6.0
7.0
8.0
9.0
0.718129
0.155972
0.134135
0.083491
0.234392
0.516929
0.616255
0.565758
0.0979359
0.728288
0.59208
0.85965
0.232109
0.497824
0.905705
0.643441
0.851803
0.981234
0.26009
0.0825448
0.769021
0.439012
0.521721
0.999883
0.793032
0.0512245
0.299046
0.526124
0.85457
0.092512
0.742653
0.767856
0.246335
0.480407
0.952623
0.997972
0.371932
0.708498
0.0723807
0.835832
0.188896
0.710133
0.531192
0.191066
0.648901
0.482695
0.818875
0.720434
0.303909
0.23561
0.994339
0.825339
0.182662
0.359638
0.231568
0.920077
0.294352
0.120436
0.0561465
0.234589
always_false (generic function with 1 method)"pt-dot {\n\tflex: 0 0 auto;\n\tbackground: grey;\n\twidth: 1em;\n\theight: 1em;\n\tbottom: -.1em;\n\tborder-radius: 100%;\n\tmargin-right: .7em;\n\tdisplay: block;\n\tposition: relative;\n\tcursor: pointer;\n}\n\npt-dot.floating {\n\tposition: fixed;\n\tz-index: 60;\n\tvisibility: hidden;\n\ttransition: transform linear 120ms;\n" ⋯ 1338 bytes ⋯ "-frame-viewer {\n max-width: 100%;\n}\n.pluto-test.expanded > p-frame-viewer > p-frames > slotted-code > line-like {\n\tflex-wrap: wrap;\n}\n.pluto-test.expanded > p-frame-viewer > p-frames > slotted-code > line-like > pluto-display[mime=\"application/vnd.pluto.tree+object\"] {\n\t/*flex-basis: 100%;*/\n}\n""p-frame-viewer {\n\tdisplay: inline-flex;\n\tflex-direction: column;\n}\np-frames,\np-frame-controls {\n\tdisplay: inline-flex;\n}\n"@test_deprecated (macro with 1 method)Type definitions
AnyTest macro
24681012141618202224262830step_by_step (generic function with 1 method)test (generic function with 1 method)begin
var"#715#expr_raw" = $(QuoteNode(:(x == [1, 2 + i])))
try
var"#716#steps" = begin
Main.workspace#6.Any[$(QuoteNode(:(x == [1, 2 + i]))), (Main.workspace#3.onestep_light)($(Expr(:copyast, :($(QuoteNode(:(x == [1, 2 + i])))))); m = Main.workspace#7)...]
end
var"#717#result" = Main.workspace#6.unwrap_computed(Main.workspace#6.last(var"#716#steps"))
if var"#717#result" === true
Main.workspace#6.CorrectCall(var"#715#expr_raw", var"#716#steps")
else
Main.workspace#6.WrongCall(var"#715#expr_raw", var"#716#steps")
end
catch var"#720#e"
Main.workspace#6.rethrow(var"#720#e")
end
end
0.276505
0.377432
0.0926563
0.568319
0.182542
0.835015
0.130801
0.489796
0.128701
0.573874
0.640606
0.0320843
0.938753
0.256354
0.420725
0.16785
0.945496
0.270829
0.830752
0.768415
0.0766091
0.0304892
0.796734
0.0610086
0.246578
0.585827
0.447928
0.496172
0.416527
0.440059
0.203395
0.429921
0.890314
0.611558
0.260663
0.843095
0.1157
0.047827
0.961238
0.787033
-40.550885
0.624419
0.791837
0.52451
0.783919
0.112528
0.28075
0.146377
0.648852
0.379882
0.495427
0.426507
0.0669624
0.424366
0.391154
0.578126
0.760781
0.721861
0.91234
0.48734
0.251115
0.868539
0.123558
0.687502
0.318283
0.847443
0.479445
0.308065
0.564114
0.243355
false"asdasd""asd asd""asdasd""asdasd""asdasd""asdasd""asdasd""asdasd"
"asd"
"asd asd"
"asd"
"asdasd"
"asd"
"asdasd"
"asd"
"asdasd"
"asd"
"asdasd"
"asd"
"asdasd"
"asd"
trueembed_display (generic function with 1 method)flatmap (generic function with 1 method)embed_display (generic function with 1 method)div (generic function with 1 method)div (generic function with 2 methods)"x + 1"expr_to_str (generic function with 2 methods)prettycolors (generic function with 1 method)remove_linenums (generic function with 1 method)remove_linenums (generic function with 2 methods)UInt64expr_hash (generic function with 1 method)expr_hash (generic function with 2 methods)
Time travel evaluation
In Julia, expressions are objects! This means that, before evaluation, code is expressed as a Julia object:
:(first([56, sqrt(9)]))Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol first
2: Expr
head: Symbol vect
args: Array{Any}((2,))
1: Int64 56
2: Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol sqrt
2: Int64 9
You can use Core.eval to evaluate expressions at runtime:
56.0But did you know that you can also partially evaluate expressions?
:([56, sqrt(9)])56.0
3.0
:(first([56.0, 3.0]))Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol first
2: Array{Float64}((2,)) [56.0, 3.0]
Here, ex2 is not a raw Expr — it contains an evaluated array!
Computed struct
Our time travel mechanism will be based on the partial evaluation principle introduced above. To differentiate between computed results and the original expression, we will wrap all computed results in a struct.
:(first(Computed([56.0, 3.0])))Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol first
2: Main.workspace#3.Computed
x: Array{Float64}((2,)) [56.0, 3.0]
We also add a function to recursively unwrap an expression with Computed entries:
unwrap_computed (generic function with 1 method)unwrap_computed (generic function with 2 methods)unwrap_computed (generic function with 3 methods):(first([56.0, 3.0]))Stepping function
begin
sqrt(Computed(2.0)) + 2
end
begin
Computed(1.4142135623730951) + 2
end
begin
Computed(3.414213562373095)
end
Computed(3.414213562373095)
onestep_light (generic function with 1 method)onestep_light (generic function with 2 methods)@eval_step_by_step (macro with 1 method)quote
#= /home/runner/work/disorganised-mess/disorganised-mess/testing and debugging 3.jl#==#e1c306e3-0a47-4149-a9fb-ec7ab380fa11:6 =#
Main.workspace#3.Any[$(QuoteNode(:(x == [1, 2]))), (Main.workspace#3.onestep_light)($(Expr(:copyast, :($(QuoteNode(:(x == [1, 2])))))); m = Main.workspace#4)...]
end:(sqrt(sqrt(length([1, 2]))))
:(sqrt(sqrt(length(Computed([1, 2])))))
:(sqrt(sqrt(Computed(2))))
:(sqrt(Computed(1.41421)))
1.18921
:(xasdf = 123)
123
UndefVarError: xasdf not defined
Here is what happened, the most recent locations are first:
begin
Computed(3)
2 + 3
4 + 5
sqrt(sqrt(sqrt(5)))
end
begin
Computed(3)
Computed(5)
4 + 5
sqrt(sqrt(sqrt(5)))
end
begin
Computed(3)
Computed(5)
Computed(9)
sqrt(sqrt(sqrt(5)))
end
begin
Computed(3)
Computed(5)
Computed(9)
sqrt(sqrt(Computed(2.23606797749979)))
end
begin
Computed(3)
Computed(5)
Computed(9)
sqrt(Computed(1.4953487812212205))
end
begin
Computed(3)
Computed(5)
Computed(9)
Computed(1.2228445449938519)
end
Computed(1.2228445449938519)
can_interpret (generic function with 1 method)can_interpret_call_arg (generic function with 1 method)can_interpret_call_arg (generic function with 2 methods)truecan_interpret (generic function with 2 methods)Displaying objects inside code
Slotting
We walk through the expression tree. Whenever we find a Computed object, we generate a random key (e.g. iosjddfo), we add it to our dictionary (found). In the expression, we replace the Computed object with a placeholder symbol __slotiosjddfo__. We will later be able to match the object to this slot.
slot! (generic function with 1 method)slot! (generic function with 2 methods)slot! (generic function with 3 methods)slot (generic function with 1 method):__slotwigtqxmikiieufkx__
3
quote
__slotwigtqxmikiieufkx__ + (7 - 6)
2 + 3
4 + 5
sqrt(sqrt(sqrt(5)))
end:__slotfjqyzqkquughvaxz__
3
:__slotzjzgkpfuhgloausu__
1
quote
__slotfjqyzqkquughvaxz__ + __slotzjzgkpfuhgloausu__
2 + 3
4 + 5
sqrt(sqrt(sqrt(5)))
end:__slotatxgmntclwqbysro__
4
quote
__slotatxgmntclwqbysro__
2 + 3
4 + 5
sqrt(sqrt(sqrt(5)))
end:__slotnrcohodrpmddwlyy__
4
:__slotdaaytrbsirkgmqxl__
5
quote
__slotnrcohodrpmddwlyy__
__slotdaaytrbsirkgmqxl__
4 + 5
sqrt(sqrt(sqrt(5)))
end:__slothhoqsqhxggchdjze__
5
:__slotkxqqnyzpbtposyvc__
4
:__slotnpyckugkrsozomtw__
9
quote
__slotkxqqnyzpbtposyvc__
__slothhoqsqhxggchdjze__
__slotnpyckugkrsozomtw__
sqrt(sqrt(sqrt(5)))
end:__slotbkqszgmdbsevegks__
4
:__slotdopeohsgpvpymwrv__
5
:__slotmaxbihmroxryaupd__
2.23607
:__slotwfnzfewhocsmoivm__
9
quote
__slotbkqszgmdbsevegks__
__slotdopeohsgpvpymwrv__
__slotwfnzfewhocsmoivm__
sqrt(sqrt(__slotmaxbihmroxryaupd__))
end:__slotjntkrzihelldprcf__
9
:__slotnhlyaekirklibjbb__
4
:__slotfwtieumvrmpxnncq__
1.49535
:__slotkvuzrlrwbusbazog__
5
quote
__slotnhlyaekirklibjbb__
__slotkvuzrlrwbusbazog__
__slotjntkrzihelldprcf__
sqrt(__slotfwtieumvrmpxnncq__)
end:__slotrkxvntgdeqsgaxnw__
1.22284
:__slotlbniztlawsudsrty__
5
:__slotefuopcqwcljubnwo__
4
:__slotyokdsxhnxonmduaw__
9
quote
__slotefuopcqwcljubnwo__
__slotlbniztlawsudsrty__
__slotyokdsxhnxonmduaw__
__slotrkxvntgdeqsgaxnw__
end:__slotaglrzkwkzobxzeea__
1.22284
:__slotaglrzkwkzobxzeea__
SlottedDisplay
We use print to turn the expression into source code.
For each line, we regex-search for slot variables, and we split the line around those. The code segments around slots are rendered inside <pre-ish> tags (like <pre> but inline), and the slots are replaced by embedded displays of the objects.
preish (generic function with 1 method)SlottedDisplay"slotted-code {\n\tfont-family: \"JuliaMono\", monospace;\n\tfont-size: .75rem;\n\tdisplay: flex;\n\tflex-direction: column;\n}\npre-ish {\n\twhite-space: pre;\n}\n\nline-like {\n\tdisplay: flex;\n\talign-items: baseline;\n}\n"plot (generic function with 1 method)UndefVarError: plot not defined
Here is what happened, the most recent locations are first:
Another cell defining rs contains errors.
Frame viewer
A widget that takes a series of elements and displays them as 'video frames' with a timeline scrubber.
Another cell defining rs contains errors.
frames (generic function with 1 method)Macro to test frames
@visual_debug (macro with 1 method)UndefVarError: plot not defined
Here is what happened, the most recent locations are first:
Appendix
DisplayOnly
is_inside_pluto (generic function with 1 method)@displayonly expression
Marks a expression as Pluto-only, which means that it won't be executed when running outside Pluto. Do not use this for your own projects.
The opposite of @skip_as_script
"hello"PlutoUI favourites
"@media screen and (min-width: 1081px) {\n\t.plutoui-toc.aside {\n\t\tposition:fixed; \n\t\tright: 1rem;\n\t\ttop: 5rem; \n\t\twidth:25%; \n\t\tpadding: 10px;\n\t\tborder: 3px solid rgba(0, 0, 0, 0.15);\n\t\tborder-radius: 10px;\n\t\tbox-shadow: 0 0 11px 0px #00000010;\n\t\t/* That is, viewport minus top minus Live Docs */\n\t\tma" ⋯ 790 bytes ⋯ " 0px;\n}\n.plutoui-toc.indent section a.H2 {\n\tpadding-left: 10px;\n}\n.plutoui-toc.indent section a.H3 {\n\tpadding-left: 20px;\n}\n.plutoui-toc.indent section a.H4 {\n\tpadding-left: 30px;\n}\n.plutoui-toc.indent section a.H5 {\n\tpadding-left: 40px;\n}\n.plutoui-toc.indent section a.H6 {\n\tpadding-left: 50px;\n}\n"#1 (generic function with 1 method)toc (generic function with 1 method)SliderDump (generic function with 1 method)Base.RefValue{Int64}
x: Int64 1
Show