diff --git a/History.md b/History.md index 9576a40..f3d57cd 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,30 @@ # Changelog +## NEXT / YYYY-MM-DD + +- 1 deprecation: + + - Deprecated `MIME::Type#priority_compare`. In a future release, this will be + will be renamed to `MIME::Type#<=>`. This method is used in tight loops, so + there is no warning message for either `MIME::Type#priority_compare` or + `MIME::Type#<=>`. + +- 1 enhancement: + + - Improved the performance of sorting by eliminating the complex comparison + flow from `MIME::Type#priority_compare`. The old version shows under 600 + i/s, and the new version shows over 900 i/s. In sorting the full set of MIME + data, there are three differences between the old and new versions; after + comparison, these differences are considered acceptable. + +- 1 bug fix: + + - Simplified the default compare implementation (`MIME::Type#<=>`) to use the + new `MIME::Type#priority_compare` operation and simplify the fallback to + `String` comparison. This _may_ result in exceptions where there had been + none, as explicit support for several special values (which should have + caused errors in any case) have been removed. + ## 3.6.0 / 2024-10-02 - 2 deprecations: @@ -123,7 +148,7 @@ there are some validation changes and updated code with formatting. ## 3.3 / 2019-09-04 -- 1 minor enhancement +- 1 minor enhancement: - Jean Boussier reduced memory usage for Ruby versions 2.3 or higher by interning various string values in each type. This is done with a diff --git a/lib/mime/type.rb b/lib/mime/type.rb index 983b64c..259af18 100644 --- a/lib/mime/type.rb +++ b/lib/mime/type.rb @@ -136,7 +136,9 @@ def to_s def initialize(content_type) # :yields: self @friendly = {} @obsolete = @registered = @provisional = false - @preferred_extension = @docs = @use_instead = nil + @preferred_extension = @docs = @use_instead = @__sort_priority = nil + __extension_priorities + self.extensions = [] case content_type @@ -167,6 +169,8 @@ def initialize(content_type) # :yields: self self.xrefs ||= {} yield self if block_given? + + update_sort_priority end # Indicates that a MIME type is like another type. This differs from @@ -185,60 +189,28 @@ def like?(other) # simplified type (the simplified type will be used if comparing against # something that can be treated as a String with #to_s). In comparisons, this # is done against the lowercase version of the MIME::Type. + # + # Note that this implementation of #<=> is deprecated and will be changed + # in the next major version to be the same as #priority_compare. + # + # Note that MIME::Types no longer compare against nil. def <=>(other) - if other.nil? - -1 - elsif other.respond_to?(:simplified) - simplified <=> other.simplified - else - filtered = "silent" if other == :silent - filtered ||= "true" if other == true - filtered ||= other.to_s - - simplified <=> MIME::Type.simplified(filtered) - end + return priority_compare(other) if other.is_a?(MIME::Type) + simplified <=> other end - # Compares the +other+ MIME::Type based on how reliable it is before doing a - # normal <=> comparison. Used by MIME::Types#[] to sort types. The - # comparisons involved are: - # - # 1. self.simplified <=> other.simplified (ensures that we - # do not try to compare different types) - # 2. IANA-registered definitions < other definitions. - # 3. Complete definitions < incomplete definitions. - # 4. Current definitions < obsolete definitions. - # 5. Obselete with use-instead names < obsolete without. - # 6. Obsolete use-instead definitions are compared. + # Compares the +other+ MIME::Type using the simplified representation, then + # a pre-computed sort priority value. Used by MIME::Types#[] to sort types. # - # While this method is public, its use is strongly discouraged by consumers - # of mime-types. In mime-types 3, this method is likely to see substantial - # revision and simplification to ensure current registered content types sort - # before unregistered or obsolete content types. + # While this method is public, its direct use is strongly discouraged by + # consumers of mime-types. For the next major version of MIME::Types, this + # method will become #<=> and #priority_compare will be removed. def priority_compare(other) - pc = simplified <=> other.simplified - if pc.zero? || !(extensions & other.extensions).empty? - pc = - if (reg = registered?) != other.registered? - reg ? -1 : 1 # registered < unregistered - elsif (comp = complete?) != other.complete? - comp ? -1 : 1 # complete < incomplete - elsif (obs = obsolete?) != other.obsolete? - obs ? 1 : -1 # current < obsolete - elsif obs && ((ui = use_instead) != (oui = other.use_instead)) - if ui.nil? - 1 - elsif oui.nil? - -1 - else - ui <=> oui - end - else - 0 - end + if (cmp = __sort_priority <=> other.__sort_priority) == 0 + simplified <=> other.simplified + else + cmp end - - pc end # Returns +true+ if the +other+ object is a MIME::Type and the content types @@ -273,6 +245,13 @@ def hash simplified.hash end + # The computed sort priority value. This is _not_ intended to be used by most + # callers. + def __sort_priority # :nodoc: + update_sort_priority if !instance_variable_defined?(:@__sort_priority) || @__sort_priority.nil? + @__sort_priority + end + # Returns the whole MIME content-type string. # # The content type is a presentation value from the MIME type registry and @@ -327,6 +306,7 @@ def extensions ## def extensions=(value) # :nodoc: + clear_sort_priority @extensions = Set[*Array(value).flatten.compact].freeze MIME::Types.send(:reindex_extensions, self) end @@ -355,10 +335,36 @@ def preferred_extension def preferred_extension=(value) # :nodoc: if value add_extensions(value) + set_preferred_extension_priority(value) + elsif instance_variable_defined?(:@preferred_extension) + clear_extension_priority(@preferred_extension) end @preferred_extension = value end + ## + # Optional extension priorities for this MIME type. This is a map of + # extensions to relative priority values (+-20..20+) similar to +nice(1)+. + # Unless otherwise specified in the data, an explicitly set + # +preferred_extension+ is automatically given a relative priority of +-10+. + # + # :attr_reader: extension_priorities + attr_accessor :extension_priorities + + ## + # Returns the priority for the provided extension or extensions. If a priority + # is not set, the default priority is +0+. The range for priorities is + # +-20..20+, inclusive. + # + # Obsolete MIME types have a +3 penalty applied to their + # extension priority and unregistered MIME types have a +2 + # penalty to their extension priority, meaning that the highest priority an + # obsolete, unregistered MIME type can have is +-15+. The lowest priority is + # always +20. + def extension_priority(*exts) + exts.map { |ext| get_extension_priority(ext) }.min + end + ## # The encoding (+7bit+, +8bit+, quoted-printable, or +base64+) # required to transport the data of this content type safely across a @@ -408,9 +414,17 @@ def use_instead attr_writer :use_instead # Returns +true+ if the media type is obsolete. - attr_accessor :obsolete + # + # :attr_accessor: obsolete + attr_reader :obsolete alias_method :obsolete?, :obsolete + ## + def obsolete=(value) + clear_sort_priority + @obsolete = !!value + end + # The documentation for this MIME::Type. attr_accessor :docs @@ -468,11 +482,27 @@ def xref_urls end # Indicates whether the MIME type has been registered with IANA. - attr_accessor :registered + # + # :attr_accessor: registered + attr_reader :registered alias_method :registered?, :registered + ## + def registered=(value) + clear_sort_priority + @registered = !!value + end + # Indicates whether the MIME type's registration with IANA is provisional. - attr_accessor :provisional + # + # :attr_accessor: provisional + attr_reader :provisional + + ## + def provisional=(value) + clear_sort_priority + @provisional = !!value + end # Indicates whether the MIME type's registration with IANA is provisional. def provisional? @@ -555,6 +585,8 @@ def encode_with(coder) coder["registered"] = registered? coder["provisional"] = provisional? if provisional? coder["signature"] = signature? if signature? + coder["sort-priority"] = __sort_priority || 0b11111111 + coder["extension-priorities"] = __extension_priorities unless __extension_priorities.empty? coder end @@ -563,6 +595,7 @@ def encode_with(coder) # # This method should be considered a private implementation detail. def init_with(coder) + @__sort_priority = 0 self.content_type = coder["content-type"] self.docs = coder["docs"] || "" self.encoding = coder["encoding"] @@ -574,8 +607,11 @@ def init_with(coder) self.signature = coder["signature"] self.xrefs = coder["xrefs"] || {} self.use_instead = coder["use-instead"] + self.extension_priorities = coder["extension-priorities"] friendly(coder["friendly"] || {}) + + update_sort_priority end def inspect # :nodoc: @@ -629,8 +665,62 @@ def simplify_matchdata(matchdata, remove_x = false, joiner: "/") end end + def __extension_priorities # :nodoc: + @extension_priorities ||= {} + end + private + def clear_extension_priority(ext) + __extension_priorities.delete(ext) if ext + end + + def get_extension_priority(ext) + [[-20, (__extension_priorities[ext] || 0) + __priority_penalty].max, 20].min + end + + def set_preferred_extension_priority(ext) + __extension_priorities[ext] = -10 unless __extension_priorities.has_key?(ext) + end + + def clear_sort_priority + @__sort_priority = nil + end + + # Update the __sort_priority value. Lower numbers sort better, so the + # bitmapping may seem a little odd. The _best_ sort priority is 0. + # + # | bit | meaning | details | + # | --- | --------------- | --------- | + # | 7 | obsolete | 1 if true | + # | 6 | provisional | 1 if true | + # | 5 | registered | 0 if true | + # | 4 | complete | 0 if true | + # | 3 | # of extensions | see below | + # | 2 | # of extensions | see below | + # | 1 | # of extensions | see below | + # | 0 | # of extensions | see below | + # + # The # of extensions is marked as the number of extensions subtracted from + # 16, to a minimum of 0. + def update_sort_priority + extension_count = @extensions.length + obsolete = (instance_variable_defined?(:@obsolete) && @obsolete) ? 1 << 7 : 0 + provisional = (instance_variable_defined?(:@provisional) && @provisional) ? 1 << 6 : 0 + registered = (instance_variable_defined?(:@registered) && @registered) ? 0 : 1 << 5 + complete = extension_count.nonzero? ? 0 : 1 << 4 + extension_count = [0, 16 - extension_count].max + + @__sort_priority = obsolete | registered | provisional | complete | extension_count + @__priority_penalty = ((instance_variable_defined?(:@obsolete) && @obsolete) ? 3 : 0) + + ((instance_variable_defined?(:@registered) && @registered) ? 0 : 2) + end + + def __priority_penalty + update_sort_priority if @__priority_penalty.nil? + @__priority_penalty + end + def content_type=(type_string) match = MEDIA_TYPE_RE.match(type_string) fail InvalidContentType, type_string if match.nil? diff --git a/lib/mime/type/columnar.rb b/lib/mime/type/columnar.rb index 1b7c3ca..11719d8 100644 --- a/lib/mime/type/columnar.rb +++ b/lib/mime/type/columnar.rb @@ -15,8 +15,10 @@ class MIME::Type::Columnar < MIME::Type def initialize(container, content_type, extensions) # :nodoc: @container = container + @__priority_penalty = nil self.content_type = content_type - self.extensions = extensions + @extensions = Set[*Array(extensions).flatten.compact].freeze + clear_sort_priority end def self.column(*methods, file: nil) # :nodoc: @@ -39,6 +41,7 @@ def self.column(*methods, file: nil) # :nodoc: :signature?, :provisional, :provisional=, :provisional?, file: "flags" column :xrefs, :xrefs=, :xref_urls column :use_instead, :use_instead= + column :extension_priorities, :extension_priorities= def encode_with(coder) # :nodoc: @container.send(:load_friendly) @@ -48,9 +51,21 @@ def encode_with(coder) # :nodoc: @container.send(:load_use_instead) @container.send(:load_xrefs) @container.send(:load_preferred_extension) + @container.send(:load_extension_priorities) super end + def update_sort_priority + if @container.__fully_loaded? + super + else + obsolete = (@__sort_priority & (1 << 7)) != 0 + registered = (@__sort_priority & (1 << 5)) == 0 + + @__priority_penalty = (obsolete ? 3 : 0) + (registered ? 0 : 2) + end + end + class << self undef column end diff --git a/lib/mime/types.rb b/lib/mime/types.rb index 026be34..ab891f0 100644 --- a/lib/mime/types.rb +++ b/lib/mime/types.rb @@ -154,7 +154,13 @@ def type_for(filename) Array(filename).flat_map { |fn| @extension_index[fn.chomp.downcase[/\.?([^.]*?)\z/m, 1]] }.compact.inject(Set.new, :+).sort { |a, b| - a.priority_compare(b) + by_ext = a.extension_priority(*a.extensions) <=> b.extension_priority(*b.extensions) + + if by_ext.zero? + a.priority_compare(b) + else + by_ext + end } end alias_method :of, :type_for @@ -196,6 +202,10 @@ def add_type(type, quiet = false) index_extensions!(type) end + def __fully_loaded? # :nodoc: + true + end + private def add_type_variant!(mime_type) diff --git a/lib/mime/types/_columnar.rb b/lib/mime/types/_columnar.rb index 9f8c132..ab39df1 100644 --- a/lib/mime/types/_columnar.rb +++ b/lib/mime/types/_columnar.rb @@ -18,6 +18,10 @@ def self.extended(obj) # :nodoc: obj.instance_variable_set(:@__files__, Set.new) end + def __fully_loaded? # :nodoc: + @__files__.size == 10 + end + # Load the first column data file (type and extensions). def load_base_data(path) # :nodoc: @__root__ = path @@ -26,13 +30,16 @@ def load_base_data(path) # :nodoc: line = line.split content_type = line.shift extensions = line - # content_type, *extensions = line.split type = MIME::Type::Columnar.new(self, content_type, extensions) @__mime_data__ << type add(type) end + each_file_byte("spri") do |type, byte| + type.instance_variable_set(:@__sort_priority, byte) + end + self end @@ -60,6 +67,25 @@ def each_file_line(name, lookup = true) end end + def each_file_byte(name) + LOAD_MUTEX.synchronize do + next if @__files__.include?(name) + + i = -1 + + filename = File.join(@__root__, "mime.#{name}.column") + + next unless File.exist?(filename) + + IO.binread(filename).unpack("C*").each do |byte| + (type = @__mime_data__[i += 1]) || next + yield type, byte + end + + @__files__ << name + end + end + def load_encoding each_file_line("encoding") do |type, line| pool ||= {} @@ -91,7 +117,7 @@ def load_flags def load_xrefs each_file_line("xrefs") { |type, line| - type.instance_variable_set(:@xrefs, dict(line, array: true)) + type.instance_variable_set(:@xrefs, dict(line, transform: :array)) } end @@ -107,18 +133,47 @@ def load_use_instead end end - def dict(line, array: false) + def load_extension_priorities + each_file_line("extpri") do |type, line| + type.instance_variable_set(:@extension_priorities, dict(line, transform: :extension_priority)) + end + rescue + # This path preserves backwards compatibility. + end + + def dict(line, transform: nil) if line == "-" {} else line.split("|").each_with_object({}) { |l, h| k, v = l.split("^") v = nil if v.empty? - h[k] = array ? Array(v) : v + + if transform + send(:"dict_#{transform}", h, k, v) + else + h[k] = v + end } end end + def dict_extension_priority(h, k, v) + return if v.nil? + + v = v.to_i if v.is_a?(String) + v = v.trunc if v.is_a?(Float) + v = [[-20, v].max, 20].min + + return if v.zero? + + h[k] = v + end + + def dict_array(h, k, v) + h[k] = Array(v) + end + def arr(line) if line == "-" [] diff --git a/mime-types.gemspec b/mime-types.gemspec index 8dc0a21..0cb7516 100644 --- a/mime-types.gemspec +++ b/mime-types.gemspec @@ -1,4 +1,3 @@ -# -*- encoding: utf-8 -*- # stub: mime-types 3.6.0 ruby lib Gem::Specification.new do |s| @@ -6,10 +5,9 @@ Gem::Specification.new do |s| s.version = "3.6.0".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= - s.metadata = { "bug_tracker_uri" => "https://github.com/mime-types/ruby-mime-types/issues", "changelog_uri" => "https://github.com/mime-types/ruby-mime-types/blob/master/History.md", "homepage_uri" => "https://github.com/mime-types/ruby-mime-types/", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/mime-types/ruby-mime-types/" } if s.respond_to? :metadata= + s.metadata = {"bug_tracker_uri" => "https://github.com/mime-types/ruby-mime-types/issues", "changelog_uri" => "https://github.com/mime-types/ruby-mime-types/blob/master/History.md", "homepage_uri" => "https://github.com/mime-types/ruby-mime-types/", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/mime-types/ruby-mime-types/"} if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Austin Ziegler".freeze] - s.date = "2024-10-02" s.description = "The mime-types library provides a library and registry for information about\nMIME content type definitions. It can be used to determine defined filename\nextensions for MIME types, or to use filename extensions to look up the likely\nMIME type definitions.\n\nVersion 3.0 is a major release that requires Ruby 2.0 compatibility and removes\ndeprecated functions. The columnar registry format introduced in 2.6 has been\nmade the primary format; the registry data has been extracted from this library\nand put into {mime-types-data}[https://github.com/mime-types/mime-types-data].\nAdditionally, mime-types is now licensed exclusively under the MIT licence and\nthere is a code of conduct in effect. There are a number of other smaller\nchanges described in the History file.".freeze s.email = ["halostatue@gmail.com".freeze] s.extra_rdoc_files = ["Code-of-Conduct.md".freeze, "Contributing.md".freeze, "History.md".freeze, "Licence.md".freeze, "Manifest.txt".freeze, "README.rdoc".freeze] @@ -18,24 +16,21 @@ Gem::Specification.new do |s| s.licenses = ["MIT".freeze] s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.0".freeze) - s.rubygems_version = "3.5.16".freeze s.summary = "The mime-types library provides a library and registry for information about MIME content type definitions".freeze - s.specification_version = 4 - - s.add_runtime_dependency(%q.freeze, ["~> 3.2015".freeze]) - s.add_runtime_dependency(%q.freeze, [">= 0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 5.25".freeze]) - s.add_development_dependency(%q.freeze, [">= 3.0".freeze, "< 5".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.1".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.7".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.4".freeze]) - s.add_development_dependency(%q.freeze, [">= 10.0".freeze, "< 14.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) - s.add_development_dependency(%q.freeze, [">= 4.0".freeze, "< 7".freeze]) - s.add_development_dependency(%q.freeze, ["~> 0.21".freeze]) + s.add_runtime_dependency("mime-types-data".freeze, ["~> 3.2015".freeze]) + s.add_runtime_dependency("logger".freeze, [">= 0".freeze]) + s.add_development_dependency("minitest".freeze, ["~> 5.25".freeze]) + s.add_development_dependency("hoe".freeze, [">= 3.0".freeze, "< 5".freeze]) + s.add_development_dependency("hoe-doofus".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("hoe-gemspec2".freeze, ["~> 1.1".freeze]) + s.add_development_dependency("hoe-git2".freeze, ["~> 1.7".freeze]) + s.add_development_dependency("hoe-rubygems".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("minitest-autotest".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("minitest-focus".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("minitest-hooks".freeze, ["~> 1.4".freeze]) + s.add_development_dependency("rake".freeze, [">= 10.0".freeze, "< 14.0".freeze]) + s.add_development_dependency("standard".freeze, ["~> 1.0".freeze]) + s.add_development_dependency("rdoc".freeze, [">= 4.0".freeze, "< 7".freeze]) + s.add_development_dependency("simplecov".freeze, ["~> 0.21".freeze]) end diff --git a/test/test_mime_type.rb b/test/test_mime_type.rb index a2349ca..0a0666a 100644 --- a/test/test_mime_type.rb +++ b/test/test_mime_type.rb @@ -175,11 +175,6 @@ def mime_type(content_type) refute_equal text_plain, "text/html" assert_operator text_plain, :>, "text/html" end - - it "correctly compares against nil" do - refute_equal text_html, nil - assert_operator text_plain, :<, nil - end end describe "#ascii?" do @@ -326,16 +321,20 @@ def mime_type(content_type) end describe "#priority_compare" do + def priority(type) + "#{type} (#{("%08b" % type.__sort_priority).chars.join(" ")})" + end + def assert_priority_less(left, right) - assert_equal(-1, left.priority_compare(right)) + assert_equal(-1, left.priority_compare(right), "#{priority(left)} is not less than #{priority(right)}") end def assert_priority_same(left, right) - assert_equal 0, left.priority_compare(right) + assert_equal 0, left.priority_compare(right), "#{priority(left)} is not equal to #{priority(right)}" end def assert_priority_more(left, right) - assert_equal 1, left.priority_compare(right) + assert_equal 1, left.priority_compare(right), "#{priority(left)} is not more than #{priority(right)}" end def assert_priority(left, middle, right) @@ -348,19 +347,24 @@ def assert_priority(left, middle, right) let(:text_1p) { mime_type("content-type" => "text/1") } let(:text_2) { mime_type("content-type" => "text/2") } - it "sorts (1) based on the simplified type" do + it "sorts based on the simplified type when the sort priorities are the same" do assert_priority text_1, text_1p, text_2 end - it "sorts (2) based on extensions" do - text_1.extensions = ["foo", "bar"] - text_2.extensions = ["foo"] + it "sorts obsolete types higher than non-obsolete types" do + text_1.obsolete = text_1p.obsolete = false + text_1b = mime_type(text_1) { |t| t.obsolete = true } + + assert_priority_less text_1, text_1b - assert_priority_same text_1, text_2 + assert_priority text_1, text_1p, text_1b + end - text_2.registered = true + it "sorts provisional types higher than non-provisional types" do + text_1.provisional = text_1p.provisional = true + text_1b = mime_type(text_1) { |t| t.provisional = false } - assert_priority_more text_1, text_2 + assert_priority text_1, text_1p, text_1b end it "sorts (3) based on the registration state" do @@ -377,13 +381,6 @@ def assert_priority(left, middle, right) assert_priority text_1, text_1p, text_1b end - it "sorts (5) based on obsolete status" do - text_1.obsolete = text_1p.obsolete = false - text_1b = mime_type(text_1) { |t| t.obsolete = true } - - assert_priority text_1, text_1p, text_1b - end - it "sorts (5) based on the use-instead value" do text_1.obsolete = text_1p.obsolete = true text_1.use_instead = text_1p.use_instead = "abc/xyz" @@ -395,6 +392,13 @@ def assert_priority(left, middle, right) assert_priority text_1, text_1p, text_1b end + + it "sorts based on extensions (more extensions sort lower)" do + text_1.extensions = ["foo", "bar"] + text_2.extensions = ["foo"] + + assert_priority_less text_1, text_2 + end end describe "#raw_media_type" do @@ -502,10 +506,10 @@ def assert_has_keys(wanted_keys, actual, msg = nil) describe "#to_json" do let(:expected_1) { - '{"content-type":"a/b","encoding":"base64","registered":false}' + '{"content-type":"a/b","encoding":"base64","registered":false,"sort-priority":48}' } let(:expected_2) { - '{"content-type":"a/b","encoding":"base64","registered":true,"provisional":true}' + '{"content-type":"a/b","encoding":"base64","registered":true,"provisional":true,"sort-priority":80}' } it "converts to JSON when requested" do diff --git a/test/test_mime_types_class.rb b/test/test_mime_types_class.rb index c4b8a33..66122f3 100644 --- a/test/test_mime_types_class.rb +++ b/test/test_mime_types_class.rb @@ -47,7 +47,7 @@ def setup } # This is this way because of a new type ending with gzip that only # appears in some data files. - assert_equal %w[application/gzip application/x-gzip multipart/x-gzip], types + assert_equal %w[application/gzip multipart/x-gzip application/x-gzip], types assert_equal 3, types.size end @@ -86,9 +86,8 @@ def setup assert_equal %w[image/jpeg], MIME::Types.of(["foo.jpeg", "bar.jpeg"]) end - it "finds multiple extensions" do - assert_equal %w[image/jpeg text/plain], - MIME::Types.type_for(%w[foo.txt foo.jpeg]) + it "finds multiple extensions ordered by the filename list" do + assert_equal %w[text/plain image/jpeg], MIME::Types.type_for(%w[foo.txt foo.jpeg]) end it "does not find unknown extensions" do