class Solano::SolanoCli
Attributes
scm[R]
user_details[R]
Public Class Methods
exit_on_failure?()
click to toggle source
Thor has the wrong default behavior
# File lib/solano/cli/solano.rb, line 80 def self.exit_on_failure? return true end
new(*args)
click to toggle source
Calls superclass method
# File lib/solano/cli/solano.rb, line 29 def initialize(*args) super(*args) # TODO: read host from .solano file # TODO: allow selecting which .solano "profile" to use cli_opts = options[:insecure] ? { :insecure => true } : {} cli_opts[:debug] = true @tddium_client = TddiumClient::InternalClient.new(options[:host], options[:port], options[:proto], 1, caller_version, cli_opts) @tddium_clientv3 = TddiumClient::InternalClient.new(options[:host], options[:port], options[:proto], "api/v3", caller_version, cli_opts) @cli_options = options end
Public Instance Methods
account()
click to toggle source
# File lib/solano/cli/commands/account.rb, line 7 def account user_details = solano_setup({:scm => false}) if user_details then # User is already logged in, so just display the info show_user_details(user_details) else exit_failure Text::Error::USE_ACTIVATE end end
activate()
click to toggle source
# File lib/solano/cli/commands/activate.rb, line 9 def activate say "To activate your account, please visit" say "https://ci.predix.io/" solano_setup({:scm => false}) end
config(scope="suite")
click to toggle source
# File lib/solano/cli/commands/config.rb, line 9 def config(scope="suite") params = {:repo => true} if scope == 'suite' then params[:suite] = true end if options[:account] then params[:account] = options[:account] end solano_setup(params) begin config_details = @solano_api.get_config_key(scope) show_config_details(scope, config_details['env']) rescue TddiumClient::Error::API => e exit_failure Text::Error::LIST_CONFIG_ERROR rescue Exception => e exit_failure e.message end end
console(*cmd)
click to toggle source
# File lib/solano/cli/commands/console.rb, line 12 def console(*cmd) solano_setup({:repo => true}) origin_url = @scm.origin_url current_suite_id = @solano_api.current_suite_id if current_suite_id then say "Checking for sessions on this branch" session_result = @solano_api.call_api(:get, "/sessions", {:suite_id => current_suite_id})["sessions"] elsif suite_for_default_branch? then say "Checking for sessions on default branch" session_result = @solano_api.call_api(:get, "/sessions", {:repo_url => origin_url})["sessions"] else say "Checking for sessions on this repository" session_result = @solano_api.call_api(:get, "/sessions", {:repo_url => origin_url})["sessions"] end if defined?(session_result) && session_result.any? then session = session_result[0] session_id = session["id"] q_result = @solano_api.query_session(session_id).tddium_response["session"] suite_id = q_result["suite_id"] start_result = @solano_api.start_console(session_id, suite_id).tddium_response session_id = start_result["interactive_session_id"] # the new interactive session's id if start_result["message"] == "interactive started" then say "Starting console session #{session_id}" ssh_command = nil failures = 0 while !ssh_command do sleep(Default::SLEEP_TIME_BETWEEN_POLLS) begin session = @solano_api.query_session(session_id).tddium_response["session"] failures = 0 # clear any previous transient failures rescue Exception => e failures += 1 say e.to_s session = {} end if failures > 2 then say "Errors connecting to server" return # give up. end if session["stage2_ready"] && session["ssh_command"] then if cmd then ssh_command = "#{session['ssh_command']} -o StrictHostKeyChecking=no \"#{cmd.join(' ')}\"" else ssh_command = "#{session['ssh_command']} -o StrictHostKeyChecking=no" end end end say "SSH Command is #{ssh_command}" # exec terminates this ruby process and lets user control the ssh i/o exec ssh_command elsif start_result["message"] == "interactive already running" dur = duration(Time.parse(start_result['session']['expires_at']) - Time.now) say "Interactive session already running (expires in #{dur})" session_id = start_result["session"]["id"] session = @solano_api.query_session(session_id).tddium_response["session"] if cmd then exec "#{session['ssh_command']} -o StrictHostKeyChecking=no \"#{cmd.join(' ')}\"" else exec "#{session['ssh_command']} -o StrictHostKeyChecking=no" end else say start_result["message"] end else say "Unable to find any previous sessions. Execute solano run first or switch to a branch with a recent session." end end
describe(session_id=nil)
click to toggle source
# File lib/solano/cli/commands/describe.rb, line 15 def describe(session_id=nil) solano_setup({:repo => false}) status_message = '' if !session_id then # params to get the most recent session id on current branch suite_params = { :suite_id => @solano_api.current_suite_id, :active => false, :limit => 1 } if suite_for_current_branch? sessions = suite_params ? @solano_api.get_sessions(suite_params) : [] if sessions.empty? then exit_failure Text::Status::NO_INACTIVE_SESSION end session_id = sessions[0]['id'] session_status = sessions[0]['status'].upcase session_commit = sessions[0]['commit'] current_commit = @scm.current_commit if session_commit == current_commit commit_message = "equal to your current commit" else cnt_ahead = @scm.number_of_commits(session_commit, current_commit) if cnt_ahead == 0 cnt_behind = @scm.number_of_commits(current_commit, session_commit) commit_message = "your workspace is behind by #{cnt_behind} commits" else commit_message = "your workspace is ahead by #{cnt_ahead} commits" end end duration = sessions[0]['duration'] start_timeago = "%s ago" % Solano::TimeFormat.seconds_to_human_time(Time.now - Time.parse(sessions[0]["start_time"])) if duration.nil? finish_timeago = "no info about duration found, started #{start_timeago}" elsif session_status == 'RUNNING' finish_timeago = "in process, started #{start_timeago}" else finish_time = Time.parse(sessions[0]["start_time"]) + duration finish_timeago = "%s ago" % Solano::TimeFormat.seconds_to_human_time(Time.now - finish_time) end status_message = Text::Status::SESSION_STATUS % [session_commit, commit_message, session_status, finish_timeago] end result = @solano_api.query_session_tests(session_id) session_result = Hash.new if options[:verbose] then session_result = @solano_api.query_session(session_id) end filtered = result['session']['tests'] if !options[:all] filtered = filtered.select{|x| x['status'] == 'failed'} end if options[:type] filtered = filtered.select{|x| x['test_type'].downcase == options[:type].downcase} end if options[:json] json = result['session'] json['session'] = session_result['session'] puts JSON.pretty_generate(json) elsif options[:names] say filtered.map{|x| x['test_name']}.join(" ") else filtered.sort!{|a,b| [a['test_type'], a['test_name']] <=> [b['test_type'], b['test_name']]} say Text::Process::DESCRIBE_SESSION % [session_id, status_message, options[:all] ? 'all' : 'failed'] table = [["Test", "Status", "Duration"], ["----", "------", "--------"]] + filtered.map do |x| [ x['test_name'], x['status'], x['elapsed_time'] ? "#{x['elapsed_time']}s" : "-" ] end print_table table end end
find_failing(*files)
click to toggle source
# File lib/solano/cli/commands/find_failing.rb, line 7 def find_failing(*files) solano_setup({:repo => true}) failing = files.pop if !files.include?(failing) exit_failure "Files have to include the failing file, use the copy helper" elsif files.size < 2 exit_failure "Files have to be more than 2, use the copy helper" elsif !success?([failing]) exit_failure "#{failing} fails when run on it's own" elsif success?(files) exit_failure "tests pass locally" else loop do a = remove_from(files, files.size / 2, :not => failing) b = files - (a - [failing]) status, files = find_failing_set([a, b], failing) if status == :finished say "Fails when #{files.join(", ")} are run together" break elsif status == :continue next else exit_failure "unable to isolate failure to 2 files" end end end end
heroku()
click to toggle source
# File lib/solano/cli/commands/heroku.rb, line 10 def heroku say "To activate your heroku account, please visit" say "https://ci.predix.io/" solano_setup({:scm => false}) end
invoke_command(command, *args)
click to toggle source
Calls superclass method
# File lib/solano/cli/solano.rb, line 90 def invoke_command(command, *args) begin super rescue InvocationError if command.name == "help" exit_failure Text::Error::CANT_INVOKE_COMMAND else raise end end end
keys()
click to toggle source
# File lib/solano/cli/commands/keys.rb, line 6 def keys user_details = solano_setup({:scm => false}) begin if user_details then show_third_party_keys_details(user_details) end keys_details = @solano_api.get_keys show_keys_details(keys_details) rescue TddiumClient::Error::API => e exit_failure Text::Error::LIST_KEYS_ERROR end end
login(*args)
click to toggle source
# File lib/solano/cli/commands/login.rb, line 9 def login(*args) user_details = solano_setup({:login => false, :scm => false}) login_options = options.dup if args.first && args.first =~ /@/ login_options[:email] ||= args.first elsif args.first # assume cli token login_options[:cli_token] = args.first end if user_details then say Text::Process::ALREADY_LOGGED_IN elsif user = @solano_api.login_user(:params => @solano_api.get_user_credentials(login_options), :show_error => true) say Text::Process::LOGGED_IN_SUCCESSFULLY if @scm.repo? then @api_config.populate_branches(@solano_api.current_branch) end @api_config.write_config else exit_failure end if prompt_missing_ssh_key then say Text::Process::NEXT_STEPS end end
logout()
click to toggle source
# File lib/solano/cli/commands/logout.rb, line 6 def logout solano_setup({:login => false, :scm => false}) @api_config.logout say Text::Process::LOGGED_OUT_SUCCESSFULLY end
password()
click to toggle source
# File lib/solano/cli/commands/password.rb, line 7 def password user_details = solano_setup({:scm => false}) params = {} params[:current_password] = HighLine.ask(Text::Prompt::CURRENT_PASSWORD) { |q| q.echo = "*" } params[:password] = HighLine.ask(Text::Prompt::NEW_PASSWORD) { |q| q.echo = "*" } params[:password_confirmation] = HighLine.ask(Text::Prompt::PASSWORD_CONFIRMATION) { |q| q.echo = "*" } begin user_id = user_details["id"] @solano_api.update_user(user_id, {:user => params}) say Text::Process::PASSWORD_CHANGED rescue TddiumClient::Error::API => e exit_failure Text::Error::PASSWORD_ERROR % e.explanation rescue TddiumClient::Error::Base => e exit_failure e.message end end
rerun(session_id=nil)
click to toggle source
# File lib/solano/cli/commands/rerun.rb, line 14 def rerun(session_id=nil) params = {:scm => true, :repo => false} if session_id.nil? then params = {:repo => true, :suite => true} end solano_setup(params) session_id ||= session_id_for_current_suite begin result = @solano_api.query_session_tests(session_id) rescue TddiumClient::Error::API => e exit_failure Text::Error::NO_SESSION_EXISTS end tests = result['session']['tests'] tests = tests.select{ |t| [ 'failed', 'error', 'notstarted', 'started'].include?(t['status']) } tests = tests.map{ |t| t['test_name'] } profile = options[:profile] || result['non_passed_profile_name'] cmd = "solano run" cmd += " --max-parallelism=#{options[:max_parallelism]}" if options[:max_parallelism] cmd += " --org=#{options[:account]}" if options[:account] cmd += " --force" if options[:force] cmd += " --profile=#{profile}" if profile cmd += " --queue=#{options[:queue]}" if options[:queue] cmd += " --pipeline=#{options[:pipeline]}" if options[:pipeline] cmd += " #{tests.join(" ")}" say cmd Kernel.exec(cmd) if !options[:no_op] end
server()
click to toggle source
# File lib/solano/cli/commands/server.rb, line 6 def server solano_setup({:scm => false, :login => false}) self.class.display end
spec(*pattern)
click to toggle source
# File lib/solano/cli/commands/spec.rb, line 31 def spec(*pattern) machine_data = {} solano_setup({:repo => true}) unless options[:pipeline] suites = @solano_api.get_suites.select { |suite| suite['repo_url'] == @scm.origin_url } repo_names = suites.map { |suite| suite['repo_name'] }.uniq if repo_names.count > 1 say "You already have pipelines with url: #{@scm.origin_url}" repo_names.each { |repo_name| say " " + repo_name } exit_failure "You must specify an pipeline by passing the --pipeline option." end end suite_auto_configure unless options[:machine] exit_failure unless suite_for_current_branch? suite_id = calc_current_suite_id if !options[:machine] && @solano_api.get_keys.empty? then warn(Text::Warning::NO_SSH_KEY) end if @scm.changes?(options) then exit_failure(Text::Error::SCM_CHANGES_NOT_COMMITTED) if !options[:force] warn(Text::Warning::SCM_CHANGES_NOT_COMMITTED) end test_execution_params = {} if user_data_file_path = options[:user_data_file] then if File.exists?(user_data_file_path) then user_data = File.open(user_data_file_path) { |file| file.read } test_execution_params[:user_data_text] = Base64.encode64(user_data) test_execution_params[:user_data_filename] = File.basename(user_data_file_path) say Text::Process::USING_SPEC_OPTION[:user_data_file] % user_data_file_path else exit_failure Text::Error::NO_USER_DATA_FILE % user_data_file_path end end if max_parallelism = options[:max_parallelism] then test_execution_params[:max_parallelism] = max_parallelism say Text::Process::USING_SPEC_OPTION[:max_parallelism] % max_parallelism end test_execution_params[:tag] = options[:tag] if options[:tag] test_pattern = nil if pattern.is_a?(Array) && pattern.size > 0 then test_pattern = pattern.join(",") end test_pattern ||= options[:test_pattern] if test_pattern then say Text::Process::USING_SPEC_OPTION[:test_pattern] % test_pattern end test_exclude_pattern ||= options[:test_exclude_pattern] if test_exclude_pattern then say Text::Process::USING_SPEC_OPTION[:test_exclude_pattern] % test_exclude_pattern end # Call the API to get the suite and its tests suite_details = @solano_api.get_suite_by_id(suite_id, :session_id => options[:session_id]) start_time = Time.now new_session_params = { :commits_encoded => read_and_encode_latest_commits, :cache_control_encoded => read_and_encode_cache_control, :cache_save_paths_encoded => read_and_encode_cache_save_paths, :raw_config_file => read_and_encode_config_file } if options[:profile] if options[:session_id].nil? say Text::Process::USING_PROFILE % options[:profile] new_session_params[:profile_name] = options[:profile] else exit_fail Text::Error::CANNOT_OVERRIDE_PROFILE end end if options[:queue] if options[:session_id].nil? say Text::Process::USING_PROFILE % options[:profile] new_session_params[:queue] = options[:queue] else exit_fail Text::Error::CANNOT_OVERRIDE_QUEUE end end if options[:env] say Text::Process::USING_CUSTOM_USER_ENV_VARS % "#{options[:env]}" new_session_params[:env] = options[:env] end if options[:volume] say Text::Process::VOLUME_OVERRIDE % options[:volume] new_session_params[:volume] = options[:volume] end if options[:session_manager] then say Text::Process::USING_SESSION_MANAGER % options[:session_manager] new_session_params[:session_manager] = options[:session_manager] end new_session_params[:cli_current_commit] = @scm.current_commit # Create a session # or use an already-created session # session_id = options[:session_id] session_data = if session_id && session_id > 0 @solano_api.update_session(session_id, new_session_params) else sess, manager = @solano_api.create_session(suite_id, new_session_params) sess end session_data ||= {} session_id ||= session_data["id"] if manager == 'DestroFreeSessionManager' begin if !options[:force_snapshot] then #check if there is a snapshot res = @solano_api.get_snapshot_commit({:session_id => session_id}) if res['snap_commit'] then snapshot_commit = res['snap_commit'] #No snapshot else say Text::Process::NO_SNAPSHOT res = @scm.create_snapshot(session_id, {:api => @solano_api, :default_branch => options[:default_branch]}) snapshot_commit = @scm.get_snap_id end say Text::Process::SNAPSHOT_COMMIT % snapshot_commit #if we already had a snapshot or we created a master snapshot #create a patch @scm.create_patch(session_id, {:api => @solano_api, :commit => snapshot_commit}) #forced snapshot creation else say Text::Process::FORCED_SNAPSHOT res = @scm.create_snapshot(session_id, {:api => @solano_api, :force => true}) snapshot_commit = @scm.get_snap_id end #start tests start_test_executions = @solano_api.start_destrofree_session(session_id, {:test_pattern => test_pattern, :test_exclude_pattern=>test_exclude_pattern}) rescue Exception, RuntimeError => e @solano_api.stop_session(session_id) say "ERROR: #{e.message}" return end else push_options = {} if options[:machine] push_options[:use_private_uri] = true end if !@scm.push_latest(session_data, suite_details, push_options) then exit_failure Text::Error::SCM_PUSH_FAILED end machine_data[:session_id] = session_id # Register the tests @solano_api.register_session(session_id, suite_id, test_pattern, test_exclude_pattern) # Start the tests start_test_executions = @solano_api.start_session(session_id, test_execution_params) num_tests_started = start_test_executions["started"].to_i say Text::Process::STARTING_TEST % num_tests_started.to_s end tests_finished = false finished_tests = {} latest_message = -100000 test_statuses = Hash.new(0) session_status = nil messages = nil last_finish_timestamp = nil report = start_test_executions["report"] # In CI mode, just hang up here. The session will continue running. if options[:machine] then say Text::Process::BUILD_CONTINUES return end say "" say Text::Process::CHECK_TEST_REPORT % report say Text::Process::TERMINATE_INSTRUCTION say "" # Catch Ctrl-C to interrupt the test Signal.trap(:INT) do say Text::Process::INTERRUPT say Text::Process::CHECK_TEST_STATUS tests_finished = true session_status = "interrupted" end while !tests_finished do current_test_executions = @solano_api.poll_session(session_id) session_status = current_test_executions['session_status'] messages, latest_message = update_messages(latest_message, finished_tests, messages, current_test_executions["messages"]) # Print out the progress of running tests current_test_executions["tests"].each do |test_name, result_params| if finished_tests.size == 0 && result_params["finished"] then say "" say Text::Process::CHECK_TEST_REPORT % report say Text::Process::TERMINATE_INSTRUCTION say "" end if result_params["finished"] && !finished_tests[test_name] test_status = result_params["status"] message = case test_status when "passed" then [".", :green, false] when "failed" then ["F", :red, false] when "error" then ["E", nil, false] when "pending" then ["*", :yellow, false] when "skipped" then [".", :yellow, false] else [".", nil, false] end finished_tests[test_name] = test_status last_finish_timestamp = Time.now test_statuses[test_status] += 1 say *message end end # XXX time out if all tests are done and the session isn't done. if current_test_executions['session_done'] || current_test_executions['phase'] == 'done' && ((!num_tests_started.nil? && finished_tests.size >= num_tests_started) && (Time.now - last_finish_timestamp) > Default::TEST_FINISH_TIMEOUT) tests_finished = true end sleep(Default::SLEEP_TIME_BETWEEN_POLLS) if !tests_finished end display_alerts(messages, 'error', Text::Status::SPEC_ERRORS) # Print out the result say "" say Text::Process::RUN_SOLANO_WEB say "" say Text::Process::FINISHED_TEST % (Time.now - start_time) say "#{finished_tests.size} tests, #{test_statuses["failed"]} failures, #{test_statuses["error"]} errors, #{test_statuses["pending"]} pending, #{test_statuses["skipped"]} skipped" if test_statuses['failed'] > 0 say "" say Text::Process::FAILED_TESTS finished_tests.each do |name, status| next if status != 'failed' say " - #{name}" end say "" end say Text::Process::SUMMARY_STATUS % session_status say "" suite = suite_details.merge({"id" => suite_id}) @api_config.set_suite(suite) @api_config.write_config exit_failure if session_status != 'passed' rescue TddiumClient::Error::API => e exit_failure "Failed due to error: #{e.explanation}" rescue TddiumClient::Error::Base => e exit_failure "Failed due to error: #{e.message}" rescue RuntimeError => e exit_failure "Failed due to internal error: #{e.inspect} #{e.backtrace}" end
status()
click to toggle source
# File lib/solano/cli/commands/status.rb, line 13 def status solano_setup({:repo => true}) begin # solano_setup asserts that we're in a supported SCM repo origin_url = @scm.origin_url repo_params = { :active => true } if suite_for_current_branch? then status_branch = @solano_api.current_branch suite_params = { :active => false, :limit => 10 } elsif suite_for_default_branch? then status_branch = @solano_api.default_branch say Text::Error::TRY_DEFAULT_BRANCH % status_branch suite_params = { :active => false, :limit => 10 } end if options[:commit] then repo_params[:last_commit_id] = options[:commit] suite_params[:last_commit_id] = options[:commit] end if options[:pipeline] suites = @solano_api.get_suites(:branch => status_branch).select { |s| s['repo_name'] == options[:pipeline] } else suites = @solano_api.get_suites(:repo_url => origin_url, :branch => status_branch) end if suites.count == 0 exit_failure Text::Error::CANT_FIND_SUITE % [origin_url, status_branch] elsif suites.count > 1 if options[:account] then suites = suites.select { |s| s['account'] == options[:account] } else say Text::Status::SUITE_IN_MULTIPLE_ACCOUNTS % [origin_url, status_branch] suites.each { |s| say ' ' + s['account'] } account = ask Text::Status::SUITE_IN_MULTIPLE_ACCOUNTS_PROMPT suites = suites.select { |s| s['account'] == account } end end if suites.count == 0 exit_failure Text::Error::INVALID_ACCOUNT_NAME end suite_params[:suite_id] = suites.first['id'] repo_params[:suite_id] = suites.first['id'] if options[:json] res = {} res[:running] = { origin_url => @solano_api.get_sessions(repo_params) } res[:history] = { status_branch => @solano_api.get_sessions(suite_params) } if suite_params puts JSON.pretty_generate(res) else show_session_details( status_branch, repo_params, Text::Status::NO_ACTIVE_SESSION, Text::Status::ACTIVE_SESSIONS, true ) show_session_details( status_branch, suite_params, Text::Status::NO_INACTIVE_SESSION, Text::Status::INACTIVE_SESSIONS, false ) if suite_params say Text::Process::RERUN_SESSION end rescue TddiumClient::Error::Base => e exit_failure e.message end end
stop(ls_id=nil)
click to toggle source
# File lib/solano/cli/commands/stop.rb, line 6 def stop(ls_id=nil) solano_setup({:scm => false}) if ls_id then begin say "Stopping session #{ls_id} ..." say @solano_api.stop_session(ls_id)['notice'] rescue end else exit_failure 'Stop requires a session id -- e.g. `solano stop 7869764`' end end
suite(*argv)
click to toggle source
# File lib/solano/cli/commands/suite.rb, line 19 def suite(*argv) solano_setup({:repo => true}) params = {} # Load tool options into params tool_cli_populate(options, params) begin if options[:delete] # Deleting works differently (for now) because api_config can't handle # multiple suites with the same branch name in two different accounts. repo_url = @scm.origin_url if argv.is_a?(Array) && argv.size > 0 branch = argv[0] else branch = @solano_api.current_branch end suites = @solano_api.get_suites(:repo_url => repo_url, :branch => branch) if suites.count == 0 exit_failure Text::Error::CANT_FIND_SUITE % [repo_url, branch] elsif suites.count > 1 say Text::Process::SUITE_IN_MULTIPLE_ACCOUNTS % [repo_url, branch] suites.each { |s| say ' ' + s['account'] } account = ask Text::Process::SUITE_IN_MULTIPLE_ACCOUNTS_PROMPT suites = suites.select { |s| s['account'] == account } if suites.count == 0 exit_failure Text::Error::INVALID_ACCOUNT_NAME end end suite = suites.first unless options[:non_interactive] yn = ask Text::Process::CONFIRM_DELETE_SUITE % [suite['repo_url'], suite['branch'], suite['account']] unless yn.downcase == Text::Prompt::Response::YES exit_failure Text::Process::ABORTING end end @solano_api.permanent_destroy_suite(suite['id']) @api_config.delete_suite(suite['branch']) @api_config.write_config elsif @solano_api.current_suite_id then # Suite ID set in solano config file current_suite = @solano_api.get_suite_by_id(@solano_api.current_suite_id) if options[:edit] then update_suite(current_suite, options) else say Text::Process::EXISTING_SUITE, :bold say format_suite_details(current_suite) end @api_config.set_suite(current_suite) @api_config.write_config else # Need to find or construct the suite (and repo) params[:branch] = @scm.current_branch params[:repo_url] = @scm.origin_url params[:repo_name] = @scm.repo_name params[:scm] = @scm.scm_name say Text::Process::NO_CONFIGURED_SUITE % [params[:repo_name], params[:branch]] prompt_suite_params(options, params) params.each do |k,v| params.delete(k) if v == 'disable' end # Create new suite if it does not exist yet say Text::Process::CREATING_SUITE % [params[:repo_name], params[:branch]] new_suite = @solano_api.create_suite(params) # Save the created suite @api_config.set_suite(new_suite) @api_config.write_config say Text::Process::CREATED_SUITE, :bold say format_suite_details(new_suite) end rescue TddiumClient::Error::Base => e exit_failure(e.explanation) end end
version()
click to toggle source
# File lib/solano/cli/solano.rb, line 75 def version say VERSION end
web(*args)
click to toggle source
# File lib/solano/cli/commands/web.rb, line 6 def web(*args) session_id = args.first params = {:login => false} if session_id.nil? then params[:scm] = true params[:repo] = true end solano_setup(params) if session_id fragment = "1/reports/#{session_id}" if session_id =~ /^(\d+)(\.\w+)*$/ end fragment ||= 'latest' begin Launchy.open("#{options[:proto]}://#{options[:host]}/#{fragment}") rescue Launchy::Error => e exit_failure e.message end end
Protected Instance Methods
calc_current_suite_id()
click to toggle source
# File lib/solano/cli/suite.rb, line 65 def calc_current_suite_id if options[:pipeline] && options[:account] suites = @solano_api.get_suites.select { |s| s['repo_name'] == options[:pipeline] && s['account'] == options[:account] } suite = suites.select { |s| s['branch'] == @scm.current_branch }.first suite ? suite['id'] : nil elsif options[:pipeline] suites = @solano_api.get_suites.select { |s| s['repo_name'] == options[:pipeline] } suite = suites.select { |s| s['branch'] == @scm.current_branch }.first suite ? suite['id'] : nil else @solano_api.current_suite_id end end
caller_version()
click to toggle source
# File lib/solano/cli/solano.rb, line 106 def caller_version "solano-#{VERSION}" end
configured_test_exclude_pattern()
click to toggle source
# File lib/solano/cli/solano.rb, line 117 def configured_test_exclude_pattern pattern = @repo_config["test_exclude_pattern"] return nil if pattern.nil? || pattern.empty? return pattern end
configured_test_pattern()
click to toggle source
# File lib/solano/cli/solano.rb, line 110 def configured_test_pattern pattern = @repo_config["test_pattern"] return nil if pattern.nil? || pattern.empty? return pattern end
display_alerts(messages, level, heading)
click to toggle source
# File lib/solano/cli/util.rb, line 123 def display_alerts(messages, level, heading) return unless messages interest = messages.select{|m| [level].include?(m['level'])} if interest.size > 0 say heading interest.each do |m| display_message(m, '') end end end
display_message(message, prefix=' ---> ')
click to toggle source
# File lib/solano/cli/util.rb, line 113 def display_message(message, prefix=' ---> ') color = case message["level"] when "error" then :red when "warn" then :yellow else nil end print prefix say message["text"].rstrip, color end
exit_failure(msg='')
click to toggle source
# File lib/solano/cli/util.rb, line 109 def exit_failure(msg='') abort msg end
format_suite_details(suite)
click to toggle source
# File lib/solano/cli/suite.rb, line 57 def format_suite_details(suite) # Given an API response containing a "suite" key, compose a string with # important information about the suite solano_deploy_key_file_name = @api_config.solano_deploy_key_file_name details = ERB.new(Text::Status::SUITE_DETAILS).result(binding) details end
format_usage(usage)
click to toggle source
# File lib/solano/cli/show.rb, line 63 def format_usage(usage) "All tests: %.2f worker-hours ($%.2f)" % [ usage["hours"] || 0, usage["charge"] || 0] end
normalize_bundler_version(bundler_version)
click to toggle source
# File lib/solano/cli/util.rb, line 78 def normalize_bundler_version(bundler_version) if !bundler_version.nil? then bundler_version.chomp! bundler_version =~ /Bundler version (.*)\z/ bundler_version = $1 end return bundler_version end
prompt(text, current_value, default_value, dont_prompt=false)
click to toggle source
# File lib/solano/cli/prompt.rb, line 7 def prompt(text, current_value, default_value, dont_prompt=false) value = current_value || (dont_prompt ? nil : ask(text % default_value, :bold)) (value.nil? || value.empty?) ? default_value : value end
prompt_missing_ssh_key()
click to toggle source
# File lib/solano/cli/prompt.rb, line 12 def prompt_missing_ssh_key keys = @solano_api.get_keys if keys.empty? then say Text::Process::SSH_KEY_NEEDED keydata = prompt_ssh_key(nil) result = @solano_api.set_keys({:keys => [keydata]}) return true end false rescue SolanoError => e exit_failure e.message rescue TddiumClient::Error::API => e exit_failure e.explanation end
prompt_ssh_key(current, name='default')
click to toggle source
# File lib/solano/cli/prompt.rb, line 27 def prompt_ssh_key(current, name='default') # Prompt for ssh-key file ssh_file = prompt(Text::Prompt::SSH_KEY, current, Default::SSH_FILE) Solano::Ssh.load_ssh_key(ssh_file, name) end
prompt_suite_params(options, params, current={})
click to toggle source
# File lib/solano/cli/prompt.rb, line 33 def prompt_suite_params(options, params, current={}) say Text::Process::DETECTED_BRANCH % params[:branch] if params[:branch] params[:ruby_version] ||= tool_version(:ruby) params[:bundler_version] ||= normalize_bundler_version(tool_version(:bundle)) params[:rubygems_version] ||= tool_version(:gem) ask_or_update = lambda do |key, text, default| params[key] = prompt(text, options[key], current.fetch(key.to_s, default), options[:non_interactive]) end account_announced = false # If we already have a suite, it already has an account, so no need to # figure it out here. unless current['account_id'] # Find an account id. Strategy: # 1. Use a command line option, if specified. # 2. If the user has only one account, use that. # 3. If the user has existing suites with the same repo, and they are # all in the same account, prompt with that as a default. # 4. Prompt. # IF we're not allowed to prompt and have no default, fail. accounts = user_details["participating_accounts"] account_name = if options[:account] account_announced = true say Text::Process::USING_ACCOUNT_FROM_FLAG % options[:account] options[:account] elsif accounts.length == 1 account_announced = true say Text::Process::USING_ACCOUNT % accounts.first["account"] accounts.first["account"] else # Get all of this user's suites with this repo. repo_suites = @solano_api.get_suites(:repo_url => params[:repo_url]) acct_ids = repo_suites.map{|s| s['account']}.uniq default = acct_ids.length == 1 ? acct_ids.first : nil if not options[:non_interactive] or default.nil? say "You are a member of these organizations:" accounts.each do |account| say " " + account['account'] end end msg = default.nil? ? Text::Prompt::ACCOUNT : Text::Prompt::ACCOUNT_DEFAULT prompt(msg, nil, default, options[:non_interactive]) end if account_name.nil? exit_failure (options[:non_interactive] ? Text::Error::MISSING_ACCOUNT_OPTION : Text::Error::MISSING_ACCOUNT) end account = accounts.select{|a| a['account'] == account_name}.first if account.nil? exit_failure Text::Error::NOT_IN_ACCOUNT % account_name end if !account_announced then say Text::Process::USING_ACCOUNT % account_name end params[:account_id] = account["account_id"].to_s end pattern = configured_test_pattern cfn = @repo_config.config_filename if pattern.is_a?(Array) say Text::Process::CONFIGURED_PATTERN % [cfn, pattern.map{|p| " - #{p}"}.join("\n"), cfn] params[:test_pattern] = pattern.join(",") elsif pattern exit_failure Text::Error::INVALID_CONFIGURED_PATTERN % [cfn, cfn, pattern.inspect, cfn] else say Text::Process::TEST_PATTERN_INSTRUCTIONS unless options[:non_interactive] ask_or_update.call(:test_pattern, Text::Prompt::TEST_PATTERN, Default::SUITE_TEST_PATTERN) end exclude_pattern = configured_test_exclude_pattern cfn = @repo_config.config_filename if exclude_pattern.is_a?(Array) say Text::Process::CONFIGURED_EXCLUDE_PATTERN % [cfn, exclude_pattern.map{|p| " - #{p}"}.join("\n"), cfn] params[:test_exclude_pattern] = exclude_pattern.join(",") elsif exclude_pattern exit_failure Text::Error::INVALID_CONFIGURED_PATTERN % [cfn, cfn, exclude_pattern.inspect, cfn] end unless options[:non_interactive] say(Text::Process::SETUP_CI) end ask_or_update.call(:ci_pull_url, Text::Prompt::CI_PULL_URL, @scm.origin_url) ask_or_update.call(:ci_push_url, Text::Prompt::CI_PUSH_URL, nil) end
set_shell()
click to toggle source
# File lib/solano/cli/util.rb, line 9 def set_shell if !$stdout.tty? || !$stderr.tty? then @shell = Thor::Shell::Basic.new end end
show_attributes(names_to_display, attributes)
click to toggle source
# File lib/solano/cli/show.rb, line 19 def show_attributes(names_to_display, attributes) names_to_display.each do |attr| say Text::Status::ATTRIBUTE_DETAIL % [attr.gsub("_", " ").capitalize, attributes[attr]] if attributes[attr] end end
show_config_details(scope, config)
click to toggle source
# File lib/solano/cli/show.rb, line 7 def show_config_details(scope, config) if !config || config.length == 0 say Text::Process::NO_CONFIG else say Text::Status::CONFIG_DETAILS % scope config.each do |k,v| say "#{k}=#{v}" end end say Text::Process::CONFIG_EDIT_COMMANDS end
show_keys_details(keys)
click to toggle source
# File lib/solano/cli/show.rb, line 25 def show_keys_details(keys) say Text::Status::KEYS_DETAILS if keys.length == 0 say Text::Process::NO_KEYS else keys.each do |k| if k["fingerprint"] say((" %-18.18s %s" % [k["name"], k["fingerprint"]]).rstrip) elsif k["pub"] fingerprint = ssh_key_fingerprint(k["pub"]) if fingerprint then say((" %-18.18s %s" % [k["name"], fingerprint]).rstrip) else say((" %-18.18s" % k["name"]).rstrip) end else say((" %-18.18s" % k["name"]).rstrip) end end end say Text::Process::KEYS_EDIT_COMMANDS end
show_ssh_config(dir=nil)
click to toggle source
# File lib/solano/cli/show.rb, line 52 def show_ssh_config(dir=nil) dir ||= ENV['SOLANO_GEM_KEY_DIR'] dir ||= Default::SSH_OUTPUT_DIR path = File.expand_path(File.join(dir, "identity.solano.*")) Dir[path].reject{|fn| fn =~ /.pub$/}.each do |fn| say Text::Process::SSH_CONFIG % {:scm_host=>"git.solanolabs.com", :file=>fn} end end
show_third_party_keys_details(user)
click to toggle source
# File lib/solano/cli/show.rb, line 48 def show_third_party_keys_details(user) say ERB.new(Text::Status::USER_THIRD_PARTY_KEY_DETAILS).result(binding) end
show_user_details(user)
click to toggle source
# File lib/solano/cli/show.rb, line 68 def show_user_details(user) current_suites = @solano_api.get_suites memberships = @solano_api.get_memberships account_usage = @solano_api.get_usage # Given the user is logged in, he should be able to # use "solano account" to display information about his account: # Email address # Account creation date say ERB.new(Text::Status::USER_DETAILS).result(binding) # Use "all_accounts" here instead of "participating_accounts" -- these # are the accounts the user can administer. # Select those accounts that match org param or all if no org param org = options['org'] regexp = org.nil? ? nil : Regexp.new(org.to_s) account_list = user['all_accounts'] account_list.select! do |acct| if regexp.nil? || regexp =~ acct[:account] then acct else nil end end account_list.compact! account_list.each do |acct| id = acct['account_id'].to_i say ERB.new(Text::Status::ACCOUNT_DETAILS).result(binding) acct_suites = current_suites.select{|s| s['account_id'].to_i == id} if acct_suites.empty? then say ' ' + Text::Status::NO_SUITE else say ' ' + Text::Status::ALL_SUITES suites = acct_suites.sort_by{|s| "#{s['org_name']}/#{s['repo_name']}"} print_table suites.map {|suite| repo_name = suite['repo_name'] if suite['org_name'] && suite['org_name'] != 'unknown' repo_name = suite['org_name'] + '/' + repo_name end [repo_name, suite['branch'], suite['repo_url'] || ''] }, :indent => 4 end # Uugh, json converts the keys to strings. usage = account_usage[id.to_s] if usage say "\n Usage:" say " Current month: " + format_usage(usage["current_month"]) say " Last month: " + format_usage(usage["last_month"]) end acct_members = memberships.select{|m| m['account_id'].to_i == id} if acct_members.length > 1 say "\n " + Text::Status::ACCOUNT_MEMBERS print_table acct_members.map {|ar| [ar['user_handle'], ar['user_email'], ar['role']] }, :indent => 4 end end rescue TddiumClient::Error::Base => e exit_failure e.message end
sniff_ruby_version()
click to toggle source
# File lib/solano/cli/util.rb, line 58 def sniff_ruby_version ruby_version = @repo_config["ruby_version"] return ruby_version unless ruby_version.nil? || ruby_version.to_s.empty? if !options[:machine] then scm_root = @scm.root if scm_root then rvmrc = File.join(scm_root, '.rvmrc') ruby_version_path = File.join(scm_root, '.ruby_version') if File.exists?(ruby_version_path) then ruby_version = sniff_ruby_version_rvmrc(ruby_version) elsif File.exists?(rvmrc) then ruby_version = sniff_ruby_version_rvmrc(rvmrc) warn("Detected ruby #{ruby_version} in .rvmrc; make sure patch level is correct") end end end return ruby_version end
sniff_ruby_version_rvmrc(rvmrc)
click to toggle source
# File lib/solano/cli/util.rb, line 41 def sniff_ruby_version_rvmrc(rvmrc) ruby_version = nil File.open(rvmrc, 'r') do |file| file.each_line do |line| line.sub!(/^\s+/, '') next unless line =~ /^rvm/ fields = Shellwords.shellsplit(line) fields.each do |field| if field =~ /^(ree|1[.][89])/ then ruby_version = field.sub(/@.*/, '') end end end end return ruby_version end
solano_setup(params={})
click to toggle source
# File lib/solano/cli/solano.rb, line 124 def solano_setup(params={}) if params[:deprecated] then say Text::Error::COMMAND_DEPRECATED end # suite => repo => scm params[:suite] = params[:suite] == true params[:repo] = params[:repo] == true params[:repo] ||= params[:suite] params[:scm] = !params.member?(:scm) || params[:scm] == true params[:scm] ||= params[:repo] params[:login] = true unless params[:login] == false $stdout.sync = true $stderr.sync = true set_shell @scm, ok = Solano::SCM.configure if params[:scm] && !ok then say Text::Error::SCM_NOT_FOUND exit_failure end @repo_config = RepoConfig.new(@scm) if origin_url = @repo_config[:origin_url] then @scm.default_origin_url = origin_url end host = @cli_options[:host] @api_config = ApiConfig.new(@scm, @tddium_client, host, @cli_options) @solano_api = SolanoAPI.new(@scm, @tddium_client, @api_config, {v3: @tddium_clientv3}) @api_config.set_api(@solano_api) begin @api_config.load_config rescue ::Solano::SolanoError => e say e.message exit_failure end user_details = @solano_api.user_logged_in?(true, params[:login]) if params[:login] && user_details.nil? then exit_failure end if params[:repo] then if !@scm.repo? then say Text::Error::SCM_NOT_A_REPOSITORY exit_failure end if @scm.origin_url.nil? then say Text::Error::SCM_NO_ORIGIN exit_failure end begin Solano::SCM.valid_repo_url?(@scm.origin_url) rescue SolanoError => e say e.message exit_failure end end if params[:suite] then if @scm.current_branch.nil? then say Text::Error::SCM_NO_BRANCH exit_failure end if !suite_for_current_branch? then exit_failure end end @user_details = user_details end
ssh_key_fingerprint(pubkey)
click to toggle source
# File lib/solano/cli/util.rb, line 87 def ssh_key_fingerprint(pubkey) tmp = Tempfile.new('ssh-pub') tmp.write(pubkey) tmp.close fingerprint = nil IO.popen("ssh-keygen -lf #{tmp.path}") do |io| fingerprint = io.read # Keep just bits and fingerprint if fingerprint then fingerprint.chomp! fingerprint = fingerprint.split(/\s+/)[0..1].join(' ') end end tmp.unlink return fingerprint end
suite_auto_configure()
click to toggle source
# File lib/solano/cli/suite.rb, line 22 def suite_auto_configure # Did the user set a configuration option on the command line? # If so, auto-configure a new suite and re-configure an existing one user_config = options.member?(:tool) current_suite_id = calc_current_suite_id if current_suite_id && !user_config then current_suite = @solano_api.get_suite_by_id(current_suite_id) else params = Hash.new params[:branch] = @scm.current_branch params[:repo_url] = @scm.origin_url params[:repo_name] = options[:pipeline] || @scm.repo_name params[:scm] = @scm.scm_name if options[:account] && !params.member?(:account_id) then account_id = @solano_api.get_account_id(options[:account]) params[:account_id] = account_id if account_id end tool_cli_populate(options, params) defaults = {} prompt_suite_params(options.merge({:non_interactive => true}), params, defaults) # Create new suite if it does not exist yet say Text::Process::CREATING_SUITE % [params[:repo_name], params[:branch]] current_suite = @solano_api.create_suite(params) # Save the created suite @api_config.set_suite(current_suite) @api_config.write_config end return current_suite end
suite_for_current_branch?()
click to toggle source
# File lib/solano/cli/suite.rb, line 79 def suite_for_current_branch? return true if calc_current_suite_id say Text::Error::NO_SUITE_EXISTS % @scm.current_branch false end
suite_for_default_branch?()
click to toggle source
# File lib/solano/cli/suite.rb, line 85 def suite_for_default_branch? return true if @solano_api.default_suite_id say Text::Error::NO_SUITE_EXISTS % @scm.default_branch false end
suite_remembered_option(options, key, default) { |result| ... }
click to toggle source
repo_config_file has authority over solano.yml now Update the suite parameters from solano.yml
def update_suite_parameters!(current_suite, session_id=nil) end
# File lib/solano/cli/suite.rb, line 96 def suite_remembered_option(options, key, default, &block) remembered = false if options[key] != default result = options[key] elsif remembered = current_suite_options[key.to_s] result = remembered remembered = true else result = default end if result then msg = Text::Process::USING_SPEC_OPTION[key] % result msg += Text::Process::REMEMBERED if remembered msg += "\n" say msg yield result if block_given? end result end
tool_cli_populate(options, params)
click to toggle source
# File lib/solano/cli/util.rb, line 15 def tool_cli_populate(options, params) if options[:tool].is_a?(Hash) then options[:tool].each_pair do |key, value| params[key.to_sym] = value end end end
tool_version(tool)
click to toggle source
# File lib/solano/cli/util.rb, line 23 def tool_version(tool) key = "#{tool}_version".to_sym result = @repo_config[key] if result say Text::Process::CONFIGURED_VERSION % [tool, result, @repo_config.config_filename] return result end begin result = `#{tool} -v`.strip rescue Errno::ENOENT exit_failure("#{tool} is not on PATH; please install and try again") end say Text::Process::DEPENDENCY_VERSION % [tool, result] result end
update_suite(suite, options)
click to toggle source
# File lib/solano/cli/suite.rb, line 7 def update_suite(suite, options) params = {} prompt_suite_params(options, params, suite) ask_or_update = lambda do |key, text, default| params[key] = prompt(text, options[key], suite.fetch(key.to_s, default), options[:non_interactive]) end ask_or_update.call(:campfire_room, Text::Prompt::CAMPFIRE_ROOM, '') ask_or_update.call(:hipchat_room, Text::Prompt::HIPCHAT_ROOM, '') @solano_api.update_suite(suite['id'], params) say Text::Process::UPDATED_SUITE end
warn(msg='')
click to toggle source
# File lib/solano/cli/util.rb, line 105 def warn(msg='') STDERR.puts("WARNING: #{msg}") end
Private Instance Methods
cache_control_config()
click to toggle source
# File lib/solano/cli/commands/spec.rb, line 354 def cache_control_config @repo_config['cache'] || {} end
capture_stdout() { || ... }
click to toggle source
# File lib/solano/cli/commands/status.rb, line 135 def capture_stdout old, $stdout = $stdout, StringIO.new yield $stdout.string ensure $stdout = old end
docker_enabled()
click to toggle source
# File lib/solano/cli/commands/spec.rb, line 342 def docker_enabled if @repo_config['system'] then if @repo_config['system']['docker'] then @repo_config['system']['docker'] else false end else false end end
duration(d)
click to toggle source
return nice string version of hrs mins secs from time delta
# File lib/solano/cli/commands/console.rb, line 82 def duration(d) secs = d.to_int mins = secs / 60 hours = mins / 60 if hours > 0 then "#{hours} hours #{mins % 60} minutes" elsif mins > 0 then "#{mins} minutes #{secs % 60} seconds" else "#{secs} seconds" end end
find_failing_set(sets, failing)
click to toggle source
# File lib/solano/cli/commands/find_failing.rb, line 38 def find_failing_set(sets, failing) sets.each do |set| next if set == [failing] if !success?(set) if set.size == 2 return [:finished, set] else return [:continue, set] end end end return [:failure, []] end
read_and_encode_cache_control()
click to toggle source
# File lib/solano/cli/commands/spec.rb, line 358 def read_and_encode_cache_control cache_key_paths = cache_control_config['key_paths'] || cache_control_config[:key_paths] cache_key_paths ||= ["Gemfile", "Gemfile.lock", "requirements.txt", "packages.json", "package.json"] cache_key_paths.reject!{|x| x =~ /(solano|tddium).yml$/} if docker_enabled then cache_key_paths << "Dockerfile" end cache_control_data = {} cache_key_paths.each do |p| if File.exists?(p) then cache_control_data[p] = Digest::SHA1.file(p).to_s end end msgpack = Solano.message_pack(cache_control_data) cache_control_encoded = Base64.encode64(msgpack) end
read_and_encode_cache_save_paths()
click to toggle source
# File lib/solano/cli/commands/spec.rb, line 378 def read_and_encode_cache_save_paths cache_save_paths = cache_control_config['save_paths'] || cache_control_config[:save_paths] if docker_enabled then (cache_save_paths ||= []) << "HOME/docker-graph" end msgpack = Solano.message_pack(cache_save_paths) cache_save_paths_encoded = Base64.encode64(msgpack) end
read_and_encode_config_file()
click to toggle source
# File lib/solano/cli/commands/spec.rb, line 389 def read_and_encode_config_file config, raw_config = @repo_config.read_config(false) encoded = Base64.encode64(raw_config) return encoded end
read_and_encode_latest_commits()
click to toggle source
# File lib/solano/cli/commands/spec.rb, line 335 def read_and_encode_latest_commits commits = @scm.commits commits_packed = Solano.message_pack(commits) commits_encoded = Base64.encode64(commits_packed) commits_encoded end
remove_from(set, x, options)
click to toggle source
# File lib/solano/cli/commands/find_failing.rb, line 52 def remove_from(set, x, options) set.dup.delete_if { |f| f != options[:not] && (x -= 1) >= 0 } end
session_id_for_current_suite()
click to toggle source
# File lib/solano/cli/commands/rerun.rb, line 51 def session_id_for_current_suite return unless suite_for_current_branch? suite_params = { :suite_id => calc_current_suite_id, :active => false, :limit => 1, :origin => %w(ci cli) } session = @solano_api.get_sessions(suite_params) session[0]["id"] end
show_session_details(status_branch, params, no_session_prompt, all_session_prompt, include_branch)
click to toggle source
# File lib/solano/cli/commands/status.rb, line 101 def show_session_details(status_branch, params, no_session_prompt, all_session_prompt, include_branch) current_sessions = @solano_api.get_sessions(params) say "" if current_sessions.empty? then say no_session_prompt else commit_size = 0...7 head = @scm.current_commit[commit_size] say all_session_prompt % (params[:suite_id] ? status_branch : "") say "" header = ["Session #", "Commit", ("Branch" if include_branch), "Status", "Duration", "Started"].compact table = [header, header.map { |t| "-" * t.size }] + current_sessions.map do |session| duration = "%ds" % session['duration'] start_timeago = "%s ago" % Solano::TimeFormat.seconds_to_human_time(Time.now - Time.parse(session["start_time"])) status = session["status"] if status.nil? || status.strip == "" then status = 'unknown' end [ session["id"].to_s, session["commit"] ? session['commit'][commit_size] : '- ', (session["branch"] if include_branch), status, duration, start_timeago ].compact end say(capture_stdout { print_table table }.gsub(head, "\e[7m#{head}\e[0m")) end end
success?(files)
click to toggle source
# File lib/solano/cli/commands/find_failing.rb, line 56 def success?(files) command = "bundle exec ruby #{files.map { |f| "-r./#{f.sub(/\.rb$/, "")}" }.join(" ")} -e ''" say "Running: #{command}" status = system(command) say "Status: #{status ? "Success" : "Failure"}" status end
update_messages(latest_message, finished_tests, messages, current, display=true)
click to toggle source
# File lib/solano/cli/commands/spec.rb, line 319 def update_messages(latest_message, finished_tests, messages, current, display=true) messages = current if !options[:machine] && finished_tests.size == 0 && messages messages.each do |m| seqno = m["seqno"].to_i if seqno > latest_message if !options[:quiet] || m["level"] == 'error' then display_message(m) end latest_message = seqno end end end [messages, latest_message] end