diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 301906e5..4b1ab78f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,7 @@ jobs: experimental: [false] include: - { ruby: truffleruby, os: ubuntu-latest, experimental: true } + - { ruby: jruby-head, os: ubuntu-latest, experimental: true } runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} timeout-minutes: 15 diff --git a/lib/net/imap.rb b/lib/net/imap.rb index e9fbb3ef..20d4b30e 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -4072,6 +4072,9 @@ def self.saslprep(string, **opts) end end +# TODO: remove after TruffleRuby and JRuby bugs are fixed +require_relative "imap/data_polyfill" + require_relative "imap/errors" require_relative "imap/config" require_relative "imap/command_data" diff --git a/lib/net/imap/data_polyfill.rb b/lib/net/imap/data_polyfill.rb new file mode 100644 index 00000000..bc8e4972 --- /dev/null +++ b/lib/net/imap/data_polyfill.rb @@ -0,0 +1,244 @@ +# frozen_string_literal: true + +# Some of the code in this file was copied from the polyfill-data gem. +# +# MIT License +# +# Copyright (c) 2023 Jim Gay, Joel Drapper, Nicholas Evans +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# simplecov:disable -- Skip test coverage for this file + +unless RUBY_ENGINE in "jruby" | "truffleruby" + Net::IMAP::Data = ::Data + return # don't load the rest of the file +end +begin + module TestData + Incompatible = Class.new(StandardError) + + class Abstract < ::Data + def self.class_method = :ok + def deconstruct = [:ok, *super] + end + + class Inherits < Abstract.define(:foo) + end + + errors = [] + begin + data = Inherits.new(1) + data.foo.nil? and errors << "member attribute" + data.deconstruct in :ok, 1 or errors << "inherited #deconstruct" + Inherits.class_method rescue errors << "inherited class method" + rescue => error + errors << error.detailed_message(highlight: false, did_you_mean: false) + end + raise Incompatible, errors.join(", ") if errors.any? + end +rescue TestData::Incompatible => err + warn "Detected incompatible Data implementation: #{err}." +else + Net::IMAP::Data = ::Data + return # don't load the rest of the file +ensure + Object.module_exec do remove_const :TestData end if defined?(TestData) +end + +warn "Using Net::IMAP::Data polyfill." + +module Net + class IMAP + # DataPolyfill is a temporary substitute for ruby 3.2's +Data+ class, but it + # is only loaded by JRuby and TruffleRuby. The core +Data+ class is + # tested, and when it is sufficiently compatible with CRuby, DataPolyfill + # won't be loaded. + # + # DataPolyfill is aliased as Net::IMAP::Data, so that Net::IMAP code can use + # it as +Data+. + # + # See {ruby 3.2's documentation for Data}[https://docs.ruby-lang.org/en/3.2/Data.html]. + # + # Some of the code in this class was copied or adapted from the + # {polyfill-data gem}[https://rubygems.org/gems/polyfill-data], by Jim Gay + # and Joel Drapper, under the MIT license terms. + class DataPolyfill + singleton_class.undef_method :new + + TYPE_ERROR = "%p is not a symbol nor a string" + ATTRSET_ERROR = "invalid data member: %p" + DUP_ERROR = "duplicate member: %p" + ARITY_ERROR = "wrong number of arguments (given %d, expected %s)" + private_constant :TYPE_ERROR, :ATTRSET_ERROR, :DUP_ERROR, :ARITY_ERROR + + # Defines a new Data class. + # + # _NOTE:_ Unlike ruby 3.2's +Data.define+, DataPolyfill.define only + # supports member names which are valid local variable names. Member + # names can't be keywords (e.g: +next+ or +class+) or start with capital + # letters, "@", etc. + def self.define(*args, &block) + members = args.each_with_object({}) do |arg, members| + arg = arg.to_str unless arg in Symbol | String if arg.respond_to?(:to_str) + arg = arg.to_sym if arg in String + arg in Symbol or raise TypeError, TYPE_ERROR % [arg] + arg in %r{=} and raise ArgumentError, ATTRSET_ERROR % [arg] + members.key?(arg) and raise ArgumentError, DUP_ERROR % [arg] + members[arg] = true + end + members = members.keys.freeze + + klass = ::Class.new(self) + + klass.singleton_class.undef_method :define + klass.define_singleton_method(:members) { members } + + def klass.new(*args, **kwargs, &block) + if kwargs.size.positive? + if args.size.positive? + raise ArgumentError, ARITY_ERROR % [args.size, 0] + end + elsif members.size < args.size + expected = members.size.zero? ? 0 : 0..members.size + raise ArgumentError, ARITY_ERROR % [args.size, expected] + else + kwargs = Hash[members.take(args.size).zip(args)] + end + allocate.tap do |instance| + instance.__send__(:initialize, **kwargs, &block) + end.freeze + end + + klass.singleton_class.alias_method :[], :new + klass.attr_reader(*members) + + # Dynamically defined initializer methods are in an included module, + # rather than directly on DataPolyfill (like in ruby 3.2+): + # * simpler to handle required kwarg ArgumentErrors + # * easier to ensure consistent ivar assignment order (object shape) + # * faster than instance_variable_set + klass.include(Module.new do + if members.any? + kwargs = members.map{"#{_1.name}:"}.join(", ") + params = members.map(&:name).join(", ") + ivars = members.map{"@#{_1.name}"}.join(", ") + attrs = members.map{"attrs[:#{_1.name}]"}.join(", ") + module_eval <<~RUBY, __FILE__, __LINE__ + 1 + protected + def initialize(#{kwargs}) #{ivars} = #{params}; freeze end + def marshal_load(attrs) #{ivars} = #{attrs}; freeze end + RUBY + end + end) + + klass.module_eval do _1.module_eval(&block) end if block_given? + + klass + end + + ## + # singleton-method: new + # call-seq: + # new(*args) -> instance + # new(**kwargs) -> instance + # + # Constuctor for classes defined with ::define. + # + # Aliased as ::[]. + + ## + # singleton-method: [] + # call-seq: + # ::[](*args) -> instance + # ::[](**kwargs) -> instance + # + # Constuctor for classes defined with ::define. + # + # Alias for ::new + + ## + def members; self.class.members end + def to_h(&block) block ? __to_h__.to_h(&block) : __to_h__ end + def hash; [self.class, __to_h__].hash end + def ==(other) self.class == other.class && to_h == other.to_h end + def eql?(other) self.class == other.class && hash == other.hash end + def deconstruct; __to_h__.values end + + def deconstruct_keys(keys) + raise TypeError unless keys.is_a?(Array) || keys.nil? + return __to_h__ if keys&.first.nil? + __to_h__.slice(*keys) + end + + def with(**kwargs) + return self if kwargs.empty? + self.class.new(**__to_h__.merge(kwargs)) + end + + def inspect + __inspect_guard__(self) do |seen| + return "#" if seen + attrs = __to_h__.map {|kv| "%s=%p" % kv }.join(", ") + display = ["data", self.class.name, attrs].compact.join(" ") + "#<#{display}>" + end + end + alias_method :to_s, :inspect + + private + + def initialize_copy(source) super.freeze end + def marshal_dump; __to_h__ end + + def __to_h__; Hash[members.map {|m| [m, send(m)] }] end + + # Yields +true+ if +obj+ has been seen already, +false+ if it hasn't. + # Marks +obj+ as seen inside the block, so circuler references don't + # recursively trigger a SystemStackError (stack level too deep). + # + # Making circular references inside a Data object _should_ be very + # uncommon, but we'll support them for the sake of completeness. + def __inspect_guard__(obj) + preexisting = Thread.current[:__net_imap_data__inspect__] + Thread.current[:__net_imap_data__inspect__] ||= {}.compare_by_identity + inspect_guard = Thread.current[:__net_imap_data__inspect__] + if inspect_guard.include?(obj) + yield true + else + begin + inspect_guard[obj] = true + yield false + ensure + inspect_guard.delete(obj) + end + end + ensure + unless preexisting.equal?(inspect_guard) + Thread.current[:__net_imap_data__inspect__] = preexisting + end + end + + end + + Data = DataPolyfill + end +end + +# simplecov:enable diff --git a/lib/net/imap/errors.rb b/lib/net/imap/errors.rb index a042604e..0475e10d 100644 --- a/lib/net/imap/errors.rb +++ b/lib/net/imap/errors.rb @@ -340,7 +340,7 @@ def initialize(state, response:, command: nil) @state, @command, @response = state, command, response cmd_desc = name ? "#{state} #{name}" : state super "Received tagged #{status} to #{cmd_desc} command (tag=#{tag})" - rescue NoMatchingPatternError => err + rescue NoMatchingPatternError, NoMatchingPatternKeyError => err raise ArgumentError, err.message end diff --git a/test/lib/helper.rb b/test/lib/helper.rb index 0977b2e8..a45ed8c7 100644 --- a/test/lib/helper.rb +++ b/test/lib/helper.rb @@ -228,7 +228,9 @@ def assert_local_raise(expected, message = nil) assert_raise(expected, &block) end stack = caller - assert_equal stack, error.backtrace&.last(stack.size) + pend_if_jruby("stack traces don't match in JRuby 10.0.1.0") do + assert_equal stack, error.backtrace&.last(stack.size) + end error end diff --git a/test/net/imap/net_imap_test_helpers.rb b/test/net/imap/net_imap_test_helpers.rb index d7ecac23..01ca72c5 100644 --- a/test/net/imap/net_imap_test_helpers.rb +++ b/test/net/imap/net_imap_test_helpers.rb @@ -12,6 +12,16 @@ module TestFixtureGenerators def load_fixture_data(*test_fixture_path) dir = self::TEST_FIXTURE_PATH YAML.unsafe_load_file File.join(dir, *test_fixture_path) + rescue TypeError => err + case err.message + in /bind argument must be an instance of Data/ + raise unless defined?(Net::IMAP::DataPolyfill) + file = test_fixture_path.last + warn "⚠️ Can't load test fixture #{file}: #{err.detailed_message}" + {tests: []} + else + raise + end end def generate_tests_from(fixture_data: nil, fixture_file: nil) diff --git a/test/net/imap/test_connection_state.rb b/test/net/imap/test_connection_state.rb index ec6b11eb..8309de5d 100644 --- a/test/net/imap/test_connection_state.rb +++ b/test/net/imap/test_connection_state.rb @@ -25,25 +25,21 @@ class ConnectionStateTest < Net::IMAP::TestCase end test "#deconstruct" do - pend_if_truffleruby "TruffleRuby bug overriding ::Data methods" do - assert_equal [:not_authenticated], NotAuthenticated[].deconstruct - assert_equal [:authenticated], Authenticated[] .deconstruct - assert_equal [:selected], Selected[] .deconstruct - assert_equal [:logout], Logout[] .deconstruct - end + assert_equal [:not_authenticated], NotAuthenticated[].deconstruct + assert_equal [:authenticated], Authenticated[] .deconstruct + assert_equal [:selected], Selected[] .deconstruct + assert_equal [:logout], Logout[] .deconstruct end test "#deconstruct_keys" do - pend_if_truffleruby "TruffleRuby bug overriding ::Data methods" do - assert_equal({symbol: :not_authenticated}, NotAuthenticated[].deconstruct_keys([:symbol])) - assert_equal({symbol: :authenticated}, Authenticated[] .deconstruct_keys([:symbol])) - assert_equal({symbol: :selected}, Selected[] .deconstruct_keys([:symbol])) - assert_equal({symbol: :logout}, Logout[] .deconstruct_keys([:symbol])) - assert_equal({name: "not_authenticated"}, NotAuthenticated[].deconstruct_keys([:name])) - assert_equal({name: "authenticated"}, Authenticated[] .deconstruct_keys([:name])) - assert_equal({name: "selected"}, Selected[] .deconstruct_keys([:name])) - assert_equal({name: "logout"}, Logout[] .deconstruct_keys([:name])) - end + assert_equal({symbol: :not_authenticated}, NotAuthenticated[].deconstruct_keys([:symbol])) + assert_equal({symbol: :authenticated}, Authenticated[] .deconstruct_keys([:symbol])) + assert_equal({symbol: :selected}, Selected[] .deconstruct_keys([:symbol])) + assert_equal({symbol: :logout}, Logout[] .deconstruct_keys([:symbol])) + assert_equal({name: "not_authenticated"}, NotAuthenticated[].deconstruct_keys([:name])) + assert_equal({name: "authenticated"}, Authenticated[] .deconstruct_keys([:name])) + assert_equal({name: "selected"}, Selected[] .deconstruct_keys([:name])) + assert_equal({name: "logout"}, Logout[] .deconstruct_keys([:name])) end test "#not_authenticated?" do diff --git a/test/net/imap/test_data_polyfill.rb b/test/net/imap/test_data_polyfill.rb new file mode 100644 index 00000000..d2a0f585 --- /dev/null +++ b/test/net/imap/test_data_polyfill.rb @@ -0,0 +1,384 @@ +# frozen_string_literal: false + +require "net/imap" +require "test/unit" + +# This test file was copied from the polyfill-data gem. +# +# MIT License +# +# Copyright (c) 2023 Jim Gay +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +module Net + class IMAP + class TestData < Net::IMAP::TestCase + Data = Net::IMAP::Data + + def test_define + klass = Data.define(:foo, :bar) + assert_kind_of(Class, klass) + assert_equal(%i[foo bar], klass.members) + + assert_raise(NoMethodError) { Data.new(:foo) } + assert_raise(TypeError) { Data.define(0) } + + # Because some code is shared with Struct, check we don't share unnecessary functionality + assert_raise(TypeError) { Data.define(:foo, keyword_init: true) } + + refute_respond_to(Data.define, :define, "Cannot define from defined Data class") + end + + def test_define_edge_cases + # non-ascii + klass = Data.define(:"r\u{e9}sum\u{e9}") + o = klass.new(1) + assert_equal(1, o.send(:"r\u{e9}sum\u{e9}")) + + assert_raise(ArgumentError) { Data.define(:x=) } + assert_raise(ArgumentError, /duplicate member/) { Data.define(:x, :x) } + end + + def test_define_with_block + klass = Data.define(:a, :b) do + def c + a + b + end + end + + assert_equal(3, klass.new(1, 2).c) + end + + def test_initialize + klass = Data.define(:foo, :bar) + + # Regular + test = klass.new(1, 2) + assert_equal(1, test.foo) + assert_equal(2, test.bar) + assert_equal(test, klass.new(1, 2)) + assert_predicate(test, :frozen?) + + # Keywords + test_kw = klass.new(foo: 1, bar: 2) + assert_equal(1, test_kw.foo) + assert_equal(2, test_kw.bar) + assert_equal(test_kw, klass.new(foo: 1, bar: 2)) + assert_equal(test_kw, test) + + # Wrong protocol + assert_raise(ArgumentError) { klass.new(1) } + assert_raise(ArgumentError) { klass.new(1, 2, 3) } + assert_raise(ArgumentError) { klass.new(foo: 1) } + assert_raise(ArgumentError) { klass.new(foo: 1, bar: 2, baz: 3) } + # Could be converted to foo: 1, bar: 2, but too smart is confusing + assert_raise(ArgumentError) { klass.new(1, bar: 2) } + end + + def test_initialize_redefine + klass = Data.define(:foo, :bar) do + attr_reader :passed + + def initialize(*args, **kwargs) + @passed = [args, kwargs] + + super(foo: 1, bar: 2) # so we can experiment with passing wrong numbers of args + end + end + + assert_equal([[], {foo: 1, bar: 2}], klass.new(foo: 1, bar: 2).passed) + + # Positional arguments are converted to keyword ones + assert_equal([[], {foo: 1, bar: 2}], klass.new(1, 2).passed) + + # Missing arguments can be fixed in initialize + assert_equal([[], {foo: 1}], klass.new(foo: 1).passed) + + # Extra keyword arguments can be dropped in initialize + assert_equal([[], {foo: 1, bar: 2, baz: 3}], klass.new(foo: 1, bar: 2, baz: 3).passed) + end + + def test_instance_behavior + klass = Data.define(:foo, :bar) + + test = klass.new(1, 2) + assert_equal(1, test.foo) + assert_equal(2, test.bar) + assert_equal(%i[foo bar], test.members) + assert_equal(1, test.public_send(:foo)) + assert_equal(0, test.method(:foo).arity) + assert_equal([], test.method(:foo).parameters) + + assert_equal({foo: 1, bar: 2}, test.to_h) + assert_equal({"foo"=>"1", "bar"=>"2"}, test.to_h { [_1.to_s, _2.to_s] }) + + assert_equal({foo: 1, bar: 2}, test.deconstruct_keys(nil)) + assert_equal({foo: 1}, test.deconstruct_keys(%i[foo])) + assert_equal({foo: 1}, test.deconstruct_keys(%i[foo baz])) + assert_raise(TypeError) { test.deconstruct_keys(0) } + + test = klass.new(bar: 2, foo: 1) + assert_equal([1, 2], test.deconstruct) + + assert_kind_of(Integer, test.hash) + end + + def test_inspect + klass = Data.define(:a) + o = klass.new(1) + assert_equal("#", o.inspect) + + Object.const_set(:Foo, klass) + assert_equal("#", o.inspect) + Object.instance_eval { remove_const(:Foo) } + + klass = Data.define(:one, :two) + o = klass.new(1,2) + assert_equal("#", o.inspect) + assert_equal("#", o.to_s) + end + + def test_recursive_inspect + klass = Data.define(:value, :head, :tail) do + def initialize(value:, head: nil, tail: nil) + case tail + in Array if tail.empty? + tail = nil + in Array + succ, *rest = *tail + tail = self.class[head: self, value: succ, tail: rest] + in [tailprev, _, _] if tail.class == self.class && tailprev == self + # noop + in [tailprev, succ, rest] if tail.class == self.class + tail = self.class[head: self, value: succ, tail: rest] + in nil + else + tail = self.class[head: self, value: tail, tail: nil] + end + super(head:, value:, tail:) + end + end + + # anonymous class + list = klass[value: 1, tail: [2, 3, 4]] + seen = "#" + assert_equal( + "#>>>", + list.inspect + ) + + # named class + Object.const_set(:DoubleLinkList, klass) + list = DoubleLinkList[value: 1, tail: [2, 3, 4]] + seen = "#" + assert_equal( + "#>>>", + list.inspect + ) + ensure + Object.instance_eval { remove_const(:DoubleLinkList) } rescue nil + end + + def test_equal + klass1 = Data.define(:a) + klass2 = Data.define(:a) + o1 = klass1.new(1) + o2 = klass1.new(1) + o3 = klass2.new(1) + assert_equal(o1, o2) + refute_equal(o1, o3) + end + + def test_eql + klass1 = Data.define(:a) + klass2 = Data.define(:a) + o1 = klass1.new(1) + o2 = klass1.new(1) + o3 = klass2.new(1) + assert_operator(o1, :eql?, o2) + refute_operator(o1, :eql?, o3) + end + + def test_with + klass = Data.define(:foo, :bar) + source = klass.new(foo: 1, bar: 2) + + # Simple + test = source.with + assert_equal(source.object_id, test.object_id) + + # Changes + test = source.with(foo: 10) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(2, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 2)) + + test = source.with(foo: 10, bar: 20) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(20, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 20)) + + # Keyword splat + changes = { foo: 10, bar: 20 } + test = source.with(**changes) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(20, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 20)) + + # Wrong protocol + assert_raise(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with(10) + end + assert_raise(ArgumentError, "unknown keywords: :baz, :quux") do + source.with(foo: 1, bar: 2, baz: 3, quux: 4) + end + assert_raise(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with(1, bar: 2) + end + assert_raise(ArgumentError, "wrong number of arguments (given 2, expected 0)") do + source.with(1, 2) + end + assert_raise(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with({ bar: 2 }) + end unless RUBY_VERSION < "2.8.0" + end + + def test_memberless + klass = Data.define + + test = klass.new + + assert_equal(klass.new, test) + refute_equal(Data.define.new, test) + + assert_match(/#/, test.inspect) + assert_equal([], test.members) + assert_equal({}, test.to_h) + end + + def test_square_braces + klass = Data.define(:amount, :unit) + + distance = klass[10, 'km'] + + assert_equal(10, distance.amount) + assert_equal('km', distance.unit) + end + + def test_dup + klass = Data.define(:foo, :bar) + test = klass.new(foo: 1, bar: 2) + assert_equal(klass.new(foo: 1, bar: 2), test.dup) + assert_predicate(test.dup, :frozen?) + end + + Klass = Data.define(:foo, :bar) + + def test_marshal + test = Klass.new(foo: 1, bar: 2) + loaded = Marshal.load(Marshal.dump(test)) + assert_equal(test, loaded) + refute_same(test, loaded) + assert_predicate(loaded, :frozen?) + end + + def test_member_precedence + name_mod = Module.new do + def name + "default name" + end + + def other + "other" + end + end + + klass = Data.define(:name) do + include name_mod + end + + data = klass.new("test") + + assert_equal("test", data.name) + assert_equal("other", data.other) + end + + class Abstract < Data + end + + class Inherited < Abstract.define(:foo) + end + + def test_subclass_can_create + assert_equal 1, Inherited[1].foo + assert_equal 2, Inherited[foo: 2].foo + assert_equal 3, Inherited.new(3).foo + assert_equal 4, Inherited.new(foo: 4).foo + end + + class AbstractWithClassMethod < Data + def self.inherited_class_method; :ok end + end + + class InheritsClassMethod < AbstractWithClassMethod.define(:foo) + end + + def test_subclass_class_method + assert_equal :ok, InheritsClassMethod.inherited_class_method + end + + class AbstractWithOverride < Data + def deconstruct; [:ok, *super] end + end + + class InheritsOverride < AbstractWithOverride.define(:foo) + end + + def test_subclass_override_deconstruct + data = InheritsOverride[:foo] + assert_equal %i[ok foo], data.deconstruct + end + + end + end +end diff --git a/test/net/imap/test_errors.rb b/test/net/imap/test_errors.rb index 6a07a7fb..433cc75d 100644 --- a/test/net/imap/test_errors.rb +++ b/test/net/imap/test_errors.rb @@ -107,6 +107,7 @@ def self.SGR(*attr) = CSI attr.join(?;), ?m ENV["TERM"], ENV["NO_COLOR"], ENV["FORCE_COLOR"] = nil, nil, "0" assert_equal(expected_no_hl, err.detailed_message) + omit_if_jruby "JRuby doesn't support fully format string hash lookup" assert_equal(expected_color_hl, err.detailed_message(highlight: true)) assert_equal(expected_no_color, err.detailed_message(highlight: true, highlight_no_color: true)) diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index d005bc50..0110ed66 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -13,6 +13,7 @@ class IMAPTest < Net::IMAP::TestCase if defined?(OpenSSL::SSL::SSLError) def test_imaps_unknown_ca + omit_if_jruby "SSL tests don't work yet" assert_local_raise(OpenSSL::SSL::SSLError) do imaps_test do |port| begin @@ -27,6 +28,7 @@ def test_imaps_unknown_ca end def test_imaps_with_ca_file + omit_if_jruby "SSL tests don't work yet" # Assert verified *after* the imaps_test and assert_nothing_raised blocks. # Otherwise, failures can't logout and need to wait for the timeout. verified, imap = :unknown, nil @@ -54,6 +56,7 @@ def test_imaps_with_ca_file end def test_imaps_verify_none + omit_if_jruby "SSL tests don't work yet" # Assert verified *after* the imaps_test and assert_nothing_raised blocks. # Otherwise, failures can't logout and need to wait for the timeout. verified, imap = :unknown, nil @@ -81,6 +84,7 @@ def test_imaps_verify_none end def test_imaps_post_connection_check + omit_if_jruby "SSL tests don't work yet" assert_local_raise(OpenSSL::SSL::SSLError) do imaps_test do |port| # server_addr is different from the hostname in the certificate, @@ -95,6 +99,7 @@ def test_imaps_post_connection_check if defined?(OpenSSL::SSL) def test_starttls_unknown_ca + omit_if_jruby "SSL tests don't work yet" omit "This test is not working with Windows" if RUBY_PLATFORM =~ /mswin|mingw/ imap = nil @@ -116,6 +121,7 @@ def test_starttls_unknown_ca end def test_starttls + omit_if_jruby "SSL tests don't work yet" initial_verified, initial_ctx, initial_params = :unknown, :unknown, :unknown imap = nil starttls_test do |port| @@ -141,6 +147,7 @@ def test_starttls end def test_starttls_stripping_not_ok + omit_if_jruby "SSL tests don't work yet" imap = nil server = create_tcp_server port = server.addr[1] @@ -173,6 +180,7 @@ def test_starttls_stripping_not_ok end def test_starttls_stripping_ok_sent_before_response + omit_if_jruby "SSL tests don't work yet" # to coordinate between threads (better than sleep) server_to_client, client_to_server = Queue.new, Queue.new rcvr_to_client = Queue.new @@ -677,6 +685,7 @@ def test_connection_closed_during_idle end def test_connection_closed_without_greeting + omit_if_jruby "???" server = create_tcp_server port = server.addr[1] h = { diff --git a/test/net/imap/test_imap_capabilities.rb b/test/net/imap/test_imap_capabilities.rb index c2274798..9d605d57 100644 --- a/test/net/imap/test_imap_capabilities.rb +++ b/test/net/imap/test_imap_capabilities.rb @@ -124,6 +124,7 @@ class IMAPCapabilitiesTest < Net::IMAP::TestCase if defined?(OpenSSL::SSL::SSLError) test "#capabilities caches greeting capabilities (implicit TLS)" do + omit_if_jruby with_fake_server(preauth: false, implicit_tls: true) do |server, imap| assert imap.capabilities_cached? assert_equal %w[IMAP4REV1 AUTH=PLAIN], imap.capabilities @@ -136,6 +137,7 @@ class IMAPCapabilitiesTest < Net::IMAP::TestCase test "#capabilities cache is cleared after #starttls" do with_fake_server(preauth: false, cleartext_auth: false) do |server, imap| + omit_if_jruby assert imap.capabilities_cached? assert imap.capable? :IMAP4rev1 refute imap.auth_capable? "plain" @@ -189,6 +191,7 @@ class IMAPCapabilitiesTest < Net::IMAP::TestCase # TODO: should we warn about this? test "#capabilities cache IGNORES tagged OK response to STARTTLS" do + omit_if_jruby with_fake_server(preauth: false) do |server, imap| server.on "STARTTLS" do |cmd| cmd.done_ok code: "[CAPABILITY IMAP4rev1 AUTH=PLAIN fnord]" diff --git a/test/net/imap/test_imap_deprecated_client_options.rb b/test/net/imap/test_imap_deprecated_client_options.rb index 9ff9ad23..b02b49bd 100644 --- a/test/net/imap/test_imap_deprecated_client_options.rb +++ b/test/net/imap/test_imap_deprecated_client_options.rb @@ -42,6 +42,7 @@ class InitializeTests < IMAPDeprecatedClientOptionsTest end test "Convert deprecated usessl (= true) and certs, with warning" do + omit_if_jruby "SSL tests don't work yet" run_fake_server_in_thread(implicit_tls: true) do |server| certs = server.config.tls[:ca_file] assert_deprecated_warning(/Call Net::IMAP\.new with keyword/i) do @@ -58,6 +59,7 @@ class InitializeTests < IMAPDeprecatedClientOptionsTest end test "Convert deprecated usessl (= true) and verify (= false), with warning" do + omit_if_jruby "SSL tests don't work yet" run_fake_server_in_thread(implicit_tls: true) do |server| assert_deprecated_warning(/Call Net::IMAP\.new with keyword/i) do with_client("localhost", server.port, true, nil, false) do |client| @@ -89,6 +91,7 @@ class InitializeTests < IMAPDeprecatedClientOptionsTest class StartTLSTests < IMAPDeprecatedClientOptionsTest test "Convert obsolete options hash to keywords" do + omit_if_jruby "SSL tests don't work yet" with_fake_server(preauth: false) do |server, imap| imap.starttls(ca_file: server.config.tls[:ca_file], min_version: :TLS1_2) assert_equal( @@ -101,6 +104,7 @@ class StartTLSTests < IMAPDeprecatedClientOptionsTest end test "Convert deprecated certs, verify with warning" do + omit_if_jruby "SSL tests don't work yet" with_fake_server(preauth: false) do |server, imap| assert_deprecated_warning(/Call Net::IMAP#starttls with keyword/i) do imap.starttls(server.config.tls[:ca_file], false)