diff options
| author | Mike McQuaid | 2014-09-20 14:24:52 +0100 |
|---|---|---|
| committer | Mike McQuaid | 2014-09-24 15:22:37 -0700 |
| commit | b7c9025d931a4e7894f8c3febf109031e775d528 (patch) | |
| tree | 4ef54f19f8b1e8d13b08df2e450d0e67d46dea2b /Library/Contributions | |
| parent | bd8559c791e7bc37c17a0f270e9b0f98e89d7a8a (diff) | |
| download | brew-b7c9025d931a4e7894f8c3febf109031e775d528.tar.bz2 | |
brew-test-bot: make an internal command.
Diffstat (limited to 'Library/Contributions')
| -rwxr-xr-x | Library/Contributions/cmd/brew-test-bot.rb | 663 |
1 files changed, 0 insertions, 663 deletions
diff --git a/Library/Contributions/cmd/brew-test-bot.rb b/Library/Contributions/cmd/brew-test-bot.rb deleted file mode 100755 index 771253173..000000000 --- a/Library/Contributions/cmd/brew-test-bot.rb +++ /dev/null @@ -1,663 +0,0 @@ -# Comprehensively test a formula or pull request. -# -# Usage: brew test-bot [options...] <pull-request|formula> -# -# Options: -# --keep-logs: Write and keep log files under ./brewbot/ -# --cleanup: Clean the Homebrew directory. Very dangerous. Use with care. -# --clean-cache: Remove all cached downloads. Use with care. -# --skip-setup: Don't check the local system is setup correctly. -# --junit: Generate a JUnit XML test results file. -# --email: Generate an email subject file. -# --no-bottle: Run brew install without --build-bottle -# --HEAD: Run brew install with --HEAD -# --local: Ask Homebrew to write verbose logs under ./logs/ -# --tap=<tap>: Use the git repository of the given tap -# --dry-run: Just print commands, don't run them. -# -# --ci-master: Shortcut for Homebrew master branch CI options. -# --ci-pr: Shortcut for Homebrew pull request CI options. -# --ci-testing: Shortcut for Homebrew testing CI options. -# --ci-pr-upload: Homebrew CI pull request bottle upload. -# --ci-testing-upload: Homebrew CI testing bottle upload. - -require 'formula' -require 'utils' -require 'date' -require 'rexml/document' -require 'rexml/xmldecl' -require 'rexml/cdata' - -EMAIL_SUBJECT_FILE = "brew-test-bot.#{MacOS.cat}.email.txt" - -def homebrew_git_repo tap=nil - if tap - HOMEBREW_LIBRARY/"Taps/#{tap}" - else - HOMEBREW_REPOSITORY - end -end - -class Step - attr_reader :command, :name, :status, :output, :time - - def initialize test, command, options={} - @test = test - @category = test.category - @command = command - @puts_output_on_success = options[:puts_output_on_success] - @name = command[1].delete("-") - @status = :running - @repository = options[:repository] || HOMEBREW_REPOSITORY - @time = 0 - end - - def log_file_path - file = "#{@category}.#{@name}.txt" - root = @test.log_root - root ? root + file : file - end - - def status_colour - case @status - when :passed then "green" - when :running then "orange" - when :failed then "red" - end - end - - def status_upcase - @status.to_s.upcase - end - - def command_short - (@command - %w[brew --force --retry --verbose --build-bottle --rb]).join(" ") - end - - def passed? - @status == :passed - end - - def failed? - @status == :failed - end - - def puts_command - cmd = @command.join(" ") - print "#{Tty.blue}==>#{Tty.white} #{cmd}#{Tty.reset}" - tabs = (80 - "PASSED".length + 1 - cmd.length) / 8 - tabs.times{ print "\t" } - $stdout.flush - end - - def puts_result - puts " #{Tty.send status_colour}#{status_upcase}#{Tty.reset}" - end - - def has_output? - @output && !@output.empty? - end - - def run - puts_command - if ARGV.include? "--dry-run" - puts - @status = :passed - return - end - - start_time = Time.now - - log = log_file_path - - pid = fork do - File.open(log, "wb") do |f| - STDOUT.reopen(f) - STDERR.reopen(f) - end - Dir.chdir(@repository) if @command.first == "git" - exec(*@command) - end - Process.wait(pid) - - @time = Time.now - start_time - - @status = $?.success? ? :passed : :failed - puts_result - - if File.exist?(log) - @output = File.read(log) - if has_output? and (failed? or @puts_output_on_success) - puts @output - end - FileUtils.rm(log) unless ARGV.include? "--keep-logs" - end - end -end - -class Test - attr_reader :log_root, :category, :name, :steps - - def initialize argument, tap=nil - @hash = nil - @url = nil - @formulae = [] - @steps = [] - @tap = tap - @repository = homebrew_git_repo @tap - @repository_requires_tapping = !@repository.directory? - - url_match = argument.match HOMEBREW_PULL_OR_COMMIT_URL_REGEX - - # Tap repository if required, this is done before everything else - # because Formula parsing and/or git commit hash lookup depends on it. - test "brew", "tap", @tap if @tap && @repository_requires_tapping - - begin - formula = Formulary.factory(argument) - rescue FormulaUnavailableError - end - - git "rev-parse", "--verify", "-q", argument - if $?.success? - @hash = argument - elsif url_match - @url = url_match[0] - elsif formula - @formulae = [argument] - else - odie "#{argument} is not a pull request URL, commit URL or formula name." - end - - @category = __method__ - @brewbot_root = Pathname.pwd + "brewbot" - FileUtils.mkdir_p @brewbot_root - end - - def no_args? - @hash == 'HEAD' - end - - def git(*args) - rd, wr = IO.pipe - - pid = fork do - rd.close - STDERR.reopen("/dev/null") - STDOUT.reopen(wr) - wr.close - Dir.chdir @repository - exec("git", *args) - end - wr.close - Process.wait(pid) - - rd.read - ensure - rd.close - end - - def download - def shorten_revision revision - git("rev-parse", "--short", revision).strip - end - - def current_sha1 - shorten_revision 'HEAD' - end - - def current_branch - git("symbolic-ref", "HEAD").gsub("refs/heads/", "").strip - end - - def single_commit? start_revision, end_revision - git("rev-list", "--count", "#{start_revision}..#{end_revision}").to_i == 1 - end - - @category = __method__ - @start_branch = current_branch - - # Use Jenkins environment variables if present. - if no_args? and ENV['GIT_PREVIOUS_COMMIT'] and ENV['GIT_COMMIT'] \ - and not ENV['ghprbPullId'] - diff_start_sha1 = shorten_revision ENV['GIT_PREVIOUS_COMMIT'] - diff_end_sha1 = shorten_revision ENV['GIT_COMMIT'] - test "brew", "update" if current_branch == "master" - elsif @hash or @url - diff_start_sha1 = current_sha1 - test "brew", "update" if current_branch == "master" - diff_end_sha1 = current_sha1 - end - - # Handle Jenkins pull request builder plugin. - if ENV['ghprbPullId'] and ENV['GIT_URL'] - git_url = ENV['GIT_URL'] - git_match = git_url.match %r{.*github.com[:/](\w+/\w+).*} - if git_match - github_repo = git_match[1] - pull_id = ENV['ghprbPullId'] - @url = "https://github.com/#{github_repo}/pull/#{pull_id}" - @hash = nil - else - puts "Invalid 'ghprbPullId' environment variable value!" - end - end - - if no_args? - if diff_start_sha1 == diff_end_sha1 or \ - single_commit?(diff_start_sha1, diff_end_sha1) - @name = diff_end_sha1 - else - @name = "#{diff_start_sha1}-#{diff_end_sha1}" - end - elsif @hash - test "git", "checkout", @hash - diff_start_sha1 = "#{@hash}^" - diff_end_sha1 = @hash - @name = @hash - elsif @url - test "git", "checkout", current_sha1 - test "brew", "pull", "--clean", @url - diff_end_sha1 = current_sha1 - @short_url = @url.gsub('https://github.com/', '') - if @short_url.include? '/commit/' - # 7 characters should be enough for a commit (not 40). - @short_url.gsub!(/(commit\/\w{7}).*/, '\1') - @name = @short_url - else - @name = "#{@short_url}-#{diff_end_sha1}" - end - else - diff_start_sha1 = diff_end_sha1 = current_sha1 - @name = "#{@formulae.first}-#{diff_end_sha1}" - end - - @log_root = @brewbot_root + @name - FileUtils.mkdir_p @log_root - - return unless diff_start_sha1 != diff_end_sha1 - return if @url and not steps.last.passed? - - if @tap - formula_path = %w[Formula HomebrewFormula].find { |dir| (@repository/dir).directory? } || "" - else - formula_path = "Library/Formula" - end - - git( - "diff-tree", "-r", "--name-only", "--diff-filter=AM", - diff_start_sha1, diff_end_sha1, "--", formula_path - ).each_line do |line| - @formulae << File.basename(line.chomp, ".rb") - end - end - - def skip formula - puts "#{Tty.blue}==>#{Tty.white} SKIPPING: #{formula}#{Tty.reset}" - end - - def satisfied_requirements? formula_object, spec - requirements = formula_object.send(spec).requirements - - unsatisfied_requirements = requirements.reject do |requirement| - requirement.satisfied? || requirement.default_formula? - end - - if unsatisfied_requirements.empty? - true - else - formula = formula_object.name - formula += " (#{spec})" unless spec == :stable - skip formula - unsatisfied_requirements.each {|r| puts r.message} - false - end - end - - def setup - @category = __method__ - return if ARGV.include? "--skip-setup" - test "brew", "doctor" - test "brew", "--env" - test "brew", "config" - end - - def formula formula - @category = __method__.to_s + ".#{formula}" - - test "brew", "uses", formula - dependencies = `brew deps #{formula}`.split("\n") - dependencies -= `brew list`.split("\n") - unchanged_dependencies = dependencies - @formulae - changed_dependences = dependencies - unchanged_dependencies - formula_object = Formulary.factory(formula) - return unless satisfied_requirements?(formula_object, :stable) - - installed_gcc = false - deps = formula_object.stable.deps.to_a - reqs = formula_object.stable.requirements.to_a - if formula_object.devel && !ARGV.include?('--HEAD') - deps |= formula_object.devel.deps.to_a - reqs |= formula_object.devel.requirements.to_a - end - - begin - deps.each { |d| CompilerSelector.select_for(d.to_formula) } - CompilerSelector.select_for(formula_object) - rescue CompilerSelectionError => e - unless installed_gcc - test "brew", "install", "gcc" - installed_gcc = true - OS::Mac.clear_version_cache - retry - end - skip formula - puts e.message - return - end - - if (deps | reqs).any? { |d| d.name == "mercurial" && d.build? } - test "brew", "install", "mercurial" - end - - test "brew", "fetch", "--retry", *unchanged_dependencies unless unchanged_dependencies.empty? - test "brew", "fetch", "--retry", "--build-bottle", *changed_dependences unless changed_dependences.empty? - formula_fetch_options = [] - formula_fetch_options << "--build-bottle" unless ARGV.include? "--no-bottle" - formula_fetch_options << "--force" if ARGV.include? "--cleanup" - formula_fetch_options << formula - test "brew", "fetch", "--retry", *formula_fetch_options - test "brew", "uninstall", "--force", formula if formula_object.installed? - install_args = %w[--verbose] - install_args << "--build-bottle" unless ARGV.include? "--no-bottle" - install_args << "--HEAD" if ARGV.include? "--HEAD" - install_args << formula - # Don't care about e.g. bottle failures for dependencies. - ENV["HOMEBREW_DEVELOPER"] = nil - test "brew", "install", "--only-dependencies", *install_args unless dependencies.empty? - ENV["HOMEBREW_DEVELOPER"] = "1" - test "brew", "install", *install_args - install_passed = steps.last.passed? - test "brew", "audit", formula - if install_passed - unless ARGV.include? '--no-bottle' - test "brew", "bottle", "--rb", formula, :puts_output_on_success => true - bottle_step = steps.last - if bottle_step.passed? and bottle_step.has_output? - bottle_filename = - bottle_step.output.gsub(/.*(\.\/\S+#{bottle_native_regex}).*/m, '\1') - test "brew", "uninstall", "--force", formula - test "brew", "install", bottle_filename - end - end - test "brew", "test", "--verbose", formula if formula_object.test_defined? - test "brew", "uninstall", "--force", formula - end - - if formula_object.devel && !ARGV.include?('--HEAD') \ - && satisfied_requirements?(formula_object, :devel) - test "brew", "fetch", "--retry", "--devel", *formula_fetch_options - test "brew", "install", "--devel", "--verbose", formula - devel_install_passed = steps.last.passed? - test "brew", "audit", "--devel", formula - if devel_install_passed - test "brew", "test", "--devel", "--verbose", formula if formula_object.test_defined? - test "brew", "uninstall", "--devel", "--force", formula - end - end - test "brew", "uninstall", "--force", *unchanged_dependencies unless unchanged_dependencies.empty? - end - - def homebrew - @category = __method__ - test "brew", "tests" - test "brew", "readall" - end - - def cleanup_before - @category = __method__ - return unless ARGV.include? '--cleanup' - git "stash" - git "am", "--abort" - git "rebase", "--abort" - git "reset", "--hard" - git "checkout", "-f", "master" - git "clean", "--force", "-dx" - end - - def cleanup_after - @category = __method__ - - checkout_args = [] - if ARGV.include? '--cleanup' - test "git", "clean", "--force", "-dx" - checkout_args << "-f" - end - - checkout_args << @start_branch - - if ARGV.include? '--cleanup' or @url or @hash - test "git", "checkout", *checkout_args - end - - if ARGV.include? '--cleanup' - test "git", "reset", "--hard" - git "stash", "pop" - test "brew", "cleanup" - end - - test "brew", "untap", @tap if @tap && @repository_requires_tapping - - FileUtils.rm_rf @brewbot_root unless ARGV.include? "--keep-logs" - end - - def test(*args) - options = Hash === args.last ? args.pop : {} - options[:repository] = @repository - step = Step.new self, args, options - step.run - steps << step - step - end - - def check_results - status = :passed - steps.each do |step| - case step.status - when :passed then next - when :running then raise - when :failed then status = :failed - end - end - status == :passed - end - - def formulae - changed_formulae_dependents = {} - dependencies = [] - non_dependencies = [] - - @formulae.each do |formula| - formula_dependencies = `brew deps #{formula}`.split("\n") - unchanged_dependencies = formula_dependencies - @formulae - changed_dependences = formula_dependencies - unchanged_dependencies - changed_dependences.each do |changed_formula| - changed_formulae_dependents[changed_formula] ||= 0 - changed_formulae_dependents[changed_formula] += 1 - end - end - - changed_formulae = changed_formulae_dependents.sort do |a1,a2| - a2[1].to_i <=> a1[1].to_i - end - changed_formulae.map!(&:first) - unchanged_formulae = @formulae - changed_formulae - changed_formulae + unchanged_formulae - end - - def run - cleanup_before - download - setup - homebrew - formulae.each do |f| - formula(f) - end - cleanup_after - check_results - end -end - -tap = ARGV.value('tap') - -if Pathname.pwd == HOMEBREW_PREFIX and ARGV.include? "--cleanup" - odie 'cannot use --cleanup from HOMEBREW_PREFIX as it will delete all output.' -end - -if ARGV.include? "--email" - File.open EMAIL_SUBJECT_FILE, 'w' do |file| - # The file should be written at the end but in case we don't get to that - # point ensure that we have something valid. - file.write "#{MacOS.version}: internal error." - end -end - -ENV['HOMEBREW_DEVELOPER'] = '1' -ENV['HOMEBREW_NO_EMOJI'] = '1' -if ARGV.include? '--ci-master' or ARGV.include? '--ci-pr' \ - or ARGV.include? '--ci-testing' - ARGV << '--cleanup' << '--junit' << '--local' -end -if ARGV.include? '--ci-master' - ARGV << '--no-bottle' << '--email' -end - -if ARGV.include? '--local' - ENV['HOMEBREW_LOGS'] = "#{Dir.pwd}/logs" -end - -if ARGV.include? '--ci-pr-upload' or ARGV.include? '--ci-testing-upload' - jenkins = ENV['JENKINS_HOME'] - job = ENV['UPSTREAM_JOB_NAME'] - id = ENV['UPSTREAM_BUILD_ID'] - raise "Missing Jenkins variables!" unless jenkins and job and id - - ARGV << '--verbose' - cp_args = Dir["#{jenkins}/jobs/#{job}/configurations/axis-version/*/builds/#{id}/archive/*.bottle*.*"] + ["."] - exit unless system "cp", *cp_args - - ENV["GIT_COMMITTER_NAME"] = "BrewTestBot" - ENV["GIT_COMMITTER_EMAIL"] = "brew-test-bot@googlegroups.com" - ENV["GIT_WORK_TREE"] = homebrew_git_repo tap - ENV["GIT_DIR"] = "#{ENV["GIT_WORK_TREE"]}/.git" - - pr = ENV['UPSTREAM_PULL_REQUEST'] - number = ENV['UPSTREAM_BUILD_NUMBER'] - - system "git am --abort 2>/dev/null" - system "git rebase --abort 2>/dev/null" - safe_system "git", "checkout", "-f", "master" - safe_system "git", "reset", "--hard", "origin/master" - safe_system "brew", "update" - - if ARGV.include? '--ci-pr-upload' - safe_system "brew", "pull", "--clean", pr - end - - ENV["GIT_AUTHOR_NAME"] = ENV["GIT_COMMITTER_NAME"] - ENV["GIT_AUTHOR_EMAIL"] = ENV["GIT_COMMITTER_EMAIL"] - safe_system "brew", "bottle", "--merge", "--write", *Dir["*.bottle.rb"] - - remote = "git@github.com:BrewTestBot/homebrew.git" - tag = pr ? "pr-#{pr}" : "testing-#{number}" - safe_system "git", "push", "--force", remote, "master:master", ":refs/tags/#{tag}" - - path = "/home/frs/project/m/ma/machomebrew/Bottles/" - url = "BrewTestBot,machomebrew@frs.sourceforge.net:#{path}" - - rsync_args = %w[--partial --progress --human-readable --compress] - rsync_args += Dir["*.bottle*.tar.gz"] + [url] - - safe_system "rsync", *rsync_args - safe_system "git", "tag", "--force", tag - safe_system "git", "push", "--force", remote, "refs/tags/#{tag}" - exit -end - -tests = [] -any_errors = false -if ARGV.named.empty? - # With no arguments just build the most recent commit. - test = Test.new('HEAD', tap) - any_errors = test.run - tests << test -else - ARGV.named.each do |argument| - test = Test.new(argument, tap) - any_errors = test.run or any_errors - tests << test - end -end - -if ARGV.include? "--junit" - xml_document = REXML::Document.new - xml_document << REXML::XMLDecl.new - testsuites = xml_document.add_element 'testsuites' - tests.each do |test| - testsuite = testsuites.add_element 'testsuite' - testsuite.attributes['name'] = "brew-test-bot.#{MacOS.cat}" - testsuite.attributes['tests'] = test.steps.count - test.steps.each do |step| - testcase = testsuite.add_element 'testcase' - testcase.attributes['name'] = step.command_short - testcase.attributes['status'] = step.status - testcase.attributes['time'] = step.time - failure = testcase.add_element 'failure' if step.failed? - if step.has_output? - # Remove invalid XML CData characters from step output. - output = step.output - if output.respond_to?(:force_encoding) && !output.valid_encoding? - output.force_encoding(Encoding::UTF_8) - end - output = REXML::CData.new output.delete("\000\a\b\e\f") - if step.passed? - system_out = testcase.add_element 'system-out' - system_out.text = output - else - failure.attributes["message"] = "#{step.status}: #{step.command.join(" ")}" - failure.text = output - end - end - end - end - - open("brew-test-bot.xml", "w") do |xml_file| - pretty_print_indent = 2 - xml_document.write(xml_file, pretty_print_indent) - end -end - -if ARGV.include? "--email" - failed_steps = [] - tests.each do |test| - test.steps.each do |step| - next unless step.failed? - failed_steps << step.command_short - end - end - - if failed_steps.empty? - email_subject = '' - else - email_subject = "#{MacOS.version}: #{failed_steps.join ', '}." - end - - File.open EMAIL_SUBJECT_FILE, 'w' do |file| - file.write email_subject - end -end - - -safe_system "rm -rf #{HOMEBREW_CACHE}/*" if ARGV.include? "--clean-cache" - -exit any_errors ? 0 : 1 |
