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 all 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/
6 changes: 5 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
name = "MagneticReadHead"
uuid = "3f5657c2-1c21-11e9-2267-d379952df7a3"
authors = ["Lyndon White <[email protected]>"]
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"

[compat]
CodeTracking = "0.3.1"
Revise = "1.1.0"

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

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

using CodeTracking
# We don't use Revise, but if it isn't loaded CodeTracking has issues
using Revise: Revise

export set_breakpoint, rm_breakpoint, @iron_debug

Expand All @@ -15,7 +17,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
9 changes: 6 additions & 3 deletions src/breakpoints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
Cassette.@context MagneticCtx

#### Breakpoint declaration helpers
# We are importing stuff here from Revise that should be done with CodeTracking
# But it doesn't matter since this whole file is getting deleted soon


"""
@uneval(expr)
Expand All @@ -10,9 +13,9 @@ Deletes a method that was declared via `@eval`
"""
macro uneval(expr)
quote
sig = get_signature($(Expr(:quote, expr)))
sigt = only(sigex2sigts(@__MODULE__, sig))
meth = get_method(sigt)
sig = Revise.get_signature($(Expr(:quote, expr)))
sigt = only(Revise.sigex2sigts(@__MODULE__, sig))
meth = Revise.get_method(sigt)
if meth == nothing
@info "Method not found, thus not removed."
else
Expand Down
93 changes: 93 additions & 0 deletions src/locate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
source_paths(mod, file)

Returns list of all source files in the module with matching (partial) filename,
the filename does not have to be relative to the base directory.
It can be any partial path.
Ambiguities can occur as
if no matching path is found then an empty list is returned
"""
function source_paths(mod, file)
mdata = pkgfiles(mod)
mfiles = joinpath.(mdata.basedir, mdata.files)

file = expanduser(file)
if !isabspath(file)
# We do not need it to be absolute
# But we do want it to start with a "/" if it isn't
# 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_inds = map(mfile->endswith(mfile, file), mfiles)

return mfiles[matched_inds]
end


"""
containing_methods([module], filename, linenum)

Returns the methods within which the provided line number
in the given file, occurs.
Returns an empty list 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.

If it is ambigious, then all matching methods in all file will be returned.
"""
function containing_methods(mod, file, linenum)
paths = source_paths(mod, file)
isempty(paths) && return Method[]
sigs = mapreduce(vcat, paths) do path
signatures_at(path, linenum)
end
# TODO: workout a way to round-forward as the signatures_at
# only matches to
# statement within the functions body, not from the line it is "declared"
# or any intervening whitespace. It also doesn't match to the `end` line.

return map(sigt2meth, sigs)
end

function containing_methods(file, linenum)
#TODO: raise issue on CodeTracking.jl to expose public way
# to get list of all loaded modules
pkgids = keys(CodeTracking._pkgfiles)
isempty(pkgids) && return Method[]
mapreduce(vcat, pkgids) do pkgid
containing_methods(pkgid, file, linenum)
end
end

function sigt2meth(::Type{SIGT})::Method 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
48 changes: 48 additions & 0 deletions test/test_locate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Revise
using MagneticReadHead
using Test

using MagneticReadHead:
source_paths,
containing_methods,
src_line2ir_statement_ind

@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 "source_paths" begin
@test source_paths(MagneticReadHead, "utils.jl") |> !isempty
@test source_paths(MagneticReadHead, "src/utils.jl") == source_paths(MagneticReadHead, "utils.jl")

@test source_paths(MagneticReadHead, "NOT_REAL") |> isempty
end

@testset "containing_methods" begin
# BADTEST: This is actually tied to line numbers in the source code
meth = containing_methods(MagneticReadHead, "src/locate.jl", 46)
for ln in (45, 46, 47)
@test meth == containing_methods(MagneticReadHead, "src/locate.jl", ln)
@test meth == containing_methods(MagneticReadHead, "locate.jl", ln)
@test meth == containing_methods("locate.jl", ln)
@test_broken meth ==
containing_methods(MagneticReadHead, "../src/locate.jl", ln)
cd(@__DIR__) do
@test meth == containing_methods(
MagneticReadHead,
realpath("../src/locate.jl"),
ln
)
end
end
end