lib/covered/source.rb in covered-0.1.0 vs lib/covered/source.rb in covered-0.3.0
- old
+ new
@@ -16,41 +16,101 @@
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
+require_relative 'eval'
+require_relative 'wrapper'
+
+require 'thread'
+
module Covered
- class Source
- def initialize(files)
- @files = files
- @paths = {}
+ # The source map, loads the source file, parses the AST to generate which lines contain executable code.
+ class Source < Wrapper
+ EXECUTABLE = /NODE_(.?CALL|.VAR|.ASGN|DEFN)/.freeze
+
+ # Deviate from the standard policy above, because all the files are already loaded, so we skip NODE_FCALL.
+ DOGFOOD = /NODE_([V]?CALL|.VAR|.ASGN|DEFN)/.freeze
+
+ # Ruby trace points don't trigger for argument execution.
+ # Constants are loaded when the file loads, so they are less interesting.
+ IGNORE = /NODE_(ARGS|CDECL)/.freeze
+
+ def initialize(output, executable: EXECUTABLE, ignore: IGNORE)
+ super(output)
+ @paths = {}
@mutex = Mutex.new
+
+ @executable = executable
+ @ignore = ignore
end
- # [String -> Source]
+ def enable
+ super
+
+ Eval::enable(self)
+ end
+
+ def disable
+ Eval::disable(self)
+
+ super
+ end
+
attr :paths
- def map(string, binding = nil, filename = nil, lineno = 1)
+ def intercept_eval(string, binding = nil, filename = nil, lineno = 1)
return unless filename
# TODO replace with Concurrent::Map
@mutex.synchronize do
@paths[filename] = string
end
end
- def mark(path, lineno)
- @files.mark(path, lineno)
+ def executable?(node)
+ node.type =~ @executable
end
+ def ignore?(node)
+ # NODE_ARGS Ruby doesn't report execution of arguments in :line tracepoint.
+ node.type =~ @ignore
+ end
+
+ def expand(node, lines)
+ # puts "#{node.first_lineno}: #{node.inspect}"
+
+ lines[node.first_lineno] ||= 0 if executable?(node)
+
+ node.children.each do |child|
+ next if child.nil? or ignore?(child)
+
+ expand(child, lines)
+ end
+ end
+
+ def parse(path)
+ # puts "Parse #{path}"
+
+ if source = @paths[path]
+ RubyVM::AST.parse(source)
+ elsif File.exist?(path)
+ RubyVM::AST.parse_file(path)
+ else
+ warn "Couldn't parse #{path}, file doesn't exist?"
+ end
+ end
+
def each(&block)
- @files.each do |path, lines|
- if source = @paths[path]
- yield path, RubyVM::AST.parse(source), lines
- else
- yield path, RubyVM::AST.parse_file(path), lines
+ @output.each do |path, lines|
+ lines = lines.dup
+
+ if top = parse(path)
+ expand(top, lines)
end
+
+ yield path, lines
end
end
end
end