Files
brew/Library/Homebrew/test/dev-cmd/audit_spec.rb
T
Bevan Kay 9d7fa14367 Require zap stanzas in new casks
- New casks often omit `zap`, and the `# No zap stanza required`
  comment that marks a deliberate omission can't be audited. Add
  `zap :not_required` so a cask can declare it instead.
- Serialise it as an empty `zap` stanza so that older versions of
  Homebrew can still load these casks from the API.
- Audit new casks in official taps for a `zap` stanza on the OS and
  architecture being audited. CI audits each one, so this catches a
  cask that supports Linux but only has a `zap` in `on_macos`.
- Casks have no stanza values for platforms they don't support, so
  have `brew audit` audit macOS-only casks under macOS and casks with
  `depends_on arch` on an architecture they support, as it already
  does for Linux-only casks. Otherwise `--os=all` or `--arch=all`
  fails a macOS-only cask with its `zap` in `on_macos`, or an ARM-only
  one with it in `on_arm`, and other audits fail the same way, e.g. on
  a `url` in `on_arm`.
- Only suggest `brew generate-zap` when auditing macOS, as it only
  scans macOS paths, and have it warn that the app must have been
  launched before it prints `zap :not_required`.
- Fix `Cask/UninstallMethodsOrder`, which crashed on the non-hash
  argument of `zap :not_required`.
2026-10-21 23:23:43 +11:00

232 lines
8.1 KiB
Ruby

# typed: true
# frozen_string_literal: true
require "dev-cmd/audit"
require "cmd/shared_examples/args_parse"
RSpec.describe Homebrew::DevCmd::Audit do
it_behaves_like "parseable arguments"
it "audits a Formula and Cask", :cask, :integration_test do
setup_test_formula "testball"
expect { brew "audit", "--skip-style", "--only=description", "testball", "local-caffeine" }
.to be_a_success
end
describe "#run" do
subject(:audit) { described_class.new(["--tap=homebrew/test"]) }
let(:tap_path) { mktmpdir }
let(:macos_only_cask_file) { tap_path/"Casks/macos-only-example.rb" }
let(:linux_cask_file) { tap_path/"Casks/linux-example.rb" }
let(:linux_only_cask_file) { tap_path/"Casks/linux-only-example.rb" }
let(:tap) do
instance_double(Tap, formula_files: [], cask_files: [macos_only_cask_file, linux_cask_file,
linux_only_cask_file])
end
before do
macos_only_cask_file.dirname.mkpath
macos_only_cask_file.write <<~RUBY
cask "macos-only-example" do
version "1.0"
sha256 arm: "0000000000000000000000000000000000000000000000000000000000000000",
intel: "1111111111111111111111111111111111111111111111111111111111111111"
url "https://example.invalid/x-\#{version}.pkg"
name "Example"
desc "macOS-only cask"
homepage "https://example.invalid/"
depends_on macos: :ventura
binary "x"
end
RUBY
linux_cask_file.write <<~RUBY
cask "linux-example" do
version "1.0"
sha256 arm: "0000000000000000000000000000000000000000000000000000000000000000",
intel: "1111111111111111111111111111111111111111111111111111111111111111"
url "https://example.invalid/x-\#{version}.tar.gz"
name "Example"
desc "Linux-supported cask"
homepage "https://example.invalid/"
binary "x"
end
RUBY
linux_only_cask_file.write <<~RUBY
cask "linux-only-example" do
version "1.0"
sha256 arm64_linux: "0000000000000000000000000000000000000000000000000000000000000000",
x86_64_linux: "1111111111111111111111111111111111111111111111111111111111111111"
url "https://example.invalid/x-\#{version}.tar.gz"
name "Example"
desc "Linux-only cask"
homepage "https://example.invalid/"
depends_on :linux
binary "x"
end
RUBY
allow(Utils::GemSetup).to receive(:install_bundler_gems!)
ENV.activate_extensions!
allow(ENV).to receive(:setup_build_environment)
allow(Tap).to receive(:fetch).and_call_original
allow(Tap).to receive(:fetch).with("homebrew/test").and_return(tap)
allow(Tap).to receive(:installed).and_return([])
end
it "audits Linux-supporting casks and skips macOS-only ones on Linux" do
problems = a_string_matching(
/\A(?=.*linux-example)(?=.*a sha256 stanza is required)(?!.*macos-only-example).*\z/m,
)
Homebrew::SimulateSystem.with(os: :linux) do
expect { audit.run }.to output(problems).to_stdout
.and output(/1 problem in 1 cask detected/).to_stderr
end
end
it "audits Linux-only casks under Linux when running on macOS" do
Homebrew::SimulateSystem.with(os: :macos) do
expect { audit.run }.not_to output.to_stdout
end
end
it "audits macOS-only casks under macOS when auditing all operating systems" do
macos_url_cask_file = tap_path/"Casks/macos-url-example.rb"
macos_url_cask_file.write <<~RUBY
cask "macos-url-example" do
version "1.0"
sha256 "0000000000000000000000000000000000000000000000000000000000000000"
on_macos do
url "https://example.invalid/x-\#{version}.pkg"
end
name "Example"
desc "macOS-only cask"
homepage "https://example.invalid/"
depends_on macos: :ventura
binary "x"
end
RUBY
allow(tap).to receive(:cask_files).and_return([macos_url_cask_file])
expect { described_class.new(["--tap=homebrew/test", "--os=all", "--arch=arm"]).run }
.not_to output.to_stdout
end
it "audits ARM-only casks under ARM when auditing Intel" do
arm_url_cask_file = tap_path/"Casks/arm-url-example.rb"
arm_url_cask_file.write <<~RUBY
cask "arm-url-example" do
version "1.0"
sha256 "0000000000000000000000000000000000000000000000000000000000000000"
on_arm do
url "https://example.invalid/x-\#{version}.tar.gz"
end
name "Example"
desc "ARM-only cask"
homepage "https://example.invalid/"
depends_on arch: :arm64
binary "x"
end
RUBY
allow(tap).to receive(:cask_files).and_return([arm_url_cask_file])
Homebrew::SimulateSystem.with(os: :macos, arch: :intel) do
expect { audit.run }.not_to output.to_stdout
end
end
it "audits casks on the architecture each operating system supports" do
os_arch_cask_file = tap_path/"Casks/os-arch-example.rb"
os_arch_cask_file.write <<~RUBY
cask "os-arch-example" do
version "1.0"
sha256 arm: "0000000000000000000000000000000000000000000000000000000000000000",
x86_64_linux: "1111111111111111111111111111111111111111111111111111111111111111"
url "https://example.invalid/x-\#{version}.tar.gz"
name "Example"
desc "macOS ARM and Linux Intel cask"
homepage "https://example.invalid/"
on_macos do
depends_on arch: :arm64
end
on_linux do
depends_on arch: :x86_64
end
binary "x"
end
RUBY
allow(tap).to receive(:cask_files).and_return([os_arch_cask_file])
expect { described_class.new(["--tap=homebrew/test", "--os=all", "--arch=intel"]).run }
.not_to output.to_stdout
end
it "audits Linux Intel for a cask that only requires macOS on ARM" do
arm_macos_cask_file = tap_path/"Casks/arm-macos-example.rb"
arm_macos_cask_file.write <<~RUBY
cask "arm-macos-example" do
version "1.0"
sha256 arm: "0000000000000000000000000000000000000000000000000000000000000000",
intel: "1111111111111111111111111111111111111111111111111111111111111111",
arm64_linux: "0000000000000000000000000000000000000000000000000000000000000000"
url "https://example.invalid/x-\#{version}.tar.gz"
name "Example"
desc "Cask without a Linux Intel checksum"
homepage "https://example.invalid/"
on_arm do
depends_on macos: :big_sur
end
binary "x"
end
RUBY
allow(tap).to receive(:cask_files).and_return([arm_macos_cask_file])
expect { described_class.new(["--tap=homebrew/test", "--os=all", "--arch=all"]).run }
.to output(/a sha256 stanza is required/).to_stdout
end
it "enables API access when auditing external formulae after it was automatically disabled" do
formula_file = tap_path/"Formula/example.rb"
formula_file.dirname.mkpath
formula_file.write <<~RUBY
class Example < Formula
desc "Example"
homepage "https://example.com"
url "https://example.com/example-1.0.tar.gz"
sha256 "0000000000000000000000000000000000000000000000000000000000000000"
depends_on "dependency"
end
RUBY
allow(tap).to receive_messages(formula_files: [formula_file], cask_files: [])
formula_auditor = instance_double(Homebrew::FormulaAuditor, audit: nil, problems: [], new_formula_problems: [])
with_env(HOMEBREW_NO_INSTALL_FROM_API: "1", HOMEBREW_AUTOMATICALLY_SET_NO_INSTALL_FROM_API: "1") do
expect(Homebrew::FormulaAuditor).to receive(:new) do
expect(Homebrew::EnvConfig.no_install_from_api?).to be(false)
formula_auditor
end
audit.run
end
end
end
end