diff --git a/lib/net/imap/sequence_set.rb b/lib/net/imap/sequence_set.rb index a5dabfc7..27248480 100644 --- a/lib/net/imap/sequence_set.rb +++ b/lib/net/imap/sequence_set.rb @@ -256,6 +256,20 @@ class IMAP # - #disjoint?: # Returns whether +self+ and a given object have no common elements. # + # Comparison to a number: + # - #all_above?: + # Returns whether every number in +self+ is greater than a given number. + # - #all_below?: + # Returns whether every number in +self+ is less than a given number. + # - #any_above?: + # Returns whether +self+ contains any numbers greater than a given number. + # - #any_below?: + # Returns whether +self+ contains any numbers less than a given number. + # - #none_above?: + # Returns whether +self+ contains no numbers greater than a given number. + # - #none_below?: + # Returns whether +self+ contains no numbers less than a given number. + # # === Methods for Querying # These methods do not modify +self+. # @@ -345,7 +359,7 @@ class IMAP # - #above: Return a copy of +self+ which only contains numbers above a # given number. # - #below: Return a copy of +self+ which only contains numbers below a - # given value. + # given number. # - #limit: Returns a copy of +self+ which has replaced * with a # given maximum value and removed all members over that maximum. # @@ -774,6 +788,225 @@ def disjoint?(other) empty? || import_runs(other).none? { intersect_run? _1 } end + # :call-seq: + # all_above?(number) -> true or false + # + # Returns whether the set only contains numbers greater than +number+ (not + # inclusive of +number+). + # + # Net::IMAP::SequenceSet["5:8"].all_above?(9) => true + # Net::IMAP::SequenceSet["5:8"].all_above?(7) => false + # Net::IMAP::SequenceSet["5:8"].all_above?(5) => false + # + # "*" is evaluated as +UINT32_MAX+: + # Net::IMAP::SequenceSet["*"].all_above?(UINT32_MAX - 1) => true + # Net::IMAP::SequenceSet["*"].all_above?(UINT32_MAX) => false + # + # This is roughly equivalent to several other comparisons: + # + # # Given the following assumptions: + # set.empty? => false + # set.include_star? => false + # number => ^(1...UINT32_MAX) + # + # # Then these expressions will always return the same result: + # set.all_above?(number) => result + # set.none_below?(number + 1) => ^result + # !set.any_below?(number + 1) => ^result + # + # set == set.above(number) => ^result + # set.disjoint?(..number) => ^result + # number < set.min => ^result + # + # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing], + # #above + def all_above?(number) + number = nz_number(number) + empty? || number < min(star: UINT32_MAX) + end + + # :call-seq: + # all_below?(number) -> true or false + # + # Returns whether the set only contains numbers less than +number+ (not + # inclusive of +number+). + # + # Net::IMAP::SequenceSet["5:8"].all_below?(9) => true + # Net::IMAP::SequenceSet["5:8"].all_below?(8) => false + # Net::IMAP::SequenceSet["5:8"].all_below?(5) => false + # + # "*" is evaluated as +UINT32_MAX+: + # Net::IMAP::SequenceSet["*"].all_below?(UINT32_MAX) => false + # + # This is roughly equivalent to several other comparisons: + # + # # Given the following assumptions: + # set.empty? => false + # set.include_star? => false + # number => ^(2..UINT32_MAX) + # + # # Then these expressions will always return the same result: + # set.all_below?(number) => result + # set.none_above?(number - 1) => ^result + # !set.any_above?(number - 1) => ^result + # + # set == set.below(number) => ^result + # set.disjoint?(number..) => ^result + # set.max < number => ^result + # + # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing], + # #below + def all_below?(number) + number = nz_number(number) + empty? || max(star: UINT32_MAX) < number + end + + # :call-seq: + # any_above?(number) -> true or false + # + # Returns whether the set contains any numbers greater than +number+ (not + # inclusive of +number+). + # + # Net::IMAP::SequenceSet["5:8"].any_above?(1) => true + # Net::IMAP::SequenceSet["5:8"].any_above?(7) => true + # Net::IMAP::SequenceSet["5:8"].any_above?(8) => false + # + # "*" is evaluated as +UINT32_MAX+: + # Net::IMAP::SequenceSet["1:*"].any_above?(UINT32_MAX - 1) => true + # Net::IMAP::SequenceSet["1:*"].any_above?(UINT32_MAX) => false + # + # This is roughly equivalent to several other comparisons: + # + # # Given the following assumptions: + # set.empty? => false + # set.include_star? => false + # number => ^(1...UINT32_MAX) + # + # # Then these expressions will always return the same result: + # set.any_above?(number) => result + # !set.none_above?(number) => ^result + # !set.all_below?(number + 1) => ^result + # + # set.cover?(set.above(number)) => ^result + # set.intersect?(number + 1..) => ^result + # number < set.max => ^result + # + # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing], + # #above + def any_above?(number) + number = nz_number(number) + valid? && number < max(star: UINT32_MAX) + end + + # :call-seq: + # any_below?(number) -> true or false + # + # Returns whether the set contains any numbers less than +number+ (not + # inclusive of +number+). + # + # Net::IMAP::SequenceSet["5:8"].any_below? 5 => false + # Net::IMAP::SequenceSet["5:8"].any_below? 6 => true + # Net::IMAP::SequenceSet["5:8"].any_below? 9 => true + # + # "*" is evaluated as +UINT32_MAX+: + # Net::IMAP::SequenceSet["*"].any_below?(UINT32_MAX) => false + # + # This is roughly equivalent to several other comparisons: + # + # # Given the following assumptions: + # set.empty? => false + # set.include_star? => false + # number => ^(2..UINT32_MAX) + # + # # Then these expressions will always return the same result: + # set.any_below?(number) => result + # !set.none_below?(number) => ^result + # !set.all_above?(number - 1) => ^result + # + # set.cover?(set.below(number)) => ^result + # set.intersect?(...number) => ^result + # set.min < number => ^result + # + # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing], + # #below + def any_below?(number) + number = nz_number(number) + valid? && min(star: UINT32_MAX) < number + end + + # :call-seq: + # none_above?(number) -> true or false + # + # Returns whether the set contains no numbers greater than +number+ (not + # inclusive of +number+). + # + # Net::IMAP::SequenceSet["5:8"].none_above?(9) => true + # Net::IMAP::SequenceSet["5:8"].none_above?(8) => true + # Net::IMAP::SequenceSet["5:8"].none_above?(7) => false + # + # "*" is evaluated as +UINT32_MAX+: + # Net::IMAP::SequenceSet["*"].none_above?(UINT32_MAX - 1) => false + # Net::IMAP::SequenceSet["*"].none_above?(UINT32_MAX) => true + # + # This is roughly equivalent to several other comparisons: + # + # # Given the following assumptions: + # set.empty? => false + # set.include_star? => false + # number => ^(1...UINT32_MAX) + # + # # Then these expressions will always return the same result: + # set.none_above?(number) => result + # !set.any_above?(number) => ^result + # set.all_below?(number + 1) => ^result + # + # set.above(number).empty? => ^result + # set.disjoint?(number..) => ^result + # set.max <= number => ^result + # + # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing], + # #above + def none_above?(number) + number = nz_number(number) + empty? || max(star: UINT32_MAX) <= number + end + + # :call-seq: + # none_below?(number) -> true or false + # + # Returns whether the set contains no numbers less than +number+ (not + # inclusive of +number+). + # + # Net::IMAP::SequenceSet["5:8"].none_below?(4) => true + # Net::IMAP::SequenceSet["5:8"].none_below?(5) => true + # Net::IMAP::SequenceSet["5:8"].none_below?(6) => false + # + # "*" is evaluated as +UINT32_MAX+: + # Net::IMAP::SequenceSet["*"].none_below?(UINT32_MAX) => true + # + # This is roughly equivalent to several other comparisons: + # + # # Given the following assumptions: + # set.empty? => false + # set.include_star? => false + # number => ^(2..UINT32_MAX) + # + # # Then these expressions will always return the same result: + # set.none_below?(number) => result + # !set.any_below?(number) => ^result + # set.all_above?(number - 1) => ^result + # + # set.above(number).empty? => ^result + # set.disjoint?(...number) => ^result + # number <= set.min => ^result + # + # Related: {other comparison methods}[rdoc-ref:SequenceSet@Methods+for+Comparing], + # #below + def none_below?(number) + number = nz_number(number) + empty? || number <= min(star: UINT32_MAX) + end + # :call-seq: # max(star: :*) => integer or star or nil # max(count) => SequenceSet @@ -1560,7 +1793,9 @@ def [](index, length = nil) # Net::IMAP::SequenceSet["5,10:22,50"] & (21..) # to_s => "21:22,50" # Net::IMAP::SequenceSet["5,10:22,50"] - (..20) # to_s => "21:22,50" # - # Related: #above, #-, #& + # Related: #below, + # {other set operations}[rdoc-ref:SequenceSet@Methods+for+Set+Operations], + # #all_above?, #any_above?, #none_above? def above(num) NumValidator.valid_nz_number?(num) or raise ArgumentError, "not a valid sequence set number" @@ -1591,7 +1826,9 @@ def above(num) # Net::IMAP::SequenceSet["5,10:22,*"].below(30) # to_s => "5,10:22" # Net::IMAP::SequenceSet["5,10:22,*"].limit(max: 29) # to_s => "5,10:22,29" # - # Related: #above, #-, #&, #limit + # Related: #above, + # {other set operations}[rdoc-ref:SequenceSet@Methods+for+Set+Operations], + # #all_below?, #any_below?, #none_below? def below(num) NumValidator.valid_nz_number?(num) or raise ArgumentError, "not a valid sequence set number" diff --git a/test/net/imap/test_sequence_set.rb b/test/net/imap/test_sequence_set.rb index fd6efa12..f216bb8b 100644 --- a/test/net/imap/test_sequence_set.rb +++ b/test/net/imap/test_sequence_set.rb @@ -142,6 +142,25 @@ def compare_to_reference_set(nums, set, seqset) end end + test "fuzz test: above/below predicates" do + 10.times do + max = 100 + size = max / 3 + set = SequenceSet[Array.new(size) { rand(1..max) }] + + 1.upto(max + 2).each.each_cons(3) do |number, next_number| + all_above = set.all_above?(number) + + assert_equal all_above, set.none_below?(next_number) + assert_equal all_above, !set.any_below?(next_number) + + none_above = set.none_above?(number) + assert_equal none_above, !set.any_above?(number) + assert_equal none_above, set.all_below?(next_number) + end + end + end + test "#== equality by value (not by identity or representation)" do assert_equal SequenceSet.new, SequenceSet.new assert_equal SequenceSet.new("1"), SequenceSet[1] @@ -746,6 +765,164 @@ def obj.to_sequence_set; 192_168.001_255 end refute set.disjoint? set end + test "#all_above?" do + set = SequenceSet.empty + assert_equal true, set.all_above?(1) + assert_equal true, set.all_above?("1") + assert_equal true, set.all_above?(2**32 - 1) + set = SequenceSet.full + assert_equal false, set.all_above?(1) + assert_equal false, set.all_above?("1") + assert_equal false, set.all_above?(2**32 - 2) + assert_equal false, set.all_above?(2**32 - 1) + set = SequenceSet["*"] + assert_equal true, set.all_above?(2**32 - 2) + assert_equal false, set.all_above?(2**32 - 1) + set = SequenceSet["4:5"] + assert_equal true, set.all_above?(1) + assert_equal true, set.all_above?(3) + assert_equal false, set.all_above?(4) + assert_equal false, set.all_above?(6) + assert_equal false, set.all_above?(2**32 - 1) + assert_raise(DataFormatError) do set.all_above?(0) end + assert_raise(DataFormatError) do set.all_above?(-1) end + assert_raise(DataFormatError) do set.all_above?(2**32) end + assert_raise(DataFormatError) do set.all_above?("*") end + assert_raise(DataFormatError) do set.all_above?(:*) end + end + + test "#all_below?" do + set = SequenceSet.empty + assert_equal true, set.all_below?(1) + assert_equal true, set.all_below?("1") + assert_equal true, set.all_below?(2**32 - 1) + set = SequenceSet.full + assert_equal false, set.all_below?(1) + assert_equal false, set.all_below?("1") + assert_equal false, set.all_below?(2**32 - 2) + assert_equal false, set.all_below?(2**32 - 1) + set = SequenceSet["*"] + assert_equal false, set.all_below?(2**32 - 2) + assert_equal false, set.all_below?(2**32 - 1) + set = SequenceSet["4:5"] + assert_equal true, set.all_below?(2**32 - 1) + assert_equal true, set.all_below?(6) + assert_equal false, set.all_below?(5) + assert_equal false, set.all_below?(1) + assert_raise(DataFormatError) do set.all_below?(0) end + assert_raise(DataFormatError) do set.all_below?(-1) end + assert_raise(DataFormatError) do set.all_below?(2**32) end + assert_raise(DataFormatError) do set.all_below?("*") end + assert_raise(DataFormatError) do set.all_below?(:*) end + end + + test "#any_above?" do + set = SequenceSet.empty + assert_equal false, set.any_above?(1) + assert_equal false, set.any_above?("1") + assert_equal false, set.any_above?(2**32 - 1) + set = SequenceSet.full + assert_equal true, set.any_above?(1) + assert_equal true, set.any_above?("1") + assert_equal true, set.any_above?(2**32 - 2) + assert_equal false, set.any_above?(2**32 - 1) + set = SequenceSet["*"] + assert_equal true, set.any_above?(2**32 - 2) + assert_equal false, set.any_above?(2**32 - 1) + set = SequenceSet["4:5,*"] + assert_equal true, set.any_above?(3) + assert_equal true, set.any_above?(4) + assert_equal true, set.any_above?(5) + assert_equal true, set.any_above?(6) + assert_equal false, set.any_above?(2**32 - 1) + set = SequenceSet["4:5"] + assert_equal true, set.any_above?(3) + assert_equal true, set.any_above?(4) + assert_equal false, set.any_above?(5) + assert_equal false, set.any_above?(6) + assert_equal false, set.any_above?(2**32 - 1) + assert_raise(DataFormatError) do set.any_above?(0) end + assert_raise(DataFormatError) do set.any_above?(-1) end + assert_raise(DataFormatError) do set.any_above?(2**32) end + assert_raise(DataFormatError) do set.any_above?("*") end + assert_raise(DataFormatError) do set.any_above?(:*) end + end + + test "#any_below?" do + set = SequenceSet.empty + assert_equal false, set.any_below?(1) + assert_equal false, set.any_below?("1") + assert_equal false, set.any_below?(2**32 - 1) + set = SequenceSet.full + assert_equal false, set.any_below?(1) + assert_equal true, set.any_below?(2) + assert_equal true, set.any_below?("2") + assert_equal true, set.any_below?(2**32 - 1) + set = SequenceSet["*"] + assert_equal false, set.any_below?(2**32 - 1) + set = SequenceSet["3:5"] + assert_equal false, set.any_below?(3) + assert_equal true, set.any_below?(4) + assert_equal true, set.any_below?(5) + assert_equal true, set.any_below?(6) + assert_raise(DataFormatError) do set.any_below?(0) end + assert_raise(DataFormatError) do set.any_below?(-1) end + assert_raise(DataFormatError) do set.any_below?(2**32) end + assert_raise(DataFormatError) do set.any_below?("*") end + assert_raise(DataFormatError) do set.any_below?(:*) end + end + + test "#none_above?" do + set = SequenceSet.empty + assert_equal true, set.none_above?(1) + assert_equal true, set.none_above?("1") + assert_equal true, set.none_above?(2**32 - 1) + set = SequenceSet.full + assert_equal false, set.none_above?(1) + assert_equal false, set.none_above?("1") + assert_equal false, set.none_above?(2**32 - 2) + assert_equal true, set.none_above?(2**32 - 1) + set = SequenceSet["*"] + assert_equal false, set.none_above?(2**32 - 2) + assert_equal true, set.none_above?(2**32 - 1) + set = SequenceSet["4:5"] + assert_equal false, set.none_above?(1) + assert_equal false, set.none_above?(3) + assert_equal true, set.none_above?(5) + assert_equal true, set.none_above?(6) + assert_equal true, set.none_above?(2**32 - 1) + assert_raise(DataFormatError) do set.none_above?(0) end + assert_raise(DataFormatError) do set.none_above?(-1) end + assert_raise(DataFormatError) do set.none_above?(2**32) end + assert_raise(DataFormatError) do set.none_above?("*") end + assert_raise(DataFormatError) do set.none_above?(:*) end + end + + test "#none_below?" do + set = SequenceSet.empty + assert_equal true, set.none_below?(1) + assert_equal true, set.none_below?("1") + assert_equal true, set.none_below?(2**32 - 1) + set = SequenceSet.full + assert_equal true, set.none_below?(1) + assert_equal true, set.none_below?("1") + assert_equal false, set.none_below?(2**32 - 2) + assert_equal false, set.none_below?(2**32 - 1) + set = SequenceSet["*"] + assert_equal true, set.none_below?(2**32 - 2) + assert_equal true, set.none_below?(2**32 - 1) + set = SequenceSet["4:5"] + assert_equal false, set.none_below?(2**32 - 1) + assert_equal false, set.none_below?(5) + assert_equal true, set.none_below?(4) + assert_equal true, set.none_below?(3) + assert_raise(DataFormatError) do set.none_below?(0) end + assert_raise(DataFormatError) do set.none_below?(-1) end + assert_raise(DataFormatError) do set.none_below?(2**32) end + assert_raise(DataFormatError) do set.none_below?("*") end + assert_raise(DataFormatError) do set.none_below?(:*) end + end + test "#delete" do seqset = -> { SequenceSet.new _1 } assert_equal seqset["1,5"], seqset["1,5"].delete("9")