brew/Library/Homebrew/test/formula_auditor_spec.rb
Issy Long 9f915a6a62
Replace FormulaTextAuditor usage
- Only two audits were using this: `audit_keg_only_reason` and `audit_text`,
  and they weren't using any of its text processing methods, so there's little
  reason to keep it around.
- The "`keg_only_reason` shouldn't contain 'HOMEBREW_PREFIX'" audit can easily
  be replaced with a RuboCop since that's "just" text parsing.
- The "tests should invoke binaries with `bin/<command>`" audit had to stay as
  a FormulaAudit because it requires accessing attributes about the Formula
  like its name, aliases, which RuboCop can't get to, but it was easy to move the
  singular "read the text in the file" line from `FormulaTextAuditor`.
2024-05-04 22:14:55 +01:00

1297 lines
41 KiB
Ruby

# frozen_string_literal: true
require "formula_auditor"
RSpec.describe Homebrew::FormulaAuditor do
include FileUtils
let(:dir) { mktmpdir }
let(:foo_version) do
@count ||= 0
@count += 1
end
let(:formula_subpath) { "Formula/foo#{foo_version}.rb" }
let(:origin_tap_path) { Tap::TAP_DIRECTORY/"homebrew/homebrew-foo" }
let(:origin_formula_path) { origin_tap_path/formula_subpath }
let(:tap_path) { Tap::TAP_DIRECTORY/"homebrew/homebrew-bar" }
let(:formula_path) { tap_path/formula_subpath }
def formula_auditor(name, text, options = {})
path = Pathname.new "#{dir}/#{name}.rb"
path.open("w") do |f|
f.write text
end
formula = Formulary.factory(path)
if options.key? :tap_audit_exceptions
tap = Tap.fetch("test/tap")
allow(tap).to receive(:audit_exceptions).and_return(options[:tap_audit_exceptions])
allow(formula).to receive(:tap).and_return(tap)
options.delete :tap_audit_exceptions
end
described_class.new(formula, options)
end
def formula_gsub(before, after = "")
text = formula_path.read
text.gsub! before, after
formula_path.unlink
formula_path.write text
end
def formula_gsub_origin_commit(before, after = "")
text = origin_formula_path.read
text.gsub!(before, after)
origin_formula_path.unlink
origin_formula_path.write text
origin_tap_path.cd do
system "git", "commit", "-am", "commit"
end
tap_path.cd do
system "git", "fetch"
system "git", "reset", "--hard", "origin/HEAD"
end
end
describe "#problems" do
it "is empty by default" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
expect(fa.problems).to be_empty
end
end
describe "#audit_license" do
let(:spdx_license_data) { SPDX.license_data }
let(:spdx_exception_data) { SPDX.exception_data }
let(:deprecated_spdx_id) { "GPL-1.0" }
let(:license_all_custom_id) { 'all_of: ["MIT", "zzz"]' }
let(:deprecated_spdx_exception) { "Nokia-Qt-exception-1.1" }
let(:license_any) { 'any_of: ["0BSD", "GPL-3.0-only"]' }
let(:license_any_with_plus) { 'any_of: ["0BSD+", "GPL-3.0-only"]' }
let(:license_nested_conditions) { 'any_of: ["0BSD", { all_of: ["GPL-3.0-only", "MIT"] }]' }
let(:license_any_mismatch) { 'any_of: ["0BSD", "MIT"]' }
let(:license_any_nonstandard) { 'any_of: ["0BSD", "zzz", "MIT"]' }
let(:license_any_deprecated) { 'any_of: ["0BSD", "GPL-1.0", "MIT"]' }
it "does not check if the formula is not a new formula" do
fa = formula_auditor "foo", <<~RUBY, new_formula: false
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "detects no license info" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true, core_tap: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
fa.audit_license
expect(fa.problems.first[:message]).to match "Formulae in homebrew/core must specify a license."
end
it "detects if license is not a standard spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "zzz"
end
RUBY
fa.audit_license
expect(fa.problems.first[:message]).to match <<~EOS
Formula foo contains non-standard SPDX licenses: ["zzz"].
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "detects if license is a deprecated spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true, strict: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "#{deprecated_spdx_id}"
end
RUBY
fa.audit_license
expect(fa.problems.first[:message]).to eq <<~EOS
Formula foo contains deprecated SPDX licenses: ["GPL-1.0"].
You may need to add `-only` or `-or-later` for GNU licenses (e.g. `GPL`, `LGPL`, `AGPL`, `GFDL`).
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "detects if license with AND contains a non-standard spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_all_custom_id}
end
RUBY
fa.audit_license
expect(fa.problems.first[:message]).to match <<~EOS
Formula foo contains non-standard SPDX licenses: ["zzz"].
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "detects if license array contains a non-standard spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_any_nonstandard}
end
RUBY
fa.audit_license
expect(fa.problems.first[:message]).to match <<~EOS
Formula foo contains non-standard SPDX licenses: ["zzz"].
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "detects if license array contains a deprecated spdx-id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true, strict: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_any_deprecated}
end
RUBY
fa.audit_license
expect(fa.problems.first[:message]).to eq <<~EOS
Formula foo contains deprecated SPDX licenses: ["GPL-1.0"].
You may need to add `-only` or `-or-later` for GNU licenses (e.g. `GPL`, `LGPL`, `AGPL`, `GFDL`).
For a list of valid licenses check: https://spdx.org/licenses/
EOS
end
it "verifies that a license info is a standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "0BSD"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license info with plus is a standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "0BSD+"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "allows :public_domain license" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license :public_domain
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license info with multiple licenses are standard spdx ids" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license any_of: ["0BSD", "MIT"]
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license info with exceptions are standard spdx ids" do
formula_text = <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license "Apache-2.0" => { with: "LLVM-exception" }
end
RUBY
fa = formula_auditor("foo", formula_text, new_formula: true,
spdx_license_data:, spdx_exception_data:)
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license array contains only standard spdx id" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_any}
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license array contains only standard spdx id with plus" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_any_with_plus}
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license array with AND contains only standard spdx ids" do
fa = formula_auditor "foo", <<~RUBY, spdx_license_data:, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
license #{license_nested_conditions}
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and verifies that a standard license id is the same " \
"as what is indicated on its Github repo", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0"
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data:,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and verifies that a standard license id with AND is the same " \
"as what is indicated on its Github repo", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license all_of: ["GPL-3.0-or-later", "MIT"]
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data:,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and verifies that a standard license id with WITH is the same " \
"as what is indicated on its Github repo", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0-or-later" => { with: "LLVM-exception" }
end
RUBY
fa = formula_auditor("cask", formula_text, online: true, core_tap: true, new_formula: true,
spdx_license_data:, spdx_exception_data:)
fa.audit_license
expect(fa.problems).to be_empty
end
it "verifies that a license exception has standard spdx ids", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0-or-later" => { with: "zzz" }
end
RUBY
fa = formula_auditor("cask", formula_text, core_tap: true, new_formula: true,
spdx_license_data:, spdx_exception_data:)
fa.audit_license
expect(fa.problems.first[:message]).to match <<~EOS
Formula cask contains invalid or deprecated SPDX license exceptions: ["zzz"].
For a list of valid license exceptions check:
https://spdx.org/licenses/exceptions-index.html
EOS
end
it "verifies that a license exception has non-deprecated spdx ids", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0-or-later" => { with: "#{deprecated_spdx_exception}" }
end
RUBY
fa = formula_auditor("cask", formula_text, core_tap: true, new_formula: true,
spdx_license_data:, spdx_exception_data:)
fa.audit_license
expect(fa.problems.first[:message]).to match <<~EOS
Formula cask contains invalid or deprecated SPDX license exceptions: ["#{deprecated_spdx_exception}"].
For a list of valid license exceptions check:
https://spdx.org/licenses/exceptions-index.html
EOS
end
it "checks online and verifies that a standard license id is in the same exempted license group " \
"as what is indicated on its GitHub repo", :needs_network do
fa = formula_auditor "cask", <<~RUBY, spdx_license_data:, online: true, new_formula: true
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "GPL-3.0-or-later"
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and verifies that a standard license array is in the same exempted license group " \
"as what is indicated on its GitHub repo", :needs_network do
fa = formula_auditor "cask", <<~RUBY, spdx_license_data:, online: true, new_formula: true
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license any_of: ["GPL-3.0-or-later", "MIT"]
end
RUBY
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and detects that a formula-specified license is not " \
"the same as what is indicated on its Github repository", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "0BSD"
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data:,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems.first[:message])
.to eq 'Formula license ["0BSD"] does not match GitHub license ["GPL-3.0"].'
end
it "allows a formula-specified license that differs from its GitHub " \
"repository for formulae on the mismatched license allowlist", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license "0BSD"
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data:,
online: true, core_tap: true, new_formula: true,
tap_audit_exceptions: { permitted_formula_license_mismatches: ["cask"] }
fa.audit_license
expect(fa.problems).to be_empty
end
it "checks online and detects that an array of license does not contain " \
"what is indicated on its Github repository", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license #{license_any_mismatch}
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data:,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems.first[:message]).to match "Formula license [\"0BSD\", \"MIT\"] " \
"does not match GitHub license [\"GPL-3.0\"]."
end
it "checks online and verifies that an array of license contains " \
"what is indicated on its Github repository", :needs_network do
formula_text = <<~RUBY
class Cask < Formula
url "https://github.com/cask/cask/archive/v0.8.4.tar.gz"
head "https://github.com/cask/cask.git"
license #{license_any}
end
RUBY
fa = formula_auditor "cask", formula_text, spdx_license_data:,
online: true, core_tap: true, new_formula: true
fa.audit_license
expect(fa.problems).to be_empty
end
end
describe "#audit_file" do
specify "no issue" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
end
RUBY
fa.audit_file
expect(fa.problems).to be_empty
end
end
describe "#audit_formula_name" do
specify "no issue" do
fa = formula_auditor "foo", <<~RUBY, core_tap: true, strict: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
end
RUBY
fa.audit_formula_name
expect(fa.problems).to be_empty
end
specify "uppercase formula name" do
fa = formula_auditor "Foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/Foo-1.0.tgz"
homepage "https://brew.sh"
end
RUBY
fa.audit_formula_name
expect(fa.problems.first[:message]).to match "must not contain uppercase letters"
end
end
describe "#audit_resource_name_matches_pypi_package_name_in_url" do
it "reports a problem if the resource name does not match the python package name" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
sha256 "abc123"
homepage "https://brew.sh"
resource "Something" do
url "https://files.pythonhosted.org/packages/FooSomething-1.0.0.tar.gz"
sha256 "def456"
end
end
RUBY
fa.audit_specs
expect(fa.problems.first[:message])
.to match("resource name should be `FooSomething` to match the PyPI package name")
end
end
describe "#check_service_command" do
specify "Not installed" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
service do
run []
end
end
RUBY
expect(fa.check_service_command(fa.formula)).to match nil
end
specify "No service" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
end
RUBY
mkdir_p fa.formula.prefix
expect(fa.check_service_command(fa.formula)).to match nil
end
specify "No command" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
service do
run []
end
end
RUBY
mkdir_p fa.formula.prefix
expect(fa.check_service_command(fa.formula)).to match nil
end
specify "Invalid command" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
service do
run [HOMEBREW_PREFIX/"bin/something"]
end
end
RUBY
mkdir_p fa.formula.prefix
expect(fa.check_service_command(fa.formula)).to match "Service command does not exist"
end
end
describe "#audit_github_repository" do
specify "#audit_github_repository when HOMEBREW_NO_GITHUB_API is set" do
ENV["HOMEBREW_NO_GITHUB_API"] = "1"
fa = formula_auditor "foo", <<~RUBY, strict: true, online: true
class Foo < Formula
homepage "https://github.com/example/example"
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
fa.audit_github_repository
expect(fa.problems).to be_empty
end
end
describe "#audit_github_repository_archived" do
specify "#audit_github_repository_archived when HOMEBREW_NO_GITHUB_API is set" do
fa = formula_auditor "foo", <<~RUBY, strict: true, online: true
class Foo < Formula
homepage "https://github.com/example/example"
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
fa.audit_github_repository_archived
expect(fa.problems).to be_empty
end
end
describe "#audit_gitlab_repository" do
specify "#audit_gitlab_repository for stars, forks and creation date" do
fa = formula_auditor "foo", <<~RUBY, strict: true, online: true
class Foo < Formula
homepage "https://gitlab.com/libtiff/libtiff"
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
fa.audit_gitlab_repository
expect(fa.problems).to be_empty
end
end
describe "#audit_gitlab_repository_archived" do
specify "#audit gitlab repository for archived status" do
fa = formula_auditor "foo", <<~RUBY, strict: true, online: true
class Foo < Formula
homepage "https://gitlab.com/libtiff/libtiff"
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
fa.audit_gitlab_repository_archived
expect(fa.problems).to be_empty
end
end
describe "#audit_bitbucket_repository" do
specify "#audit_bitbucket_repository for stars, forks and creation date" do
fa = formula_auditor "foo", <<~RUBY, strict: true, online: true
class Foo < Formula
homepage "https://bitbucket.com/libtiff/libtiff"
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
fa.audit_bitbucket_repository
expect(fa.problems).to be_empty
end
end
describe "#audit_specs" do
let(:livecheck_throttle) { "livecheck do\n throttle 10\n end" }
let(:versioned_head_spec_list) { { versioned_head_spec_allowlist: ["foo"] } }
it "doesn't allow to miss a checksum" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
end
RUBY
fa.audit_specs
expect(fa.problems.first[:message]).to match "Checksum is missing"
end
it "allows to miss a checksum for git strategy" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo.git", tag: "1.0", revision: "f5e00e485e7aa4c5baa20355b27e3b84a6912790"
end
RUBY
fa.audit_specs
expect(fa.problems).to be_empty
end
it "allows to miss a checksum for HEAD" do
fa = formula_auditor "foo", <<~RUBY
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
head "https://brew.sh/foo.tgz"
end
RUBY
fa.audit_specs
expect(fa.problems).to be_empty
end
it "allows versions with no throttle rate" do
fa = formula_auditor "bar", <<~RUBY, core_tap: true
class Bar < Formula
url "https://brew.sh/foo-1.0.1.tgz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
end
RUBY
fa.audit_specs
expect(fa.problems).to be_empty
end
it "allows major/minor versions with throttle rate" do
fa = formula_auditor "foo", <<~RUBY, core_tap: true
class Foo < Formula
url "https://brew.sh/foo-1.0.0.tgz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
#{livecheck_throttle}
end
RUBY
fa.audit_specs
expect(fa.problems).to be_empty
end
it "allows patch versions to be multiples of the throttle rate" do
fa = formula_auditor "foo", <<~RUBY, core_tap: true
class Foo < Formula
url "https://brew.sh/foo-1.0.10.tgz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
#{livecheck_throttle}
end
RUBY
fa.audit_specs
expect(fa.problems).to be_empty
end
it "doesn't allow patch versions that aren't multiples of the throttle rate" do
fa = formula_auditor "foo", <<~RUBY, core_tap: true
class Foo < Formula
url "https://brew.sh/foo-1.0.1.tgz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
#{livecheck_throttle}
end
RUBY
fa.audit_specs
expect(fa.problems.first[:message]).to match "should only be updated every 10 releases on multiples of 10"
end
it "allows non-versioned formulae to have a `HEAD` spec" do
fa = formula_auditor "bar", <<~RUBY, core_tap: true, tap_audit_exceptions: versioned_head_spec_list
class Bar < Formula
url "https://brew.sh/foo-1.0.tgz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
head "https://brew.sh/foo.git"
end
RUBY
fa.audit_specs
expect(fa.problems).to be_empty
end
it "doesn't allow versioned formulae to have a `HEAD` spec" do
fa = formula_auditor "bar@1", <<~RUBY, core_tap: true, tap_audit_exceptions: versioned_head_spec_list
class BarAT1 < Formula
url "https://brew.sh/foo-1.0.tgz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
head "https://brew.sh/foo.git"
end
RUBY
fa.audit_specs
expect(fa.problems.first[:message]).to match "Versioned formulae should not have a `HEAD` spec"
end
it "allows versioned formulae on the allowlist to have a `HEAD` spec" do
fa = formula_auditor "foo", <<~RUBY, core_tap: true, tap_audit_exceptions: versioned_head_spec_list
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
head "https://brew.sh/foo.git"
end
RUBY
fa.audit_specs
expect(fa.problems).to be_empty
end
end
describe "#audit_deps" do
describe "a dependency on a macOS-provided keg-only formula" do
describe "which is allowlisted" do
subject(:f_a) { fa }
let(:fa) do
formula_auditor "foo", <<~RUBY, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
depends_on "openssl"
end
RUBY
end
let(:f_openssl) do
formula do
url "https://brew.sh/openssl-1.0.tgz"
homepage "https://brew.sh"
keg_only :provided_by_macos
end
end
before do
allow(fa.formula.deps.first)
.to receive(:to_formula).and_return(f_openssl)
fa.audit_deps
end
it(:problems) { expect(f_a.problems).to be_empty }
end
describe "which is not allowlisted", :needs_macos do
subject(:f_a) { fa }
let(:fa) do
formula_auditor "foo", <<~RUBY, new_formula: true, core_tap: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
homepage "https://brew.sh"
depends_on "bc"
end
RUBY
end
let(:f_bc) do
formula do
url "https://brew.sh/bc-1.0.tgz"
homepage "https://brew.sh"
keg_only :provided_by_macos
end
end
before do
allow(fa.formula.deps.first)
.to receive(:to_formula).and_return(f_bc)
fa.audit_deps
end
it(:new_formula_problems) do
expect(f_a.new_formula_problems)
.to include(a_hash_including(message: a_string_matching(/is provided by macOS/)))
end
end
end
end
describe "#audit_stable_version" do
subject do
fa = described_class.new(Formulary.factory(formula_path), git: true)
fa.audit_stable_version
fa.problems.first&.fetch(:message)
end
before do
origin_formula_path.dirname.mkpath
origin_formula_path.write <<~RUBY
class Foo#{foo_version} < Formula
url "https://brew.sh/foo-1.0.tar.gz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
revision 2
version_scheme 1
end
RUBY
origin_tap_path.mkpath
origin_tap_path.cd do
system "git", "init"
system "git", "add", "--all"
system "git", "commit", "-m", "init"
end
tap_path.mkpath
tap_path.cd do
system "git", "clone", origin_tap_path, "."
end
end
describe "versions" do
context "when uncommitted should not decrease" do
before { formula_gsub "foo-1.0.tar.gz", "foo-0.9.tar.gz" }
it { is_expected.to match("stable version should not decrease (from 1.0 to 0.9)") }
end
context "when committed can decrease" do
before do
formula_gsub_origin_commit "revision 2"
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-0.9.tar.gz"
end
it { is_expected.to be_nil }
end
describe "can decrease with version_scheme increased" do
before do
formula_gsub "revision 2"
formula_gsub "foo-1.0.tar.gz", "foo-0.9.tar.gz"
formula_gsub "version_scheme 1", "version_scheme 2"
end
it { is_expected.to be_nil }
end
end
end
describe "#audit_revision" do
subject do
fa = described_class.new(Formulary.factory(formula_path), git: true)
fa.audit_revision
fa.problems.first&.fetch(:message)
end
before do
origin_formula_path.dirname.mkpath
origin_formula_path.write <<~RUBY
class Foo#{foo_version} < Formula
url "https://brew.sh/foo-1.0.tar.gz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
revision 2
version_scheme 1
end
RUBY
origin_tap_path.mkpath
origin_tap_path.cd do
system "git", "init"
system "git", "add", "--all"
system "git", "commit", "-m", "init"
end
tap_path.mkpath
tap_path.cd do
system "git", "clone", origin_tap_path, "."
end
end
describe "new formulae should not have a revision" do
it "doesn't allow new formulae to have a revision" do
fa = formula_auditor "foo", <<~RUBY, new_formula: true
class Foo < Formula
url "https://brew.sh/foo-1.0.tgz"
revision 1
end
RUBY
fa.audit_revision
expect(fa.new_formula_problems).to include(
a_hash_including(message: a_string_matching(/should not define a revision/)),
)
end
end
describe "revisions" do
describe "should not be removed when first committed above 0" do
it { is_expected.to be_nil }
end
describe "with the same version, should not decrease" do
before { formula_gsub_origin_commit "revision 2", "revision 1" }
it { is_expected.to match("revision should not decrease (from 2 to 1)") }
end
describe "should not be removed with the same version" do
before { formula_gsub_origin_commit "revision 2" }
it { is_expected.to match("revision should not decrease (from 2 to 0)") }
end
describe "should not decrease with the same, uncommitted version" do
before { formula_gsub "revision 2", "revision 1" }
it { is_expected.to match("revision should not decrease (from 2 to 1)") }
end
describe "should be removed with a newer version" do
before { formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz" }
it { is_expected.to match("'revision 2' should be removed") }
end
describe "should be removed with a newer local version" do
before { formula_gsub "foo-1.0.tar.gz", "foo-1.1.tar.gz" }
it { is_expected.to match("'revision 2' should be removed") }
end
describe "should not warn on an newer version revision removal" do
before do
formula_gsub_origin_commit "revision 2", ""
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
end
it { is_expected.to be_nil }
end
describe "should not warn when revision from previous version matches current revision" do
before do
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit "revision 2", "# no revision"
formula_gsub_origin_commit "# no revision", "revision 1"
formula_gsub_origin_commit "revision 1", "revision 2"
end
it { is_expected.to be_nil }
end
describe "should only increment by 1 with an uncommitted version" do
before do
formula_gsub "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub "revision 2", "revision 4"
end
it { is_expected.to match("revisions should only increment by 1") }
end
describe "should not warn on past increment by more than 1" do
before do
formula_gsub_origin_commit "revision 2", "# no revision"
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit "# no revision", "revision 3"
end
it { is_expected.to be_nil }
end
end
end
describe "#audit_version_scheme" do
subject do
fa = described_class.new(Formulary.factory(formula_path), git: true)
fa.audit_version_scheme
fa.problems.first&.fetch(:message)
end
before do
origin_formula_path.dirname.mkpath
origin_formula_path.write <<~RUBY
class Foo#{foo_version} < Formula
url "https://brew.sh/foo-1.0.tar.gz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
revision 2
version_scheme 1
end
RUBY
origin_tap_path.mkpath
origin_tap_path.cd do
system "git", "init"
system "git", "add", "--all"
system "git", "commit", "-m", "init"
end
tap_path.mkpath
tap_path.cd do
system "git", "clone", origin_tap_path, "."
end
end
describe "version_schemes" do
describe "should not decrease with the same version" do
before { formula_gsub_origin_commit "version_scheme 1" }
it { is_expected.to match("version_scheme should not decrease (from 1 to 0)") }
end
describe "should not decrease with a new version" do
before do
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit "revision 2", ""
formula_gsub_origin_commit "version_scheme 1", ""
end
it { is_expected.to match("version_scheme should not decrease (from 1 to 0)") }
end
describe "should only increment by 1" do
before do
formula_gsub_origin_commit "version_scheme 1", "# no version_scheme"
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit "revision 2", ""
formula_gsub_origin_commit "# no version_scheme", "version_scheme 3"
end
it { is_expected.to match("version_schemes should only increment by 1") }
end
end
end
describe "#audit_unconfirmed_checksum_change" do
subject do
fa = described_class.new(Formulary.factory(formula_path), git: true)
fa.audit_unconfirmed_checksum_change
fa.problems.first&.fetch(:message)
end
before do
origin_formula_path.dirname.mkpath
origin_formula_path.write <<~RUBY
class Foo#{foo_version} < Formula
url "https://brew.sh/foo-1.0.tar.gz"
sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"
revision 2
version_scheme 1
end
RUBY
origin_tap_path.mkpath
origin_tap_path.cd do
system "git", "init"
system "git", "add", "--all"
system "git", "commit", "-m", "init"
end
tap_path.mkpath
tap_path.cd do
system "git", "clone", origin_tap_path, "."
end
end
describe "checksums" do
describe "should not change with the same version" do
before do
formula_gsub(
'sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"',
'sha256 "3622d2a53236ed9ca62de0616a7e80fd477a9a3f862ba09d503da188f53ca523"',
)
end
it { is_expected.to match("stable sha256 changed without the url/version also changing") }
end
describe "should not change with the same version when not the first commit" do
before do
formula_gsub_origin_commit(
'sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"',
'sha256 "3622d2a53236ed9ca62de0616a7e80fd477a9a3f862ba09d503da188f53ca523"',
)
formula_gsub_origin_commit "revision 2"
formula_gsub_origin_commit "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub(
'sha256 "3622d2a53236ed9ca62de0616a7e80fd477a9a3f862ba09d503da188f53ca523"',
'sha256 "e048c5e6144f5932d8672c2fade81d9073d5b3ca1517b84df006de3d25414fc1"',
)
end
it { is_expected.to match("stable sha256 changed without the url/version also changing") }
end
describe "can change with the different version" do
before do
formula_gsub_origin_commit(
'sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"',
'sha256 "3622d2a53236ed9ca62de0616a7e80fd477a9a3f862ba09d503da188f53ca523"',
)
formula_gsub "foo-1.0.tar.gz", "foo-1.1.tar.gz"
formula_gsub_origin_commit(
'sha256 "3622d2a53236ed9ca62de0616a7e80fd477a9a3f862ba09d503da188f53ca523"',
'sha256 "e048c5e6144f5932d8672c2fade81d9073d5b3ca1517b84df006de3d25414fc1"',
)
end
it { is_expected.to be_nil }
end
describe "can be removed when switching schemes" do
before do
formula_gsub_origin_commit(
'url "https://brew.sh/foo-1.0.tar.gz"',
'url "https://foo.com/brew/bar.git", tag: "1.0", revision: "f5e00e485e7aa4c5baa20355b27e3b84a6912790"',
)
formula_gsub_origin_commit('sha256 "31cccfc6630528db1c8e3a06f6decf2a370060b982841cfab2b8677400a5092e"',
"")
end
it { is_expected.to be_nil }
end
end
end
describe "#audit_versioned_keg_only" do
specify "it warns when a versioned formula is not `keg_only`" do
fa = formula_auditor "foo@1.1", <<~RUBY, core_tap: true
class FooAT11 < Formula
url "https://brew.sh/foo-1.1.tgz"
end
RUBY
fa.audit_versioned_keg_only
expect(fa.problems.first[:message])
.to match("Versioned formulae in homebrew/core should use `keg_only :versioned_formula`")
end
specify "it warns when a versioned formula has an incorrect `keg_only` reason" do
fa = formula_auditor "foo@1.1", <<~RUBY, core_tap: true
class FooAT11 < Formula
url "https://brew.sh/foo-1.1.tgz"
keg_only :provided_by_macos
end
RUBY
fa.audit_versioned_keg_only
expect(fa.problems.first[:message])
.to match("Versioned formulae in homebrew/core should use `keg_only :versioned_formula`")
end
specify "it does not warn when a versioned formula has `keg_only :versioned_formula`" do
fa = formula_auditor "foo@1.1", <<~RUBY, core_tap: true
class FooAT11 < Formula
url "https://brew.sh/foo-1.1.tgz"
keg_only :versioned_formula
end
RUBY
fa.audit_versioned_keg_only
expect(fa.problems).to be_empty
end
end
describe "#audit_conflicts" do
before do
# We don't really test the formula text retrieval here
allow(File).to receive(:open).and_return("")
end
specify "it warns when conflicting with non-existing formula" do
foo = formula("foo") do
url "https://brew.sh/bar-1.0.tgz"
conflicts_with "bar"
end
fa = described_class.new foo
fa.audit_conflicts
expect(fa.problems.first[:message])
.to match("Can't find conflicting formula \"bar\"")
end
specify "it warns when conflicting with itself" do
foo = formula("foo") do
url "https://brew.sh/bar-1.0.tgz"
conflicts_with "foo"
end
stub_formula_loader foo
fa = described_class.new foo
fa.audit_conflicts
expect(fa.problems.first[:message])
.to match("Formula should not conflict with itself")
end
specify "it warns when another formula does not have a symmetric conflict" do
stub_formula_loader formula("gcc") { url "gcc-1.0" }
stub_formula_loader formula("glibc") { url "glibc-1.0" }
foo = formula("foo") do
url "https://brew.sh/foo-1.0.tgz"
end
stub_formula_loader foo
bar = formula("bar") do
url "https://brew.sh/bar-1.0.tgz"
conflicts_with "foo"
end
fa = described_class.new bar
fa.audit_conflicts
expect(fa.problems.first[:message])
.to match("Formula foo should also have a conflict declared with bar")
end
end
end