Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionality to find out about locations from file numbers #19

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
*.jl.cov
*.jl.*.cov
*.jl.mem

dev/
4 changes: 1 addition & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ version = "0.1.0"

[deps]
Cassette = "7057c7e9-c182-5462-911a-8362d720325c"
CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
Mocking = "78c3b35d-d492-501b-9361-3d52fe80e533"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accidentally removed when doing a rm?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revolved in #23

5 changes: 3 additions & 2 deletions src/MagneticReadHead.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ using Cassette
using MacroTools
using Mocking
using OrderedCollections
using Revise: get_method, sigex2sigts, get_signature

using Revise
using Revise: get_method, sigex2sigts, get_signature

export set_breakpoint, rm_breakpoint, @iron_debug

Expand All @@ -15,7 +16,7 @@ include("utils.jl")
include("inner_repl.jl")
include("breakpoints.jl")
include("break_action.jl")

include("locate.jl")

struct UserAbortedException <: Exception end

Expand Down
37 changes: 36 additions & 1 deletion src/break_action.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,46 @@ function breadcrumbs(f, args)
meth = methods(f, typeof.(args)) |> only
printstyled("\nBreakpoint Hit: "; color=:blue)
printstyled(string(meth); color=:light_blue)
breadcrumbs(string(meth.file), meth.line)
println()
end

function breadcrumbs(file::AbstractString, line_num; nbefore=2, nafter=2)
return breadcrumbs(stdout, file, line_num; nbefore=nbefore, nafter=nafter)
end

function breadcrumbs(io, file::AbstractString, line_num; nbefore=2, nafter=2)
@assert(nbefore >= 0)
@assert(nafter >= 0)

all_lines = readlines(file)
first_line_num = max(1, line_num - nbefore)
last_line_num = min(length(all_lines), line_num + nafter)

for ln in first_line_num:last_line_num
line = all_lines[ln]
if ln == line_num
line = "➧" * line
color = :cyan
else
line = " " * line
color = :light_green
if ln ∈ (first_line_num, last_line_num)
color = :light_black
end
end
printstyled(io, line, "\n"; color=color)
end
end


# this function exists only for mocking so we can test it.
breakpoint_hit(f, args) = nothing

###########
function iron_repl(f, args, eval_module)
@mock breadcrumbs(f, args)
@mock breakpoint_hit(f, args)
breadcrumbs(f, args)

name2arg = argnames(f, args)

Expand Down
145 changes: 145 additions & 0 deletions src/locate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""
pkgdata(mod)

Gets all the data Revise has on the given module.
"""
pkgdata(pkg_id::Base.PkgId) = Revise.pkgdatas[pkg_id]
pkgdata(mod::Module) = pkgdata(Base.PkgId(mod))

"""
filemap(mod, file)

Retrieve all the data Revise has on the contents of given file
from the given module.
"""
function filemap(mod, file)
mdata = pkgdata(mod)
mfiles = joinpath.(mdata.path, keys(mdata.fileinfos))

file = expanduser(file)
if !isabspath(file)
# We do not need it to be absolute
# But we do want it to start with a "/"
# So we can use `endswith` to check that it matchs one of the paths
# that we know about, without worry of matching part of a filename
file = "/" * file
end
matched_ind = findfirst(mfile->endswith(mfile, file) , mfiles)
matched_ind === nothing && return nothing

internal_file = collect(keys(mdata.fileinfos))[matched_ind]
Revise.maybe_parse_from_cache!(mdata, internal_file) # Ensure fileinfo filled
finfo = collect(values(mdata.fileinfos))[matched_ind]
return finfo.fm
end

######################################################################
# These come from Rebugger.jl
# Including them here as Rebugger itself is causing problems.
# They can away once code tracking gets a bit more stuff.
# https://github.com/timholy/CodeTracking.jl/issues/3

using Revise: ExLike


"""
r = linerange(expr, offset=0)
Compute the range of lines occupied by `expr`.
Returns `nothing` if no line statements can be found.
"""
function linerange(def::ExLike, offset=0)
start, haslinestart = findline(def, identity)
stop, haslinestop = findline(def, Iterators.reverse)
(haslinestart & haslinestop) && return (start+offset):(stop+offset)
return nothing
end

function findline(ex, order)
ex.head == :line && return ex.args[1], true
for a in order(ex.args)
a isa LineNumberNode && return a.line, true
if a isa ExLike
ln, hasline = findline(a, order)
hasline && return ln, true
end
end
return 0, false
end


function linerange((def, (sig, offset))::Tuple{Any, Tuple{Any, Int}})
return linerange(def, offset)
end

# This is not a function so just return an empty range
linerange((def, none)::Tuple{Any, Nothing}) = 1:0


########################################################################
"""
containing_method([module], filename, linenum)

Returns the method within which that line number, in that file, occurs.
Returns `nothing` on no match.

If the module is not provided, then all modules loaded
will be searched for a file with that name that has a function over that line.

Filenames can be absolute, or partial.
E.g. `/home/user1/dev/MyMod/src/helpers/utils.jl`,
`helpers/utils.jl` or `utils.jl` are all acceptable.

However, if multiple files match the module (or lack of module),
and filename specification then only one will be selected.
"""
function containing_method(mod, file, linenum)
module_fmaps = filemap(mod, file)
module_fmaps === nothing && return nothing
for (inner_mod, fmaps) in module_fmaps
for entry in fmaps.defmap
def, info = entry
lr = linerange((def, info))
# TODO: workout a way to round-forward as the linerange starts from first
# statement within the functions body, not from the line it is "declared"
# And there could be quiet some whitespace
if linenum ∈ lr
sigt, offset = info
return sigt2meth(sigt[end])
end
end
end
end

function containing_method(file, linenum)
for pkg_id in keys(Revise.pkgdatas)
meth = containing_method(pkg_id, file, linenum)
meth !== nothing && return meth
end
end

function sigt2meth(::Type{SIGT}) where SIGT
params = SIGT.parameters
func_t = params[1]
func = func_t.instance

args_t = Tuple{params[2:end]...}

return only(methods(func, args_t))
end



##############
"""
src_line2ir_statement_ind(irr, src_line)

Given a CodeIR, and line number from source code
determines the index of the last IR statement that occurs on that line.
"""
function src_line2ir_statement_ind(ir, src_line)
linetable_ind = findlast(ir.linetable) do lineinfo
lineinfo.line == src_line
end
statement_ind = findlast(isequal(linetable_ind), ir.codelocs)
return statement_ind
end
4 changes: 3 additions & 1 deletion test/run_non_ui_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ using Test
test_files = [
"test_inner_repl.jl",
"test_utils.jl",
"test_breakpoints.jl",
"test_behavour.jl",
"test_breadcrumbs.jl",
"test_breakpoints.jl",
"test_locate.jl",
]

@testset "MagneticReadHead" begin
Expand Down
11 changes: 6 additions & 5 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module CanHaveNoBreakpoints

@testset "$(@__MODULE__)" begin
p_readline = make_readline_patch([])
p_breadcrumbs, record = make_recording_breadcrumbs_patch()
p_breadcrumbs, record = make_recording_breakpoint_hit_patch()

apply([p_readline, p_breadcrumbs]) do
@iron_debug eg1()
Expand All @@ -39,7 +39,7 @@ module CanHave1Breakpoint

@testset "$(@__MODULE__)" begin
p_readline = make_readline_patch(["CC"])
p_breadcrumbs, record = make_recording_breadcrumbs_patch()
p_breadcrumbs, record = make_recording_breakpoint_hit_patch()

set_breakpoint(eg2)
apply([p_readline, p_breadcrumbs]) do
Expand All @@ -54,7 +54,7 @@ module CanHave2Breakpoints

@testset "$(@__MODULE__)" begin
p_readline = make_readline_patch(["CC", "CC"])
p_breadcrumbs, record = make_recording_breadcrumbs_patch()
p_breadcrumbs, record = make_recording_breakpoint_hit_patch()

set_breakpoint(eg2)
set_breakpoint(eg3)
Expand All @@ -73,7 +73,7 @@ module CanHave1BreakpointThenStepInThenContinue

@testset "$(@__MODULE__)" begin
p_readline = make_readline_patch(["SI", "CC"])
p_breadcrumbs, record = make_recording_breadcrumbs_patch()
p_breadcrumbs, record = make_recording_breakpoint_hit_patch()

set_breakpoint(eg2)
apply([p_readline, p_breadcrumbs]) do
Expand All @@ -89,7 +89,7 @@ module RunningStepNextWhenThereIsNoNextDoesNotCauseNextDebugRunToStep

@testset "$(@__MODULE__)" begin
p_readline = make_readline_patch(["SN", "CC"])
p_breadcrumbs, record = make_recording_breadcrumbs_patch()
p_breadcrumbs, record = make_recording_breakpoint_hit_patch()

set_breakpoint(eg_last)
apply([p_readline, p_breadcrumbs]) do
Expand All @@ -115,6 +115,7 @@ module CanInfluenceCallingEnviroment
apply(patch) do
@iron_debug eg1()
end
@test zzz==20
end
end

Expand Down
4 changes: 2 additions & 2 deletions test/setup_ui_test_module.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ function make_readline_patch(text_queue)
end
end

function make_recording_breadcrumbs_patch()
function make_recording_breakpoint_hit_patch()
record = []
patch = @patch function breadcrumbs(f, args)
patch = @patch function breakpoint_hit(f, args)
println("\n", f, typeof.(args)) #simple breadcrumbs for handchecking
push!(record, (f=f, args=args))
end
Expand Down
8 changes: 8 additions & 0 deletions test/test_breadcrumbs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MagneticReadHead
using Test
@testset "breadcrumbs" begin
iob = IOBuffer()
MagneticReadHead.breadcrumbs(iob, "demo.jl", 4)
@test String(take!(iob)) ==
" \n \n➧function eg1()\n z = eg2(2)\n eg_last(z)\n"
end
55 changes: 55 additions & 0 deletions test/test_locate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Revise
using MagneticReadHead
using Test

using MagneticReadHead:
filemap, pkgdata, containing_method, src_line2ir_statement_ind

@show Revise.pkgdatas #BLACKMAGIC: Remove this line and the tests fail

@testset "src_line2ir_statement_ind" begin
ir1line = first(methods(()->1)) |> Base.uncompressed_ast
@test src_line2ir_statement_ind(ir1line, (@__LINE__)-1) == 1
@test src_line2ir_statement_ind(ir1line, 1000) == nothing

ir1line2 = first(methods(()->(x=1;x*x))) |> Base.uncompressed_ast
@test src_line2ir_statement_ind(ir1line2, (@__LINE__)-1) == 3


ir2line = first(methods(()->(x=1;
x*x))) |> Base.uncompressed_ast
@test src_line2ir_statement_ind(ir2line, (@__LINE__)-1) == 3
end


@testset "pkgdata" begin
@test pkgdata(MagneticReadHead) !==nothing
end

@testset "filemap" begin
@test filemap(MagneticReadHead, "utils.jl") !==nothing
@test filemap(MagneticReadHead, "src/utils.jl") == filemap(MagneticReadHead, "utils.jl")

@test filemap(MagneticReadHead, "NOT_REAL") ===nothing
end


@testset "containing_method" begin

meth = containing_method(MagneticReadHead, "src/locate.jl", 30)
for ln in (29, 30, 31)
@test meth == containing_method(MagneticReadHead, "src/locate.jl", ln)
@test meth == containing_method(MagneticReadHead, "locate.jl", ln)
@test meth == containing_method("locate.jl", ln)
@test_broken meth ==
containing_method(MagneticReadHead, "../src/locate.jl", ln)
cd(@__DIR__) do
@test meth == containing_method(
MagneticReadHead,
realpath("../src/locate.jl"),
ln
)
end
end
end