Class: Covered::Source

Inherits:
Wrapper show all
Defined in:
lib/covered/source.rb

Overview

The source map, loads the source file, parses the AST to generate which lines contain executable code.

Constant Summary collapse

EXECUTABLE =
/NODE_(.?CALL|.VAR|.ASGN|DEFN)/.freeze
DOGFOOD =

Deviate from the standard policy above, because all the files are already loaded, so we skip NODE_FCALL.

/NODE_([V]?CALL|.VAR|.ASGN|DEFN)/.freeze
IGNORE =

Ruby trace points don’t trigger for argument execution. Constants are loaded when the file loads, so they are less interesting.

/NODE_(ARGS|CDECL)/.freeze

Instance Attribute Summary collapse

Attributes inherited from Wrapper

#output

Instance Method Summary collapse

Methods inherited from Wrapper

#mark, #to_h

Constructor Details

#initialize(output, executable: EXECUTABLE, ignore: IGNORE) ⇒ Source

Returns a new instance of Source.



38
39
40
41
42
43
44
45
46
# File 'lib/covered/source.rb', line 38

def initialize(output, executable: EXECUTABLE, ignore: IGNORE)
	super(output)
	
	@paths = {}
	@mutex = Mutex.new
	
	@executable = executable
	@ignore = ignore
end

Instance Attribute Details

#pathsObject (readonly)

Returns the value of attribute paths.



60
61
62
# File 'lib/covered/source.rb', line 60

def paths
  @paths
end

Instance Method Details

#disableObject



54
55
56
57
58
# File 'lib/covered/source.rb', line 54

def disable
	Eval::disable(self)
	
	super
end

#each(&block) ⇒ Object



104
105
106
107
108
109
110
111
112
113
# File 'lib/covered/source.rb', line 104

def each(&block)
	@output.each do |coverage|
		# This is a little bit inefficient, perhaps add a cache layer?
		if top = parse(coverage.path)
			expand(top, coverage.counts)
		end
		
		yield coverage.freeze
	end
end

#enableObject



48
49
50
51
52
# File 'lib/covered/source.rb', line 48

def enable
	super
	
	Eval::enable(self)
end

#executable?(node) ⇒ Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/covered/source.rb', line 71

def executable?(node)
	node.type =~ @executable
end

#expand(node, counts) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/covered/source.rb', line 80

def expand(node, counts)
	# puts "#{node.first_lineno}: #{node.inspect}"
	
	counts[node.first_lineno] ||= 0 if executable?(node)
	
	node.children.each do |child|
		next if child.nil? or ignore?(child)
		
		expand(child, counts)
	end
end

#ignore?(node) ⇒ Boolean

Returns:

  • (Boolean)


75
76
77
78
# File 'lib/covered/source.rb', line 75

def ignore?(node)
	# NODE_ARGS Ruby doesn't report execution of arguments in :line tracepoint.
	node.type =~ @ignore
end

#intercept_eval(string, binding = nil, filename = nil, lineno = 1) ⇒ Object



62
63
64
65
66
67
68
69
# File 'lib/covered/source.rb', line 62

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

#parse(path) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/covered/source.rb', line 92

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