diff --git a/.travis.yml b/.travis.yml index 70c32ca4..48cfc625 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ os: - osx julia: - 1.0 + - 1.1 - nightly notifications: email: false diff --git a/REQUIRE b/REQUIRE index 13bcb588..f4852202 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,2 +1,3 @@ julia 1.0 OrderedCollections +CodeTracking diff --git a/appveyor.yml b/appveyor.yml index a5091374..dfc809a2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,7 @@ environment: matrix: - julia_version: 1 + - julia_version: 1.1 - julia_version: nightly platform: diff --git a/src/Revise.jl b/src/Revise.jl index 428c3a18..90e0a0c8 100644 --- a/src/Revise.jl +++ b/src/Revise.jl @@ -4,7 +4,8 @@ using FileWatching, REPL, Distributed, UUIDs import LibGit2 using Base: PkgId -using OrderedCollections: OrderedDict +using OrderedCollections, CodeTracking +using CodeTracking: PkgFiles, basedir, srcfiles export revise, includet, MethodSummary @@ -330,7 +331,7 @@ function init_watching(pkgdata::PkgData, files) udirs = Set{String}() for file in files dir, basename = splitdir(file) - dirfull = joinpath(pkgdata.path, dir) + dirfull = joinpath(basedir(pkgdata), dir) haskey(watched_files, dirfull) || (watched_files[dirfull] = WatchList()) push!(watched_files[dirfull], basename) if watching_files[] @@ -341,7 +342,7 @@ function init_watching(pkgdata::PkgData, files) end end for dir in udirs - dirfull = joinpath(pkgdata.path, dir) + dirfull = joinpath(basedir(pkgdata), dir) updatetime!(watched_files[dirfull]) if !watching_files[] dwatcher = Rescheduler(revise_dir_queued, (pkgdata, dir)) @@ -362,7 +363,7 @@ This is generally called via a [`Rescheduler`](@ref). @noinline function revise_dir_queued(pkgdata::PkgData, dirname) dirname0 = dirname if !isabspath(dirname) - dirname = joinpath(pkgdata.path, dirname) + dirname = joinpath(basedir(pkgdata), dirname) end if !isdir(dirname) sleep(0.1) # in case git has done a delete/replace cycle @@ -376,7 +377,7 @@ This is generally called via a [`Rescheduler`](@ref). latestfiles, stillwatching = watch_files_via_dir(dirname) # will block here until file(s) change for file in latestfiles key = joinpath(dirname0, file) - if haskey(pkgdata.fileinfos, key) # issue #228 + if hasfile(pkgdata, key) # issue #228 push!(revision_queue, (pkgdata, key)) end end @@ -395,7 +396,7 @@ This is used only on platforms (like BSD) which cannot use [`revise_dir_queued`] function revise_file_queued(pkgdata::PkgData, file) file0 = file if !isabspath(file) - file = joinpath(pkgdata.path, file) + file = joinpath(basedir(pkgdata), file) end if !isfile(file) sleep(0.1) # in case git has done a delete/replace cycle @@ -428,20 +429,20 @@ It then deletes any removed methods and re-evaluates any changed expressions. `Revise.pkgdatas[id].fileinfos`. """ function revise_file_now(pkgdata::PkgData, file) - pkgdict = pkgdata.fileinfos - if !haskey(pkgdict, file) + i = fileindex(pkgdata, file) + if i === nothing println("Revise is currently tracking the following files in $(pkgdata.id): ", keys(pkgdict)) error(file, " is not currently being tracked.") end maybe_parse_from_cache!(pkgdata, file) - fi = pkgdict[file] + fi = fileinfo(pkgdata, i) fmref = fi.fm - filep = normpath(joinpath(pkgdata.path, file)) + filep = normpath(joinpath(basedir(pkgdata), file)) topmod = first(keys(fi.fm)) fmnew = parse_source(filep, topmod) if fmnew != nothing fmrep = eval_revised(fmnew, fmref) - pkgdict[file] = FileInfo(fmrep, fi) + pkgdata.fileinfos[i] = FileInfo(fmrep, fi) end nothing end @@ -467,8 +468,14 @@ function instantiate_sigs!(fmm::FMMaps, def::RelocatableExpr, sig::RelocatableEx sigts = Any[sigt2methsig(sigt) for sigt in sigts] # Insert into the maps fmm.defmap[def] = (sigts, 0) + defex = convert(Expr, def) for sigt in sigts fmm.sigtmap[sigt] = def + # Also add to CodeTracking + meth = whichtt(sigt) + if isa(meth, Method) + CodeTracking.method_info[sigt] = (LineNumberNode(Int(meth.line), meth.file), defex) + end end return def end @@ -526,8 +533,9 @@ to propagate an updated macro definition, or to force recompiling generated func function revise(mod::Module) mod == Main && error("cannot revise(Main)") id = PkgId(mod) - pkgdict = pkgdatas[id].fileinfos - for (file, fi) in pkgdict + pkgdata = pkgdatas[id] + for (i, file) in enumerate(srcfiles(pkgdata)) + fi = fileinfo(pkgdata, i) for (mod,fmm) in fi.fm for def in keys(fmm.defmap) Core.eval(mod, convert(Expr, def)) @@ -556,7 +564,7 @@ function track(mod::Module, file::AbstractString) pkgdatas[id] = PkgData(id, pathof(mod)) end pkgdata = pkgdatas[id] - pkgdata.fileinfos[file] = FileInfo(fm) + push!(pkgdata, file=>FileInfo(fm)) init_watching(pkgdata, (file,)) end end @@ -658,14 +666,14 @@ function get_def(method::Method; modified_files=revision_queue) id === nothing && return nothing pkgdata = pkgdatas[id] filename = relpath(filename, pkgdata) - if haskey(pkgdata.fileinfos, filename) + if hasfile(pkgdata, filename) def = get_def(method, pkgdata, filename) def !== nothing && return def end # Perhaps this is a method that was defined via a macro defined in a different module. # E.g., https://github.com/timholy/Rebugger.jl/issues/3 # Try to find the actual file in the same package/module - for file in keys(pkgdata.fileinfos) + for file in srcfiles(pkgdata) def = get_def(method, pkgdata, file) def !== nothing && return def end @@ -675,7 +683,7 @@ end function get_def(method, pkgdata, filename) maybe_parse_from_cache!(pkgdata, filename) - fi = pkgdata.fileinfos[filename] + fi = fileinfo(pkgdata, filename) fmm = get(fi.fm, method.module, nothing) fmm === nothing && return nothing map = fmm.sigtmap @@ -683,35 +691,29 @@ function get_def(method, pkgdata, filename) return nothing end -function get_tracked_id(mod::Module; modified_files=revision_queue) - id = PkgId(mod) +function get_tracked_id(id::PkgId; modified_files=revision_queue) # Methods from Base or the stdlibs may require that we start tracking if !haskey(pkgdatas, id) - recipemod = mod - if id.name == "Base" - recipemod = Base - elseif id.name == "Core" - recipemod = Core.Compiler - elseif Symbol(id.name) ∈ stdlib_names - prevmod = recipemod - while String(nameof(recipemod)) != id.name - recipemod = parentmodule(recipemod) - recipemod == prevmod && break - prevmod = recipemod - end - else - @warn "$id not found, not able to track" - return nothing - end - @info "tracking $recipemod" - track(recipemod; modified_files=modified_files) + recipe = id.name === "Compiler" ? :Compiler : Symbol(id.name) + _track(id, recipe; modified_files=modified_files) + @info "tracking $recipe" if !haskey(pkgdatas, id) - @warn "despite tracking $recipemod, $id was not found" + @warn "despite tracking $recipe, $id was not found" return nothing end end return id end +get_tracked_id(mod::Module; modified_files=revision_queue) = + get_tracked_id(PkgId(mod); modified_files=modified_files) + +function get_expressions(id::PkgId, filename) + get_tracked_id(id) + pkgdata = pkgdatas[id] + maybe_parse_from_cache!(pkgdata, filename) + fi = fileinfo(pkgdata, filename) + return fi.fm +end function fix_line_statements!(ex::Expr, file::Symbol, line_offset::Int=0) if ex.head == :line @@ -859,6 +861,12 @@ function __init__() end # Correct line numbers for code moving around Base.update_stackframes_callback[] = update_stacktrace_lineno! + # Populate CodeTracking data for dependencies + parse_pkg_files(PkgId(CodeTracking)) + parse_pkg_files(PkgId(OrderedCollections)) + # Set the lookup callbacks + CodeTracking.method_lookup_callback[] = get_def + CodeTracking.expressions_callback[] = get_expressions # Watch the manifest file for changes mfile = manifest_file() diff --git a/src/exprutils.jl b/src/exprutils.jl index b4472562..42862ce5 100644 --- a/src/exprutils.jl +++ b/src/exprutils.jl @@ -129,6 +129,12 @@ function sigt2methsig(sig) return methsig end +function whichtt(tt) + m = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), tt, typemax(UInt)) + m === nothing && return nothing + return m.func::Method +end + """ callex = get_callexpr(sigex::ExLike) diff --git a/src/pkgs.jl b/src/pkgs.jl index 9af7dc06..4905fe5f 100644 --- a/src/pkgs.jl +++ b/src/pkgs.jl @@ -1,4 +1,5 @@ using Base: PkgId +using CodeTracking: basepath # A near-copy of the same method in `base/loading.jl`. However, this retains the full module path to the file. function parse_cache_header(f::IO) @@ -61,7 +62,10 @@ Its job is to organize the files and expressions defining the module so that lat detect and process revisions. """ function parse_pkg_files(id::PkgId) - files = String[] + if !haskey(pkgdatas, id) + pkgdatas[id] = PkgData(id) + end + pkgdata = pkgdatas[id] modsym = Symbol(id.name) if use_compiled_modules() # We probably got the top-level file from the precompile cache @@ -74,18 +78,14 @@ function parse_pkg_files(id::PkgId) for (pkgid, buildid) in provides if pkgid.uuid === uuid && pkgid.name == id.name # found the right cache file - if !haskey(pkgdatas, id) - pkgdatas[id] = PkgData(id) - end - pkgdata = pkgdatas[id] for (mod, fname, _) in mods_files_mtimes fname = relpath(fname, pkgdata) # For precompiled packages, we can read the source later (whenever we need it) # from the *.ji cachefile. - pkgdata.fileinfos[fname] = FileInfo(mod, path) - push!(files, fname) + push!(pkgdata, fname=>FileInfo(mod, path)) end - return files + CodeTracking._pkgfiles[id] = pkgdata.info + return srcfiles(pkgdata) end end end @@ -93,8 +93,8 @@ function parse_pkg_files(id::PkgId) # Non-precompiled package(s). Here we rely on the `include` callbacks to have # already populated `included_files`; all we have to do is collect the relevant # files. - queue_includes!(files, id) - return files + queue_includes!(pkgdata, id) + return srcfiles(pkgdata) end # The main trick here is that since `using` is recursive, `included_files` @@ -107,13 +107,9 @@ end # we can't use the module-of-evaluation to find it. Here we hope that the # top-level filename follows convention and matches the module. TODO?: it's # possible that this needs to be supplemented with parsing. -function queue_includes!(files, id::PkgId) +function queue_includes!(pkgdata::PkgData, id::PkgId) modstring = id.name delids = Int[] - if !haskey(pkgdatas, id) - pkgdatas[id] = PkgData(id) - end - pkgdata = pkgdatas[id] for i = 1:length(included_files) mod, fname = included_files[i] modname = String(Symbol(mod)) @@ -122,20 +118,23 @@ function queue_includes!(files, id::PkgId) instantiate_sigs!(fm) fname = relpath(fname, pkgdata) if fm != nothing - pkgdata.fileinfos[fname] = FileInfo(fm) + push!(pkgdata, fname=>FileInfo(fm)) end - push!(files, fname) push!(delids, i) end end deleteat!(included_files, delids) - return files + CodeTracking._pkgfiles[id] = pkgdata.info + return srcfiles(pkgdata) end function queue_includes(mod::Module) id = PkgId(mod) - files = queue_includes!(String[], id) + if !haskey(pkgdatas, id) + pkgdatas[id] = PkgData(id) + end pkgdata = pkgdatas[id] + files = queue_includes!(pkgdata, id) if has_writable_paths(pkgdata) init_watching(pkgdata, files) end @@ -159,8 +158,8 @@ function remove_from_included_files(modsym::Symbol) end function read_from_cache(pkgdata::PkgData, file::AbstractString) - fi = pkgdata.fileinfos[file] - filep = joinpath(pkgdata.path, file) + fi = fileinfo(pkgdata, file) + filep = joinpath(basedir(pkgdata), file) if fi.cachefile == basesrccache # Get the original path filec = get(cache_file_key, filep, filep) @@ -172,11 +171,11 @@ function read_from_cache(pkgdata::PkgData, file::AbstractString) end function maybe_parse_from_cache!(pkgdata::PkgData, file::AbstractString) - fi = pkgdata.fileinfos[file] + fi = fileinfo(pkgdata, file) if isempty(fi.fm) # Source was never parsed, get it from the precompile cache src = read_from_cache(pkgdata, file) - filep = joinpath(pkgdata.path, file) + filep = joinpath(basedir(pkgdata), file) filec = get(cache_file_key, filep, filep) topmod = first(keys(fi.fm)) if parse_source!(fi.fm, src, Symbol(filec), 1, topmod) === nothing @@ -237,8 +236,8 @@ end function has_writable_paths(pkgdata::PkgData) haswritable = false - for file in keys(pkgdata.fileinfos) - haswritable |= iswritable(joinpath(pkgdata.path, file)) + for file in srcfiles(pkgdata) + haswritable |= iswritable(joinpath(basedir(pkgdata), file)) end return haswritable end @@ -247,13 +246,6 @@ end # Much of this is adapted from base/loading.jl -function basepath(id::PkgId) - id.name ∈ ("Main", "Base", "Core") && return "" - loc = Base.locate_package(id) - loc === nothing && return "" - return dirname(dirname(loc)) -end - function manifest_file(project_file) if project_file isa String mfile = Base.project_file_manifest_path(project_file) @@ -321,26 +313,26 @@ function watch_manifest(mfile) for (id, pkgdir) in pkgdirs if haskey(pkgdatas, id) pkgdata = pkgdatas[id] - if pkgdir != pkgdata.path + if pkgdir != basedir(pkgdata) ## The package directory has changed - @debug "Pkg" _group="pathswitch" oldpath=pkgdata.path newpath=pkgdir + @debug "Pkg" _group="pathswitch" oldpath=basedir(pkgdata) newpath=pkgdir # Stop all associated watching tasks - for dir in unique_dirs(keys(pkgdata.fileinfos)) + for dir in unique_dirs(srcfiles(pkgdata)) @debug "Pkg" _group="unwatch" dir=dir - delete!(watched_files, joinpath(pkgdata.path, dir)) + delete!(watched_files, joinpath(basedir(pkgdata), dir)) # Note: if the file is revised, the task(s) will run one more time. # However, because we've removed the directory from the watch list this will be a no-op, # and then the tasks will be dropped. end # Revise code as needed files = String[] - for file in keys(pkgdata.fileinfos) + for file in srcfiles(pkgdata) maybe_parse_from_cache!(pkgdata, file) push!(revision_queue, (pkgdata, file)) push!(files, file) end # Update the directory - pkgdata.path = pkgdir + pkgdata.info.basedir = pkgdir # Restart watching, if applicable if has_writable_paths(pkgdata) init_watching(pkgdata, files) diff --git a/src/recipes.jl b/src/recipes.jl index 019dfcd5..becda80f 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -7,35 +7,39 @@ Track updates to the code in Julia's `base` directory, `base/compiler`, or one o standard libraries. """ function track(mod::Module; modified_files=revision_queue) - inpath(path, dirs) = all(dir->occursin(dir, path), dirs) id = PkgId(mod) - isbase = mod == Base - isstdlib = !isbase && nameof(mod) ∈ stdlib_names + modname = nameof(mod) + return _track(id, modname; modified_files=modified_files) +end + +function _track(id, modname; modified_files=revision_queue) + inpath(path, dirs) = all(dir->occursin(dir, path), dirs) + isbase = modname == :Base + isstdlib = !isbase && modname ∈ stdlib_names if isbase || isstdlib # Test whether we know where to find the files if isbase srcdir = fixpath(joinpath(juliadir, "base")) dirs = ["base"] else - stdlibv = joinpath("stdlib", "v$(VERSION.major).$(VERSION.minor)", String(nameof(mod))) + stdlibv = joinpath("stdlib", "v$(VERSION.major).$(VERSION.minor)", String(modname)) srcdir = fixpath(joinpath(juliadir, stdlibv)) if !isdir(srcdir) - srcdir = fixpath(joinpath(juliadir, "stdlib", String(nameof(mod)))) + srcdir = fixpath(joinpath(juliadir, "stdlib", String(modname))) end if !isdir(srcdir) # This can happen for Pkg, since it's developed out-of-tree srcdir = joinpath(juliadir, "usr", "share", "julia", stdlibv) end - dirs = ["stdlib", String(nameof(mod))] + dirs = ["stdlib", String(modname)] end if !isdir(srcdir) - @error "unable to find path containing source for $mod, tracking is not possible" + @error "unable to find path containing source for $modname, tracking is not possible" end # Determine when the basesrccache was built mtcache = mtime(basesrccache) # Initialize expression-tracking for files, and # note any modified since Base was built - files = String[] if !haskey(pkgdatas, id) pkgdatas[id] = PkgData(id, srcdir) end @@ -45,13 +49,12 @@ function track(mod::Module; modified_files=revision_queue) inpath(ffilename, dirs) || continue keypath = ffilename[1:last(findfirst(dirs[end], ffilename))] rpath = relpath(ffilename, keypath) - pkgdata.fileinfos[rpath] = FileInfo(submod, basesrccache) - fullpath = joinpath(pkgdata.path, rpath) + fullpath = joinpath(basedir(pkgdata), rpath) if fullpath != filename cache_file_key[fullpath] = filename src_file_key[filename] = fullpath end - push!(files, rpath) + push!(pkgdata, rpath=>FileInfo(submod, basesrccache)) if mtime(ffilename) > mtcache with_logger(_debug_logger) do @debug "Recipe for Base/StdLib" _group="Watching" filename=filename mtime=mtime(filename) mtimeref=mtcache @@ -60,15 +63,15 @@ function track(mod::Module; modified_files=revision_queue) end end # Add the files to the watch list - init_watching(pkgdata, files) - elseif mod == Core.Compiler + init_watching(pkgdata, srcfiles(pkgdata)) + elseif modname == :Compiler compilerdir = normpath(joinpath(juliadir, "base", "compiler")) if !haskey(pkgdatas, id) pkgdatas[id] = PkgData(id, compilerdir) end track_subdir_from_git(id, compilerdir; modified_files=modified_files) else - error("no Revise.track recipe for module ", mod) + error("no Revise.track recipe for module ", modname) end return nothing end @@ -110,7 +113,6 @@ function track_subdir_from_git(id::PkgId, subdir::AbstractString; commit=Base.GI tree = git_tree(repo, commit) files = Iterators.filter(file->startswith(file, prefix) && endswith(file, ".jl"), keys(tree)) ccall((:giterr_clear, :libgit2), Cvoid, ()) # necessary to avoid errors like "the global/xdg file 'attributes' doesn't exist: No such file or directory" - wfiles = String[] # files to watch for file in files fullpath = joinpath(repo_path, file) rpath = relpath(fullpath, pkgdata) # this might undo the above, except for Core.Compiler @@ -134,12 +136,11 @@ function track_subdir_from_git(id::PkgId, subdir::AbstractString; commit=Base.GI else instantiate_sigs!(fi.fm) end - pkgdata.fileinfos[rpath] = fi - push!(wfiles, rpath) + push!(pkgdata, rpath=>fi) end if !isempty(pkgdata.fileinfos) pkgdatas[id] = pkgdata - init_watching(pkgdata, wfiles) + init_watching(pkgdata, srcfiles(pkgdata)) end return nothing end diff --git a/src/types.jl b/src/types.jl index d580705c..fd6c0be5 100644 --- a/src/types.jl +++ b/src/types.jl @@ -71,6 +71,18 @@ function Base.show(io::IO, fmm::FMMaps) end end +Base.iterate(fmm::FMMaps) = _returnval(iterate(fmm.defmap)) +Base.iterate(fmm::FMMaps, state) = _returnval(iterate(fmm.defmap, state)) + +function _returnval(ret) + ret === nothing && return nothing + (rex, val), state = ret + if val !== nothing + val = val[1] + end + return (rex, val), state +end + """ FileModules @@ -137,15 +149,41 @@ For the `PkgData` associated with `Main` (e.g., for files loaded with [`includet the corresponding `path` entry will be empty. """ mutable struct PkgData - id::PkgId - path::String - fileinfos::Dict{String,FileInfo} + info::PkgFiles + fileinfos::Vector{FileInfo} end -PkgData(id::PkgId, path) = PkgData(id, path, Dict{String,FileInfo}()) +PkgData(id::PkgId, path) = PkgData(PkgFiles(id, path), FileInfo[]) PkgData(id::PkgId, ::Nothing) = PkgData(id, "") PkgData(id::PkgId) = PkgData(id, normpath(basepath(id))) +# Abstraction interface for PkgData +Base.PkgId(pkgdata::PkgData) = PkgId(pkgdata.info) +CodeTracking.basedir(pkgdata::PkgData) = basedir(pkgdata.info) +CodeTracking.srcfiles(pkgdata::PkgData) = srcfiles(pkgdata.info) + +function fileindex(info, file::AbstractString) + for (i, f) in enumerate(srcfiles(info)) + f == file && return i + end + return nothing +end + +hasfile(info, file) = fileindex(info, file) !== nothing + +function fileinfo(pkgdata::PkgData, file::AbstractString) + i = fileindex(pkgdata, file) + i === nothing && error("file ", file, " not found") + return pkgdata.fileinfos[i] +end +fileinfo(pkgdata::PkgData, i::Integer) = pkgdata.fileinfos[i] + +function Base.push!(pkgdata::PkgData, pr::Pair{<:AbstractString,FileInfo}) + push!(srcfiles(pkgdata), pr.first) + push!(pkgdata.fileinfos, pr.second) + return pkgdata +end + struct GitRepoException <: Exception filename::String end diff --git a/src/utils.jl b/src/utils.jl index 894deed9..7ae8a00c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,8 +1,8 @@ relpath_safe(path, startpath) = isempty(startpath) ? path : relpath(path, startpath) function Base.relpath(filename, pkgdata::PkgData) - if isabspath(filename) && startswith(filename, pkgdata.path) - filename = relpath_safe(filename, pkgdata.path) + if isabspath(filename) && startswith(filename, basedir(pkgdata)) + filename = relpath_safe(filename, basedir(pkgdata)) elseif startswith(filename, "compiler") # Core.Compiler's pkgid includes "compiler/" in the path filename = relpath(filename, "compiler") diff --git a/test/runtests.jl b/test/runtests.jl index d4b16a8d..3a9badce 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using Revise +using Revise, CodeTracking using Test @test isempty(detect_ambiguities(Revise, Base, Core)) @@ -81,6 +81,8 @@ end sig_type_exprs(ex) = Revise.sig_type_exprs(Main, ex) # just for testing purposes +const pair_op_string = string(Dict(1=>2))[7:end-2] # accomodate changes in Dict printing w/ Julia version + @testset "Revise" begin function collectexprs(ex::Revise.RelocatableExpr) @@ -462,7 +464,7 @@ k(x) = 4 @test str == ":(@inbounds x[2])" fm = Revise.parse_source(joinpath(@__DIR__, "revisetest.jl"), Main) Revise.instantiate_sigs!(fm) - @test string(fm) == "OrderedCollections.OrderedDict(Main=>FMMaps(<1 expressions>, <0 signatures>),Main.ReviseTest=>FMMaps(<2 expressions>, <2 signatures>),Main.ReviseTest.Internal=>FMMaps(<6 expressions>, <5 signatures>))" + @test string(fm) == "OrderedCollections.OrderedDict(Main$(pair_op_string)FMMaps(<1 expressions>, <0 signatures>),Main.ReviseTest$(pair_op_string)FMMaps(<2 expressions>, <2 signatures>),Main.ReviseTest.Internal$(pair_op_string)FMMaps(<6 expressions>, <5 signatures>))" fmmr = fm[ReviseTest] @test string(fmmr) == "FMMaps(<2 expressions>, <2 signatures>)" io = IOBuffer() @@ -541,13 +543,20 @@ end @eval @test $(fn5)() == 5 @eval @test $(fn6)() == 6 m = @eval first(methods($fn1)) - ex = Revise.get_def(m) + ex = Revise.relocatable!(definition(m)) @test ex == convert(Revise.RelocatableExpr, :( $fn1() = 1 )) - # Check that get_def returns copies + # Check that definition returns copies ex2 = deepcopy(ex) ex.args[end].args[end] = 2 - @test Revise.get_def(m) == ex2 - @test Revise.get_def(m) != ex + @test Revise.relocatable!(definition(m)) == ex2 + @test Revise.relocatable!(definition(m)) != ex + # CodeTracking methods + m3 = first(methods(eval(fn3))) + m3file = joinpath(dn, "subdir", "file3.jl") + @test whereis(m3) == (m3file, 1) + @test signatures_at(m3file, 1) == [m3.sig] + @test signatures_at(eval(Symbol(modname)), joinpath("src", "subdir", "file3.jl"), 1) == [m3.sig] + sleep(0.1) # to ensure that the file watching has kicked in # Change the definition of function 1 (easiest to just rewrite the whole file) open(joinpath(dn, modname*".jl"), "w") do io @@ -627,7 +636,7 @@ end joinpath("src", "subdir", "file3.jl"), joinpath("src", "subdir", "file4.jl"), joinpath("src", "file5.jl")] - @test haskey(pkgdata.fileinfos, file) + @test Revise.hasfile(pkgdata, file) end end # Remove the precompiled file @@ -1344,7 +1353,7 @@ end @test GetDef.f([1.0]) == 2 @test GetDef.f([1]) == 3 m = @which GetDef.f([1]) - ex = Revise.get_def(m) + ex = Revise.relocatable!(definition(m)) @test ex isa Revise.RelocatableExpr @test isequal(ex, Revise.relocatable!(:(f(v::AbstractVector{<:Integer}) = 3))) @@ -1570,7 +1579,7 @@ end Revise.track_subdir_from_git(id, joinpath(randdir, "src"); commit="HEAD") end yry() - @test haskey(Revise.pkgdatas[id].fileinfos, mainjl) + @test Revise.hasfile(Revise.pkgdatas[id], mainjl) @test startswith(logs[1].message, "skipping src/extra.jl") rm_precompile("ModuleWithNewFile") pop!(LOAD_PATH) @@ -1582,23 +1591,23 @@ end Revise.track(Base) id = Base.PkgId(Base) pkgdata = Revise.pkgdatas[id] - @test any(k->endswith(k, "number.jl"), keys(pkgdata.fileinfos)) - @test length(filter(k->endswith(k, "file.jl"), keys(pkgdata.fileinfos))) == 1 + @test any(k->endswith(k, "number.jl"), Revise.srcfiles(pkgdata)) + @test length(filter(k->endswith(k, "file.jl"), Revise.srcfiles(pkgdata))) == 1 m = @which show([1,2,3]) - @test Revise.get_def(m) isa Revise.RelocatableExpr + @test definition(m) isa Expr # Tracking stdlibs Revise.track(Unicode) id = Base.PkgId(Unicode) pkgdata = Revise.pkgdatas[id] - @test any(k->endswith(k, "Unicode.jl"), keys(pkgdata.fileinfos)) - @test Revise.get_def(first(methods(Unicode.isassigned))) isa Revise.RelocatableExpr + @test any(k->endswith(k, "Unicode.jl"), Revise.srcfiles(pkgdata)) + @test definition(first(methods(Unicode.isassigned))) isa Expr # Submodule of Pkg (note that package is developed outside the # Julia repo, this tests new cases) id = Revise.get_tracked_id(Pkg.Types) pkgdata = Revise.pkgdatas[id] - @test Revise.get_def(first(methods(Pkg.API.add))) isa Revise.RelocatableExpr + @test definition(first(methods(Pkg.API.add))) isa Expr # Determine whether a git repo is available. Travis & Appveyor do not have this. repo, path = Revise.git_repo(Revise.juliadir) @@ -1607,9 +1616,9 @@ end Revise.track(Core.Compiler) id = Base.PkgId(Core.Compiler) pkgdata = Revise.pkgdatas[id] - @test any(k->endswith(k, "compiler.jl"), keys(pkgdata.fileinfos)) + @test any(k->endswith(k, "compiler.jl"), Revise.srcfiles(pkgdata)) m = first(methods(Core.Compiler.typeinf_code)) - @test Revise.get_def(m) isa Revise.RelocatableExpr + @test definition(m) isa Expr # Test that we skip over files that don't end in ".jl" logs, _ = Test.collect_test_logs() do @@ -1734,8 +1743,8 @@ if Sys.islinux() end # https://github.com/timholy/Rebugger.jl/issues/3 m = which(Plots.histogram, Tuple{Vector{Float64}}) - def = Revise.get_def(m) - @test def isa Revise.RelocatableExpr + def = definition(m) + @test_broken def isa Expr # Tests for "module hygiene" @test !isdefined(Main, :JSON) # internal to Plots