# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com># # Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the "Software"), to deal# in the Software without restriction, including without limitation the rights# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell# copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:# # The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# 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'require'parser/current'moduleCovered# The source map, loads the source file, parses the AST to generate which lines contain executable code.classSource<WrapperEXECUTABLE=/(.?CALL|.VAR|.ASGN|DEFN)/.freeze# Deviate from the standard policy above, because all the files are already loaded, so we skip NODE_FCALL.DOGFOOD=/(^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=/(ARGS|CDECL)/.freezedefinitialize(output,executable: EXECUTABLE,ignore: IGNORE)super(output)@paths={}@mutex=Mutex.new@executable=executable@ignore=ignore@annotations={}enddefenablesuperEval::enable(self)enddefdisableEval::disable(self)superendattr:pathsdefintercept_eval(string,binding=nil,filename=nil,lineno=1)returnunlessfilename@mutex.synchronizedo@paths[filename]=stringendenddefexecutable?(node)node.type==:sendenddefignore?(node)node.nil?ornode.type==:argenddefexpand(node,coverage,level=0)ifnode.is_a?Parser::AST::Nodeifignore?(node)coverage.annotate(node.location.line,"ignoring #{node.type}")elseifexecutable?(node)# coverage.annotate(node.first_lineno, "executable #{node.type}")coverage.counts[node.location.line]||=0else# coverage.annotate(node.first_lineno, "not executable #{node.type}")endexpand(node.children,coverage,level+1)endelsifnode.is_a?Arraynode.eachdo|child|expand(child,coverage,level)endelsereturnfalseendenddefparse(path)ifsource=@paths[path]Parser::CurrentRuby.parse(source)elsifFile.exist?(path)Parser::CurrentRuby.parse_file(path)elsewarn"Couldn't parse #{path}, file doesn't exist?"endrescuewarn"Couldn't parse #{path}: #{$!}"enddefeach(&block)@output.eachdo|coverage|iftop=parse(coverage.path)self.expand(top,coverage)endyieldcoverage.freezeendendendend