From fc31d5560cf73dc9e7f371eafc969ae91294dff2 Mon Sep 17 00:00:00 2001 From: Tim Visher Date: Sat, 22 Oct 2022 12:25:53 -0400 Subject: [PATCH] livecheck/strategy/sparkle.rb: Add `macos` to the candidate `os` values list As can be seen by TextExpander's Sparkle Feed, `macos` is a possible value. ``` $ date -u '+%FT%T%z' && xmlstarlet sel -t -v '//rss//channel//item//enclosure/@*[name()="url" or name()="sparkle:version" or name()="sparkle:shortVersionString" or name()="sparkle:os"]' <(curl --location --silent https://textexpander.com/appcast/TextExpander-macOS.xml) 2022-10-22T17:07:06+0000 https://cdn.textexpander.com/mac/731.2/TextExpander_7.3.1.dmg 731.2 7.3.1 macos https://cdn.textexpander.com/mac/720.16/TextExpander_7.2.dmg 720.16 7.2 macos https://cdn.textexpander.com/mac/710.6/TextExpander_7.1.dmg 710.6 7.1 macos https://cdn.textexpander.com/mac/702.2/TextExpander_7.0.2.dmg 702.2 7.0.2 macos https://cdn.textexpander.com/mac/701.2/TextExpander_7.0.1.dmg 701.2 7.0.1 macos https://cdn.textexpander.com/mac/700.33/TextExpander_7.0.dmg 700.33 7.0 macos https://cdn.textexpander.com/mac/685.6/TextExpander_6.8.5.zip 685.6 6.8.5 https://cdn.textexpander.com/mac/684.8/TextExpander_6.8.4.zip 684.8 6.8.4 https://cdn.textexpander.com/mac/683.2/TextExpander_6.8.3.zip 683.2 6.8.3 https://cdn.textexpander.com/mac/682.10/TextExpander_6.8.2.zip 682.10 6.8.2 https://cdn.textexpander.com/mac/681.3/TextExpander_6.8.1.zip 681.3 6.8.1 https://cdn.textexpander.com/mac/680.30/TextExpander_6.8.zip 680.30 6.8 https://cdn.textexpander.com/mac/TextExpander_6.5.6.zip 656.3 6.5.6 https://cdn.textexpander.com/mac/TextExpander_6.5.5.zip 655.0 6.5.5 https://cdn.textexpander.com/mac/TextExpander_6.5.4.zip 654.3 6.5.4 https://cdn.textexpander.com/mac/TextExpander_6.5.3.zip 653.3 6.5.3 https://cdn.textexpander.com/mac/TextExpander_6.5.2.zip 652.0 6.5.2 https://smilesoftware.com/downloads/test/TextExpander_6.5.1.zip 651.5 6.5.1 ``` Co-authored-by: Sam Ford <1584702+samford@users.noreply.github.com> --- .../Homebrew/livecheck/strategy/sparkle.rb | 2 +- .../test/livecheck/strategy/sparkle_spec.rb | 305 ++++++++++++------ 2 files changed, 199 insertions(+), 108 deletions(-) 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