class JSInstrument::Instrumenter
Attributes
batch_text[RW]
commit_hash[RW]
data_compress_method[RW]
js_object_name[RW]
project_name[RW]
src_filename[RW]
upload_url[RW]
Public Instance Methods
instrument(src, filename)
click to toggle source
# File lib/jsinstrument/instrumenter.rb, line 20 def instrument src, filename raise 'Filename not set' unless filename self.src_filename = filename ast = RKelly::Parser.new.parse src raise 'Parse source failed.' unless ast ast, instrumented_lines, instrumented_func_lines, instrumented_branch_lines = instrument_ast ast instrumented_src = ast.to_ecma inserting = File.open(File.dirname(__FILE__) + '/inserting.js').read bindings = { js_object_name: self.js_object_name, upload_url: self.upload_url, src_filename: self.src_filename, project_name: self.project_name, commit_hash: self.commit_hash, batch_text: self.batch_text, data_compress_method: self.data_compress_method, instrumented_lines: instrumented_lines, instrumented_func_lines: instrumented_func_lines, instrumented_branch_lines: instrumented_branch_lines } (inserting % bindings) + instrumented_src end
instrument_ast(ast)
click to toggle source
# File lib/jsinstrument/instrumenter.rb, line 46 def instrument_ast ast # build parent attr for all nodes in ast # ast = RKelly::Visitors::ParentBuilder.new.build ast instrumented_lines = Set.new instrumented_func_lines = Set.new instrumented_branch_lines = Set.new ast.each do |node| parent = node.parent line = node.line node_classname = get_node_classname node parent_classname = get_node_classname parent # instrument only if we can get the line no. and we haven't instrumented it # line coverage # if line and not instrumented_lines.member? line if ['ExpressionStatement', 'EmptyStatement', 'DoWhile', 'While', 'For', 'ForIn', 'Continue', 'VarDecl', 'Switch', 'Break', 'Throw', 'Return', 'If', 'VarStatement'].index node_classname if 'DoWhile' == parent_classname if parent.left.eql? node instrumented_lines.add line parent.left = get_hitline_call_block node end # in RKelly, ConditionalNode extends IfNode # which is so annoying # I mean why they mix a expression and a statement? elsif ['If', 'While', 'For', 'ForIn', 'With'].index parent_classname if parent.value.eql? node instrumented_lines.add line parent.value = get_hitline_call_block node elsif (parent.respond_to? :else) and parent.else.eql? node instrumented_lines.add line parent.else = get_hitline_call_block node end elsif ['CaseBlock', 'Label'].index parent_classname # do nothing here elsif 'SourceElements' == parent_classname if insert_hitline_before node instrumented_lines.add line end end elsif ['With', 'Try'].index node_classname if 'SourceElements' == parent_classname if insert_hitline_before node instrumented_lines.add line end end end end # function coverage # if line and not instrumented_func_lines.member? line # function declaration and anonymous function if ['FunctionDecl', 'FunctionExpr'].index node_classname if node.function_body insert_hitfunc_in node.function_body, line instrumented_func_lines.add line end end end # branch coverage # if line and not instrumented_branch_lines.member? line # if else if 'Block' == node_classname if 'If' == parent_classname if parent.value.eql? node instrumented_branch_lines.add line insert_hitbranch_in node elsif parent.else.eql? node instrumented_branch_lines.add line insert_hitbranch_in node end end # switch case # CaseClause node has the same structure with BlockNode # so we can share the instrument logic elsif 'CaseClause' == node_classname if node.value instrumented_branch_lines.add line insert_hitbranch_in node end end end end [ast, instrumented_lines.sort, instrumented_func_lines.sort, instrumented_branch_lines.sort] end
Private Instance Methods
get_hit_call(line, func)
click to toggle source
# File lib/jsinstrument/instrumenter.rb, line 144 def get_hit_call line, func ExpressionStatementNode.new( FunctionCallNode.new( ResolveNode.new("#{self.js_object_name}.#{func}"), ArgumentsNode.new([ StringNode.new("\"#{self.src_filename}\""), NumberNode.new(line) ]) ) ) end
get_hitline_call_block(node)
click to toggle source
# File lib/jsinstrument/instrumenter.rb, line 156 def get_hitline_call_block node BlockNode.new( SourceElementsNode.new([ get_hit_call(node.line, "hit_line"), node ]) ) end
get_node_classname(node)
click to toggle source
# File lib/jsinstrument/instrumenter.rb, line 140 def get_node_classname node node.class.name.split(/::/)[-1].sub('Node', '') end
insert_hitbranch_in(block_node)
click to toggle source
# File lib/jsinstrument/instrumenter.rb, line 188 def insert_hitbranch_in block_node # Block.value(SourceElement).value(array).unshift hit_func block_node.value.value.unshift get_hit_call(block_node.line, "hit_branch") end
insert_hitfunc_in(function_body_node, line)
click to toggle source
# File lib/jsinstrument/instrumenter.rb, line 183 def insert_hitfunc_in function_body_node, line # FunctionBody.value(SourceElement).value(array).unshift hit_func function_body_node.value.value.unshift get_hit_call(line, "hit_func") end
insert_hitline_before(node)
click to toggle source
# File lib/jsinstrument/instrumenter.rb, line 165 def insert_hitline_before node # get the original node line # line = node.line # backtrack until we can insert the func # while not SourceElementsNode === node.parent and node.parent != nil node = node.parent end if node.parent == nil false else index = node.parent.value.index{|x| x.eql? node} node.parent.value.insert index, get_hit_call(line, "hit_line") true end end