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