lib/org-converge/command.rb in org-converge-0.0.7 vs lib/org-converge/command.rb in org-converge-0.0.8
- old
+ new
@@ -4,18 +4,25 @@
attr_reader :logger
attr_reader :ob
attr_reader :engine
def initialize(options)
- @options = options
- @dotorg = options['<org_file>']
- @root_dir = options['--root-dir']
- @run_dir = if @root_dir
- File.expand_path(File.join(@root_dir, 'run'))
- else
- File.expand_path('run')
- end
+ @options = options
+ @dotorg = options['<org_file>']
+ @root_dir = options['--root-dir']
+ @run_dir = if @root_dir
+ File.expand_path(File.join(@root_dir, 'run'))
+ else
+ File.expand_path('run')
+ end
+ # The results dir will have a timestamp to avoid having to refresh all the time
+ results_dirname = "results_#{Time.now.strftime("%Y%m%d%H%M%S")}"
+ @results_dir = if @root_dir
+ File.expand_path(File.join(@root_dir, results_dirname))
+ else
+ File.expand_path(results_dirname)
+ end
@ob = Orgmode::Parser.new(File.read(dotorg)).babelize
@babel = nil
@logger = Logger.new(options['--log'] || STDOUT)
logger.formatter = proc do |severity, datetime, progname, msg|
"[#{datetime.strftime('%Y-%m-%dT%H:%M:%S %z')}] #{msg}\n"
@@ -38,18 +45,19 @@
false
end
def converge!
tangle!
+ runmode = @options['--runmode'] || ob.in_buffer_settings['RUNMODE']
case
- when @options['--runmode']
- dispatch_runmode(@options['--runmode'])
when @options['--name']
- run_matching_blocks!
+ if runmode == 'sequentially'
+ run_matching_blocks_sequentially!
+ else
+ run_matching_blocks!
+ end
else
- # Try to find one in the buffer
- runmode = ob.in_buffer_settings['RUNMODE']
dispatch_runmode(runmode)
end
end
def dispatch_runmode(runmode)
@@ -58,10 +66,12 @@
run_blocks_in_parallel!
when 'sequential'
run_blocks_sequentially!
when 'chained', 'chain', 'tasks'
run_blocks_chain!
+ when 'spec'
+ run_against_blocks_results!
else # parallel by default
run_blocks_in_parallel!
end
end
@@ -175,11 +185,138 @@
logger.info "Running code blocks now! (#{scripts.count} runnable blocks found in total)"
@engine.start
logger.info "Run has completed successfully.".fg 'green'
end
- def with_running_engine
- engine = OrgConverge::Engine.new(:logger => @logger, :babel => @babel)
+ def run_matching_blocks_sequentially!
+ babel.tangle_runnable_blocks!(@run_dir)
+
+ runlist_stack = []
+ scripts = babel.ob.scripts.select {|k, h| h[:header][:name] =~ Regexp.new(@options['--name']) }
+ scripts.each do |key, script|
+ runlist_stack << [key, script]
+ end
+
+ while not runlist_stack.empty?
+ key, script = runlist_stack.shift
+
+ # Decision: Only run blocks which have a name
+ next unless script[:header][:name]
+
+ display_name = script[:header][:name]
+ with_running_engine do |engine|
+ file = File.expand_path("#{@run_dir}/#{key}")
+ cmd = "#{script[:lang]} #{file}"
+ engine.register display_name, cmd, { :cwd => @root_dir, :logger => logger }
+ end
+ end
+ logger.info "Run has completed successfully.".fg 'green'
+ end
+
+ def run_against_blocks_results!
+ require 'diff/lcs'
+ require 'diff/lcs/hunk'
+
+ succeeded = []
+ failed = []
+
+ logger.info "Runmode: spec"
+ runlist_stack = []
+ scripts = if @options['--name']
+ babel.ob.scripts.select {|k, h| h[:header][:name] =~ Regexp.new(@options['--name']) }
+ else
+ babel.ob.scripts
+ end
+ scripts.each { |key, script| runlist_stack << [key, script] }
+
+ babel.tangle_runnable_blocks!(@run_dir)
+ FileUtils.mkdir_p(@results_dir)
+
+ while not runlist_stack.empty?
+ key, script = runlist_stack.shift
+
+ # Decision: Only run blocks which have a name
+ next unless script[:header][:name]
+
+ display_name = script[:header][:name]
+ script_file = File.expand_path("#{@run_dir}/#{key}")
+ results_file = File.expand_path("#{@results_dir}/#{key}")
+ cmd = "#{script[:lang]} #{script_file}"
+
+ with_running_engine(:runmode => 'spec', :results_dir => @results_dir) \
+ do |engine|
+ engine.register display_name, cmd, {
+ :cwd => @root_dir,
+ :logger => logger,
+ :results => results_file
+ }
+ end
+
+ if scripts[:results]
+ print "Checking results from '#{display_name.fg 'yellow'}' code block:\t"
+ expected_lines = script[:results].split("\n").map! {|e| e.chomp }
+ actual_lines = File.open(results_file).read.split("\n").map! {|e| e.chomp }
+
+ output_diff = diff(expected_lines, actual_lines)
+ if output_diff.empty?
+ puts "OK".fg 'green'
+ succeeded << display_name
+ else
+ puts "DIFF".fg 'red'
+ puts output_diff.fg 'red'
+ failed << display_name
+ end
+ end
+ end
+
+ if failed.count > 0
+ puts ''
+ puts 'Failed code blocks:'.fg 'red'
+ failed.each do |name|
+ puts " - #{name.fg 'yellow'}"
+ end
+ puts ''
+ end
+
+ puts "#{succeeded.count + failed.count} examples, #{failed.count} failures".fg 'green'
+ exit 1 if failed.count > 0
+ end
+
+ def diff(expected_lines, actual_lines)
+ output = ""
+ file_length_difference = 0
+
+ diffs = Diff::LCS.diff(expected_lines, actual_lines)
+ hunks = diffs.map do |piece|
+ Diff::LCS::Hunk.new(
+ expected_lines, actual_lines, piece, 3, 0
+ ).tap do |h|
+ file_length_difference = h.file_length_difference
+ end
+ end
+
+ hunks.each_cons(2) do |prev_hunk, current_hunk|
+ begin
+ if current_hunk.overlaps?(prev_hunk)
+ current_hunk.merge(prev_hunk)
+ else
+ output << prev_hunk.diff(:unified).to_s
+ end
+ rescue => e
+ end
+ end
+
+ if hunks.last
+ output << hunks.last.diff(:unified).to_s
+ end
+
+ output
+ end
+
+ def with_running_engine(opts={})
+ default_options = { :logger => @logger, :babel => @babel }
+ options = default_options.merge!(opts)
+ engine = OrgConverge::Engine.new(options)
yield engine
engine.start
end
def babel