# Debugging Revise

## Handling errors

Revise attempts to make error reports mimic Julia's own stacktraces, and as a consequence it has
to prevent stacktraces from containing lots of lines pointing to Revise's own code.
If you're trying to debug a Revise error, you'd probably prefer to see the entire stacktrace.
You can uncomment the obvious commented-out line in [`Revise.trim_toplevel!`](@ref).

## The logging framework

If Revise isn't behaving the way you expect it to, it can be useful to examine the
decisions it made.
Revise supports Julia's [Logging framework](https://docs.julialang.org/en/v1/stdlib/Logging/)
and can optionally record its decisions in a format suitable for later inspection.
What follows is a simple series of steps you can use to turn on logging, capture messages,
and then submit them with a bug report.
Alternatively, more advanced developers may want to examine the logs themselves to determine
the source of Revise's error, and for such users a few tips about interpreting the log
messages are also provided below.

### Turning on logging

Currently, the best way to turn on logging is within a running Julia session:

```jldoctest; setup=(using Revise)
julia> rlogger = Revise.debug_logger()
ReviseLogger with min_level=Debug
```
You'll use `rlogger` at the end to retrieve the logs.

Now carry out the series of julia commands and code edits that reproduces the problem.

### Capturing the logs and submitting them with your bug report

Once all the revisions have been triggered and the mistake has been reproduced,
it's time to capture the logs.
To capture all the logs, use

```julia-repl
julia> using Base.CoreLogging: Debug

julia> logs = filter(r->r.level==Debug, rlogger.logs);
```

You can capture just the changes that Revise made to running code with

```julia-repl
julia> logs = Revise.actions(rlogger)
```

You can either let these print to the console and copy/paste the text output into the
issue, or if they are extensive you can save `logs` to a file:

```julia
open("/tmp/revise.logs", "w") do io
    for log in logs
        println(io, log)
    end
end
```

Then you can upload the logs somewhere (e.g., https://gist.github.com/) and link the url in your bug report.
To assist in the resolution of the bug, please also specify additional relevant information such as the name of the function that was misbehaving after revision and/or any error messages that your received.

See also [A complete debugging demo](@ref) below.

### Logging by default

If you suspect a bug in Revise but have difficulty isolating it, you can include the lines

```julia
    # Turn on logging
    Revise.debug_logger()
```

within the `Revise` block of your `~/.julia/config/startup.jl` file.
This will ensure that you always log Revise's actions.
Then carry out your normal Julia development.
If a Revise-related problem arises, executing these lines

```julia
rlogger = Revise.debug_logger()
using Base.CoreLogging: Debug
logs = filter(r->r.level==Debug, rlogger.logs)
open("/tmp/revise.logs", "w") do io
    for log in logs
        println(io, log)
    end
    # also print in verbose form
    ioctx = IOContext(io, :verbose=>true)
    for log in logs
        println(ioctx, log)
    end
end
```

within the same session will generate the `/tmp/revise.logs` file that
you can submit with your bug report.
(What makes this possible is that a second call to `Revise.debug_logger()` returns
the same logger object created by the first call--it is not necessary to hold
on to `rlogger`.)

### The structure of the logs

For those who want to do a little investigating on their own, it may be helpful to
know that Revise's core changes are captured in the group called "Action," and they come in three
flavors:

- log entries with message `"Eval"` signify a call to `eval`; for these events,
  keyword `:deltainfo` has value `(mod, expr)` where `mod` is the module of evaluation
  and `expr` is a [`Revise.RelocatableExpr`](@ref) containing the expression
  that was evaluated.
- log entries with message `"DeleteMethod"` signify a method deletion; for these events,
  keyword `:deltainfo` has value `(sigt, methsummary)` where `sigt` is the signature of the
  method that Revise *intended* to delete and `methsummary` is a [`MethodSummary`](@ref) of the
  method that Revise actually found to delete.
- log entries with message `"LineOffset"` correspond to updates to Revise's own internal
  estimates of how far a given method has become displaced from the line number it
  occupied when it was last evaluated. For these events, `:deltainfo` has value
  `(sigt, newlineno, oldoffset=>newoffset)`.

If you're debugging mistakes in method creation/deletion, the `"LineOffset"` events
may be distracting; by default [`Revise.actions`](@ref) excludes these events.

Note that Revise records the time of each revision, which can sometimes be useful in
determining which revisions occur in conjunction with which user actions.
If you want to make use of this, it can be handy to capture the start time with `tstart = time()`
before commencing on a session.

See [`Revise.debug_logger`](@ref) for information on groups besides "Action."


### A complete debugging demo

From within Revise's `test/` directory, try the following:

```julia-repl
julia> rlogger = Revise.debug_logger();

shell> cp revisetest.jl /tmp/

julia> includet("/tmp/revisetest.jl")

julia> ReviseTest.cube(3)
81

shell> cp revisetest_revised.jl /tmp/revisetest.jl

julia> ReviseTest.cube(3)
27

julia> rlogger.logs
9-element Vector{Revise.LogRecord}:
 Revise DeleteMethod: cube(::Any)
 Revise DeleteMethod: mult3(::Any)
 Revise DeleteMethod: mult4(::Any)
 Revise Eval: #= /tmp/revisetest.jl:7 =#
 Revise Eval: #= /tmp/revisetest.jl:9 =#
 Revise LineOffset
 Revise Eval: #= /tmp/revisetest.jl:14 =#
 Revise LineOffset
 Revise LineOffset
```

You can see that Revise started by deleting three methods, followed by evaluating three new versions of those methods. Interspersed are various changes to the line numbering.

In verbose mode, the same information looks like this:

```julia-repl
julia> show(IOContext(stdout, :verbose=>true), MIME("text/plain"), rlogger.logs)
9-element Vector{Revise.LogRecord}:
 Revise.LogRecord(Debug, DeleteMethod, Action, Revise_4ac0f476, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 296, (time=1.753703251773368e9, deltainfo=(Tuple{typeof(Main.ReviseTest.cube), Any}, cube(::Any))))
 Revise.LogRecord(Debug, DeleteMethod, Action, Revise_4ac0f476, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 296, (time=1.753703251782686e9, deltainfo=(Tuple{typeof(Main.ReviseTest.Internal.mult3), Any}, mult3(::Any))))
 Revise.LogRecord(Debug, DeleteMethod, Action, Revise_4ac0f476, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 296, (time=1.753703251782706e9, deltainfo=(Tuple{typeof(Main.ReviseTest.Internal.mult4), Any}, mult4(::Any))))
 Revise.LogRecord(Debug, Eval, Action, Revise_9147188b, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 347, (time=1.753703251786138e9, deltainfo=(Main.ReviseTest, quote
    #= /tmp/revisetest.jl:7 =#
    cube(x) = begin
            #= /tmp/revisetest.jl:7 =#
            x ^ 3
        end
end)))
 Revise.LogRecord(Debug, Eval, Action, Revise_9147188b, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 347, (time=1.75370325178672e9, deltainfo=(Main.ReviseTest, quote
    #= /tmp/revisetest.jl:9 =#
    fourth(x) = begin
            #= /tmp/revisetest.jl:9 =#
            x ^ 4
        end
end)))
 Revise.LogRecord(Debug, LineOffset, Action, Revise_fcadbd44, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 369, (time=1.753703251787128e9, deltainfo=(CodeTracking.MethodInfoKey[CodeTracking.MethodInfoKey(nothing, Tuple{typeof(Main.ReviseTest.Internal.mult2), Any})], :(#= /tmp/revisetest.jl:11 =#) => :(#= /tmp/revisetest.jl:13 =#))))
 Revise.LogRecord(Debug, Eval, Action, Revise_9147188b, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 347, (time=1.753703251793889e9, deltainfo=(Main.ReviseTest.Internal, quote
    #= /tmp/revisetest.jl:14 =#
    mult3(x) = begin
            #= /tmp/revisetest.jl:14 =#
            3x
        end
end)))
 Revise.LogRecord(Debug, LineOffset, Action, Revise_fcadbd44, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 369, (time=1.753703251795383e9, deltainfo=(CodeTracking.MethodInfoKey[CodeTracking.MethodInfoKey(nothing, Tuple{typeof(Main.ReviseTest.Internal.unchanged), Any})], :(#= /tmp/revisetest.jl:18 =#) => :(#= /tmp/revisetest.jl:19 =#))))
 Revise.LogRecord(Debug, LineOffset, Action, Revise_fcadbd44, "/home/tim/.julia/dev/Revise/src/packagedef.jl", 369, (time=1.753703251795399e9, deltainfo=(CodeTracking.MethodInfoKey[CodeTracking.MethodInfoKey(nothing, Tuple{typeof(Main.ReviseTest.Internal.unchanged2), Any})], :(#= /tmp/revisetest.jl:20 =#) => :(#= /tmp/revisetest.jl:21 =#))))
```

Perhaps the most useful component of this is the timing information, as it gives clues to what you might have been doing to trigger revisions.

In rare cases it might be helpful to independently record the sequence of edits to the file.
You can make copies `cp editedfile.jl > /tmp/version1.jl`, edit code, `cp editedfile.jl > /tmp/version2.jl`,
etc.
`diff version1.jl version2.jl` can be used to capture a compact summary of the changes
and pasted into the bug report.

## Debugging problems with paths

During certain types of usage you might receive messages like

```
Warning: /some/system/path/stdlib/v1.0/SHA/src is not an existing directory, Revise is not watching
```

Unless you've just deleted that directory, this indicates that some of Revise's functionality is broken.

In the majority of cases, failures come down to Revise having trouble locating source
code on your drive.
This problem should be fixable, because Revise includes functionality
to update its links to source files, as long as it knows what to do.

One of the best approaches is to run Revise's own tests via `pkg> test Revise`.
Here are some possible test warnings and errors, and steps you might take to fix them:

- `Base & stdlib file paths: Test Failed at /some/path...  Expression: isfile(Revise.basesrccache)`
  This failure is quite serious, and indicates that you will be unable to access code in `Base`.
  To fix this, look for a file called `"base.cache"` somewhere in your Julia install
  or build directory (for the author, it is at `/home/tim/src/julia-1.0/usr/share/julia/base.cache`).
  Now compare this with the value of `Revise.basesrccache`.
  (If you're getting this failure, presumably they are different.)
  An important "top level" directory is `Sys.BINDIR`; if they differ already at this level,
  consider adding a symbolic link from the location pointed at by `Sys.BINDIR` to the
  corresponding top-level directory in your actual Julia installation.
  You'll know you've succeeded in specifying it correctly when, after restarting
  Julia, `Revise.basesrccache` points to the correct file and `Revise.juliadir`
  points to the directory that contains `base/`.
  If this workaround is not possible or does not succeed, please
  [file an issue](https://github.com/timholy/Revise.jl/issues) with a description of
  why you can't use it and/or
  + details from `versioninfo` and information about how you obtained your Julia installation;
  + the values of `Revise.basesrccache` and `Revise.juliadir`, and the actual paths to `base.cache`
    and the directory containing the running Julia's `base/`;
  + what you attempted when trying to fix the problem;
  + if possible, your best understanding of why this failed to fix it.
- `skipping Core.Compiler tests due to lack of git repo`: this likely indicates
  that you downloaded a Julia binary rather than building Julia from source.
  While Revise should be able to access the code in `Base` and standard libraries,
  at the current time it is not possible for Revise to access julia's Core.Compiler module
  unless you clone Julia's repository and build it from source.
- `skipping git tests because Revise is not under development`: this warning should be
  harmless. Revise has built-in functionality for extracting source code using `git`,
  and it uses itself (i.e., its own git repository) for testing purposes.
  These tests run only if you have checked out Revise for development (`pkg> dev Revise`)
  or on the continuous integration servers (Travis and Appveyor).
