Skip to content

Commit

Permalink
parse source files statically getgauge/gauge-vscode#85 #47
Browse files Browse the repository at this point in the history
* parsing source files statically to build method cahce
* reloading steps on cache file request
* executing source only for execution phase
* removed step_text_map and recoverable_steps_map. step holds required info
  • Loading branch information
BugDiver committed Jan 11, 2018
1 parent 3a70912 commit 34647ea
Show file tree
Hide file tree
Showing 17 changed files with 253 additions and 79 deletions.
10 changes: 5 additions & 5 deletions lib/code_parser.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2015 ThoughtWorks, Inc.
# Copyright 2018 ThoughtWorks, Inc.
#
# This file is part of Gauge-Ruby.
#
Expand Down Expand Up @@ -46,17 +46,17 @@ def self.refactor_args(code, param_positions, new_param_values, new_step_text)
}
buffer = Parser::Source::Buffer.new '(rewriter)'
buffer.source=code

ast = code_to_ast(code)
new_params_string = "|#{new_params.join(', ')}|".gsub("||", "") # no params = empty string

rewriter = Parser::Source::Rewriter.new(buffer)
.replace(ast.children[0].location.expression, "step '#{new_step_text}'")

# hack, could not find an easy way to manipulate the ast to include arguments, when none existed originally.
# it's just easy to add arguments via string substitution.
return include_args(rewriter.process, new_params_string) if ast.children[1].location.expression.nil?

#insert new arguments
rewriter.replace(ast.children[1].location.expression, new_params_string).process
end
Expand Down
13 changes: 7 additions & 6 deletions lib/executor.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2015 ThoughtWorks, Inc.
# Copyright 2018 ThoughtWorks, Inc.

# This file is part of Gauge-Ruby.

Expand All @@ -21,21 +21,22 @@ module Gauge
# @api private
module Executor
def self.load_steps(steps_implementation_dir)
Dir["#{steps_implementation_dir}/**/*.rb"].each { |x|
Dir["#{steps_implementation_dir}/**/*.rb"].each do |x|
begin
ENV['GAUGE_STEP_FILE'] = x
require x
rescue Exception => e
puts "[ERROR] Cannot import #{x}. Reason: #{e.message}"
end
}
end
end

def self.execute_step(step, args)
block = MethodCache.get_step step
si = MethodCache.get_step_info step
if args.size == 1
block.call(args[0])
si[:block].call(args[0])
else
block.call(args)
si[:block].call(args)
end
end

Expand Down
33 changes: 17 additions & 16 deletions lib/gauge.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2015 ThoughtWorks, Inc.
# Copyright 2018 ThoughtWorks, Inc.

# This file is part of Gauge-Ruby.

Expand Down Expand Up @@ -32,7 +32,7 @@ class << self
# puts "I am the $1 hook"
# end
def hook(hook)
define_method hook do |options={}, &block|
define_method hook do |options = {}, &block|
Gauge::MethodCache.send("add_#{hook}_hook".to_sym, options, &block)
end
end
Expand Down Expand Up @@ -104,31 +104,32 @@ def tagged_hook(hook)
# @param block [block] the implementation block for given step.
def step(*args, &block)
opts = args.select {|x| x.is_a? Hash}
step_texts = args-opts
opts = {:continue_on_failure => false}.merge opts.reduce({}, :merge)
step_texts = args - opts
opts = { continue_on_failure: false }.merge opts.reduce({}, :merge)
step_texts.each do |text|
parameterized_step_text = Gauge::Connector.step_value(text)
Gauge::MethodCache.add_step(parameterized_step_text, &block)
Gauge::MethodCache.add_step_text(parameterized_step_text, text)
Gauge::MethodCache.set_recoverable(parameterized_step_text) if opts[:continue_on_failure]
step_value = Gauge::Connector.step_value(text)
si = { location: { file: ENV['GAUGE_STEP_FILE'], span: {} },
block: block, step_text: text,
recoverable: opts[:continue_on_failure] }
Gauge::MethodCache.add_step(step_value, si)
end
Gauge::MethodCache.add_step_alias(*step_texts)
end

# Invoked before execution of every step.
tagged_hook "before_step"
tagged_hook 'before_step'
# Invoked after execution of every step.
tagged_hook "after_step"
tagged_hook 'after_step'
# Invoked before execution of every specification.
tagged_hook "before_spec"
tagged_hook 'before_spec'
# Invoked after execution of every specification.
tagged_hook "after_spec"
tagged_hook 'after_spec'
# Invoked before execution of every scenario.
tagged_hook "before_scenario"
tagged_hook 'before_scenario'
# Invoked after execution of every scenario.
tagged_hook "after_scenario"
tagged_hook 'after_scenario'
# Invoked before execution of the entire suite.
hook "before_suite"
hook 'before_suite'
# Invoked after execution of the entire suite.
hook "after_suite"
hook 'after_suite'
end
9 changes: 5 additions & 4 deletions lib/gauge_runtime.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2015 ThoughtWorks, Inc.
# Copyright 2018 ThoughtWorks, Inc.

# This file is part of Gauge-Ruby.

Expand All @@ -20,14 +20,15 @@

require_relative 'messages.pb'
require_relative 'executor'
require_relative 'static_loader'
require_relative 'connector'
require_relative 'message_processor'


module Gauge
# @api private
module Runtime
DEFAULT_IMPLEMENTATIONS_DIR_PATH = File.join(Dir.pwd, 'step_implementations')
DEFAULT_IMPLEMENTATIONS_DIR_PATH = File.join(ENV["GAUGE_PROJECT_ROOT"], 'step_implementations')

def self.dispatch_messages(socket)
while (!socket.eof?)
Expand All @@ -51,7 +52,7 @@ def self.handle_message(socket, message)
write_message(socket, message)
else
response = MessageProcessor.process_message message
write_message(socket, response)
write_message(socket, response) if response
end
end

Expand All @@ -72,7 +73,7 @@ def self.portFromEnvVariable(envVariable)

STDOUT.sync = true
Connector.make_connections()
Executor.load_steps(DEFAULT_IMPLEMENTATIONS_DIR_PATH)
StaticLoader.load_files(DEFAULT_IMPLEMENTATIONS_DIR_PATH)
dispatch_messages(Connector.executionSocket)
exit(0)
end
Expand Down
2 changes: 2 additions & 0 deletions lib/message_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class MessageProcessor
@processors[Messages::Message::MessageType::ScenarioDataStoreInit] = method(:process_datastore_init)
@processors[Messages::Message::MessageType::StepNameRequest] = method(:process_step_name_request)
@processors[Messages::Message::MessageType::RefactorRequest] = method(:refactor_step)
@processors[Messages::Message::MessageType::CacheFileRequest] = method(:process_cache_file_request)
# @processors[Messages::Message::MessageType::StepPositionsRequest] = method(:process_step_positions_request)

def self.is_valid_message(message)
return @processors.has_key? message.messageType
Expand Down
62 changes: 36 additions & 26 deletions lib/method_cache.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2015 ThoughtWorks, Inc.
# Copyright 2018 ThoughtWorks, Inc.

# This file is part of Gauge-Ruby.

Expand All @@ -19,39 +19,44 @@
module Gauge
# @api private
class MethodCache
["before_step", "after_step", "before_spec", "after_spec", "before_scenario", "after_scenario", "before_suite", "after_suite"].each { |hook|
define_singleton_method "add_#{hook}_hook" do |options={}, &block|
%w[before_step after_step before_spec after_spec before_scenario after_scenario before_suite after_suite].each { |hook|
define_singleton_method "add_#{hook}_hook" do |options = {}, &block|
options = {operator: "AND", tags: []}.merge options
self.class_variable_get("@@#{hook}_hooks").push :block=> block, :options=> options
class_variable_get("@@#{hook}_hooks").push :block => block, :options => options
end
define_singleton_method "get_#{hook}_hooks" do
self.class_variable_get("@@#{hook}_hooks")
class_variable_get("@@#{hook}_hooks")
end
}

def self.clear_hooks(hook)
self.class_variable_get("@@#{hook}_hooks").clear
class_variable_get("@@#{hook}_hooks").clear
end

def self.add_step(parameterized_step_text, &block)
@@steps_map[parameterized_step_text] = @@steps_map[parameterized_step_text] || []
@@steps_map[parameterized_step_text].push(block)
def self.clear()
class_variable_get("@@steps_map").clear
end

def self.get_step(parameterized_step_text)
@@steps_map[parameterized_step_text][0]
def self.add_step(step_value, step_info)
if @@steps_map.key? step_value
@@steps_map[step_value][:locations].push(step_info[:location])
else
@@steps_map[step_value] = {
locations: [step_info[:location]],
block: step_info[:block],
step_text: step_info[:step_text],
recoverable: step_info[:recoverable]
}
end
end

def self.get_steps(parameterized_step_text)
@@steps_map[parameterized_step_text] || []
def self.get_step_info(step_value)
@@steps_map[step_value]
end

def self.add_step_text(parameterized_step_text, step_text)
@@steps_text_map[parameterized_step_text] = step_text
end

def self.get_step_text(parameterized_step_text)
@@steps_text_map[parameterized_step_text]
def self.get_step_text(step_value)
@@steps_map[step_value][:step_text]
end

def self.add_step_alias(*step_texts)
Expand All @@ -63,26 +68,31 @@ def self.has_alias?(step_text)
end

def self.valid_step?(step)
@@steps_map.has_key? step
@@steps_map.key? step
end

def self.all_steps
@@steps_text_map.values
@@steps_map.values.map { |si| si[:step_text] }
end

def self.is_recoverable?(parameterized_step_text)
@@recoverable_steps.include? parameterized_step_text
def self.recoverable?(step_value)
@@steps_map[step_value][:recoverable]
end

def self.remove_steps(file)
@@steps_map.each_pair do |step, info|
l = info[:locations].reject { |loc| loc[:file] == file }
l.empty? ? @@steps_map.delete(step) : @@steps_map[step][:locations] = l
end
end

def self.set_recoverable(parameterized_step_text)
@@recoverable_steps.push parameterized_step_text
def self.multiple_implementation?(step_value)
@@steps_map[step_value][:locations].length > 1
end

private
@@steps_map = Hash.new
@@steps_text_map = Hash.new
@@steps_with_aliases = []
@@recoverable_steps = []
@@before_suite_hooks = []
@@after_suite_hooks = []
@@before_spec_hooks = []
Expand Down
38 changes: 38 additions & 0 deletions lib/processors/cache_file_processor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2018 ThoughtWorks, Inc.

# This file is part of Gauge-Ruby.

# Gauge-Ruby is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# Gauge-Ruby is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with Gauge-Ruby. If not, see <http://www.gnu.org/licenses/>.

require_relative '../static_loader'
require_relative '../method_cache'
module Gauge
# @api private
module Processors
def process_cache_file_request(message)
if !message.cacheFileRequest.isClosed
ast = CodeParser.code_to_ast(message.cacheFileRequest.content)
StaticLoader.reload_steps(message.cacheFileRequest.filePath, ast)
else
load_from_disk message.cacheFileRequest.filePath
end
nil
end

def load_from_disk(f)
ast = CodeParser.code_to_ast File.read(f)
File.file? f ? StaticLoader.reload_steps(f, ast) : StaticLoader.remove_steps(f)
end
end
end
2 changes: 1 addition & 1 deletion lib/processors/execute_step_request_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def process_execute_step_request(message)
begin
Executor.execute_step step_text, args
rescue Exception => e
return handle_failure message, e, time_elapsed_since(start_time), MethodCache.is_recoverable?(step_text)
return handle_failure message, e, time_elapsed_since(start_time), MethodCache.recoverable?(step_text)
end
handle_pass message, time_elapsed_since(start_time)
end
Expand Down
5 changes: 4 additions & 1 deletion lib/processors/execution_hook_processors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@

require_relative "execution_handler"
require_relative "../gauge_messages"

require_relative "../executor"
require_relative "../method_cache"
module Gauge
module Processors
include ExecutionHandler

def process_execution_start_request(message)
Gauge::MethodCache.clear
Executor.load_steps(File.join(ENV["GAUGE_PROJECT_ROOT"], 'step_implementations'))
handle_hooks_execution(MethodCache.get_before_suite_hooks, message, message.executionStartingRequest.currentExecutionInfo,false)
end

Expand Down
6 changes: 3 additions & 3 deletions lib/processors/refactor_step_request_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ def refactor_step(message)
end

def get_step(step_text)
blocks = MethodCache.get_steps step_text
raise "Multiple step implementations found for => '#{step_text}'" if blocks.length > 1
blocks[0]
md = MethodCache.multiple_implementation? step_text
raise "Multiple step implementations found for => '#{step_text}'" if md
MethodCache.get_step_info(step_text)[:block]
end
end
end
11 changes: 5 additions & 6 deletions lib/processors/step_validation_request_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ def process_step_validation_request(message)
is_valid = true
msg = ''
err_type = nil
blocks = MethodCache.get_steps(step_validate_request.stepText)
if blocks.length > 1
is_valid = false
msg = "Multiple step implementations found for => '#{step_validate_request.stepText}'"
err_type = Messages::StepValidateResponse::ErrorType::DUPLICATE_STEP_IMPLEMENTATION
elsif blocks.length == 0
if !MethodCache.valid_step? step_validate_request.stepText
is_valid = false
msg = 'Step implementation not found'
err_type = Messages::StepValidateResponse::ErrorType::STEP_IMPLEMENTATION_NOT_FOUND
elsif MethodCache.multiple_implementation?(step_validate_request.stepText)
is_valid = false
msg = "Multiple step implementations found for => '#{step_validate_request.stepText}'"
err_type = Messages::StepValidateResponse::ErrorType::DUPLICATE_STEP_IMPLEMENTATION
end
step_validate_response = Messages::StepValidateResponse.new(:isValid => is_valid, :errorMessage => msg, :errorType => err_type)
Messages::Message.new(:messageType => Messages::Message::MessageType::StepValidateResponse,
Expand Down
Loading

0 comments on commit 34647ea

Please sign in to comment.