diff options
Diffstat (limited to 'test')
36 files changed, 1672 insertions, 248 deletions
diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb new file mode 100644 index 0000000..ff78c2b --- /dev/null +++ b/test/lib/core_assertions.rb @@ -0,0 +1,764 @@ +# frozen_string_literal: true + +module Test + module Unit + module Assertions + def _assertions= n # :nodoc: + @_assertions = n + end + + def _assertions # :nodoc: + @_assertions ||= 0 + end + + ## + # Returns a proc that will output +msg+ along with the default message. + + def message msg = nil, ending = nil, &default + proc { + msg = msg.call.chomp(".") if Proc === msg + custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? + "#{custom_message}#{default.call}#{ending || "."}" + } + end + end + + module CoreAssertions + if defined?(MiniTest) + require_relative '../../envutil' + # for ruby core testing + include MiniTest::Assertions + + # Compatibility hack for assert_raise + Test::Unit::AssertionFailedError = MiniTest::Assertion + else + module MiniTest + class Assertion < Exception; end + class Skip < Assertion; end + end + + require 'pp' + require_relative 'envutil' + include Test::Unit::Assertions + end + + def mu_pp(obj) #:nodoc: + obj.pretty_inspect.chomp + end + + def assert_file + AssertFile + end + + FailDesc = proc do |status, message = "", out = ""| + now = Time.now + proc do + EnvUtil.failure_description(status, now, message, out) + end + end + + def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, + success: nil, **opt) + args = Array(args).dup + args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') + stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) + desc = FailDesc[status, message, stderr] + if block_given? + raise "test_stdout ignored, use block only or without block" if test_stdout != [] + raise "test_stderr ignored, use block only or without block" if test_stderr != [] + yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }, status) + else + all_assertions(desc) do |a| + [["stdout", test_stdout, stdout], ["stderr", test_stderr, stderr]].each do |key, exp, act| + a.for(key) do + if exp.is_a?(Regexp) + assert_match(exp, act) + elsif exp.all? {|e| String === e} + assert_equal(exp, act.lines.map {|l| l.chomp }) + else + assert_pattern_list(exp, act) + end + end + end + unless success.nil? + a.for("success?") do + if success + assert_predicate(status, :success?) + else + assert_not_predicate(status, :success?) + end + end + end + end + status + end + end + + if defined?(RubyVM::InstructionSequence) + def syntax_check(code, fname, line) + code = code.dup.force_encoding(Encoding::UTF_8) + RubyVM::InstructionSequence.compile(code, fname, fname, line) + :ok + ensure + raise if SyntaxError === $! + end + else + def syntax_check(code, fname, line) + code = code.b + code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { + "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" + } + code = code.force_encoding(Encoding::UTF_8) + catch {|tag| eval(code, binding, fname, line - 1)} + end + end + + def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) + # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail + pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? + + require_relative '../../memory_status' + raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status) + + token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" + token_dump = token.dump + token_re = Regexp.quote(token) + envs = args.shift if Array === args and Hash === args.first + args = [ + "--disable=gems", + "-r", File.expand_path("../../../memory_status", __FILE__), + *args, + "-v", "-", + ] + if defined? Memory::NO_MEMORY_LEAK_ENVS then + envs ||= {} + newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break } + envs = newenvs if newenvs + end + args.unshift(envs) if envs + cmd = [ + 'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}', + prepare, + 'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")', + '$initial_size = $initial_status.size', + code, + 'GC.start', + ].join("\n") + _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt) + before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1) + after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1) + assert(status.success?, FailDesc[status, message, err]) + ([:size, (rss && :rss)] & after.members).each do |n| + b = before[n] + a = after[n] + next unless a > 0 and b > 0 + assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) + end + rescue LoadError + pend + end + + # :call-seq: + # assert_nothing_raised( *args, &block ) + # + #If any exceptions are given as arguments, the assertion will + #fail if one of those exceptions are raised. Otherwise, the test fails + #if any exceptions are raised. + # + #The final argument may be a failure message. + # + # assert_nothing_raised RuntimeError do + # raise Exception #Assertion passes, Exception is not a RuntimeError + # end + # + # assert_nothing_raised do + # raise Exception #Assertion fails + # end + def assert_nothing_raised(*args) + self._assertions += 1 + if Module === args.last + msg = nil + else + msg = args.pop + end + begin + line = __LINE__; yield + rescue MiniTest::Skip + raise + rescue Exception => e + bt = e.backtrace + as = e.instance_of?(MiniTest::Assertion) + if as + ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o + bt.reject! {|ln| ans =~ ln} + end + if ((args.empty? && !as) || + args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) + msg = message(msg) { + "Exception raised:\n<#{mu_pp(e)}>\n" + + "Backtrace:\n" + + e.backtrace.map{|frame| " #{frame}"}.join("\n") + } + raise MiniTest::Assertion, msg.call, bt + else + raise + end + end + end + + def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil) + fname ||= caller_locations(2, 1)[0] + mesg ||= fname.to_s + verbose, $VERBOSE = $VERBOSE, verbose + case + when Array === fname + fname, line = *fname + when defined?(fname.path) && defined?(fname.lineno) + fname, line = fname.path, fname.lineno + else + line = 1 + end + yield(code, fname, line, message(mesg) { + if code.end_with?("\n") + "```\n#{code}```\n" + else + "```\n#{code}\n```\n""no-newline" + end + }) + ensure + $VERBOSE = verbose + end + + def assert_valid_syntax(code, *args, **opt) + prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg| + yield if defined?(yield) + assert_nothing_raised(SyntaxError, mesg) do + assert_equal(:ok, syntax_check(src, fname, line), mesg) + end + end + end + + def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) + assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) + if child_env + child_env = [child_env] + else + child_env = [] + end + out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) + assert !status.signaled?, FailDesc[status, message, out] + end + + def assert_ruby_status(args, test_stdin="", message=nil, **opt) + out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) + desc = FailDesc[status, message, out] + assert(!status.signaled?, desc) + message ||= "ruby exit status is not success:" + assert(status.success?, desc) + end + + ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") + + def separated_runner(out = nil) + include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) + out = out ? IO.new(out, 'w') : STDOUT + at_exit { + out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" + } + Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) + end + + def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) + unless file and line + loc, = caller_locations(1,1) + file ||= loc.path + line ||= loc.lineno + end + capture_stdout = true + unless /mswin|mingw/ =~ RUBY_PLATFORM + capture_stdout = false + opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit) + res_p, res_c = IO.pipe + opt[:ios] = [res_c] + end + src = <<eom +# -*- coding: #{line += __LINE__; src.encoding}; -*- +BEGIN { + require "test/unit";include Test::Unit::Assertions;require #{(__dir__ + "/core_assertions").dump};include Test::Unit::CoreAssertions + separated_runner #{res_c&.fileno} +} +#{line -= __LINE__; src} +eom + args = args.dup + args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"}) + stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt) + ensure + if res_c + res_c.close + res = res_p.read + res_p.close + else + res = stdout + end + raise if $! + abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig)) + assert(!abort, FailDesc[status, nil, stderr]) + self._assertions += res[/^assertions=(\d+)/, 1].to_i + begin + res = Marshal.load(res.unpack1("m")) + rescue => marshal_error + ignore_stderr = nil + res = nil + end + if res and !(SystemExit === res) + if bt = res.backtrace + bt.each do |l| + l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} + end + bt.concat(caller) + else + res.set_backtrace(caller) + end + raise res + end + + # really is it succeed? + unless ignore_stderr + # the body of assert_separately must not output anything to detect error + assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) + end + assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) + raise marshal_error if marshal_error + end + + # Run Ractor-related test without influencing the main test suite + def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) + return unless defined?(Ractor) + + require = "require #{require.inspect}" if require + if require_relative + dir = File.dirname(caller_locations[0,1][0].absolute_path) + full_path = File.expand_path(require_relative, dir) + require = "#{require}; require #{full_path.inspect}" + end + + assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt) + #{require} + previous_verbose = $VERBOSE + $VERBOSE = nil + Ractor.new {} # trigger initial warning + $VERBOSE = previous_verbose + #{src} + RUBY + end + + # :call-seq: + # assert_throw( tag, failure_message = nil, &block ) + # + #Fails unless the given block throws +tag+, returns the caught + #value otherwise. + # + #An optional failure message may be provided as the final argument. + # + # tag = Object.new + # assert_throw(tag, "#{tag} was not thrown!") do + # throw tag + # end + def assert_throw(tag, msg = nil) + ret = catch(tag) do + begin + yield(tag) + rescue UncaughtThrowError => e + thrown = e.tag + end + msg = message(msg) { + "Expected #{mu_pp(tag)} to have been thrown"\ + "#{%Q[, not #{thrown}] if thrown}" + } + assert(false, msg) + end + assert(true) + ret + end + + # :call-seq: + # assert_raise( *args, &block ) + # + #Tests if the given block raises an exception. Acceptable exception + #types may be given as optional arguments. If the last argument is a + #String, it will be used as the error message. + # + # assert_raise do #Fails, no Exceptions are raised + # end + # + # assert_raise NameError do + # puts x #Raises NameError, so assertion succeeds + # end + def assert_raise(*exp, &b) + case exp.last + when String, Proc + msg = exp.pop + end + + begin + yield + rescue MiniTest::Skip => e + return e if exp.include? MiniTest::Skip + raise e + rescue Exception => e + expected = exp.any? { |ex| + if ex.instance_of? Module then + e.kind_of? ex + else + e.instance_of? ex + end + } + + assert expected, proc { + flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"}) + } + + return e + ensure + unless e + exp = exp.first if exp.size == 1 + + flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) + end + end + end + + # :call-seq: + # assert_raise_with_message(exception, expected, msg = nil, &block) + # + #Tests if the given block raises an exception with the expected + #message. + # + # assert_raise_with_message(RuntimeError, "foo") do + # nil #Fails, no Exceptions are raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise ArgumentError, "foo" #Fails, different Exception is raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "bar" #Fails, RuntimeError is raised but the message differs + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "foo" #Raises RuntimeError with the message, so assertion succeeds + # end + def assert_raise_with_message(exception, expected, msg = nil, &block) + case expected + when String + assert = :assert_equal + when Regexp + assert = :assert_match + else + raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" + end + + ex = m = nil + EnvUtil.with_default_internal(expected.encoding) do + ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do + yield + end + m = ex.message + end + msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} + + if assert == :assert_equal + assert_equal(expected, m, msg) + else + msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } + assert expected =~ m, msg + block.binding.eval("proc{|_|$~=_}").call($~) + end + ex + end + + MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: + + # :call-seq: + # assert(test, [failure_message]) + # + #Tests if +test+ is true. + # + #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used + #as the failure message. Otherwise, the result of calling +msg+ will be + #used as the message if the assertion fails. + # + #If no +msg+ is given, a default message will be used. + # + # assert(false, "This was expected to be true") + def assert(test, *msgs) + case msg = msgs.first + when String, Proc + when nil + msgs.shift + else + bt = caller.reject { |s| s.start_with?(MINI_DIR) } + raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt + end unless msgs.empty? + super + end + + # :call-seq: + # assert_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object responds to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_respond_to("hello", :reverse) #Succeeds + # assert_respond_to("hello", :does_not_exist) #Fails + def assert_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" + } + return assert obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return if obj.respond_to?(meth) + end + super(obj, meth, msg) + end + + # :call-seq: + # assert_not_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object does not respond to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_not_respond_to("hello", :reverse) #Fails + # assert_not_respond_to("hello", :does_not_exist) #Succeeds + def assert_not_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" + } + return assert !obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return unless obj.respond_to?(meth) + end + refute_respond_to(obj, meth, msg) + end + + # pattern_list is an array which contains regexp and :*. + # :* means any sequence. + # + # pattern_list is anchored. + # Use [:*, regexp, :*] for non-anchored match. + def assert_pattern_list(pattern_list, actual, message=nil) + rest = actual + anchored = true + pattern_list.each_with_index {|pattern, i| + if pattern == :* + anchored = false + else + if anchored + match = /\A#{pattern}/.match(rest) + else + match = pattern.match(rest) + end + unless match + msg = message(msg) { + expect_msg = "Expected #{mu_pp pattern}\n" + if /\n[^\n]/ =~ rest + actual_mesg = +"to match\n" + rest.scan(/.*\n+/) { + actual_mesg << ' ' << $&.inspect << "+\n" + } + actual_mesg.sub!(/\+\n\z/, '') + else + actual_mesg = "to match " + mu_pp(rest) + end + actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" + expect_msg + actual_mesg + } + assert false, msg + end + rest = match.post_match + anchored = true + end + } + if anchored + assert_equal("", rest) + end + end + + def assert_warning(pat, msg = nil) + result = nil + stderr = EnvUtil.with_default_internal(pat.encoding) { + EnvUtil.verbose_warning { + result = yield + } + } + msg = message(msg) {diff pat, stderr} + assert(pat === stderr, msg) + result + end + + def assert_warn(*args) + assert_warning(*args) {$VERBOSE = false; yield} + end + + def assert_deprecated_warning(mesg = /deprecated/) + assert_warning(mesg) do + Warning[:deprecated] = true + yield + end + end + + def assert_deprecated_warn(mesg = /deprecated/) + assert_warn(mesg) do + Warning[:deprecated] = true + yield + end + end + + class << (AssertFile = Struct.new(:failure_message).new) + include CoreAssertions + def assert_file_predicate(predicate, *args) + if /\Anot_/ =~ predicate + predicate = $' + neg = " not" + end + result = File.__send__(predicate, *args) + result = !result if neg + mesg = "Expected file ".dup << args.shift.inspect + mesg << "#{neg} to be #{predicate}" + mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? + mesg << " #{failure_message}" if failure_message + assert(result, mesg) + end + alias method_missing assert_file_predicate + + def for(message) + clone.tap {|a| a.failure_message = message} + end + end + + class AllFailures + attr_reader :failures + + def initialize + @count = 0 + @failures = {} + end + + def for(key) + @count += 1 + yield + rescue Exception => e + @failures[key] = [@count, e] + end + + def foreach(*keys) + keys.each do |key| + @count += 1 + begin + yield key + rescue Exception => e + @failures[key] = [@count, e] + end + end + end + + def message + i = 0 + total = @count.to_s + fmt = "%#{total.size}d" + @failures.map {|k, (n, v)| + v = v.message + "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}" + }.join("\n") + end + + def pass? + @failures.empty? + end + end + + # threads should respond to shift method. + # Array can be used. + def assert_join_threads(threads, message = nil) + errs = [] + values = [] + while th = threads.shift + begin + values << th.value + rescue Exception + errs << [th, $!] + th = nil + end + end + values + ensure + if th&.alive? + th.raise(Timeout::Error.new) + th.join rescue errs << [th, $!] + end + if !errs.empty? + msg = "exceptions on #{errs.length} threads:\n" + + errs.map {|t, err| + "#{t.inspect}:\n" + + RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message + }.join("\n---\n") + if message + msg = "#{message}\n#{msg}" + end + raise MiniTest::Assertion, msg + end + end + + def assert_all_assertions(msg = nil) + all = AllFailures.new + yield all + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions assert_all_assertions + + def message(msg = nil, *args, &default) # :nodoc: + if Proc === msg + super(nil, *args) do + ary = [msg.call, (default.call if default)].compact.reject(&:empty?) + if 1 < ary.length + ary[0...-1] = ary[0...-1].map {|str| str.sub(/(?<!\.)\z/, '.') } + end + begin + ary.join("\n") + rescue Encoding::CompatibilityError + ary.map(&:b).join("\n") + end + end + else + super + end + end + + def diff(exp, act) + require 'pp' + q = PP.new(+"") + q.guard_inspect_key do + q.group(2, "expected: ") do + q.pp exp + end + q.text q.newline + q.group(2, "actual: ") do + q.pp act + end + q.flush + end + q.output + end + end + end +end diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb new file mode 100644 index 0000000..0391b90 --- /dev/null +++ b/test/lib/envutil.rb @@ -0,0 +1,367 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true +require "open3" +require "timeout" +require_relative "find_executable" +begin + require 'rbconfig' +rescue LoadError +end +begin + require "rbconfig/sizeof" +rescue LoadError +end + +module EnvUtil + def rubybin + if ruby = ENV["RUBY"] + return ruby + end + ruby = "ruby" + exeext = RbConfig::CONFIG["EXEEXT"] + rubyexe = (ruby + exeext if exeext and !exeext.empty?) + 3.times do + if File.exist? ruby and File.executable? ruby and !File.directory? ruby + return File.expand_path(ruby) + end + if rubyexe and File.exist? rubyexe and File.executable? rubyexe + return File.expand_path(rubyexe) + end + ruby = File.join("..", ruby) + end + if defined?(RbConfig.ruby) + RbConfig.ruby + else + "ruby" + end + end + module_function :rubybin + + LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" + + DEFAULT_SIGNALS = Signal.list + DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM + + RUBYLIB = ENV["RUBYLIB"] + + class << self + attr_accessor :timeout_scale + attr_reader :original_internal_encoding, :original_external_encoding, + :original_verbose, :original_warning + + def capture_global_values + @original_internal_encoding = Encoding.default_internal + @original_external_encoding = Encoding.default_external + @original_verbose = $VERBOSE + @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil + end + end + + def apply_timeout_scale(t) + if scale = EnvUtil.timeout_scale + t * scale + else + t + end + end + module_function :apply_timeout_scale + + def timeout(sec, klass = nil, message = nil, &blk) + return yield(sec) if sec == nil or sec.zero? + sec = apply_timeout_scale(sec) + Timeout.timeout(sec, klass, message, &blk) + end + module_function :timeout + + def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) + reprieve = apply_timeout_scale(reprieve) if reprieve + + signals = Array(signal).select do |sig| + DEFAULT_SIGNALS[sig.to_s] or + DEFAULT_SIGNALS[Signal.signame(sig)] rescue false + end + signals |= [:ABRT, :KILL] + case pgroup + when 0, true + pgroup = -pid + when nil, false + pgroup = pid + end + + lldb = true if /darwin/ =~ RUBY_PLATFORM + + while signal = signals.shift + + if lldb and [:ABRT, :KILL].include?(signal) + lldb = false + # sudo -n: --non-interactive + # lldb -p: attach + # -o: run command + system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) + true + end + + begin + Process.kill signal, pgroup + rescue Errno::EINVAL + next + rescue Errno::ESRCH + break + end + if signals.empty? or !reprieve + Process.wait(pid) + else + begin + Timeout.timeout(reprieve) {Process.wait(pid)} + rescue Timeout::Error + else + break + end + end + end + $? + end + module_function :terminate + + def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, + encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, + stdout_filter: nil, stderr_filter: nil, ios: nil, + signal: :TERM, + rubybin: EnvUtil.rubybin, precommand: nil, + **opt) + timeout = apply_timeout_scale(timeout) + + in_c, in_p = IO.pipe + out_p, out_c = IO.pipe if capture_stdout + err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout + opt[:in] = in_c + opt[:out] = out_c if capture_stdout + opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr + if encoding + out_p.set_encoding(encoding) if out_p + err_p.set_encoding(encoding) if err_p + end + ios.each {|i, o = i|opt[i] = o} if ios + + c = "C" + child_env = {} + LANG_ENVS.each {|lc| child_env[lc] = c} + if Array === args and Hash === args.first + child_env.update(args.shift) + end + if RUBYLIB and lib = child_env["RUBYLIB"] + child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) + end + child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS'] + args = [args] if args.kind_of?(String) + pid = spawn(child_env, *precommand, rubybin, *args, opt) + in_c.close + out_c&.close + out_c = nil + err_c&.close + err_c = nil + if block_given? + return yield in_p, out_p, err_p, pid + else + th_stdout = Thread.new { out_p.read } if capture_stdout + th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout + in_p.write stdin_data.to_str unless stdin_data.empty? + in_p.close + if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) + timeout_error = nil + else + status = terminate(pid, signal, opt[:pgroup], reprieve) + terminated = Time.now + end + stdout = th_stdout.value if capture_stdout + stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout + out_p.close if capture_stdout + err_p.close if capture_stderr && capture_stderr != :merge_to_stdout + status ||= Process.wait2(pid)[1] + stdout = stdout_filter.call(stdout) if stdout_filter + stderr = stderr_filter.call(stderr) if stderr_filter + if timeout_error + bt = caller_locations + msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" + msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n")) + raise timeout_error, msg, bt.map(&:to_s) + end + return stdout, stderr, status + end + ensure + [th_stdout, th_stderr].each do |th| + th.kill if th + end + [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| + io&.close + end + [th_stdout, th_stderr].each do |th| + th.join if th + end + end + module_function :invoke_ruby + + def verbose_warning + class << (stderr = "".dup) + alias write concat + def flush; end + end + stderr, $stderr = $stderr, stderr + $VERBOSE = true + yield stderr + return $stderr + ensure + stderr, $stderr = $stderr, stderr + $VERBOSE = EnvUtil.original_verbose + EnvUtil.original_warning&.each {|i, v| Warning[i] = v} + end + module_function :verbose_warning + + def default_warning + $VERBOSE = false + yield + ensure + $VERBOSE = EnvUtil.original_verbose + end + module_function :default_warning + + def suppress_warning + $VERBOSE = nil + yield + ensure + $VERBOSE = EnvUtil.original_verbose + end + module_function :suppress_warning + + def under_gc_stress(stress = true) + stress, GC.stress = GC.stress, stress + yield + ensure + GC.stress = stress + end + module_function :under_gc_stress + + def with_default_external(enc) + suppress_warning { Encoding.default_external = enc } + yield + ensure + suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } + end + module_function :with_default_external + + def with_default_internal(enc) + suppress_warning { Encoding.default_internal = enc } + yield + ensure + suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } + end + module_function :with_default_internal + + def labeled_module(name, &block) + Module.new do + singleton_class.class_eval { + define_method(:to_s) {name} + alias inspect to_s + alias name to_s + } + class_eval(&block) if block + end + end + module_function :labeled_module + + def labeled_class(name, superclass = Object, &block) + Class.new(superclass) do + singleton_class.class_eval { + define_method(:to_s) {name} + alias inspect to_s + alias name to_s + } + class_eval(&block) if block + end + end + module_function :labeled_class + + if /darwin/ =~ RUBY_PLATFORM + DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") + DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' + @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] + + def self.diagnostic_reports(signame, pid, now) + return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) + cmd = File.basename(rubybin) + cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd + path = DIAGNOSTIC_REPORTS_PATH + timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT + pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" + first = true + 30.times do + first ? (first = false) : sleep(0.1) + Dir.glob(pat) do |name| + log = File.read(name) rescue next + if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log + File.unlink(name) + File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil + return log + end + end + end + nil + end + else + def self.diagnostic_reports(signame, pid, now) + end + end + + def self.failure_description(status, now, message = "", out = "") + pid = status.pid + if signo = status.termsig + signame = Signal.signame(signo) + sigdesc = "signal #{signo}" + end + log = diagnostic_reports(signame, pid, now) + if signame + sigdesc = "SIG#{signame} (#{sigdesc})" + end + if status.coredump? + sigdesc = "#{sigdesc} (core dumped)" + end + full_message = ''.dup + message = message.call if Proc === message + if message and !message.empty? + full_message << message << "\n" + end + full_message << "pid #{pid}" + full_message << " exit #{status.exitstatus}" if status.exited? + full_message << " killed by #{sigdesc}" if sigdesc + if out and !out.empty? + full_message << "\n" << out.b.gsub(/^/, '| ') + full_message.sub!(/(?<!\n)\z/, "\n") + end + if log + full_message << "Diagnostic reports:\n" << log.b.gsub(/^/, '| ') + end + full_message + end + + def self.gc_stress_to_class? + unless defined?(@gc_stress_to_class) + _, _, status = invoke_ruby(["-e""exit GC.respond_to?(:add_stress_to_class)"]) + @gc_stress_to_class = status.success? + end + @gc_stress_to_class + end +end + +if defined?(RbConfig) + module RbConfig + @ruby = EnvUtil.rubybin + class << self + undef ruby if method_defined?(:ruby) + attr_reader :ruby + end + dir = File.dirname(ruby) + CONFIG['bindir'] = dir + end +end + +EnvUtil.capture_global_values diff --git a/test/lib/find_executable.rb b/test/lib/find_executable.rb new file mode 100644 index 0000000..89c6fb8 --- /dev/null +++ b/test/lib/find_executable.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +require "rbconfig" + +module EnvUtil + def find_executable(cmd, *args) + exts = RbConfig::CONFIG["EXECUTABLE_EXTS"].split | [RbConfig::CONFIG["EXEEXT"]] + ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| + next if path.empty? + path = File.join(path, cmd) + exts.each do |ext| + cmdline = [path + ext, *args] + begin + return cmdline if yield(IO.popen(cmdline, "r", err: [:child, :out], &:read)) + rescue + next + end + end + end + nil + end + module_function :find_executable +end diff --git a/test/lib/helper.rb b/test/lib/helper.rb new file mode 100644 index 0000000..909f8f9 --- /dev/null +++ b/test/lib/helper.rb @@ -0,0 +1,4 @@ +require "test/unit" +require_relative "core_assertions" + +Test::Unit::TestCase.include Test::Unit::CoreAssertions diff --git a/test/psych/helper.rb b/test/psych/helper.rb index 9348457..0643139 100644 --- a/test/psych/helper.rb +++ b/test/psych/helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'minitest/autorun' +require 'test/unit' require 'stringio' require 'tempfile' require 'date' @@ -7,13 +7,7 @@ require 'date' require 'psych' module Psych - superclass = if defined?(Minitest::Test) - Minitest::Test - else - MiniTest::Unit::TestCase - end - - class TestCase < superclass + class TestCase < Test::Unit::TestCase def self.suppress_warning verbose, $VERBOSE = $VERBOSE, nil yield @@ -47,24 +41,30 @@ module Psych # Convert between Psych and the object to verify correct parsing and # emitting # - def assert_to_yaml( obj, yaml ) - assert_equal( obj, Psych::load( yaml ) ) + def assert_to_yaml( obj, yaml, loader = :load ) + assert_equal( obj, Psych.send(loader, yaml) ) assert_equal( obj, Psych::parse( yaml ).transform ) - assert_equal( obj, Psych::load( obj.to_yaml ) ) + assert_equal( obj, Psych.send(loader, obj.to_yaml) ) assert_equal( obj, Psych::parse( obj.to_yaml ).transform ) - assert_equal( obj, Psych::load( + assert_equal( obj, Psych.send(loader, obj.to_yaml( :UseVersion => true, :UseHeader => true, :SortKeys => true ) )) + rescue Psych::DisallowedClass, Psych::BadAlias + assert_to_yaml obj, yaml, :unsafe_load end # # Test parser only # def assert_parse_only( obj, yaml ) - assert_equal( obj, Psych::load( yaml ) ) - assert_equal( obj, Psych::parse( yaml ).transform ) + begin + assert_equal obj, Psych::load( yaml ) + rescue Psych::DisallowedClass, Psych::BadAlias + assert_equal obj, Psych::unsafe_load( yaml ) + end + assert_equal obj, Psych::parse( yaml ).transform end def assert_cycle( obj ) @@ -75,9 +75,15 @@ module Psych assert_nil Psych::load(Psych.dump(obj)) assert_nil Psych::load(obj.to_yaml) else - assert_equal(obj, Psych.load(v.tree.yaml)) - assert_equal(obj, Psych::load(Psych.dump(obj))) - assert_equal(obj, Psych::load(obj.to_yaml)) + begin + assert_equal(obj, Psych.load(v.tree.yaml)) + assert_equal(obj, Psych::load(Psych.dump(obj))) + assert_equal(obj, Psych::load(obj.to_yaml)) + rescue Psych::DisallowedClass, Psych::BadAlias + assert_equal(obj, Psych.unsafe_load(v.tree.yaml)) + assert_equal(obj, Psych::unsafe_load(Psych.dump(obj))) + assert_equal(obj, Psych::unsafe_load(obj.to_yaml)) + end end end diff --git a/test/psych/test_alias_and_anchor.rb b/test/psych/test_alias_and_anchor.rb index 91c09df..81ebd66 100644 --- a/test/psych/test_alias_and_anchor.rb +++ b/test/psych/test_alias_and_anchor.rb @@ -19,7 +19,7 @@ module Psych - *id001 - *id001 EOYAML - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each {|el| assert_same(result[0], el) } end @@ -33,7 +33,7 @@ EOYAML - *id001 EOYAML - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each do |el| assert_same(result[0], el) assert_equal('test1', el.var1) @@ -50,7 +50,7 @@ EOYAML - *id001 - *id001 EOYAML - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each do |el| assert_same(result[0], el) assert_equal('test', el.var1) @@ -62,7 +62,7 @@ EOYAML original = [o,o,o] yaml = Psych.dump original - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each {|el| assert_same(result[0], el) } end @@ -73,7 +73,7 @@ EOYAML original = [o,o,o] yaml = Psych.dump original - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each do |el| assert_same(result[0], el) assert_equal('test1', el.var1) @@ -87,7 +87,7 @@ EOYAML original = [o,o,o] yaml = Psych.dump original - result = Psych.load yaml + result = Psych.unsafe_load yaml result.each do |el| assert_same(result[0], el) assert_equal('test', el.var1) diff --git a/test/psych/test_array.rb b/test/psych/test_array.rb index f2bbdca..28b76da 100644 --- a/test/psych/test_array.rb +++ b/test/psych/test_array.rb @@ -24,7 +24,7 @@ module Psych def test_another_subclass_with_attributes y = Y.new.tap {|o| o.val = 1} y << "foo" << "bar" - y = Psych.load Psych.dump y + y = Psych.unsafe_load Psych.dump y assert_equal %w{foo bar}, y assert_equal Y, y.class @@ -42,13 +42,13 @@ module Psych end def test_subclass_with_attributes - y = Psych.load Psych.dump Y.new.tap {|o| o.val = 1} + y = Psych.unsafe_load Psych.dump Y.new.tap {|o| o.val = 1} assert_equal Y, y.class assert_equal 1, y.val end def test_backwards_with_syck - x = Psych.load "--- !seq:#{X.name} []\n\n" + x = Psych.unsafe_load "--- !seq:#{X.name} []\n\n" assert_equal X, x.class end diff --git a/test/psych/test_class.rb b/test/psych/test_class.rb index 71f7ec3..faa504c 100644 --- a/test/psych/test_class.rb +++ b/test/psych/test_class.rb @@ -7,13 +7,13 @@ module Psych end def test_cycle_anonymous_class - assert_raises(::TypeError) do + assert_raise(::TypeError) do assert_cycle(Class.new) end end def test_cycle_anonymous_module - assert_raises(::TypeError) do + assert_raise(::TypeError) do assert_cycle(Module.new) end end diff --git a/test/psych/test_coder.rb b/test/psych/test_coder.rb index 5ea8cab..b2be0a4 100644 --- a/test/psych/test_coder.rb +++ b/test/psych/test_coder.rb @@ -112,9 +112,19 @@ module Psych end end + class CustomEncode + def initialize(**opts) + @opts = opts + end + + def encode_with(coder) + @opts.each { |k,v| coder.public_send :"#{k}=", v } + end + end + def test_self_referential x = Referential.new - copy = Psych.load Psych.dump x + copy = Psych.unsafe_load Psych.dump x assert_equal copy, copy.a end @@ -153,23 +163,23 @@ module Psych end def test_represent_map - thing = Psych.load(Psych.dump(RepresentWithMap.new)) + thing = Psych.unsafe_load(Psych.dump(RepresentWithMap.new)) assert_equal({ "string" => 'a', :symbol => 'b' }, thing.map) end def test_represent_sequence - thing = Psych.load(Psych.dump(RepresentWithSeq.new)) + thing = Psych.unsafe_load(Psych.dump(RepresentWithSeq.new)) assert_equal %w{ foo bar }, thing.seq end def test_represent_with_init - thing = Psych.load(Psych.dump(RepresentWithInit.new)) + thing = Psych.unsafe_load(Psych.dump(RepresentWithInit.new)) assert_equal 'bar', thing.str end def test_represent! assert_match(/foo/, Psych.dump(Represent.new)) - assert_instance_of(Represent, Psych.load(Psych.dump(Represent.new))) + assert_instance_of(Represent, Psych.unsafe_load(Psych.dump(Represent.new))) end def test_scalar_coder @@ -179,7 +189,7 @@ module Psych def test_load_dumped_tagging foo = InitApi.new - bar = Psych.load(Psych.dump(foo)) + bar = Psych.unsafe_load(Psych.dump(foo)) assert_equal false, bar.implicit assert_equal "!ruby/object:Psych::TestCoder::InitApi", bar.tag assert_equal Psych::Nodes::Mapping::BLOCK, bar.style @@ -198,10 +208,121 @@ module Psych def test_dump_init_with foo = InitApi.new - bar = Psych.load(Psych.dump(foo)) + bar = Psych.unsafe_load(Psych.dump(foo)) assert_equal foo.a, bar.a assert_equal foo.b, bar.b assert_nil bar.c end + + def test_coder_style_map_default + foo = Psych.dump a: 1, b: 2 + assert_equal "---\n:a: 1\n:b: 2\n", foo + end + + def test_coder_style_map_any + foo = Psych.dump CustomEncode.new \ + map: {a: 1, b: 2}, + style: Psych::Nodes::Mapping::ANY, + tag: nil + assert_equal "---\n:a: 1\n:b: 2\n", foo + end + + def test_coder_style_map_block + foo = Psych.dump CustomEncode.new \ + map: {a: 1, b: 2}, + style: Psych::Nodes::Mapping::BLOCK, + tag: nil + assert_equal "---\n:a: 1\n:b: 2\n", foo + end + + def test_coder_style_map_flow + foo = Psych.dump CustomEncode.new \ + map: { a: 1, b: 2 }, + style: Psych::Nodes::Mapping::FLOW, + tag: nil + assert_equal "--- {! ':a': 1, ! ':b': 2}\n", foo + end + + def test_coder_style_seq_default + foo = Psych.dump [ 1, 2, 3 ] + assert_equal "---\n- 1\n- 2\n- 3\n", foo + end + + def test_coder_style_seq_any + foo = Psych.dump CustomEncode.new \ + seq: [ 1, 2, 3 ], + style: Psych::Nodes::Sequence::ANY, + tag: nil + assert_equal "---\n- 1\n- 2\n- 3\n", foo + end + + def test_coder_style_seq_block + foo = Psych.dump CustomEncode.new \ + seq: [ 1, 2, 3 ], + style: Psych::Nodes::Sequence::BLOCK, + tag: nil + assert_equal "---\n- 1\n- 2\n- 3\n", foo + end + + def test_coder_style_seq_flow + foo = Psych.dump CustomEncode.new \ + seq: [ 1, 2, 3 ], + style: Psych::Nodes::Sequence::FLOW, + tag: nil + assert_equal "--- [1, 2, 3]\n", foo + end + + def test_coder_style_scalar_default + foo = Psych.dump 'some scalar' + assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo) + end + + def test_coder_style_scalar_any + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::ANY, + tag: nil + assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo) + end + + def test_coder_style_scalar_plain + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::PLAIN, + tag: nil + assert_match(/\A--- some scalar\n(?:\.\.\.\n)?\z/, foo) + end + + def test_coder_style_scalar_single_quoted + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::SINGLE_QUOTED, + tag: nil + assert_equal "--- ! 'some scalar'\n", foo + end + + def test_coder_style_scalar_double_quoted + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::DOUBLE_QUOTED, + tag: nil + assert_equal %Q'--- ! "some scalar"\n', foo + end + + def test_coder_style_scalar_literal + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::LITERAL, + tag: nil + assert_equal "--- ! |-\n some scalar\n", foo + end + + def test_coder_style_scalar_folded + foo = Psych.dump CustomEncode.new \ + scalar: 'some scalar', + style: Psych::Nodes::Scalar::FOLDED, + tag: nil + assert_equal "--- ! >-\n some scalar\n", foo + end end end diff --git a/test/psych/test_date_time.rb b/test/psych/test_date_time.rb index f73f346..6f1e8b5 100644 --- a/test/psych/test_date_time.rb +++ b/test/psych/test_date_time.rb @@ -22,7 +22,7 @@ module Psych def test_timezone_offset times = [Time.new(2017, 4, 13, 12, 0, 0, "+09:00"), Time.new(2017, 4, 13, 12, 0, 0, "-05:00")] - cycled = Psych::load(Psych.dump times) + cycled = Psych::unsafe_load(Psych.dump times) assert_match(/12:00:00 \+0900/, cycled.first.to_s) assert_match(/12:00:00 -0500/, cycled.last.to_s) end @@ -39,7 +39,7 @@ module Psych def test_datetime_timezone_offset times = [DateTime.new(2017, 4, 13, 12, 0, 0, "+09:00"), DateTime.new(2017, 4, 13, 12, 0, 0, "-05:00")] - cycled = Psych::load(Psych.dump times) + cycled = Psych::unsafe_load(Psych.dump times) assert_match(/12:00:00\+09:00/, cycled.first.to_s) assert_match(/12:00:00-05:00/, cycled.last.to_s) end diff --git a/test/psych/test_deprecated.rb b/test/psych/test_deprecated.rb index 624f437..af33799 100644 --- a/test/psych/test_deprecated.rb +++ b/test/psych/test_deprecated.rb @@ -41,7 +41,7 @@ module Psych def test_recursive_quick_emit_encode_with qeew = QuickEmitterEncodeWith.new hash = { :qe => qeew } - hash2 = Psych.load Psych.dump hash + hash2 = Psych.unsafe_load Psych.dump hash qe = hash2[:qe] assert_equal qeew.name, qe.name @@ -72,7 +72,7 @@ module Psych # receive the yaml_initialize call. def test_yaml_initialize_and_init_with hash = { :yi => YamlInitAndInitWith.new } - hash2 = Psych.load Psych.dump hash + hash2 = Psych.unsafe_load Psych.dump hash yi = hash2[:yi] assert_equal 'TGIF!', yi.name diff --git a/test/psych/test_document.rb b/test/psych/test_document.rb index a88dd32..cf3b700 100644 --- a/test/psych/test_document.rb +++ b/test/psych/test_document.rb @@ -30,7 +30,7 @@ module Psych end def test_emit_bad_tag - assert_raises(RuntimeError) do + assert_raise(RuntimeError) do @doc.tag_directives = [['!']] @stream.yaml end diff --git a/test/psych/test_emitter.rb b/test/psych/test_emitter.rb index 52d5e9d..506d722 100644 --- a/test/psych/test_emitter.rb +++ b/test/psych/test_emitter.rb @@ -40,7 +40,7 @@ module Psych end def test_start_stream_arg_error - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.start_stream 'asdfasdf' end end @@ -56,7 +56,7 @@ module Psych [[], [nil,nil], false], [[1,1], [[nil, "tag:TALOS"]], 0], ].each do |args| - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.start_document(*args) end end @@ -73,7 +73,7 @@ module Psych ['foo', nil, nil, false, true, :foo], [nil, nil, nil, false, true, 1], ].each do |args| - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.scalar(*args) end end @@ -83,11 +83,11 @@ module Psych @emitter.start_stream Psych::Nodes::Stream::UTF8 @emitter.start_document [], [], false - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.start_sequence(nil, Object.new, true, 1) end - assert_raises(TypeError) do + assert_raise(TypeError) do @emitter.start_sequence(nil, nil, true, :foo) end end diff --git a/test/psych/test_encoding.rb b/test/psych/test_encoding.rb index ef66531..e5831c9 100644 --- a/test/psych/test_encoding.rb +++ b/test/psych/test_encoding.rb @@ -63,7 +63,7 @@ module Psych # If the external encoding isn't utf8, utf16le, or utf16be, we cannot # process the file. File.open(t.path, 'r', :encoding => 'SHIFT_JIS') do |f| - assert_raises Psych::SyntaxError do + assert_raise Psych::SyntaxError do Psych.load(f) end end @@ -121,7 +121,7 @@ module Psych def test_emit_alias @emitter.start_stream Psych::Parser::UTF8 @emitter.start_document [], [], true - e = assert_raises(RuntimeError) do + e = assert_raise(RuntimeError) do @emitter.alias 'ドラえもん'.encode('EUC-JP') end assert_match(/alias value/, e.message) diff --git a/test/psych/test_exception.rb b/test/psych/test_exception.rb index e355c26..c1e69ab 100644 --- a/test/psych/test_exception.rb +++ b/test/psych/test_exception.rb @@ -33,42 +33,36 @@ module Psych def test_backtrace err = make_ex - new_err = Psych.load(Psych.dump(err)) + new_err = Psych.unsafe_load(Psych.dump(err)) assert_equal err.backtrace, new_err.backtrace end def test_naming_exception err = String.xxx rescue $! - new_err = Psych.load(Psych.dump(err)) + new_err = Psych.unsafe_load(Psych.dump(err)) assert_equal err.message, new_err.message end def test_load_takes_file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load '--- `' end assert_nil ex.file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load '--- `', filename: 'meow' end assert_equal 'meow', ex.file - - # deprecated interface - ex = assert_raises(Psych::SyntaxError) do - Psych.load '--- `', 'deprecated' - end - assert_equal 'deprecated', ex.file end def test_psych_parse_stream_takes_file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse_stream '--- `' end assert_nil ex.file assert_match '(<unknown>)', ex.message - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse_stream '--- `', filename: 'omg!' end assert_equal 'omg!', ex.file @@ -76,22 +70,16 @@ module Psych end def test_load_stream_takes_file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load_stream '--- `' end assert_nil ex.file assert_match '(<unknown>)', ex.message - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load_stream '--- `', filename: 'omg!' end assert_equal 'omg!', ex.file - - # deprecated interface - ex = assert_raises(Psych::SyntaxError) do - Psych.load_stream '--- `', 'deprecated' - end - assert_equal 'deprecated', ex.file end def test_parse_file_exception @@ -99,7 +87,7 @@ module Psych t.binmode t.write '--- `' t.close - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse_file t.path end assert_equal t.path, ex.file @@ -111,34 +99,40 @@ module Psych t.binmode t.write '--- `' t.close - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.load_file t.path end assert_equal t.path, ex.file } end + def test_safe_load_file_exception + Tempfile.create(['loadfile', 'yml']) {|t| + t.binmode + t.write '--- `' + t.close + ex = assert_raise(Psych::SyntaxError) do + Psych.safe_load_file t.path + end + assert_equal t.path, ex.file + } + end + def test_psych_parse_takes_file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse '--- `' end assert_match '(<unknown>)', ex.message assert_nil ex.file - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do Psych.parse '--- `', filename: 'omg!' end assert_match 'omg!', ex.message - - # deprecated interface - ex = assert_raises(Psych::SyntaxError) do - Psych.parse '--- `', 'deprecated' - end - assert_match 'deprecated', ex.message end def test_attributes - e = assert_raises(Psych::SyntaxError) { + e = assert_raise(Psych::SyntaxError) { Psych.load '--- `foo' } @@ -153,7 +147,7 @@ module Psych end def test_convert - w = Psych.load(Psych.dump(@wups)) + w = Psych.unsafe_load(Psych.dump(@wups)) assert_equal @wups.message, w.message assert_equal @wups.backtrace, w.backtrace assert_equal 1, w.foo diff --git a/test/psych/test_hash.rb b/test/psych/test_hash.rb index ba11b82..5374781 100644 --- a/test/psych/test_hash.rb +++ b/test/psych/test_hash.rb @@ -39,7 +39,7 @@ module Psych def test_hash_with_ivar t1 = HashWithIvar.new t1[:foo] = :bar - t2 = Psych.load(Psych.dump(t1)) + t2 = Psych.unsafe_load(Psych.dump(t1)) assert_equal t1, t2 assert_cycle t1 end @@ -54,14 +54,14 @@ module Psych def test_custom_initialized a = [1,2,3,4,5] t1 = HashWithCustomInit.new(a) - t2 = Psych.load(Psych.dump(t1)) + t2 = Psych.unsafe_load(Psych.dump(t1)) assert_equal t1, t2 assert_cycle t1 end def test_custom_initialize_no_ivar t1 = HashWithCustomInitNoIvar.new(nil) - t2 = Psych.load(Psych.dump(t1)) + t2 = Psych.unsafe_load(Psych.dump(t1)) assert_equal t1, t2 assert_cycle t1 end @@ -70,25 +70,25 @@ module Psych x = X.new x[:a] = 'b' x.instance_variable_set :@foo, 'bar' - dup = Psych.load Psych.dump x + dup = Psych.unsafe_load Psych.dump x assert_cycle x assert_equal 'bar', dup.instance_variable_get(:@foo) assert_equal X, dup.class end def test_load_with_class_syck_compatibility - hash = Psych.load "--- !ruby/object:Hash\n:user_id: 7\n:username: Lucas\n" + hash = Psych.unsafe_load "--- !ruby/object:Hash\n:user_id: 7\n:username: Lucas\n" assert_equal({ user_id: 7, username: 'Lucas'}, hash) end def test_empty_subclass assert_match "!ruby/hash:#{X}", Psych.dump(X.new) - x = Psych.load Psych.dump X.new + x = Psych.unsafe_load Psych.dump X.new assert_equal X, x.class end def test_map - x = Psych.load "--- !map:#{X} { }\n" + x = Psych.unsafe_load "--- !map:#{X} { }\n" assert_equal X, x.class end @@ -102,7 +102,7 @@ module Psych end def test_ref_append - hash = Psych.load(<<-eoyml) + hash = Psych.unsafe_load(<<-eoyml) --- foo: &foo hello: world @@ -114,7 +114,7 @@ eoyml def test_key_deduplication unless String.method_defined?(:-@) && (-("a" * 20)).equal?((-("a" * 20))) - skip "This Ruby implementation doesn't support string deduplication" + pend "This Ruby implementation doesn't support string deduplication" end hashes = Psych.load(<<-eoyml) diff --git a/test/psych/test_marshalable.rb b/test/psych/test_marshalable.rb index b1f4a83..74ee902 100644 --- a/test/psych/test_marshalable.rb +++ b/test/psych/test_marshalable.rb @@ -6,7 +6,7 @@ module Psych class TestMarshalable < TestCase def test_objects_defining_marshal_dump_and_marshal_load_can_be_dumped sd = SimpleDelegator.new(1) - loaded = Psych.load(Psych.dump(sd)) + loaded = Psych.unsafe_load(Psych.dump(sd)) assert_instance_of(SimpleDelegator, loaded) assert_equal(sd, loaded) @@ -46,7 +46,15 @@ module Psych def test_init_with_takes_priority_over_marshal_methods obj = PsychCustomMarshalable.new(1) - loaded = Psych.load(Psych.dump(obj)) + loaded = Psych.unsafe_load(Psych.dump(obj)) + + assert(PsychCustomMarshalable === loaded) + assert_equal(2, loaded.foo) + end + + def test_init_symbolize_names + obj = PsychCustomMarshalable.new(1) + loaded = Psych.unsafe_load(Psych.dump(obj), symbolize_names: true) assert(PsychCustomMarshalable === loaded) assert_equal(2, loaded.foo) diff --git a/test/psych/test_merge_keys.rb b/test/psych/test_merge_keys.rb index 08ffe58..dcf4f1f 100644 --- a/test/psych/test_merge_keys.rb +++ b/test/psych/test_merge_keys.rb @@ -34,7 +34,7 @@ map: end def test_explicit_string - doc = Psych.load <<-eoyml + doc = Psych.unsafe_load <<-eoyml a: &me { hello: world } b: { !!str '<<': *me } eoyml @@ -55,7 +55,7 @@ product: !ruby/object:#{Product.name} <<: *foo eoyml - hash = Psych.load s + hash = Psych.unsafe_load s assert_equal({"bar" => 10}, hash["foo"]) product = hash["product"] assert_equal 10, product.bar @@ -67,7 +67,7 @@ defaults: &defaults development: <<: *defaults eoyml - assert_equal({'<<' => nil }, Psych.load(yaml)['development']) + assert_equal({'<<' => nil }, Psych.unsafe_load(yaml)['development']) end def test_merge_array @@ -77,7 +77,7 @@ foo: &hello baz: <<: *hello eoyml - assert_equal({'<<' => [1]}, Psych.load(yaml)['baz']) + assert_equal({'<<' => [1]}, Psych.unsafe_load(yaml)['baz']) end def test_merge_is_not_partial @@ -89,9 +89,9 @@ foo: &hello baz: <<: [*hello, *default] eoyml - doc = Psych.load yaml + doc = Psych.unsafe_load yaml refute doc['baz'].key? 'hello' - assert_equal({'<<' => [[1], {"hello"=>"world"}]}, Psych.load(yaml)['baz']) + assert_equal({'<<' => [[1], {"hello"=>"world"}]}, Psych.unsafe_load(yaml)['baz']) end def test_merge_seq_nil @@ -100,7 +100,7 @@ foo: &hello baz: <<: [*hello] eoyml - assert_equal({'<<' => [nil]}, Psych.load(yaml)['baz']) + assert_equal({'<<' => [nil]}, Psych.unsafe_load(yaml)['baz']) end def test_bad_seq_merge @@ -109,7 +109,7 @@ defaults: &defaults [1, 2, 3] development: <<: *defaults eoyml - assert_equal({'<<' => [1,2,3]}, Psych.load(yaml)['development']) + assert_equal({'<<' => [1,2,3]}, Psych.unsafe_load(yaml)['development']) end def test_missing_merge_key @@ -117,7 +117,7 @@ development: bar: << : *foo eoyml - exp = assert_raises(Psych::BadAlias) { Psych.load yaml } + exp = assert_raise(Psych::BadAlias) { Psych.load yaml } assert_match 'foo', exp.message end @@ -134,7 +134,7 @@ bar: hash = { "foo" => { "hello" => "world"}, "bar" => { "hello" => "world", "baz" => "boo" } } - assert_equal hash, Psych.load(yaml) + assert_equal hash, Psych.unsafe_load(yaml) end def test_multiple_maps @@ -159,7 +159,7 @@ bar: 'label' => 'center/big' } - assert_equal hash, Psych.load(yaml)[4] + assert_equal hash, Psych.unsafe_load(yaml)[4] end def test_override @@ -185,7 +185,7 @@ bar: 'label' => 'center/big' } - assert_equal hash, Psych.load(yaml)[4] + assert_equal hash, Psych.unsafe_load(yaml)[4] end end end diff --git a/test/psych/test_numeric.rb b/test/psych/test_numeric.rb index db99a2a..8c3dcd1 100644 --- a/test/psych/test_numeric.rb +++ b/test/psych/test_numeric.rb @@ -33,6 +33,7 @@ module Psych def test_big_decimal_round_trip decimal = BigDecimal("12.34") + $DEBUG = false assert_cycle decimal end diff --git a/test/psych/test_object.rb b/test/psych/test_object.rb index f1c6145..0faf6b2 100644 --- a/test/psych/test_object.rb +++ b/test/psych/test_object.rb @@ -28,7 +28,7 @@ module Psych def test_tag_round_trip tag = Tagged.new - tag2 = Psych.load(Psych.dump(tag)) + tag2 = Psych.unsafe_load(Psych.dump(tag)) assert_equal tag.baz, tag2.baz assert_instance_of(Tagged, tag2) end @@ -36,7 +36,7 @@ module Psych def test_cyclic_references foo = Foo.new(nil) foo.parent = foo - loaded = Psych.load Psych.dump foo + loaded = Psych.unsafe_load Psych.dump foo assert_instance_of(Foo, loaded) assert_equal loaded, loaded.parent diff --git a/test/psych/test_object_references.rb b/test/psych/test_object_references.rb index ca69c7d..269d722 100644 --- a/test/psych/test_object_references.rb +++ b/test/psych/test_object_references.rb @@ -34,12 +34,16 @@ module Psych def assert_reference_trip obj yml = Psych.dump([obj, obj]) assert_match(/\*-?\d+/, yml) - data = Psych.load yml + begin + data = Psych.load yml + rescue Psych::DisallowedClass + data = Psych.unsafe_load yml + end assert_equal data.first.object_id, data.last.object_id end def test_float_references - data = Psych.load <<-eoyml + data = Psych.unsafe_load <<-eoyml ---\s - &name 1.2 - *name @@ -49,7 +53,7 @@ module Psych end def test_binary_references - data = Psych.load <<-eoyml + data = Psych.unsafe_load <<-eoyml --- - &name !binary |- aGVsbG8gd29ybGQh @@ -60,7 +64,7 @@ module Psych end def test_regexp_references - data = Psych.load <<-eoyml + data = Psych.unsafe_load <<-eoyml ---\s - &name !ruby/regexp /pattern/i - *name diff --git a/test/psych/test_omap.rb b/test/psych/test_omap.rb index 98636de..6de0286 100644 --- a/test/psych/test_omap.rb +++ b/test/psych/test_omap.rb @@ -4,7 +4,7 @@ require_relative 'helper' module Psych class TestOmap < TestCase def test_parse_as_map - o = Psych.load "--- !!omap\na: 1\nb: 2" + o = Psych.unsafe_load "--- !!omap\na: 1\nb: 2" assert_kind_of Psych::Omap, o assert_equal 1, o['a'] assert_equal 2, o['b'] @@ -14,7 +14,7 @@ module Psych map = Psych::Omap.new map['foo'] = 'bar' map['self'] = map - assert_equal(map, Psych.load(Psych.dump(map))) + assert_equal(map, Psych.unsafe_load(Psych.dump(map))) end def test_keys diff --git a/test/psych/test_parser.rb b/test/psych/test_parser.rb index e8225da..3604e7c 100644 --- a/test/psych/test_parser.rb +++ b/test/psych/test_parser.rb @@ -63,7 +63,7 @@ module Psych parser = Psych::Parser.new klass.new 2.times { - assert_raises(RuntimeError, method.to_s) do + assert_raise(RuntimeError, method.to_s) do parser.parse yaml end } @@ -77,7 +77,7 @@ module Psych end def test_filename - ex = assert_raises(Psych::SyntaxError) do + ex = assert_raise(Psych::SyntaxError) do @parser.parse '--- `', 'omg!' end assert_match 'omg!', ex.message @@ -180,7 +180,7 @@ module Psych def o.external_encoding; nil end def o.read len; self end - assert_raises(TypeError) do + assert_raise(TypeError) do @parser.parse o end end @@ -193,23 +193,23 @@ module Psych end def test_syntax_error - assert_raises(Psych::SyntaxError) do + assert_raise(Psych::SyntaxError) do @parser.parse("---\n\"foo\"\n\"bar\"\n") end end def test_syntax_error_twice - assert_raises(Psych::SyntaxError) do + assert_raise(Psych::SyntaxError) do @parser.parse("---\n\"foo\"\n\"bar\"\n") end - assert_raises(Psych::SyntaxError) do + assert_raise(Psych::SyntaxError) do @parser.parse("---\n\"foo\"\n\"bar\"\n") end end def test_syntax_error_has_path_for_string - e = assert_raises(Psych::SyntaxError) do + e = assert_raise(Psych::SyntaxError) do @parser.parse("---\n\"foo\"\n\"bar\"\n") end assert_match '(<unknown>):', e.message @@ -219,7 +219,7 @@ module Psych io = StringIO.new "---\n\"foo\"\n\"bar\"\n" def io.path; "hello!"; end - e = assert_raises(Psych::SyntaxError) do + e = assert_raise(Psych::SyntaxError) do @parser.parse(io) end assert_match "(#{io.path}):", e.message diff --git a/test/psych/test_psych.rb b/test/psych/test_psych.rb index 55d9f19..1abd69c 100644 --- a/test/psych/test_psych.rb +++ b/test/psych/test_psych.rb @@ -16,7 +16,7 @@ class TestPsych < Psych::TestCase end def test_line_width_invalid - assert_raises(ArgumentError) { Psych.dump('x', { :line_width => -2 }) } + assert_raise(ArgumentError) { Psych.dump('x', { :line_width => -2 }) } end def test_line_width_no_limit @@ -61,7 +61,7 @@ class TestPsych < Psych::TestCase end def test_load_argument_error - assert_raises(TypeError) do + assert_raise(TypeError) do Psych.load nil end end @@ -75,16 +75,12 @@ class TestPsych < Psych::TestCase end def test_parse_raises_on_bad_input - assert_raises(Psych::SyntaxError) { Psych.parse("--- `") } - end - - def test_parse_with_fallback - assert_equal 42, Psych.parse("", fallback: 42) + assert_raise(Psych::SyntaxError) { Psych.parse("--- `") } end def test_non_existing_class_on_deserialize - e = assert_raises(ArgumentError) do - Psych.load("--- !ruby/object:NonExistent\nfoo: 1") + e = assert_raise(ArgumentError) do + Psych.unsafe_load("--- !ruby/object:NonExistent\nfoo: 1") end assert_equal 'undefined class/module NonExistent', e.message end @@ -125,12 +121,25 @@ class TestPsych < Psych::TestCase assert_equal %w{ foo bar }, docs end + def test_load_stream_freeze + docs = Psych.load_stream("--- foo\n...\n--- bar\n...", freeze: true) + assert_equal %w{ foo bar }, docs + docs.each do |string| + assert_predicate string, :frozen? + end + end + + def test_load_stream_symbolize_names + docs = Psych.load_stream("---\nfoo: bar", symbolize_names: true) + assert_equal [{foo: 'bar'}], docs + end + def test_load_stream_default_fallback assert_equal [], Psych.load_stream("") end def test_load_stream_raises_on_bad_input - assert_raises(Psych::SyntaxError) { Psych.load_stream("--- `") } + assert_raise(Psych::SyntaxError) { Psych.load_stream("--- `") } end def test_parse_stream @@ -162,7 +171,7 @@ class TestPsych < Psych::TestCase end def test_parse_stream_raises_on_bad_input - assert_raises(Psych::SyntaxError) { Psych.parse_stream("--- `") } + assert_raise(Psych::SyntaxError) { Psych.parse_stream("--- `") } end def test_add_builtin_type @@ -201,7 +210,7 @@ class TestPsych < Psych::TestCase def test_load_freeze_deduplication unless String.method_defined?(:-@) && (-("a" * 20)).equal?((-("a" * 20))) - skip "This Ruby implementation doesn't support string deduplication" + pend "This Ruby implementation doesn't support string deduplication" end data = Psych.load("--- ['a']", freeze: true) @@ -209,28 +218,28 @@ class TestPsych < Psych::TestCase end def test_load_default_fallback - assert_equal false, Psych.load("") + assert_equal false, Psych.unsafe_load("") end def test_load_with_fallback - assert_equal 42, Psych.load("", "file", fallback: 42) + assert_equal 42, Psych.load("", filename: "file", fallback: 42) end def test_load_with_fallback_nil_or_false - assert_nil Psych.load("", "file", fallback: nil) - assert_equal false, Psych.load("", "file", fallback: false) + assert_nil Psych.load("", filename: "file", fallback: nil) + assert_equal false, Psych.load("", filename: "file", fallback: false) end def test_load_with_fallback_hash - assert_equal Hash.new, Psych.load("", "file", fallback: Hash.new) + assert_equal Hash.new, Psych.load("", filename: "file", fallback: Hash.new) end def test_load_with_fallback_for_nil - assert_nil Psych.load("--- null", "file", fallback: 42) + assert_nil Psych.unsafe_load("--- null", filename: "file", fallback: 42) end def test_load_with_fallback_for_false - assert_equal false, Psych.load("--- false", "file", fallback: 42) + assert_equal false, Psych.unsafe_load("--- false", filename: "file", fallback: 42) end def test_load_file @@ -242,9 +251,30 @@ class TestPsych < Psych::TestCase } end + def test_load_file_freeze + Tempfile.create(['yikes', 'yml']) {|t| + t.binmode + t.write('--- hello world') + t.close + + object = Psych.load_file(t.path, freeze: true) + assert_predicate object, :frozen? + } + end + + def test_load_file_symbolize_names + Tempfile.create(['yikes', 'yml']) {|t| + t.binmode + t.write("---\nfoo: bar") + t.close + + assert_equal({foo: 'bar'}, Psych.load_file(t.path, symbolize_names: true)) + } + end + def test_load_file_default_fallback Tempfile.create(['empty', 'yml']) {|t| - assert_equal false, Psych.load_file(t.path) + assert_equal false, Psych.unsafe_load_file(t.path) } end @@ -285,6 +315,18 @@ class TestPsych < Psych::TestCase } end + def test_safe_load_file_with_permitted_classe + Tempfile.create(['false', 'yml']) {|t| + t.binmode + t.write("--- !ruby/range\nbegin: 0\nend: 42\nexcl: false\n") + t.close + assert_equal 0..42, Psych.safe_load_file(t.path, permitted_classes: [Range]) + assert_raise(Psych::DisallowedClass) { + Psych.safe_load_file(t.path) + } + } + end + def test_parse_file Tempfile.create(['yikes', 'yml']) {|t| t.binmode @@ -301,9 +343,9 @@ class TestPsych < Psych::TestCase end def test_degenerate_strings - assert_equal false, Psych.load(' ') + assert_equal false, Psych.unsafe_load(' ') assert_equal false, Psych.parse(' ') - assert_equal false, Psych.load('') + assert_equal false, Psych.unsafe_load('') assert_equal false, Psych.parse('') end @@ -325,17 +367,75 @@ class TestPsych < Psych::TestCase yaml = <<-eoyml foo: bar: baz + 1: 2 hoge: - fuga: piyo eoyml result = Psych.load(yaml) - assert_equal result, { "foo" => { "bar" => "baz"}, "hoge" => [{ "fuga" => "piyo" }] } + assert_equal result, { "foo" => { "bar" => "baz", 1 => 2 }, "hoge" => [{ "fuga" => "piyo" }] } result = Psych.load(yaml, symbolize_names: true) - assert_equal result, { foo: { bar: "baz" }, hoge: [{ fuga: "piyo" }] } + assert_equal result, { foo: { bar: "baz", 1 => 2 }, hoge: [{ fuga: "piyo" }] } result = Psych.safe_load(yaml, symbolize_names: true) - assert_equal result, { foo: { bar: "baz" }, hoge: [{ fuga: "piyo" }] } + assert_equal result, { foo: { bar: "baz", 1 => 2 }, hoge: [{ fuga: "piyo" }] } + end + + def test_safe_dump_defaults + yaml = <<-eoyml +--- +array: +- 1 +float: 13.12 +booleans: +- true +- false +eoyml + + payload = Psych.safe_dump({ + "array" => [1], + "float" => 13.12, + "booleans" => [true, false], + }) + assert_equal yaml, payload + end + + def test_safe_dump_unpermitted_class + error = assert_raise Psych::DisallowedClass do + Psych.safe_dump(Object.new) + end + assert_equal "Tried to dump unspecified class: Object", error.message + + hash_subclass = Class.new(Hash) + error = assert_raise Psych::DisallowedClass do + Psych.safe_dump(hash_subclass.new) + end + assert_equal "Tried to dump unspecified class: #{hash_subclass.inspect}", error.message end + + def test_safe_dump_extra_permitted_classes + assert_equal "--- !ruby/object {}\n", Psych.safe_dump(Object.new, permitted_classes: [Object]) + end + + def test_safe_dump_symbols + error = assert_raise Psych::DisallowedClass do + Psych.safe_dump(:foo, permitted_classes: [Symbol]) + end + assert_equal "Tried to dump unspecified class: Symbol(:foo)", error.message + + assert_match(/\A--- :foo\n(?:\.\.\.\n)?\z/, Psych.safe_dump(:foo, permitted_classes: [Symbol], permitted_symbols: [:foo])) + end + + def test_safe_dump_aliases + x = [] + x << x + error = assert_raise Psych::BadAlias do + Psych.safe_dump(x) + end + assert_equal "Tried to dump an aliased object", error.message + + assert_equal "--- &1\n" + "- *1\n", Psych.safe_dump(x, aliases: true) + end + end diff --git a/test/psych/test_ractor.rb b/test/psych/test_ractor.rb new file mode 100644 index 0000000..1b0d810 --- /dev/null +++ b/test/psych/test_ractor.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true +require_relative 'helper' + +class TestPsychRactor < Test::Unit::TestCase + def test_ractor_round_trip + assert_ractor(<<~RUBY, require_relative: 'helper') + obj = {foo: [42]} + obj2 = Ractor.new(obj) do |obj| + Psych.unsafe_load(Psych.dump(obj)) + end.take + assert_equal obj, obj2 + RUBY + end + + def test_not_shareable + # There's no point in making these frozen / shareable + # and the C-ext disregards begin frozen + assert_ractor(<<~RUBY, require_relative: 'helper') + parser = Psych::Parser.new + emitter = Psych::Emitter.new(nil) + assert_raise(Ractor::Error) { Ractor.make_shareable(parser) } + assert_raise(Ractor::Error) { Ractor.make_shareable(emitter) } + RUBY + end + + def test_ractor_config + # Config is ractor-local + # Test is to make sure it works, even though usage is probably very low. + # The methods are not documented and might be deprecated one day + assert_ractor(<<~RUBY, require_relative: 'helper') + r = Ractor.new do + Psych.add_builtin_type 'omap' do |type, val| + val * 2 + end + Psych.load('--- !!omap hello') + end.take + assert_equal 'hellohello', r + assert_equal 'hello', Psych.load('--- !!omap hello') + RUBY + end + + def test_ractor_constants + assert_ractor(<<~RUBY, require_relative: 'helper') + r = Ractor.new do + Psych.libyaml_version.join('.') == Psych::LIBYAML_VERSION + end.take + assert_equal true, r + RUBY + end +end if defined?(Ractor) diff --git a/test/psych/test_safe_load.rb b/test/psych/test_safe_load.rb index e397271..b52d604 100644 --- a/test/psych/test_safe_load.rb +++ b/test/psych/test_safe_load.rb @@ -22,7 +22,7 @@ module Psych def test_no_recursion x = [] x << x - assert_raises(Psych::BadAlias) do + assert_raise(Psych::BadAlias) do Psych.safe_load Psych.dump(x) end end @@ -31,13 +31,11 @@ module Psych x = [] x << x assert_equal(x, Psych.safe_load(Psych.dump(x), permitted_classes: [], permitted_symbols: [], aliases: true)) - # deprecated interface - assert_equal(x, Psych.safe_load(Psych.dump(x), [], [], true)) end def test_permitted_symbol yml = Psych.dump :foo - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load yml end assert_equal( @@ -48,55 +46,36 @@ module Psych permitted_symbols: [:foo] ) ) - - # deprecated interface - assert_equal(:foo, Psych.safe_load(yml, [Symbol], [:foo])) end def test_symbol - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do assert_safe_cycle :foo end - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load '--- !ruby/symbol foo', permitted_classes: [] end - # deprecated interface - assert_raises(Psych::DisallowedClass) do - Psych.safe_load '--- !ruby/symbol foo', [] - end - assert_safe_cycle :foo, permitted_classes: [Symbol] assert_safe_cycle :foo, permitted_classes: %w{ Symbol } assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', permitted_classes: [Symbol]) - - # deprecated interface - assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', [Symbol]) end def test_foo - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load '--- !ruby/object:Foo {}', permitted_classes: [Foo] end - # deprecated interface - assert_raises(Psych::DisallowedClass) do - Psych.safe_load '--- !ruby/object:Foo {}', [Foo] - end - - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do assert_safe_cycle Foo.new end assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), permitted_classes: [Foo])) - - # deprecated interface - assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), [Foo])) end X = Struct.new(:x) def test_struct_depends_on_sym assert_safe_cycle(X.new, permitted_classes: [X, Symbol]) - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do cycle X.new, permitted_classes: [X] end end @@ -107,14 +86,14 @@ module Psych foo: bar eoyml - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load(<<-eoyml, permitted_classes: [Struct]) --- !ruby/struct foo: bar eoyml end - assert_raises(Psych::DisallowedClass) do + assert_raise(Psych::DisallowedClass) do Psych.safe_load(<<-eoyml, permitted_classes: [Symbol]) --- !ruby/struct foo: bar @@ -122,27 +101,6 @@ module Psych end end - def test_deprecated_anon_struct - assert Psych.safe_load(<<-eoyml, [Struct, Symbol]) ---- !ruby/struct - foo: bar - eoyml - - assert_raises(Psych::DisallowedClass) do - Psych.safe_load(<<-eoyml, [Struct]) ---- !ruby/struct - foo: bar - eoyml - end - - assert_raises(Psych::DisallowedClass) do - Psych.safe_load(<<-eoyml, [Symbol]) ---- !ruby/struct - foo: bar - eoyml - end - end - def test_safe_load_default_fallback assert_nil Psych.safe_load("") end @@ -152,15 +110,13 @@ module Psych end def test_safe_load_raises_on_bad_input - assert_raises(Psych::SyntaxError) { Psych.safe_load("--- `") } + assert_raise(Psych::SyntaxError) { Psych.safe_load("--- `") } end private def cycle object, permitted_classes: [] Psych.safe_load(Psych.dump(object), permitted_classes: permitted_classes) - # deprecated interface test - Psych.safe_load(Psych.dump(object), permitted_classes) end def assert_safe_cycle object, permitted_classes: [] diff --git a/test/psych/test_scalar_scanner.rb b/test/psych/test_scalar_scanner.rb index ec67a33..cac8b8f 100644 --- a/test/psych/test_scalar_scanner.rb +++ b/test/psych/test_scalar_scanner.rb @@ -115,7 +115,15 @@ module Psych end def test_scan_strings_starting_with_underscores - assert_equal "_100", ss.tokenize('_100') + assert_equal '_100', ss.tokenize('_100') + end + + def test_scan_strings_starting_with_number + assert_equal '450D', ss.tokenize('450D') + end + + def test_scan_strings_ending_with_underscores + assert_equal '100_', ss.tokenize('100_') end def test_scan_int_commas_and_underscores @@ -124,7 +132,10 @@ module Psych assert_equal 123_456_789, ss.tokenize('123_456_789') assert_equal 123_456_789, ss.tokenize('123,456,789') assert_equal 123_456_789, ss.tokenize('1_2,3,4_5,6_789') - assert_equal 123_456_789, ss.tokenize('1_2,3,4_5,6_789_') + + assert_equal 1, ss.tokenize('1') + assert_equal 1, ss.tokenize('+1') + assert_equal -1, ss.tokenize('-1') assert_equal 0b010101010, ss.tokenize('0b010101010') assert_equal 0b010101010, ss.tokenize('0b0,1_0,1_,0,1_01,0') diff --git a/test/psych/test_serialize_subclasses.rb b/test/psych/test_serialize_subclasses.rb index 8e1d0d3..344c79b 100644 --- a/test/psych/test_serialize_subclasses.rb +++ b/test/psych/test_serialize_subclasses.rb @@ -17,7 +17,7 @@ module Psych def test_some_object so = SomeObject.new('foo', [1,2,3]) - assert_equal so, Psych.load(Psych.dump(so)) + assert_equal so, Psych.unsafe_load(Psych.dump(so)) end class StructSubclass < Struct.new(:foo) @@ -33,7 +33,7 @@ module Psych def test_struct_subclass so = StructSubclass.new('foo', [1,2,3]) - assert_equal so, Psych.load(Psych.dump(so)) + assert_equal so, Psych.unsafe_load(Psych.dump(so)) end end end diff --git a/test/psych/test_set.rb b/test/psych/test_set.rb index 5690957..87944d8 100644 --- a/test/psych/test_set.rb +++ b/test/psych/test_set.rb @@ -21,7 +21,7 @@ module Psych ### # FIXME: Syck should also support !!set as shorthand def test_load_from_yaml - loaded = Psych.load(<<-eoyml) + loaded = Psych.unsafe_load(<<-eoyml) --- !set foo: bar bar: baz @@ -30,11 +30,11 @@ bar: baz end def test_loaded_class - assert_instance_of(Psych::Set, Psych.load(Psych.dump(@set))) + assert_instance_of(Psych::Set, Psych.unsafe_load(Psych.dump(@set))) end def test_set_shorthand - loaded = Psych.load(<<-eoyml) + loaded = Psych.unsafe_load(<<-eoyml) --- !!set foo: bar bar: baz diff --git a/test/psych/test_string.rb b/test/psych/test_string.rb index 973f38b..20ab79c 100644 --- a/test/psych/test_string.rb +++ b/test/psych/test_string.rb @@ -104,7 +104,7 @@ module Psych end def test_string_subclass_with_anchor - y = Psych.load <<-eoyml + y = Psych.unsafe_load <<-eoyml --- body: string: &70121654388580 !ruby/string @@ -116,7 +116,7 @@ body: end def test_self_referential_string - y = Psych.load <<-eoyml + y = Psych.unsafe_load <<-eoyml --- string: &70121654388580 !ruby/string str: ! 'foo' @@ -129,32 +129,32 @@ string: &70121654388580 !ruby/string end def test_another_subclass_with_attributes - y = Psych.load Psych.dump Y.new("foo").tap {|o| o.val = 1} + y = Psych.unsafe_load Psych.dump Y.new("foo").tap {|o| o.val = 1} assert_equal "foo", y assert_equal Y, y.class assert_equal 1, y.val end def test_backwards_with_syck - x = Psych.load "--- !str:#{X.name} foo\n\n" + x = Psych.unsafe_load "--- !str:#{X.name} foo\n\n" assert_equal X, x.class assert_equal 'foo', x end def test_empty_subclass assert_match "!ruby/string:#{X}", Psych.dump(X.new) - x = Psych.load Psych.dump X.new + x = Psych.unsafe_load Psych.dump X.new assert_equal X, x.class end def test_empty_character_subclass assert_match "!ruby/string:#{Z}", Psych.dump(Z.new) - x = Psych.load Psych.dump Z.new + x = Psych.unsafe_load Psych.dump Z.new assert_equal Z, x.class end def test_subclass_with_attributes - y = Psych.load Psych.dump Y.new.tap {|o| o.val = 1} + y = Psych.unsafe_load Psych.dump Y.new.tap {|o| o.val = 1} assert_equal Y, y.class assert_equal 1, y.val end diff --git a/test/psych/test_struct.rb b/test/psych/test_struct.rb index 721df44..1479798 100644 --- a/test/psych/test_struct.rb +++ b/test/psych/test_struct.rb @@ -22,7 +22,7 @@ module Psych ss = StructSubclass.new(nil, 'foo') ss.foo = ss - loaded = Psych.load(Psych.dump(ss)) + loaded = Psych.unsafe_load(Psych.dump(ss)) assert_instance_of(StructSubclass, loaded.foo) assert_equal(ss, loaded) @@ -30,14 +30,14 @@ module Psych def test_roundtrip thing = PsychStructWithIvar.new('bar') - struct = Psych.load(Psych.dump(thing)) + struct = Psych.unsafe_load(Psych.dump(thing)) assert_equal 'hello', struct.bar assert_equal 'bar', struct.foo end def test_load - obj = Psych.load(<<-eoyml) + obj = Psych.unsafe_load(<<-eoyml) --- !ruby/struct:PsychStructWithIvar :foo: bar :@bar: hello diff --git a/test/psych/test_yaml.rb b/test/psych/test_yaml.rb index 84cbe26..e12b976 100644 --- a/test/psych/test_yaml.rb +++ b/test/psych/test_yaml.rb @@ -17,7 +17,7 @@ class Psych_Unit_Tests < Psych::TestCase end def test_y_method - assert_raises(NoMethodError) do + assert_raise(NoMethodError) do OpenStruct.new.y 1 end end @@ -573,7 +573,7 @@ EOY end def test_spec_root_mapping - y = Psych::load( <<EOY + y = Psych::unsafe_load( <<EOY # This stream is an example of a top-level mapping. invoice : 34843 date : 2001-01-23 @@ -1077,7 +1077,7 @@ EOY # Read Psych dumped by the ruby 1.8.3. assert_to_yaml( Rational(1, 2), "!ruby/object:Rational 1/2\n" ) - assert_raises( ArgumentError ) { Psych.load("!ruby/object:Rational INVALID/RATIONAL\n") } + assert_raise( ArgumentError ) { Psych.unsafe_load("!ruby/object:Rational INVALID/RATIONAL\n") } end def test_ruby_complex @@ -1089,7 +1089,7 @@ EOY # Read Psych dumped by the ruby 1.8.3. assert_to_yaml( Complex(3, 4), "!ruby/object:Complex 3+4i\n" ) - assert_raises( ArgumentError ) { Psych.load("!ruby/object:Complex INVALID+COMPLEXi\n") } + assert_raise( ArgumentError ) { Psych.unsafe_load("!ruby/object:Complex INVALID+COMPLEXi\n") } end def test_emitting_indicators @@ -1209,7 +1209,7 @@ EOY def test_circular_references a = []; a[0] = a; a[1] = a inspect_str = "[[...], [...]]" - assert_equal( inspect_str, Psych::load(Psych.dump(a)).inspect ) + assert_equal( inspect_str, Psych::unsafe_load(Psych.dump(a)).inspect ) end # @@ -1264,11 +1264,11 @@ EOY end def test_date_out_of_range - Psych::load('1900-01-01T00:00:00+00:00') + Psych::unsafe_load('1900-01-01T00:00:00+00:00') end def test_normal_exit - Psych.load("2000-01-01 00:00:00.#{"0"*1000} +00:00\n") + Psych.unsafe_load("2000-01-01 00:00:00.#{"0"*1000} +00:00\n") # '[ruby-core:13735]' end diff --git a/test/psych/test_yaml_special_cases.rb b/test/psych/test_yaml_special_cases.rb index 4501704..205457b 100644 --- a/test/psych/test_yaml_special_cases.rb +++ b/test/psych/test_yaml_special_cases.rb @@ -13,7 +13,7 @@ module Psych def test_empty_string s = "" - assert_equal false, Psych.load(s) + assert_equal false, Psych.unsafe_load(s) assert_equal [], Psych.load_stream(s) assert_equal false, Psych.parse(s) assert_equal [], Psych.parse_stream(s).transform @@ -58,8 +58,8 @@ module Psych def test_NaN s = ".NaN" - assert Float::NAN, Psych.load(s).nan? - assert [Float::NAN], Psych.load_stream(s).first.nan? + assert Psych.load(s).nan? + assert Psych.load_stream(s).first.nan? assert Psych.parse(s).transform.nan? assert Psych.parse_stream(s).transform.first.nan? assert Psych.safe_load(s).nan? diff --git a/test/psych/test_yamlstore.rb b/test/psych/test_yamlstore.rb index d1e927c..1a1be37 100644 --- a/test/psych/test_yamlstore.rb +++ b/test/psych/test_yamlstore.rb @@ -4,7 +4,22 @@ require 'yaml/store' require 'tmpdir' module Psych - Psych::Store = YAML::Store unless defined?(Psych::Store) + class YAML::Store + alias :old_load :load + + def load(content) + table = YAML.load(content, fallback: false) + if table == false + {} + else + table + end + end + end + + unless defined?(Psych::Store) + Psych::Store = YAML::Store + end class YAMLStoreTest < TestCase def setup @@ -24,61 +39,61 @@ module Psych def test_opening_new_file_in_readonly_mode_should_result_in_empty_values @yamlstore.transaction(true) do - assert_nil @yamlstore[:foo] - assert_nil @yamlstore[:bar] + assert_nil @yamlstore["foo"] + assert_nil @yamlstore["bar"] end end def test_opening_new_file_in_readwrite_mode_should_result_in_empty_values @yamlstore.transaction do - assert_nil @yamlstore[:foo] - assert_nil @yamlstore[:bar] + assert_nil @yamlstore["foo"] + assert_nil @yamlstore["bar"] end end def test_data_should_be_loaded_correctly_when_in_readonly_mode @yamlstore.transaction do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" end @yamlstore.transaction(true) do - assert_equal "bar", @yamlstore[:foo] + assert_equal "bar", @yamlstore["foo"] end end def test_data_should_be_loaded_correctly_when_in_readwrite_mode @yamlstore.transaction do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" end @yamlstore.transaction do - assert_equal "bar", @yamlstore[:foo] + assert_equal "bar", @yamlstore["foo"] end end def test_changes_after_commit_are_discarded @yamlstore.transaction do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" @yamlstore.commit - @yamlstore[:foo] = "baz" + @yamlstore["foo"] = "baz" end @yamlstore.transaction(true) do - assert_equal "bar", @yamlstore[:foo] + assert_equal "bar", @yamlstore["foo"] end end def test_changes_are_not_written_on_abort @yamlstore.transaction do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" @yamlstore.abort end @yamlstore.transaction(true) do - assert_nil @yamlstore[:foo] + assert_nil @yamlstore["foo"] end end def test_writing_inside_readonly_transaction_raises_error - assert_raises(PStore::Error) do + assert_raise(PStore::Error) do @yamlstore.transaction(true) do - @yamlstore[:foo] = "bar" + @yamlstore["foo"] = "bar" end end end diff --git a/test/psych/visitors/test_to_ruby.rb b/test/psych/visitors/test_to_ruby.rb index e1a0056..3d4608b 100644 --- a/test/psych/visitors/test_to_ruby.rb +++ b/test/psych/visitors/test_to_ruby.rb @@ -20,13 +20,13 @@ module Psych end def test_tz_00_00_loads_without_error - assert Psych.load('1900-01-01T00:00:00+00:00') + assert Psych.unsafe_load('1900-01-01T00:00:00+00:00') end def test_legacy_struct Struct.send(:remove_const, :AWESOME) if Struct.const_defined?(:AWESOME) foo = Struct.new('AWESOME', :bar) - assert_equal foo.new('baz'), Psych.load(<<-eoyml) + assert_equal foo.new('baz'), Psych.unsafe_load(<<-eoyml) !ruby/struct:AWESOME bar: baz eoyml diff --git a/test/psych/visitors/test_yaml_tree.rb b/test/psych/visitors/test_yaml_tree.rb index 69885ee..4c48670 100644 --- a/test/psych/visitors/test_yaml_tree.rb +++ b/test/psych/visitors/test_yaml_tree.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require 'psych/helper' +require 'delegate' module Psych module Visitors @@ -62,19 +63,19 @@ module Psych def test_struct_anon s = Struct.new(:foo).new('bar') - obj = Psych.load(Psych.dump(s)) + obj = Psych.unsafe_load(Psych.dump(s)) assert_equal s.foo, obj.foo end def test_override_method s = Struct.new(:method).new('override') - obj = Psych.load(Psych.dump(s)) + obj = Psych.unsafe_load(Psych.dump(s)) assert_equal s.method, obj.method end def test_exception ex = Exception.new 'foo' - loaded = Psych.load(Psych.dump(ex)) + loaded = Psych.unsafe_load(Psych.dump(ex)) assert_equal ex.message, loaded.message assert_equal ex.class, loaded.class @@ -88,7 +89,7 @@ module Psych def test_time t = Time.now - assert_equal t, Psych.load(Psych.dump(t)) + assert_equal t, Psych.unsafe_load(Psych.dump(t)) end def test_date @@ -127,11 +128,11 @@ module Psych end def test_anon_class - assert_raises(TypeError) do + assert_raise(TypeError) do @v.accept Class.new end - assert_raises(TypeError) do + assert_raise(TypeError) do Psych.dump(Class.new) end end |