diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index db019bda75..fbaff98c80 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -17,12 +17,12 @@ module Cask attr_predicate :appcast? - def initialize(cask, appcast: false, download: false, + def initialize(cask, appcast: false, download: false, quarantine: nil, token_conflicts: false, online: false, strict: false, new_cask: false, commit_range: nil, command: SystemCommand) @cask = cask @appcast = appcast - @download = download + @download = Download.new(cask, quarantine: quarantine) if download @online = online @strict = strict @new_cask = new_cask diff --git a/Library/Homebrew/cask/auditor.rb b/Library/Homebrew/cask/auditor.rb index 4f209120dc..7abc4bb2c6 100644 --- a/Library/Homebrew/cask/auditor.rb +++ b/Library/Homebrew/cask/auditor.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "cask/download" - module Cask class Auditor include Checkable @@ -51,28 +49,26 @@ module Cask private def audit_all_languages - saved_languages = MacOS.instance_variable_get(:@languages) - begin - language_blocks.keys.all?(&method(:audit_languages)) - ensure - MacOS.instance_variable_set(:@languages, saved_languages) - end + language_blocks.keys.all?(&method(:audit_languages)) end def audit_languages(languages) ohai "Auditing language: #{languages.map { |lang| "'#{lang}'" }.to_sentence}" - MacOS.instance_variable_set(:@languages, languages) - audit_cask_instance(CaskLoader.load(cask.sourcefile_path)) + localized_cask = CaskLoader.load(cask.sourcefile_path) + config = localized_cask.config + config.languages = languages + localized_cask.config = config + audit_cask_instance(localized_cask) end def audit_cask_instance(cask) - download = audit_download? && Download.new(cask, quarantine: quarantine?) audit = Audit.new(cask, appcast: audit_appcast?, online: audit_online?, strict: audit_strict?, new_cask: audit_new_cask?, token_conflicts: audit_token_conflicts?, - download: download, + download: audit_download?, + quarantine: quarantine?, commit_range: commit_range) audit.run! puts audit.summary diff --git a/Library/Homebrew/cask/cmd.rb b/Library/Homebrew/cask/cmd.rb index 139e063887..60abf6580a 100644 --- a/Library/Homebrew/cask/cmd.rb +++ b/Library/Homebrew/cask/cmd.rb @@ -65,8 +65,7 @@ module Cask option "--help", :help, false - # handled in OS::Mac - option "--language a,b,c", ->(*) {} + option "--language=a,b,c", ->(value) { Config.global.languages = value } # override default handling of --version option "--version", ->(*) { raise OptionParser::InvalidOption } @@ -180,8 +179,6 @@ module Cask end def process_options(*args) - exclude_regex = /^--#{Regexp.union(*Config::DEFAULT_DIRS.keys.map(&Regexp.public_method(:escape)))}=/ - non_options = [] if idx = args.index("--") @@ -189,15 +186,31 @@ module Cask args = args.first(idx) end + exclude_regex = /^--#{Regexp.union(*Config::DEFAULT_DIRS.keys.map(&Regexp.public_method(:escape)))}=/ cask_opts = Shellwords.shellsplit(ENV.fetch("HOMEBREW_CASK_OPTS", "")) .reject { |arg| arg.match?(exclude_regex) } all_args = cask_opts + args - remaining = all_args.select do |arg| - !process_arguments([arg]).empty? - rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::AmbiguousOption - true + i = 0 + remaining = [] + + while i < all_args.count + begin + arg = all_args[i] + + remaining << arg unless process_arguments([arg]).empty? + rescue OptionParser::MissingArgument + raise if i + 1 >= all_args.count + + args = all_args[i..(i + 1)] + process_arguments(args) + i += 1 + rescue OptionParser::InvalidOption + remaining << arg + end + + i += 1 end remaining + non_options diff --git a/Library/Homebrew/cask/config.rb b/Library/Homebrew/cask/config.rb index bd9bd71155..74c68bc243 100644 --- a/Library/Homebrew/cask/config.rb +++ b/Library/Homebrew/cask/config.rb @@ -2,6 +2,9 @@ require "json" +require "lazy_object" +require "locale" + require "extend/hash_validator" using HashValidator @@ -24,6 +27,12 @@ module Cask screen_saverdir: "~/Library/Screen Savers", }.freeze + def self.defaults + { + languages: LazyObject.new { MacOS.languages }, + }.merge(DEFAULT_DIRS).freeze + end + def self.global @global ||= new end @@ -69,16 +78,16 @@ module Cask attr_accessor :explicit def initialize(default: nil, env: nil, explicit: {}) - @default = self.class.canonicalize(DEFAULT_DIRS.merge(default)) if default + @default = self.class.canonicalize(self.class.defaults.merge(default)) if default @env = self.class.canonicalize(env) if env @explicit = self.class.canonicalize(explicit) - @env&.assert_valid_keys!(*DEFAULT_DIRS.keys) - @explicit.assert_valid_keys!(*DEFAULT_DIRS.keys) + @env&.assert_valid_keys!(*self.class.defaults.keys) + @explicit.assert_valid_keys!(*self.class.defaults.keys) end def default - @default ||= self.class.canonicalize(DEFAULT_DIRS) + @default ||= self.class.canonicalize(self.class.defaults) end def env @@ -86,7 +95,16 @@ module Cask Shellwords.shellsplit(ENV.fetch("HOMEBREW_CASK_OPTS", "")) .select { |arg| arg.include?("=") } .map { |arg| arg.split("=", 2) } - .map { |(flag, value)| [flag.sub(/^--/, ""), value] }, + .map do |(flag, value)| + key = flag.sub(/^--/, "") + + if key == "language" + key = "languages" + value = value.split(",") + end + + [key, value] + end, ) end @@ -98,6 +116,24 @@ module Cask @manpagedir ||= HOMEBREW_PREFIX/"share/man" end + def languages + [ + *explicit[:languages], + *env[:languages], + *default[:languages], + ].uniq.select do |lang| + # Ensure all languages are valid. + Locale.parse(lang) + true + rescue Locale::ParserError + false + end + end + + def languages=(languages) + explicit[:languages] = languages + end + DEFAULT_DIRS.each_key do |dir| define_method(dir) do explicit.fetch(dir, env.fetch(dir, default.fetch(dir))) diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb index 525f927a2f..87b83e8d3f 100644 --- a/Library/Homebrew/cask/dsl.rb +++ b/Library/Homebrew/cask/dsl.rb @@ -137,13 +137,13 @@ module Cask raise CaskInvalidError.new(cask, "No default language specified.") if @language_blocks.default.nil? - locales = MacOS.languages - .map do |language| - Locale.parse(language) - rescue Locale::ParserError - nil - end - .compact + locales = cask.config.languages + .map do |language| + Locale.parse(language) + rescue Locale::ParserError + nil + end + .compact locales.each do |locale| key = locale.detect(@language_blocks.keys) @@ -225,7 +225,7 @@ module Cask end def caskroom_path - @cask.caskroom_path + cask.caskroom_path end def staged_path diff --git a/Library/Homebrew/os/mac.rb b/Library/Homebrew/os/mac.rb index 8a854ab01f..b44a47bcc4 100644 --- a/Library/Homebrew/os/mac.rb +++ b/Library/Homebrew/os/mac.rb @@ -68,21 +68,7 @@ module OS end os_langs = os_langs.scan(/[^ \n"(),]+/) - @languages = [ - *Homebrew.args.value("language")&.split(","), - *ENV["HOMEBREW_LANGUAGES"]&.split(","), - *os_langs, - ].uniq - - # Ensure all languages are valid - @languages.select! do |lang| - Locale.parse(lang) - true - rescue Locale::ParserError - false - end - - @languages + @languages = os_langs end def language diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb index 122913370b..3ddfdcc7ea 100644 --- a/Library/Homebrew/test/cask/audit_spec.rb +++ b/Library/Homebrew/test/cask/audit_spec.rb @@ -15,16 +15,6 @@ describe Cask::Audit, :cask do end end - matcher :fail do - match(&:errors?) - end - - matcher :warn do - match do |audit| - audit.warnings? && !audit.errors? - end - end - matcher :fail_with do |error_msg| match do |audit| include_msg?(audit.errors, error_msg) @@ -764,23 +754,27 @@ describe Cask::Audit, :cask do describe "audit of downloads" do let(:cask_token) { "with-binary" } let(:cask) { Cask::CaskLoader.load(cask_token) } - let(:download) { instance_double(Cask::Download) } + let(:download_double) { instance_double(Cask::Download) } let(:verify) { class_double(Cask::Verify).as_stubbed_const } let(:error_msg) { "Download Failed" } + before do + allow(audit).to receive(:download).and_return(download_double) + end + it "when download and verification succeed it does not fail" do - expect(download).to receive(:perform) + expect(download_double).to receive(:perform) expect(verify).to receive(:all) expect(subject).not_to fail_with(/#{error_msg}/) end it "when download fails it does not fail" do - expect(download).to receive(:perform).and_raise(StandardError.new(error_msg)) + expect(download_double).to receive(:perform).and_raise(StandardError.new(error_msg)) expect(subject).to fail_with(/#{error_msg}/) end it "when verification fails it does not fail" do - expect(download).to receive(:perform) + expect(download_double).to receive(:perform) expect(verify).to receive(:all).and_raise(StandardError.new(error_msg)) expect(subject).to fail_with(/#{error_msg}/) end diff --git a/Library/Homebrew/test/cask/cmd_spec.rb b/Library/Homebrew/test/cask/cmd_spec.rb index bd6b9d9795..6e5c3be828 100644 --- a/Library/Homebrew/test/cask/cmd_spec.rb +++ b/Library/Homebrew/test/cask/cmd_spec.rb @@ -15,12 +15,6 @@ describe Cask::Cmd, :cask do ]) end - it "ignores the `--language` option, which is handled in `OS::Mac`" do - cli = described_class.new("--language=en") - expect(cli).to receive(:detect_internal_command).with(no_args) - cli.run - end - context "when given no arguments" do it "exits successfully" do expect(subject).not_to receive(:exit).with(be_nonzero) diff --git a/Library/Homebrew/test/cask/dsl_spec.rb b/Library/Homebrew/test/cask/dsl_spec.rb index 24b4a06dc2..949a1c804f 100644 --- a/Library/Homebrew/test/cask/dsl_spec.rb +++ b/Library/Homebrew/test/cask/dsl_spec.rb @@ -118,8 +118,8 @@ describe Cask::DSL, :cask do end describe "language stanza" do - it "allows multilingual casks" do - cask = lambda do + context "when language is set explicitly" do + subject(:cask) { Cask::Cask.new("cask-with-apps") do language "zh" do sha256 "abc123" @@ -133,37 +133,65 @@ describe Cask::DSL, :cask do url "https://example.org/#{language}.zip" end + } + + matcher :be_the_chinese_version do + match do |cask| + expect(cask.language).to eq("zh-CN") + expect(cask.sha256).to eq("abc123") + expect(cask.url.to_s).to eq("https://example.org/zh-CN.zip") + end end - allow(MacOS).to receive(:languages).and_return(["zh"]) - expect(cask.call.language).to eq("zh-CN") - expect(cask.call.sha256).to eq("abc123") - expect(cask.call.url.to_s).to eq("https://example.org/zh-CN.zip") + matcher :be_the_english_version do + match do |cask| + expect(cask.language).to eq("en-US") + expect(cask.sha256).to eq("xyz789") + expect(cask.url.to_s).to eq("https://example.org/en-US.zip") + end + end - allow(MacOS).to receive(:languages).and_return(["zh-XX"]) - expect(cask.call.language).to eq("zh-CN") - expect(cask.call.sha256).to eq("abc123") - expect(cask.call.url.to_s).to eq("https://example.org/zh-CN.zip") + before do + config = cask.config + config.languages = languages + cask.config = config + end - allow(MacOS).to receive(:languages).and_return(["en"]) - expect(cask.call.language).to eq("en-US") - expect(cask.call.sha256).to eq("xyz789") - expect(cask.call.url.to_s).to eq("https://example.org/en-US.zip") + context "to 'zh'" do + let(:languages) { ["zh"] } - allow(MacOS).to receive(:languages).and_return(["xx-XX"]) - expect(cask.call.language).to eq("en-US") - expect(cask.call.sha256).to eq("xyz789") - expect(cask.call.url.to_s).to eq("https://example.org/en-US.zip") + it { is_expected.to be_the_chinese_version } + end - allow(MacOS).to receive(:languages).and_return(["xx-XX", "zh", "en"]) - expect(cask.call.language).to eq("zh-CN") - expect(cask.call.sha256).to eq("abc123") - expect(cask.call.url.to_s).to eq("https://example.org/zh-CN.zip") + context "to 'zh-XX'" do + let(:languages) { ["zh-XX"] } - allow(MacOS).to receive(:languages).and_return(["xx-XX", "en-US", "zh"]) - expect(cask.call.language).to eq("en-US") - expect(cask.call.sha256).to eq("xyz789") - expect(cask.call.url.to_s).to eq("https://example.org/en-US.zip") + it { is_expected.to be_the_chinese_version } + end + + context "to 'en'" do + let(:languages) { ["en"] } + + it { is_expected.to be_the_english_version } + end + + context "to 'xx-XX'" do + let(:languages) { ["xx-XX"] } + + it { is_expected.to be_the_english_version } + end + + context "to 'xx-XX,zh,en'" do + let(:languages) { ["xx-XX", "zh", "en"] } + + it { is_expected.to be_the_chinese_version } + end + + context "to 'xx-XX,en-US,zh'" do + let(:languages) { ["xx-XX", "en-US", "zh"] } + + it { is_expected.to be_the_english_version } + end end it "returns an empty array if no languages are specified" do diff --git a/Library/Homebrew/test/cmd/readall_spec.rb b/Library/Homebrew/test/cmd/readall_spec.rb index 03f3b2ae5c..00a740c5f6 100644 --- a/Library/Homebrew/test/cmd/readall_spec.rb +++ b/Library/Homebrew/test/cmd/readall_spec.rb @@ -6,7 +6,7 @@ describe "Homebrew.readall_args" do it_behaves_like "parseable arguments" end -describe "brew readall", :integration_test do +describe "brew readall", :integration_test, timeout: 180 do it "imports all Formulae for a given Tap" do formula_file = setup_test_formula "testball" diff --git a/Library/Homebrew/test/os/mac_spec.rb b/Library/Homebrew/test/os/mac_spec.rb index dcb04a77a8..e3c1aa8cbb 100644 --- a/Library/Homebrew/test/os/mac_spec.rb +++ b/Library/Homebrew/test/os/mac_spec.rb @@ -5,10 +5,8 @@ require "os/mac" describe OS::Mac do describe "::languages" do - specify "all languages can be parsed by Locale::parse" do - subject.languages.each do |language| - expect { Locale.parse(language) }.not_to raise_error - end + it "returns a list of all languages" do + expect(subject.languages).not_to be_empty end end @@ -16,10 +14,6 @@ describe OS::Mac do it "returns the first item from #languages" do expect(subject.language).to eq(subject.languages.first) end - - it "can be parsed by Locale::parse" do - expect { Locale.parse(subject.language) }.not_to raise_error - end end describe "::sdk_path_if_needed" do diff --git a/Library/Homebrew/test/spec_helper.rb b/Library/Homebrew/test/spec_helper.rb index 337569b431..9e6ee3e826 100644 --- a/Library/Homebrew/test/spec_helper.rb +++ b/Library/Homebrew/test/spec_helper.rb @@ -29,6 +29,7 @@ require "rubocop" require "rubocop/rspec/support" require "find" require "byebug" +require "timeout" $LOAD_PATH.push(File.expand_path("#{ENV["HOMEBREW_LIBRARY"]}/Homebrew/test/support/lib")) @@ -181,7 +182,19 @@ RSpec.configure do |config| $stderr.reopen(File::NULL) end - example.run + begin + timeout = example.metadata.fetch(:timeout, 60) + inner_timeout = nil + Timeout.timeout(timeout) do + example.run + rescue Timeout::Error => e + inner_timeout = e + end + rescue Timeout::Error + raise "Example exceeded maximum runtime of #{timeout} seconds." + end + + raise inner_timeout if inner_timeout rescue SystemExit => e raise "Unexpected exit with status #{e.status}." ensure