brew test, install, update-test: add --keep-tmp option

Also enables sandbox for --interactive and --debug use of install
and test, using automatic retention.

Closes #66.

Signed-off-by: Andrew Janke <andrew@apjanke.net>
This commit is contained in:
Andrew Janke 2016-04-10 22:53:56 -04:00
parent 0e8140b012
commit acc9a7ca85
17 changed files with 166 additions and 94 deletions

View File

@ -105,7 +105,8 @@ class Build
formula.extend(Debrew::Formula) if ARGV.debug?
formula.brew do
formula.brew do |_formula, staging|
staging.retain! if ARGV.keep_tmp?
formula.patch
if ARGV.git?

View File

@ -1,4 +1,4 @@
#: * `install` [`--debug`] [`--env=`<std>|<super>] [`--ignore-dependencies`] [`--only-dependencies`] [`--cc=`<compiler>] [`--build-from-source`|`--force-bottle`] [`--devel`|`--HEAD`] <formula>:
#: * `install` [`--debug`] [`--env=`<std>|<super>] [`--ignore-dependencies`] [`--only-dependencies`] [`--cc=`<compiler>] [`--build-from-source`|`--force-bottle`] [`--devel`|`--HEAD`] [`--keep-tmp`] <formula>:
#: Install <formula>.
#:
#: <formula> is usually the name of the formula to install, but it can be specified
@ -35,6 +35,9 @@
#: If `--HEAD` is passed, and <formula> defines it, install the HEAD version,
#: aka master, trunk, unstable.
#:
#: If `--keep-tmp` is passed, the temporary files created for the test are
#: not deleted.
#:
#: To install a newer version of HEAD use
#: `brew rm <foo> && brew install --HEAD <foo>`.
#:

View File

@ -22,15 +22,11 @@ module Homebrew
end
if Sandbox.available? && ARGV.sandbox?
if Sandbox.auto_disable?
Sandbox.print_autodisable_warning
else
Sandbox.print_sandbox_message
end
end
Utils.safe_fork do
if Sandbox.available? && ARGV.sandbox? && !Sandbox.auto_disable?
if Sandbox.available? && ARGV.sandbox?
sandbox = Sandbox.new
formula.logs.mkpath
sandbox.record_log(formula.logs/"sandbox.postinstall.log")

View File

@ -21,6 +21,7 @@
# --verbose: Print test step output in realtime. Has the side effect of passing output
# as raw bytes instead of re-encoding in UTF-8.
# --fast: Don't install any packages, but run e.g. audit anyway.
# --keep-tmp: Keep temporary files written by main installs and tests that are run.
#
# --ci-master: Shortcut for Homebrew master branch CI options.
# --ci-pr: Shortcut for Homebrew pull request CI options.
@ -532,7 +533,12 @@ module Homebrew
end
test "brew", "fetch", "--retry", *fetch_args
test "brew", "uninstall", "--force", formula_name if formula.installed?
install_args = ["--verbose"]
# shared_*_args are applied to both the main and --devel spec
shared_install_args = ["--verbose"]
shared_install_args << "--keep-tmp" if ARGV.keep_tmp?
# install_args is just for the main (stable, or devel if in a devel-only tap) spec
install_args = []
install_args << "--build-bottle" if !ARGV.include?("--fast") && !ARGV.include?("--no-bottle") && !formula.bottle_disabled?
install_args << "--HEAD" if ARGV.include? "--HEAD"
@ -548,6 +554,7 @@ module Homebrew
formula_bottled = formula.bottled?
end
install_args.concat(shared_install_args)
install_args << formula_name
# Don't care about e.g. bottle failures for dependencies.
install_passed = false
@ -582,7 +589,9 @@ module Homebrew
test "brew", "install", bottle_filename
end
end
test "brew", "test", "--verbose", formula_name if formula.test_defined?
shared_test_args = ["--verbose"]
shared_test_args << "--keep-tmp" if ARGV.keep_tmp?
test "brew", "test", formula_name, *shared_test_args if formula.test_defined?
testable_dependents.each do |dependent|
unless dependent.installed?
test "brew", "fetch", "--retry", dependent.name
@ -607,11 +616,13 @@ module Homebrew
&& !ARGV.include?("--HEAD") && !ARGV.include?("--fast") \
&& satisfied_requirements?(formula, :devel)
test "brew", "fetch", "--retry", "--devel", *fetch_args
run_as_not_developer { test "brew", "install", "--devel", "--verbose", formula_name }
run_as_not_developer do
test "brew", "install", "--devel", formula_name, *shared_install_args
end
devel_install_passed = steps.last.passed?
test "brew", "audit", "--devel", *audit_args
if devel_install_passed
test "brew", "test", "--devel", "--verbose", formula_name if formula.test_defined?
test "brew", "test", "--devel", formula_name, *shared_test_args if formula.test_defined?
test "brew", "uninstall", "--devel", "--force", formula_name
end
end

View File

@ -1,4 +1,4 @@
#: * `test` [`--devel`|`--HEAD`] [`--debug`] <formula>:
#: * `test` [`--devel`|`--HEAD`] [`--debug`] [`--keep-tmp`] <formula>:
#: A few formulae provide a test method. `brew test` <formula> runs this
#: test method. There is no standard output or return code, but it should
#: generally indicate to the user if something is wrong with the installed
@ -10,6 +10,9 @@
#: If `--debug` is passed and the test fails, an interactive debugger will be
#: launched with access to IRB or a shell inside the temporary test directory.
#:
#: If `--keep-tmp` is passed, the temporary files created for the test are
#: not deleted.
#:
#: Example: `brew install jruby && brew test jruby`
require "extend/ENV"
@ -55,15 +58,11 @@ module Homebrew
end
if Sandbox.available? && !ARGV.no_sandbox?
if Sandbox.auto_disable?
Sandbox.print_autodisable_warning
else
Sandbox.print_sandbox_message
end
end
Utils.safe_fork do
if Sandbox.available? && !ARGV.no_sandbox? && !Sandbox.auto_disable?
if Sandbox.available? && !ARGV.no_sandbox?
sandbox = Sandbox.new
f.logs.mkpath
sandbox.record_log(f.logs/"sandbox.test.log")

View File

@ -5,6 +5,8 @@ module Homebrew
# brew update-test --commit=<sha1> # using <sha1> as start commit
# brew update-test --before=<date> # using commit at <date> as start commit
#
# Options:
# --keep-tmp Retain temporary directory containing the new clone
def update_test
cd HOMEBREW_REPOSITORY
start_sha1 = if commit = ARGV.value("commit")
@ -19,7 +21,8 @@ module Homebrew
puts "Start commit: #{start_sha1}"
puts "End commit: #{end_sha1}"
mktemp do
mktemp("update-test") do |staging|
staging.retain! if ARGV.keep_tmp?
curdir = Pathname.new(Dir.pwd)
oh1 "Setup test environment..."

View File

@ -17,7 +17,9 @@ class AbstractDownloadStrategy
def fetch
end
# Unpack {#cached_location} into the current working directory.
# Unpack {#cached_location} into the current working directory, and possibly
# chdir into the newly-unpacked directory.
# Unlike {Resource#stage}, this does not take a block.
def stage
end

View File

@ -120,6 +120,10 @@ module HomebrewArgvExtension
include?("--dry-run") || switch?("n")
end
def keep_tmp?
include? "--keep-tmp"
end
def git?
flag? "--git"
end

View File

@ -6,10 +6,52 @@ require "etc"
# @see http://ruby-doc.org/stdlib-1.8.7/libdoc/fileutils/rdoc/FileUtils.html Ruby's FileUtils API
module FileUtils
# Create a temporary directory then yield. When the block returns,
# recursively delete the temporary directory.
def mktemp(prefix = name)
prev = pwd
tmp = Dir.mktmpdir(prefix, HOMEBREW_TEMP)
# recursively delete the temporary directory. Passing opts[:retain]
# or calling `do |staging| ... staging.retain!` in the block will skip
# the deletion and retain the temporary directory's contents.
def mktemp(prefix = name, opts = {})
Mktemp.new(prefix, opts).run do |staging|
yield staging
end
end
module_function :mktemp
# Performs mktemp's functionality, and tracks the results.
# Each instance is only intended to be used once.
class Mktemp
include FileUtils
# Path to the tmpdir used in this run, as a Pathname.
attr_reader :tmpdir
def initialize(prefix = name, opts = {})
@prefix = prefix
@retain = opts[:retain]
@quiet = false
end
# Instructs this Mktemp to retain the staged files
def retain!
@retain = true
end
# True if the staged temporary files should be retained
def retain?
@retain
end
# Instructs this Mktemp to not emit messages when retention is triggered
def quiet!
@quiet = true
end
def to_s
"[Mktemp: #{tmpdir} retain=#{@retain} quiet=#{@quiet}]"
end
def run
@tmpdir = Pathname.new(Dir.mktmpdir("#{@prefix}-", HOMEBREW_TEMP))
# Make sure files inside the temporary directory have the same group as the
# brew instance.
@ -24,24 +66,23 @@ module FileUtils
end
begin
# group_id.to_s makes OS X 10.6.7 (ruby-1.8.7-p174) and earlier happy.
chown(nil, group_id.to_s, tmp)
chown(nil, group_id.to_s, tmpdir)
rescue Errno::EPERM
opoo "Failed setting group \"#{Etc.getgrgid(group_id).name}\" on #{tmp}"
end
begin
cd(tmp)
begin
yield
Dir.chdir(tmpdir) { yield self }
ensure
cd(prev)
ignore_interrupts { rm_rf(tmpdir) } unless retain?
end
ensure
ignore_interrupts { rm_rf(tmp) }
if retain? && !@tmpdir.nil? && !@quiet
ohai "Kept temporary files"
puts "Temporary files retained at #{@tmpdir}"
end
end
end
module_function :mktemp
# @private
alias_method :old_mkdir, :mkdir

View File

@ -920,14 +920,19 @@ class Formula
end
end
# yields self with current working directory set to the uncompressed tarball
# yields |self,staging| with current working directory set to the uncompressed tarball
# where staging is a Mktemp staging context
# @private
def brew
stage do
stage do |staging|
staging.retain! if ARGV.keep_tmp?
prepare_patches
begin
yield self
yield self, staging
rescue StandardError
staging.retain! if ARGV.interactive? || ARGV.debug?
raise
ensure
cp Dir["config.log", "CMakeCache.txt"], logs
end
@ -1320,11 +1325,17 @@ class Formula
def run_test
old_home = ENV["HOME"]
build, self.build = self.build, Tab.for_formula(self)
mktemp do
@testpath = Pathname.pwd
mktemp("#{name}-test") do |staging|
staging.retain! if ARGV.keep_tmp?
@testpath = staging.tmpdir
ENV["HOME"] = @testpath
setup_home @testpath
begin
test
rescue Exception
staging.retain! if ARGV.debug?
raise
end
end
ensure
@testpath = nil
@ -1537,7 +1548,7 @@ class Formula
end
def stage
active_spec.stage do
active_spec.stage do |_resource, staging|
@source_modified_time = active_spec.source_modified_time
@buildpath = Pathname.pwd
env_home = buildpath/".brew_home"
@ -1547,7 +1558,7 @@ class Formula
setup_home env_home
begin
yield
yield staging
ensure
@buildpath = nil
ENV["HOME"] = old_home

View File

@ -523,6 +523,7 @@ class FormulaInstaller
args << "--debug" if debug?
args << "--cc=#{ARGV.cc}" if ARGV.cc
args << "--default-fortran-flags" if ARGV.include? "--default-fortran-flags"
args << "--keep-tmp" if ARGV.keep_tmp?
if ARGV.env
args << "--env=#{ARGV.env}"
@ -567,18 +568,14 @@ class FormulaInstaller
].concat(build_argv)
if Sandbox.available? && ARGV.sandbox?
if Sandbox.auto_disable?
Sandbox.print_autodisable_warning
else
Sandbox.print_sandbox_message
end
end
Utils.safe_fork do
# Invalidate the current sudo timestamp in case a build script calls sudo
system "/usr/bin/sudo", "-k"
if Sandbox.available? && ARGV.sandbox? && !Sandbox.auto_disable?
if Sandbox.available? && ARGV.sandbox?
sandbox = Sandbox.new
formula.logs.mkpath
sandbox.record_log(formula.logs/"sandbox.build.log")

View File

@ -72,6 +72,10 @@ class Resource
downloader.clear_cache
end
# Verifies download and unpacks it
# The block may call `|resource,staging| staging.retain!` to retain the staging
# directory. Subclasses that override stage should implement the tmp
# dir using FileUtils.mktemp so that works with all subtypes.
def stage(target = nil, &block)
unless target || block
raise ArgumentError, "target directory or block is required"
@ -81,15 +85,16 @@ class Resource
unpack(target, &block)
end
# If a target is given, unpack there; else unpack to a temp folder
# If block is given, yield to that block
# A target or a block must be given, but not both
# If a target is given, unpack there; else unpack to a temp folder.
# If block is given, yield to that block with |self, staging|, where staging
# is a staging context that responds to retain!().
# A target or a block must be given, but not both.
def unpack(target = nil)
mktemp(download_name) do
mktemp(download_name) do |staging|
downloader.stage
@source_modified_time = downloader.source_modified_time
if block_given?
yield self
yield self, staging
elsif target
target = Pathname.new(target) unless target.is_a? Pathname
target.install Dir["*"]

View File

@ -8,18 +8,6 @@ class Sandbox
OS.mac? && File.executable?(SANDBOX_EXEC)
end
# there are times the sandbox cannot be used.
def self.auto_disable?
@auto_disable ||= ARGV.interactive? || ARGV.debug?
end
def self.print_autodisable_warning
unless @printed_autodisable_warning
opoo "The sandbox cannot be used in debug or interactive mode."
@printed_autodisable_warning = true
end
end
def self.print_sandbox_message
unless @printed_sandbox_message
ohai "Using the sandbox"

View File

@ -122,7 +122,7 @@ class PatchingTests < Homebrew::TestCase
url PATCH_URL_A
sha256 PATCH_A_SHA256
end
end.brew(&:patch)
end.brew { |f, _staging| f.patch }
end
end
end
@ -136,7 +136,7 @@ class PatchingTests < Homebrew::TestCase
sha256 TESTBALL_PATCHES_SHA256
apply APPLY_A
end
end.brew(&:patch)
end.brew { |f, _staging| f.patch }
end
end
end
@ -234,7 +234,7 @@ class PatchingTests < Homebrew::TestCase
sha256 TESTBALL_PATCHES_SHA256
apply "patches/#{APPLY_A}"
end
end.brew(&:patch)
end.brew { |f, _staging| f.patch }
end
end
end

View File

@ -172,8 +172,7 @@ def interactive_shell(f = nil)
if $?.success?
return
elsif $?.exited?
puts "Aborting due to non-zero exit status"
exit $?.exitstatus
raise "Aborted due to non-zero exit status (#{$?.exitstatus})"
else
raise $?.inspect
end

View File

@ -155,7 +155,7 @@ information on all installed formulae.</p>
<p>See the docs for examples of using the JSON:
<a href="https://github.com/Homebrew/brew/blob/master/share/doc/homebrew/Querying-Brew.md" data-bare-link="true">https://github.com/Homebrew/brew/blob/master/share/doc/homebrew/Querying-Brew.md</a></p></dd>
<dt><code>install</code> [<code>--debug</code>] [<code>--env=</code><var>std</var>|<var>super</var>] [<code>--ignore-dependencies</code>] [<code>--only-dependencies</code>] [<code>--cc=</code><var>compiler</var>] [<code>--build-from-source</code>|<code>--force-bottle</code>] [<code>--devel</code>|<code>--HEAD</code>] <var>formula</var></dt><dd><p>Install <var>formula</var>.</p>
<dt><code>install</code> [<code>--debug</code>] [<code>--env=</code><var>std</var>|<var>super</var>] [<code>--ignore-dependencies</code>] [<code>--only-dependencies</code>] [<code>--cc=</code><var>compiler</var>] [<code>--build-from-source</code>|<code>--force-bottle</code>] [<code>--devel</code>|<code>--HEAD</code>] [<code>--keep-tmp</code>] <var>formula</var></dt><dd><p>Install <var>formula</var>.</p>
<p><var>formula</var> is usually the name of the formula to install, but it can be specified
several different ways. See <a href="#SPECIFYING-FORMULAE" title="SPECIFYING FORMULAE" data-bare-link="true">SPECIFYING FORMULAE</a>.</p>
@ -191,6 +191,9 @@ for the current version of OS X, even if custom options are given.</p>
<p>If <code>--HEAD</code> is passed, and <var>formula</var> defines it, install the HEAD version,
aka master, trunk, unstable.</p>
<p>If <code>--keep-tmp</code> is passed, the temporary files created for the test are
not deleted.</p>
<p>To install a newer version of HEAD use
<code>brew rm &lt;foo> &amp;&amp; brew install --HEAD &lt;foo></code>.</p></dd>
<dt><code>install</code> <code>--interactive</code> [<code>--git</code>] <var>formula</var></dt><dd><p>Download and patch <var>formula</var>, then open a shell. This allows the user to
@ -321,7 +324,7 @@ for <var>version</var> is <code>v1</code>.</p>
<dt><code>tap-pin</code> <var>tap</var></dt><dd><p>Pin <var>tap</var>, prioritizing its formulae over core when formula names are supplied
by the user. See also <code>tap-unpin</code>.</p></dd>
<dt><code>tap-unpin</code> <var>tap</var></dt><dd><p>Unpin <var>tap</var> so its formulae are no longer prioritized. See also <code>tap-pin</code>.</p></dd>
<dt><code>test</code> [<code>--devel</code>|<code>--HEAD</code>] [<code>--debug</code>] <var>formula</var></dt><dd><p>A few formulae provide a test method. <code>brew test</code> <var>formula</var> runs this
<dt><code>test</code> [<code>--devel</code>|<code>--HEAD</code>] [<code>--debug</code>] [<code>--keep-tmp</code>] <var>formula</var></dt><dd><p>A few formulae provide a test method. <code>brew test</code> <var>formula</var> runs this
test method. There is no standard output or return code, but it should
generally indicate to the user if something is wrong with the installed
formula.</p>
@ -332,6 +335,9 @@ formula.</p>
<p>If <code>--debug</code> is passed and the test fails, an interactive debugger will be
launched with access to IRB or a shell inside the temporary test directory.</p>
<p>If <code>--keep-tmp</code> is passed, the temporary files created for the test are
not deleted.</p>
<p>Example: <code>brew install jruby &amp;&amp; brew test jruby</code></p></dd>
<dt><code>uninstall</code>, <code>rm</code>, <code>remove</code> [<code>--force</code>] <var>formula</var></dt><dd><p>Uninstall <var>formula</var>.</p>

View File

@ -214,7 +214,7 @@ Pass \fB\-\-all\fR to get information on all formulae, or \fB\-\-installed\fR to
See the docs for examples of using the JSON: \fIhttps://github\.com/Homebrew/brew/blob/master/share/doc/homebrew/Querying\-Brew\.md\fR
.
.TP
\fBinstall\fR [\fB\-\-debug\fR] [\fB\-\-env=\fR\fIstd\fR|\fIsuper\fR] [\fB\-\-ignore\-dependencies\fR] [\fB\-\-only\-dependencies\fR] [\fB\-\-cc=\fR\fIcompiler\fR] [\fB\-\-build\-from\-source\fR|\fB\-\-force\-bottle\fR] [\fB\-\-devel\fR|\fB\-\-HEAD\fR] \fIformula\fR
\fBinstall\fR [\fB\-\-debug\fR] [\fB\-\-env=\fR\fIstd\fR|\fIsuper\fR] [\fB\-\-ignore\-dependencies\fR] [\fB\-\-only\-dependencies\fR] [\fB\-\-cc=\fR\fIcompiler\fR] [\fB\-\-build\-from\-source\fR|\fB\-\-force\-bottle\fR] [\fB\-\-devel\fR|\fB\-\-HEAD\fR] [\fB\-\-keep\-tmp\fR] \fIformula\fR
Install \fIformula\fR\.
.
.IP
@ -251,6 +251,9 @@ If \fB\-\-devel\fR is passed, and \fIformula\fR defines it, install the developm
If \fB\-\-HEAD\fR is passed, and \fIformula\fR defines it, install the HEAD version, aka master, trunk, unstable\.
.
.IP
If \fB\-\-keep\-tmp\fR is passed, the temporary files created for the test are not deleted\.
.
.IP
To install a newer version of HEAD use \fBbrew rm <foo> && brew install \-\-HEAD <foo>\fR\.
.
.TP
@ -449,7 +452,7 @@ Pin \fItap\fR, prioritizing its formulae over core when formula names are suppli
Unpin \fItap\fR so its formulae are no longer prioritized\. See also \fBtap\-pin\fR\.
.
.TP
\fBtest\fR [\fB\-\-devel\fR|\fB\-\-HEAD\fR] [\fB\-\-debug\fR] \fIformula\fR
\fBtest\fR [\fB\-\-devel\fR|\fB\-\-HEAD\fR] [\fB\-\-debug\fR] [\fB\-\-keep\-tmp\fR] \fIformula\fR
A few formulae provide a test method\. \fBbrew test\fR \fIformula\fR runs this test method\. There is no standard output or return code, but it should generally indicate to the user if something is wrong with the installed formula\.
.
.IP
@ -459,6 +462,9 @@ To test the development or head version of a formula, use \fB\-\-devel\fR or \fB
If \fB\-\-debug\fR is passed and the test fails, an interactive debugger will be launched with access to IRB or a shell inside the temporary test directory\.
.
.IP
If \fB\-\-keep\-tmp\fR is passed, the temporary files created for the test are not deleted\.
.
.IP
Example: \fBbrew install jruby && brew test jruby\fR
.
.TP