Add Brew Test Bot for comprehensive testing.
Closes Homebrew/homebrew#11642.
This commit is contained in:
parent
0eb97d5c32
commit
a67478488f
24
Library/Contributions/cmds/brew-test-bot.commit.html.erb
Normal file
24
Library/Contributions/cmds/brew-test-bot.commit.html.erb
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>BrewBot: <%= @test.name %></title>
|
||||||
|
<title>BrewBot</title>
|
||||||
|
<style>
|
||||||
|
<%= css %>
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h1><a href="../index.html">BrewBot</a></h1>
|
||||||
|
<h2><%= @test.name %> <%= DateTime.now.strftime "%T %D" %></h2>
|
||||||
|
<table>
|
||||||
|
<% for step in @test.steps %>
|
||||||
|
<tr>
|
||||||
|
<td><span class="prompt">$</span> <%= step.command %></td>
|
||||||
|
<td class='status <%= step.status %>'><a href='<%= step.log_file_path false %> '><%= step.status_upcase %></a></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
75
Library/Contributions/cmds/brew-test-bot.css
Normal file
75
Library/Contributions/cmds/brew-test-bot.css
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
body {
|
||||||
|
background: #27221a;
|
||||||
|
color: #f6e6cc;
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2 {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3em;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
text-shadow: 1px 1px 10px rgba(0,0,0,0.25);
|
||||||
|
color: #D7AF72;
|
||||||
|
letter-spacing: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
color: #A1804C;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 a:link, h1 a:visited, h2 a:link, h2 a:visited {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
background-color: rgba(0, 0, 0, 0.30);
|
||||||
|
color: white;
|
||||||
|
border-radius: 0.4em;
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: monospace;
|
||||||
|
list-style-type: none;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt {
|
||||||
|
color: #E3D796;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running a:link, .running a:visited {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.passed a:link, .passed a:visited {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed a:link, .failed a:visited {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active, a:visited {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
23
Library/Contributions/cmds/brew-test-bot.index.html.erb
Normal file
23
Library/Contributions/cmds/brew-test-bot.index.html.erb
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>BrewBot</title>
|
||||||
|
<style>
|
||||||
|
<%= css %>
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h1><a href="index.html">BrewBot</h1>
|
||||||
|
<table>
|
||||||
|
<% dirs.each_with_index do |dir,index| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= dir %></td>
|
||||||
|
<td><%= dates[index] %></td>
|
||||||
|
<td class='status <%= statuses[index] %>'><a href='<%= dir %>/index.html'><%= statuses[index].upcase %></a></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
277
Library/Contributions/cmds/brew-test-bot.rb
Executable file
277
Library/Contributions/cmds/brew-test-bot.rb
Executable file
@ -0,0 +1,277 @@
|
|||||||
|
# Comprehensively test a formula or pull request.
|
||||||
|
#
|
||||||
|
# Usage: brew test-bot [options...] <pull-request|formula>
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --log: Writes log files under ./brewbot/
|
||||||
|
# --html: Writes html and log files under ./brewbot/
|
||||||
|
# --comment: Comment on the pull request
|
||||||
|
# --clean: Clean the Homebrew directory. Very dangerous. Use with care.
|
||||||
|
|
||||||
|
require 'utils'
|
||||||
|
require 'date'
|
||||||
|
|
||||||
|
HOMEBREW_CONTRIBUTED_CMDS = HOMEBREW_REPOSITORY + "Library/Contributions/cmds/"
|
||||||
|
|
||||||
|
class Step
|
||||||
|
attr_reader :command
|
||||||
|
attr_accessor :status
|
||||||
|
|
||||||
|
def initialize test, command
|
||||||
|
@test = test
|
||||||
|
@category = test.category
|
||||||
|
@command = command
|
||||||
|
@name = command.split[1].delete '-'
|
||||||
|
@status = :running
|
||||||
|
@test.steps << self
|
||||||
|
write_html
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_file_path full_path=true
|
||||||
|
return "/dev/null" unless ARGV.include? "--log" or ARGV.include? "--html"
|
||||||
|
file = "#{@category}.#{@name}.txt"
|
||||||
|
return file unless @test.log_root and full_path
|
||||||
|
@test.log_root + 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 puts_command
|
||||||
|
print "#{Tty.blue}==>#{Tty.white} #{@command}#{Tty.reset}"
|
||||||
|
tabs = (80 - "PASSED".length + 1 - @command.length) / 8
|
||||||
|
tabs.times{ print "\t" }
|
||||||
|
$stdout.flush
|
||||||
|
end
|
||||||
|
|
||||||
|
def puts_result
|
||||||
|
puts "#{Tty.send status_colour}#{status_upcase}#{Tty.reset}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_html
|
||||||
|
return unless @test.log_root and ARGV.include? "--html"
|
||||||
|
|
||||||
|
open(@test.log_root + "index.html", "w") do |index|
|
||||||
|
commit_html, css = @test.commit_html_and_css
|
||||||
|
index.write commit_html.result binding
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.run test, command
|
||||||
|
step = new test, command
|
||||||
|
step.puts_command
|
||||||
|
`#{step.command} &>#{step.log_file_path}`
|
||||||
|
step.status = $?.success? ? :passed : :failed
|
||||||
|
step.puts_result
|
||||||
|
step.write_html
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Test
|
||||||
|
attr_reader :log_root, :category, :name
|
||||||
|
attr_reader :core_changed, :formulae
|
||||||
|
attr_accessor :steps
|
||||||
|
|
||||||
|
@@css = @@index_html = @@commit_html = nil
|
||||||
|
|
||||||
|
def commit_html_and_css
|
||||||
|
return @@commit_html, @@css
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize arg
|
||||||
|
begin
|
||||||
|
Formula.factory arg
|
||||||
|
rescue FormulaUnavailableError
|
||||||
|
ofail "#{arg} is not a pull request number or formula." unless arg.to_i > 0
|
||||||
|
@url = arg
|
||||||
|
@formulae = []
|
||||||
|
else
|
||||||
|
@url = nil
|
||||||
|
@formulae = [arg]
|
||||||
|
end
|
||||||
|
|
||||||
|
@start_sha1 = nil
|
||||||
|
@category = __method__
|
||||||
|
@steps = []
|
||||||
|
@core_changed = false
|
||||||
|
@brewbot_root = Pathname.pwd + "brewbot"
|
||||||
|
FileUtils.mkdir_p @brewbot_root if ARGV.include? "--log" or ARGV.include? "--html"
|
||||||
|
|
||||||
|
if ARGV.include? "--html" and not @@css
|
||||||
|
require 'erb'
|
||||||
|
root = HOMEBREW_CONTRIBUTED_CMDS
|
||||||
|
@@css = IO.read root + "brew-test-bot.css"
|
||||||
|
@@index_html = ERB.new IO.read root + "brew-test-bot.index.html.erb"
|
||||||
|
@@commit_html = ERB.new IO.read root + "brew-test-bot.commit.html.erb"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_root_html status
|
||||||
|
return unless ARGV.include? "--html"
|
||||||
|
|
||||||
|
FileUtils.mv Dir.glob("*.txt"), @log_root
|
||||||
|
open(@log_root + "status.txt", "w") do |file|
|
||||||
|
file.write status
|
||||||
|
end
|
||||||
|
|
||||||
|
dirs = []
|
||||||
|
dates = []
|
||||||
|
statuses = []
|
||||||
|
|
||||||
|
Pathname.glob("#{@brewbot_root}/*/status.txt").each do |result|
|
||||||
|
dirs << result.dirname.basename
|
||||||
|
status_file = result.dirname + "status.txt"
|
||||||
|
dates << File.mtime(status_file).strftime("%T %D")
|
||||||
|
statuses << IO.read(status_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
open(@brewbot_root + "index.html", "w") do |index|
|
||||||
|
css = @@css
|
||||||
|
index.write @@index_html.result binding
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def download
|
||||||
|
def current_sha1
|
||||||
|
`git rev-parse --short HEAD`.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_branch
|
||||||
|
`git symbolic-ref HEAD`.slice!("refs/heads/").strip
|
||||||
|
end
|
||||||
|
|
||||||
|
@category = __method__
|
||||||
|
if @url
|
||||||
|
`git am --abort 2>/dev/null`
|
||||||
|
test "brew update" if current_branch == "master"
|
||||||
|
@start_sha1 = current_sha1
|
||||||
|
test "brew pull --clean #{@url}"
|
||||||
|
end_sha1 = current_sha1
|
||||||
|
else
|
||||||
|
@start_sha1 = end_sha1 = current_sha1
|
||||||
|
end
|
||||||
|
|
||||||
|
name_prefix = @url ? @url : @formulae.first
|
||||||
|
@name = "#{name_prefix}-#{end_sha1}"
|
||||||
|
@log_root = @brewbot_root + @name
|
||||||
|
FileUtils.mkdir_p @log_root if ARGV.include? "--log" or ARGV.include? "--html"
|
||||||
|
|
||||||
|
write_root_html :running
|
||||||
|
|
||||||
|
return unless @url and @start_sha1 != end_sha1 and steps.last.status == :passed
|
||||||
|
|
||||||
|
`git diff #{@start_sha1}..#{end_sha1} --name-status`.each_line do |line|
|
||||||
|
status, filename = line.split
|
||||||
|
# Don't try and do anything to removed files.
|
||||||
|
if (status == 'A' or status == 'M')
|
||||||
|
if filename.include? '/Formula/'
|
||||||
|
@formulae << File.basename(filename, '.rb')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if filename.include? '/Homebrew/' or filename.include? 'bin/brew'
|
||||||
|
@homebrew_changed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@category = __method__
|
||||||
|
|
||||||
|
test "brew doctor"
|
||||||
|
test "brew --env"
|
||||||
|
test "brew --config"
|
||||||
|
end
|
||||||
|
|
||||||
|
def formula formula
|
||||||
|
@category = __method__.to_s + ".#{formula}"
|
||||||
|
|
||||||
|
test "brew audit #{formula}"
|
||||||
|
test "brew install --verbose --build-bottle #{formula}"
|
||||||
|
return unless steps.last.status == :passed
|
||||||
|
test "brew test #{formula}"
|
||||||
|
test "brew bottle #{formula}"
|
||||||
|
test "brew uninstall #{formula}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def homebrew
|
||||||
|
@category = __method__
|
||||||
|
test "brew tests"
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup
|
||||||
|
@category = __method__
|
||||||
|
if ARGV.include? "--clean"
|
||||||
|
test "git reset --hard origin/master"
|
||||||
|
test "git clean --force -dx"
|
||||||
|
else
|
||||||
|
`git diff --exit-code HEAD 2>/dev/null`
|
||||||
|
ofail "Uncommitted changes, aborting." unless $?.success?
|
||||||
|
test "git reset --hard #{@start_sha1}" if @start_sha1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test cmd
|
||||||
|
Step.run self, cmd
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_results
|
||||||
|
message = "All tests passed and raring to brew."
|
||||||
|
|
||||||
|
status = :passed
|
||||||
|
steps.each do |step|
|
||||||
|
case step.status
|
||||||
|
when :passed then next
|
||||||
|
when :running then raise
|
||||||
|
when :failed then
|
||||||
|
if status == :passed
|
||||||
|
status = :failed
|
||||||
|
message = ""
|
||||||
|
end
|
||||||
|
message += "#{step.command}: #{step.status.to_s.upcase}\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
write_root_html status
|
||||||
|
|
||||||
|
if ARGV.include? "--comment" and @url
|
||||||
|
username, password = IO.read(File.expand_path('~/.brewbot')).split(':')
|
||||||
|
url = "https://api.github.com/repos/mxcl/homebrew/issues/#{@url}/comments"
|
||||||
|
require 'vendor/multi_json'
|
||||||
|
json = MultiJson.encode(:body => message)
|
||||||
|
curl url, "-X", "POST", "--user", "#{username}:#{password}", "--data", json, "-o", "/dev/null"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.run url
|
||||||
|
test = new url
|
||||||
|
test.cleanup
|
||||||
|
test.download
|
||||||
|
test.setup
|
||||||
|
test.formulae.each do |f|
|
||||||
|
test.formula f
|
||||||
|
end
|
||||||
|
test.homebrew if test.core_changed
|
||||||
|
test.cleanup
|
||||||
|
|
||||||
|
test.check_results
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ARGV.empty?
|
||||||
|
ofail 'This command requires at least one argument containing a pull request number or formula.'
|
||||||
|
end
|
||||||
|
|
||||||
|
Dir.chdir HOMEBREW_REPOSITORY
|
||||||
|
|
||||||
|
ARGV.named.each do|arg|
|
||||||
|
Test.run arg
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user