diff --git a/Library/Homebrew/livecheck/strategy/sparkle.rb b/Library/Homebrew/livecheck/strategy/sparkle.rb index 6b50d97510..7cf8cd9acf 100644 --- a/Library/Homebrew/livecheck/strategy/sparkle.rb +++ b/Library/Homebrew/livecheck/strategy/sparkle.rb @@ -124,7 +124,7 @@ module Homebrew bundle_version = BundleVersion.new(short_version, version) if short_version || version - next if os && os != "osx" + next if os && !((os == "osx") || (os == "macos")) if (minimum_system_version = item.elements["minimumSystemVersion"]&.text&.gsub(/\A\D+|\D+\z/, "")) macos_minimum_system_version = begin diff --git a/Library/Homebrew/test/livecheck/strategy/sparkle_spec.rb b/Library/Homebrew/test/livecheck/strategy/sparkle_spec.rb index 74c533af14..f70f5937b3 100644 --- a/Library/Homebrew/test/livecheck/strategy/sparkle_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy/sparkle_spec.rb @@ -7,60 +7,8 @@ require "bundle_version" describe Homebrew::Livecheck::Strategy::Sparkle do subject(:sparkle) { described_class } - let(:appcast_url) { "https://www.example.com/example/appcast.xml" } - let(:non_http_url) { "ftp://brew.sh/" } - - let(:item_hash) { - [ - { - title: "Version 1.2.3", - pub_date: "Fri, 01 Jan 2021 01:23:45 +0000", - url: "https://www.example.com/example/example-1.2.3.tar.gz", - short_version: "1.2.3", - version: "123", - }, - { - title: "Version 1.2.2", - pub_date: "Not a parseable date string", - url: "https://www.example.com/example/example-1.2.2.tar.gz", - short_version: "1.2.2", - version: "122", - }, - ] - } - - let(:xml) { - first_item = <<~EOS - - #{item_hash[0][:title]} - 10.10 - https://www.example.com/example/#{item_hash[0][:short_version]}.html - #{item_hash[0][:pub_date]} - - - EOS - - second_item = <<~EOS - - #{item_hash[1][:title]} - 10.10 - https://www.example.com/example/#{item_hash[1][:short_version]}.html - #{item_hash[1][:pub_date]} - #{item_hash[1][:version]} - #{item_hash[1][:short_version]} - #{item_hash[1][:url]} - - EOS - - items_to_omit = <<~EOS - #{first_item.sub(%r{<(enclosure[^>]+?)\s*?/>}, '<\1 os="not-osx" />')} - #{first_item.sub(/()[^<]+?)[^<]+? - - EOS - - appcast = <<~EOS + def create_appcast_xml(items_str = "") + <<~EOS @@ -68,30 +16,132 @@ describe Homebrew::Livecheck::Strategy::Sparkle do #{appcast_url} Most recent changes with links to updates. en - #{first_item} - #{second_item} + #{items_str} EOS + end - omitted_items = appcast.sub("", "\n#{items_to_omit}") + let(:appcast_url) { "https://www.example.com/example/appcast.xml" } + let(:non_http_url) { "ftp://brew.sh/" } + + # The `item_hashes` data is used to create test appcast XML and expected + # `Sparkle::Item` objects. + let(:item_hashes) { + { + v123: { + title: "Version 1.2.3", + pub_date: "Fri, 01 Jan 2021 01:23:45 +0000", + url: "https://www.example.com/example/example-1.2.3.tar.gz", + short_version: "1.2.3", + version: "123", + }, + v122: { + title: "Version 1.2.2", + pub_date: "Not a parseable date string", + url: "https://www.example.com/example/example-1.2.2.tar.gz", + short_version: "1.2.2", + version: "122", + }, + v121: { + title: "Version 1.2.1", + pub_date: "Thu, 31 Dec 2020 01:23:45 +0000", + url: "https://www.example.com/example/example-1.2.1.tar.gz", + short_version: "1.2.1", + version: "121", + }, + v120: { + title: "Version 1.2.0", + pub_date: "Wed, 30 Dec 2020 01:23:45 +0000", + url: "https://www.example.com/example/example-1.2.0.tar.gz", + short_version: "1.2.0", + version: "120", + }, + } + } + + let(:xml) { + v123_item = <<~EOS + + #{item_hashes[:v123][:title]} + 10.10 + https://www.example.com/example/#{item_hashes[:v123][:short_version]}.html + #{item_hashes[:v123][:pub_date]} + + + EOS + + v122_item = <<~EOS + + #{item_hashes[:v122][:title]} + 10.10 + https://www.example.com/example/#{item_hashes[:v122][:short_version]}.html + #{item_hashes[:v122][:pub_date]} + #{item_hashes[:v122][:version]} + #{item_hashes[:v122][:short_version]} + #{item_hashes[:v122][:url]} + + EOS + + v121_item_with_osx_os = <<~EOS + + #{item_hashes[:v121][:title]} + 10.10 + https://www.example.com/example/#{item_hashes[:v121][:short_version]}.html + #{item_hashes[:v121][:pub_date]} + + + EOS + + v120_item_with_macos_os = <<~EOS + + #{item_hashes[:v120][:title]} + 10.10 + https://www.example.com/example/#{item_hashes[:v120][:short_version]}.html + #{item_hashes[:v120][:pub_date]} + + + EOS + + # This main `appcast` data is intended as a relatively normal example. + # As such, it also serves as a base for some other test data. + appcast = create_appcast_xml <<~EOS + #{v123_item} + #{v122_item} + #{v121_item_with_osx_os} + #{v120_item_with_macos_os} + EOS + + omitted_items = create_appcast_xml <<~EOS + #{v123_item.sub(%r{<(enclosure[^>]+?)\s*?/>}, '<\1 os="not-osx-or-macos" />')} + #{v123_item.sub(/()[^<]+? + + EOS + + # Set the first item in a copy of `appcast` to the "beta" channel, to test + # filtering items by channel using a `strategy` block. beta_channel_item = appcast.sub( - first_item, - first_item.sub( - "", "\nbeta", ), ) - no_versions_item = - appcast - .sub(second_item, "") - .gsub(/sparkle:(shortVersionString|version)="[^"]+?"\s*/, "") - .sub( - "#{item_hash[0][:title]}", - "Version", - ) - no_items = appcast.sub(%r{.+}m, "") - undefined_namespace = appcast.sub(/\s*xmlns:sparkle="[^"]+?"/, "") + + no_versions_item = create_appcast_xml <<~EOS + + Version + 10.10 + https://www.example.com/example/#{item_hashes[:v123][:short_version]}.html + #{item_hashes[:v123][:pub_date]} + + + EOS + + no_items = create_appcast_xml + + undefined_namespace = appcast.sub(/\s*xmlns:sparkle="[^"]+"/, "") { appcast: appcast, @@ -106,36 +156,74 @@ describe Homebrew::Livecheck::Strategy::Sparkle do let(:title_regex) { /Version\s+v?(\d+(?:\.\d+)+)\s*$/i } let(:items) { - items = { - appcast: [ - Homebrew::Livecheck::Strategy::Sparkle::Item.new( - title: item_hash[0][:title], - pub_date: Time.parse(item_hash[0][:pub_date]), - url: item_hash[0][:url], - bundle_version: Homebrew::BundleVersion.new(item_hash[0][:short_version], item_hash[0][:version]), - ), - Homebrew::Livecheck::Strategy::Sparkle::Item.new( - title: item_hash[1][:title], - pub_date: Time.new(0), - url: item_hash[1][:url], - bundle_version: Homebrew::BundleVersion.new(item_hash[1][:short_version], item_hash[1][:version]), - ), + { + v123: Homebrew::Livecheck::Strategy::Sparkle::Item.new( + title: item_hashes[:v123][:title], + pub_date: Time.parse(item_hashes[:v123][:pub_date]), + url: item_hashes[:v123][:url], + bundle_version: Homebrew::BundleVersion.new(item_hashes[:v123][:short_version], + item_hashes[:v123][:version]), + ), + v122: Homebrew::Livecheck::Strategy::Sparkle::Item.new( + title: item_hashes[:v122][:title], + # `#items_from_content` falls back to a default `pub_date` when + # one isn't provided or can't be successfully parsed. + pub_date: Time.new(0), + url: item_hashes[:v122][:url], + bundle_version: Homebrew::BundleVersion.new(item_hashes[:v122][:short_version], + item_hashes[:v122][:version]), + ), + v121: Homebrew::Livecheck::Strategy::Sparkle::Item.new( + title: item_hashes[:v121][:title], + pub_date: Time.parse(item_hashes[:v121][:pub_date]), + url: item_hashes[:v121][:url], + bundle_version: Homebrew::BundleVersion.new(item_hashes[:v121][:short_version], + item_hashes[:v121][:version]), + ), + v120: Homebrew::Livecheck::Strategy::Sparkle::Item.new( + title: item_hashes[:v120][:title], + pub_date: Time.parse(item_hashes[:v120][:pub_date]), + url: item_hashes[:v120][:url], + bundle_version: Homebrew::BundleVersion.new(item_hashes[:v120][:short_version], + item_hashes[:v120][:version]), + ), + } + } + + let(:item_arrays) { + item_arrays = { + appcast: [ + items[:v123], + items[:v122], + items[:v121], + items[:v120], + ], + appcast_sorted: [ + items[:v123], + items[:v121], + items[:v120], + items[:v122], ], } - beta_channel_item = items[:appcast][0].clone + beta_channel_item = items[:v123].clone beta_channel_item.channel = "beta" - items[:beta_channel_item] = [beta_channel_item, items[:appcast][1].clone] + item_arrays[:beta_channel_item] = [ + beta_channel_item, + items[:v122], + items[:v121], + items[:v120], + ] - no_versions_item = items[:appcast][0].clone + no_versions_item = items[:v123].clone no_versions_item.title = "Version" no_versions_item.bundle_version = nil - items[:no_versions_item] = [no_versions_item] + item_arrays[:no_versions_item] = [no_versions_item] - items + item_arrays } - let(:versions) { [items[:appcast][0].nice_version] } + let(:versions) { [items[:v123].nice_version] } describe "::match?" do it "returns true for an HTTP URL" do @@ -149,31 +237,34 @@ describe Homebrew::Livecheck::Strategy::Sparkle do describe "::items_from_content" do let(:items_from_appcast) { sparkle.items_from_content(xml[:appcast]) } - let(:first_item) { items_from_appcast[0] } it "returns nil if content is blank" do expect(sparkle.items_from_content("")).to eq([]) end it "returns an array of Items when given XML data" do - expect(items_from_appcast).to eq(items[:appcast]) - expect(first_item.title).to eq(item_hash[0][:title]) - expect(first_item.pub_date).to eq(Time.parse(item_hash[0][:pub_date])) - expect(first_item.url).to eq(item_hash[0][:url]) - expect(first_item.short_version).to eq(item_hash[0][:short_version]) - expect(first_item.version).to eq(item_hash[0][:version]) + expect(items_from_appcast).to eq(item_arrays[:appcast]) + expect(items_from_appcast[0].title).to eq(item_hashes[:v123][:title]) + expect(items_from_appcast[0].pub_date).to eq(Time.parse(item_hashes[:v123][:pub_date])) + expect(items_from_appcast[0].url).to eq(item_hashes[:v123][:url]) + expect(items_from_appcast[0].short_version).to eq(item_hashes[:v123][:short_version]) + expect(items_from_appcast[0].version).to eq(item_hashes[:v123][:version]) - expect(sparkle.items_from_content(xml[:beta_channel_item])).to eq(items[:beta_channel_item]) - expect(sparkle.items_from_content(xml[:no_versions_item])).to eq(items[:no_versions_item]) + expect(sparkle.items_from_content(xml[:beta_channel_item])).to eq(item_arrays[:beta_channel_item]) + expect(sparkle.items_from_content(xml[:no_versions_item])).to eq(item_arrays[:no_versions_item]) end end + # `#versions_from_content` sorts items by `pub_date` and `bundle_version`, so + # these tests have to account for this behavior in the expected output. + # For example, the version 122 item doesn't have a parseable `pub_date` and + # the substituted default will cause it to be sorted last. describe "::versions_from_content" do - let(:subbed_items) { items[:appcast].map { |item| item.nice_version.sub("1", "0") } } + let(:subbed_items) { item_arrays[:appcast_sorted].map { |item| item.nice_version.sub("1", "0") } } it "returns an array of version strings when given content" do expect(sparkle.versions_from_content(xml[:appcast])).to eq(versions) - expect(sparkle.versions_from_content(xml[:omitted_items])).to eq(versions) + expect(sparkle.versions_from_content(xml[:omitted_items])).to eq([]) expect(sparkle.versions_from_content(xml[:beta_channel_item])).to eq(versions) expect(sparkle.versions_from_content(xml[:no_versions_item])).to eq([]) expect(sparkle.versions_from_content(xml[:undefined_namespace])).to eq(versions) @@ -202,7 +293,7 @@ describe Homebrew::Livecheck::Strategy::Sparkle do sparkle.versions_from_content(xml[:beta_channel_item]) do |items| items.find { |item| item.channel.nil? }&.nice_version end, - ).to eq([items[:appcast][1].nice_version]) + ).to eq([items[:v121].nice_version]) end it "returns an array of version strings when given content, a regex, and a block" do @@ -211,7 +302,7 @@ describe Homebrew::Livecheck::Strategy::Sparkle do sparkle.versions_from_content(xml[:appcast], title_regex) do |item, regex| item.title[regex, 1] end, - ).to eq([item_hash[0][:short_version]]) + ).to eq([item_hashes[:v123][:short_version]]) expect( sparkle.versions_from_content(xml[:appcast], title_regex) do |items, regex| @@ -222,24 +313,24 @@ describe Homebrew::Livecheck::Strategy::Sparkle do "#{match[1]},#{item.version}" end, - ).to eq(["#{item_hash[0][:short_version]},#{item_hash[0][:version]}"]) + ).to eq(["#{item_hashes[:v123][:short_version]},#{item_hashes[:v123][:version]}"]) # Returning an array of strings from the block expect( sparkle.versions_from_content(xml[:appcast], title_regex) do |item, regex| [item.title[regex, 1]] end, - ).to eq([item_hash[0][:short_version]]) + ).to eq([item_hashes[:v123][:short_version]]) expect( sparkle.versions_from_content(xml[:appcast], &:short_version), - ).to eq([item_hash[0][:short_version]]) + ).to eq([item_hashes[:v123][:short_version]]) expect( sparkle.versions_from_content(xml[:appcast], title_regex) do |items, regex| items.map { |item| item.title[regex, 1] } end, - ).to eq(items[:appcast].map(&:short_version)) + ).to eq(item_arrays[:appcast_sorted].map(&:short_version)) end it "allows a nil return from a block" do