diff options
Diffstat (limited to 'spec/ruby/core')
833 files changed, 19705 insertions, 7543 deletions
diff --git a/spec/ruby/core/argf/readpartial_spec.rb b/spec/ruby/core/argf/readpartial_spec.rb index 5e284b3423..ea4301f25c 100644 --- a/spec/ruby/core/argf/readpartial_spec.rb +++ b/spec/ruby/core/argf/readpartial_spec.rb @@ -29,7 +29,7 @@ describe "ARGF.readpartial" do it "clears output buffer even if EOFError is raised because @argf is at end" do begin - output = "to be cleared" + output = +"to be cleared" argf [@file1_name] do @argf.read @@ -69,7 +69,7 @@ describe "ARGF.readpartial" do print ARGF.readpartial(#{@stdin.size}) ARGF.readpartial(1) rescue print $!.class STR - stdin = ruby_exe(ruby_str, args: "< #{@stdin_name}", escape: true) + stdin = ruby_exe(ruby_str, args: "< #{@stdin_name}") stdin.should == @stdin + "EOFError" end end diff --git a/spec/ruby/core/argf/shared/getc.rb b/spec/ruby/core/argf/shared/getc.rb index 8be39c60b6..d63372d9d7 100644 --- a/spec/ruby/core/argf/shared/getc.rb +++ b/spec/ruby/core/argf/shared/getc.rb @@ -9,7 +9,7 @@ describe :argf_getc, shared: true do it "reads each char of files" do argf [@file1, @file2] do - chars = "" + chars = +"" @chars.size.times { chars << @argf.send(@method) } chars.should == @chars end diff --git a/spec/ruby/core/argf/shared/read.rb b/spec/ruby/core/argf/shared/read.rb index fe903983c0..e76d022139 100644 --- a/spec/ruby/core/argf/shared/read.rb +++ b/spec/ruby/core/argf/shared/read.rb @@ -15,7 +15,7 @@ describe :argf_read, shared: true do it "treats second argument as an output buffer" do argf [@file1_name] do - buffer = "" + buffer = +"" @argf.send(@method, @file1.size, buffer) buffer.should == @file1 end @@ -23,7 +23,7 @@ describe :argf_read, shared: true do it "clears output buffer before appending to it" do argf [@file1_name] do - buffer = "to be cleared" + buffer = +"to be cleared" @argf.send(@method, @file1.size, buffer) buffer.should == @file1 end diff --git a/spec/ruby/core/array/assoc_spec.rb b/spec/ruby/core/array/assoc_spec.rb index af95528281..f0be3de795 100644 --- a/spec/ruby/core/array/assoc_spec.rb +++ b/spec/ruby/core/array/assoc_spec.rb @@ -37,4 +37,16 @@ describe "Array#assoc" do a.assoc(s1.first).should equal(s1) a.assoc(s2.first).should equal(s2) end + + it "calls to_ary on non-array elements" do + s1 = [1, 2] + s2 = ArraySpecs::ArrayConvertible.new(2, 3) + a = [s1, s2] + + s1.should_not_receive(:to_ary) + a.assoc(s1.first).should equal(s1) + + a.assoc(2).should == [2, 3] + s2.called.should equal(:to_ary) + end end diff --git a/spec/ruby/core/array/bsearch_index_spec.rb b/spec/ruby/core/array/bsearch_index_spec.rb index df2c7c098e..94d85b37f3 100644 --- a/spec/ruby/core/array/bsearch_index_spec.rb +++ b/spec/ruby/core/array/bsearch_index_spec.rb @@ -63,10 +63,6 @@ describe "Array#bsearch_index" do @array.bsearch_index { |x| -1 }.should be_nil end - it "returns the middle element when block always returns zero" do - @array.bsearch_index { |x| 0 }.should == 2 - end - context "magnitude does not effect the result" do it "returns the index of any matched elements where element is between 4n <= xn < 8n" do [1, 2].should include(@array.bsearch_index { |x| (1 - x / 4) * (2**100) }) diff --git a/spec/ruby/core/array/drop_spec.rb b/spec/ruby/core/array/drop_spec.rb index 0ea748e47d..5926c291b8 100644 --- a/spec/ruby/core/array/drop_spec.rb +++ b/spec/ruby/core/array/drop_spec.rb @@ -7,7 +7,7 @@ describe "Array#drop" do end it "raises an ArgumentError if the number of elements specified is negative" do - -> { [1, 2].drop(-3) }.should raise_error(ArgumentError) + -> { [1, 2].drop(-3) }.should raise_error(ArgumentError) end it "returns an empty Array if all elements are dropped" do diff --git a/spec/ruby/core/array/each_spec.rb b/spec/ruby/core/array/each_spec.rb index 57d6082f01..f4b5b758d0 100644 --- a/spec/ruby/core/array/each_spec.rb +++ b/spec/ruby/core/array/each_spec.rb @@ -7,7 +7,7 @@ require_relative '../enumerable/shared/enumeratorized' # Mutating the array while it is being iterated is discouraged as it can result in confusing behavior. # Yet a Ruby implementation must not crash in such a case, and following the simple CRuby behavior makes sense. # CRuby simply reads the array storage and checks the size for every iteration; -# like `i = 0; while i < size; yield self[i]; end` +# like `i = 0; while i < size; yield self[i]; i += 1; end` describe "Array#each" do it "yields each element to the block" do diff --git a/spec/ruby/core/array/fetch_values_spec.rb b/spec/ruby/core/array/fetch_values_spec.rb new file mode 100644 index 0000000000..cf377b3b71 --- /dev/null +++ b/spec/ruby/core/array/fetch_values_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#fetch_values" do + before :each do + @array = [:a, :b, :c] + end + + ruby_version_is "3.4" do + describe "with matched indexes" do + it "returns the values for indexes" do + @array.fetch_values(0).should == [:a] + @array.fetch_values(0, 2).should == [:a, :c] + @array.fetch_values(-1).should == [:c] + end + + it "returns the values for indexes ordered in the order of the requested indexes" do + @array.fetch_values(2, 0).should == [:c, :a] + end + end + + describe "with unmatched indexes" do + it "raises a index error if no block is provided" do + -> { @array.fetch_values(0, 1, 44) }.should raise_error(IndexError, "index 44 outside of array bounds: -3...3") + end + + it "returns the default value from block" do + @array.fetch_values(44) { |index| "`#{index}' is not found" }.should == ["`44' is not found"] + @array.fetch_values(0, 44) { |index| "`#{index}' is not found" }.should == [:a, "`44' is not found"] + end + end + + describe "without keys" do + it "returns an empty Array" do + @array.fetch_values.should == [] + end + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + @array.fetch_values(obj).should == [:c] + end + + it "does not support a Range object as argument" do + -> { + @array.fetch_values(1..2) + }.should raise_error(TypeError, "no implicit conversion of Range into Integer") + end + + it "raises a TypeError when the passed argument can't be coerced to Integer" do + -> { [].fetch_values("cat") }.should raise_error(TypeError, "no implicit conversion of String into Integer") + end + end +end diff --git a/spec/ruby/core/array/fill_spec.rb b/spec/ruby/core/array/fill_spec.rb index 02360e550d..2c3b5d9e84 100644 --- a/spec/ruby/core/array/fill_spec.rb +++ b/spec/ruby/core/array/fill_spec.rb @@ -21,7 +21,7 @@ describe "Array#fill" do it "does not replicate the filler" do ary = [1, 2, 3, 4] - str = "x" + str = +"x" ary.fill(str).should == [str, str, str, str] str << "y" ary.should == [str, str, str, str] diff --git a/spec/ruby/core/array/fixtures/classes.rb b/spec/ruby/core/array/fixtures/classes.rb index 8596245fb8..05283c0f74 100644 --- a/spec/ruby/core/array/fixtures/classes.rb +++ b/spec/ruby/core/array/fixtures/classes.rb @@ -56,23 +56,20 @@ module ArraySpecs 101.621, 102.816, 104.010, 105.202, 106.393, 107.583, 108.771, 109.958, 111.144, 112.329, 113.512, 114.695, 115.876, 117.057, 118.236, 119.414, 120.591, 121.767, 122.942, 124.116, 125.289, 126.462, 127.633, 128.803, 129.973, 131.141, 132.309, 133.476, 134.642, 135.807, - ] + ] def self.measure_sample_fairness(size, samples, iters) ary = Array.new(size) { |x| x } + expected = iters.fdiv size (samples).times do |i| chi_results = [] 3.times do - counts = Array.new(size) { 0 } - expected = iters / size + counts = Array.new(size, 0) iters.times do x = ary.sample(samples)[i] counts[x] += 1 end - chi_squared = 0.0 - counts.each do |count| - chi_squared += (((count - expected) ** 2) * 1.0 / expected) - end + chi_squared = counts.sum {|count| (count - expected) ** 2} / expected chi_results << chi_squared break if chi_squared <= CHI_SQUARED_CRITICAL_VALUES[size] end @@ -83,17 +80,14 @@ module ArraySpecs def self.measure_sample_fairness_large_sample_size(size, samples, iters) ary = Array.new(size) { |x| x } - counts = Array.new(size) { 0 } - expected = iters * samples / size + counts = Array.new(size, 0) + expected = (iters * samples).fdiv size iters.times do ary.sample(samples).each do |sample| counts[sample] += 1 end end - chi_squared = 0.0 - counts.each do |count| - chi_squared += (((count - expected) ** 2) * 1.0 / expected) - end + chi_squared = counts.sum {|count| (count - expected) ** 2} / expected # Chi squared critical values for tests with 4 degrees of freedom # Values obtained from NIST Engineering Statistic Handbook at @@ -223,366 +217,370 @@ module ArraySpecs obj end - LargeArray = ["test_create_table_with_force_true_does_not_drop_nonexisting_table", - "test_add_table", - "assert_difference", - "assert_operator", - "instance_variables", - "class", - "instance_variable_get", - "__class__", - "expects", - "assert_no_difference", - "name", - "assert_blank", - "assert_not_same", - "is_a?", - "test_add_table_with_decimals", - "test_create_table_with_timestamps_should_create_datetime_columns", - "assert_present", - "assert_no_match", - "__instance_of__", - "assert_deprecated", - "assert", - "assert_throws", - "kind_of?", - "try", - "__instance_variable_get__", - "object_id", - "timeout", - "instance_variable_set", - "assert_nothing_thrown", - "__instance_variable_set__", - "copy_object", - "test_create_table_with_timestamps_should_create_datetime_columns_with_options", - "assert_not_deprecated", - "assert_in_delta", - "id", - "copy_metaclass", - "test_create_table_without_a_block", - "dup", - "assert_not_nil", - "send", - "__instance_variables__", - "to_sql", - "mock", - "assert_send", - "instance_variable_defined?", - "clone", - "require", - "test_migrator", - "__instance_variable_defined_eh__", - "frozen?", - "test_add_column_not_null_with_default", - "freeze", - "test_migrator_one_up", - "test_migrator_one_down", - "singleton_methods", - "method_exists?", - "create_fixtures", - "test_migrator_one_up_one_down", - "test_native_decimal_insert_manual_vs_automatic", - "instance_exec", - "__is_a__", - "test_migrator_double_up", - "stub", - "private_methods", - "stubs", - "test_migrator_double_down", - "fixture_path", - "private_singleton_methods", - "stub_everything", - "test_migrator_one_up_with_exception_and_rollback", - "sequence", - "protected_methods", - "enum_for", - "test_finds_migrations", - "run_before_mocha", - "states", - "protected_singleton_methods", - "to_json", - "instance_values", - "==", - "mocha_setup", - "public_methods", - "test_finds_pending_migrations", - "mocha_verify", - "assert_kind_of", - "===", - "=~", - "test_relative_migrations", - "mocha_teardown", - "gem", - "mocha", - "test_only_loads_pending_migrations", - "test_add_column_with_precision_and_scale", - "require_or_load", - "eql?", - "require_dependency", - "test_native_types", - "test_target_version_zero_should_run_only_once", - "extend", - "to_matcher", - "unloadable", - "require_association", - "hash", - "__id__", - "load_dependency", - "equals", - "test_migrator_db_has_no_schema_migrations_table", - "test_migrator_verbosity", - "kind_of", - "to_yaml", - "to_bool", - "test_migrator_verbosity_off", - "taint", - "test_migrator_going_down_due_to_version_target", - "tainted?", - "mocha_inspect", - "test_migrator_rollback", - "vim", - "untaint", - "taguri=", - "test_migrator_forward", - "test_schema_migrations_table_name", - "test_proper_table_name", - "all_of", - "test_add_drop_table_with_prefix_and_suffix", - "_setup_callbacks", - "setup", - "Not", - "test_create_table_with_binary_column", - "assert_not_equal", - "enable_warnings", - "acts_like?", - "Rational", - "_removed_setup_callbacks", - "Table", - "bind", - "any_of", - "__method__", - "test_migrator_with_duplicates", - "_teardown_callbacks", - "method", - "test_migrator_with_duplicate_names", - "_removed_teardown_callbacks", - "any_parameters", - "test_migrator_with_missing_version_numbers", - "test_add_remove_single_field_using_string_arguments", - "test_create_table_with_custom_sequence_name", - "test_add_remove_single_field_using_symbol_arguments", - "_one_time_conditions_valid_14?", - "_one_time_conditions_valid_16?", - "run_callbacks", - "anything", - "silence_warnings", - "instance_variable_names", - "_fixture_path", - "copy_instance_variables_from", - "fixture_path?", - "has_entry", - "__marshal__", - "_fixture_table_names", - "__kind_of__", - "fixture_table_names?", - "test_add_rename", - "assert_equal", - "_fixture_class_names", - "fixture_class_names?", - "has_entries", - "_use_transactional_fixtures", - "people", - "test_rename_column_using_symbol_arguments", - "use_transactional_fixtures?", - "instance_eval", - "blank?", - "with_warnings", - "__nil__", - "load", - "metaclass", - "_use_instantiated_fixtures", - "has_key", - "class_eval", - "present?", - "test_rename_column", - "teardown", - "use_instantiated_fixtures?", - "method_name", - "silence_stderr", - "presence", - "test_rename_column_preserves_default_value_not_null", - "silence_stream", - "_pre_loaded_fixtures", - "__metaclass__", - "__fixnum__", - "pre_loaded_fixtures?", - "has_value", - "suppress", - "to_yaml_properties", - "test_rename_nonexistent_column", - "test_add_index", - "includes", - "find_correlate_in", - "equality_predicate_sql", - "assert_nothing_raised", - "let", - "not_predicate_sql", - "test_rename_column_with_sql_reserved_word", - "singleton_class", - "test_rename_column_with_an_index", - "display", - "taguri", - "to_yaml_style", - "test_remove_column_with_index", - "size", - "current_adapter?", - "test_remove_column_with_multi_column_index", - "respond_to?", - "test_change_type_of_not_null_column", - "is_a", - "to_a", - "test_rename_table_for_sqlite_should_work_with_reserved_words", - "require_library_or_gem", - "setup_fixtures", - "equal?", - "teardown_fixtures", - "nil?", - "fixture_table_names", - "fixture_class_names", - "test_create_table_without_id", - "use_transactional_fixtures", - "test_add_column_with_primary_key_attribute", - "repair_validations", - "use_instantiated_fixtures", - "instance_of?", - "test_create_table_adds_id", - "test_rename_table", - "pre_loaded_fixtures", - "to_enum", - "test_create_table_with_not_null_column", - "instance_of", - "test_change_column_nullability", - "optionally", - "test_rename_table_with_an_index", - "run", - "test_change_column", - "default_test", - "assert_raise", - "test_create_table_with_defaults", - "assert_nil", - "flunk", - "regexp_matches", - "duplicable?", - "reset_mocha", - "stubba_method", - "filter_backtrace", - "test_create_table_with_limits", - "responds_with", - "stubba_object", - "test_change_column_with_nil_default", - "assert_block", - "__show__", - "assert_date_from_db", - "__respond_to_eh__", - "run_in_transaction?", - "inspect", - "assert_sql", - "test_change_column_with_new_default", - "yaml_equivalent", - "build_message", - "to_s", - "test_change_column_default", - "assert_queries", - "pending", - "as_json", - "assert_no_queries", - "test_change_column_quotes_column_names", - "assert_match", - "test_keeping_default_and_notnull_constraint_on_change", - "methods", - "connection_allow_concurrency_setup", - "connection_allow_concurrency_teardown", - "test_create_table_with_primary_key_prefix_as_table_name_with_underscore", - "__send__", - "make_connection", - "assert_raises", - "tap", - "with_kcode", - "assert_instance_of", - "test_create_table_with_primary_key_prefix_as_table_name", - "assert_respond_to", - "test_change_column_default_to_null", - "assert_same", - "__extend__"] - - LargeTestArraySorted = ["test_add_column_not_null_with_default", - "test_add_column_with_precision_and_scale", - "test_add_column_with_primary_key_attribute", - "test_add_drop_table_with_prefix_and_suffix", - "test_add_index", - "test_add_remove_single_field_using_string_arguments", - "test_add_remove_single_field_using_symbol_arguments", - "test_add_rename", - "test_add_table", - "test_add_table_with_decimals", - "test_change_column", - "test_change_column_default", - "test_change_column_default_to_null", - "test_change_column_nullability", - "test_change_column_quotes_column_names", - "test_change_column_with_new_default", - "test_change_column_with_nil_default", - "test_change_type_of_not_null_column", - "test_create_table_adds_id", - "test_create_table_with_binary_column", - "test_create_table_with_custom_sequence_name", - "test_create_table_with_defaults", - "test_create_table_with_force_true_does_not_drop_nonexisting_table", - "test_create_table_with_limits", - "test_create_table_with_not_null_column", - "test_create_table_with_primary_key_prefix_as_table_name", - "test_create_table_with_primary_key_prefix_as_table_name_with_underscore", - "test_create_table_with_timestamps_should_create_datetime_columns", - "test_create_table_with_timestamps_should_create_datetime_columns_with_options", - "test_create_table_without_a_block", - "test_create_table_without_id", - "test_finds_migrations", - "test_finds_pending_migrations", - "test_keeping_default_and_notnull_constraint_on_change", - "test_migrator", - "test_migrator_db_has_no_schema_migrations_table", - "test_migrator_double_down", - "test_migrator_double_up", - "test_migrator_forward", - "test_migrator_going_down_due_to_version_target", - "test_migrator_one_down", - "test_migrator_one_up", - "test_migrator_one_up_one_down", - "test_migrator_one_up_with_exception_and_rollback", - "test_migrator_rollback", - "test_migrator_verbosity", - "test_migrator_verbosity_off", - "test_migrator_with_duplicate_names", - "test_migrator_with_duplicates", - "test_migrator_with_missing_version_numbers", - "test_native_decimal_insert_manual_vs_automatic", - "test_native_types", - "test_only_loads_pending_migrations", - "test_proper_table_name", - "test_relative_migrations", - "test_remove_column_with_index", - "test_remove_column_with_multi_column_index", - "test_rename_column", - "test_rename_column_preserves_default_value_not_null", - "test_rename_column_using_symbol_arguments", - "test_rename_column_with_an_index", - "test_rename_column_with_sql_reserved_word", - "test_rename_nonexistent_column", - "test_rename_table", - "test_rename_table_for_sqlite_should_work_with_reserved_words", - "test_rename_table_with_an_index", - "test_schema_migrations_table_name", - "test_target_version_zero_should_run_only_once"] + LargeArray = [ + "test_create_table_with_force_true_does_not_drop_nonexisting_table", + "test_add_table", + "assert_difference", + "assert_operator", + "instance_variables", + "class", + "instance_variable_get", + "__class__", + "expects", + "assert_no_difference", + "name", + "assert_blank", + "assert_not_same", + "is_a?", + "test_add_table_with_decimals", + "test_create_table_with_timestamps_should_create_datetime_columns", + "assert_present", + "assert_no_match", + "__instance_of__", + "assert_deprecated", + "assert", + "assert_throws", + "kind_of?", + "try", + "__instance_variable_get__", + "object_id", + "timeout", + "instance_variable_set", + "assert_nothing_thrown", + "__instance_variable_set__", + "copy_object", + "test_create_table_with_timestamps_should_create_datetime_columns_with_options", + "assert_not_deprecated", + "assert_in_delta", + "id", + "copy_metaclass", + "test_create_table_without_a_block", + "dup", + "assert_not_nil", + "send", + "__instance_variables__", + "to_sql", + "mock", + "assert_send", + "instance_variable_defined?", + "clone", + "require", + "test_migrator", + "__instance_variable_defined_eh__", + "frozen?", + "test_add_column_not_null_with_default", + "freeze", + "test_migrator_one_up", + "test_migrator_one_down", + "singleton_methods", + "method_exists?", + "create_fixtures", + "test_migrator_one_up_one_down", + "test_native_decimal_insert_manual_vs_automatic", + "instance_exec", + "__is_a__", + "test_migrator_double_up", + "stub", + "private_methods", + "stubs", + "test_migrator_double_down", + "fixture_path", + "private_singleton_methods", + "stub_everything", + "test_migrator_one_up_with_exception_and_rollback", + "sequence", + "protected_methods", + "enum_for", + "test_finds_migrations", + "run_before_mocha", + "states", + "protected_singleton_methods", + "to_json", + "instance_values", + "==", + "mocha_setup", + "public_methods", + "test_finds_pending_migrations", + "mocha_verify", + "assert_kind_of", + "===", + "=~", + "test_relative_migrations", + "mocha_teardown", + "gem", + "mocha", + "test_only_loads_pending_migrations", + "test_add_column_with_precision_and_scale", + "require_or_load", + "eql?", + "require_dependency", + "test_native_types", + "test_target_version_zero_should_run_only_once", + "extend", + "to_matcher", + "unloadable", + "require_association", + "hash", + "__id__", + "load_dependency", + "equals", + "test_migrator_db_has_no_schema_migrations_table", + "test_migrator_verbosity", + "kind_of", + "to_yaml", + "to_bool", + "test_migrator_verbosity_off", + "taint", + "test_migrator_going_down_due_to_version_target", + "tainted?", + "mocha_inspect", + "test_migrator_rollback", + "vim", + "untaint", + "taguri=", + "test_migrator_forward", + "test_schema_migrations_table_name", + "test_proper_table_name", + "all_of", + "test_add_drop_table_with_prefix_and_suffix", + "_setup_callbacks", + "setup", + "Not", + "test_create_table_with_binary_column", + "assert_not_equal", + "enable_warnings", + "acts_like?", + "Rational", + "_removed_setup_callbacks", + "Table", + "bind", + "any_of", + "__method__", + "test_migrator_with_duplicates", + "_teardown_callbacks", + "method", + "test_migrator_with_duplicate_names", + "_removed_teardown_callbacks", + "any_parameters", + "test_migrator_with_missing_version_numbers", + "test_add_remove_single_field_using_string_arguments", + "test_create_table_with_custom_sequence_name", + "test_add_remove_single_field_using_symbol_arguments", + "_one_time_conditions_valid_14?", + "_one_time_conditions_valid_16?", + "run_callbacks", + "anything", + "silence_warnings", + "instance_variable_names", + "_fixture_path", + "copy_instance_variables_from", + "fixture_path?", + "has_entry", + "__marshal__", + "_fixture_table_names", + "__kind_of__", + "fixture_table_names?", + "test_add_rename", + "assert_equal", + "_fixture_class_names", + "fixture_class_names?", + "has_entries", + "_use_transactional_fixtures", + "people", + "test_rename_column_using_symbol_arguments", + "use_transactional_fixtures?", + "instance_eval", + "blank?", + "with_warnings", + "__nil__", + "load", + "metaclass", + "_use_instantiated_fixtures", + "has_key", + "class_eval", + "present?", + "test_rename_column", + "teardown", + "use_instantiated_fixtures?", + "method_name", + "silence_stderr", + "presence", + "test_rename_column_preserves_default_value_not_null", + "silence_stream", + "_pre_loaded_fixtures", + "__metaclass__", + "__fixnum__", + "pre_loaded_fixtures?", + "has_value", + "suppress", + "to_yaml_properties", + "test_rename_nonexistent_column", + "test_add_index", + "includes", + "find_correlate_in", + "equality_predicate_sql", + "assert_nothing_raised", + "let", + "not_predicate_sql", + "test_rename_column_with_sql_reserved_word", + "singleton_class", + "test_rename_column_with_an_index", + "display", + "taguri", + "to_yaml_style", + "test_remove_column_with_index", + "size", + "current_adapter?", + "test_remove_column_with_multi_column_index", + "respond_to?", + "test_change_type_of_not_null_column", + "is_a", + "to_a", + "test_rename_table_for_sqlite_should_work_with_reserved_words", + "require_library_or_gem", + "setup_fixtures", + "equal?", + "teardown_fixtures", + "nil?", + "fixture_table_names", + "fixture_class_names", + "test_create_table_without_id", + "use_transactional_fixtures", + "test_add_column_with_primary_key_attribute", + "repair_validations", + "use_instantiated_fixtures", + "instance_of?", + "test_create_table_adds_id", + "test_rename_table", + "pre_loaded_fixtures", + "to_enum", + "test_create_table_with_not_null_column", + "instance_of", + "test_change_column_nullability", + "optionally", + "test_rename_table_with_an_index", + "run", + "test_change_column", + "default_test", + "assert_raise", + "test_create_table_with_defaults", + "assert_nil", + "flunk", + "regexp_matches", + "duplicable?", + "reset_mocha", + "stubba_method", + "filter_backtrace", + "test_create_table_with_limits", + "responds_with", + "stubba_object", + "test_change_column_with_nil_default", + "assert_block", + "__show__", + "assert_date_from_db", + "__respond_to_eh__", + "run_in_transaction?", + "inspect", + "assert_sql", + "test_change_column_with_new_default", + "yaml_equivalent", + "build_message", + "to_s", + "test_change_column_default", + "assert_queries", + "pending", + "as_json", + "assert_no_queries", + "test_change_column_quotes_column_names", + "assert_match", + "test_keeping_default_and_notnull_constraint_on_change", + "methods", + "connection_allow_concurrency_setup", + "connection_allow_concurrency_teardown", + "test_create_table_with_primary_key_prefix_as_table_name_with_underscore", + "__send__", + "make_connection", + "assert_raises", + "tap", + "with_kcode", + "assert_instance_of", + "test_create_table_with_primary_key_prefix_as_table_name", + "assert_respond_to", + "test_change_column_default_to_null", + "assert_same", + "__extend__", + ] + + LargeTestArraySorted = [ + "test_add_column_not_null_with_default", + "test_add_column_with_precision_and_scale", + "test_add_column_with_primary_key_attribute", + "test_add_drop_table_with_prefix_and_suffix", + "test_add_index", + "test_add_remove_single_field_using_string_arguments", + "test_add_remove_single_field_using_symbol_arguments", + "test_add_rename", + "test_add_table", + "test_add_table_with_decimals", + "test_change_column", + "test_change_column_default", + "test_change_column_default_to_null", + "test_change_column_nullability", + "test_change_column_quotes_column_names", + "test_change_column_with_new_default", + "test_change_column_with_nil_default", + "test_change_type_of_not_null_column", + "test_create_table_adds_id", + "test_create_table_with_binary_column", + "test_create_table_with_custom_sequence_name", + "test_create_table_with_defaults", + "test_create_table_with_force_true_does_not_drop_nonexisting_table", + "test_create_table_with_limits", + "test_create_table_with_not_null_column", + "test_create_table_with_primary_key_prefix_as_table_name", + "test_create_table_with_primary_key_prefix_as_table_name_with_underscore", + "test_create_table_with_timestamps_should_create_datetime_columns", + "test_create_table_with_timestamps_should_create_datetime_columns_with_options", + "test_create_table_without_a_block", + "test_create_table_without_id", + "test_finds_migrations", + "test_finds_pending_migrations", + "test_keeping_default_and_notnull_constraint_on_change", + "test_migrator", + "test_migrator_db_has_no_schema_migrations_table", + "test_migrator_double_down", + "test_migrator_double_up", + "test_migrator_forward", + "test_migrator_going_down_due_to_version_target", + "test_migrator_one_down", + "test_migrator_one_up", + "test_migrator_one_up_one_down", + "test_migrator_one_up_with_exception_and_rollback", + "test_migrator_rollback", + "test_migrator_verbosity", + "test_migrator_verbosity_off", + "test_migrator_with_duplicate_names", + "test_migrator_with_duplicates", + "test_migrator_with_missing_version_numbers", + "test_native_decimal_insert_manual_vs_automatic", + "test_native_types", + "test_only_loads_pending_migrations", + "test_proper_table_name", + "test_relative_migrations", + "test_remove_column_with_index", + "test_remove_column_with_multi_column_index", + "test_rename_column", + "test_rename_column_preserves_default_value_not_null", + "test_rename_column_using_symbol_arguments", + "test_rename_column_with_an_index", + "test_rename_column_with_sql_reserved_word", + "test_rename_nonexistent_column", + "test_rename_table", + "test_rename_table_for_sqlite_should_work_with_reserved_words", + "test_rename_table_with_an_index", + "test_schema_migrations_table_name", + "test_target_version_zero_should_run_only_once", + ] class PrivateToAry private diff --git a/spec/ruby/core/array/fixtures/encoded_strings.rb b/spec/ruby/core/array/fixtures/encoded_strings.rb index 5b85bd0e06..b5888d86ae 100644 --- a/spec/ruby/core/array/fixtures/encoded_strings.rb +++ b/spec/ruby/core/array/fixtures/encoded_strings.rb @@ -2,14 +2,14 @@ module ArraySpecs def self.array_with_usascii_and_7bit_utf8_strings [ - 'foo'.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('US-ASCII'), 'bar' ] end def self.array_with_usascii_and_utf8_strings [ - 'foo'.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('US-ASCII'), 'báz' ] end @@ -17,7 +17,7 @@ module ArraySpecs def self.array_with_7bit_utf8_and_usascii_strings [ 'bar', - 'foo'.force_encoding('US-ASCII') + 'foo'.dup.force_encoding('US-ASCII') ] end @@ -25,13 +25,13 @@ module ArraySpecs [ 'báz', 'bar', - 'foo'.force_encoding('US-ASCII') + 'foo'.dup.force_encoding('US-ASCII') ] end def self.array_with_usascii_and_utf8_strings [ - 'foo'.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('US-ASCII'), 'bar', 'báz' ] @@ -41,7 +41,7 @@ module ArraySpecs [ 'bar', 'báz', - 'foo'.force_encoding('BINARY') + 'foo'.dup.force_encoding('BINARY') ] end @@ -55,14 +55,14 @@ module ArraySpecs def self.array_with_usascii_and_7bit_binary_strings [ - 'bar'.force_encoding('US-ASCII'), - 'foo'.force_encoding('BINARY') + 'bar'.dup.force_encoding('US-ASCII'), + 'foo'.dup.force_encoding('BINARY') ] end def self.array_with_usascii_and_binary_strings [ - 'bar'.force_encoding('US-ASCII'), + 'bar'.dup.force_encoding('US-ASCII'), [255].pack('C').force_encoding('BINARY') ] end diff --git a/spec/ruby/core/array/intersect_spec.rb b/spec/ruby/core/array/intersect_spec.rb index 62ac157278..456aa26c6e 100644 --- a/spec/ruby/core/array/intersect_spec.rb +++ b/spec/ruby/core/array/intersect_spec.rb @@ -2,65 +2,63 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe 'Array#intersect?' do - ruby_version_is '3.1' do # https://bugs.ruby-lang.org/issues/15198 - describe 'when at least one element in two Arrays is the same' do - it 'returns true' do - [1, 2].intersect?([2, 3, 4]).should == true - [2, 3, 4].intersect?([1, 2]).should == true - end + describe 'when at least one element in two Arrays is the same' do + it 'returns true' do + [1, 2].intersect?([2, 3, 4]).should == true + [2, 3, 4].intersect?([1, 2]).should == true end + end - describe 'when there are no elements in common between two Arrays' do - it 'returns false' do - [0, 1, 2].intersect?([3, 4]).should == false - [3, 4].intersect?([0, 1, 2]).should == false - [3, 4].intersect?([]).should == false - [].intersect?([0, 1, 2]).should == false - end + describe 'when there are no elements in common between two Arrays' do + it 'returns false' do + [0, 1, 2].intersect?([3, 4]).should == false + [3, 4].intersect?([0, 1, 2]).should == false + [3, 4].intersect?([]).should == false + [].intersect?([0, 1, 2]).should == false end + end - it "tries to convert the passed argument to an Array using #to_ary" do - obj = mock('[1,2,3]') - obj.should_receive(:to_ary).and_return([1, 2, 3]) + it "tries to convert the passed argument to an Array using #to_ary" do + obj = mock('[1,2,3]') + obj.should_receive(:to_ary).and_return([1, 2, 3]) - [1, 2].intersect?(obj).should == true - end + [1, 2].intersect?(obj).should == true + end - it "determines equivalence between elements in the sense of eql?" do - obj1 = mock('1') - obj2 = mock('2') - obj1.stub!(:hash).and_return(0) - obj2.stub!(:hash).and_return(0) - obj1.stub!(:eql?).and_return(true) - obj2.stub!(:eql?).and_return(true) + it "determines equivalence between elements in the sense of eql?" do + obj1 = mock('1') + obj2 = mock('2') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj1.stub!(:eql?).and_return(true) + obj2.stub!(:eql?).and_return(true) - [obj1].intersect?([obj2]).should == true + [obj1].intersect?([obj2]).should == true - obj1 = mock('3') - obj2 = mock('4') - obj1.stub!(:hash).and_return(0) - obj2.stub!(:hash).and_return(0) - obj1.stub!(:eql?).and_return(false) - obj2.stub!(:eql?).and_return(false) + obj1 = mock('3') + obj2 = mock('4') + obj1.stub!(:hash).and_return(0) + obj2.stub!(:hash).and_return(0) + obj1.stub!(:eql?).and_return(false) + obj2.stub!(:eql?).and_return(false) - [obj1].intersect?([obj2]).should == false - end + [obj1].intersect?([obj2]).should == false + end - it "does not call to_ary on array subclasses" do - [5, 6].intersect?(ArraySpecs::ToAryArray[1, 2, 5, 6]).should == true - end + it "does not call to_ary on array subclasses" do + [5, 6].intersect?(ArraySpecs::ToAryArray[1, 2, 5, 6]).should == true + end - it "properly handles an identical item even when its #eql? isn't reflexive" do - x = mock('x') - x.stub!(:hash).and_return(42) - x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + it "properly handles an identical item even when its #eql? isn't reflexive" do + x = mock('x') + x.stub!(:hash).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. - [x].intersect?([x]).should == true - end + [x].intersect?([x]).should == true + end - it "has semantic of !(a & b).empty?" do - [].intersect?([]).should == false - [nil].intersect?([nil]).should == true - end + it "has semantic of !(a & b).empty?" do + [].intersect?([]).should == false + [nil].intersect?([nil]).should == true end end diff --git a/spec/ruby/core/array/pack/a_spec.rb b/spec/ruby/core/array/pack/a_spec.rb index f4a40502c2..8245cd5470 100644 --- a/spec/ruby/core/array/pack/a_spec.rb +++ b/spec/ruby/core/array/pack/a_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' @@ -27,7 +27,7 @@ describe "Array#pack with format 'A'" do ["abc"].pack("A*").should == "abc" end - it "padds the output with spaces when the count exceeds the size of the String" do + it "pads the output with spaces when the count exceeds the size of the String" do ["abc"].pack("A6").should == "abc " end @@ -55,7 +55,7 @@ describe "Array#pack with format 'a'" do ["abc"].pack("a*").should == "abc" end - it "padds the output with NULL bytes when the count exceeds the size of the String" do + it "pads the output with NULL bytes when the count exceeds the size of the String" do ["abc"].pack("a6").should == "abc\x00\x00\x00" end diff --git a/spec/ruby/core/array/pack/at_spec.rb b/spec/ruby/core/array/pack/at_spec.rb index 3942677913..bb9801440a 100644 --- a/spec/ruby/core/array/pack/at_spec.rb +++ b/spec/ruby/core/array/pack/at_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/b_spec.rb b/spec/ruby/core/array/pack/b_spec.rb index ec82b7d1ab..247a9ca023 100644 --- a/spec/ruby/core/array/pack/b_spec.rb +++ b/spec/ruby/core/array/pack/b_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/buffer_spec.rb b/spec/ruby/core/array/pack/buffer_spec.rb index ecb40bfd06..b77b2d1efa 100644 --- a/spec/ruby/core/array/pack/buffer_spec.rb +++ b/spec/ruby/core/array/pack/buffer_spec.rb @@ -13,13 +13,13 @@ describe "Array#pack with :buffer option" do it "adds result at the end of buffer content" do n = [ 65, 66, 67 ] # result without buffer is "ABC" - buffer = "" + buffer = +"" n.pack("ccc", buffer: buffer).should == "ABC" - buffer = "123" + buffer = +"123" n.pack("ccc", buffer: buffer).should == "123ABC" - buffer = "12345" + buffer = +"12345" n.pack("ccc", buffer: buffer).should == "12345ABC" end @@ -28,22 +28,32 @@ describe "Array#pack with :buffer option" do TypeError, "buffer must be String, not Array") end + it "raise FrozenError if buffer is frozen" do + -> { [65].pack("c", buffer: "frozen-string".freeze) }.should raise_error(FrozenError) + end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + [65, 66, 67].pack("ccc", buffer: buffer) + buffer.encoding.should == Encoding::ISO_8859_1 + end + context "offset (@) is specified" do it 'keeps buffer content if it is longer than offset' do n = [ 65, 66, 67 ] - buffer = "123456" + buffer = +"123456" n.pack("@3ccc", buffer: buffer).should == "123ABC" end it "fills the gap with \\0 if buffer content is shorter than offset" do n = [ 65, 66, 67 ] - buffer = "123" + buffer = +"123" n.pack("@6ccc", buffer: buffer).should == "123\0\0\0ABC" end it 'does not keep buffer content if it is longer than offset + result' do n = [ 65, 66, 67 ] - buffer = "1234567890" + buffer = +"1234567890" n.pack("@3ccc", buffer: buffer).should == "123ABC" end end diff --git a/spec/ruby/core/array/pack/c_spec.rb b/spec/ruby/core/array/pack/c_spec.rb index ac133ff9b6..47b71b663d 100644 --- a/spec/ruby/core/array/pack/c_spec.rb +++ b/spec/ruby/core/array/pack/c_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/array/pack/comment_spec.rb b/spec/ruby/core/array/pack/comment_spec.rb index 254c827ccc..daf1cff06a 100644 --- a/spec/ruby/core/array/pack/comment_spec.rb +++ b/spec/ruby/core/array/pack/comment_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/array/pack/h_spec.rb b/spec/ruby/core/array/pack/h_spec.rb index 2c1dac8d4a..ba188874ba 100644 --- a/spec/ruby/core/array/pack/h_spec.rb +++ b/spec/ruby/core/array/pack/h_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/l_spec.rb b/spec/ruby/core/array/pack/l_spec.rb index b446a7a36a..f6dfb1da83 100644 --- a/spec/ruby/core/array/pack/l_spec.rb +++ b/spec/ruby/core/array/pack/l_spec.rb @@ -29,7 +29,7 @@ describe "Array#pack with format 'L'" do it_behaves_like :array_pack_32bit_be, 'L>' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "with modifier '<' and '_'" do it_behaves_like :array_pack_32bit_le, 'L<_' it_behaves_like :array_pack_32bit_le, 'L_<' @@ -51,7 +51,7 @@ describe "Array#pack with format 'L'" do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "with modifier '<' and '_'" do it_behaves_like :array_pack_64bit_le, 'L<_' it_behaves_like :array_pack_64bit_le, 'L_<' @@ -83,7 +83,7 @@ describe "Array#pack with format 'l'" do it_behaves_like :array_pack_32bit_be, 'l>' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "with modifier '<' and '_'" do it_behaves_like :array_pack_32bit_le, 'l<_' it_behaves_like :array_pack_32bit_le, 'l_<' @@ -105,7 +105,7 @@ describe "Array#pack with format 'l'" do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "with modifier '<' and '_'" do it_behaves_like :array_pack_64bit_le, 'l<_' it_behaves_like :array_pack_64bit_le, 'l_<' @@ -137,7 +137,7 @@ little_endian do it_behaves_like :array_pack_32bit_le, 'l' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "Array#pack with format 'L' with modifier '_'" do it_behaves_like :array_pack_32bit_le, 'L_' end @@ -155,7 +155,7 @@ little_endian do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "Array#pack with format 'L' with modifier '_'" do it_behaves_like :array_pack_64bit_le, 'L_' end @@ -183,7 +183,7 @@ big_endian do it_behaves_like :array_pack_32bit_be, 'l' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "Array#pack with format 'L' with modifier '_'" do it_behaves_like :array_pack_32bit_be, 'L_' end @@ -201,7 +201,7 @@ big_endian do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "Array#pack with format 'L' with modifier '_'" do it_behaves_like :array_pack_64bit_be, 'L_' end diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb index c6364af12d..a80f91275c 100644 --- a/spec/ruby/core/array/pack/m_spec.rb +++ b/spec/ruby/core/array/pack/m_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/shared/basic.rb b/spec/ruby/core/array/pack/shared/basic.rb index 5e3eea55f9..a63f64d296 100644 --- a/spec/ruby/core/array/pack/shared/basic.rb +++ b/spec/ruby/core/array/pack/shared/basic.rb @@ -32,34 +32,21 @@ describe :array_pack_basic_non_float, shared: true do [@obj, @obj, @obj, @obj].pack("aa #{pack_format} # some comment \n#{pack_format}").should be_an_instance_of(String) end - ruby_version_is ""..."3.2" do - it "warns in verbose mode that a directive is unknown" do - # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a R" + pack_format) }.should complain(/unknown pack directive 'R'/, verbose: true) - -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0'/, verbose: true) - -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':'/, verbose: true) - end - end - - ruby_version_is "3.2"..."3.3" do - # https://bugs.ruby-lang.org/issues/19150 - # NOTE: it's just a plan of the Ruby core team + ruby_version_is ""..."3.3" do it "warns that a directive is unknown" do # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a R" + pack_format) }.should complain(/unknown pack directive 'R'/) - -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0'/) - -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':'/) + -> { [@obj, @obj].pack("a K" + pack_format) }.should complain(/unknown pack directive 'K' in 'a K#{pack_format}'/) + -> { [@obj, @obj].pack("a 0" + pack_format) }.should complain(/unknown pack directive '0' in 'a 0#{pack_format}'/) + -> { [@obj, @obj].pack("a :" + pack_format) }.should complain(/unknown pack directive ':' in 'a :#{pack_format}'/) end end ruby_version_is "3.3" do - # https://bugs.ruby-lang.org/issues/19150 - # NOTE: Added this case just to not forget about the decision in the ticket it "raise ArgumentError when a directive is unknown" do # additional directive ('a') is required for the X directive - -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError) - -> { [@obj, @obj].pack("a 0" + pack_format) }.should raise_error(ArgumentError) - -> { [@obj, @obj].pack("a :" + pack_format) }.should raise_error(ArgumentError) + -> { [@obj, @obj].pack("a R" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive 'R'/) + -> { [@obj, @obj].pack("a 0" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive '0'/) + -> { [@obj, @obj].pack("a :" + pack_format) }.should raise_error(ArgumentError, /unknown pack directive ':'/) end end diff --git a/spec/ruby/core/array/pack/shared/float.rb b/spec/ruby/core/array/pack/shared/float.rb index 1780d7635e..76c800b74d 100644 --- a/spec/ruby/core/array/pack/shared/float.rb +++ b/spec/ruby/core/array/pack/shared/float.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :array_pack_float_le, shared: true do it "encodes a positive Float" do diff --git a/spec/ruby/core/array/pack/shared/integer.rb b/spec/ruby/core/array/pack/shared/integer.rb index fd21b25b19..61f7cca184 100644 --- a/spec/ruby/core/array/pack/shared/integer.rb +++ b/spec/ruby/core/array/pack/shared/integer.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :array_pack_16bit_le, shared: true do it "encodes the least significant 16 bits of a positive number" do @@ -273,7 +273,7 @@ describe :array_pack_32bit_le_platform, shared: true do str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78" end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "encodes the least significant 32 bits of a number that is greater than 32 bits" do [ [[0xff_7865_4321], "\x21\x43\x65\x78"], [[-0xff_7865_4321], "\xdf\xbc\x9a\x87"] @@ -299,7 +299,7 @@ describe :array_pack_32bit_be_platform, shared: true do str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21" end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "encodes the least significant 32 bits of a number that is greater than 32 bits" do [ [[0xff_7865_4321], "\x78\x65\x43\x21"], [[-0xff_7865_4321], "\x87\x9a\xbc\xdf"] diff --git a/spec/ruby/core/array/pack/shared/string.rb b/spec/ruby/core/array/pack/shared/string.rb index 8c82e8c617..805f78b53b 100644 --- a/spec/ruby/core/array/pack/shared/string.rb +++ b/spec/ruby/core/array/pack/shared/string.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :array_pack_string, shared: true do it "adds count bytes of a String to the output" do ["abc"].pack(pack_format(2)).should == "ab" @@ -40,7 +40,7 @@ describe :array_pack_string, shared: true do f = pack_format("*") [ [["\u{3042 3044 3046 3048}", 0x2000B].pack(f+"U"), Encoding::BINARY], [["abcde\xd1", "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY], - [["a".force_encoding("ascii"), "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY], + [["a".dup.force_encoding("ascii"), "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY], # under discussion [ruby-dev:37294] [["\u{3042 3044 3046 3048}", 1].pack(f+"N"), Encoding::BINARY] ].should be_computed_by(:encoding) diff --git a/spec/ruby/core/array/pack/u_spec.rb b/spec/ruby/core/array/pack/u_spec.rb index b20093a647..1f84095ac4 100644 --- a/spec/ruby/core/array/pack/u_spec.rb +++ b/spec/ruby/core/array/pack/u_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/w_spec.rb b/spec/ruby/core/array/pack/w_spec.rb index 48ed4496a5..e770288d67 100644 --- a/spec/ruby/core/array/pack/w_spec.rb +++ b/spec/ruby/core/array/pack/w_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/x_spec.rb b/spec/ruby/core/array/pack/x_spec.rb index 86c3ad1aa4..012fe4567f 100644 --- a/spec/ruby/core/array/pack/x_spec.rb +++ b/spec/ruby/core/array/pack/x_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/array/pack/z_spec.rb b/spec/ruby/core/array/pack/z_spec.rb index 5ad3afd69e..60f8f7bf10 100644 --- a/spec/ruby/core/array/pack/z_spec.rb +++ b/spec/ruby/core/array/pack/z_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' @@ -26,7 +26,7 @@ describe "Array#pack with format 'Z'" do ["abc"].pack("Z*").should == "abc\x00" end - it "padds the output with NULL bytes when the count exceeds the size of the String" do + it "pads the output with NULL bytes when the count exceeds the size of the String" do ["abc"].pack("Z6").should == "abc\x00\x00\x00" end diff --git a/spec/ruby/core/array/plus_spec.rb b/spec/ruby/core/array/plus_spec.rb index 635bd131c9..b7153fd3ef 100644 --- a/spec/ruby/core/array/plus_spec.rb +++ b/spec/ruby/core/array/plus_spec.rb @@ -21,7 +21,7 @@ describe "Array#+" do ([1, 2, 3] + obj).should == [1, 2, 3, "x", "y"] end - it "raises a Typeerror if the given argument can't be converted to an array" do + it "raises a TypeError if the given argument can't be converted to an array" do -> { [1, 2, 3] + nil }.should raise_error(TypeError) -> { [1, 2, 3] + "abc" }.should raise_error(TypeError) end diff --git a/spec/ruby/core/array/rassoc_spec.rb b/spec/ruby/core/array/rassoc_spec.rb index 62fbd40611..632a05e8b3 100644 --- a/spec/ruby/core/array/rassoc_spec.rb +++ b/spec/ruby/core/array/rassoc_spec.rb @@ -35,4 +35,18 @@ describe "Array#rassoc" do [[1, :foobar, o], [2, o, 1], [3, mock('foo')]].rassoc(key).should == [2, o, 1] end + + ruby_version_is "3.3" do + it "calls to_ary on non-array elements" do + s1 = [1, 2] + s2 = ArraySpecs::ArrayConvertible.new(2, 3) + a = [s1, s2] + + s1.should_not_receive(:to_ary) + a.rassoc(2).should equal(s1) + + a.rassoc(3).should == [2, 3] + s2.called.should equal(:to_ary) + end + end end diff --git a/spec/ruby/core/array/sample_spec.rb b/spec/ruby/core/array/sample_spec.rb index 6ef78594f0..d4e945152d 100644 --- a/spec/ruby/core/array/sample_spec.rb +++ b/spec/ruby/core/array/sample_spec.rb @@ -114,6 +114,13 @@ describe "Array#sample" do -> { [1, 2].sample(random: random) }.should raise_error(RangeError) end + + it "raises a RangeError if the value is greater than the Array size" do + random = mock("array_sample_random") + random.should_receive(:rand).and_return(3) + + -> { [1, 2].sample(random: random) }.should raise_error(RangeError) + end end end diff --git a/spec/ruby/core/array/shared/inspect.rb b/spec/ruby/core/array/shared/inspect.rb index a2b43d4959..af5128c645 100644 --- a/spec/ruby/core/array/shared/inspect.rb +++ b/spec/ruby/core/array/shared/inspect.rb @@ -19,7 +19,7 @@ describe :array_inspect, shared: true do end it "does not call #to_s on a String returned from #inspect" do - str = "abc" + str = +"abc" str.should_not_receive(:to_s) [str].send(@method).should == '["abc"]' @@ -98,8 +98,8 @@ describe :array_inspect, shared: true do end it "does not raise if inspected result is not default external encoding" do - utf_16be = mock("utf_16be") - utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode!(Encoding::UTF_16BE)) + utf_16be = mock(+"utf_16be") + utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode(Encoding::UTF_16BE)) [utf_16be].send(@method).should == '["utf_16be \u3042"]' end diff --git a/spec/ruby/core/array/shared/slice.rb b/spec/ruby/core/array/shared/slice.rb index d2866970a5..b80261d32f 100644 --- a/spec/ruby/core/array/shared/slice.rb +++ b/spec/ruby/core/array/shared/slice.rb @@ -754,99 +754,97 @@ describe :array_slice, shared: true do a.send(@method, (...-9)).should == [] end - ruby_version_is "3.2" do - describe "can be sliced with Enumerator::ArithmeticSequence" do - it "with infinite/inverted ranges and negative steps" do - @array = [0, 1, 2, 3, 4, 5] - @array.send(@method, (2..).step(-1)).should == [2, 1, 0] - @array.send(@method, (2..).step(-2)).should == [2, 0] - @array.send(@method, (2..).step(-3)).should == [2] - @array.send(@method, (2..).step(-4)).should == [2] - - @array.send(@method, (-3..).step(-1)).should == [3, 2, 1, 0] - @array.send(@method, (-3..).step(-2)).should == [3, 1] - @array.send(@method, (-3..).step(-3)).should == [3, 0] - @array.send(@method, (-3..).step(-4)).should == [3] - @array.send(@method, (-3..).step(-5)).should == [3] - - @array.send(@method, (..0).step(-1)).should == [5, 4, 3, 2, 1, 0] - @array.send(@method, (..0).step(-2)).should == [5, 3, 1] - @array.send(@method, (..0).step(-3)).should == [5, 2] - @array.send(@method, (..0).step(-4)).should == [5, 1] - @array.send(@method, (..0).step(-5)).should == [5, 0] - @array.send(@method, (..0).step(-6)).should == [5] - @array.send(@method, (..0).step(-7)).should == [5] - - @array.send(@method, (...0).step(-1)).should == [5, 4, 3, 2, 1] - @array.send(@method, (...0).step(-2)).should == [5, 3, 1] - @array.send(@method, (...0).step(-3)).should == [5, 2] - @array.send(@method, (...0).step(-4)).should == [5, 1] - @array.send(@method, (...0).step(-5)).should == [5] - @array.send(@method, (...0).step(-6)).should == [5] - - @array.send(@method, (...1).step(-1)).should == [5, 4, 3, 2] - @array.send(@method, (...1).step(-2)).should == [5, 3] - @array.send(@method, (...1).step(-3)).should == [5, 2] - @array.send(@method, (...1).step(-4)).should == [5] - @array.send(@method, (...1).step(-5)).should == [5] - - @array.send(@method, (..-5).step(-1)).should == [5, 4, 3, 2, 1] - @array.send(@method, (..-5).step(-2)).should == [5, 3, 1] - @array.send(@method, (..-5).step(-3)).should == [5, 2] - @array.send(@method, (..-5).step(-4)).should == [5, 1] - @array.send(@method, (..-5).step(-5)).should == [5] - @array.send(@method, (..-5).step(-6)).should == [5] - - @array.send(@method, (...-5).step(-1)).should == [5, 4, 3, 2] - @array.send(@method, (...-5).step(-2)).should == [5, 3] - @array.send(@method, (...-5).step(-3)).should == [5, 2] - @array.send(@method, (...-5).step(-4)).should == [5] - @array.send(@method, (...-5).step(-5)).should == [5] - - @array.send(@method, (4..1).step(-1)).should == [4, 3, 2, 1] - @array.send(@method, (4..1).step(-2)).should == [4, 2] - @array.send(@method, (4..1).step(-3)).should == [4, 1] - @array.send(@method, (4..1).step(-4)).should == [4] - @array.send(@method, (4..1).step(-5)).should == [4] - - @array.send(@method, (4...1).step(-1)).should == [4, 3, 2] - @array.send(@method, (4...1).step(-2)).should == [4, 2] - @array.send(@method, (4...1).step(-3)).should == [4] - @array.send(@method, (4...1).step(-4)).should == [4] - - @array.send(@method, (-2..1).step(-1)).should == [4, 3, 2, 1] - @array.send(@method, (-2..1).step(-2)).should == [4, 2] - @array.send(@method, (-2..1).step(-3)).should == [4, 1] - @array.send(@method, (-2..1).step(-4)).should == [4] - @array.send(@method, (-2..1).step(-5)).should == [4] - - @array.send(@method, (-2...1).step(-1)).should == [4, 3, 2] - @array.send(@method, (-2...1).step(-2)).should == [4, 2] - @array.send(@method, (-2...1).step(-3)).should == [4] - @array.send(@method, (-2...1).step(-4)).should == [4] - - @array.send(@method, (4..-5).step(-1)).should == [4, 3, 2, 1] - @array.send(@method, (4..-5).step(-2)).should == [4, 2] - @array.send(@method, (4..-5).step(-3)).should == [4, 1] - @array.send(@method, (4..-5).step(-4)).should == [4] - @array.send(@method, (4..-5).step(-5)).should == [4] - - @array.send(@method, (4...-5).step(-1)).should == [4, 3, 2] - @array.send(@method, (4...-5).step(-2)).should == [4, 2] - @array.send(@method, (4...-5).step(-3)).should == [4] - @array.send(@method, (4...-5).step(-4)).should == [4] - - @array.send(@method, (-2..-5).step(-1)).should == [4, 3, 2, 1] - @array.send(@method, (-2..-5).step(-2)).should == [4, 2] - @array.send(@method, (-2..-5).step(-3)).should == [4, 1] - @array.send(@method, (-2..-5).step(-4)).should == [4] - @array.send(@method, (-2..-5).step(-5)).should == [4] - - @array.send(@method, (-2...-5).step(-1)).should == [4, 3, 2] - @array.send(@method, (-2...-5).step(-2)).should == [4, 2] - @array.send(@method, (-2...-5).step(-3)).should == [4] - @array.send(@method, (-2...-5).step(-4)).should == [4] - end + describe "can be sliced with Enumerator::ArithmeticSequence" do + it "with infinite/inverted ranges and negative steps" do + @array = [0, 1, 2, 3, 4, 5] + @array.send(@method, (2..).step(-1)).should == [2, 1, 0] + @array.send(@method, (2..).step(-2)).should == [2, 0] + @array.send(@method, (2..).step(-3)).should == [2] + @array.send(@method, (2..).step(-4)).should == [2] + + @array.send(@method, (-3..).step(-1)).should == [3, 2, 1, 0] + @array.send(@method, (-3..).step(-2)).should == [3, 1] + @array.send(@method, (-3..).step(-3)).should == [3, 0] + @array.send(@method, (-3..).step(-4)).should == [3] + @array.send(@method, (-3..).step(-5)).should == [3] + + @array.send(@method, (..0).step(-1)).should == [5, 4, 3, 2, 1, 0] + @array.send(@method, (..0).step(-2)).should == [5, 3, 1] + @array.send(@method, (..0).step(-3)).should == [5, 2] + @array.send(@method, (..0).step(-4)).should == [5, 1] + @array.send(@method, (..0).step(-5)).should == [5, 0] + @array.send(@method, (..0).step(-6)).should == [5] + @array.send(@method, (..0).step(-7)).should == [5] + + @array.send(@method, (...0).step(-1)).should == [5, 4, 3, 2, 1] + @array.send(@method, (...0).step(-2)).should == [5, 3, 1] + @array.send(@method, (...0).step(-3)).should == [5, 2] + @array.send(@method, (...0).step(-4)).should == [5, 1] + @array.send(@method, (...0).step(-5)).should == [5] + @array.send(@method, (...0).step(-6)).should == [5] + + @array.send(@method, (...1).step(-1)).should == [5, 4, 3, 2] + @array.send(@method, (...1).step(-2)).should == [5, 3] + @array.send(@method, (...1).step(-3)).should == [5, 2] + @array.send(@method, (...1).step(-4)).should == [5] + @array.send(@method, (...1).step(-5)).should == [5] + + @array.send(@method, (..-5).step(-1)).should == [5, 4, 3, 2, 1] + @array.send(@method, (..-5).step(-2)).should == [5, 3, 1] + @array.send(@method, (..-5).step(-3)).should == [5, 2] + @array.send(@method, (..-5).step(-4)).should == [5, 1] + @array.send(@method, (..-5).step(-5)).should == [5] + @array.send(@method, (..-5).step(-6)).should == [5] + + @array.send(@method, (...-5).step(-1)).should == [5, 4, 3, 2] + @array.send(@method, (...-5).step(-2)).should == [5, 3] + @array.send(@method, (...-5).step(-3)).should == [5, 2] + @array.send(@method, (...-5).step(-4)).should == [5] + @array.send(@method, (...-5).step(-5)).should == [5] + + @array.send(@method, (4..1).step(-1)).should == [4, 3, 2, 1] + @array.send(@method, (4..1).step(-2)).should == [4, 2] + @array.send(@method, (4..1).step(-3)).should == [4, 1] + @array.send(@method, (4..1).step(-4)).should == [4] + @array.send(@method, (4..1).step(-5)).should == [4] + + @array.send(@method, (4...1).step(-1)).should == [4, 3, 2] + @array.send(@method, (4...1).step(-2)).should == [4, 2] + @array.send(@method, (4...1).step(-3)).should == [4] + @array.send(@method, (4...1).step(-4)).should == [4] + + @array.send(@method, (-2..1).step(-1)).should == [4, 3, 2, 1] + @array.send(@method, (-2..1).step(-2)).should == [4, 2] + @array.send(@method, (-2..1).step(-3)).should == [4, 1] + @array.send(@method, (-2..1).step(-4)).should == [4] + @array.send(@method, (-2..1).step(-5)).should == [4] + + @array.send(@method, (-2...1).step(-1)).should == [4, 3, 2] + @array.send(@method, (-2...1).step(-2)).should == [4, 2] + @array.send(@method, (-2...1).step(-3)).should == [4] + @array.send(@method, (-2...1).step(-4)).should == [4] + + @array.send(@method, (4..-5).step(-1)).should == [4, 3, 2, 1] + @array.send(@method, (4..-5).step(-2)).should == [4, 2] + @array.send(@method, (4..-5).step(-3)).should == [4, 1] + @array.send(@method, (4..-5).step(-4)).should == [4] + @array.send(@method, (4..-5).step(-5)).should == [4] + + @array.send(@method, (4...-5).step(-1)).should == [4, 3, 2] + @array.send(@method, (4...-5).step(-2)).should == [4, 2] + @array.send(@method, (4...-5).step(-3)).should == [4] + @array.send(@method, (4...-5).step(-4)).should == [4] + + @array.send(@method, (-2..-5).step(-1)).should == [4, 3, 2, 1] + @array.send(@method, (-2..-5).step(-2)).should == [4, 2] + @array.send(@method, (-2..-5).step(-3)).should == [4, 1] + @array.send(@method, (-2..-5).step(-4)).should == [4] + @array.send(@method, (-2..-5).step(-5)).should == [4] + + @array.send(@method, (-2...-5).step(-1)).should == [4, 3, 2] + @array.send(@method, (-2...-5).step(-2)).should == [4, 2] + @array.send(@method, (-2...-5).step(-3)).should == [4] + @array.send(@method, (-2...-5).step(-4)).should == [4] end end diff --git a/spec/ruby/core/array/shared/unshift.rb b/spec/ruby/core/array/shared/unshift.rb index 4941e098f6..9e0fe7556a 100644 --- a/spec/ruby/core/array/shared/unshift.rb +++ b/spec/ruby/core/array/shared/unshift.rb @@ -49,7 +49,7 @@ describe :array_unshift, shared: true do -> { ArraySpecs.frozen_array.send(@method) }.should raise_error(FrozenError) end - # https://github.com/oracle/truffleruby/issues/2772 + # https://github.com/truffleruby/truffleruby/issues/2772 it "doesn't rely on Array#[]= so it can be overridden" do subclass = Class.new(Array) do def []=(*) diff --git a/spec/ruby/core/array/shuffle_spec.rb b/spec/ruby/core/array/shuffle_spec.rb index 1d528c124f..b84394bcb5 100644 --- a/spec/ruby/core/array/shuffle_spec.rb +++ b/spec/ruby/core/array/shuffle_spec.rb @@ -69,9 +69,18 @@ describe "Array#shuffle" do -> { [1, 2].shuffle(random: random) }.should raise_error(RangeError) end - it "raises a RangeError if the value is equal to one" do + it "raises a RangeError if the value is equal to the Array size" do value = mock("array_shuffle_random_value") - value.should_receive(:to_int).at_least(1).times.and_return(1) + value.should_receive(:to_int).at_least(1).times.and_return(2) + random = mock("array_shuffle_random") + random.should_receive(:rand).at_least(1).times.and_return(value) + + -> { [1, 2].shuffle(random: random) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is greater than the Array size" do + value = mock("array_shuffle_random_value") + value.should_receive(:to_int).at_least(1).times.and_return(3) random = mock("array_shuffle_random") random.should_receive(:rand).at_least(1).times.and_return(value) diff --git a/spec/ruby/core/array/to_h_spec.rb b/spec/ruby/core/array/to_h_spec.rb index f4578211a1..1c814f3d01 100644 --- a/spec/ruby/core/array/to_h_spec.rb +++ b/spec/ruby/core/array/to_h_spec.rb @@ -45,6 +45,12 @@ describe "Array#to_h" do [:a, :b].to_h { |k| [k, k.to_s] }.should == { a: 'a', b: 'b' } end + it "passes to a block each element as a single argument" do + ScratchPad.record [] + [[:a, 1], [:b, 2]].to_h { |*args| ScratchPad << args; [args[0], args[1]] } + ScratchPad.recorded.sort.should == [[[:a, 1]], [[:b, 2]]] + end + it "raises ArgumentError if block returns longer or shorter array" do -> do [:a, :b].to_h { |k| [k, k.to_s, 1] } diff --git a/spec/ruby/core/basicobject/equal_spec.rb b/spec/ruby/core/basicobject/equal_spec.rb index 3c1ad56d4a..ce28eaed95 100644 --- a/spec/ruby/core/basicobject/equal_spec.rb +++ b/spec/ruby/core/basicobject/equal_spec.rb @@ -11,8 +11,10 @@ describe "BasicObject#equal?" do it "is unaffected by overriding __id__" do o1 = mock("object") o2 = mock("object") - def o1.__id__; 10; end - def o2.__id__; 10; end + suppress_warning { + def o1.__id__; 10; end + def o2.__id__; 10; end + } o1.equal?(o2).should be_false end diff --git a/spec/ruby/core/basicobject/instance_eval_spec.rb b/spec/ruby/core/basicobject/instance_eval_spec.rb index 1f3a43f341..633b5c2cb1 100644 --- a/spec/ruby/core/basicobject/instance_eval_spec.rb +++ b/spec/ruby/core/basicobject/instance_eval_spec.rb @@ -141,22 +141,11 @@ describe "BasicObject#instance_eval" do caller.get_constant_with_string(receiver).should == :singleton_class end - ruby_version_is ""..."3.1" do - it "looks in the caller scope next" do - receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::ReceiverScope::Receiver.new - caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::CallerScope::Caller.new + it "looks in the receiver class next" do + receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::ReceiverScope::Receiver.new + caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::CallerScope::Caller.new - caller.get_constant_with_string(receiver).should == :Caller - end - end - - ruby_version_is "3.1" do - it "looks in the receiver class next" do - receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::ReceiverScope::Receiver.new - caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::CallerScope::Caller.new - - caller.get_constant_with_string(receiver).should == :Receiver - end + caller.get_constant_with_string(receiver).should == :Receiver end it "looks in the caller class next" do diff --git a/spec/ruby/core/basicobject/instance_exec_spec.rb b/spec/ruby/core/basicobject/instance_exec_spec.rb index 289fdd889b..370f03d33c 100644 --- a/spec/ruby/core/basicobject/instance_exec_spec.rb +++ b/spec/ruby/core/basicobject/instance_exec_spec.rb @@ -84,17 +84,17 @@ describe "BasicObject#instance_exec" do end.should raise_error(TypeError) end -quarantine! do # Not clean, leaves cvars lying around to break other specs - it "scopes class var accesses in the caller when called on an Integer" do - # Integer can take instance vars - Integer.class_eval "@@__tmp_instance_exec_spec = 1" - (defined? @@__tmp_instance_exec_spec).should == nil - - @@__tmp_instance_exec_spec = 2 - 1.instance_exec { @@__tmp_instance_exec_spec }.should == 2 - Integer.__send__(:remove_class_variable, :@@__tmp_instance_exec_spec) + quarantine! do # Not clean, leaves cvars lying around to break other specs + it "scopes class var accesses in the caller when called on an Integer" do + # Integer can take instance vars + Integer.class_eval "@@__tmp_instance_exec_spec = 1" + (defined? @@__tmp_instance_exec_spec).should == nil + + @@__tmp_instance_exec_spec = 2 + 1.instance_exec { @@__tmp_instance_exec_spec }.should == 2 + Integer.__send__(:remove_class_variable, :@@__tmp_instance_exec_spec) + end end -end it "raises a TypeError when defining methods on numerics" do -> do diff --git a/spec/ruby/core/basicobject/singleton_method_added_spec.rb b/spec/ruby/core/basicobject/singleton_method_added_spec.rb index ab6b2a2d10..bd21458ea7 100644 --- a/spec/ruby/core/basicobject/singleton_method_added_spec.rb +++ b/spec/ruby/core/basicobject/singleton_method_added_spec.rb @@ -94,8 +94,10 @@ describe "BasicObject#singleton_method_added" do -> { def self.foo end - }.should raise_error(NoMethodError, /undefined method `singleton_method_added' for/) + }.should raise_error(NoMethodError, /undefined method [`']singleton_method_added' for/) end + ensure + BasicObjectSpecs.send(:remove_const, :NoSingletonMethodAdded) end it "raises NoMethodError for a singleton instance" do @@ -106,16 +108,16 @@ describe "BasicObject#singleton_method_added" do -> { def foo end - }.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/) + }.should raise_error(NoMethodError, /undefined method [`']singleton_method_added' for #<Object:/) -> { define_method(:bar) {} - }.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/) + }.should raise_error(NoMethodError, /undefined method [`']singleton_method_added' for #<Object:/) end -> { object.define_singleton_method(:baz) {} - }.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/) + }.should raise_error(NoMethodError, /undefined method [`']singleton_method_added' for #<Object:/) end it "calls #method_missing" do diff --git a/spec/ruby/core/binding/clone_spec.rb b/spec/ruby/core/binding/clone_spec.rb index ebd40f5377..f1769ac6de 100644 --- a/spec/ruby/core/binding/clone_spec.rb +++ b/spec/ruby/core/binding/clone_spec.rb @@ -4,4 +4,10 @@ require_relative 'shared/clone' describe "Binding#clone" do it_behaves_like :binding_clone, :clone + + it "preserves frozen status" do + bind = binding.freeze + bind.frozen?.should == true + bind.clone.frozen?.should == true + end end diff --git a/spec/ruby/core/binding/dup_spec.rb b/spec/ruby/core/binding/dup_spec.rb index 43968213c8..4eff66bd9a 100644 --- a/spec/ruby/core/binding/dup_spec.rb +++ b/spec/ruby/core/binding/dup_spec.rb @@ -4,4 +4,27 @@ require_relative 'shared/clone' describe "Binding#dup" do it_behaves_like :binding_clone, :dup + + it "resets frozen status" do + bind = binding.freeze + bind.frozen?.should == true + bind.dup.frozen?.should == false + end + + it "retains original binding variables but the list is distinct" do + bind1 = binding + eval "a = 1", bind1 + + bind2 = bind1.dup + eval("a = 2", bind2) + eval("a", bind1).should == 2 + eval("a", bind2).should == 2 + + eval("b = 2", bind2) + -> { eval("b", bind1) }.should raise_error(NameError) + eval("b", bind2).should == 2 + + bind1.local_variables.sort.should == [:a, :bind1, :bind2] + bind2.local_variables.sort.should == [:a, :b, :bind1, :bind2] + end end diff --git a/spec/ruby/core/binding/fixtures/irb.rb b/spec/ruby/core/binding/fixtures/irb.rb deleted file mode 100644 index 5f305f2d5d..0000000000 --- a/spec/ruby/core/binding/fixtures/irb.rb +++ /dev/null @@ -1,3 +0,0 @@ -a = 10 - -binding.irb diff --git a/spec/ruby/core/binding/fixtures/irbrc b/spec/ruby/core/binding/fixtures/irbrc deleted file mode 100644 index 2bc12af2f7..0000000000 --- a/spec/ruby/core/binding/fixtures/irbrc +++ /dev/null @@ -1 +0,0 @@ -# empty configuration diff --git a/spec/ruby/core/binding/irb_spec.rb b/spec/ruby/core/binding/irb_spec.rb deleted file mode 100644 index b3bc274f78..0000000000 --- a/spec/ruby/core/binding/irb_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require_relative '../../spec_helper' - -describe "Binding#irb" do - it "creates an IRB session with the binding in scope" do - irb_fixture = fixture __FILE__, "irb.rb" - irbrc_fixture = fixture __FILE__, "irbrc" - - out = IO.popen([{"IRBRC"=>irbrc_fixture}, *ruby_exe, irb_fixture], "r+") do |pipe| - pipe.puts "a ** 2" - pipe.puts "exit" - pipe.readlines.map(&:chomp) - end - - out[-3..-1].should == ["a ** 2", "100", "exit"] - end -end diff --git a/spec/ruby/core/binding/shared/clone.rb b/spec/ruby/core/binding/shared/clone.rb index 0e934ac1b5..2d854fce96 100644 --- a/spec/ruby/core/binding/shared/clone.rb +++ b/spec/ruby/core/binding/shared/clone.rb @@ -31,4 +31,26 @@ describe :binding_clone, shared: true do b2.local_variable_defined?(:x).should == false end end + + ruby_version_is "3.4" do + it "copies instance variables" do + @b1.instance_variable_set(:@ivar, 1) + cl = @b1.send(@method) + cl.instance_variables.should == [:@ivar] + end + + it "copies the finalizer" do + code = <<-'RUBY' + obj = binding + + ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) + + obj.clone + + exit 0 + RUBY + + ruby_exe(code).lines.sort.should == ["finalized\n", "finalized\n"] + end + end end diff --git a/spec/ruby/core/builtin_constants/builtin_constants_spec.rb b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb index 1960f5721f..13e066cc7f 100644 --- a/spec/ruby/core/builtin_constants/builtin_constants_spec.rb +++ b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb @@ -4,6 +4,10 @@ describe "RUBY_VERSION" do it "is a String" do RUBY_VERSION.should be_kind_of(String) end + + it "is frozen" do + RUBY_VERSION.should.frozen? + end end describe "RUBY_PATCHLEVEL" do @@ -16,34 +20,132 @@ describe "RUBY_COPYRIGHT" do it "is a String" do RUBY_COPYRIGHT.should be_kind_of(String) end + + it "is frozen" do + RUBY_COPYRIGHT.should.frozen? + end end describe "RUBY_DESCRIPTION" do it "is a String" do RUBY_DESCRIPTION.should be_kind_of(String) end + + it "is frozen" do + RUBY_DESCRIPTION.should.frozen? + end end describe "RUBY_ENGINE" do it "is a String" do RUBY_ENGINE.should be_kind_of(String) end + + it "is frozen" do + RUBY_ENGINE.should.frozen? + end +end + +describe "RUBY_ENGINE_VERSION" do + it "is a String" do + RUBY_ENGINE_VERSION.should be_kind_of(String) + end + + it "is frozen" do + RUBY_ENGINE_VERSION.should.frozen? + end end describe "RUBY_PLATFORM" do it "is a String" do RUBY_PLATFORM.should be_kind_of(String) end + + it "is frozen" do + RUBY_PLATFORM.should.frozen? + end end describe "RUBY_RELEASE_DATE" do it "is a String" do RUBY_RELEASE_DATE.should be_kind_of(String) end + + it "is frozen" do + RUBY_RELEASE_DATE.should.frozen? + end end describe "RUBY_REVISION" do it "is a String" do RUBY_REVISION.should be_kind_of(String) end + + it "is frozen" do + RUBY_REVISION.should.frozen? + end +end + +ruby_version_is "4.0" do + context "The constant" do + describe "Ruby" do + it "is a Module" do + Ruby.should.instance_of?(Module) + end + end + + describe "Ruby::VERSION" do + it "is equal to RUBY_VERSION" do + Ruby::VERSION.should equal(RUBY_VERSION) + end + end + + describe "RUBY::PATCHLEVEL" do + it "is equal to RUBY_PATCHLEVEL" do + Ruby::PATCHLEVEL.should equal(RUBY_PATCHLEVEL) + end + end + + describe "Ruby::COPYRIGHT" do + it "is equal to RUBY_COPYRIGHT" do + Ruby::COPYRIGHT.should equal(RUBY_COPYRIGHT) + end + end + + describe "Ruby::DESCRIPTION" do + it "is equal to RUBY_DESCRIPTION" do + Ruby::DESCRIPTION.should equal(RUBY_DESCRIPTION) + end + end + + describe "Ruby::ENGINE" do + it "is equal to RUBY_ENGINE" do + Ruby::ENGINE.should equal(RUBY_ENGINE) + end + end + + describe "Ruby::ENGINE_VERSION" do + it "is equal to RUBY_ENGINE_VERSION" do + Ruby::ENGINE_VERSION.should equal(RUBY_ENGINE_VERSION) + end + end + + describe "Ruby::PLATFORM" do + it "is equal to RUBY_PLATFORM" do + Ruby::PLATFORM.should equal(RUBY_PLATFORM) + end + end + + describe "Ruby::RELEASE_DATE" do + it "is equal to RUBY_RELEASE_DATE" do + Ruby::RELEASE_DATE.should equal(RUBY_RELEASE_DATE) + end + end + + describe "Ruby::REVISION" do + it "is equal to RUBY_REVISION" do + Ruby::REVISION.should equal(RUBY_REVISION) + end + end + end end diff --git a/spec/ruby/core/class/attached_object_spec.rb b/spec/ruby/core/class/attached_object_spec.rb index 115d5fa563..8f8a0734c6 100644 --- a/spec/ruby/core/class/attached_object_spec.rb +++ b/spec/ruby/core/class/attached_object_spec.rb @@ -1,31 +1,29 @@ require_relative '../../spec_helper' -ruby_version_is '3.2' do - describe "Class#attached_object" do - it "returns the object that is attached to a singleton class" do - a = Class.new +describe "Class#attached_object" do + it "returns the object that is attached to a singleton class" do + a = Class.new - a_obj = a.new - a_obj.singleton_class.attached_object.should == a_obj - end + a_obj = a.new + a_obj.singleton_class.attached_object.should == a_obj + end - it "returns the class object that is attached to a class's singleton class" do - a = Class.new - singleton_class = (class << a; self; end) + it "returns the class object that is attached to a class's singleton class" do + a = Class.new + singleton_class = (class << a; self; end) - singleton_class.attached_object.should == a - end + singleton_class.attached_object.should == a + end - it "raises TypeError if the class is not a singleton class" do - a = Class.new + it "raises TypeError if the class is not a singleton class" do + a = Class.new - -> { a.attached_object }.should raise_error(TypeError) - end + -> { a.attached_object }.should raise_error(TypeError, /is not a singleton class/) + end - it "raises TypeError for special singleton classes" do - -> { nil.singleton_class.attached_object }.should raise_error(TypeError) - -> { true.singleton_class.attached_object }.should raise_error(TypeError) - -> { false.singleton_class.attached_object }.should raise_error(TypeError) - end + it "raises TypeError for special singleton classes" do + -> { nil.singleton_class.attached_object }.should raise_error(TypeError, /[`']NilClass' is not a singleton class/) + -> { true.singleton_class.attached_object }.should raise_error(TypeError, /[`']TrueClass' is not a singleton class/) + -> { false.singleton_class.attached_object }.should raise_error(TypeError, /[`']FalseClass' is not a singleton class/) end end diff --git a/spec/ruby/core/class/dup_spec.rb b/spec/ruby/core/class/dup_spec.rb index 701fd72e19..1ff9abf7b4 100644 --- a/spec/ruby/core/class/dup_spec.rb +++ b/spec/ruby/core/class/dup_spec.rb @@ -59,6 +59,11 @@ describe "Class#dup" do it "stores the new name if assigned to a constant" do CoreClassSpecs::RecordCopy = CoreClassSpecs::Record.dup CoreClassSpecs::RecordCopy.name.should == "CoreClassSpecs::RecordCopy" + ensure + CoreClassSpecs.send(:remove_const, :RecordCopy) end + it "raises TypeError if called on BasicObject" do + -> { BasicObject.dup }.should raise_error(TypeError, "can't copy the root class") + end end diff --git a/spec/ruby/core/class/inherited_spec.rb b/spec/ruby/core/class/inherited_spec.rb index 8ef8bb8c35..2a8d1ff813 100644 --- a/spec/ruby/core/class/inherited_spec.rb +++ b/spec/ruby/core/class/inherited_spec.rb @@ -98,4 +98,21 @@ describe "Class.inherited" do -> { Class.new(top) }.should_not raise_error end + it "if the subclass is assigned to a constant, it is all set" do + ScratchPad.record [] + + parent = Class.new do + def self.inherited(subclass) + ScratchPad << defined?(self::C) + ScratchPad << const_defined?(:C) + ScratchPad << constants + ScratchPad << const_get(:C) + ScratchPad << subclass.name.match?(/\A#<Class:0x\w+>::C\z/) + end + end + + class parent::C < parent; end + + ScratchPad.recorded.should == ["constant", true, [:C], parent::C, true] + end end diff --git a/spec/ruby/core/class/new_spec.rb b/spec/ruby/core/class/new_spec.rb index 93152a83ee..6fe54c3209 100644 --- a/spec/ruby/core/class/new_spec.rb +++ b/spec/ruby/core/class/new_spec.rb @@ -83,6 +83,8 @@ describe "Class.new" do a = Class.new MyClass::NestedClass = a MyClass::NestedClass.name.should == "MyClass::NestedClass" + ensure + Object.send(:remove_const, :MyClass) end it "sets the new class' superclass to the given class" do diff --git a/spec/ruby/core/class/subclasses_spec.rb b/spec/ruby/core/class/subclasses_spec.rb index a16b934d4f..f692152787 100644 --- a/spec/ruby/core/class/subclasses_spec.rb +++ b/spec/ruby/core/class/subclasses_spec.rb @@ -1,60 +1,85 @@ require_relative '../../spec_helper' require_relative '../module/fixtures/classes' -ruby_version_is '3.1' do - describe "Class#subclasses" do - it "returns a list of classes directly inheriting from self" do - assert_subclasses(ModuleSpecs::Parent, [ModuleSpecs::Child, ModuleSpecs::Child2]) - end +describe "Class#subclasses" do + it "returns a list of classes directly inheriting from self" do + assert_subclasses(ModuleSpecs::Parent, [ModuleSpecs::Child, ModuleSpecs::Child2]) + end - it "does not return included modules" do - parent = Class.new - child = Class.new(parent) - mod = Module.new - parent.include(mod) + it "does not return included modules from the parent" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + parent.include(mod) - assert_subclasses(parent, [child]) - end + assert_subclasses(parent, [child]) + end - it "does not return singleton classes" do - a = Class.new + it "does not return included modules from the child" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + parent.include(mod) - a_obj = a.new - def a_obj.force_singleton_class - 42 - end + assert_subclasses(parent, [child]) + end - a.subclasses.should_not include(a_obj.singleton_class) - end + it "does not return prepended modules from the parent" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + parent.prepend(mod) + + assert_subclasses(parent, [child]) + end + + it "does not return prepended modules from the child" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + child.prepend(mod) + + assert_subclasses(parent, [child]) + end + + it "does not return singleton classes" do + a = Class.new - it "has 1 entry per module or class" do - ModuleSpecs::Parent.subclasses.should == ModuleSpecs::Parent.subclasses.uniq + a_obj = a.new + def a_obj.force_singleton_class + 42 end - it "works when creating subclasses concurrently" do - t = 16 - n = 1000 - go = false - superclass = Class.new - - threads = t.times.map do - Thread.new do - Thread.pass until go - n.times.map do - Class.new(superclass) - end + a.subclasses.should_not include(a_obj.singleton_class) + end + + it "has 1 entry per module or class" do + ModuleSpecs::Parent.subclasses.should == ModuleSpecs::Parent.subclasses.uniq + end + + it "works when creating subclasses concurrently" do + t = 16 + n = 1000 + go = false + superclass = Class.new + + threads = t.times.map do + Thread.new do + Thread.pass until go + n.times.map do + Class.new(superclass) end end + end - go = true - classes = threads.map(&:value) + go = true + classes = threads.map(&:value) - superclass.subclasses.size.should == t * n - superclass.subclasses.each { |c| c.should be_kind_of(Class) } - end + superclass.subclasses.size.should == t * n + superclass.subclasses.each { |c| c.should be_kind_of(Class) } + end - def assert_subclasses(mod,subclasses) - mod.subclasses.sort_by(&:inspect).should == subclasses.sort_by(&:inspect) - end + def assert_subclasses(mod,subclasses) + mod.subclasses.sort_by(&:inspect).should == subclasses.sort_by(&:inspect) end end diff --git a/spec/ruby/core/comparable/clamp_spec.rb b/spec/ruby/core/comparable/clamp_spec.rb index 796d4a18c1..18f616a997 100644 --- a/spec/ruby/core/comparable/clamp_spec.rb +++ b/spec/ruby/core/comparable/clamp_spec.rb @@ -24,7 +24,7 @@ describe 'Comparable#clamp' do c.clamp(two, three).should equal(c) end - it 'returns the min parameter if smaller than it' do + it 'returns the min parameter if less than it' do one = ComparableSpecs::WithOnlyCompareDefined.new(1) two = ComparableSpecs::WithOnlyCompareDefined.new(2) c = ComparableSpecs::Weird.new(0) @@ -40,6 +40,39 @@ describe 'Comparable#clamp' do c.clamp(one, two).should equal(two) end + context 'max is nil' do + it 'returns min if less than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(0) + c.clamp(one, nil).should equal(one) + end + + it 'always returns self if greater than min' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + c.clamp(one, nil).should equal(c) + end + end + + context 'min is nil' do + it 'returns max if greater than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + c.clamp(nil, one).should equal(one) + end + + it 'always returns self if less than max' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(0) + c.clamp(nil, one).should equal(c) + end + end + + it 'always returns self when min is nil and max is nil' do + c = ComparableSpecs::Weird.new(1) + c.clamp(nil, nil).should equal(c) + end + it 'returns self if within the given range parameters' do one = ComparableSpecs::WithOnlyCompareDefined.new(1) two = ComparableSpecs::WithOnlyCompareDefined.new(2) @@ -52,7 +85,7 @@ describe 'Comparable#clamp' do c.clamp(two..three).should equal(c) end - it 'returns the minimum value of the range parameters if smaller than it' do + it 'returns the minimum value of the range parameters if less than it' do one = ComparableSpecs::WithOnlyCompareDefined.new(1) two = ComparableSpecs::WithOnlyCompareDefined.new(2) c = ComparableSpecs::Weird.new(0) @@ -75,4 +108,116 @@ describe 'Comparable#clamp' do -> { c.clamp(one...two) }.should raise_error(ArgumentError) end + + context 'with nil as the max argument' do + it 'returns min argument if less than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + zero = ComparableSpecs::WithOnlyCompareDefined.new(0) + c = ComparableSpecs::Weird.new(0) + + c.clamp(one, nil).should equal(one) + c.clamp(zero, nil).should equal(c) + end + + it 'always returns self if greater than min argument' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + two = ComparableSpecs::WithOnlyCompareDefined.new(2) + c = ComparableSpecs::Weird.new(2) + + c.clamp(one, nil).should equal(c) + c.clamp(two, nil).should equal(c) + end + end + + context 'with endless range' do + it 'returns minimum value of the range parameters if less than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + zero = ComparableSpecs::WithOnlyCompareDefined.new(0) + c = ComparableSpecs::Weird.new(0) + + c.clamp(one..).should equal(one) + c.clamp(zero..).should equal(c) + end + + it 'always returns self if greater than minimum value of the range parameters' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + two = ComparableSpecs::WithOnlyCompareDefined.new(2) + c = ComparableSpecs::Weird.new(2) + + c.clamp(one..).should equal(c) + c.clamp(two..).should equal(c) + end + + it 'works with exclusive range' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + + c.clamp(one...).should equal(c) + end + end + + context 'with nil as the min argument' do + it 'returns max argument if greater than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + + c.clamp(nil, one).should equal(one) + end + + it 'always returns self if less than max argument' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + zero = ComparableSpecs::WithOnlyCompareDefined.new(0) + c = ComparableSpecs::Weird.new(0) + + c.clamp(nil, one).should equal(c) + c.clamp(nil, zero).should equal(c) + end + end + + context 'with beginless range' do + it 'returns maximum value of the range parameters if greater than it' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(2) + + c.clamp(..one).should equal(one) + end + + it 'always returns self if less than maximum value of the range parameters' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + zero = ComparableSpecs::WithOnlyCompareDefined.new(0) + c = ComparableSpecs::Weird.new(0) + + c.clamp(..one).should equal(c) + c.clamp(..zero).should equal(c) + end + + it 'raises an Argument error if the range parameter is exclusive' do + one = ComparableSpecs::WithOnlyCompareDefined.new(1) + c = ComparableSpecs::Weird.new(0) + + -> { c.clamp(...one) }.should raise_error(ArgumentError) + end + end + + context 'with nil as the min and the max argument' do + it 'always returns self' do + c = ComparableSpecs::Weird.new(1) + + c.clamp(nil, nil).should equal(c) + end + end + + context 'with beginless-and-endless range' do + it 'always returns self' do + c = ComparableSpecs::Weird.new(1) + + c.clamp(nil..nil).should equal(c) + end + + it 'works with exclusive range' do + c = ComparableSpecs::Weird.new(2) + + c.clamp(nil...nil).should equal(c) + end + end end diff --git a/spec/ruby/core/comparable/fixtures/classes.rb b/spec/ruby/core/comparable/fixtures/classes.rb index 4239a47d2f..2bdabbf014 100644 --- a/spec/ruby/core/comparable/fixtures/classes.rb +++ b/spec/ruby/core/comparable/fixtures/classes.rb @@ -7,6 +7,7 @@ module ComparableSpecs end def <=>(other) + return nil if other.nil? self.value <=> other.value end end diff --git a/spec/ruby/core/complex/equal_value_spec.rb b/spec/ruby/core/complex/equal_value_spec.rb index ad7236b1bd..97c486d820 100644 --- a/spec/ruby/core/complex/equal_value_spec.rb +++ b/spec/ruby/core/complex/equal_value_spec.rb @@ -76,7 +76,7 @@ describe "Complex#==" do (Complex(real, 0) == @other).should be_true end - it "returns false when when the imaginary part is not zero" do + it "returns false when the imaginary part is not zero" do (Complex(3, 1) == @other).should be_false end end diff --git a/spec/ruby/core/complex/inspect_spec.rb b/spec/ruby/core/complex/inspect_spec.rb index 7a89ec6854..045be94b22 100644 --- a/spec/ruby/core/complex/inspect_spec.rb +++ b/spec/ruby/core/complex/inspect_spec.rb @@ -17,7 +17,8 @@ describe "Complex#inspect" do it "calls #inspect on real and imaginary" do real = NumericSpecs::Subclass.new - real.should_receive(:inspect).and_return("1") + # + because of https://bugs.ruby-lang.org/issues/20337 + real.should_receive(:inspect).and_return(+"1") imaginary = NumericSpecs::Subclass.new imaginary.should_receive(:inspect).and_return("2") imaginary.should_receive(:<).any_number_of_times.and_return(false) @@ -26,7 +27,8 @@ describe "Complex#inspect" do it "adds an `*' before the `i' if the last character of the imaginary part is not numeric" do real = NumericSpecs::Subclass.new - real.should_receive(:inspect).and_return("(1)") + # + because of https://bugs.ruby-lang.org/issues/20337 + real.should_receive(:inspect).and_return(+"(1)") imaginary = NumericSpecs::Subclass.new imaginary.should_receive(:inspect).and_return("(2)") imaginary.should_receive(:<).any_number_of_times.and_return(false) diff --git a/spec/ruby/core/complex/polar_spec.rb b/spec/ruby/core/complex/polar_spec.rb index 3bb3751bc6..56335584ef 100644 --- a/spec/ruby/core/complex/polar_spec.rb +++ b/spec/ruby/core/complex/polar_spec.rb @@ -11,20 +11,18 @@ describe "Complex.polar" do ->{ Complex.polar(nil, nil) }.should raise_error(TypeError) end - ruby_bug "#19004", ""..."3.2" do - it "computes the real values of the real & imaginary parts from the polar form" do - a = Complex.polar(1.0+0.0i, Math::PI/2+0.0i) - a.real.should be_close(0.0, TOLERANCE) - a.imag.should be_close(1.0, TOLERANCE) - a.real.real?.should be_true - a.imag.real?.should be_true + it "computes the real values of the real & imaginary parts from the polar form" do + a = Complex.polar(1.0+0.0i, Math::PI/2+0.0i) + a.real.should be_close(0.0, TOLERANCE) + a.imag.should be_close(1.0, TOLERANCE) + a.real.real?.should be_true + a.imag.real?.should be_true - b = Complex.polar(1+0.0i) - b.real.should be_close(1.0, TOLERANCE) - b.imag.should be_close(0.0, TOLERANCE) - b.real.real?.should be_true - b.imag.real?.should be_true - end + b = Complex.polar(1+0.0i) + b.real.should be_close(1.0, TOLERANCE) + b.imag.should be_close(0.0, TOLERANCE) + b.real.real?.should be_true + b.imag.real?.should be_true end end diff --git a/spec/ruby/core/complex/shared/rect.rb b/spec/ruby/core/complex/shared/rect.rb index 9f5de1ffeb..4ac294e771 100644 --- a/spec/ruby/core/complex/shared/rect.rb +++ b/spec/ruby/core/complex/shared/rect.rb @@ -24,15 +24,15 @@ describe :complex_rect, shared: true do end it "returns the real part of self as the first element" do - @numbers.each do |number| - number.send(@method).first.should == number.real - end + @numbers.each do |number| + number.send(@method).first.should == number.real + end end it "returns the imaginary part of self as the last element" do - @numbers.each do |number| - number.send(@method).last.should == number.imaginary - end + @numbers.each do |number| + number.send(@method).last.should == number.imaginary + end end it "raises an ArgumentError if given any arguments" do diff --git a/spec/ruby/core/complex/to_r_spec.rb b/spec/ruby/core/complex/to_r_spec.rb index 4559921492..788027a500 100644 --- a/spec/ruby/core/complex/to_r_spec.rb +++ b/spec/ruby/core/complex/to_r_spec.rb @@ -34,8 +34,16 @@ describe "Complex#to_r" do end describe "when the imaginary part is Float 0.0" do - it "raises RangeError" do - -> { Complex(0, 0.0).to_r }.should raise_error(RangeError) + ruby_version_is ''...'3.4' do + it "raises RangeError" do + -> { Complex(0, 0.0).to_r }.should raise_error(RangeError) + end + end + + ruby_version_is '3.4' do + it "returns a Rational" do + Complex(0, 0.0).to_r.should == 0r + end end end end diff --git a/spec/ruby/core/complex/to_s_spec.rb b/spec/ruby/core/complex/to_s_spec.rb index 7677dcd0b5..ceccffe470 100644 --- a/spec/ruby/core/complex/to_s_spec.rb +++ b/spec/ruby/core/complex/to_s_spec.rb @@ -45,7 +45,8 @@ describe "Complex#to_s" do it "treats real and imaginary parts as strings" do real = NumericSpecs::Subclass.new - real.should_receive(:to_s).and_return("1") + # + because of https://bugs.ruby-lang.org/issues/20337 + real.should_receive(:to_s).and_return(+"1") imaginary = NumericSpecs::Subclass.new imaginary.should_receive(:to_s).and_return("2") imaginary.should_receive(:<).any_number_of_times.and_return(false) diff --git a/spec/ruby/core/conditionvariable/broadcast_spec.rb b/spec/ruby/core/conditionvariable/broadcast_spec.rb index d88159df23..55a7b89c72 100644 --- a/spec/ruby/core/conditionvariable/broadcast_spec.rb +++ b/spec/ruby/core/conditionvariable/broadcast_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'thread' describe "ConditionVariable#broadcast" do it "releases all threads waiting in line for this resource" do diff --git a/spec/ruby/core/conditionvariable/marshal_dump_spec.rb b/spec/ruby/core/conditionvariable/marshal_dump_spec.rb index f951a13e28..88b1cc38c1 100644 --- a/spec/ruby/core/conditionvariable/marshal_dump_spec.rb +++ b/spec/ruby/core/conditionvariable/marshal_dump_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'thread' describe "ConditionVariable#marshal_dump" do it "raises a TypeError" do diff --git a/spec/ruby/core/conditionvariable/signal_spec.rb b/spec/ruby/core/conditionvariable/signal_spec.rb index 86383073f1..43a9cc611b 100644 --- a/spec/ruby/core/conditionvariable/signal_spec.rb +++ b/spec/ruby/core/conditionvariable/signal_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'thread' describe "ConditionVariable#signal" do it "releases the first thread waiting in line for this resource" do diff --git a/spec/ruby/core/conditionvariable/wait_spec.rb b/spec/ruby/core/conditionvariable/wait_spec.rb index 9a68c2b5a1..fe73e513c0 100644 --- a/spec/ruby/core/conditionvariable/wait_spec.rb +++ b/spec/ruby/core/conditionvariable/wait_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'thread' describe "ConditionVariable#wait" do it "calls #sleep on the given object" do diff --git a/spec/ruby/core/data/constants_spec.rb b/spec/ruby/core/data/constants_spec.rb index 2eb43d501e..ad0b1ddea7 100644 --- a/spec/ruby/core/data/constants_spec.rb +++ b/spec/ruby/core/data/constants_spec.rb @@ -1,21 +1,11 @@ require_relative '../../spec_helper' -ruby_version_is ''...'3.2' do - describe "Data" do - it "does not exist anymore" do - Object.should_not have_constant(:Data) - end +describe "Data" do + it "is a new constant" do + Data.superclass.should == Object end -end - -ruby_version_is '3.2' do - describe "Data" do - it "is a new constant" do - Data.superclass.should == Object - end - it "is not deprecated" do - -> { Data }.should_not complain - end + it "is not deprecated" do + -> { Data }.should_not complain end end diff --git a/spec/ruby/core/data/deconstruct_keys_spec.rb b/spec/ruby/core/data/deconstruct_keys_spec.rb new file mode 100644 index 0000000000..df378f8b98 --- /dev/null +++ b/spec/ruby/core/data/deconstruct_keys_spec.rb @@ -0,0 +1,130 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Data#deconstruct_keys" do + it "returns a hash of attributes" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys([:x, :y]).should == {x: 1, y: 2} + end + + it "requires one argument" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + -> { + d.deconstruct_keys + }.should raise_error(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/) + end + + it "returns only specified keys" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys([:x, :y]).should == {x: 1, y: 2} + d.deconstruct_keys([:x] ).should == {x: 1} + d.deconstruct_keys([] ).should == {} + end + + it "accepts string attribute names" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys(['x', 'y']).should == {'x' => 1, 'y' => 2} + end + + it "accepts argument position number as well but returns them as keys" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys([0, 1]).should == {0 => 1, 1 => 2} + d.deconstruct_keys([0] ).should == {0 => 1} + d.deconstruct_keys([-1] ).should == {-1 => 2} + end + + it "ignores incorrect position numbers" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys([0, 3]).should == {0 => 1} + end + + it "support mixing attribute names and argument position numbers" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys([0, :x]).should == {0 => 1, :x => 1} + end + + it "returns an empty hash when there are more keys than attributes" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys([:x, :y, :x]).should == {} + end + + it "returns at first not existing attribute name" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys([:a, :x]).should == {} + d.deconstruct_keys([:x, :a]).should == {x: 1} + end + + it "returns at first not existing argument position number" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys([3, 0]).should == {} + d.deconstruct_keys([0, 3]).should == {0 => 1} + end + + it "accepts nil argument and return all the attributes" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + d.deconstruct_keys(nil).should == {x: 1, y: 2} + end + + it "tries to convert a key with #to_int if index is not a String nor a Symbol, but responds to #to_int" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + key = mock("to_int") + key.should_receive(:to_int).and_return(1) + + d.deconstruct_keys([key]).should == { key => 2 } + end + + it "raises a TypeError if the conversion with #to_int does not return an Integer" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + key = mock("to_int") + key.should_receive(:to_int).and_return("not an Integer") + + -> { + d.deconstruct_keys([key]) + }.should raise_error(TypeError, /can't convert MockObject to Integer/) + end + + it "raises TypeError if index is not a String, a Symbol and not convertible to Integer " do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + -> { + d.deconstruct_keys([0, []]) + }.should raise_error(TypeError, "no implicit conversion of Array into Integer") + end + + it "raise TypeError if passed anything except nil or array" do + klass = Data.define(:x, :y) + d = klass.new(1, 2) + + -> { d.deconstruct_keys('x') }.should raise_error(TypeError, /expected Array or nil/) + -> { d.deconstruct_keys(1) }.should raise_error(TypeError, /expected Array or nil/) + -> { d.deconstruct_keys(:x) }.should raise_error(TypeError, /expected Array or nil/) + -> { d.deconstruct_keys({}) }.should raise_error(TypeError, /expected Array or nil/) + end +end diff --git a/spec/ruby/core/data/deconstruct_spec.rb b/spec/ruby/core/data/deconstruct_spec.rb new file mode 100644 index 0000000000..4ca0b87039 --- /dev/null +++ b/spec/ruby/core/data/deconstruct_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Data#deconstruct" do + it "returns an array of attribute values" do + DataSpecs::Measure.new(42, "km").deconstruct.should == [42, "km"] + end +end diff --git a/spec/ruby/core/data/define_spec.rb b/spec/ruby/core/data/define_spec.rb index 2aa2c50d4c..c0b4671e39 100644 --- a/spec/ruby/core/data/define_spec.rb +++ b/spec/ruby/core/data/define_spec.rb @@ -1,36 +1,34 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is "3.2" do - describe "Data.define" do - it "accepts no arguments" do - empty_data = Data.define - empty_data.members.should == [] - end +describe "Data.define" do + it "accepts no arguments" do + empty_data = Data.define + empty_data.members.should == [] + end - it "accepts symbols" do - movie = Data.define(:title, :year) - movie.members.should == [:title, :year] - end + it "accepts symbols" do + movie = Data.define(:title, :year) + movie.members.should == [:title, :year] + end - it "accepts strings" do - movie = Data.define("title", "year") - movie.members.should == [:title, :year] - end + it "accepts strings" do + movie = Data.define("title", "year") + movie.members.should == [:title, :year] + end - it "accepts a mix of strings and symbols" do - movie = Data.define("title", :year, "genre") - movie.members.should == [:title, :year, :genre] - end + it "accepts a mix of strings and symbols" do + movie = Data.define("title", :year, "genre") + movie.members.should == [:title, :year, :genre] + end - it "accepts a block" do - movie = Data.define(:title, :year) do - def title_with_year - "#{title} (#{year})" - end + it "accepts a block" do + movie = Data.define(:title, :year) do + def title_with_year + "#{title} (#{year})" end - movie.members.should == [:title, :year] - movie.new("Matrix", 1999).title_with_year.should == "Matrix (1999)" end + movie.members.should == [:title, :year] + movie.new("Matrix", 1999).title_with_year.should == "Matrix (1999)" end end diff --git a/spec/ruby/core/data/eql_spec.rb b/spec/ruby/core/data/eql_spec.rb new file mode 100644 index 0000000000..6958d5de4a --- /dev/null +++ b/spec/ruby/core/data/eql_spec.rb @@ -0,0 +1,63 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Data#eql?" do + it "returns true if the other is the same object" do + a = DataSpecs::Measure.new(42, "km") + a.should.eql?(a) + end + + it "returns true if the other has all the same fields" do + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(42, "km") + a.should.eql?(b) + end + + it "returns false if the other is a different object or has different fields" do + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(42, "mi") + a.should_not.eql?(b) + end + + it "returns false if other is of a different class" do + a = DataSpecs::Measure.new(42, "km") + klass = Data.define(*DataSpecs::Measure.members) + b = klass.new(42, "km") + a.should_not.eql?(b) + end + + it "returns false if any corresponding elements are not equal with #eql?" do + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(42.0, "mi") + a.should_not.eql?(b) + end + + context "recursive structure" do + it "returns true the other is the same object" do + a = DataSpecs::Measure.allocate + a.send(:initialize, amount: 42, unit: a) + + a.should.eql?(a) + end + + it "returns true if the other has all the same fields" do + a = DataSpecs::Measure.allocate + a.send(:initialize, amount: 42, unit: a) + + b = DataSpecs::Measure.allocate + b.send(:initialize, amount: 42, unit: b) + + a.should.eql?(b) + end + + it "returns false if any corresponding elements are not equal with #eql?" do + a = DataSpecs::Measure.allocate + a.send(:initialize, amount: a, unit: "km") + + b = DataSpecs::Measure.allocate + b.send(:initialize, amount: b, unit: "mi") + + a.should_not.eql?(b) + end + end +end diff --git a/spec/ruby/core/data/equal_value_spec.rb b/spec/ruby/core/data/equal_value_spec.rb new file mode 100644 index 0000000000..d9a0dcff3e --- /dev/null +++ b/spec/ruby/core/data/equal_value_spec.rb @@ -0,0 +1,63 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Data#==" do + it "returns true if the other is the same object" do + a = DataSpecs::Measure.new(42, "km") + a.should == a + end + + it "returns true if the other has all the same fields" do + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(42, "km") + a.should == b + end + + it "returns false if the other is a different object or has different fields" do + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(42, "mi") + a.should_not == b + end + + it "returns false if other is of a different class" do + a = DataSpecs::Measure.new(42, "km") + klass = Data.define(*DataSpecs::Measure.members) + b = klass.new(42, "km") + a.should_not == b + end + + it "returns false if any corresponding elements are not equal with #==" do + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(42.0, "mi") + a.should_not == b + end + + context "recursive structure" do + it "returns true the other is the same object" do + a = DataSpecs::Measure.allocate + a.send(:initialize, amount: 42, unit: a) + + a.should == a + end + + it "returns true if the other has all the same fields" do + a = DataSpecs::Measure.allocate + a.send(:initialize, amount: 42, unit: a) + + b = DataSpecs::Measure.allocate + b.send(:initialize, amount: 42, unit: b) + + a.should == b + end + + it "returns false if any corresponding elements are not equal with #==" do + a = DataSpecs::Measure.allocate + a.send(:initialize, amount: a, unit: "km") + + b = DataSpecs::Measure.allocate + b.send(:initialize, amount: b, unit: "mi") + + a.should_not == b + end + end +end diff --git a/spec/ruby/core/data/fixtures/classes.rb b/spec/ruby/core/data/fixtures/classes.rb index d1e10e02ed..650c0b2a62 100644 --- a/spec/ruby/core/data/fixtures/classes.rb +++ b/spec/ruby/core/data/fixtures/classes.rb @@ -1,5 +1,34 @@ module DataSpecs - ruby_version_is "3.2" do + if Data.respond_to?(:define) Measure = Data.define(:amount, :unit) + + class MeasureWithOverriddenName < Measure + def self.name + "A" + end + end + + class DataSubclass < Data; end + + MeasureSubclass = Class.new(Measure) do + def initialize(amount:, unit:) + super + end + end + + Empty = Data.define() + + DataWithOverriddenInitialize = Data.define(:amount, :unit) do + def initialize(*rest, **kw) + super + ScratchPad.record [:initialize, rest, kw] + end + end + + Area = Data.define(:width, :height, :area) do + def initialize(width:, height:) + super(width: width, height: height, area: width * height) + end + end end end diff --git a/spec/ruby/core/data/hash_spec.rb b/spec/ruby/core/data/hash_spec.rb new file mode 100644 index 0000000000..c23f08a71d --- /dev/null +++ b/spec/ruby/core/data/hash_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Data#hash" do + it "returns the same integer for objects with the same content" do + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(42, "km") + a.hash.should == b.hash + a.hash.should be_an_instance_of(Integer) + end + + it "returns different hashes for objects with different values" do + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(42, "ml") + a.hash.should_not == b.hash + + a = DataSpecs::Measure.new(42, "km") + b = DataSpecs::Measure.new(13, "km") + a.hash.should_not == b.hash + end + + it "returns different hashes for different classes" do + Data.define(:x).new(1).hash.should != Data.define(:x).new(1).hash + end +end diff --git a/spec/ruby/core/data/initialize_spec.rb b/spec/ruby/core/data/initialize_spec.rb index 94470cd108..010c73b91b 100644 --- a/spec/ruby/core/data/initialize_spec.rb +++ b/spec/ruby/core/data/initialize_spec.rb @@ -1,58 +1,124 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is "3.2" do - describe "Data#initialize" do - it "accepts positional arguments" do - data = DataSpecs::Measure.new(42, "km") +describe "Data#initialize" do + it "accepts positional arguments" do + data = DataSpecs::Measure.new(42, "km") - data.amount.should == 42 - data.unit.should == "km" - end + data.amount.should == 42 + data.unit.should == "km" + end - it "accepts alternative positional arguments" do - data = DataSpecs::Measure[42, "km"] + it "accepts alternative positional arguments" do + data = DataSpecs::Measure[42, "km"] - data.amount.should == 42 - data.unit.should == "km" - end + data.amount.should == 42 + data.unit.should == "km" + end + + it "accepts keyword arguments" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + + data.amount.should == 42 + data.unit.should == "km" + end + + it "accepts alternative keyword arguments" do + data = DataSpecs::Measure[amount: 42, unit: "km"] + + data.amount.should == 42 + data.unit.should == "km" + end + + it "accepts String keyword arguments" do + data = DataSpecs::Measure.new("amount" => 42, "unit" => "km") + + data.amount.should == 42 + data.unit.should == "km" + end + + it "raises ArgumentError if no arguments are given" do + -> { + DataSpecs::Measure.new + }.should raise_error(ArgumentError) { |e| + e.message.should.include?("missing keywords: :amount, :unit") + } + end + + it "raises ArgumentError if at least one argument is missing" do + -> { + DataSpecs::Measure.new(unit: "km") + }.should raise_error(ArgumentError) { |e| + e.message.should.include?("missing keyword: :amount") + } + end + + it "raises ArgumentError if unknown keyword is given" do + -> { + DataSpecs::Measure.new(amount: 42, unit: "km", system: "metric") + }.should raise_error(ArgumentError) { |e| + e.message.should.include?("unknown keyword: :system") + } + end + + it "supports super from a subclass" do + ms = DataSpecs::MeasureSubclass.new(amount: 1, unit: "km") + + ms.amount.should == 1 + ms.unit.should == "km" + end + + it "supports Data with no fields" do + -> { DataSpecs::Empty.new }.should_not raise_error + end - it "accepts keyword arguments" do - data = DataSpecs::Measure.new(amount: 42, unit: "km") + it "can be overridden" do + ScratchPad.record [] - data.amount.should == 42 - data.unit.should == "km" + measure_class = Data.define(:amount, :unit) do + def initialize(*, **) + super + ScratchPad << :initialize + end end - it "accepts alternative keyword arguments" do - data = DataSpecs::Measure[amount: 42, unit: "km"] + measure_class.new(42, "m") + ScratchPad.recorded.should == [:initialize] + end - data.amount.should == 42 - data.unit.should == "km" + context "when it is overridden" do + it "is called with keyword arguments when given positional arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize.new(42, "m") + ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] end - it "raises ArgumentError if no arguments are given" do - -> { - DataSpecs::Measure.new - }.should raise_error(ArgumentError) { |e| - e.message.should.include?("missing keywords: :amount, :unit") - } + it "is called with keyword arguments when given keyword arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize.new(amount: 42, unit: "m") + ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] end - it "raises ArgumentError if at least one argument is missing" do - -> { - DataSpecs::Measure.new(unit: "km") - }.should raise_error(ArgumentError) { |e| - e.message.should.include?("missing keyword: :amount") - } + it "is called with keyword arguments when given alternative positional arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize[42, "m"] + ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] end - it "raises ArgumentError if unknown keyword is given" do - -> { - DataSpecs::Measure.new(amount: 42, unit: "km", system: "metric") - }.should raise_error(ArgumentError) { |e| - e.message.should.include?("unknown keyword: :system") - } + it "is called with keyword arguments when given alternative keyword arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize[amount: 42, unit: "m"] + ScratchPad.recorded.should == [:initialize, [], {amount: 42, unit: "m"}] + end + + # See https://github.com/ruby/psych/pull/765 + it "can be deserialized by calling Data.instance_method(:initialize)" do + d1 = DataSpecs::Area.new(width: 2, height: 3) + d1.area.should == 6 + + d2 = DataSpecs::Area.allocate + Data.instance_method(:initialize).bind_call(d2, **d1.to_h) + d2.should == d1 end end end diff --git a/spec/ruby/core/data/inspect_spec.rb b/spec/ruby/core/data/inspect_spec.rb new file mode 100644 index 0000000000..38642910a0 --- /dev/null +++ b/spec/ruby/core/data/inspect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/inspect' + +describe "Data#inspect" do + it_behaves_like :data_inspect, :inspect +end diff --git a/spec/ruby/core/data/members_spec.rb b/spec/ruby/core/data/members_spec.rb new file mode 100644 index 0000000000..457a90a0d6 --- /dev/null +++ b/spec/ruby/core/data/members_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Data#members" do + it "returns an array of attribute names" do + measure = DataSpecs::Measure.new(amount: 42, unit: 'km') + measure.members.should == [:amount, :unit] + end +end + +describe "DataClass#members" do + it "returns an array of attribute names" do + DataSpecs::Measure.members.should == [:amount, :unit] + end + + context "class inheriting Data" do + it "isn't available in a subclass" do + DataSpecs::DataSubclass.should_not.respond_to?(:members) + end + end +end diff --git a/spec/ruby/core/data/shared/inspect.rb b/spec/ruby/core/data/shared/inspect.rb new file mode 100644 index 0000000000..6cd5664da7 --- /dev/null +++ b/spec/ruby/core/data/shared/inspect.rb @@ -0,0 +1,62 @@ +require_relative '../fixtures/classes' + +describe :data_inspect, shared: true do + it "returns a string representation showing members and values" do + a = DataSpecs::Measure.new(42, "km") + a.send(@method).should == '#<data DataSpecs::Measure amount=42, unit="km">' + end + + it "returns a string representation without the class name for anonymous structs" do + Data.define(:a).new("").send(@method).should == '#<data a="">' + end + + it "returns a string representation without the class name for structs nested in anonymous classes" do + c = Class.new + c.class_eval <<~DOC + Foo = Data.define(:a) + DOC + + c::Foo.new("").send(@method).should == '#<data a="">' + end + + it "returns a string representation without the class name for structs nested in anonymous modules" do + m = Module.new + m.class_eval <<~DOC + Foo = Data.define(:a) + DOC + + m::Foo.new("").send(@method).should == '#<data a="">' + end + + it "does not call #name method" do + struct = DataSpecs::MeasureWithOverriddenName.new(42, "km") + struct.send(@method).should == '#<data DataSpecs::MeasureWithOverriddenName amount=42, unit="km">' + end + + it "does not call #name method when struct is anonymous" do + klass = Class.new(DataSpecs::Measure) do + def self.name + "A" + end + end + struct = klass.new(42, "km") + struct.send(@method).should == '#<data amount=42, unit="km">' + end + + context "recursive structure" do + it "returns string representation with recursive attribute replaced with ..." do + a = DataSpecs::Measure.allocate + a.send(:initialize, amount: 42, unit: a) + + a.send(@method).should == "#<data DataSpecs::Measure amount=42, unit=#<data DataSpecs::Measure:...>>" + end + + it "returns string representation with recursive attribute replaced with ... when an anonymous class" do + klass = Class.new(DataSpecs::Measure) + a = klass.allocate + a.send(:initialize, amount: 42, unit: a) + + a.send(@method).should =~ /#<data amount=42, unit=#<data #<Class:0x.+?>:\.\.\.>>/ + end + end +end diff --git a/spec/ruby/core/data/to_h_spec.rb b/spec/ruby/core/data/to_h_spec.rb new file mode 100644 index 0000000000..64816b7251 --- /dev/null +++ b/spec/ruby/core/data/to_h_spec.rb @@ -0,0 +1,63 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Data#to_h" do + it "transforms the data object into a hash" do + data = DataSpecs::Measure.new(amount: 42, unit: 'km') + data.to_h.should == { amount: 42, unit: 'km' } + end + + context "with block" do + it "transforms [key, value] pairs returned by the block into a hash" do + data = DataSpecs::Measure.new(amount: 42, unit: 'km') + data.to_h { |key, value| [value, key] }.should == { 42 => :amount, 'km' => :unit } + end + + it "passes to a block each pair's key and value as separate arguments" do + ScratchPad.record [] + data = DataSpecs::Measure.new(amount: 42, unit: 'km') + data.to_h { |k, v| ScratchPad << [k, v]; [k, v] } + ScratchPad.recorded.sort.should == [[:amount, 42], [:unit, 'km']] + + ScratchPad.record [] + data.to_h { |*args| ScratchPad << args; [args[0], args[1]] } + ScratchPad.recorded.sort.should == [[:amount, 42], [:unit, 'km']] + end + + it "raises ArgumentError if block returns longer or shorter array" do + data = DataSpecs::Measure.new(amount: 42, unit: 'km') + -> do + data.to_h { |k, v| [k.to_s, v*v, 1] } + end.should raise_error(ArgumentError, /element has wrong array length/) + + -> do + data.to_h { |k, v| [k] } + end.should raise_error(ArgumentError, /element has wrong array length/) + end + + it "raises TypeError if block returns something other than Array" do + data = DataSpecs::Measure.new(amount: 42, unit: 'km') + -> do + data.to_h { |k, v| "not-array" } + end.should raise_error(TypeError, /wrong element type String/) + end + + it "coerces returned pair to Array with #to_ary" do + x = mock('x') + x.stub!(:to_ary).and_return([:b, 'b']) + data = DataSpecs::Measure.new(amount: 42, unit: 'km') + + data.to_h { |k| x }.should == { :b => 'b' } + end + + it "does not coerce returned pair to Array with #to_a" do + x = mock('x') + x.stub!(:to_a).and_return([:b, 'b']) + data = DataSpecs::Measure.new(amount: 42, unit: 'km') + + -> do + data.to_h { |k| x } + end.should raise_error(TypeError, /wrong element type MockObject/) + end + end +end diff --git a/spec/ruby/core/data/to_s_spec.rb b/spec/ruby/core/data/to_s_spec.rb new file mode 100644 index 0000000000..2b4a670e8e --- /dev/null +++ b/spec/ruby/core/data/to_s_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/inspect' + +describe "Data#to_s" do + it_behaves_like :data_inspect, :to_s +end diff --git a/spec/ruby/core/data/with_spec.rb b/spec/ruby/core/data/with_spec.rb new file mode 100644 index 0000000000..fd0a99d1fa --- /dev/null +++ b/spec/ruby/core/data/with_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Data#with" do + it "returns self if given no arguments" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + data = data.with.should.equal?(data) + end + + it "accepts keyword arguments" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + data = data.with(amount: 4, unit: "m") + + data.amount.should == 4 + data.unit.should == "m" + end + + it "accepts String keyword arguments" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + data = data.with("amount" => 4, "unit" => "m") + + data.amount.should == 4 + data.unit.should == "m" + end + + it "raises ArgumentError if no keyword arguments are given" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") + + -> { + data.with(4, "m") + }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 0)") + end + + it "does not depend on the Data.new method" do + subclass = Class.new(DataSpecs::Measure) + data = subclass.new(amount: 42, unit: "km") + + def subclass.new(*) + raise "Data.new is called" + end + + data_copy = data.with(unit: "m") + data_copy.amount.should == 42 + data_copy.unit.should == "m" + end + + ruby_version_is "3.3" do + it "calls #initialize" do + data = DataSpecs::DataWithOverriddenInitialize.new(42, "m") + ScratchPad.clear + + data.with(amount: 0) + + ScratchPad.recorded.should == [:initialize, [], {amount: 0, unit: "m"}] + end + end +end diff --git a/spec/ruby/core/dir/chdir_spec.rb b/spec/ruby/core/dir/chdir_spec.rb index 729ac403e3..015386a902 100644 --- a/spec/ruby/core/dir/chdir_spec.rb +++ b/spec/ruby/core/dir/chdir_spec.rb @@ -19,14 +19,14 @@ describe "Dir.chdir" do end it "defaults to $HOME with no arguments" do - if ENV['HOME'] - Dir.chdir - current_dir = Dir.pwd + skip "$HOME not valid directory" unless ENV['HOME'] && File.directory?(ENV['HOME']) - Dir.chdir(ENV['HOME']) - home = Dir.pwd - current_dir.should == home - end + Dir.chdir + current_dir = Dir.pwd + + Dir.chdir(ENV['HOME']) + home = Dir.pwd + current_dir.should == home end it "changes to the specified directory" do @@ -70,6 +70,8 @@ describe "Dir.chdir" do end it "defaults to the home directory when given a block but no argument" do + skip "$HOME not valid directory" unless ENV['HOME'] && File.directory?(ENV['HOME']) + # Windows will return a path with forward slashes for ENV["HOME"] so we have # to compare the route representations returned by Dir.chdir. current_dir = "" @@ -93,10 +95,10 @@ describe "Dir.chdir" do end it "raises an Errno::ENOENT if the original directory no longer exists" do - dir1 = tmp('/testdir1') - dir2 = tmp('/testdir2') - File.should_not.exist?(dir1) - File.should_not.exist?(dir2) + dir1 = tmp('testdir1') + dir2 = tmp('testdir2') + Dir.should_not.exist?(dir1) + Dir.should_not.exist?(dir2) Dir.mkdir dir1 Dir.mkdir dir2 begin @@ -106,8 +108,8 @@ describe "Dir.chdir" do end }.should raise_error(Errno::ENOENT) ensure - Dir.unlink dir1 if File.exist?(dir1) - Dir.unlink dir2 if File.exist?(dir2) + Dir.unlink dir1 if Dir.exist?(dir1) + Dir.unlink dir2 if Dir.exist?(dir2) end end @@ -122,3 +124,97 @@ describe "Dir.chdir" do Dir.pwd.should == @original end end + +ruby_version_is '3.3' do + describe "Dir#chdir" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + before :each do + @original = Dir.pwd + end + + after :each do + Dir.chdir(@original) + end + + it "changes the current working directory to self" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir + Dir.pwd.should == DirSpecs.mock_dir + ensure + dir.close + end + + it "changes the current working directory to self for duration of the block when a block is given" do + dir = Dir.new(DirSpecs.mock_dir) + pwd_in_block = nil + + dir.chdir { pwd_in_block = Dir.pwd } + + pwd_in_block.should == DirSpecs.mock_dir + Dir.pwd.should == @original + ensure + dir.close + end + + it "returns 0 when successfully changing directory" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir.should == 0 + ensure + dir.close + end + + it "returns the value of the block when a block is given" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir { :block_value }.should == :block_value + ensure + dir.close + end + + platform_is_not :windows do + it "does not raise an Errno::ENOENT if the original directory no longer exists" do + dir_name1 = tmp('testdir1') + dir_name2 = tmp('testdir2') + Dir.should_not.exist?(dir_name1) + Dir.should_not.exist?(dir_name2) + Dir.mkdir dir_name1 + Dir.mkdir dir_name2 + + dir2 = Dir.new(dir_name2) + + begin + Dir.chdir(dir_name1) do + dir2.chdir { Dir.unlink dir_name1 } + end + Dir.pwd.should == @original + ensure + Dir.unlink dir_name1 if Dir.exist?(dir_name1) + Dir.unlink dir_name2 if Dir.exist?(dir_name2) + end + ensure + dir2.close + end + end + + it "always returns to the original directory when given a block" do + dir = Dir.new(DirSpecs.mock_dir) + + begin + dir.chdir do + raise StandardError, "something bad happened" + end + rescue StandardError + end + + Dir.pwd.should == @original + ensure + dir.close + end + end +end diff --git a/spec/ruby/core/dir/children_spec.rb b/spec/ruby/core/dir/children_spec.rb index 03698cc246..0ad3df4669 100644 --- a/spec/ruby/core/dir/children_spec.rb +++ b/spec/ruby/core/dir/children_spec.rb @@ -47,7 +47,7 @@ describe "Dir.children" do encoding = Encoding.find("filesystem") encoding = Encoding::BINARY if encoding == Encoding::US_ASCII platform_is_not :windows do - children.should include("ã“ã‚“ã«ã¡ã¯.txt".force_encoding(encoding)) + children.should include("ã“ã‚“ã«ã¡ã¯.txt".dup.force_encoding(encoding)) end children.first.encoding.should equal(Encoding.find("filesystem")) end @@ -113,7 +113,7 @@ describe "Dir#children" do encoding = Encoding.find("filesystem") encoding = Encoding::BINARY if encoding == Encoding::US_ASCII platform_is_not :windows do - children.should include("ã“ã‚“ã«ã¡ã¯.txt".force_encoding(encoding)) + children.should include("ã“ã‚“ã«ã¡ã¯.txt".dup.force_encoding(encoding)) end children.first.encoding.should equal(Encoding.find("filesystem")) end @@ -131,4 +131,17 @@ describe "Dir#children" do children = @dir.children.sort children.first.encoding.should equal(Encoding::EUC_KR) end + + it "returns the same result when called repeatedly" do + @dir = Dir.open DirSpecs.mock_dir + + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end end diff --git a/spec/ruby/core/dir/close_spec.rb b/spec/ruby/core/dir/close_spec.rb index 5fad5eecfb..f7cce318b8 100644 --- a/spec/ruby/core/dir/close_spec.rb +++ b/spec/ruby/core/dir/close_spec.rb @@ -11,9 +11,43 @@ describe "Dir#close" do it "does not raise an IOError even if the Dir instance is closed" do dir = Dir.open DirSpecs.mock_dir - dir.close - -> { - dir.close - }.should_not raise_error(IOError) + dir.close.should == nil + dir.close.should == nil + + platform_is_not :windows do + -> { dir.fileno }.should raise_error(IOError, /closed directory/) + end + end + + it "returns nil" do + dir = Dir.open DirSpecs.mock_dir + dir.close.should == nil + end + + ruby_version_is '3.3'...'3.4' do + platform_is_not :windows do + it "does not raise an error even if the file descriptor is closed with another Dir instance" do + dir = Dir.open DirSpecs.mock_dir + dir_new = Dir.for_fd(dir.fileno) + + dir.close + dir_new.close + + -> { dir.fileno }.should raise_error(IOError, /closed directory/) + -> { dir_new.fileno }.should raise_error(IOError, /closed directory/) + end + end + end + + ruby_version_is '3.4' do + platform_is_not :windows do + it "raises an error if the file descriptor is closed with another Dir instance" do + dir = Dir.open DirSpecs.mock_dir + dir_new = Dir.for_fd(dir.fileno) + dir.close + + -> { dir_new.close }.should raise_error(Errno::EBADF, 'Bad file descriptor - closedir') + end + end end end diff --git a/spec/ruby/core/dir/each_child_spec.rb b/spec/ruby/core/dir/each_child_spec.rb index 520186e79e..7194273b95 100644 --- a/spec/ruby/core/dir/each_child_spec.rb +++ b/spec/ruby/core/dir/each_child_spec.rb @@ -86,6 +86,19 @@ describe "Dir#each_child" do @dir.each_child { |f| f }.should == @dir end + it "returns the same result when called repeatedly" do + @dir = Dir.open DirSpecs.mock_dir + + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end + describe "when no block is given" do it "returns an Enumerator" do @dir = Dir.new(DirSpecs.mock_dir) diff --git a/spec/ruby/core/dir/each_spec.rb b/spec/ruby/core/dir/each_spec.rb index 8c69a7212b..7674663d82 100644 --- a/spec/ruby/core/dir/each_spec.rb +++ b/spec/ruby/core/dir/each_spec.rb @@ -35,6 +35,17 @@ describe "Dir#each" do ls.should include(@dir.read) end + it "returns the same result when called repeatedly" do + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end + describe "when no block is given" do it "returns an Enumerator" do @dir.each.should be_an_instance_of(Enumerator) diff --git a/spec/ruby/core/dir/entries_spec.rb b/spec/ruby/core/dir/entries_spec.rb index 91c30fccae..7462542acf 100644 --- a/spec/ruby/core/dir/entries_spec.rb +++ b/spec/ruby/core/dir/entries_spec.rb @@ -47,7 +47,7 @@ describe "Dir.entries" do encoding = Encoding.find("filesystem") encoding = Encoding::BINARY if encoding == Encoding::US_ASCII platform_is_not :windows do - entries.should include("ã“ã‚“ã«ã¡ã¯.txt".force_encoding(encoding)) + entries.should include("ã“ã‚“ã«ã¡ã¯.txt".dup.force_encoding(encoding)) end entries.first.encoding.should equal(Encoding.find("filesystem")) end diff --git a/spec/ruby/core/dir/exist_spec.rb b/spec/ruby/core/dir/exist_spec.rb index 43987b0f32..0b8e521894 100644 --- a/spec/ruby/core/dir/exist_spec.rb +++ b/spec/ruby/core/dir/exist_spec.rb @@ -13,3 +13,9 @@ describe "Dir.exist?" do it_behaves_like :dir_exist, :exist? end + +describe "Dir.exists?" do + it "has been removed" do + Dir.should_not.respond_to?(:exists?) + end +end diff --git a/spec/ruby/core/dir/fchdir_spec.rb b/spec/ruby/core/dir/fchdir_spec.rb index 429e569691..52600a95f2 100644 --- a/spec/ruby/core/dir/fchdir_spec.rb +++ b/spec/ruby/core/dir/fchdir_spec.rb @@ -2,7 +2,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/common' ruby_version_is '3.3' do - guard -> { Dir.respond_to? :fchdir } do + platform_is_not :windows do describe "Dir.fchdir" do before :all do DirSpecs.create_mock_dirs @@ -13,51 +13,56 @@ ruby_version_is '3.3' do end before :each do - @dirs = [Dir.new('.')] - @original = @dirs.first.fileno + @original = Dir.pwd end after :each do - Dir.fchdir(@original) - @dirs.each(&:close) + Dir.chdir(@original) end - it "changes to the specified directory" do + it "changes the current working directory to the directory specified by the integer file descriptor" do dir = Dir.new(DirSpecs.mock_dir) - @dirs << dir Dir.fchdir dir.fileno Dir.pwd.should == DirSpecs.mock_dir + ensure + dir.close end it "returns 0 when successfully changing directory" do - Dir.fchdir(@original).should == 0 + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir(dir.fileno).should == 0 + ensure + dir.close end it "returns the value of the block when a block is given" do - Dir.fchdir(@original) { :block_value }.should == :block_value + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir(dir.fileno) { :block_value }.should == :block_value + ensure + dir.close end it "changes to the specified directory for the duration of the block" do - pwd = Dir.pwd dir = Dir.new(DirSpecs.mock_dir) - @dirs << dir Dir.fchdir(dir.fileno) { Dir.pwd }.should == DirSpecs.mock_dir - Dir.pwd.should == pwd + Dir.pwd.should == @original + ensure + dir.close end it "raises a SystemCallError if the file descriptor given is not valid" do - -> { Dir.fchdir(-1) }.should raise_error(SystemCallError) - -> { Dir.fchdir(-1) { } }.should raise_error(SystemCallError) + -> { Dir.fchdir(-1) }.should raise_error(SystemCallError, "Bad file descriptor - fchdir") + -> { Dir.fchdir(-1) { } }.should raise_error(SystemCallError, "Bad file descriptor - fchdir") end it "raises a SystemCallError if the file descriptor given is not for a directory" do - -> { Dir.fchdir $stdout.fileno }.should raise_error(SystemCallError) - -> { Dir.fchdir($stdout.fileno) { } }.should raise_error(SystemCallError) + -> { Dir.fchdir $stdout.fileno }.should raise_error(SystemCallError, /(Not a directory|Invalid argument) - fchdir/) + -> { Dir.fchdir($stdout.fileno) { } }.should raise_error(SystemCallError, /(Not a directory|Invalid argument) - fchdir/) end end end - guard_not -> { Dir.respond_to? :fchdir } do + platform_is :windows do describe "Dir.fchdir" do it "raises NotImplementedError" do -> { Dir.fchdir 1 }.should raise_error(NotImplementedError) diff --git a/spec/ruby/core/dir/fixtures/common.rb b/spec/ruby/core/dir/fixtures/common.rb index 087f46b331..848656c9b9 100644 --- a/spec/ruby/core/dir/fixtures/common.rb +++ b/spec/ruby/core/dir/fixtures/common.rb @@ -192,13 +192,7 @@ module DirSpecs ] end - if RUBY_VERSION > '3.1' - def self.expected_glob_paths - expected_paths - ['..'] - end - else - def self.expected_glob_paths - expected_paths - end + def self.expected_glob_paths + expected_paths - ['..'] end end diff --git a/spec/ruby/core/dir/for_fd_spec.rb b/spec/ruby/core/dir/for_fd_spec.rb new file mode 100644 index 0000000000..ba467f2f86 --- /dev/null +++ b/spec/ruby/core/dir/for_fd_spec.rb @@ -0,0 +1,79 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +quarantine! do # leads to "Errno::EBADF: Bad file descriptor - closedir" in DirSpecs.delete_mock_dirs +ruby_version_is '3.3' do + platform_is_not :windows do + describe "Dir.for_fd" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + before :each do + @original = Dir.pwd + end + + after :each do + Dir.chdir(@original) + end + + it "returns a new Dir object representing the directory specified by the given integer directory file descriptor" do + dir = Dir.new(DirSpecs.mock_dir) + dir_new = Dir.for_fd(dir.fileno) + + dir_new.should.instance_of?(Dir) + dir_new.children.should == dir.children + dir_new.fileno.should == dir.fileno + ensure + dir.close + end + + it "returns a new Dir object without associated path" do + dir = Dir.new(DirSpecs.mock_dir) + dir_new = Dir.for_fd(dir.fileno) + + dir_new.path.should == nil + ensure + dir.close + end + + it "calls #to_int to convert a value to an Integer" do + dir = Dir.new(DirSpecs.mock_dir) + obj = mock("fd") + obj.should_receive(:to_int).and_return(dir.fileno) + + dir_new = Dir.for_fd(obj) + dir_new.fileno.should == dir.fileno + ensure + dir.close + end + + it "raises TypeError when value cannot be converted to Integer" do + -> { + Dir.for_fd(nil) + }.should raise_error(TypeError, "no implicit conversion from nil to integer") + end + + it "raises a SystemCallError if the file descriptor given is not valid" do + -> { Dir.for_fd(-1) }.should raise_error(SystemCallError, "Bad file descriptor - fdopendir") + end + + it "raises a SystemCallError if the file descriptor given is not for a directory" do + -> { Dir.for_fd $stdout.fileno }.should raise_error(SystemCallError, "Not a directory - fdopendir") + end + end + end + + platform_is :windows do + describe "Dir.for_fd" do + it "raises NotImplementedError" do + -> { Dir.for_fd 1 }.should raise_error(NotImplementedError) + end + end + end +end +end diff --git a/spec/ruby/core/dir/glob_spec.rb b/spec/ruby/core/dir/glob_spec.rb index 61115b5eec..a60b233bc0 100644 --- a/spec/ruby/core/dir/glob_spec.rb +++ b/spec/ruby/core/dir/glob_spec.rb @@ -89,31 +89,15 @@ describe "Dir.glob" do Dir.glob('**/', File::FNM_DOTMATCH).sort.should == expected end - ruby_version_is ''...'3.1' do - it "recursively matches files and directories in nested dot subdirectory with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do - expected = %w[ - nested/. - nested/.dotsubir - nested/.dotsubir/. - nested/.dotsubir/.dotfile - nested/.dotsubir/nondotfile - ] - - Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort - end - end - - ruby_version_is '3.1' do - it "recursively matches files and directories in nested dot subdirectory except . with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do - expected = %w[ - nested/. - nested/.dotsubir - nested/.dotsubir/.dotfile - nested/.dotsubir/nondotfile - ] + it "recursively matches files and directories in nested dot subdirectory except . with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do + expected = %w[ + nested/. + nested/.dotsubir + nested/.dotsubir/.dotfile + nested/.dotsubir/nondotfile + ] - Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort - end + Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort end # This is a separate case to check **/ coming after a constant @@ -260,34 +244,31 @@ describe "Dir.glob" do Dir.glob('**/.*', base: "deeply/nested").sort.should == expected end - # < 3.1 include a "." entry for every dir: ["directory/.", "directory/structure/.", ...] - ruby_version_is '3.1' do - it "handles **/.* with base keyword argument and FNM_DOTMATCH" do - expected = %w[ - . - .dotfile.ext - directory/structure/.ext - ].sort + it "handles **/.* with base keyword argument and FNM_DOTMATCH" do + expected = %w[ + . + .dotfile.ext + directory/structure/.ext + ].sort - Dir.glob('**/.*', File::FNM_DOTMATCH, base: "deeply/nested").sort.should == expected - end + Dir.glob('**/.*', File::FNM_DOTMATCH, base: "deeply/nested").sort.should == expected + end - it "handles **/** with base keyword argument and FNM_DOTMATCH" do - expected = %w[ - . - .dotfile.ext - directory - directory/structure - directory/structure/.ext - directory/structure/bar - directory/structure/baz - directory/structure/file_one - directory/structure/file_one.ext - directory/structure/foo - ].sort - - Dir.glob('**/**', File::FNM_DOTMATCH, base: "deeply/nested").sort.should == expected - end + it "handles **/** with base keyword argument and FNM_DOTMATCH" do + expected = %w[ + . + .dotfile.ext + directory + directory/structure + directory/structure/.ext + directory/structure/bar + directory/structure/baz + directory/structure/file_one + directory/structure/file_one.ext + directory/structure/foo + ].sort + + Dir.glob('**/**', File::FNM_DOTMATCH, base: "deeply/nested").sort.should == expected end it "handles **/*pattern* with base keyword argument and FNM_DOTMATCH" do diff --git a/spec/ruby/core/dir/home_spec.rb b/spec/ruby/core/dir/home_spec.rb index 90a008faf1..966ac38af3 100644 --- a/spec/ruby/core/dir/home_spec.rb +++ b/spec/ruby/core/dir/home_spec.rb @@ -33,13 +33,11 @@ describe "Dir.home" do end platform_is :windows do - ruby_version_is "3.2" do - it "returns the home directory with forward slashs and as UTF-8" do - ENV['HOME'] = "C:\\rubyspäc\\home" - home = Dir.home - home.should == "C:/rubyspäc/home" - home.encoding.should == Encoding::UTF_8 - end + it "returns the home directory with forward slashs and as UTF-8" do + ENV['HOME'] = "C:\\rubyspäc\\home" + home = Dir.home + home.should == "C:/rubyspäc/home" + home.encoding.should == Encoding::UTF_8 end it "retrieves the directory from HOME, USERPROFILE, HOMEDRIVE/HOMEPATH and the WinAPI in that order" do @@ -50,8 +48,7 @@ describe "Dir.home" do ENV['HOMEPATH'] = "\\rubyspec\\home1" Dir.home.should == "C:/rubyspec/home1" ENV['USERPROFILE'] = "C:\\rubyspec\\home2" - # https://bugs.ruby-lang.org/issues/19244 - # Dir.home.should == "C:/rubyspec/home2" + Dir.home.should == "C:/rubyspec/home2" ENV['HOME'] = "C:\\rubyspec\\home3" Dir.home.should == "C:/rubyspec/home3" ensure @@ -61,13 +58,7 @@ describe "Dir.home" do end describe "when called with the current user name" do - platform_is :solaris do - it "returns the named user's home directory from the user database" do - Dir.home(ENV['USER']).should == `getent passwd #{ENV['USER']}|cut -d: -f6`.chomp - end - end - - platform_is_not :windows, :solaris, :android, :wasi do + platform_is_not :windows, :android, :wasi do it "returns the named user's home directory, from the user database" do Dir.home(ENV['USER']).should == `echo ~#{ENV['USER']}`.chomp end diff --git a/spec/ruby/core/dir/shared/chroot.rb b/spec/ruby/core/dir/shared/chroot.rb index 8c0599fe3f..a8f7c10a19 100644 --- a/spec/ruby/core/dir/shared/chroot.rb +++ b/spec/ruby/core/dir/shared/chroot.rb @@ -2,7 +2,7 @@ describe :dir_chroot_as_root, shared: true do before :all do DirSpecs.create_mock_dirs - @real_root = "../" * (File.dirname(__FILE__).count('/') - 1) + @real_root = "../" * (__dir__.count('/') - 1) @ref_dir = File.join("/", File.basename(Dir["/*"].first)) end @@ -18,7 +18,7 @@ describe :dir_chroot_as_root, shared: true do compilations_ci = ENV["GITHUB_WORKFLOW"] == "Compilations" it "can be used to change the process' root directory" do - -> { Dir.send(@method, File.dirname(__FILE__)) }.should_not raise_error + -> { Dir.send(@method, __dir__) }.should_not raise_error File.should.exist?("/#{File.basename(__FILE__)}") end unless compilations_ci diff --git a/spec/ruby/core/dir/shared/delete.rb b/spec/ruby/core/dir/shared/delete.rb index 49e88360e8..a81b059759 100644 --- a/spec/ruby/core/dir/shared/delete.rb +++ b/spec/ruby/core/dir/shared/delete.rb @@ -17,20 +17,10 @@ describe :dir_delete, shared: true do Dir.send(@method, p) end - platform_is_not :solaris do - it "raises an Errno::ENOTEMPTY when trying to remove a nonempty directory" do - -> do - Dir.send @method, DirSpecs.mock_rmdir("nonempty") - end.should raise_error(Errno::ENOTEMPTY) - end - end - - platform_is :solaris do - it "raises an Errno::EEXIST when trying to remove a nonempty directory" do - -> do - Dir.send @method, DirSpecs.mock_rmdir("nonempty") - end.should raise_error(Errno::EEXIST) - end + it "raises an Errno::ENOTEMPTY when trying to remove a nonempty directory" do + -> do + Dir.send @method, DirSpecs.mock_rmdir("nonempty") + end.should raise_error(Errno::ENOTEMPTY) end it "raises an Errno::ENOENT when trying to remove a non-existing directory" do diff --git a/spec/ruby/core/dir/shared/exist.rb b/spec/ruby/core/dir/shared/exist.rb index 765d1b656c..3097f57715 100644 --- a/spec/ruby/core/dir/shared/exist.rb +++ b/spec/ruby/core/dir/shared/exist.rb @@ -1,6 +1,6 @@ describe :dir_exist, shared: true do it "returns true if the given directory exists" do - Dir.send(@method, File.dirname(__FILE__)).should be_true + Dir.send(@method, __dir__).should be_true end it "returns true for '.'" do @@ -20,7 +20,7 @@ describe :dir_exist, shared: true do end it "understands relative paths" do - Dir.send(@method, File.dirname(__FILE__) + '/../').should be_true + Dir.send(@method, __dir__ + '/../').should be_true end it "returns false if the given directory doesn't exist" do @@ -28,12 +28,13 @@ describe :dir_exist, shared: true do end it "doesn't require the name to have a trailing slash" do - dir = File.dirname(__FILE__) + dir = __dir__ dir.sub!(/\/$/,'') Dir.send(@method, dir).should be_true end it "doesn't expand paths" do + skip "$HOME not valid directory" unless ENV['HOME'] && File.directory?(ENV['HOME']) Dir.send(@method, File.expand_path('~')).should be_true Dir.send(@method, '~').should be_false end @@ -50,7 +51,7 @@ describe :dir_exist, shared: true do it "calls #to_path on non String arguments" do p = mock('path') - p.should_receive(:to_path).and_return(File.dirname(__FILE__)) + p.should_receive(:to_path).and_return(__dir__) Dir.send(@method, p) end end diff --git a/spec/ruby/core/dir/shared/glob.rb b/spec/ruby/core/dir/shared/glob.rb index 27ae0e3000..b1fc924f08 100644 --- a/spec/ruby/core/dir/shared/glob.rb +++ b/spec/ruby/core/dir/shared/glob.rb @@ -12,7 +12,7 @@ describe :dir_glob, shared: true do end it "raises an Encoding::CompatibilityError if the argument encoding is not compatible with US-ASCII" do - pattern = "file*".force_encoding Encoding::UTF_16BE + pattern = "files*".dup.force_encoding Encoding::UTF_16BE -> { Dir.send(@method, pattern) }.should raise_error(Encoding::CompatibilityError) end @@ -42,25 +42,10 @@ describe :dir_glob, shared: true do result.sort.should == Dir.send(@method, '*').sort end - ruby_version_is ""..."3.1" do - it "result is sorted with any non false value of sort:" do - result = Dir.send(@method, '*', sort: 0) - result.should == result.sort - - result = Dir.send(@method, '*', sort: nil) - result.should == result.sort - - result = Dir.send(@method, '*', sort: 'false') - result.should == result.sort - end - end - - ruby_version_is "3.1" do - it "raises an ArgumentError if sort: is not true or false" do - -> { Dir.send(@method, '*', sort: 0) }.should raise_error ArgumentError, /expected true or false/ - -> { Dir.send(@method, '*', sort: nil) }.should raise_error ArgumentError, /expected true or false/ - -> { Dir.send(@method, '*', sort: 'false') }.should raise_error ArgumentError, /expected true or false/ - end + it "raises an ArgumentError if sort: is not true or false" do + -> { Dir.send(@method, '*', sort: 0) }.should raise_error ArgumentError, /expected true or false/ + -> { Dir.send(@method, '*', sort: nil) }.should raise_error ArgumentError, /expected true or false/ + -> { Dir.send(@method, '*', sort: 'false') }.should raise_error ArgumentError, /expected true or false/ end it "matches non-dotfiles with '*'" do @@ -151,16 +136,8 @@ describe :dir_glob, shared: true do Dir.send(@method, 'special/test\{1\}/*').should == ['special/test{1}/file[1]'] end - ruby_version_is ''...'3.1' do - it "matches dotfiles with '.*'" do - Dir.send(@method, '.*').sort.should == %w|. .. .dotfile .dotsubdir|.sort - end - end - - ruby_version_is '3.1' do - it "matches dotfiles except .. with '.*'" do - Dir.send(@method, '.*').sort.should == %w|. .dotfile .dotsubdir|.sort - end + it "matches dotfiles except .. with '.*'" do + Dir.send(@method, '.*').sort.should == %w|. .dotfile .dotsubdir|.sort end it "matches non-dotfiles with '*<non-special characters>'" do @@ -205,16 +182,8 @@ describe :dir_glob, shared: true do Dir.send(@method, '**').sort.should == expected end - ruby_version_is ''...'3.1' do - it "matches dotfiles in the current directory with '.**'" do - Dir.send(@method, '.**').sort.should == %w|. .. .dotsubdir .dotfile|.sort - end - end - - ruby_version_is '3.1' do - it "matches dotfiles in the current directory except .. with '.**'" do - Dir.send(@method, '.**').sort.should == %w|. .dotsubdir .dotfile|.sort - end + it "matches dotfiles in the current directory except .. with '.**'" do + Dir.send(@method, '.**').sort.should == %w|. .dotsubdir .dotfile|.sort end it "recursively matches any nondot subdirectories with '**/'" do @@ -245,19 +214,9 @@ describe :dir_glob, shared: true do Dir.send(@method, '**/*ory', base: 'deeply').sort.should == expected end - ruby_version_is ''...'3.1' do - it "recursively matches any subdirectories including ./ and ../ with '.**/'" do - Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do - Dir.send(@method, '.**/').sort.should == %w|./ ../|.sort - end - end - end - - ruby_version_is '3.1' do - it "recursively matches any subdirectories including ./ with '.**/'" do - Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do - Dir.send(@method, '.**/').should == ['./'] - end + it "recursively matches any subdirectories including ./ with '.**/'" do + Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do + Dir.send(@method, '.**/').should == ['./'] end end diff --git a/spec/ruby/core/encoding/compatible_spec.rb b/spec/ruby/core/encoding/compatible_spec.rb index 80ecab6155..31376a3b75 100644 --- a/spec/ruby/core/encoding/compatible_spec.rb +++ b/spec/ruby/core/encoding/compatible_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' @@ -7,7 +7,7 @@ require_relative '../../spec_helper' describe "Encoding.compatible? String, String" do describe "when the first's Encoding is valid US-ASCII" do before :each do - @str = "abc".force_encoding Encoding::US_ASCII + @str = "abc".dup.force_encoding Encoding::US_ASCII end it "returns US-ASCII when the second's is US-ASCII" do @@ -33,28 +33,28 @@ describe "Encoding.compatible? String, String" do describe "when the first's Encoding is ASCII compatible and ASCII only" do it "returns the first's Encoding if the second is ASCII compatible and ASCII only" do - [ [Encoding, "abc".force_encoding("UTF-8"), "123".force_encoding("Shift_JIS"), Encoding::UTF_8], - [Encoding, "123".force_encoding("Shift_JIS"), "abc".force_encoding("UTF-8"), Encoding::Shift_JIS] + [ [Encoding, "abc".dup.force_encoding("UTF-8"), "123".dup.force_encoding("Shift_JIS"), Encoding::UTF_8], + [Encoding, "123".dup.force_encoding("Shift_JIS"), "abc".dup.force_encoding("UTF-8"), Encoding::Shift_JIS] ].should be_computed_by(:compatible?) end it "returns the first's Encoding if the second is ASCII compatible and ASCII only" do - [ [Encoding, "abc".force_encoding("BINARY"), "123".force_encoding("US-ASCII"), Encoding::BINARY], - [Encoding, "123".force_encoding("US-ASCII"), "abc".force_encoding("BINARY"), Encoding::US_ASCII] + [ [Encoding, "abc".dup.force_encoding("BINARY"), "123".dup.force_encoding("US-ASCII"), Encoding::BINARY], + [Encoding, "123".dup.force_encoding("US-ASCII"), "abc".dup.force_encoding("BINARY"), Encoding::US_ASCII] ].should be_computed_by(:compatible?) end it "returns the second's Encoding if the second is ASCII compatible but not ASCII only" do - [ [Encoding, "abc".force_encoding("UTF-8"), "\xff".force_encoding("Shift_JIS"), Encoding::Shift_JIS], - [Encoding, "123".force_encoding("Shift_JIS"), "\xff".force_encoding("UTF-8"), Encoding::UTF_8], - [Encoding, "abc".force_encoding("BINARY"), "\xff".force_encoding("US-ASCII"), Encoding::US_ASCII], - [Encoding, "123".force_encoding("US-ASCII"), "\xff".force_encoding("BINARY"), Encoding::BINARY], + [ [Encoding, "abc".dup.force_encoding("UTF-8"), "\xff".dup.force_encoding("Shift_JIS"), Encoding::Shift_JIS], + [Encoding, "123".dup.force_encoding("Shift_JIS"), "\xff".dup.force_encoding("UTF-8"), Encoding::UTF_8], + [Encoding, "abc".dup.force_encoding("BINARY"), "\xff".dup.force_encoding("US-ASCII"), Encoding::US_ASCII], + [Encoding, "123".dup.force_encoding("US-ASCII"), "\xff".dup.force_encoding("BINARY"), Encoding::BINARY], ].should be_computed_by(:compatible?) end it "returns nil if the second's Encoding is not ASCII compatible" do - a = "abc".force_encoding("UTF-8") - b = "1234".force_encoding("UTF-16LE") + a = "abc".dup.force_encoding("UTF-8") + b = "1234".dup.force_encoding("UTF-16LE") Encoding.compatible?(a, b).should be_nil end end @@ -75,7 +75,7 @@ describe "Encoding.compatible? String, String" do describe "when the first's Encoding is not ASCII compatible" do before :each do - @str = "abc".force_encoding Encoding::UTF_7 + @str = "abc".dup.force_encoding Encoding::UTF_7 end it "returns nil when the second String is US-ASCII" do @@ -91,14 +91,14 @@ describe "Encoding.compatible? String, String" do end it "returns the Encoding when the second's Encoding is not ASCII compatible but the same as the first's Encoding" do - encoding = Encoding.compatible?(@str, "def".force_encoding("utf-7")) + encoding = Encoding.compatible?(@str, "def".dup.force_encoding("utf-7")) encoding.should == Encoding::UTF_7 end end describe "when the first's Encoding is invalid" do before :each do - @str = "\xff".force_encoding Encoding::UTF_8 + @str = "\xff".dup.force_encoding Encoding::UTF_8 end it "returns the first's Encoding when the second's Encoding is US-ASCII" do @@ -114,11 +114,11 @@ describe "Encoding.compatible? String, String" do end it "returns nil when the second's Encoding is invalid and ASCII only" do - Encoding.compatible?(@str, "\x7f".force_encoding("utf-16be")).should be_nil + Encoding.compatible?(@str, "\x7f\x7f".dup.force_encoding("utf-16be")).should be_nil end it "returns nil when the second's Encoding is invalid and not ASCII only" do - Encoding.compatible?(@str, "\xff".force_encoding("utf-16be")).should be_nil + Encoding.compatible?(@str, "\xff\xff".dup.force_encoding("utf-16be")).should be_nil end it "returns the Encoding when the second's Encoding is invalid but the same as the first" do @@ -129,7 +129,7 @@ describe "Encoding.compatible? String, String" do describe "when the first String is empty and the second is not" do describe "and the first's Encoding is ASCII compatible" do before :each do - @str = "".force_encoding("utf-8") + @str = "".dup.force_encoding("utf-8") end it "returns the first's encoding when the second String is ASCII only" do @@ -143,7 +143,7 @@ describe "Encoding.compatible? String, String" do describe "when the first's Encoding is not ASCII compatible" do before :each do - @str = "".force_encoding Encoding::UTF_7 + @str = "".dup.force_encoding Encoding::UTF_7 end it "returns the second string's encoding" do @@ -154,18 +154,391 @@ describe "Encoding.compatible? String, String" do describe "when the second String is empty" do before :each do - @str = "abc".force_encoding("utf-7") + @str = "abc".dup.force_encoding("utf-7") end it "returns the first Encoding" do Encoding.compatible?(@str, "").should == Encoding::UTF_7 end end + + # Encoding negotiation depends on whether encodings are ASCII-compatible, empty + # and contain only ASCII characters (that take 7 bits). Check US-ASCII, UTF-8 and + # BINARY encodings (as most common) as well as an ASCII-compatible, a non-ASCII-compatible and a dummy + # encodings in all possible combinations. + describe "compatibility matrix" do + +# Use the following script to regenerate the matrix: +# +# ``` +# # encoding: binary +# +# ENCODINGS = [ +# "US-ASCII", +# "UTF-8", +# "ASCII-8BIT", +# "ISO-8859-1", # ASCII-compatible +# "UTF-16BE", # non-ASCII-compatible +# "ISO-2022-JP" # dummy +# ] +# +# TYPES = [:empty, :"7bits", :non7bits] +# +# VALUES = { +# empty: "", +# :"7bits" => "\x01\x01", +# non7bits: "\x01\x81" +# } +# +# ENCODINGS.product(TYPES, ENCODINGS, TYPES).each do |encoding1, type1, encoding2, type2| +# value1 = VALUES[type1].dup.force_encoding(encoding1) +# value2 = VALUES[type2].dup.force_encoding(encoding2) +# +# result_encoding = Encoding.compatible?(value1, value2) +# +# puts "[#{encoding1.inspect}, #{value1.inspect}, #{encoding2.inspect}, #{value2.inspect}, #{result_encoding&.name.inspect}]," +# end +# ``` + + matrix = [ + ["US-ASCII", "", "US-ASCII", "", "US-ASCII"], + ["US-ASCII", "", "US-ASCII", "\x01\x01", "US-ASCII"], + ["US-ASCII", "", "US-ASCII", "\x01\x81", "US-ASCII"], + ["US-ASCII", "", "UTF-8", "", "US-ASCII"], + ["US-ASCII", "", "UTF-8", "\u0001\u0001", "US-ASCII"], + ["US-ASCII", "", "UTF-8", "\u0001\x81", "UTF-8"], + ["US-ASCII", "", "ASCII-8BIT", "", "US-ASCII"], + ["US-ASCII", "", "ASCII-8BIT", "\x01\x01", "US-ASCII"], + ["US-ASCII", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["US-ASCII", "", "ISO-8859-1", "", "US-ASCII"], + ["US-ASCII", "", "ISO-8859-1", "\x01\x01", "US-ASCII"], + ["US-ASCII", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["US-ASCII", "", "UTF-16BE", "", "US-ASCII"], + ["US-ASCII", "", "UTF-16BE", "\u0101", "UTF-16BE"], + ["US-ASCII", "", "UTF-16BE", "\u0181", "UTF-16BE"], + ["US-ASCII", "", "ISO-2022-JP", "", "US-ASCII"], + ["US-ASCII", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"], + ["US-ASCII", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"], + ["US-ASCII", "\x01\x01", "US-ASCII", "", "US-ASCII"], + ["US-ASCII", "\x01\x01", "US-ASCII", "\x01\x01", "US-ASCII"], + ["US-ASCII", "\x01\x01", "US-ASCII", "\x01\x81", "US-ASCII"], + ["US-ASCII", "\x01\x01", "UTF-8", "", "US-ASCII"], + ["US-ASCII", "\x01\x01", "UTF-8", "\u0001\u0001", "US-ASCII"], + ["US-ASCII", "\x01\x01", "UTF-8", "\u0001\x81", "UTF-8"], + ["US-ASCII", "\x01\x01", "ASCII-8BIT", "", "US-ASCII"], + ["US-ASCII", "\x01\x01", "ASCII-8BIT", "\x01\x01", "US-ASCII"], + ["US-ASCII", "\x01\x01", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["US-ASCII", "\x01\x01", "ISO-8859-1", "", "US-ASCII"], + ["US-ASCII", "\x01\x01", "ISO-8859-1", "\x01\x01", "US-ASCII"], + ["US-ASCII", "\x01\x01", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["US-ASCII", "\x01\x01", "UTF-16BE", "", "US-ASCII"], + ["US-ASCII", "\x01\x01", "UTF-16BE", "\u0101", nil], + ["US-ASCII", "\x01\x01", "UTF-16BE", "\u0181", nil], + ["US-ASCII", "\x01\x01", "ISO-2022-JP", "", "US-ASCII"], + ["US-ASCII", "\x01\x01", "ISO-2022-JP", "\x01\x01", nil], + ["US-ASCII", "\x01\x01", "ISO-2022-JP", "\x01\x81", nil], + ["US-ASCII", "\x01\x81", "US-ASCII", "", "US-ASCII"], + ["US-ASCII", "\x01\x81", "US-ASCII", "\x01\x01", "US-ASCII"], + ["US-ASCII", "\x01\x81", "US-ASCII", "\x01\x81", "US-ASCII"], + ["US-ASCII", "\x01\x81", "UTF-8", "", "US-ASCII"], + ["US-ASCII", "\x01\x81", "UTF-8", "\u0001\u0001", "US-ASCII"], + ["US-ASCII", "\x01\x81", "UTF-8", "\u0001\x81", nil], + ["US-ASCII", "\x01\x81", "ASCII-8BIT", "", "US-ASCII"], + ["US-ASCII", "\x01\x81", "ASCII-8BIT", "\x01\x01", "US-ASCII"], + ["US-ASCII", "\x01\x81", "ASCII-8BIT", "\x01\x81", nil], + ["US-ASCII", "\x01\x81", "ISO-8859-1", "", "US-ASCII"], + ["US-ASCII", "\x01\x81", "ISO-8859-1", "\x01\x01", "US-ASCII"], + ["US-ASCII", "\x01\x81", "ISO-8859-1", "\x01\x81", nil], + ["US-ASCII", "\x01\x81", "UTF-16BE", "", "US-ASCII"], + ["US-ASCII", "\x01\x81", "UTF-16BE", "\u0101", nil], + ["US-ASCII", "\x01\x81", "UTF-16BE", "\u0181", nil], + ["US-ASCII", "\x01\x81", "ISO-2022-JP", "", "US-ASCII"], + ["US-ASCII", "\x01\x81", "ISO-2022-JP", "\x01\x01", nil], + ["US-ASCII", "\x01\x81", "ISO-2022-JP", "\x01\x81", nil], + ["UTF-8", "", "US-ASCII", "", "UTF-8"], + ["UTF-8", "", "US-ASCII", "\x01\x01", "UTF-8"], + ["UTF-8", "", "US-ASCII", "\x01\x81", "US-ASCII"], + ["UTF-8", "", "UTF-8", "", "UTF-8"], + ["UTF-8", "", "UTF-8", "\u0001\u0001", "UTF-8"], + ["UTF-8", "", "UTF-8", "\u0001\x81", "UTF-8"], + ["UTF-8", "", "ASCII-8BIT", "", "UTF-8"], + ["UTF-8", "", "ASCII-8BIT", "\x01\x01", "UTF-8"], + ["UTF-8", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["UTF-8", "", "ISO-8859-1", "", "UTF-8"], + ["UTF-8", "", "ISO-8859-1", "\x01\x01", "UTF-8"], + ["UTF-8", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["UTF-8", "", "UTF-16BE", "", "UTF-8"], + ["UTF-8", "", "UTF-16BE", "\u0101", "UTF-16BE"], + ["UTF-8", "", "UTF-16BE", "\u0181", "UTF-16BE"], + ["UTF-8", "", "ISO-2022-JP", "", "UTF-8"], + ["UTF-8", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"], + ["UTF-8", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"], + ["UTF-8", "\u0001\u0001", "US-ASCII", "", "UTF-8"], + ["UTF-8", "\u0001\u0001", "US-ASCII", "\x01\x01", "UTF-8"], + ["UTF-8", "\u0001\u0001", "US-ASCII", "\x01\x81", "US-ASCII"], + ["UTF-8", "\u0001\u0001", "UTF-8", "", "UTF-8"], + ["UTF-8", "\u0001\u0001", "UTF-8", "\u0001\u0001", "UTF-8"], + ["UTF-8", "\u0001\u0001", "UTF-8", "\u0001\x81", "UTF-8"], + ["UTF-8", "\u0001\u0001", "ASCII-8BIT", "", "UTF-8"], + ["UTF-8", "\u0001\u0001", "ASCII-8BIT", "\x01\x01", "UTF-8"], + ["UTF-8", "\u0001\u0001", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["UTF-8", "\u0001\u0001", "ISO-8859-1", "", "UTF-8"], + ["UTF-8", "\u0001\u0001", "ISO-8859-1", "\x01\x01", "UTF-8"], + ["UTF-8", "\u0001\u0001", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["UTF-8", "\u0001\u0001", "UTF-16BE", "", "UTF-8"], + ["UTF-8", "\u0001\u0001", "UTF-16BE", "\u0101", nil], + ["UTF-8", "\u0001\u0001", "UTF-16BE", "\u0181", nil], + ["UTF-8", "\u0001\u0001", "ISO-2022-JP", "", "UTF-8"], + ["UTF-8", "\u0001\u0001", "ISO-2022-JP", "\x01\x01", nil], + ["UTF-8", "\u0001\u0001", "ISO-2022-JP", "\x01\x81", nil], + ["UTF-8", "\u0001\x81", "US-ASCII", "", "UTF-8"], + ["UTF-8", "\u0001\x81", "US-ASCII", "\x01\x01", "UTF-8"], + ["UTF-8", "\u0001\x81", "US-ASCII", "\x01\x81", nil], + ["UTF-8", "\u0001\x81", "UTF-8", "", "UTF-8"], + ["UTF-8", "\u0001\x81", "UTF-8", "\u0001\u0001", "UTF-8"], + ["UTF-8", "\u0001\x81", "UTF-8", "\u0001\x81", "UTF-8"], + ["UTF-8", "\u0001\x81", "ASCII-8BIT", "", "UTF-8"], + ["UTF-8", "\u0001\x81", "ASCII-8BIT", "\x01\x01", "UTF-8"], + ["UTF-8", "\u0001\x81", "ASCII-8BIT", "\x01\x81", nil], + ["UTF-8", "\u0001\x81", "ISO-8859-1", "", "UTF-8"], + ["UTF-8", "\u0001\x81", "ISO-8859-1", "\x01\x01", "UTF-8"], + ["UTF-8", "\u0001\x81", "ISO-8859-1", "\x01\x81", nil], + ["UTF-8", "\u0001\x81", "UTF-16BE", "", "UTF-8"], + ["UTF-8", "\u0001\x81", "UTF-16BE", "\u0101", nil], + ["UTF-8", "\u0001\x81", "UTF-16BE", "\u0181", nil], + ["UTF-8", "\u0001\x81", "ISO-2022-JP", "", "UTF-8"], + ["UTF-8", "\u0001\x81", "ISO-2022-JP", "\x01\x01", nil], + ["UTF-8", "\u0001\x81", "ISO-2022-JP", "\x01\x81", nil], + ["ASCII-8BIT", "", "US-ASCII", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "US-ASCII", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "", "US-ASCII", "\x01\x81", "US-ASCII"], + ["ASCII-8BIT", "", "UTF-8", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "UTF-8", "\u0001\u0001", "ASCII-8BIT"], + ["ASCII-8BIT", "", "UTF-8", "\u0001\x81", "UTF-8"], + ["ASCII-8BIT", "", "ASCII-8BIT", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ISO-8859-1", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ISO-8859-1", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["ASCII-8BIT", "", "UTF-16BE", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "UTF-16BE", "\u0101", "UTF-16BE"], + ["ASCII-8BIT", "", "UTF-16BE", "\u0181", "UTF-16BE"], + ["ASCII-8BIT", "", "ISO-2022-JP", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"], + ["ASCII-8BIT", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"], + ["ASCII-8BIT", "\x01\x01", "US-ASCII", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "US-ASCII", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "US-ASCII", "\x01\x81", "US-ASCII"], + ["ASCII-8BIT", "\x01\x01", "UTF-8", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "UTF-8", "\u0001\u0001", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "UTF-8", "\u0001\x81", "UTF-8"], + ["ASCII-8BIT", "\x01\x01", "ASCII-8BIT", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "ISO-8859-1", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "ISO-8859-1", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["ASCII-8BIT", "\x01\x01", "UTF-16BE", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "UTF-16BE", "\u0101", nil], + ["ASCII-8BIT", "\x01\x01", "UTF-16BE", "\u0181", nil], + ["ASCII-8BIT", "\x01\x01", "ISO-2022-JP", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x01", "ISO-2022-JP", "\x01\x01", nil], + ["ASCII-8BIT", "\x01\x01", "ISO-2022-JP", "\x01\x81", nil], + ["ASCII-8BIT", "\x01\x81", "US-ASCII", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "US-ASCII", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "US-ASCII", "\x01\x81", nil], + ["ASCII-8BIT", "\x01\x81", "UTF-8", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "UTF-8", "\u0001\u0001", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "UTF-8", "\u0001\x81", nil], + ["ASCII-8BIT", "\x01\x81", "ASCII-8BIT", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "ISO-8859-1", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "ISO-8859-1", "\x01\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "ISO-8859-1", "\x01\x81", nil], + ["ASCII-8BIT", "\x01\x81", "UTF-16BE", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "UTF-16BE", "\u0101", nil], + ["ASCII-8BIT", "\x01\x81", "UTF-16BE", "\u0181", nil], + ["ASCII-8BIT", "\x01\x81", "ISO-2022-JP", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01\x81", "ISO-2022-JP", "\x01\x01", nil], + ["ASCII-8BIT", "\x01\x81", "ISO-2022-JP", "\x01\x81", nil], + ["ISO-8859-1", "", "US-ASCII", "", "ISO-8859-1"], + ["ISO-8859-1", "", "US-ASCII", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "", "US-ASCII", "\x01\x81", "US-ASCII"], + ["ISO-8859-1", "", "UTF-8", "", "ISO-8859-1"], + ["ISO-8859-1", "", "UTF-8", "\u0001\u0001", "ISO-8859-1"], + ["ISO-8859-1", "", "UTF-8", "\u0001\x81", "UTF-8"], + ["ISO-8859-1", "", "ASCII-8BIT", "", "ISO-8859-1"], + ["ISO-8859-1", "", "ASCII-8BIT", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["ISO-8859-1", "", "ISO-8859-1", "", "ISO-8859-1"], + ["ISO-8859-1", "", "ISO-8859-1", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["ISO-8859-1", "", "UTF-16BE", "", "ISO-8859-1"], + ["ISO-8859-1", "", "UTF-16BE", "\u0101", "UTF-16BE"], + ["ISO-8859-1", "", "UTF-16BE", "\u0181", "UTF-16BE"], + ["ISO-8859-1", "", "ISO-2022-JP", "", "ISO-8859-1"], + ["ISO-8859-1", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"], + ["ISO-8859-1", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"], + ["ISO-8859-1", "\x01\x01", "US-ASCII", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "US-ASCII", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "US-ASCII", "\x01\x81", "US-ASCII"], + ["ISO-8859-1", "\x01\x01", "UTF-8", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "UTF-8", "\u0001\u0001", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "UTF-8", "\u0001\x81", "UTF-8"], + ["ISO-8859-1", "\x01\x01", "ASCII-8BIT", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "ASCII-8BIT", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["ISO-8859-1", "\x01\x01", "ISO-8859-1", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "ISO-8859-1", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "UTF-16BE", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "UTF-16BE", "\u0101", nil], + ["ISO-8859-1", "\x01\x01", "UTF-16BE", "\u0181", nil], + ["ISO-8859-1", "\x01\x01", "ISO-2022-JP", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x01", "ISO-2022-JP", "\x01\x01", nil], + ["ISO-8859-1", "\x01\x01", "ISO-2022-JP", "\x01\x81", nil], + ["ISO-8859-1", "\x01\x81", "US-ASCII", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "US-ASCII", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "US-ASCII", "\x01\x81", nil], + ["ISO-8859-1", "\x01\x81", "UTF-8", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "UTF-8", "\u0001\u0001", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "UTF-8", "\u0001\x81", nil], + ["ISO-8859-1", "\x01\x81", "ASCII-8BIT", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "ASCII-8BIT", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "ASCII-8BIT", "\x01\x81", nil], + ["ISO-8859-1", "\x01\x81", "ISO-8859-1", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "ISO-8859-1", "\x01\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "UTF-16BE", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "UTF-16BE", "\u0101", nil], + ["ISO-8859-1", "\x01\x81", "UTF-16BE", "\u0181", nil], + ["ISO-8859-1", "\x01\x81", "ISO-2022-JP", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01\x81", "ISO-2022-JP", "\x01\x01", nil], + ["ISO-8859-1", "\x01\x81", "ISO-2022-JP", "\x01\x81", nil], + ["UTF-16BE", "", "US-ASCII", "", "UTF-16BE"], + ["UTF-16BE", "", "US-ASCII", "\x01\x01", "US-ASCII"], + ["UTF-16BE", "", "US-ASCII", "\x01\x81", "US-ASCII"], + ["UTF-16BE", "", "UTF-8", "", "UTF-16BE"], + ["UTF-16BE", "", "UTF-8", "\u0001\u0001", "UTF-8"], + ["UTF-16BE", "", "UTF-8", "\u0001\x81", "UTF-8"], + ["UTF-16BE", "", "ASCII-8BIT", "", "UTF-16BE"], + ["UTF-16BE", "", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"], + ["UTF-16BE", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["UTF-16BE", "", "ISO-8859-1", "", "UTF-16BE"], + ["UTF-16BE", "", "ISO-8859-1", "\x01\x01", "ISO-8859-1"], + ["UTF-16BE", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["UTF-16BE", "", "UTF-16BE", "", "UTF-16BE"], + ["UTF-16BE", "", "UTF-16BE", "\u0101", "UTF-16BE"], + ["UTF-16BE", "", "UTF-16BE", "\u0181", "UTF-16BE"], + ["UTF-16BE", "", "ISO-2022-JP", "", "UTF-16BE"], + ["UTF-16BE", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"], + ["UTF-16BE", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"], + ["UTF-16BE", "\u0101", "US-ASCII", "", "UTF-16BE"], + ["UTF-16BE", "\u0101", "US-ASCII", "\x01\x01", nil], + ["UTF-16BE", "\u0101", "US-ASCII", "\x01\x81", nil], + ["UTF-16BE", "\u0101", "UTF-8", "", "UTF-16BE"], + ["UTF-16BE", "\u0101", "UTF-8", "\u0001\u0001", nil], + ["UTF-16BE", "\u0101", "UTF-8", "\u0001\x81", nil], + ["UTF-16BE", "\u0101", "ASCII-8BIT", "", "UTF-16BE"], + ["UTF-16BE", "\u0101", "ASCII-8BIT", "\x01\x01", nil], + ["UTF-16BE", "\u0101", "ASCII-8BIT", "\x01\x81", nil], + ["UTF-16BE", "\u0101", "ISO-8859-1", "", "UTF-16BE"], + ["UTF-16BE", "\u0101", "ISO-8859-1", "\x01\x01", nil], + ["UTF-16BE", "\u0101", "ISO-8859-1", "\x01\x81", nil], + ["UTF-16BE", "\u0101", "UTF-16BE", "", "UTF-16BE"], + ["UTF-16BE", "\u0101", "UTF-16BE", "\u0101", "UTF-16BE"], + ["UTF-16BE", "\u0101", "UTF-16BE", "\u0181", "UTF-16BE"], + ["UTF-16BE", "\u0101", "ISO-2022-JP", "", "UTF-16BE"], + ["UTF-16BE", "\u0101", "ISO-2022-JP", "\x01\x01", nil], + ["UTF-16BE", "\u0101", "ISO-2022-JP", "\x01\x81", nil], + ["UTF-16BE", "\u0181", "US-ASCII", "", "UTF-16BE"], + ["UTF-16BE", "\u0181", "US-ASCII", "\x01\x01", nil], + ["UTF-16BE", "\u0181", "US-ASCII", "\x01\x81", nil], + ["UTF-16BE", "\u0181", "UTF-8", "", "UTF-16BE"], + ["UTF-16BE", "\u0181", "UTF-8", "\u0001\u0001", nil], + ["UTF-16BE", "\u0181", "UTF-8", "\u0001\x81", nil], + ["UTF-16BE", "\u0181", "ASCII-8BIT", "", "UTF-16BE"], + ["UTF-16BE", "\u0181", "ASCII-8BIT", "\x01\x01", nil], + ["UTF-16BE", "\u0181", "ASCII-8BIT", "\x01\x81", nil], + ["UTF-16BE", "\u0181", "ISO-8859-1", "", "UTF-16BE"], + ["UTF-16BE", "\u0181", "ISO-8859-1", "\x01\x01", nil], + ["UTF-16BE", "\u0181", "ISO-8859-1", "\x01\x81", nil], + ["UTF-16BE", "\u0181", "UTF-16BE", "", "UTF-16BE"], + ["UTF-16BE", "\u0181", "UTF-16BE", "\u0101", "UTF-16BE"], + ["UTF-16BE", "\u0181", "UTF-16BE", "\u0181", "UTF-16BE"], + ["UTF-16BE", "\u0181", "ISO-2022-JP", "", "UTF-16BE"], + ["UTF-16BE", "\u0181", "ISO-2022-JP", "\x01\x01", nil], + ["UTF-16BE", "\u0181", "ISO-2022-JP", "\x01\x81", nil], + ["ISO-2022-JP", "", "US-ASCII", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "US-ASCII", "\x01\x01", "US-ASCII"], + ["ISO-2022-JP", "", "US-ASCII", "\x01\x81", "US-ASCII"], + ["ISO-2022-JP", "", "UTF-8", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "UTF-8", "\u0001\u0001", "UTF-8"], + ["ISO-2022-JP", "", "UTF-8", "\u0001\x81", "UTF-8"], + ["ISO-2022-JP", "", "ASCII-8BIT", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"], + ["ISO-2022-JP", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"], + ["ISO-2022-JP", "", "ISO-8859-1", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "ISO-8859-1", "\x01\x01", "ISO-8859-1"], + ["ISO-2022-JP", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"], + ["ISO-2022-JP", "", "UTF-16BE", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "UTF-16BE", "\u0101", "UTF-16BE"], + ["ISO-2022-JP", "", "UTF-16BE", "\u0181", "UTF-16BE"], + ["ISO-2022-JP", "", "ISO-2022-JP", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"], + ["ISO-2022-JP", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x01", "US-ASCII", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x01", "US-ASCII", "\x01\x01", nil], + ["ISO-2022-JP", "\x01\x01", "US-ASCII", "\x01\x81", nil], + ["ISO-2022-JP", "\x01\x01", "UTF-8", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x01", "UTF-8", "\u0001\u0001", nil], + ["ISO-2022-JP", "\x01\x01", "UTF-8", "\u0001\x81", nil], + ["ISO-2022-JP", "\x01\x01", "ASCII-8BIT", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x01", "ASCII-8BIT", "\x01\x01", nil], + ["ISO-2022-JP", "\x01\x01", "ASCII-8BIT", "\x01\x81", nil], + ["ISO-2022-JP", "\x01\x01", "ISO-8859-1", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x01", "ISO-8859-1", "\x01\x01", nil], + ["ISO-2022-JP", "\x01\x01", "ISO-8859-1", "\x01\x81", nil], + ["ISO-2022-JP", "\x01\x01", "UTF-16BE", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x01", "UTF-16BE", "\u0101", nil], + ["ISO-2022-JP", "\x01\x01", "UTF-16BE", "\u0181", nil], + ["ISO-2022-JP", "\x01\x01", "ISO-2022-JP", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x01", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x01", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x81", "US-ASCII", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x81", "US-ASCII", "\x01\x01", nil], + ["ISO-2022-JP", "\x01\x81", "US-ASCII", "\x01\x81", nil], + ["ISO-2022-JP", "\x01\x81", "UTF-8", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x81", "UTF-8", "\u0001\u0001", nil], + ["ISO-2022-JP", "\x01\x81", "UTF-8", "\u0001\x81", nil], + ["ISO-2022-JP", "\x01\x81", "ASCII-8BIT", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x81", "ASCII-8BIT", "\x01\x01", nil], + ["ISO-2022-JP", "\x01\x81", "ASCII-8BIT", "\x01\x81", nil], + ["ISO-2022-JP", "\x01\x81", "ISO-8859-1", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x81", "ISO-8859-1", "\x01\x01", nil], + ["ISO-2022-JP", "\x01\x81", "ISO-8859-1", "\x01\x81", nil], + ["ISO-2022-JP", "\x01\x81", "UTF-16BE", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x81", "UTF-16BE", "\u0101", nil], + ["ISO-2022-JP", "\x01\x81", "UTF-16BE", "\u0181", nil], + ["ISO-2022-JP", "\x01\x81", "ISO-2022-JP", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x81", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01\x81", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"], + ] + + matrix.each do |encoding1, value1, encoding2, value2, compatible_encoding| + it "returns #{compatible_encoding} for #{value1.inspect} in #{encoding1} and #{value2.inspect} in #{encoding2}" do + actual_encoding = Encoding.compatible?(value1.dup.force_encoding(encoding1), value2.dup.force_encoding(encoding2)) + actual_encoding&.name.should == compatible_encoding + end + end + end end describe "Encoding.compatible? String, Regexp" do it "returns US-ASCII if both are US-ASCII" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Encoding.compatible?(str, /abc/).should == Encoding::US_ASCII end @@ -180,15 +553,15 @@ describe "Encoding.compatible? String, Regexp" do it "returns the String's Encoding if the String is not ASCII only" do [ [Encoding, "\xff", Encoding::BINARY], [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8], - [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP], - [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS], + [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP], + [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, /abc/) end end describe "Encoding.compatible? String, Symbol" do it "returns US-ASCII if both are ASCII only" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Encoding.compatible?(str, :abc).should == Encoding::US_ASCII end @@ -203,8 +576,8 @@ describe "Encoding.compatible? String, Symbol" do it "returns the String's Encoding if the String is not ASCII only" do [ [Encoding, "\xff", Encoding::BINARY], [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8], - [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP], - [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS], + [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP], + [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, :abc) end end @@ -221,8 +594,8 @@ describe "Encoding.compatible? String, Encoding" do it "returns the String's encoding if the Encoding is US-ASCII" do [ [Encoding, "\xff", Encoding::BINARY], [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8], - [Encoding, "\xa4\xa2".force_encoding("euc-jp"), Encoding::EUC_JP], - [Encoding, "\x82\xa0".force_encoding("shift_jis"), Encoding::Shift_JIS], + [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP], + [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, Encoding::US_ASCII) end @@ -242,7 +615,7 @@ end describe "Encoding.compatible? Regexp, String" do it "returns US-ASCII if both are US-ASCII" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Encoding.compatible?(/abc/, str).should == Encoding::US_ASCII end @@ -256,8 +629,8 @@ describe "Encoding.compatible? Regexp, Regexp" do it "returns the first's Encoding if it is not US-ASCII and not ASCII only" do [ [Encoding, Regexp.new("\xff"), Encoding::BINARY], [Encoding, Regexp.new("\u3042".encode("utf-8")), Encoding::UTF_8], - [Encoding, Regexp.new("\xa4\xa2".force_encoding("euc-jp")), Encoding::EUC_JP], - [Encoding, Regexp.new("\x82\xa0".force_encoding("shift_jis")), Encoding::Shift_JIS], + [Encoding, Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp")), Encoding::EUC_JP], + [Encoding, Regexp.new("\x82\xa0".dup.force_encoding("shift_jis")), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, /abc/) end end @@ -270,15 +643,15 @@ describe "Encoding.compatible? Regexp, Symbol" do it "returns the first's Encoding if it is not US-ASCII and not ASCII only" do [ [Encoding, Regexp.new("\xff"), Encoding::BINARY], [Encoding, Regexp.new("\u3042".encode("utf-8")), Encoding::UTF_8], - [Encoding, Regexp.new("\xa4\xa2".force_encoding("euc-jp")), Encoding::EUC_JP], - [Encoding, Regexp.new("\x82\xa0".force_encoding("shift_jis")), Encoding::Shift_JIS], + [Encoding, Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp")), Encoding::EUC_JP], + [Encoding, Regexp.new("\x82\xa0".dup.force_encoding("shift_jis")), Encoding::Shift_JIS], ].should be_computed_by(:compatible?, /abc/) end end describe "Encoding.compatible? Symbol, String" do it "returns US-ASCII if both are ASCII only" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Encoding.compatible?(str, :abc).should == Encoding::US_ASCII end end @@ -291,8 +664,8 @@ describe "Encoding.compatible? Symbol, Regexp" do it "returns the Regexp's Encoding if it is not US-ASCII and not ASCII only" do a = Regexp.new("\xff") b = Regexp.new("\u3042".encode("utf-8")) - c = Regexp.new("\xa4\xa2".force_encoding("euc-jp")) - d = Regexp.new("\x82\xa0".force_encoding("shift_jis")) + c = Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp")) + d = Regexp.new("\x82\xa0".dup.force_encoding("shift_jis")) [ [Encoding, :abc, a, Encoding::BINARY], [Encoding, :abc, b, Encoding::UTF_8], @@ -310,8 +683,8 @@ describe "Encoding.compatible? Symbol, Symbol" do it "returns the first's Encoding if it is not ASCII only" do [ [Encoding, "\xff".to_sym, Encoding::BINARY], [Encoding, "\u3042".encode("utf-8").to_sym, Encoding::UTF_8], - [Encoding, "\xa4\xa2".force_encoding("euc-jp").to_sym, Encoding::EUC_JP], - [Encoding, "\x82\xa0".force_encoding("shift_jis").to_sym, Encoding::Shift_JIS], + [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp").to_sym, Encoding::EUC_JP], + [Encoding, "\x82\xa0".dup.force_encoding("shift_jis").to_sym, Encoding::Shift_JIS], ].should be_computed_by(:compatible?, :abc) end end @@ -377,3 +750,9 @@ describe "Encoding.compatible? Object, Object" do Encoding.compatible?(:sym, Object.new).should be_nil end end + +describe "Encoding.compatible? nil, nil" do + it "returns nil" do + Encoding.compatible?(nil, nil).should be_nil + end +end diff --git a/spec/ruby/core/encoding/converter/convert_spec.rb b/spec/ruby/core/encoding/converter/convert_spec.rb index 95a9e0b758..8533af4565 100644 --- a/spec/ruby/core/encoding/converter/convert_spec.rb +++ b/spec/ruby/core/encoding/converter/convert_spec.rb @@ -1,4 +1,5 @@ -# -*- encoding: binary -*- +# encoding: binary +# frozen_string_literal: true require_relative '../../../spec_helper' describe "Encoding::Converter#convert" do @@ -9,31 +10,31 @@ describe "Encoding::Converter#convert" do it "sets the encoding of the result to the target encoding" do ec = Encoding::Converter.new('ascii', 'utf-8') - str = 'glark'.force_encoding('ascii') + str = 'glark'.dup.force_encoding('ascii') ec.convert(str).encoding.should == Encoding::UTF_8 end it "transcodes the given String to the target encoding" do ec = Encoding::Converter.new("utf-8", "euc-jp") - ec.convert("\u3042".force_encoding('UTF-8')).should == \ - "\xA4\xA2".force_encoding('EUC-JP') + ec.convert("\u3042".dup.force_encoding('UTF-8')).should == \ + "\xA4\xA2".dup.force_encoding('EUC-JP') end it "allows Strings of different encodings to the source encoding" do ec = Encoding::Converter.new('ascii', 'utf-8') - str = 'glark'.force_encoding('SJIS') + str = 'glark'.dup.force_encoding('SJIS') ec.convert(str).encoding.should == Encoding::UTF_8 end it "reuses the given encoding pair if called multiple times" do ec = Encoding::Converter.new('ascii', 'SJIS') - ec.convert('a'.force_encoding('ASCII')).should == 'a'.force_encoding('SJIS') - ec.convert('b'.force_encoding('ASCII')).should == 'b'.force_encoding('SJIS') + ec.convert('a'.dup.force_encoding('ASCII')).should == 'a'.dup.force_encoding('SJIS') + ec.convert('b'.dup.force_encoding('ASCII')).should == 'b'.dup.force_encoding('SJIS') end it "raises UndefinedConversionError if the String contains characters invalid for the target encoding" do ec = Encoding::Converter.new('UTF-8', Encoding.find('macCyrillic')) - -> { ec.convert("\u{6543}".force_encoding('UTF-8')) }.should \ + -> { ec.convert("\u{6543}".dup.force_encoding('UTF-8')) }.should \ raise_error(Encoding::UndefinedConversionError) end diff --git a/spec/ruby/core/encoding/converter/finish_spec.rb b/spec/ruby/core/encoding/converter/finish_spec.rb index 11ca7e8510..22e66df38c 100644 --- a/spec/ruby/core/encoding/converter/finish_spec.rb +++ b/spec/ruby/core/encoding/converter/finish_spec.rb @@ -16,8 +16,8 @@ describe "Encoding::Converter#finish" do end it "returns the last part of the converted String if it hasn't already" do - @ec.convert("\u{9999}").should == "\e$B9a".force_encoding('iso-2022-jp') - @ec.finish.should == "\e(B".force_encoding('iso-2022-jp') + @ec.convert("\u{9999}").should == "\e$B9a".dup.force_encoding('iso-2022-jp') + @ec.finish.should == "\e(B".dup.force_encoding('iso-2022-jp') end it "returns a String in the destination encoding" do diff --git a/spec/ruby/core/encoding/converter/last_error_spec.rb b/spec/ruby/core/encoding/converter/last_error_spec.rb index 68567737b7..ff2a2b4cbe 100644 --- a/spec/ruby/core/encoding/converter/last_error_spec.rb +++ b/spec/ruby/core/encoding/converter/last_error_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::Converter#last_error" do @@ -9,45 +9,45 @@ describe "Encoding::Converter#last_error" do it "returns nil when the last conversion did not produce an error" do ec = Encoding::Converter.new('ascii','utf-8') - ec.convert('a'.force_encoding('ascii')) + ec.convert('a'.dup.force_encoding('ascii')) ec.last_error.should be_nil end it "returns nil when #primitive_convert last returned :destination_buffer_full" do ec = Encoding::Converter.new("utf-8", "iso-2022-jp") - ec.primitive_convert("\u{9999}", "", 0, 0, partial_input: false) \ + ec.primitive_convert(+"\u{9999}", +"", 0, 0, partial_input: false) \ .should == :destination_buffer_full ec.last_error.should be_nil end it "returns nil when #primitive_convert last returned :finished" do ec = Encoding::Converter.new("utf-8", "iso-8859-1") - ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished + ec.primitive_convert("glark".dup.force_encoding('utf-8'), +"").should == :finished ec.last_error.should be_nil end it "returns nil if the last conversion succeeded but the penultimate failed" do ec = Encoding::Converter.new("utf-8", "iso-8859-1") - ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence - ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished + ec.primitive_convert(+"\xf1abcd", +"").should == :invalid_byte_sequence + ec.primitive_convert("glark".dup.force_encoding('utf-8'), +"").should == :finished ec.last_error.should be_nil end it "returns an Encoding::InvalidByteSequenceError when #primitive_convert last returned :invalid_byte_sequence" do ec = Encoding::Converter.new("utf-8", "iso-8859-1") - ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence + ec.primitive_convert(+"\xf1abcd", +"").should == :invalid_byte_sequence ec.last_error.should be_an_instance_of(Encoding::InvalidByteSequenceError) end it "returns an Encoding::UndefinedConversionError when #primitive_convert last returned :undefined_conversion" do ec = Encoding::Converter.new("utf-8", "iso-8859-1") - ec.primitive_convert("\u{9876}","").should == :undefined_conversion + ec.primitive_convert(+"\u{9876}", +"").should == :undefined_conversion ec.last_error.should be_an_instance_of(Encoding::UndefinedConversionError) end it "returns an Encoding::InvalidByteSequenceError when #primitive_convert last returned :incomplete_input" do ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - ec.primitive_convert("\xa4", "", nil, 10).should == :incomplete_input + ec.primitive_convert(+"\xa4", +"", nil, 10).should == :incomplete_input ec.last_error.should be_an_instance_of(Encoding::InvalidByteSequenceError) end diff --git a/spec/ruby/core/encoding/converter/new_spec.rb b/spec/ruby/core/encoding/converter/new_spec.rb index 1f7affc72b..a7bef53809 100644 --- a/spec/ruby/core/encoding/converter/new_spec.rb +++ b/spec/ruby/core/encoding/converter/new_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::Converter.new" do @@ -107,7 +107,7 @@ describe "Encoding::Converter.new" do it "sets the replacement String to '\\uFFFD'" do conv = Encoding::Converter.new("us-ascii", "utf-8", replace: nil) - conv.replacement.should == "\u{fffd}".force_encoding("utf-8") + conv.replacement.should == "\u{fffd}".dup.force_encoding("utf-8") end it "sets the replacement String encoding to UTF-8" do diff --git a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb index ab34ebf33f..e4aeed103e 100644 --- a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb +++ b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb @@ -1,4 +1,5 @@ -# -*- encoding: binary -*- +# encoding: binary +# frozen_string_literal: false require_relative '../../../spec_helper' describe "Encoding::Converter#primitive_convert" do diff --git a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb index 1f836b259f..5ee8b1fecd 100644 --- a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb +++ b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb @@ -1,4 +1,5 @@ -# -*- encoding: binary -*- +# encoding: binary +# frozen_string_literal: false require_relative '../../../spec_helper' describe "Encoding::Converter#primitive_errinfo" do diff --git a/spec/ruby/core/encoding/converter/putback_spec.rb b/spec/ruby/core/encoding/converter/putback_spec.rb index c4e0a5da21..04bb565655 100644 --- a/spec/ruby/core/encoding/converter/putback_spec.rb +++ b/spec/ruby/core/encoding/converter/putback_spec.rb @@ -1,10 +1,10 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::Converter#putback" do before :each do @ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - @ret = @ec.primitive_convert(@src="abc\xa1def", @dst="", nil, 10) + @ret = @ec.primitive_convert(@src=+"abc\xa1def", @dst=+"", nil, 10) end it "returns a String" do @@ -36,21 +36,21 @@ describe "Encoding::Converter#putback" do it "returns the problematic bytes for UTF-16LE" do ec = Encoding::Converter.new("utf-16le", "iso-8859-1") - src = "\x00\xd8\x61\x00" - dst = "" + src = +"\x00\xd8\x61\x00" + dst = +"" ec.primitive_convert(src, dst).should == :invalid_byte_sequence ec.primitive_errinfo.should == [:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "a\x00"] - ec.putback.should == "a\x00".force_encoding("utf-16le") + ec.putback.should == "a\x00".dup.force_encoding("utf-16le") ec.putback.should == "" end it "accepts an integer argument corresponding to the number of bytes to be put back" do ec = Encoding::Converter.new("utf-16le", "iso-8859-1") - src = "\x00\xd8\x61\x00" - dst = "" + src = +"\x00\xd8\x61\x00" + dst = +"" ec.primitive_convert(src, dst).should == :invalid_byte_sequence ec.primitive_errinfo.should == [:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "a\x00"] - ec.putback(2).should == "a\x00".force_encoding("utf-16le") + ec.putback(2).should == "a\x00".dup.force_encoding("utf-16le") ec.putback.should == "" end end diff --git a/spec/ruby/core/encoding/converter/replacement_spec.rb b/spec/ruby/core/encoding/converter/replacement_spec.rb index 5ca42e7e5a..ea514ca8dd 100644 --- a/spec/ruby/core/encoding/converter/replacement_spec.rb +++ b/spec/ruby/core/encoding/converter/replacement_spec.rb @@ -13,7 +13,7 @@ describe "Encoding::Converter#replacement" do it "returns \\uFFFD when the destination encoding is UTF-8" do ec = Encoding::Converter.new("us-ascii", "utf-8") - ec.replacement.should == "\u{fffd}".force_encoding('utf-8') + ec.replacement.should == "\u{fffd}".dup.force_encoding('utf-8') ec.replacement.encoding.should == Encoding::UTF_8 end end @@ -38,33 +38,33 @@ describe "Encoding::Converter#replacement=" do it "sets #replacement" do ec = Encoding::Converter.new("us-ascii", "utf-8") - ec.replacement.should == "\u{fffd}".force_encoding('utf-8') + ec.replacement.should == "\u{fffd}".dup.force_encoding('utf-8') ec.replacement = '?'.encode('utf-8') - ec.replacement.should == '?'.force_encoding('utf-8') + ec.replacement.should == '?'.dup.force_encoding('utf-8') end it "raises an UndefinedConversionError is the argument cannot be converted into the destination encoding" do ec = Encoding::Converter.new("sjis", "ascii") - utf8_q = "\u{986}".force_encoding('utf-8') - ec.primitive_convert(utf8_q.dup, "").should == :undefined_conversion + utf8_q = "\u{986}".dup.force_encoding('utf-8') + ec.primitive_convert(utf8_q.dup, +"").should == :undefined_conversion -> { ec.replacement = utf8_q }.should \ raise_error(Encoding::UndefinedConversionError) end it "does not change the replacement character if the argument cannot be converted into the destination encoding" do ec = Encoding::Converter.new("sjis", "ascii") - utf8_q = "\u{986}".force_encoding('utf-8') - ec.primitive_convert(utf8_q.dup, "").should == :undefined_conversion + utf8_q = "\u{986}".dup.force_encoding('utf-8') + ec.primitive_convert(utf8_q.dup, +"").should == :undefined_conversion -> { ec.replacement = utf8_q }.should \ raise_error(Encoding::UndefinedConversionError) - ec.replacement.should == "?".force_encoding('us-ascii') + ec.replacement.should == "?".dup.force_encoding('us-ascii') end it "uses the replacement character" do ec = Encoding::Converter.new("utf-8", "us-ascii", :invalid => :replace, :undef => :replace) ec.replacement = "!" - dest = "" - status = ec.primitive_convert "䏿–‡123", dest + dest = +"" + status = ec.primitive_convert(+"䏿–‡123", dest) status.should == :finished dest.should == "!!123" diff --git a/spec/ruby/core/encoding/converter/search_convpath_spec.rb b/spec/ruby/core/encoding/converter/search_convpath_spec.rb index 0882af5539..59fe4520c0 100644 --- a/spec/ruby/core/encoding/converter/search_convpath_spec.rb +++ b/spec/ruby/core/encoding/converter/search_convpath_spec.rb @@ -23,8 +23,8 @@ describe "Encoding::Converter.search_convpath" do end it "raises an Encoding::ConverterNotFoundError if no conversion path exists" do - -> do - Encoding::Converter.search_convpath(Encoding::BINARY, Encoding::Emacs_Mule) - end.should raise_error(Encoding::ConverterNotFoundError) + -> do + Encoding::Converter.search_convpath(Encoding::BINARY, Encoding::Emacs_Mule) + end.should raise_error(Encoding::ConverterNotFoundError) end end diff --git a/spec/ruby/core/encoding/find_spec.rb b/spec/ruby/core/encoding/find_spec.rb index 8a0873070f..9c34fe0e77 100644 --- a/spec/ruby/core/encoding/find_spec.rb +++ b/spec/ruby/core/encoding/find_spec.rb @@ -50,7 +50,7 @@ describe "Encoding.find" do end it "raises an ArgumentError if the given encoding does not exist" do - -> { Encoding.find('dh2dh278d') }.should raise_error(ArgumentError) + -> { Encoding.find('dh2dh278d') }.should raise_error(ArgumentError, 'unknown encoding name - dh2dh278d') end # Not sure how to do a better test, since locale depends on weird platform-specific stuff diff --git a/spec/ruby/core/encoding/fixtures/classes.rb b/spec/ruby/core/encoding/fixtures/classes.rb index 12e9a4f348..943865e8d8 100644 --- a/spec/ruby/core/encoding/fixtures/classes.rb +++ b/spec/ruby/core/encoding/fixtures/classes.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary module EncodingSpecs class UndefinedConversionError def self.exception diff --git a/spec/ruby/core/encoding/inspect_spec.rb b/spec/ruby/core/encoding/inspect_spec.rb index 9a930b2a77..df96141db9 100644 --- a/spec/ruby/core/encoding/inspect_spec.rb +++ b/spec/ruby/core/encoding/inspect_spec.rb @@ -5,9 +5,23 @@ describe "Encoding#inspect" do Encoding::UTF_8.inspect.should be_an_instance_of(String) end - it "returns #<Encoding:name> for a non-dummy encoding named 'name'" do - Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc| - enc.inspect.should =~ /#<Encoding:#{enc.name}>/ + ruby_version_is ""..."3.4" do + it "returns #<Encoding:name> for a non-dummy encoding named 'name'" do + Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc| + enc.inspect.should =~ /#<Encoding:#{enc.name}>/ + end + end + end + + ruby_version_is "3.4" do + it "returns #<Encoding:name> for a non-dummy encoding named 'name'" do + Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc| + if enc.name == "ASCII-8BIT" + enc.inspect.should == "#<Encoding:BINARY (ASCII-8BIT)>" + else + enc.inspect.should =~ /#<Encoding:#{enc.name}>/ + end + end end end diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb index d2fc360dce..8b7e87960f 100644 --- a/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb +++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative "../../../spec_helper" require_relative '../fixtures/classes' diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb index 94201a9b15..83606f77b4 100644 --- a/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb +++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::InvalidByteSequenceError#incomplete_input?" do @@ -8,7 +8,7 @@ describe "Encoding::InvalidByteSequenceError#incomplete_input?" do it "returns true if #primitive_convert returned :incomplete_input for the same data" do ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - ec.primitive_convert("\xA1",'').should == :incomplete_input + ec.primitive_convert(+"\xA1", +'').should == :incomplete_input begin ec.convert("\xA1") rescue Encoding::InvalidByteSequenceError => e @@ -18,7 +18,7 @@ describe "Encoding::InvalidByteSequenceError#incomplete_input?" do it "returns false if #primitive_convert returned :invalid_byte_sequence for the same data" do ec = Encoding::Converter.new("ascii", "utf-8") - ec.primitive_convert("\xfffffffff",'').should == :invalid_byte_sequence + ec.primitive_convert(+"\xfffffffff", +'').should == :invalid_byte_sequence begin ec.convert("\xfffffffff") rescue Encoding::InvalidByteSequenceError => e diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb index 9866310c25..e5ad0a61bd 100644 --- a/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb +++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative "../../../spec_helper" require_relative '../fixtures/classes' @@ -15,11 +15,11 @@ describe "Encoding::InvalidByteSequenceError#readagain_bytes" do it "returns the bytes to be read again" do @exception.readagain_bytes.size.should == 1 - @exception.readagain_bytes.should == "a".force_encoding('binary') + @exception.readagain_bytes.should == "a".dup.force_encoding('binary') @exception.readagain_bytes.should == @errinfo[-1] @exception2.readagain_bytes.size.should == 1 - @exception2.readagain_bytes.should == "\xFF".force_encoding('binary') + @exception2.readagain_bytes.should == "\xFF".dup.force_encoding('binary') @exception2.readagain_bytes.should == @errinfo2[-1] end diff --git a/spec/ruby/core/encoding/replicate_spec.rb b/spec/ruby/core/encoding/replicate_spec.rb index 498d03581a..2da998837f 100644 --- a/spec/ruby/core/encoding/replicate_spec.rb +++ b/spec/ruby/core/encoding/replicate_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' describe "Encoding#replicate" do @@ -18,8 +18,8 @@ describe "Encoding#replicate" do e.name.should == name Encoding.find(name).should == e - "a".force_encoding(e).valid_encoding?.should be_true - "\x80".force_encoding(e).valid_encoding?.should be_false + "a".dup.force_encoding(e).valid_encoding?.should be_true + "\x80".dup.force_encoding(e).valid_encoding?.should be_false end it "returns a replica of UTF-8" do @@ -28,9 +28,9 @@ describe "Encoding#replicate" do e.name.should == name Encoding.find(name).should == e - "a".force_encoding(e).valid_encoding?.should be_true - "\u3042".force_encoding(e).valid_encoding?.should be_true - "\x80".force_encoding(e).valid_encoding?.should be_false + "a".dup.force_encoding(e).valid_encoding?.should be_true + "\u3042".dup.force_encoding(e).valid_encoding?.should be_true + "\x80".dup.force_encoding(e).valid_encoding?.should be_false end it "returns a replica of UTF-16BE" do @@ -39,9 +39,9 @@ describe "Encoding#replicate" do e.name.should == name Encoding.find(name).should == e - "a".force_encoding(e).valid_encoding?.should be_false - "\x30\x42".force_encoding(e).valid_encoding?.should be_true - "\x80".force_encoding(e).valid_encoding?.should be_false + "a".dup.force_encoding(e).valid_encoding?.should be_false + "\x30\x42".dup.force_encoding(e).valid_encoding?.should be_true + "\x80".dup.force_encoding(e).valid_encoding?.should be_false end it "returns a replica of ISO-2022-JP" do @@ -61,18 +61,23 @@ describe "Encoding#replicate" do e.name.should == name Encoding.find(name).should == e - s = "abc".force_encoding(e) + s = "abc".dup.force_encoding(e) s.encoding.should == e s.encoding.name.should == name end end - ruby_version_is "3.2"..."3.3" do + ruby_version_is ""..."3.3" do it "warns about deprecation" do -> { Encoding::US_ASCII.replicate('MY-US-ASCII') }.should complain(/warning: Encoding#replicate is deprecated and will be removed in Ruby 3.3; use the original encoding instead/) end + + it "raises EncodingError if too many encodings" do + code = '1_000.times {|i| Encoding::US_ASCII.replicate("R_#{i}") }' + ruby_exe(code, args: "2>&1", exit_status: 1).should.include?('too many encoding (> 256) (EncodingError)') + end end ruby_version_is "3.3" do diff --git a/spec/ruby/core/enumerable/collect_concat_spec.rb b/spec/ruby/core/enumerable/collect_concat_spec.rb index 6e34c9eb93..59317cfe34 100644 --- a/spec/ruby/core/enumerable/collect_concat_spec.rb +++ b/spec/ruby/core/enumerable/collect_concat_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/collect_concat' describe "Enumerable#collect_concat" do - it_behaves_like :enumerable_collect_concat , :collect_concat + it_behaves_like :enumerable_collect_concat, :collect_concat end diff --git a/spec/ruby/core/enumerable/collect_spec.rb b/spec/ruby/core/enumerable/collect_spec.rb index 1016b67798..cfa2895cce 100644 --- a/spec/ruby/core/enumerable/collect_spec.rb +++ b/spec/ruby/core/enumerable/collect_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/collect' describe "Enumerable#collect" do - it_behaves_like :enumerable_collect , :collect + it_behaves_like :enumerable_collect, :collect end diff --git a/spec/ruby/core/enumerable/compact_spec.rb b/spec/ruby/core/enumerable/compact_spec.rb index 86e95dce08..1895430c4e 100644 --- a/spec/ruby/core/enumerable/compact_spec.rb +++ b/spec/ruby/core/enumerable/compact_spec.rb @@ -1,11 +1,9 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is '3.1' do - describe "Enumerable#compact" do - it 'returns array without nil elements' do - arr = EnumerableSpecs::Numerous.new(nil, 1, 2, nil, true) - arr.compact.should == [1, 2, true] - end +describe "Enumerable#compact" do + it 'returns array without nil elements' do + arr = EnumerableSpecs::Numerous.new(nil, 1, 2, nil, true) + arr.compact.should == [1, 2, true] end end diff --git a/spec/ruby/core/enumerable/detect_spec.rb b/spec/ruby/core/enumerable/detect_spec.rb index e912134fed..6959aadc44 100644 --- a/spec/ruby/core/enumerable/detect_spec.rb +++ b/spec/ruby/core/enumerable/detect_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find' describe "Enumerable#detect" do - it_behaves_like :enumerable_find , :detect + it_behaves_like :enumerable_find, :detect end diff --git a/spec/ruby/core/enumerable/each_cons_spec.rb b/spec/ruby/core/enumerable/each_cons_spec.rb index 8fb31fb925..ed77741862 100644 --- a/spec/ruby/core/enumerable/each_cons_spec.rb +++ b/spec/ruby/core/enumerable/each_cons_spec.rb @@ -56,10 +56,8 @@ describe "Enumerable#each_cons" do multi.each_cons(2).to_a.should == [[[1, 2], [3, 4, 5]], [[3, 4, 5], [6, 7, 8, 9]]] end - ruby_version_is "3.1" do - it "returns self when a block is given" do - @enum.each_cons(3){}.should == @enum - end + it "returns self when a block is given" do + @enum.each_cons(3){}.should == @enum end describe "when no block is given" do diff --git a/spec/ruby/core/enumerable/each_slice_spec.rb b/spec/ruby/core/enumerable/each_slice_spec.rb index a57a1dba81..47b8c9ba33 100644 --- a/spec/ruby/core/enumerable/each_slice_spec.rb +++ b/spec/ruby/core/enumerable/each_slice_spec.rb @@ -57,10 +57,8 @@ describe "Enumerable#each_slice" do e.to_a.should == @sliced end - ruby_version_is "3.1" do - it "returns self when a block is given" do - @enum.each_slice(3){}.should == @enum - end + it "returns self when a block is given" do + @enum.each_slice(3){}.should == @enum end it "gathers whole arrays as elements when each yields multiple" do diff --git a/spec/ruby/core/enumerable/entries_spec.rb b/spec/ruby/core/enumerable/entries_spec.rb index 83232cfa06..2de4fc756a 100644 --- a/spec/ruby/core/enumerable/entries_spec.rb +++ b/spec/ruby/core/enumerable/entries_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/entries' describe "Enumerable#entries" do - it_behaves_like :enumerable_entries , :entries + it_behaves_like :enumerable_entries, :entries end diff --git a/spec/ruby/core/enumerable/filter_spec.rb b/spec/ruby/core/enumerable/filter_spec.rb index 7e4f8c0b50..1c3a7e9ff5 100644 --- a/spec/ruby/core/enumerable/filter_spec.rb +++ b/spec/ruby/core/enumerable/filter_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find_all' describe "Enumerable#filter" do - it_behaves_like(:enumerable_find_all , :filter) + it_behaves_like :enumerable_find_all, :filter end diff --git a/spec/ruby/core/enumerable/find_all_spec.rb b/spec/ruby/core/enumerable/find_all_spec.rb index ce9058fe77..9cd635f247 100644 --- a/spec/ruby/core/enumerable/find_all_spec.rb +++ b/spec/ruby/core/enumerable/find_all_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find_all' describe "Enumerable#find_all" do - it_behaves_like :enumerable_find_all , :find_all + it_behaves_like :enumerable_find_all, :find_all end diff --git a/spec/ruby/core/enumerable/find_spec.rb b/spec/ruby/core/enumerable/find_spec.rb index 25aa3bf103..5ddebc05f8 100644 --- a/spec/ruby/core/enumerable/find_spec.rb +++ b/spec/ruby/core/enumerable/find_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find' describe "Enumerable#find" do - it_behaves_like :enumerable_find , :find + it_behaves_like :enumerable_find, :find end diff --git a/spec/ruby/core/enumerable/fixtures/classes.rb b/spec/ruby/core/enumerable/fixtures/classes.rb index fb4951c6e6..b5feafcfb7 100644 --- a/spec/ruby/core/enumerable/fixtures/classes.rb +++ b/spec/ruby/core/enumerable/fixtures/classes.rb @@ -38,12 +38,14 @@ module EnumerableSpecs class Empty include Enumerable def each + self end end class EmptyWithSize include Enumerable def each + self end def size 0 @@ -342,4 +344,7 @@ module EnumerableSpecs @block.call(*args) end end + + class SetSubclass < Set + end end # EnumerableSpecs utility classes diff --git a/spec/ruby/core/enumerable/flat_map_spec.rb b/spec/ruby/core/enumerable/flat_map_spec.rb index a294b9ddad..bd07eab6c5 100644 --- a/spec/ruby/core/enumerable/flat_map_spec.rb +++ b/spec/ruby/core/enumerable/flat_map_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/collect_concat' describe "Enumerable#flat_map" do - it_behaves_like :enumerable_collect_concat , :flat_map + it_behaves_like :enumerable_collect_concat, :flat_map end diff --git a/spec/ruby/core/enumerable/map_spec.rb b/spec/ruby/core/enumerable/map_spec.rb index d65aec238c..98a70781cf 100644 --- a/spec/ruby/core/enumerable/map_spec.rb +++ b/spec/ruby/core/enumerable/map_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/collect' describe "Enumerable#map" do - it_behaves_like :enumerable_collect , :map + it_behaves_like :enumerable_collect, :map end diff --git a/spec/ruby/core/enumerable/select_spec.rb b/spec/ruby/core/enumerable/select_spec.rb index 11168eb42e..7fc61926f9 100644 --- a/spec/ruby/core/enumerable/select_spec.rb +++ b/spec/ruby/core/enumerable/select_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/find_all' describe "Enumerable#select" do - it_behaves_like :enumerable_find_all , :select + it_behaves_like :enumerable_find_all, :select end diff --git a/spec/ruby/core/enumerable/shared/inject.rb b/spec/ruby/core/enumerable/shared/inject.rb index 693d34d675..8fb7e98c2b 100644 --- a/spec/ruby/core/enumerable/shared/inject.rb +++ b/spec/ruby/core/enumerable/shared/inject.rb @@ -16,6 +16,23 @@ describe :enumerable_inject, shared: true do it "can take two argument" do EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, :-).should == 4 + EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, "-").should == 4 + + [1, 2, 3].send(@method, 10, :-).should == 4 + [1, 2, 3].send(@method, 10, "-").should == 4 + end + + it "converts non-Symbol method name argument to String with #to_str if two arguments" do + name = Object.new + def name.to_str; "-"; end + + EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, name).should == 4 + [1, 2, 3].send(@method, 10, name).should == 4 + end + + it "raises TypeError when the second argument is not Symbol or String and it cannot be converted to String if two arguments" do + -> { EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, Object.new) }.should raise_error(TypeError, /is not a symbol nor a string/) + -> { [1, 2, 3].send(@method, 10, Object.new) }.should raise_error(TypeError, /is not a symbol nor a string/) end it "ignores the block if two arguments" do @@ -39,6 +56,25 @@ describe :enumerable_inject, shared: true do it "can take a symbol argument" do EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, :-).should == 4 + [10, 1, 2, 3].send(@method, :-).should == 4 + end + + it "can take a String argument" do + EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, "-").should == 4 + [10, 1, 2, 3].send(@method, "-").should == 4 + end + + it "converts non-Symbol method name argument to String with #to_str" do + name = Object.new + def name.to_str; "-"; end + + EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, name).should == 4 + [10, 1, 2, 3].send(@method, name).should == 4 + end + + it "raises TypeError when passed not Symbol or String method name argument and it cannot be converted to String" do + -> { EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, Object.new) }.should raise_error(TypeError, /is not a symbol nor a string/) + -> { [10, 1, 2, 3].send(@method, Object.new) }.should raise_error(TypeError, /is not a symbol nor a string/) end it "without argument takes a block with an accumulator (with first element as initial value) and the current element. Value of block becomes new accumulator" do @@ -67,7 +103,7 @@ describe :enumerable_inject, shared: true do it "without inject arguments(legacy rubycon)" do # no inject argument - EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| 999 } .should == 2 + EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| 999 }.should == 2 EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| acc }.should == 2 EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| x }.should == 2 @@ -77,7 +113,6 @@ describe :enumerable_inject, shared: true do EnumerableSpecs::EachDefiner.new('a','b','c').send(@method) {|result, i| i+result}.should == "cba" EnumerableSpecs::EachDefiner.new(3, 4, 5).send(@method) {|result, i| result*i}.should == 60 EnumerableSpecs::EachDefiner.new([1], 2, 'a','b').send(@method){|r,i| r<<i}.should == [1, 2, 'a', 'b'] - end it "returns nil when fails(legacy rubycon)" do @@ -100,10 +135,8 @@ describe :enumerable_inject, shared: true do actual.sort_by(&:to_s).should == expected.sort_by(&:to_s) end - ruby_bug '#18635', ''...'3.2' do - it "raises an ArgumentError when no parameters or block is given" do - -> { [1,2].send(@method) }.should raise_error(ArgumentError) - -> { {one: 1, two: 2}.send(@method) }.should raise_error(ArgumentError) - end + it "raises an ArgumentError when no parameters or block is given" do + -> { [1,2].send(@method) }.should raise_error(ArgumentError) + -> { {one: 1, two: 2}.send(@method) }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/core/enumerable/tally_spec.rb b/spec/ruby/core/enumerable/tally_spec.rb index e0edc8dc75..95c64c1294 100644 --- a/spec/ruby/core/enumerable/tally_spec.rb +++ b/spec/ruby/core/enumerable/tally_spec.rb @@ -32,62 +32,60 @@ describe "Enumerable#tally" do end end -ruby_version_is "3.1" do - describe "Enumerable#tally with a hash" do - before :each do - ScratchPad.record [] - end - - it "returns a hash with counts according to the value" do - enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') - enum.tally({ 'foo' => 1 }).should == { 'foo' => 3, 'bar' => 1, 'baz' => 1} - end - - it "returns the given hash" do - enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') - hash = { 'foo' => 1 } - enum.tally(hash).should equal(hash) - end - - it "calls #to_hash to convert argument to Hash implicitly if passed not a Hash" do - enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') - object = Object.new - def object.to_hash; { 'foo' => 1 }; end - enum.tally(object).should == { 'foo' => 3, 'bar' => 1, 'baz' => 1} - end - - it "raises a FrozenError and does not update the given hash when the hash is frozen" do - enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') - hash = { 'foo' => 1 }.freeze - -> { enum.tally(hash) }.should raise_error(FrozenError) - hash.should == { 'foo' => 1 } - end - - it "raises a FrozenError even if enumerable is empty" do - enum = EnumerableSpecs::Numerous.new() - hash = { 'foo' => 1 }.freeze - -> { enum.tally(hash) }.should raise_error(FrozenError) - end - - it "does not call given block" do - enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') - enum.tally({ 'foo' => 1 }) { |v| ScratchPad << v } - ScratchPad.recorded.should == [] - end - - it "ignores the default value" do - enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') - enum.tally(Hash.new(100)).should == { 'foo' => 2, 'bar' => 1, 'baz' => 1} - end - - it "ignores the default proc" do - enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') - enum.tally(Hash.new {100}).should == { 'foo' => 2, 'bar' => 1, 'baz' => 1} - end - - it "needs the values counting each elements to be an integer" do - enum = EnumerableSpecs::Numerous.new('foo') - -> { enum.tally({ 'foo' => 'bar' }) }.should raise_error(TypeError) - end +describe "Enumerable#tally with a hash" do + before :each do + ScratchPad.record [] + end + + it "returns a hash with counts according to the value" do + enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') + enum.tally({ 'foo' => 1 }).should == { 'foo' => 3, 'bar' => 1, 'baz' => 1} + end + + it "returns the given hash" do + enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') + hash = { 'foo' => 1 } + enum.tally(hash).should equal(hash) + end + + it "calls #to_hash to convert argument to Hash implicitly if passed not a Hash" do + enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') + object = Object.new + def object.to_hash; { 'foo' => 1 }; end + enum.tally(object).should == { 'foo' => 3, 'bar' => 1, 'baz' => 1} + end + + it "raises a FrozenError and does not update the given hash when the hash is frozen" do + enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') + hash = { 'foo' => 1 }.freeze + -> { enum.tally(hash) }.should raise_error(FrozenError) + hash.should == { 'foo' => 1 } + end + + it "raises a FrozenError even if enumerable is empty" do + enum = EnumerableSpecs::Numerous.new() + hash = { 'foo' => 1 }.freeze + -> { enum.tally(hash) }.should raise_error(FrozenError) + end + + it "does not call given block" do + enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') + enum.tally({ 'foo' => 1 }) { |v| ScratchPad << v } + ScratchPad.recorded.should == [] + end + + it "ignores the default value" do + enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') + enum.tally(Hash.new(100)).should == { 'foo' => 2, 'bar' => 1, 'baz' => 1} + end + + it "ignores the default proc" do + enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') + enum.tally(Hash.new {100}).should == { 'foo' => 2, 'bar' => 1, 'baz' => 1} + end + + it "needs the values counting each elements to be an integer" do + enum = EnumerableSpecs::Numerous.new('foo') + -> { enum.tally({ 'foo' => 'bar' }) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/enumerable/to_a_spec.rb b/spec/ruby/core/enumerable/to_a_spec.rb index 0f3060dc48..723f922574 100644 --- a/spec/ruby/core/enumerable/to_a_spec.rb +++ b/spec/ruby/core/enumerable/to_a_spec.rb @@ -3,5 +3,5 @@ require_relative 'fixtures/classes' require_relative 'shared/entries' describe "Enumerable#to_a" do - it_behaves_like :enumerable_entries , :to_a + it_behaves_like :enumerable_entries, :to_a end diff --git a/spec/ruby/core/enumerable/to_h_spec.rb b/spec/ruby/core/enumerable/to_h_spec.rb index 0489134552..11a4933c10 100644 --- a/spec/ruby/core/enumerable/to_h_spec.rb +++ b/spec/ruby/core/enumerable/to_h_spec.rb @@ -53,6 +53,14 @@ describe "Enumerable#to_h" do @enum.to_h { |k| [k, k.to_s] }.should == { a: 'a', b: 'b' } end + it "passes to a block each element as a single argument" do + enum_of_arrays = EnumerableSpecs::EachDefiner.new([:a, 1], [:b, 2]) + + ScratchPad.record [] + enum_of_arrays.to_h { |*args| ScratchPad << args; [args[0], args[1]] } + ScratchPad.recorded.sort.should == [[[:a, 1]], [[:b, 2]]] + end + it "raises ArgumentError if block returns longer or shorter array" do -> do @enum.to_h { |k| [k, k.to_s, 1] } diff --git a/spec/ruby/core/enumerable/to_set_spec.rb b/spec/ruby/core/enumerable/to_set_spec.rb new file mode 100644 index 0000000000..c02ead11fa --- /dev/null +++ b/spec/ruby/core/enumerable/to_set_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Enumerable#to_set" do + it "returns a new Set created from self" do + [1, 2, 3].to_set.should == Set[1, 2, 3] + {a: 1, b: 2}.to_set.should == Set[[:b, 2], [:a, 1]] + end + + it "passes down passed blocks" do + [1, 2, 3].to_set { |x| x * x }.should == Set[1, 4, 9] + end + + ruby_version_is "4.0"..."4.1" do + it "instantiates an object of provided as the first argument set class" do + set = nil + proc{set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass)}.should complain(/Enumerable#to_set/) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end + end + + ruby_version_is ""..."4.0" do + it "instantiates an object of provided as the first argument set class" do + set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end + end +end diff --git a/spec/ruby/core/enumerator/chain/initialize_spec.rb b/spec/ruby/core/enumerator/chain/initialize_spec.rb index 69484dfcb4..daa30351d7 100644 --- a/spec/ruby/core/enumerator/chain/initialize_spec.rb +++ b/spec/ruby/core/enumerator/chain/initialize_spec.rb @@ -22,10 +22,10 @@ describe "Enumerator::Chain#initialize" do end describe "on frozen instance" do - it "raises a RuntimeError" do + it "raises a FrozenError" do -> { @uninitialized.freeze.send(:initialize) - }.should raise_error(RuntimeError) + }.should raise_error(FrozenError) end end end diff --git a/spec/ruby/core/enumerator/each_spec.rb b/spec/ruby/core/enumerator/each_spec.rb index 99ac3120af..3af16e5587 100644 --- a/spec/ruby/core/enumerator/each_spec.rb +++ b/spec/ruby/core/enumerator/each_spec.rb @@ -10,41 +10,41 @@ describe "Enumerator#each" do @enum_with_arguments = object_each_with_arguments.to_enum(:each_with_arguments, :arg0, :arg1, :arg2) - @enum_with_yielder = Enumerator.new {|y| y.yield :ok} + @enum_with_yielder = Enumerator.new { |y| y.yield :ok } end it "yields each element of self to the given block" do acc = [] - [1,2,3].to_enum.each {|e| acc << e } - acc.should == [1,2,3] + [1, 2, 3].to_enum.each { |e| acc << e } + acc.should == [1, 2, 3] end it "calls #each on the object given in the constructor by default" do each = mock('each') each.should_receive(:each) - each.to_enum.each {|e| e } + each.to_enum.each { |e| e } end it "calls #each on the underlying object until it's exhausted" do each = mock('each') each.should_receive(:each).and_yield(1).and_yield(2).and_yield(3) acc = [] - each.to_enum.each {|e| acc << e } - acc.should == [1,2,3] + each.to_enum.each { |e| acc << e } + acc.should == [1, 2, 3] end it "calls the method given in the constructor instead of #each" do each = mock('peach') each.should_receive(:peach) - each.to_enum(:peach).each {|e| e } + each.to_enum(:peach).each { |e| e } end it "calls the method given in the constructor until it's exhausted" do each = mock('peach') each.should_receive(:peach).and_yield(1).and_yield(2).and_yield(3) acc = [] - each.to_enum(:peach).each {|e| acc << e } - acc.should == [1,2,3] + each.to_enum(:peach).each { |e| acc << e } + acc.should == [1, 2, 3] end it "raises a NoMethodError if the object doesn't respond to #each" do diff --git a/spec/ruby/core/enumerator/each_with_index_spec.rb b/spec/ruby/core/enumerator/each_with_index_spec.rb index 96e53a2804..4898e86fa9 100644 --- a/spec/ruby/core/enumerator/each_with_index_spec.rb +++ b/spec/ruby/core/enumerator/each_with_index_spec.rb @@ -1,5 +1,5 @@ require_relative '../../spec_helper' -require_relative '../../shared/enumerator/with_index' +require_relative 'shared/with_index' require_relative '../enumerable/shared/enumeratorized' describe "Enumerator#each_with_index" do @@ -21,16 +21,16 @@ describe "Enumerator#each_with_index" do it "passes on the given block's return value" do arr = [1,2,3] - arr.delete_if.with_index { |a,b| false } + arr.delete_if.each_with_index { |a,b| false } arr.should == [1,2,3] end it "returns the iterator's return value" do - [1,2,3].select.with_index { |a,b| false }.should == [] + [1,2,3].select.each_with_index { |a,b| false }.should == [] + [1,2,3].select.each_with_index { |a,b| true }.should == [1,2,3] end it "returns the correct value if chained with itself" do [:a].each_with_index.each_with_index.to_a.should == [[[:a,0],0]] - [:a].each.with_index.with_index.to_a.should == [[[:a,0],0]] end end diff --git a/spec/ruby/core/enumerator/each_with_object_spec.rb b/spec/ruby/core/enumerator/each_with_object_spec.rb index 68524dc74a..84a45ae89d 100644 --- a/spec/ruby/core/enumerator/each_with_object_spec.rb +++ b/spec/ruby/core/enumerator/each_with_object_spec.rb @@ -1,5 +1,5 @@ require_relative '../../spec_helper' -require_relative '../../shared/enumerator/with_object' +require_relative 'shared/with_object' describe "Enumerator#each_with_object" do it_behaves_like :enum_with_object, :each_with_object diff --git a/spec/ruby/core/enumerator/enum_for_spec.rb b/spec/ruby/core/enumerator/enum_for_spec.rb index fd33f463bf..fbdf98545a 100644 --- a/spec/ruby/core/enumerator/enum_for_spec.rb +++ b/spec/ruby/core/enumerator/enum_for_spec.rb @@ -1,5 +1,5 @@ require_relative '../../spec_helper' -require_relative '../../shared/enumerator/enum_for' +require_relative 'shared/enum_for' describe "Enumerator#enum_for" do it_behaves_like :enum_for, :enum_for diff --git a/spec/ruby/core/enumerator/fixtures/classes.rb b/spec/ruby/core/enumerator/fixtures/classes.rb new file mode 100644 index 0000000000..6f285b8efa --- /dev/null +++ b/spec/ruby/core/enumerator/fixtures/classes.rb @@ -0,0 +1,15 @@ +module EnumSpecs + class Numerous + + include Enumerable + + def initialize(*list) + @list = list.empty? ? [2, 5, 3, 6, 1, 4] : list + end + + def each + @list.each { |i| yield i } + end + end + +end diff --git a/spec/ruby/core/enumerator/generator/initialize_spec.rb b/spec/ruby/core/enumerator/generator/initialize_spec.rb index f75c7d6f26..acc1174253 100644 --- a/spec/ruby/core/enumerator/generator/initialize_spec.rb +++ b/spec/ruby/core/enumerator/generator/initialize_spec.rb @@ -17,10 +17,10 @@ describe "Enumerator::Generator#initialize" do end describe "on frozen instance" do - it "raises a RuntimeError" do + it "raises a FrozenError" do -> { @uninitialized.freeze.send(:initialize) {} - }.should raise_error(RuntimeError) + }.should raise_error(FrozenError) end end end diff --git a/spec/ruby/core/enumerator/initialize_spec.rb b/spec/ruby/core/enumerator/initialize_spec.rb index 5e937aff2b..5e0256ca46 100644 --- a/spec/ruby/core/enumerator/initialize_spec.rb +++ b/spec/ruby/core/enumerator/initialize_spec.rb @@ -48,10 +48,10 @@ describe "Enumerator#initialize" do end describe "on frozen instance" do - it "raises a RuntimeError" do + it "raises a FrozenError" do -> { @uninitialized.freeze.send(:initialize) {} - }.should raise_error(RuntimeError) + }.should raise_error(FrozenError) end end end diff --git a/spec/ruby/core/enumerator/lazy/compact_spec.rb b/spec/ruby/core/enumerator/lazy/compact_spec.rb index e678bc71eb..65c544f062 100644 --- a/spec/ruby/core/enumerator/lazy/compact_spec.rb +++ b/spec/ruby/core/enumerator/lazy/compact_spec.rb @@ -1,16 +1,14 @@ require_relative '../../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is '3.1' do - describe "Enumerator::Lazy#compact" do - it 'returns array without nil elements' do - arr = [1, nil, 3, false, 5].to_enum.lazy.compact - arr.should be_an_instance_of(Enumerator::Lazy) - arr.force.should == [1, 3, false, 5] - end +describe "Enumerator::Lazy#compact" do + it 'returns array without nil elements' do + arr = [1, nil, 3, false, 5].to_enum.lazy.compact + arr.should be_an_instance_of(Enumerator::Lazy) + arr.force.should == [1, 3, false, 5] + end - it "sets #size to nil" do - Enumerator::Lazy.new(Object.new, 100) {}.compact.size.should == nil - end + it "sets #size to nil" do + Enumerator::Lazy.new(Object.new, 100) {}.compact.size.should == nil end end diff --git a/spec/ruby/core/enumerator/lazy/initialize_spec.rb b/spec/ruby/core/enumerator/lazy/initialize_spec.rb index f23018d010..e1e0b1d608 100644 --- a/spec/ruby/core/enumerator/lazy/initialize_spec.rb +++ b/spec/ruby/core/enumerator/lazy/initialize_spec.rb @@ -56,8 +56,8 @@ describe "Enumerator::Lazy#initialize" do end describe "on frozen instance" do - it "raises a RuntimeError" do - -> { @uninitialized.freeze.send(:initialize, @receiver) {} }.should raise_error(RuntimeError) + it "raises a FrozenError" do + -> { @uninitialized.freeze.send(:initialize, @receiver) {} }.should raise_error(FrozenError) end end end diff --git a/spec/ruby/core/enumerator/lazy/lazy_spec.rb b/spec/ruby/core/enumerator/lazy/lazy_spec.rb index 0fb104e25a..b43864b673 100644 --- a/spec/ruby/core/enumerator/lazy/lazy_spec.rb +++ b/spec/ruby/core/enumerator/lazy/lazy_spec.rb @@ -9,16 +9,11 @@ describe "Enumerator::Lazy" do it "defines lazy versions of a whitelist of Enumerator methods" do lazy_methods = [ - :chunk, :collect, :collect_concat, :drop, :drop_while, :enum_for, + :chunk, :chunk_while, :collect, :collect_concat, :compact, :drop, :drop_while, :enum_for, :find_all, :flat_map, :force, :grep, :grep_v, :lazy, :map, :reject, :select, :slice_after, :slice_before, :slice_when, :take, :take_while, - :to_enum, :zip + :to_enum, :uniq, :zip ] - lazy_methods += [:chunk_while, :uniq] - - ruby_version_is '3.1' do - lazy_methods += [:compact] - end Enumerator::Lazy.instance_methods(false).should include(*lazy_methods) end diff --git a/spec/ruby/core/enumerator/next_values_spec.rb b/spec/ruby/core/enumerator/next_values_spec.rb index 201b5d323f..2202700c58 100644 --- a/spec/ruby/core/enumerator/next_values_spec.rb +++ b/spec/ruby/core/enumerator/next_values_spec.rb @@ -11,6 +11,7 @@ describe "Enumerator#next_values" do yield :e1, :e2, :e3 yield nil yield + yield [:f1, :f2] end @e = o.to_enum @@ -48,8 +49,13 @@ describe "Enumerator#next_values" do @e.next_values.should == [] end - it "raises StopIteration if called on a finished enumerator" do + it "returns an array of array if yield is called with an array" do 7.times { @e.next } + @e.next_values.should == [[:f1, :f2]] + end + + it "raises StopIteration if called on a finished enumerator" do + 8.times { @e.next } -> { @e.next_values }.should raise_error(StopIteration) end end diff --git a/spec/ruby/core/enumerator/peek_values_spec.rb b/spec/ruby/core/enumerator/peek_values_spec.rb index 7865546515..8b84fc8afc 100644 --- a/spec/ruby/core/enumerator/peek_values_spec.rb +++ b/spec/ruby/core/enumerator/peek_values_spec.rb @@ -11,6 +11,7 @@ describe "Enumerator#peek_values" do yield :e1, :e2, :e3 yield nil yield + yield [:f1, :f2] end @e = o.to_enum @@ -50,8 +51,13 @@ describe "Enumerator#peek_values" do @e.peek_values.should == [] end - it "raises StopIteration if called on a finished enumerator" do + it "returns an array of array if yield is called with an array" do 7.times { @e.next } + @e.peek_values.should == [[:f1, :f2]] + end + + it "raises StopIteration if called on a finished enumerator" do + 8.times { @e.next } -> { @e.peek_values }.should raise_error(StopIteration) end end diff --git a/spec/ruby/core/enumerator/product/each_spec.rb b/spec/ruby/core/enumerator/product/each_spec.rb new file mode 100644 index 0000000000..88f115a712 --- /dev/null +++ b/spec/ruby/core/enumerator/product/each_spec.rb @@ -0,0 +1,71 @@ +require_relative '../../../spec_helper' +require_relative '../../enumerable/shared/enumeratorized' + +describe "Enumerator::Product#each" do + it_behaves_like :enumeratorized_with_origin_size, :each, Enumerator::Product.new([1, 2], [:a, :b]) + + it "yields each element of Cartesian product of enumerators" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + acc = [] + enum.each { |e| acc << e } + acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "calls #each_entry method on enumerators" do + object1 = Object.new + def object1.each_entry + yield 1 + yield 2 + end + + object2 = Object.new + def object2.each_entry + yield :a + yield :b + end + + enum = Enumerator::Product.new(object1, object2) + acc = [] + enum.each { |e| acc << e } + acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "raises a NoMethodError if the object doesn't respond to #each_entry" do + -> { + Enumerator::Product.new(Object.new).each {} + }.should raise_error(NoMethodError, /undefined method [`']each_entry' for/) + end + + it "returns enumerator if not given a block" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each.should.kind_of?(Enumerator) + + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "returns self if given a block" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each {}.should.equal?(enum) + end + + it "doesn't accept arguments" do + Enumerator::Product.instance_method(:each).arity.should == 0 + end + + it "yields each element to a block that takes multiple arguments" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + + acc = [] + enum.each { |x, y| acc << x } + acc.should == [1, 1, 2, 2] + + acc = [] + enum.each { |x, y| acc << y } + acc.should == [:a, :b, :a, :b] + + acc = [] + enum.each { |x, y, z| acc << z } + acc.should == [nil, nil, nil, nil] + end +end diff --git a/spec/ruby/core/enumerator/product/initialize_copy_spec.rb b/spec/ruby/core/enumerator/product/initialize_copy_spec.rb new file mode 100644 index 0000000000..b1b9f3ca9b --- /dev/null +++ b/spec/ruby/core/enumerator/product/initialize_copy_spec.rb @@ -0,0 +1,52 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#initialize_copy" do + it "replaces content of the receiver with content of the other object" do + enum = Enumerator::Product.new([true, false]) + enum2 = Enumerator::Product.new([1, 2], [:a, :b]) + + enum.send(:initialize_copy, enum2) + enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "returns self" do + enum = Enumerator::Product.new([true, false]) + enum2 = Enumerator::Product.new([1, 2], [:a, :b]) + + enum.send(:initialize_copy, enum2).should.equal?(enum) + end + + it "is a private method" do + Enumerator::Product.should have_private_instance_method(:initialize_copy, false) + end + + it "does nothing if the argument is the same as the receiver" do + enum = Enumerator::Product.new(1..2) + enum.send(:initialize_copy, enum).should.equal?(enum) + + enum.freeze + enum.send(:initialize_copy, enum).should.equal?(enum) + end + + it "raises FrozenError if the receiver is frozen" do + enum = Enumerator::Product.new(1..2) + enum2 = Enumerator::Product.new(3..4) + + -> { enum.freeze.send(:initialize_copy, enum2) }.should raise_error(FrozenError) + end + + it "raises TypeError if the objects are of different class" do + enum = Enumerator::Product.new(1..2) + enum2 = Class.new(Enumerator::Product).new(3..4) + + -> { enum.send(:initialize_copy, enum2) }.should raise_error(TypeError, 'initialize_copy should take same class object') + -> { enum2.send(:initialize_copy, enum) }.should raise_error(TypeError, 'initialize_copy should take same class object') + end + + it "raises ArgumentError if the argument is not initialized yet" do + enum = Enumerator::Product.new(1..2) + enum2 = Enumerator::Product.allocate + + -> { enum.send(:initialize_copy, enum2) }.should raise_error(ArgumentError, 'uninitialized product') + end +end diff --git a/spec/ruby/core/enumerator/product/initialize_spec.rb b/spec/ruby/core/enumerator/product/initialize_spec.rb new file mode 100644 index 0000000000..ed2a8a2a13 --- /dev/null +++ b/spec/ruby/core/enumerator/product/initialize_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#initialize" do + before :each do + @uninitialized = Enumerator::Product.allocate + end + + it "is a private method" do + Enumerator::Product.should have_private_instance_method(:initialize, false) + end + + it "returns self" do + @uninitialized.send(:initialize).should equal(@uninitialized) + end + + it "accepts many arguments" do + @uninitialized.send(:initialize, 0..1, 2..3, 4..5).should equal(@uninitialized) + end + + it "accepts arguments that are not Enumerable nor responding to :each_entry" do + @uninitialized.send(:initialize, Object.new).should equal(@uninitialized) + end + + describe "on frozen instance" do + it "raises a FrozenError" do + -> { + @uninitialized.freeze.send(:initialize, 0..1) + }.should raise_error(FrozenError) + end + end +end diff --git a/spec/ruby/core/enumerator/product/inspect_spec.rb b/spec/ruby/core/enumerator/product/inspect_spec.rb new file mode 100644 index 0000000000..e0d7441f26 --- /dev/null +++ b/spec/ruby/core/enumerator/product/inspect_spec.rb @@ -0,0 +1,20 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#inspect" do + it "returns a String including enumerators" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.inspect.should == "#<Enumerator::Product: [[1, 2], [:a, :b]]>" + end + + it "represents a recursive element with '[...]'" do + enum = [1, 2] + enum_recursive = Enumerator::Product.new(enum) + + enum << enum_recursive + enum_recursive.inspect.should == "#<Enumerator::Product: [[1, 2, #<Enumerator::Product: ...>]]>" + end + + it "returns a not initialized representation if #initialized is not called yet" do + Enumerator::Product.allocate.inspect.should == "#<Enumerator::Product: uninitialized>" + end +end diff --git a/spec/ruby/core/enumerator/product/rewind_spec.rb b/spec/ruby/core/enumerator/product/rewind_spec.rb new file mode 100644 index 0000000000..2beffaf5c1 --- /dev/null +++ b/spec/ruby/core/enumerator/product/rewind_spec.rb @@ -0,0 +1,62 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#rewind" do + before :each do + @enum = Enumerator::Product.new([1, 2].each.to_enum, [:a, :b].each.to_enum) + end + + it "resets the enumerator to its initial state" do + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + @enum.rewind + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "returns self" do + @enum.rewind.should.equal? @enum + end + + it "has no effect on a new enumerator" do + @enum.rewind + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "has no effect if called multiple, consecutive times" do + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + @enum.rewind + @enum.rewind + @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end + + it "calls the enclosed object's rewind method if one exists" do + obj = mock('rewinder') + enum = Enumerator::Product.new(obj.to_enum) + + obj.should_receive(:rewind) + enum.rewind + end + + it "does nothing if the object doesn't have a #rewind method" do + obj = mock('rewinder') + enum = Enumerator::Product.new(obj.to_enum) + + enum.rewind.should == enum + end + + it "calls a rewind method on each enumerable in direct order" do + ScratchPad.record [] + + object1 = Object.new + def object1.rewind; ScratchPad << :object1; end + + object2 = Object.new + def object2.rewind; ScratchPad << :object2; end + + object3 = Object.new + def object3.rewind; ScratchPad << :object3; end + + enum = Enumerator::Product.new(object1, object2, object3) + enum.rewind + + ScratchPad.recorded.should == [:object1, :object2, :object3] + end +end diff --git a/spec/ruby/core/enumerator/product/size_spec.rb b/spec/ruby/core/enumerator/product/size_spec.rb new file mode 100644 index 0000000000..96632d6eee --- /dev/null +++ b/spec/ruby/core/enumerator/product/size_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../../spec_helper' + +describe "Enumerator::Product#size" do + it "returns the total size of the enumerator product calculated by multiplying the sizes of enumerables in the product" do + product = Enumerator::Product.new(1..2, 1..3, 1..4) + product.size.should == 24 # 2 * 3 * 4 + end + + it "returns nil if any enumerable reports its size as nil" do + enum = Object.new + def enum.size; nil; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + it "returns Float::INFINITY if any enumerable reports its size as Float::INFINITY" do + enum = Object.new + def enum.size; Float::INFINITY; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == Float::INFINITY + end + + it "returns nil if any enumerable reports its size as Float::NAN" do + enum = Object.new + def enum.size; Float::NAN; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + it "returns nil if any enumerable doesn't respond to #size" do + enum = Object.new + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + it "returns nil if any enumerable reports a not-convertible to Integer" do + enum = Object.new + def enum.size; :symbol; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end + + it "returns nil if any enumerable reports a non-Integer but convertible to Integer size" do + enum = Object.new + def enum.size; 1.0; end + + product = Enumerator::Product.new(1..2, enum) + product.size.should == nil + end +end diff --git a/spec/ruby/core/enumerator/product_spec.rb b/spec/ruby/core/enumerator/product_spec.rb index 44fc6441e1..83c7cb8e0e 100644 --- a/spec/ruby/core/enumerator/product_spec.rb +++ b/spec/ruby/core/enumerator/product_spec.rb @@ -1,88 +1,91 @@ require_relative '../../spec_helper' -ruby_version_is "3.2" do - describe "Enumerator.product" do - it "returns a Cartesian product of enumerators" do - enum = Enumerator.product(1..2, ["A", "B"]) - enum.to_a.should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"]] - end +describe "Enumerator.product" do + it "returns a Cartesian product of enumerators" do + enum = Enumerator.product(1..2, ["A", "B"]) + enum.to_a.should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"]] + end - it "accepts a list of enumerators of any length" do - enum = Enumerator.product(1..2) - enum.to_a.should == [[1], [2]] + it "accepts a list of enumerators of any length" do + enum = Enumerator.product(1..2) + enum.to_a.should == [[1], [2]] - enum = Enumerator.product(1..2, ["A"]) - enum.to_a.should == [[1, "A"], [2, "A"]] + enum = Enumerator.product(1..2, ["A"]) + enum.to_a.should == [[1, "A"], [2, "A"]] - enum = Enumerator.product(1..2, ["A"], ["B"]) - enum.to_a.should == [[1, "A", "B"], [2, "A", "B"]] + enum = Enumerator.product(1..2, ["A"], ["B"]) + enum.to_a.should == [[1, "A", "B"], [2, "A", "B"]] - enum = Enumerator.product(2..3, ["A"], ["B"], ["C"]) - enum.to_a.should == [[2, "A", "B", "C"], [3, "A", "B", "C"]] - end + enum = Enumerator.product(2..3, ["A"], ["B"], ["C"]) + enum.to_a.should == [[2, "A", "B", "C"], [3, "A", "B", "C"]] + end - it "returns an enumerator with an empty array when no arguments passed" do - enum = Enumerator.product - enum.to_a.should == [[]] - end + it "returns an enumerator with an empty array when no arguments passed" do + enum = Enumerator.product + enum.to_a.should == [[]] + end - it "returns an instance of Enumerator::Product" do - enum = Enumerator.product - enum.class.should == Enumerator::Product - end + it "returns an instance of Enumerator::Product" do + enum = Enumerator.product + enum.class.should == Enumerator::Product + end - it "accepts infinite enumerators and returns infinite enumerator" do - enum = Enumerator.product(1.., ["A", "B"]) - enum.take(5).should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"]] - enum.size.should == Float::INFINITY - end + it "accepts infinite enumerators and returns infinite enumerator" do + enum = Enumerator.product(1.., ["A", "B"]) + enum.take(5).should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"]] + enum.size.should == Float::INFINITY + end - it "accepts a block" do - elems = [] - enum = Enumerator.product(1..2, ["X", "Y"]) { elems << _1 } + it "accepts a block" do + elems = [] + enum = Enumerator.product(1..2, ["X", "Y"]) { elems << _1 } - elems.should == [[1, "X"], [1, "Y"], [2, "X"], [2, "Y"]] - end + elems.should == [[1, "X"], [1, "Y"], [2, "X"], [2, "Y"]] + end - it "reject keyword arguments" do - -> { - Enumerator.product(1..3, foo: 1, bar: 2) - }.should raise_error(ArgumentError, "unknown keywords: :foo, :bar") - end + it "returns nil when a block passed" do + Enumerator.product(1..2) {}.should == nil + end - it "calls only #each_entry method on arguments" do - object = Object.new - def object.each_entry - yield 1 - yield 2 - end + # https://bugs.ruby-lang.org/issues/19829 + it "reject keyword arguments" do + -> { + Enumerator.product(1..3, foo: 1, bar: 2) + }.should raise_error(ArgumentError, "unknown keywords: :foo, :bar") + end - enum = Enumerator.product(object, ["A", "B"]) - enum.to_a.should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"]] + it "calls only #each_entry method on arguments" do + object = Object.new + def object.each_entry + yield 1 + yield 2 end - it "raises NoMethodError when argument doesn't respond to #each_entry" do - -> { - Enumerator.product(Object.new).to_a - }.should raise_error(NoMethodError, /undefined method `each_entry' for/) - end + enum = Enumerator.product(object, ["A", "B"]) + enum.to_a.should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"]] + end - it "calls #each_entry lazily" do - Enumerator.product(Object.new).should be_kind_of(Enumerator) - end + it "raises NoMethodError when argument doesn't respond to #each_entry" do + -> { + Enumerator.product(Object.new).to_a + }.should raise_error(NoMethodError, /undefined method [`']each_entry' for/) + end + + it "calls #each_entry lazily" do + Enumerator.product(Object.new).should be_kind_of(Enumerator) + end - it "iterates through consuming enumerator elements only once" do - a = [1, 2, 3] - i = 0 + it "iterates through consuming enumerator elements only once" do + a = [1, 2, 3] + i = 0 - enum = Enumerator.new do |y| - while i < a.size - y << a[i] - i += 1 - end + enum = Enumerator.new do |y| + while i < a.size + y << a[i] + i += 1 end - - Enumerator.product(['a', 'b'], enum).to_a.should == [["a", 1], ["a", 2], ["a", 3]] end + + Enumerator.product(['a', 'b'], enum).to_a.should == [["a", 1], ["a", 2], ["a", 3]] end end diff --git a/spec/ruby/core/enumerator/rewind_spec.rb b/spec/ruby/core/enumerator/rewind_spec.rb index a105f2c619..6ba0edf174 100644 --- a/spec/ruby/core/enumerator/rewind_spec.rb +++ b/spec/ruby/core/enumerator/rewind_spec.rb @@ -14,7 +14,7 @@ describe "Enumerator#rewind" do end it "returns self" do - @enum.rewind.should == @enum + @enum.rewind.should.equal? @enum end it "has no effect on a new enumerator" do @@ -49,7 +49,7 @@ describe "Enumerator#rewind" do obj = mock('rewinder') enum = obj.to_enum obj.should_receive(:each).at_most(1) - -> { enum.rewind.should == enum }.should_not raise_error + enum.rewind.should == enum end end diff --git a/spec/ruby/core/enumerator/shared/enum_for.rb b/spec/ruby/core/enumerator/shared/enum_for.rb new file mode 100644 index 0000000000..a67a76c461 --- /dev/null +++ b/spec/ruby/core/enumerator/shared/enum_for.rb @@ -0,0 +1,57 @@ +describe :enum_for, shared: true do + it "is defined in Kernel" do + Kernel.method_defined?(@method).should be_true + end + + it "returns a new enumerator" do + "abc".send(@method).should be_an_instance_of(Enumerator) + end + + it "defaults the first argument to :each" do + enum = [1,2].send(@method) + enum.map { |v| v }.should == [1,2].each { |v| v } + end + + it "sets regexp matches in the caller" do + "wawa".send(@method, :scan, /./).map {|o| $& }.should == ["w", "a", "w", "a"] + a = [] + "wawa".send(@method, :scan, /./).each {|o| a << $& } + a.should == ["w", "a", "w", "a"] + end + + it "exposes multi-arg yields as an array" do + o = Object.new + def o.each + yield :a + yield :b1, :b2 + yield [:c] + yield :d1, :d2 + yield :e1, :e2, :e3 + end + + enum = o.send(@method) + enum.next.should == :a + enum.next.should == [:b1, :b2] + enum.next.should == [:c] + enum.next.should == [:d1, :d2] + enum.next.should == [:e1, :e2, :e3] + end + + it "uses the passed block's value to calculate the size of the enumerator" do + Object.new.enum_for { 100 }.size.should == 100 + end + + it "defers the evaluation of the passed block until #size is called" do + ScratchPad.record [] + + enum = Object.new.enum_for do + ScratchPad << :called + 100 + end + + ScratchPad.recorded.should be_empty + + enum.size + ScratchPad.recorded.should == [:called] + end +end diff --git a/spec/ruby/core/enumerator/shared/with_index.rb b/spec/ruby/core/enumerator/shared/with_index.rb new file mode 100644 index 0000000000..78771ffe82 --- /dev/null +++ b/spec/ruby/core/enumerator/shared/with_index.rb @@ -0,0 +1,33 @@ +require_relative '../../../spec_helper' + +describe :enum_with_index, shared: true do + + require_relative '../fixtures/classes' + + before :each do + @origin = [1, 2, 3, 4] + @enum = @origin.to_enum + end + + it "passes each element and its index to block" do + a = [] + @enum.send(@method) { |o, i| a << [o, i] } + a.should == [[1, 0], [2, 1], [3, 2], [4, 3]] + end + + it "returns the object being enumerated when given a block" do + @enum.send(@method) { |o, i| :glark }.should equal(@origin) + end + + it "binds splat arguments properly" do + acc = [] + @enum.send(@method) { |*b| c,d = b; acc << c; acc << d } + [1, 0, 2, 1, 3, 2, 4, 3].should == acc + end + + it "returns an enumerator if no block is supplied" do + ewi = @enum.send(@method) + ewi.should be_an_instance_of(Enumerator) + ewi.to_a.should == [[1, 0], [2, 1], [3, 2], [4, 3]] + end +end diff --git a/spec/ruby/core/enumerator/shared/with_object.rb b/spec/ruby/core/enumerator/shared/with_object.rb new file mode 100644 index 0000000000..d8e9d8e9f7 --- /dev/null +++ b/spec/ruby/core/enumerator/shared/with_object.rb @@ -0,0 +1,42 @@ +require_relative '../../../spec_helper' + +describe :enum_with_object, shared: true do + before :each do + @enum = [:a, :b].to_enum + @memo = '' + @block_params = @enum.send(@method, @memo).to_a + end + + it "receives an argument" do + @enum.method(@method).arity.should == 1 + end + + context "with block" do + it "returns the given object" do + ret = @enum.send(@method, @memo) do |elm, memo| + # nothing + end + ret.should equal(@memo) + end + + context "the block parameter" do + it "passes each element to first parameter" do + @block_params[0][0].should equal(:a) + @block_params[1][0].should equal(:b) + end + + it "passes the given object to last parameter" do + @block_params[0][1].should equal(@memo) + @block_params[1][1].should equal(@memo) + end + end + end + + context "without block" do + it "returns new Enumerator" do + ret = @enum.send(@method, @memo) + ret.should be_an_instance_of(Enumerator) + ret.should_not equal(@enum) + end + end +end diff --git a/spec/ruby/core/enumerator/to_enum_spec.rb b/spec/ruby/core/enumerator/to_enum_spec.rb index cadfcf6314..7fb73d0c3c 100644 --- a/spec/ruby/core/enumerator/to_enum_spec.rb +++ b/spec/ruby/core/enumerator/to_enum_spec.rb @@ -1,6 +1,6 @@ require_relative '../../spec_helper' -require_relative '../../shared/enumerator/enum_for' +require_relative 'shared/enum_for' describe "Enumerator#to_enum" do - it_behaves_like :enum_for, :enum_for + it_behaves_like :enum_for, :to_enum end diff --git a/spec/ruby/core/enumerator/with_index_spec.rb b/spec/ruby/core/enumerator/with_index_spec.rb index ac37cee508..e49aa7a939 100644 --- a/spec/ruby/core/enumerator/with_index_spec.rb +++ b/spec/ruby/core/enumerator/with_index_spec.rb @@ -1,5 +1,5 @@ require_relative '../../spec_helper' -require_relative '../../shared/enumerator/with_index' +require_relative 'shared/with_index' require_relative '../enumerable/shared/enumeratorized' describe "Enumerator#with_index" do @@ -69,4 +69,21 @@ describe "Enumerator#with_index" do @enum.with_index(-1) { |*x| res << x} res.should == [[1,-1], [2,0], [3,1], [4,2]] end + + it "passes on the given block's return value" do + arr = [1,2,3] + arr.delete_if.with_index { |a,b| false } + arr.should == [1,2,3] + + arr.delete_if.with_index { |a,b| true } + arr.should == [] + end + + it "returns the iterator's return value" do + @enum.select.with_index { |a,b| false }.should == [] + end + + it "returns the correct value if chained with itself" do + [:a].each.with_index.with_index.to_a.should == [[[:a,0],0]] + end end diff --git a/spec/ruby/core/enumerator/with_object_spec.rb b/spec/ruby/core/enumerator/with_object_spec.rb index e7ba83fd9f..58031fd765 100644 --- a/spec/ruby/core/enumerator/with_object_spec.rb +++ b/spec/ruby/core/enumerator/with_object_spec.rb @@ -1,5 +1,5 @@ require_relative '../../spec_helper' -require_relative '../../shared/enumerator/with_object' +require_relative 'shared/with_object' describe "Enumerator#with_object" do it_behaves_like :enum_with_object, :with_object diff --git a/spec/ruby/core/env/clone_spec.rb b/spec/ruby/core/env/clone_spec.rb index 991e4e6774..01a29c6ab4 100644 --- a/spec/ruby/core/env/clone_spec.rb +++ b/spec/ruby/core/env/clone_spec.rb @@ -13,11 +13,9 @@ describe "ENV#clone" do }.should raise_error(ArgumentError) end - ruby_version_is "3.2" do - it "raises TypeError" do - -> { - ENV.clone - }.should raise_error(TypeError, /Cannot clone ENV, use ENV.to_h to get a copy of ENV as a hash/) - end + it "raises TypeError" do + -> { + ENV.clone + }.should raise_error(TypeError, /Cannot clone ENV, use ENV.to_h to get a copy of ENV as a hash/) end end diff --git a/spec/ruby/core/env/delete_spec.rb b/spec/ruby/core/env/delete_spec.rb index 3d69e4b022..f28ac97911 100644 --- a/spec/ruby/core/env/delete_spec.rb +++ b/spec/ruby/core/env/delete_spec.rb @@ -41,6 +41,14 @@ describe "ENV.delete" do ENV["foo"].should == nil end + it "removes the variable coerced with #to_str" do + ENV["foo"] = "bar" + k = mock('key') + k.should_receive(:to_str).and_return("foo") + ENV.delete(k) + ENV["foo"].should == nil + end + it "raises TypeError if the argument is not a String and does not respond to #to_str" do -> { ENV.delete(Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String") end diff --git a/spec/ruby/core/env/dup_spec.rb b/spec/ruby/core/env/dup_spec.rb index 46d125aca8..ac66b455cd 100644 --- a/spec/ruby/core/env/dup_spec.rb +++ b/spec/ruby/core/env/dup_spec.rb @@ -1,11 +1,9 @@ require_relative '../../spec_helper' describe "ENV#dup" do - ruby_version_is "3.1" do - it "raises TypeError" do - -> { - ENV.dup - }.should raise_error(TypeError, /Cannot dup ENV, use ENV.to_h to get a copy of ENV as a hash/) - end + it "raises TypeError" do + -> { + ENV.dup + }.should raise_error(TypeError, /Cannot dup ENV, use ENV.to_h to get a copy of ENV as a hash/) end end diff --git a/spec/ruby/core/env/element_reference_spec.rb b/spec/ruby/core/env/element_reference_spec.rb index 560c127a9c..66a9bc9690 100644 --- a/spec/ruby/core/env/element_reference_spec.rb +++ b/spec/ruby/core/env/element_reference_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/common' diff --git a/spec/ruby/core/env/fetch_spec.rb b/spec/ruby/core/env/fetch_spec.rb index b2e7a88cab..2c5d7cc3a0 100644 --- a/spec/ruby/core/env/fetch_spec.rb +++ b/spec/ruby/core/env/fetch_spec.rb @@ -47,7 +47,7 @@ describe "ENV.fetch" do it "warns on block and default parameter given" do -> do - ENV.fetch("foo", "default") { "bar" }.should == "bar" + ENV.fetch("foo", "default") { "bar" }.should == "bar" end.should complain(/block supersedes default value argument/) end diff --git a/spec/ruby/core/env/inspect_spec.rb b/spec/ruby/core/env/inspect_spec.rb index 3c611c24a1..7dd92b120f 100644 --- a/spec/ruby/core/env/inspect_spec.rb +++ b/spec/ruby/core/env/inspect_spec.rb @@ -4,7 +4,7 @@ describe "ENV.inspect" do it "returns a String that looks like a Hash with real data" do ENV["foo"] = "bar" - ENV.inspect.should =~ /\{.*"foo"=>"bar".*\}/ + ENV.inspect.should =~ /\{.*"foo" *=> *"bar".*\}/ ENV.delete "foo" end diff --git a/spec/ruby/core/env/key_spec.rb b/spec/ruby/core/env/key_spec.rb index 82cfbefa39..cf70286409 100644 --- a/spec/ruby/core/env/key_spec.rb +++ b/spec/ruby/core/env/key_spec.rb @@ -1,11 +1,39 @@ require_relative '../../spec_helper' require_relative 'shared/include' -require_relative 'shared/key' describe "ENV.key?" do it_behaves_like :env_include, :key? end describe "ENV.key" do - it_behaves_like :env_key, :key + before :each do + @saved_foo = ENV["foo"] + end + + after :each do + ENV["foo"] = @saved_foo + end + + it "returns the index associated with the passed value" do + ENV["foo"] = "bar" + ENV.key("bar").should == "foo" + end + + it "returns nil if the passed value is not found" do + ENV.delete("foo") + ENV.key("foo").should be_nil + end + + it "coerces the key element with #to_str" do + ENV["foo"] = "bar" + k = mock('key') + k.should_receive(:to_str).and_return("bar") + ENV.key(k).should == "foo" + end + + it "raises TypeError if the argument is not a String and does not respond to #to_str" do + -> { + ENV.key(Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end end diff --git a/spec/ruby/core/env/length_spec.rb b/spec/ruby/core/env/length_spec.rb index 536af9edf5..c6f9062892 100644 --- a/spec/ruby/core/env/length_spec.rb +++ b/spec/ruby/core/env/length_spec.rb @@ -2,5 +2,5 @@ require_relative '../../spec_helper' require_relative 'shared/length' describe "ENV.length" do - it_behaves_like :env_length, :length + it_behaves_like :env_length, :length end diff --git a/spec/ruby/core/env/shared/include.rb b/spec/ruby/core/env/shared/include.rb index 3efcd523d6..70aa555301 100644 --- a/spec/ruby/core/env/shared/include.rb +++ b/spec/ruby/core/env/shared/include.rb @@ -17,6 +17,13 @@ describe :env_include, shared: true do ENV.send(@method, "foo").should == false end + it "coerces the key with #to_str" do + ENV["foo"] = "bar" + k = mock('key') + k.should_receive(:to_str).and_return("foo") + ENV.send(@method, k).should == true + end + it "raises TypeError if the argument is not a String and does not respond to #to_str" do -> { ENV.send(@method, Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String") end diff --git a/spec/ruby/core/env/shared/key.rb b/spec/ruby/core/env/shared/key.rb deleted file mode 100644 index 93396d2aca..0000000000 --- a/spec/ruby/core/env/shared/key.rb +++ /dev/null @@ -1,31 +0,0 @@ -describe :env_key, shared: true do - before :each do - @saved_foo = ENV["foo"] - end - - after :each do - ENV["foo"] = @saved_foo - end - - it "returns the index associated with the passed value" do - ENV["foo"] = "bar" - suppress_warning { - ENV.send(@method, "bar").should == "foo" - } - end - - it "returns nil if the passed value is not found" do - ENV.delete("foo") - suppress_warning { - ENV.send(@method, "foo").should be_nil - } - end - - it "raises TypeError if the argument is not a String and does not respond to #to_str" do - -> { - suppress_warning { - ENV.send(@method, Object.new) - } - }.should raise_error(TypeError, "no implicit conversion of Object into String") - end -end diff --git a/spec/ruby/core/env/shared/update.rb b/spec/ruby/core/env/shared/update.rb index 7d4799955b..e1b1c9c290 100644 --- a/spec/ruby/core/env/shared/update.rb +++ b/spec/ruby/core/env/shared/update.rb @@ -15,12 +15,10 @@ describe :env_update, shared: true do ENV["bar"].should == "1" end - ruby_version_is "3.2" do - it "adds the multiple parameter hashes to ENV, returning ENV" do - ENV.send(@method, {"foo" => "multi1"}, {"bar" => "multi2"}).should equal(ENV) - ENV["foo"].should == "multi1" - ENV["bar"].should == "multi2" - end + it "adds the multiple parameter hashes to ENV, returning ENV" do + ENV.send(@method, {"foo" => "multi1"}, {"bar" => "multi2"}).should equal(ENV) + ENV["foo"].should == "multi1" + ENV["bar"].should == "multi2" end it "returns ENV when no block given" do diff --git a/spec/ruby/core/env/shared/value.rb b/spec/ruby/core/env/shared/value.rb index bef96b5fef..c2b5025465 100644 --- a/spec/ruby/core/env/shared/value.rb +++ b/spec/ruby/core/env/shared/value.rb @@ -16,6 +16,13 @@ describe :env_value, shared: true do ENV.send(@method, "foo").should == false end + it "coerces the value element with #to_str" do + ENV["foo"] = "bar" + v = mock('value') + v.should_receive(:to_str).and_return("bar") + ENV.send(@method, v).should == true + end + it "returns nil if the argument is not a String and does not respond to #to_str" do ENV.send(@method, Object.new).should == nil end diff --git a/spec/ruby/core/env/size_spec.rb b/spec/ruby/core/env/size_spec.rb index f050e9e5a9..7c8072481e 100644 --- a/spec/ruby/core/env/size_spec.rb +++ b/spec/ruby/core/env/size_spec.rb @@ -2,5 +2,5 @@ require_relative '../../spec_helper' require_relative 'shared/length' describe "ENV.size" do - it_behaves_like :env_length, :size + it_behaves_like :env_length, :size end diff --git a/spec/ruby/core/env/slice_spec.rb b/spec/ruby/core/env/slice_spec.rb index e3b6020391..959239d2b2 100644 --- a/spec/ruby/core/env/slice_spec.rb +++ b/spec/ruby/core/env/slice_spec.rb @@ -21,6 +21,16 @@ describe "ENV.slice" do ENV.slice("foo", "boo", "bar").should == {"foo" => "0", "bar" => "1"} end + it "returns the values for the keys coerced with #to_str, but keeps the original objects as result keys" do + foo = mock('key 1') + foo.should_receive(:to_str).and_return("foo") + boo = mock('key 2') + boo.should_receive(:to_str).and_return("boo") + bar = mock('key 3') + bar.should_receive(:to_str).and_return("bar") + ENV.slice(foo, boo, bar).should == {foo => "0", bar => "1"} + end + it "raises TypeError if any argument is not a String and does not respond to #to_str" do -> { ENV.slice(Object.new) }.should raise_error(TypeError, "no implicit conversion of Object into String") end diff --git a/spec/ruby/core/env/to_h_spec.rb b/spec/ruby/core/env/to_h_spec.rb index 3c4a92aa57..58ea2d2030 100644 --- a/spec/ruby/core/env/to_h_spec.rb +++ b/spec/ruby/core/env/to_h_spec.rb @@ -18,6 +18,18 @@ describe "ENV.to_h" do ENV.to_h { |k, v| [k, v.upcase] }.should == { 'a' => "B", 'c' => "D" } end + it "passes to a block each pair's key and value as separate arguments" do + ENV.replace("a" => "b", "c" => "d") + + ScratchPad.record [] + ENV.to_h { |k, v| ScratchPad << [k, v]; [k, v] } + ScratchPad.recorded.sort.should == [["a", "b"], ["c", "d"]] + + ScratchPad.record [] + ENV.to_h { |*args| ScratchPad << args; [args[0], args[1]] } + ScratchPad.recorded.sort.should == [["a", "b"], ["c", "d"]] + end + it "does not require the array elements to be strings" do ENV.replace("a" => "b", "c" => "d") ENV.to_h { |k, v| [k.to_sym, v.to_sym] }.should == { :a => :b, :c => :d } diff --git a/spec/ruby/core/exception/backtrace_spec.rb b/spec/ruby/core/exception/backtrace_spec.rb index 3f74c4cefe..9a65ea3820 100644 --- a/spec/ruby/core/exception/backtrace_spec.rb +++ b/spec/ruby/core/exception/backtrace_spec.rb @@ -27,7 +27,7 @@ describe "Exception#backtrace" do end it "includes the name of the method from where self raised in the first element" do - @backtrace.first.should =~ /in `backtrace'/ + @backtrace.first.should =~ /in [`'](?:ExceptionSpecs::Backtrace\.)?backtrace'/ end it "includes the filename of the location immediately prior to where self raised in the second element" do @@ -38,12 +38,25 @@ describe "Exception#backtrace" do @backtrace[1].should =~ /:6(:in )?/ end - it "contains lines of the same format for each prior position in the stack" do - @backtrace[2..-1].each do |line| - # This regexp is deliberately imprecise to account for the need to abstract out - # the paths of the included mspec files and the desire to avoid specifying in any - # detail what the in `...' portion looks like. - line.should =~ /^.+:\d+:in `[^`]+'$/ + ruby_version_is ""..."3.4" do + it "contains lines of the same format for each prior position in the stack" do + @backtrace[2..-1].each do |line| + # This regexp is deliberately imprecise to account for the need to abstract out + # the paths of the included mspec files and the desire to avoid specifying in any + # detail what the in `...' portion looks like. + line.should =~ /^.+:\d+:in `[^`]+'$/ + end + end + end + + ruby_version_is "3.4" do + it "contains lines of the same format for each prior position in the stack" do + @backtrace[2..-1].each do |line| + # This regexp is deliberately imprecise to account for the need to abstract out + # the paths of the included mspec files and the desire to avoid specifying in any + # detail what the in '...' portion looks like. + line.should =~ /^.+:\d+:in '[^`]+'$/ + end end end diff --git a/spec/ruby/core/exception/detailed_message_spec.rb b/spec/ruby/core/exception/detailed_message_spec.rb index bd85927dbe..9df164a1cf 100644 --- a/spec/ruby/core/exception/detailed_message_spec.rb +++ b/spec/ruby/core/exception/detailed_message_spec.rb @@ -2,34 +2,49 @@ require_relative '../../spec_helper' require_relative 'fixtures/common' describe "Exception#detailed_message" do - ruby_version_is "3.2" do - it "returns decorated message" do - RuntimeError.new("new error").detailed_message.should == "new error (RuntimeError)" - end + it "returns decorated message" do + RuntimeError.new("new error").detailed_message.should == "new error (RuntimeError)" + end - it "accepts highlight keyword argument and adds escape control sequences" do - RuntimeError.new("new error").detailed_message(highlight: true).should == "\e[1mnew error (\e[1;4mRuntimeError\e[m\e[1m)\e[m" + it "is called by #full_message to allow message customization" do + exception = Exception.new("new error") + def exception.detailed_message(**) + "<prefix>#{message}<suffix>" end + exception.full_message(highlight: false).should.include? "<prefix>new error<suffix>" + end - it "allows and ignores other keyword arguments" do - RuntimeError.new("new error").detailed_message(foo: true).should == "new error (RuntimeError)" - end + it "returns just a message if exception class is anonymous" do + Class.new(RuntimeError).new("message").detailed_message.should == "message" + end - it "returns just a message if exception class is anonymous" do - Class.new(RuntimeError).new("message").detailed_message.should == "message" - end + it "returns 'unhandled exception' for an instance of RuntimeError with empty message" do + RuntimeError.new("").detailed_message.should == "unhandled exception" + end - it "returns 'unhandled exception' for an instance of RuntimeError with empty message" do - RuntimeError.new("").detailed_message.should == "unhandled exception" - end + it "returns just class name for an instance other than RuntimeError with empty message" do + DetailedMessageSpec::C.new("").detailed_message.should == "DetailedMessageSpec::C" + StandardError.new("").detailed_message.should == "StandardError" + end - it "returns just class name for an instance of RuntimeError sublass with empty message" do - DetailedMessageSpec::C.new("").detailed_message.should == "DetailedMessageSpec::C" - end + it "returns a generated class name for an instance of RuntimeError anonymous subclass with empty message" do + klass = Class.new(RuntimeError) + klass.new("").detailed_message.should =~ /\A#<Class:0x\h+>\z/ + end - it "returns a generated class name for an instance of RuntimeError anonymous subclass with empty message" do - klass = Class.new(RuntimeError) - klass.new("").detailed_message.should =~ /\A#<Class:0x\h+>\z/ - end + it "accepts highlight keyword argument and adds escape control sequences" do + RuntimeError.new("new error").detailed_message(highlight: true).should == "\e[1mnew error (\e[1;4mRuntimeError\e[m\e[1m)\e[m" + end + + it "accepts highlight keyword argument and adds escape control sequences for an instance of RuntimeError with empty message" do + RuntimeError.new("").detailed_message(highlight: true).should == "\e[1;4munhandled exception\e[m" + end + + it "accepts highlight keyword argument and adds escape control sequences for an instance other than RuntimeError with empty message" do + StandardError.new("").detailed_message(highlight: true).should == "\e[1;4mStandardError\e[m" + end + + it "allows and ignores other keyword arguments" do + RuntimeError.new("new error").detailed_message(foo: true).should == "new error (RuntimeError)" end end diff --git a/spec/ruby/core/exception/equal_value_spec.rb b/spec/ruby/core/exception/equal_value_spec.rb index 7f2065511a..e8f3ce0f8d 100644 --- a/spec/ruby/core/exception/equal_value_spec.rb +++ b/spec/ruby/core/exception/equal_value_spec.rb @@ -22,18 +22,18 @@ describe "Exception#==" do it "returns true if both exceptions have the same class, the same message, and the same backtrace" do one = TypeError.new("message") - one.set_backtrace [File.dirname(__FILE__)] + one.set_backtrace [__dir__] two = TypeError.new("message") - two.set_backtrace [File.dirname(__FILE__)] + two.set_backtrace [__dir__] one.should == two end it "returns false if the two exceptions inherit from Exception but have different classes" do one = RuntimeError.new("message") - one.set_backtrace [File.dirname(__FILE__)] + one.set_backtrace [__dir__] one.should be_kind_of(Exception) two = TypeError.new("message") - two.set_backtrace [File.dirname(__FILE__)] + two.set_backtrace [__dir__] two.should be_kind_of(Exception) one.should_not == two end @@ -52,7 +52,7 @@ describe "Exception#==" do it "returns false if the two exceptions differ only in their backtrace" do one = RuntimeError.new("message") - one.set_backtrace [File.dirname(__FILE__)] + one.set_backtrace [__dir__] two = RuntimeError.new("message") two.set_backtrace nil one.should_not == two @@ -60,9 +60,9 @@ describe "Exception#==" do it "returns false if the two exceptions differ only in their message" do one = RuntimeError.new("message") - one.set_backtrace [File.dirname(__FILE__)] + one.set_backtrace [__dir__] two = RuntimeError.new("message2") - two.set_backtrace [File.dirname(__FILE__)] + two.set_backtrace [__dir__] one.should_not == two end end diff --git a/spec/ruby/core/exception/errno_spec.rb b/spec/ruby/core/exception/errno_spec.rb index a063e522ea..1ab4277700 100644 --- a/spec/ruby/core/exception/errno_spec.rb +++ b/spec/ruby/core/exception/errno_spec.rb @@ -29,6 +29,8 @@ describe "Errno::EMFILE" do ExceptionSpecs::EMFILESub = Class.new(Errno::EMFILE) exc = ExceptionSpecs::EMFILESub.new exc.should be_an_instance_of(ExceptionSpecs::EMFILESub) + ensure + ExceptionSpecs.send(:remove_const, :EMFILESub) end end diff --git a/spec/ruby/core/exception/fixtures/common.rb b/spec/ruby/core/exception/fixtures/common.rb index 1e243098bd..3d8a3c3430 100644 --- a/spec/ruby/core/exception/fixtures/common.rb +++ b/spec/ruby/core/exception/fixtures/common.rb @@ -84,6 +84,9 @@ module NoMethodErrorSpecs class InstanceException < Exception end + + class AClass; end + module AModule; end end class NameErrorSpecs diff --git a/spec/ruby/core/exception/fixtures/syntax_error.rb b/spec/ruby/core/exception/fixtures/syntax_error.rb new file mode 100644 index 0000000000..ccec62f7a1 --- /dev/null +++ b/spec/ruby/core/exception/fixtures/syntax_error.rb @@ -0,0 +1,3 @@ +# rubocop:disable Lint/Syntax +1+1=2 +# rubocop:enable Lint/Syntax diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb index 2efdc239d8..af2e925661 100644 --- a/spec/ruby/core/exception/frozen_error_spec.rb +++ b/spec/ruby/core/exception/frozen_error_spec.rb @@ -20,3 +20,35 @@ describe "FrozenError#receiver" do end end end + +describe "FrozenError#message" do + it "includes a receiver" do + object = Object.new + object.freeze + + msg_class = ruby_version_is("4.0") ? "Object" : "object" + + -> { + def object.x; end + }.should raise_error(FrozenError, "can't modify frozen #{msg_class}: #{object}") + + object = [].freeze + -> { object << nil }.should raise_error(FrozenError, "can't modify frozen Array: []") + end +end + +describe "Modifying a frozen object" do + context "#inspect is redefined and modifies the object" do + it "returns ... instead of String representation of object" do + object = Object.new + def object.inspect; @a = 1 end + def object.modify; @a = 2 end + + object.freeze + + # CRuby's message contains multiple whitespaces before '...'. + # So handle both multiple and single whitespace. + -> { object.modify }.should raise_error(FrozenError, /can't modify frozen .*?: \s*.../) + end + end +end diff --git a/spec/ruby/core/exception/full_message_spec.rb b/spec/ruby/core/exception/full_message_spec.rb index ee66582022..0761d2b40c 100644 --- a/spec/ruby/core/exception/full_message_spec.rb +++ b/spec/ruby/core/exception/full_message_spec.rb @@ -42,16 +42,77 @@ describe "Exception#full_message" do e = RuntimeError.new("Some runtime error") e.backtrace.should == nil full_message = e.full_message(highlight: false, order: :top).lines - full_message[0].should.start_with?("#{__FILE__}:#{__LINE__-1}:in `") + full_message[0].should.start_with?("#{__FILE__}:#{__LINE__-1}:in ") full_message[0].should.end_with?("': Some runtime error (RuntimeError)\n") end + describe "includes details about whether an exception was handled" do + describe "RuntimeError" do + it "should report as unhandled if message is empty" do + err = RuntimeError.new("") + + err.full_message.should =~ /unhandled exception/ + err.full_message(highlight: true).should =~ /unhandled exception/ + err.full_message(highlight: false).should =~ /unhandled exception/ + end + + it "should not report as unhandled if the message is not empty" do + err = RuntimeError.new("non-empty") + + err.full_message.should !~ /unhandled exception/ + err.full_message(highlight: true).should !~ /unhandled exception/ + err.full_message(highlight: false).should !~ /unhandled exception/ + end + + it "should not report as unhandled if the message is nil" do + err = RuntimeError.new(nil) + + err.full_message.should !~ /unhandled exception/ + err.full_message(highlight: true).should !~ /unhandled exception/ + err.full_message(highlight: false).should !~ /unhandled exception/ + end + + it "should not report as unhandled if the message is not specified" do + err = RuntimeError.new() + + err.full_message.should !~ /unhandled exception/ + err.full_message(highlight: true).should !~ /unhandled exception/ + err.full_message(highlight: false).should !~ /unhandled exception/ + end + + it "adds escape sequences to highlight some strings if the message is not specified and :highlight option is specified" do + e = RuntimeError.new("") + + full_message = e.full_message(highlight: true, order: :top).lines + full_message[0].should.end_with? "\e[1;4munhandled exception\e[m\n" + + full_message = e.full_message(highlight: true, order: :bottom).lines + full_message[0].should == "\e[1mTraceback\e[m (most recent call last):\n" + full_message[-1].should.end_with? "\e[1;4munhandled exception\e[m\n" + + full_message = e.full_message(highlight: false, order: :top).lines + full_message[0].should.end_with? "unhandled exception\n" + + full_message = e.full_message(highlight: false, order: :bottom).lines + full_message[0].should == "Traceback (most recent call last):\n" + full_message[-1].should.end_with? "unhandled exception\n" + end + end + + describe "generic Error" do + it "should not report as unhandled in any event" do + StandardError.new("").full_message.should !~ /unhandled exception/ + StandardError.new("non-empty").full_message.should !~ /unhandled exception/ + end + end + end + it "shows the exception class at the end of the first line of the message when the message contains multiple lines" do begin line = __LINE__; raise "first line\nsecond line" rescue => e full_message = e.full_message(highlight: false, order: :top).lines - full_message[0].should.start_with?("#{__FILE__}:#{line}:in `") + full_message[0].should.start_with?("#{__FILE__}:#{line}:in ") full_message[0].should.end_with?(": first line (RuntimeError)\n") full_message[1].should == "second line\n" end @@ -62,7 +123,7 @@ describe "Exception#full_message" do line = __LINE__; raise "first line\nsecond line\nthird line" rescue => e full_message = e.full_message(highlight: true, order: :top).lines - full_message[0].should.start_with?("#{__FILE__}:#{line}:in `") + full_message[0].should.start_with?("#{__FILE__}:#{line}:in ") full_message[0].should.end_with?(": \e[1mfirst line (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n") full_message[1].should == "\e[1msecond line\e[m\n" full_message[2].should == "\e[1mthird line\e[m\n" @@ -104,24 +165,62 @@ describe "Exception#full_message" do exception.full_message.should include "origin exception" end - ruby_version_is "3.2" do - it "relies on #detailed_message" do - e = RuntimeError.new("new error") - e.define_singleton_method(:detailed_message) { |**opt| "DETAILED MESSAGE" } + it "relies on #detailed_message" do + e = RuntimeError.new("new error") + e.define_singleton_method(:detailed_message) { |**| "DETAILED MESSAGE" } + + e.full_message.lines.first.should =~ /DETAILED MESSAGE/ + end - e.full_message.lines.first.should =~ /DETAILED MESSAGE/ + it "passes all its own keyword arguments (with :highlight default value and without :order default value) to #detailed_message" do + e = RuntimeError.new("new error") + options_passed = nil + e.define_singleton_method(:detailed_message) do |**options| + options_passed = options + "DETAILED MESSAGE" end - it "passes all its own keyword arguments to #detailed_message" do - e = RuntimeError.new("new error") - opt_ = nil - e.define_singleton_method(:detailed_message) do |**opt| - opt_ = opt - "DETAILED MESSAGE" - end + e.full_message(foo: "bar") + options_passed.should == { foo: "bar", highlight: Exception.to_tty? } + end + + it "converts #detailed_message returned value to String if it isn't a String" do + message = Object.new + def message.to_str; "DETAILED MESSAGE"; end + + e = RuntimeError.new("new error") + e.define_singleton_method(:detailed_message) { |**| message } + + e.full_message.lines.first.should =~ /DETAILED MESSAGE/ + end + + it "uses class name if #detailed_message returns nil" do + e = RuntimeError.new("new error") + e.define_singleton_method(:detailed_message) { |**| nil } + + e.full_message(highlight: false).lines.first.should =~ /RuntimeError/ + e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/ + end - e.full_message(foo: "bar") - opt_.should == { foo: "bar", highlight: Exception.to_tty? } + it "uses class name if exception object doesn't respond to #detailed_message" do + e = RuntimeError.new("new error") + class << e + undef :detailed_message end + + e.full_message(highlight: false).lines.first.should =~ /RuntimeError/ + e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/ + end + + it "allows cause with empty backtrace" do + begin + raise RuntimeError.new("Some runtime error"), cause: RuntimeError.new("Some other runtime error") + rescue => e + end + + full_message = e.full_message + full_message.should include "RuntimeError" + full_message.should include "Some runtime error" + full_message.should include "Some other runtime error" end end diff --git a/spec/ruby/core/exception/interrupt_spec.rb b/spec/ruby/core/exception/interrupt_spec.rb index 299b5b81f3..90d261e470 100644 --- a/spec/ruby/core/exception/interrupt_spec.rb +++ b/spec/ruby/core/exception/interrupt_spec.rb @@ -54,7 +54,7 @@ describe "Interrupt" do err = IO.popen([*ruby_exe, '-e', 'Process.kill :INT, Process.pid; sleep'], err: [:child, :out], &:read) $?.termsig.should == Signal.list.fetch('INT') err.should.include? ': Interrupt' - err.should.include? "from -e:1:in `<main>'" + err.should =~ /from -e:1:in [`']<main>'/ end end end diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb index 4621e36f44..772c569f67 100644 --- a/spec/ruby/core/exception/no_method_error_spec.rb +++ b/spec/ruby/core/exception/no_method_error_spec.rb @@ -62,12 +62,12 @@ describe "NoMethodError#message" do NoMethodErrorSpecs::NoMethodErrorC.new.a_private_method rescue Exception => e e.should be_kind_of(NoMethodError) - e.message.lines[0].should =~ /private method `a_private_method' called for / + e.message.lines[0].should =~ /private method [`']a_private_method' called for / end end ruby_version_is ""..."3.3" do - it "calls receiver.inspect only when calling Exception#message" do + it "calls #inspect when calling Exception#message" do ScratchPad.record [] test_class = Class.new do def inspect @@ -76,19 +76,163 @@ describe "NoMethodError#message" do end end instance = test_class.new + begin instance.bar - rescue Exception => e - e.name.should == :bar - ScratchPad.recorded.should == [] - e.message.should =~ /undefined method.+\bbar\b/ + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for <inspect>:#<Class:0x\h+>$/ ScratchPad.recorded.should == [:inspect_called] end end + + it "fallbacks to a simpler representation of the receiver when receiver.inspect raises an exception" do + test_class = Class.new do + def inspect + raise NoMethodErrorSpecs::InstanceException + end + end + instance = test_class.new + + begin + instance.bar + rescue NoMethodError => error + message = error.message + message.should =~ /undefined method.+\bbar\b/ + message.should include test_class.inspect + end + end + + it "uses #name to display the receiver if it is a class" do + klass = Class.new { def self.name; "MyClass"; end } + + begin + klass.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for MyClass:Class$/ + end + end + + it "uses #name to display the receiver if it is a module" do + mod = Module.new { def self.name; "MyModule"; end } + + begin + mod.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for MyModule:Module$/ + end + end end ruby_version_is "3.3" do - it "does not call receiver.inspect even when calling Exception#message" do + it "uses a literal name when receiver is nil" do + begin + nil.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for nil\Z/ + end + end + + it "uses a literal name when receiver is true" do + begin + true.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for true\Z/ + end + end + + it "uses a literal name when receiver is false" do + begin + false.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for false\Z/ + end + end + + it "uses #name when receiver is a class" do + klass = Class.new { def self.name; "MyClass"; end } + + begin + klass.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class MyClass\Z/ + end + end + + it "uses class' string representation when receiver is an anonymous class" do + klass = Class.new + + begin + klass.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class #<Class:0x\h+>\Z/ + end + end + + it "uses class' string representation when receiver is a singleton class" do + obj = Object.new + singleton_class = obj.singleton_class + + begin + singleton_class.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for class #<Class:#<Object:0x\h+>>\Z/ + end + end + + it "uses #name when receiver is a module" do + mod = Module.new { def self.name; "MyModule"; end } + + begin + mod.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for module MyModule\Z/ + end + end + + it "uses module's string representation when receiver is an anonymous module" do + m = Module.new + + begin + m.foo + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for module #<Module:0x\h+>\Z/ + end + end + + it "uses class #name when receiver is an ordinary object" do + klass = Class.new { def self.name; "MyClass"; end } + instance = klass.new + + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of MyClass\Z/ + end + end + + it "uses class string representation when receiver is an instance of anonymous class" do + klass = Class.new + instance = klass.new + + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of #<Class:0x\h+>\Z/ + end + end + + it "uses class name when receiver has a singleton class" do + instance = NoMethodErrorSpecs::NoMethodErrorA.new + def instance.foo; end + + begin + instance.bar + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for #<NoMethodErrorSpecs::NoMethodErrorA:0x\h+>\Z/ + end + end + + it "does not call #inspect when calling Exception#message" do ScratchPad.record [] test_class = Class.new do def inspect @@ -97,47 +241,29 @@ describe "NoMethodError#message" do end end instance = test_class.new + begin instance.bar - rescue Exception => e - e.name.should == :bar - ScratchPad.recorded.should == [] - e.message.should =~ /undefined method.+\bbar\b/ + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']bar' for an instance of #<Class:0x\h+>\Z/ ScratchPad.recorded.should == [] end end - end - it "fallbacks to a simpler representation of the receiver when receiver.inspect raises an exception" do - test_class = Class.new do - def inspect - raise NoMethodErrorSpecs::InstanceException - end - end - instance = test_class.new - begin - instance.bar - rescue Exception => e - e.name.should == :bar - message = e.message - message.should =~ /undefined method.+\bbar\b/ - message.should include test_class.inspect - end - end + it "does not truncate long class names" do + class_name = 'ExceptionSpecs::A' + 'a'*100 - it "uses #name to display the receiver if it is a class or a module" do - klass = Class.new { def self.name; "MyClass"; end } - begin - klass.foo - rescue NoMethodError => error - error.message.lines.first.chomp.should =~ /^undefined method `foo' for / - end + begin + eval <<~RUBY + class #{class_name} + end - mod = Module.new { def self.name; "MyModule"; end } - begin - mod.foo - rescue NoMethodError => error - error.message.lines.first.chomp.should =~ /^undefined method `foo' for / + obj = #{class_name}.new + obj.foo + RUBY + rescue NoMethodError => error + error.message.should =~ /\Aundefined method [`']foo' for an instance of #{class_name}\Z/ + end end end end diff --git a/spec/ruby/core/exception/set_backtrace_spec.rb b/spec/ruby/core/exception/set_backtrace_spec.rb index ba2e1bf7aa..2cd93326ec 100644 --- a/spec/ruby/core/exception/set_backtrace_spec.rb +++ b/spec/ruby/core/exception/set_backtrace_spec.rb @@ -1,56 +1,23 @@ require_relative '../../spec_helper' require_relative 'fixtures/common' +require_relative 'shared/set_backtrace' describe "Exception#set_backtrace" do - it "accepts an Array of Strings" do - err = RuntimeError.new - err.set_backtrace ["unhappy"] - err.backtrace.should == ["unhappy"] - end - it "allows the user to set the backtrace from a rescued exception" do bt = ExceptionSpecs::Backtrace.backtrace err = RuntimeError.new + err.backtrace.should == nil + err.backtrace_locations.should == nil err.set_backtrace bt - err.backtrace.should == bt - end - - it "accepts an empty Array" do - err = RuntimeError.new - err.set_backtrace [] - err.backtrace.should == [] - end - - it "accepts a String" do - err = RuntimeError.new - err.set_backtrace "unhappy" - err.backtrace.should == ["unhappy"] - end - it "accepts nil" do - err = RuntimeError.new - err.set_backtrace nil - err.backtrace.should be_nil - end - - it "raises a TypeError when passed a Symbol" do - err = RuntimeError.new - -> { err.set_backtrace :unhappy }.should raise_error(TypeError) + err.backtrace.should == bt + err.backtrace_locations.should == nil end - it "raises a TypeError when the Array contains a Symbol" do + it_behaves_like :exception_set_backtrace, -> backtrace { err = RuntimeError.new - -> { err.set_backtrace ["String", :unhappy] }.should raise_error(TypeError) - end - - it "raises a TypeError when the array contains nil" do - err = Exception.new - -> { err.set_backtrace ["String", nil] }.should raise_error(TypeError) - end - - it "raises a TypeError when the argument is a nested array" do - err = Exception.new - -> { err.set_backtrace ["String", ["String"]] }.should raise_error(TypeError) - end + err.set_backtrace(backtrace) + err + } end diff --git a/spec/ruby/core/exception/shared/set_backtrace.rb b/spec/ruby/core/exception/shared/set_backtrace.rb new file mode 100644 index 0000000000..c6213b42b4 --- /dev/null +++ b/spec/ruby/core/exception/shared/set_backtrace.rb @@ -0,0 +1,64 @@ +require_relative '../fixtures/common' + +describe :exception_set_backtrace, shared: true do + it "accepts an Array of Strings" do + err = @method.call(["unhappy"]) + err.backtrace.should == ["unhappy"] + end + + it "allows the user to set the backtrace from a rescued exception" do + bt = ExceptionSpecs::Backtrace.backtrace + err = @method.call(bt) + err.backtrace.should == bt + end + + ruby_version_is "3.4" do + it "allows the user to set backtrace locations from a rescued exception" do + bt_locations = ExceptionSpecs::Backtrace.backtrace_locations + err = @method.call(bt_locations) + err.backtrace_locations.size.should == bt_locations.size + err.backtrace_locations.each_with_index do |loc, index| + other_loc = bt_locations[index] + + loc.path.should == other_loc.path + loc.label.should == other_loc.label + loc.base_label.should == other_loc.base_label + loc.lineno.should == other_loc.lineno + loc.absolute_path.should == other_loc.absolute_path + loc.to_s.should == other_loc.to_s + end + err.backtrace.size.should == err.backtrace_locations.size + end + end + + it "accepts an empty Array" do + err = @method.call([]) + err.backtrace.should == [] + end + + it "accepts a String" do + err = @method.call("unhappy") + err.backtrace.should == ["unhappy"] + end + + it "accepts nil" do + err = @method.call(nil) + err.backtrace.should be_nil + end + + it "raises a TypeError when passed a Symbol" do + -> { @method.call(:unhappy) }.should raise_error(TypeError) + end + + it "raises a TypeError when the Array contains a Symbol" do + -> { @method.call(["String", :unhappy]) }.should raise_error(TypeError) + end + + it "raises a TypeError when the array contains nil" do + -> { @method.call(["String", nil]) }.should raise_error(TypeError) + end + + it "raises a TypeError when the argument is a nested array" do + -> { @method.call(["String", ["String"]]) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/exception/syntax_error_spec.rb b/spec/ruby/core/exception/syntax_error_spec.rb new file mode 100644 index 0000000000..4c713a3507 --- /dev/null +++ b/spec/ruby/core/exception/syntax_error_spec.rb @@ -0,0 +1,25 @@ +require_relative '../../spec_helper' + +describe "SyntaxError#path" do + it "returns the file path provided to eval" do + filename = "speccing.rb" + + -> { + eval("if true", TOPLEVEL_BINDING, filename) + }.should raise_error(SyntaxError) { |e| + e.path.should == filename + } + end + + it "returns the file path that raised an exception" do + expected_path = fixture(__FILE__, "syntax_error.rb") + + -> { + require_relative "fixtures/syntax_error" + }.should raise_error(SyntaxError) { |e| e.path.should == expected_path } + end + + it "returns nil when constructed directly" do + SyntaxError.new.path.should == nil + end +end diff --git a/spec/ruby/core/exception/system_call_error_spec.rb b/spec/ruby/core/exception/system_call_error_spec.rb index 73167bc288..4fe51901c8 100644 --- a/spec/ruby/core/exception/system_call_error_spec.rb +++ b/spec/ruby/core/exception/system_call_error_spec.rb @@ -16,6 +16,8 @@ describe "SystemCallError" do exc = ExceptionSpecs::SCESub.new ScratchPad.recorded.should equal(:initialize) exc.should be_an_instance_of(ExceptionSpecs::SCESub) + ensure + ExceptionSpecs.send(:remove_const, :SCESub) end end @@ -25,6 +27,7 @@ describe "SystemCallError.new" do @example_errno_class = Errno::EINVAL @last_known_errno = Errno.constants.size - 1 @unknown_errno = Errno.constants.size + @some_human_readable = /[[:graph:]]+/ end it "requires at least one argument" do @@ -53,6 +56,11 @@ describe "SystemCallError.new" do e.should be_an_instance_of(@example_errno_class) end + it "sets an error message corresponding to an appropriate Errno class" do + e = SystemCallError.new(@example_errno) + e.message.should == 'Invalid argument' + end + it "accepts an optional custom message preceding the errno" do exc = SystemCallError.new("custom message", @example_errno) exc.should be_an_instance_of(@example_errno_class) @@ -81,6 +89,23 @@ describe "SystemCallError.new" do SystemCallError.new('foo', 2.9).should == SystemCallError.new('foo', 2) end + it "treats nil errno as unknown error value" do + SystemCallError.new(nil).should be_an_instance_of(SystemCallError) + end + + it "treats nil custom message as if it is not passed at all" do + exc = SystemCallError.new(nil, @example_errno) + exc.message.should == 'Invalid argument' + end + + it "sets an 'unknown error' message when an unknown error number" do + SystemCallError.new(-1).message.should =~ @some_human_readable + end + + it "adds a custom error message to an 'unknown error' message when an unknown error number and a custom message specified" do + SystemCallError.new("custom message", -1).message.should =~ /#{@some_human_readable}.* - custom message/ + end + it "converts to Integer if errno is a Complex convertible to Integer" do SystemCallError.new('foo', Complex(2.9, 0)).should == SystemCallError.new('foo', 2) end @@ -115,12 +140,7 @@ end describe "SystemCallError#message" do it "returns the default message when no message is given" do - platform_is :aix do - SystemCallError.new(2**28).message.should =~ /Error .*occurred/i - end - platform_is_not :aix do - SystemCallError.new(2**28).message.should =~ /Unknown error/i - end + SystemCallError.new(2**28).message.should =~ @some_human_readable end it "returns the message given as an argument to new" do diff --git a/spec/ruby/core/exception/to_s_spec.rb b/spec/ruby/core/exception/to_s_spec.rb index 4c4c7ab432..65c0d73a98 100644 --- a/spec/ruby/core/exception/to_s_spec.rb +++ b/spec/ruby/core/exception/to_s_spec.rb @@ -23,7 +23,7 @@ describe "NameError#to_s" do begin puts not_defined rescue => exception - exception.message.should =~ /undefined local variable or method `not_defined'/ + exception.message.should =~ /undefined local variable or method [`']not_defined'/ end end diff --git a/spec/ruby/core/exception/top_level_spec.rb b/spec/ruby/core/exception/top_level_spec.rb index bcd09205b6..cc961d06d5 100644 --- a/spec/ruby/core/exception/top_level_spec.rb +++ b/spec/ruby/core/exception/top_level_spec.rb @@ -2,30 +2,38 @@ require_relative '../../spec_helper' describe "An Exception reaching the top level" do it "is printed on STDERR" do - ruby_exe('raise "foo"', args: "2>&1", exit_status: 1).should.include?("in `<main>': foo (RuntimeError)") + ruby_exe('raise "foo"', args: "2>&1", exit_status: 1).should =~ /in [`']<main>': foo \(RuntimeError\)/ end it "the Exception#cause is printed to STDERR with backtraces" do code = <<-RUBY def raise_cause - raise "the cause" + raise "the cause" # 2 end def raise_wrapped - raise "wrapped" + raise "wrapped" # 5 end begin - raise_cause + raise_cause # 8 rescue - raise_wrapped + raise_wrapped # 10 end RUBY lines = ruby_exe(code, args: "2>&1", exit_status: 1).lines - lines.reject! { |l| l.include?('rescue in') } - lines.map! { |l| l.chomp[/:(in.+)/, 1] } - lines.should == ["in `raise_wrapped': wrapped (RuntimeError)", - "in `<main>'", - "in `raise_cause': the cause (RuntimeError)", - "in `<main>'"] + + lines.map! { |l| l.chomp[/:(\d+:in.+)/, 1] } + lines[0].should =~ /\A5:in [`'](?:Object#)?raise_wrapped': wrapped \(RuntimeError\)\z/ + if lines[1].include? 'rescue in' + # CRuby < 3.4 has an extra 'rescue in' backtrace entry + lines[1].should =~ /\A10:in [`']rescue in <main>'\z/ + lines.delete_at 1 + lines[1].should =~ /\A7:in [`']<main>'\z/ + else + lines[1].should =~ /\A10:in [`']<main>'\z/ + end + lines[2].should =~ /\A2:in [`'](?:Object#)?raise_cause': the cause \(RuntimeError\)\z/ + lines[3].should =~ /\A8:in [`']<main>'\z/ + lines.size.should == 4 end describe "with a custom backtrace" do diff --git a/spec/ruby/core/false/singleton_method_spec.rb b/spec/ruby/core/false/singleton_method_spec.rb index 6cf29af411..738794b46c 100644 --- a/spec/ruby/core/false/singleton_method_spec.rb +++ b/spec/ruby/core/false/singleton_method_spec.rb @@ -3,10 +3,10 @@ require_relative '../../spec_helper' describe "FalseClass#singleton_method" do ruby_version_is '3.3' do it "raises regardless of whether FalseClass defines the method" do - proc{false.singleton_method(:foo)}.should raise_error(NameError) + -> { false.singleton_method(:foo) }.should raise_error(NameError) begin - def false.foo; end - proc{false.singleton_method(:foo)}.should raise_error(NameError) + def (false).foo; end + -> { false.singleton_method(:foo) }.should raise_error(NameError) ensure FalseClass.send(:remove_method, :foo) end diff --git a/spec/ruby/core/fiber/alive_spec.rb b/spec/ruby/core/fiber/alive_spec.rb new file mode 100644 index 0000000000..a1df582435 --- /dev/null +++ b/spec/ruby/core/fiber/alive_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../spec_helper' + +describe "Fiber#alive?" do + it "returns true for a Fiber that hasn't had #resume called" do + fiber = Fiber.new { true } + fiber.alive?.should be_true + end + + # FIXME: Better description? + it "returns true for a Fiber that's yielded to the caller" do + fiber = Fiber.new { Fiber.yield } + fiber.resume + fiber.alive?.should be_true + end + + it "returns true when called from its Fiber" do + fiber = Fiber.new { fiber.alive?.should be_true } + fiber.resume + end + + it "doesn't invoke the block associated with the Fiber" do + offthehook = mock('do not call') + offthehook.should_not_receive(:ring) + fiber = Fiber.new { offthehook.ring } + fiber.alive? + end + + it "returns false for a Fiber that's dead" do + fiber = Fiber.new { true } + fiber.resume + -> { fiber.resume }.should raise_error(FiberError) + fiber.alive?.should be_false + end + + it "always returns false for a dead Fiber" do + fiber = Fiber.new { true } + fiber.resume + -> { fiber.resume }.should raise_error(FiberError) + fiber.alive?.should be_false + -> { fiber.resume }.should raise_error(FiberError) + fiber.alive?.should be_false + fiber.alive?.should be_false + end +end diff --git a/spec/ruby/core/fiber/blocking_spec.rb b/spec/ruby/core/fiber/blocking_spec.rb index ebefa116af..d5caf81fbe 100644 --- a/spec/ruby/core/fiber/blocking_spec.rb +++ b/spec/ruby/core/fiber/blocking_spec.rb @@ -1,8 +1,6 @@ require_relative '../../spec_helper' require_relative 'shared/blocking' -require "fiber" - describe "Fiber.blocking?" do it_behaves_like :non_blocking_fiber, -> { Fiber.blocking? } @@ -59,19 +57,17 @@ describe "Fiber#blocking?" do end end -ruby_version_is "3.2" do - describe "Fiber.blocking" do - context "when fiber is non-blocking" do - it "can become blocking" do - fiber = Fiber.new(blocking: false) do - Fiber.blocking do |f| - f.blocking? ? :blocking : :non_blocking - end +describe "Fiber.blocking" do + context "when fiber is non-blocking" do + it "can become blocking" do + fiber = Fiber.new(blocking: false) do + Fiber.blocking do |f| + f.blocking? ? :blocking : :non_blocking end - - blocking = fiber.resume - blocking.should == :blocking end + + blocking = fiber.resume + blocking.should == :blocking end end end diff --git a/spec/ruby/core/fiber/current_spec.rb b/spec/ruby/core/fiber/current_spec.rb new file mode 100644 index 0000000000..b93df77a89 --- /dev/null +++ b/spec/ruby/core/fiber/current_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' + +describe "Fiber.current" do + it "returns the root Fiber when called outside of a Fiber" do + root = Fiber.current + root.should be_an_instance_of(Fiber) + # We can always transfer to the root Fiber; it will never die + 5.times do + root.transfer.should be_nil + root.alive?.should be_true + end + end + + it "returns the current Fiber when called from a Fiber" do + fiber = Fiber.new do + this = Fiber.current + this.should be_an_instance_of(Fiber) + this.should == fiber + this.alive?.should be_true + end + fiber.resume + end + + it "returns the current Fiber when called from a Fiber that transferred to another" do + states = [] + fiber = Fiber.new do + states << :fiber + this = Fiber.current + this.should be_an_instance_of(Fiber) + this.should == fiber + this.alive?.should be_true + end + + fiber2 = Fiber.new do + states << :fiber2 + fiber.transfer + flunk + end + + fiber3 = Fiber.new do + states << :fiber3 + fiber2.transfer + states << :fiber3_terminated + end + + fiber3.resume + + states.should == [:fiber3, :fiber2, :fiber, :fiber3_terminated] + end +end diff --git a/spec/ruby/core/fiber/fixtures/classes.rb b/spec/ruby/core/fiber/fixtures/classes.rb index c00facd6e1..6b0e0fbc42 100644 --- a/spec/ruby/core/fiber/fixtures/classes.rb +++ b/spec/ruby/core/fiber/fixtures/classes.rb @@ -1,10 +1,20 @@ module FiberSpecs class NewFiberToRaise - def self.raise(*args) - fiber = Fiber.new { Fiber.yield } + def self.raise(*args, **kwargs, &block) + fiber = Fiber.new do + if block_given? + block.call do + Fiber.yield + end + else + Fiber.yield + end + end + fiber.resume - fiber.raise(*args) + + fiber.raise(*args, **kwargs) end end diff --git a/spec/ruby/core/fiber/fixtures/scheduler.rb b/spec/ruby/core/fiber/fixtures/scheduler.rb new file mode 100644 index 0000000000..16bd2f6b44 --- /dev/null +++ b/spec/ruby/core/fiber/fixtures/scheduler.rb @@ -0,0 +1,35 @@ +module FiberSpecs + + class LoggingScheduler + attr_reader :events + def initialize + @events = [] + end + + def block(*args) + @events << { event: :block, fiber: Fiber.current, args: args } + Fiber.yield + end + + def io_wait(*args) + @events << { event: :io_wait, fiber: Fiber.current, args: args } + Fiber.yield + end + + def kernel_sleep(*args) + @events << { event: :kernel_sleep, fiber: Fiber.current, args: args } + Fiber.yield + end + + def unblock(*args) + @events << { event: :unblock, fiber: Fiber.current, args: args } + Fiber.yield + end + + def fiber_interrupt(*args) + @events << { event: :fiber_interrupt, fiber: Fiber.current, args: args } + Fiber.yield + end + end + +end diff --git a/spec/ruby/core/fiber/inspect_spec.rb b/spec/ruby/core/fiber/inspect_spec.rb index f20a153fc2..fcfef20716 100644 --- a/spec/ruby/core/fiber/inspect_spec.rb +++ b/spec/ruby/core/fiber/inspect_spec.rb @@ -1,5 +1,4 @@ require_relative '../../spec_helper' -require 'fiber' describe "Fiber#inspect" do describe "status" do diff --git a/spec/ruby/core/fiber/raise_spec.rb b/spec/ruby/core/fiber/raise_spec.rb index eb4b39c8be..896f760290 100644 --- a/spec/ruby/core/fiber/raise_spec.rb +++ b/spec/ruby/core/fiber/raise_spec.rb @@ -4,6 +4,7 @@ require_relative '../../shared/kernel/raise' describe "Fiber#raise" do it_behaves_like :kernel_raise, :raise, FiberSpecs::NewFiberToRaise + it_behaves_like :kernel_raise_across_contexts, :raise, FiberSpecs::NewFiberToRaise end describe "Fiber#raise" do @@ -42,7 +43,7 @@ describe "Fiber#raise" do -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error' }.should raise_error(FiberSpecs::CustomError, 'test error') end - it 'accepts error class with with error message and backtrace information' do + it 'accepts error class with error message and backtrace information' do -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error', ['foo', 'boo'] }.should raise_error(FiberSpecs::CustomError) { |e| @@ -91,12 +92,45 @@ describe "Fiber#raise" do fiber_two.resume.should == [:yield_one, :rescued] end + + ruby_version_is "3.4" do + it "raises on the resumed fiber" do + root_fiber = Fiber.current + f1 = Fiber.new { root_fiber.transfer } + f2 = Fiber.new { f1.resume } + f2.transfer + + -> do + f2.raise(RuntimeError, "Expected error") + end.should raise_error(RuntimeError, "Expected error") + end + + it "raises on itself" do + -> do + Fiber.current.raise(RuntimeError, "Expected error") + end.should raise_error(RuntimeError, "Expected error") + end + + it "should raise on parent fiber" do + f2 = nil + f1 = Fiber.new do + # This is equivalent to Kernel#raise: + f2.raise(RuntimeError, "Expected error") + end + f2 = Fiber.new do + f1.resume + end + + -> do + f2.resume + end.should raise_error(RuntimeError, "Expected error") + end + end end describe "Fiber#raise" do it "transfers and raises on a transferring fiber" do - require "fiber" root = Fiber.current fiber = Fiber.new { root.transfer } fiber.transfer diff --git a/spec/ruby/core/fiber/resume_spec.rb b/spec/ruby/core/fiber/resume_spec.rb index ab9a6799ab..4b20f4b4bf 100644 --- a/spec/ruby/core/fiber/resume_spec.rb +++ b/spec/ruby/core/fiber/resume_spec.rb @@ -1,5 +1,5 @@ require_relative '../../spec_helper' -require_relative '../../shared/fiber/resume' +require_relative 'shared/resume' describe "Fiber#resume" do it_behaves_like :fiber_resume, :resume @@ -67,4 +67,17 @@ describe "Fiber#resume" do ruby_exe(code).should == "ensure executed\n" end + + it "can work with Fiber#transfer" do + fiber1 = Fiber.new { true } + fiber2 = Fiber.new { fiber1.transfer; Fiber.yield 10 ; Fiber.yield 20; raise } + fiber2.resume.should == 10 + fiber2.resume.should == 20 + end + + it "raises a FiberError if the Fiber attempts to resume a resuming fiber" do + root_fiber = Fiber.current + fiber1 = Fiber.new { root_fiber.resume } + -> { fiber1.resume }.should raise_error(FiberError, /attempt to resume a resuming fiber/) + end end diff --git a/spec/ruby/core/fiber/scheduler_spec.rb b/spec/ruby/core/fiber/scheduler_spec.rb new file mode 100644 index 0000000000..15a03c1479 --- /dev/null +++ b/spec/ruby/core/fiber/scheduler_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'shared/scheduler' + +require "fiber" + +describe "Fiber.scheduler" do + it_behaves_like :scheduler, :scheduler +end diff --git a/spec/ruby/core/fiber/set_scheduler_spec.rb b/spec/ruby/core/fiber/set_scheduler_spec.rb new file mode 100644 index 0000000000..82f6acbe86 --- /dev/null +++ b/spec/ruby/core/fiber/set_scheduler_spec.rb @@ -0,0 +1,8 @@ +require_relative '../../spec_helper' +require_relative 'shared/scheduler' + +require "fiber" + +describe "Fiber.scheduler" do + it_behaves_like :scheduler, :set_scheduler +end diff --git a/spec/ruby/core/fiber/shared/resume.rb b/spec/ruby/core/fiber/shared/resume.rb new file mode 100644 index 0000000000..5ee27d1d24 --- /dev/null +++ b/spec/ruby/core/fiber/shared/resume.rb @@ -0,0 +1,58 @@ +describe :fiber_resume, shared: true do + it "can be invoked from the root Fiber" do + fiber = Fiber.new { :fiber } + fiber.send(@method).should == :fiber + end + + it "raises a FiberError if invoked from a different Thread" do + fiber = Fiber.new { 42 } + Thread.new do + -> { + fiber.send(@method) + }.should raise_error(FiberError) + end.join + + # Check the Fiber can still be used + fiber.send(@method).should == 42 + end + + it "passes control to the beginning of the block on first invocation" do + invoked = false + fiber = Fiber.new { invoked = true } + fiber.send(@method) + invoked.should be_true + end + + it "returns the last value encountered on first invocation" do + fiber = Fiber.new { 1+1; true } + fiber.send(@method).should be_true + end + + it "runs until the end of the block" do + obj = mock('obj') + obj.should_receive(:do).once + fiber = Fiber.new { 1 + 2; a = "glark"; obj.do } + fiber.send(@method) + end + + it "accepts any number of arguments" do + fiber = Fiber.new { |a| } + -> { fiber.send(@method, *(1..10).to_a) }.should_not raise_error + end + + it "raises a FiberError if the Fiber is dead" do + fiber = Fiber.new { true } + fiber.send(@method) + -> { fiber.send(@method) }.should raise_error(FiberError) + end + + it "raises a LocalJumpError if the block includes a return statement" do + fiber = Fiber.new { return; } + -> { fiber.send(@method) }.should raise_error(LocalJumpError) + end + + it "raises a LocalJumpError if the block includes a break statement" do + fiber = Fiber.new { break; } + -> { fiber.send(@method) }.should raise_error(LocalJumpError) + end +end diff --git a/spec/ruby/core/fiber/shared/scheduler.rb b/spec/ruby/core/fiber/shared/scheduler.rb new file mode 100644 index 0000000000..19bfb75e3e --- /dev/null +++ b/spec/ruby/core/fiber/shared/scheduler.rb @@ -0,0 +1,51 @@ +describe :scheduler, shared: true do + it "validates the scheduler for required methods" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + required_methods.each do |missing_method| + scheduler = Object.new + required_methods.difference([missing_method]).each do |method| + scheduler.define_singleton_method(method) {} + end + -> { + suppress_warning { Fiber.set_scheduler(scheduler) } + }.should raise_error(ArgumentError, /Scheduler must implement ##{missing_method}/) + end + end + + it "can set and get the scheduler" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + suppress_warning { Fiber.set_scheduler(scheduler) } + Fiber.scheduler.should == scheduler + end + + it "returns the scheduler after setting it" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + result = suppress_warning { Fiber.set_scheduler(scheduler) } + result.should == scheduler + end + + it "can remove the scheduler" do + required_methods = [:block, :unblock, :kernel_sleep, :io_wait] + scheduler = Object.new + required_methods.each do |method| + scheduler.define_singleton_method(method) {} + end + suppress_warning { Fiber.set_scheduler(scheduler) } + Fiber.set_scheduler(nil) + Fiber.scheduler.should be_nil + end + + it "can assign a nil scheduler multiple times" do + Fiber.set_scheduler(nil) + Fiber.set_scheduler(nil) + Fiber.scheduler.should be_nil + end +end diff --git a/spec/ruby/core/fiber/storage_spec.rb b/spec/ruby/core/fiber/storage_spec.rb index e99fe6e4df..015caaf3bb 100644 --- a/spec/ruby/core/fiber/storage_spec.rb +++ b/spec/ruby/core/fiber/storage_spec.rb @@ -1,110 +1,181 @@ require_relative '../../spec_helper' -require 'fiber' - describe "Fiber.new(storage:)" do - ruby_version_is "3.2" do - it "creates a Fiber with the given storage" do - storage = {life: 42} - fiber = Fiber.new(storage: storage) { Fiber.current.storage } - fiber.resume.should == storage - end + it "creates a Fiber with the given storage" do + storage = {life: 42} + fiber = Fiber.new(storage: storage) { Fiber.current.storage } + fiber.resume.should == storage + end - it "creates a fiber with lazily initialized storage" do - Fiber.new(storage: nil) { Fiber[:x] = 10; Fiber.current.storage }.resume.should == {x: 10} + it "creates a fiber with lazily initialized storage" do + Fiber.new(storage: nil) { Fiber[:x] = 10; Fiber.current.storage }.resume.should == {x: 10} + end + + it "creates a fiber by inheriting the storage of the parent fiber" do + fiber = Fiber.new(storage: {life: 42}) do + Fiber.new { Fiber.current.storage }.resume end + fiber.resume.should == {life: 42} + end - it "creates a fiber by inheriting the storage of the parent fiber" do - fiber = Fiber.new(storage: {life: 42}) do - Fiber.new { Fiber.current.storage }.resume - end - fiber.resume.should == {life: 42} + it "cannot create a fiber with non-hash storage" do + -> { Fiber.new(storage: 42) {} }.should raise_error(TypeError) + end + + it "cannot create a fiber with a frozen hash as storage" do + -> { Fiber.new(storage: {life: 43}.freeze) {} }.should raise_error(FrozenError) + end + + it "cannot create a fiber with a storage hash with non-symbol keys" do + -> { Fiber.new(storage: {life: 43, Object.new => 44}) {} }.should raise_error(TypeError) + end +end + +describe "Fiber#storage" do + it "cannot be accessed from a different fiber" do + f = Fiber.new(storage: {life: 42}) { nil } + -> { + f.storage + }.should raise_error(ArgumentError, /Fiber storage can only be accessed from the Fiber it belongs to/) + end +end + +describe "Fiber#storage=" do + it "can clear the storage of the fiber" do + fiber = Fiber.new(storage: {life: 42}) do + Fiber.current.storage = nil + Fiber[:x] = 10 + Fiber.current.storage end + fiber.resume.should == {x: 10} + end - it "cannot create a fiber with non-hash storage" do - -> { Fiber.new(storage: 42) {} }.should raise_error(TypeError) + it "can set the storage of the fiber" do + fiber = Fiber.new(storage: {life: 42}) do + Fiber.current.storage = {life: 43} + Fiber.current.storage end + fiber.resume.should == {life: 43} + end + + it "can't set the storage of the fiber to non-hash" do + -> { Fiber.current.storage = 42 }.should raise_error(TypeError) + end + + it "can't set the storage of the fiber to a frozen hash" do + -> { Fiber.current.storage = {life: 43}.freeze }.should raise_error(FrozenError) + end + + it "can't set the storage of the fiber to a hash with non-symbol keys" do + -> { Fiber.current.storage = {life: 43, Object.new => 44} }.should raise_error(TypeError) end end -describe "Fiber#storage=" do - ruby_version_is "3.2" do - it "can clear the storage of the fiber" do - fiber = Fiber.new(storage: {life: 42}) do - Fiber.current.storage = nil - Fiber[:x] = 10 - Fiber.current.storage - end - fiber.resume.should == {x: 10} +describe "Fiber.[]" do + it "returns the value of the given key in the storage of the current fiber" do + Fiber.new(storage: {life: 42}) { Fiber[:life] }.resume.should == 42 + end + + it "returns nil if the key is not present in the storage of the current fiber" do + Fiber.new(storage: {life: 42}) { Fiber[:death] }.resume.should be_nil + end + + it "returns nil if the current fiber has no storage" do + Fiber.new { Fiber[:life] }.resume.should be_nil + end + + ruby_version_is "3.2.3" do + it "can use dynamically defined keys" do + key = :"#{self.class.name}#.#{self.object_id}" + Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 end - it "can set the storage of the fiber" do - fiber = Fiber.new(storage: {life: 42}) do - Fiber.current.storage = {life: 43} - Fiber.current.storage + it "can't use invalid keys" do + invalid_keys = [Object.new, 12] + invalid_keys.each do |key| + -> { Fiber[key] }.should raise_error(TypeError) end - fiber.resume.should == {life: 43} end + end - it "can't set the storage of the fiber to non-hash" do - -> { Fiber.current.storage = 42 }.should raise_error(TypeError) + ruby_bug "#20978", ""..."3.4" do + it "can use keys as strings" do + key = Object.new + def key.to_str; "Foo"; end + Fiber.new { Fiber[key] = 42; Fiber["Foo"] }.resume.should == 42 end - it "can't set the storage of the fiber to a frozen hash" do - -> { Fiber.current.storage = {life: 43}.freeze }.should raise_error(FrozenError) + it "converts a String key into a Symbol" do + Fiber.new { Fiber["key"] = 42; Fiber[:key] }.resume.should == 42 + Fiber.new { Fiber[:key] = 42; Fiber["key"] }.resume.should == 42 end - it "can't set the storage of the fiber to a hash with non-symbol keys" do - -> { Fiber.current.storage = {life: 43, Object.new => 44} }.should raise_error(TypeError) + it "can use any object that responds to #to_str as a key" do + key = mock("key") + key.should_receive(:to_str).twice.and_return("key") + Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 end end -end -describe "Fiber.[]" do - ruby_version_is "3.2" do - it "returns the value of the given key in the storage of the current fiber" do - Fiber.new(storage: {life: 42}) { Fiber[:life] }.resume.should == 42 - end + it "does not call #to_sym on the key" do + key = mock("key") + key.should_not_receive(:to_sym) + -> { Fiber[key] }.should raise_error(TypeError) + end - it "returns nil if the key is not present in the storage of the current fiber" do - Fiber.new(storage: {life: 42}) { Fiber[:death] }.resume.should be_nil + it "can access the storage of the parent fiber" do + f = Fiber.new(storage: {life: 42}) do + Fiber.new { Fiber[:life] }.resume end + f.resume.should == 42 + end - it "returns nil if the current fiber has no storage" do - Fiber.new { Fiber[:life] }.resume.should be_nil - end + it "can't access the storage of the fiber with non-symbol keys" do + -> { Fiber[Object.new] }.should raise_error(TypeError) end end describe "Fiber.[]=" do - ruby_version_is "3.2" do - it "sets the value of the given key in the storage of the current fiber" do - Fiber.new(storage: {life: 42}) { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43 - end + it "sets the value of the given key in the storage of the current fiber" do + Fiber.new(storage: {life: 42}) { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43 + end - it "sets the value of the given key in the storage of the current fiber" do - Fiber.new(storage: {life: 42}) { Fiber[:death] = 43; Fiber[:death] }.resume.should == 43 - end + it "sets the value of the given key in the storage of the current fiber" do + Fiber.new(storage: {life: 42}) { Fiber[:death] = 43; Fiber[:death] }.resume.should == 43 + end - it "sets the value of the given key in the storage of the current fiber" do - Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43 + it "sets the value of the given key in the storage of the current fiber" do + Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43 + end + + it "does not overwrite the storage of the parent fiber" do + f = Fiber.new(storage: {life: 42}) do + Fiber.yield Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume + Fiber[:life] end + f.resume.should == 43 # Value of the inner fiber + f.resume.should == 42 # Value of the outer fiber + end + + it "can't access the storage of the fiber with non-symbol keys" do + -> { Fiber[Object.new] = 44 }.should raise_error(TypeError) end ruby_version_is "3.3" do it "deletes the fiber storage key when assigning nil" do - Fiber.new(storage: {life: 42}) { Fiber[:life] = nil; Fiber.current.storage }.resume.should == {} + Fiber.new(storage: {life: 42}) { + Fiber[:life] = nil + Fiber.current.storage + }.resume.should == {} end end end describe "Thread.new" do - ruby_version_is "3.2" do - it "creates a thread with the storage of the current fiber" do - fiber = Fiber.new(storage: {life: 42}) do - Thread.new { Fiber.current.storage }.value - end - fiber.resume.should == {life: 42} + it "creates a thread with the storage of the current fiber" do + fiber = Fiber.new(storage: {life: 42}) do + Thread.new { Fiber.current.storage }.value end + fiber.resume.should == {life: 42} end end diff --git a/spec/ruby/core/fiber/transfer_spec.rb b/spec/ruby/core/fiber/transfer_spec.rb new file mode 100644 index 0000000000..238721475d --- /dev/null +++ b/spec/ruby/core/fiber/transfer_spec.rb @@ -0,0 +1,84 @@ +require_relative '../../spec_helper' +require_relative 'shared/resume' + +describe "Fiber#transfer" do + it_behaves_like :fiber_resume, :transfer +end + +describe "Fiber#transfer" do + it "transfers control from one Fiber to another when called from a Fiber" do + fiber1 = Fiber.new { :fiber1 } + fiber2 = Fiber.new { fiber1.transfer; :fiber2 } + fiber2.resume.should == :fiber2 + end + + it "returns to the root Fiber when finished" do + f1 = Fiber.new { :fiber_1 } + f2 = Fiber.new { f1.transfer; :fiber_2 } + + f2.transfer.should == :fiber_1 + f2.transfer.should == :fiber_2 + end + + it "can be invoked from the same Fiber it transfers control to" do + states = [] + fiber = Fiber.new { states << :start; fiber.transfer; states << :end } + fiber.transfer + states.should == [:start, :end] + + states = [] + fiber = Fiber.new { states << :start; fiber.transfer; states << :end } + fiber.resume + states.should == [:start, :end] + end + + it "can not transfer control to a Fiber that has suspended by Fiber.yield" do + states = [] + fiber1 = Fiber.new { states << :fiber1 } + fiber2 = Fiber.new { states << :fiber2_start; Fiber.yield fiber1.transfer; states << :fiber2_end} + fiber2.resume.should == [:fiber2_start, :fiber1] + -> { fiber2.transfer }.should raise_error(FiberError) + end + + it "raises a FiberError when transferring to a Fiber which resumes itself" do + fiber = Fiber.new { fiber.resume } + -> { fiber.transfer }.should raise_error(FiberError) + end + + it "works if Fibers in different Threads each transfer to a Fiber in the same Thread" do + # This catches a bug where Fibers are running on a thread-pool + # and Fibers from a different Ruby Thread reuse the same native thread. + # Caching the Ruby Thread based on the native thread is not correct in that case, + # and the check for "fiber called across threads" in Fiber#transfer + # might be incorrect based on that. + 2.times do + Thread.new do + io_fiber = Fiber.new do |calling_fiber| + calling_fiber.transfer + end + io_fiber.transfer(Fiber.current) + value = Object.new + io_fiber.transfer(value).should equal value + end.join + end + end + + it "transfers control between a non-main thread's root fiber to a child fiber and back again" do + states = [] + thread = Thread.new do + f1 = Fiber.new do |f0| + states << 0 + value2 = f0.transfer(1) + states << value2 + 3 + end + + value1 = f1.transfer(Fiber.current) + states << value1 + value3 = f1.transfer(2) + states << value3 + end + thread.join + states.should == [0, 1, 2, 3] + end +end diff --git a/spec/ruby/core/file/absolute_path_spec.rb b/spec/ruby/core/file/absolute_path_spec.rb index e35c80ec3c..315eead34f 100644 --- a/spec/ruby/core/file/absolute_path_spec.rb +++ b/spec/ruby/core/file/absolute_path_spec.rb @@ -85,7 +85,7 @@ describe "File.absolute_path" do end it "accepts a second argument of a directory from which to resolve the path" do - File.absolute_path(__FILE__, File.dirname(__FILE__)).should == @abs + File.absolute_path(__FILE__, __dir__).should == @abs end it "calls #to_path on its argument" do diff --git a/spec/ruby/core/file/atime_spec.rb b/spec/ruby/core/file/atime_spec.rb index 1b47576e6b..e47e70e5ac 100644 --- a/spec/ruby/core/file/atime_spec.rb +++ b/spec/ruby/core/file/atime_spec.rb @@ -27,6 +27,9 @@ describe "File.atime" do else File.atime(__FILE__).usec.should == 0 end + rescue Errno::ENOENT => e + # Native Windows don't have stat command. + skip e.message end end end diff --git a/spec/ruby/core/file/birthtime_spec.rb b/spec/ruby/core/file/birthtime_spec.rb index 755601df64..f82eaf7cca 100644 --- a/spec/ruby/core/file/birthtime_spec.rb +++ b/spec/ruby/core/file/birthtime_spec.rb @@ -1,60 +1,56 @@ require_relative '../../spec_helper' -describe "File.birthtime" do - before :each do - @file = __FILE__ - end +platform_is :windows, :darwin, :freebsd, :netbsd, :linux do + not_implemented_messages = [ + "birthtime() function is unimplemented", # unsupported OS/version + "birthtime is unimplemented", # unsupported filesystem + ] + + describe "File.birthtime" do + before :each do + @file = __FILE__ + end - after :each do - @file = nil - end + after :each do + @file = nil + end - platform_is :windows, :darwin, :freebsd, :netbsd do it "returns the birth time for the named file as a Time object" do File.birthtime(@file) File.birthtime(@file).should be_kind_of(Time) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end it "accepts an object that has a #to_path method" do + File.birthtime(@file) # Avoid to failure of mock object with old Kernel and glibc File.birthtime(mock_to_path(@file)) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end it "raises an Errno::ENOENT exception if the file is not found" do -> { File.birthtime('bogus') }.should raise_error(Errno::ENOENT) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end end - platform_is :openbsd do - it "raises an NotImplementedError" do - -> { File.birthtime(@file) }.should raise_error(NotImplementedError) + describe "File#birthtime" do + before :each do + @file = File.open(__FILE__) end - end - - # TODO: depends on Linux kernel version -end -describe "File#birthtime" do - before :each do - @file = File.open(__FILE__) - end - - after :each do - @file.close - @file = nil - end + after :each do + @file.close + @file = nil + end - platform_is :windows, :darwin, :freebsd, :netbsd do it "returns the birth time for self" do @file.birthtime @file.birthtime.should be_kind_of(Time) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end end - - platform_is :openbsd do - it "raises an NotImplementedError" do - -> { @file.birthtime }.should raise_error(NotImplementedError) - end - end - - # TODO: depends on Linux kernel version end diff --git a/spec/ruby/core/file/chown_spec.rb b/spec/ruby/core/file/chown_spec.rb index 8cc8f0d04b..4db0e3712c 100644 --- a/spec/ruby/core/file/chown_spec.rb +++ b/spec/ruby/core/file/chown_spec.rb @@ -78,15 +78,15 @@ describe "File.chown" do end describe "File#chown" do - before :each do - @fname = tmp('file_chown_test') - @file = File.open(@fname, 'w') - end + before :each do + @fname = tmp('file_chown_test') + @file = File.open(@fname, 'w') + end - after :each do - @file.close unless @file.closed? - rm_r @fname - end + after :each do + @file.close unless @file.closed? + rm_r @fname + end as_superuser do platform_is :windows do diff --git a/spec/ruby/core/file/constants/constants_spec.rb b/spec/ruby/core/file/constants/constants_spec.rb index 86946822c5..bba248c21e 100644 --- a/spec/ruby/core/file/constants/constants_spec.rb +++ b/spec/ruby/core/file/constants/constants_spec.rb @@ -4,7 +4,7 @@ require_relative '../../../spec_helper' "FNM_DOTMATCH", "FNM_EXTGLOB", "FNM_NOESCAPE", "FNM_PATHNAME", "FNM_SYSCASE", "LOCK_EX", "LOCK_NB", "LOCK_SH", "LOCK_UN", "NONBLOCK", "RDONLY", - "RDWR", "TRUNC", "WRONLY"].each do |const| + "RDWR", "TRUNC", "WRONLY", "SHARE_DELETE"].each do |const| describe "File::Constants::#{const}" do it "is defined" do File::Constants.const_defined?(const).should be_true diff --git a/spec/ruby/core/file/ctime_spec.rb b/spec/ruby/core/file/ctime_spec.rb index d17ba1a77f..718f26d5cc 100644 --- a/spec/ruby/core/file/ctime_spec.rb +++ b/spec/ruby/core/file/ctime_spec.rb @@ -22,6 +22,9 @@ describe "File.ctime" do else File.ctime(__FILE__).usec.should == 0 end + rescue Errno::ENOENT => e + # Windows don't have stat command. + skip e.message end end diff --git a/spec/ruby/core/file/dirname_spec.rb b/spec/ruby/core/file/dirname_spec.rb index 8dd6c4ca88..8e6016ce6f 100644 --- a/spec/ruby/core/file/dirname_spec.rb +++ b/spec/ruby/core/file/dirname_spec.rb @@ -11,34 +11,32 @@ describe "File.dirname" do File.dirname('/foo/foo').should == '/foo' end - ruby_version_is '3.1' do - context "when level is passed" do - it "returns all the components of filename except the last parts by the level" do - File.dirname('/home/jason', 2).should == '/' - File.dirname('/home/jason/poot.txt', 2).should == '/home' - end - - it "returns the same String if the level is 0" do - File.dirname('poot.txt', 0).should == 'poot.txt' - File.dirname('/', 0).should == '/' - end - - it "raises ArgumentError if the level is negative" do - -> { - File.dirname('/home/jason', -1) - }.should raise_error(ArgumentError, "negative level: -1") - end - - it "returns '/' when level exceeds the number of segments in the path" do - File.dirname("/home/jason", 100).should == '/' - end - - it "calls #to_int if passed not numeric value" do - object = Object.new - def object.to_int; 2; end - - File.dirname("/a/b/c/d", object).should == '/a/b' - end + context "when level is passed" do + it "returns all the components of filename except the last parts by the level" do + File.dirname('/home/jason', 2).should == '/' + File.dirname('/home/jason/poot.txt', 2).should == '/home' + end + + it "returns the same String if the level is 0" do + File.dirname('poot.txt', 0).should == 'poot.txt' + File.dirname('/', 0).should == '/' + end + + it "raises ArgumentError if the level is negative" do + -> { + File.dirname('/home/jason', -1) + }.should raise_error(ArgumentError, "negative level: -1") + end + + it "returns '/' when level exceeds the number of segments in the path" do + File.dirname("/home/jason", 100).should == '/' + end + + it "calls #to_int if passed not numeric value" do + object = Object.new + def object.to_int; 2; end + + File.dirname("/a/b/c/d", object).should == '/a/b' end end @@ -65,19 +63,19 @@ describe "File.dirname" do end it "returns all the components of filename except the last one (edge cases on all platforms)" do - File.dirname("").should == "." - File.dirname(".").should == "." - File.dirname("./").should == "." - File.dirname("./b/./").should == "./b" - File.dirname("..").should == "." - File.dirname("../").should == "." - File.dirname("/").should == "/" - File.dirname("/.").should == "/" - File.dirname("/foo/").should == "/" - File.dirname("/foo/.").should == "/foo" - File.dirname("/foo/./").should == "/foo" - File.dirname("/foo/../.").should == "/foo/.." - File.dirname("foo/../").should == "foo" + File.dirname("").should == "." + File.dirname(".").should == "." + File.dirname("./").should == "." + File.dirname("./b/./").should == "./b" + File.dirname("..").should == "." + File.dirname("../").should == "." + File.dirname("/").should == "/" + File.dirname("/.").should == "/" + File.dirname("/foo/").should == "/" + File.dirname("/foo/.").should == "/foo" + File.dirname("/foo/./").should == "/foo" + File.dirname("/foo/../.").should == "/foo/.." + File.dirname("foo/../").should == "foo" end platform_is_not :windows do diff --git a/spec/ruby/core/file/empty_spec.rb b/spec/ruby/core/file/empty_spec.rb index 77f132303e..e8c9941676 100644 --- a/spec/ruby/core/file/empty_spec.rb +++ b/spec/ruby/core/file/empty_spec.rb @@ -4,10 +4,4 @@ require_relative '../../shared/file/zero' describe "File.empty?" do it_behaves_like :file_zero, :empty?, File it_behaves_like :file_zero_missing, :empty?, File - - platform_is :solaris do - it "returns false for /dev/null" do - File.empty?('/dev/null').should == true - end - end end diff --git a/spec/ruby/core/file/exist_spec.rb b/spec/ruby/core/file/exist_spec.rb index ddb5febcba..b5600e5b07 100644 --- a/spec/ruby/core/file/exist_spec.rb +++ b/spec/ruby/core/file/exist_spec.rb @@ -4,3 +4,9 @@ require_relative '../../shared/file/exist' describe "File.exist?" do it_behaves_like :file_exist, :exist?, File end + +describe "File.exists?" do + it "has been removed" do + File.should_not.respond_to?(:exists?) + end +end diff --git a/spec/ruby/core/file/expand_path_spec.rb b/spec/ruby/core/file/expand_path_spec.rb index c31f885b92..1abcf93900 100644 --- a/spec/ruby/core/file/expand_path_spec.rb +++ b/spec/ruby/core/file/expand_path_spec.rb @@ -137,7 +137,7 @@ describe "File.expand_path" do it "returns a String in the same encoding as the argument" do Encoding.default_external = Encoding::SHIFT_JIS - path = "./a".force_encoding Encoding::CP1251 + path = "./a".dup.force_encoding Encoding::CP1251 File.expand_path(path).encoding.should equal(Encoding::CP1251) weird_path = [222, 173, 190, 175].pack('C*') diff --git a/spec/ruby/core/file/flock_spec.rb b/spec/ruby/core/file/flock_spec.rb index 751e99d994..23ddf89ed8 100644 --- a/spec/ruby/core/file/flock_spec.rb +++ b/spec/ruby/core/file/flock_spec.rb @@ -30,7 +30,7 @@ describe "File#flock" do it "returns false if trying to lock an exclusively locked file" do @file.flock File::LOCK_EX - ruby_exe(<<-END_OF_CODE, escape: true).should == "false" + ruby_exe(<<-END_OF_CODE).should == "false" File.open('#{@name}', "w") do |f2| print f2.flock(File::LOCK_EX | File::LOCK_NB).to_s end @@ -40,7 +40,7 @@ describe "File#flock" do it "blocks if trying to lock an exclusively locked file" do @file.flock File::LOCK_EX - out = ruby_exe(<<-END_OF_CODE, escape: true) + out = ruby_exe(<<-END_OF_CODE) running = false t = Thread.new do @@ -72,35 +72,3 @@ describe "File#flock" do end end end - -platform_is :solaris do - describe "File#flock on Solaris" do - before :each do - @name = tmp("flock_test") - touch(@name) - - @read_file = File.open @name, "r" - @write_file = File.open @name, "w" - end - - after :each do - @read_file.flock File::LOCK_UN - @read_file.close - @write_file.flock File::LOCK_UN - @write_file.close - rm_r @name - end - - it "fails with EBADF acquiring exclusive lock on read-only File" do - -> do - @read_file.flock File::LOCK_EX - end.should raise_error(Errno::EBADF) - end - - it "fails with EBADF acquiring shared lock on read-only File" do - -> do - @write_file.flock File::LOCK_SH - end.should raise_error(Errno::EBADF) - end - end -end diff --git a/spec/ruby/core/file/lchmod_spec.rb b/spec/ruby/core/file/lchmod_spec.rb index 7420b95e4a..3c44374983 100644 --- a/spec/ruby/core/file/lchmod_spec.rb +++ b/spec/ruby/core/file/lchmod_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' describe "File.lchmod" do - platform_is_not :linux, :windows, :openbsd, :solaris, :aix do + platform_is_not :linux, :windows, :openbsd, :aix do before :each do @fname = tmp('file_chmod_test') @lname = @fname + '.lnk' diff --git a/spec/ruby/core/file/lutime_spec.rb b/spec/ruby/core/file/lutime_spec.rb index 1f0625f61e..0f6df42ea3 100644 --- a/spec/ruby/core/file/lutime_spec.rb +++ b/spec/ruby/core/file/lutime_spec.rb @@ -1,7 +1,12 @@ require_relative '../../spec_helper' +require_relative 'shared/update_time' -describe "File.lutime" do - platform_is_not :windows do +platform_is_not :windows do + describe "File.lutime" do + it_behaves_like :update_time, :lutime + end + + describe "File.lutime" do before :each do @atime = Time.utc(2000) @mtime = Time.utc(2001) diff --git a/spec/ruby/core/file/mtime_spec.rb b/spec/ruby/core/file/mtime_spec.rb index 5304bbf057..0e9c95caee 100644 --- a/spec/ruby/core/file/mtime_spec.rb +++ b/spec/ruby/core/file/mtime_spec.rb @@ -26,6 +26,9 @@ describe "File.mtime" do else File.mtime(__FILE__).usec.should == 0 end + rescue Errno::ENOENT => e + # Windows don't have stat command. + skip e.message end end end diff --git a/spec/ruby/core/file/new_spec.rb b/spec/ruby/core/file/new_spec.rb index 715ac1aaf3..1e82a070b1 100644 --- a/spec/ruby/core/file/new_spec.rb +++ b/spec/ruby/core/file/new_spec.rb @@ -100,7 +100,7 @@ describe "File.new" do File.should.exist?(@file) end - it "raises an Errorno::EEXIST if the file exists when create a new file with File::CREAT|File::EXCL" do + it "raises an Errno::EEXIST if the file exists when create a new file with File::CREAT|File::EXCL" do -> { @fh = File.new(@file, File::CREAT|File::EXCL) }.should raise_error(Errno::EEXIST) end @@ -188,6 +188,12 @@ describe "File.new" do }.should raise_error(Errno::EEXIST, /File exists/) end + it "does not use the given block and warns to use File::open" do + -> { + @fh = File.new(@file) { raise } + }.should complain(/warning: File::new\(\) does not take block; use File::open\(\) instead/) + end + it "raises a TypeError if the first parameter can't be coerced to a string" do -> { File.new(true) }.should raise_error(TypeError) -> { File.new(false) }.should raise_error(TypeError) diff --git a/spec/ruby/core/file/open_spec.rb b/spec/ruby/core/file/open_spec.rb index 0346224b93..6bfc16bbf9 100644 --- a/spec/ruby/core/file/open_spec.rb +++ b/spec/ruby/core/file/open_spec.rb @@ -314,7 +314,7 @@ describe "File.open" do end end - it "raises an IOError when read in a block opened with File::RDONLY|File::APPEND mode" do + it "raises an IOError when write in a block opened with File::RDONLY|File::APPEND mode" do -> { File.open(@file, File::RDONLY|File::APPEND ) do |f| f.puts("writing") @@ -354,7 +354,7 @@ describe "File.open" do end end - it "raises an Errorno::EEXIST if the file exists when open with File::CREAT|File::EXCL" do + it "raises an Errno::EEXIST if the file exists when open with File::CREAT|File::EXCL" do -> { File.open(@file, File::CREAT|File::EXCL) do |f| f.puts("writing") @@ -423,7 +423,7 @@ describe "File.open" do }.should raise_error(IOError) end - it "raises an Errorno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do + it "raises an Errno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do -> { File.open(@file, File::RDONLY|File::TRUNC) do |f| f.puts("writing").should == nil @@ -441,7 +441,7 @@ describe "File.open" do }.should raise_error(Errno::EINVAL) end - it "raises an Errorno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do + it "raises an Errno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do -> { File.open(@file, File::RDONLY|File::TRUNC) do |f| f.puts("writing").should == nil diff --git a/spec/ruby/core/file/path_spec.rb b/spec/ruby/core/file/path_spec.rb index dfa0c4ec02..726febcc2b 100644 --- a/spec/ruby/core/file/path_spec.rb +++ b/spec/ruby/core/file/path_spec.rb @@ -37,4 +37,45 @@ describe "File.path" do path.should_receive(:to_path).and_return("abc") File.path(path).should == "abc" end + + it "raises TypeError when #to_path result is not a string" do + path = mock("path") + path.should_receive(:to_path).and_return(nil) + -> { File.path(path) }.should raise_error TypeError + + path = mock("path") + path.should_receive(:to_path).and_return(42) + -> { File.path(path) }.should raise_error TypeError + end + + it "raises ArgumentError for string argument contains NUL character" do + -> { File.path("\0") }.should raise_error ArgumentError + -> { File.path("a\0") }.should raise_error ArgumentError + -> { File.path("a\0c") }.should raise_error ArgumentError + end + + it "raises ArgumentError when #to_path result contains NUL character" do + path = mock("path") + path.should_receive(:to_path).and_return("\0") + -> { File.path(path) }.should raise_error ArgumentError + + path = mock("path") + path.should_receive(:to_path).and_return("a\0") + -> { File.path(path) }.should raise_error ArgumentError + + path = mock("path") + path.should_receive(:to_path).and_return("a\0c") + -> { File.path(path) }.should raise_error ArgumentError + end + + it "raises Encoding::CompatibilityError for ASCII-incompatible string argument" do + path = "abc".encode(Encoding::UTF_32BE) + -> { File.path(path) }.should raise_error Encoding::CompatibilityError + end + + it "raises Encoding::CompatibilityError when #to_path result is ASCII-incompatible" do + path = mock("path") + path.should_receive(:to_path).and_return("abc".encode(Encoding::UTF_32BE)) + -> { File.path(path) }.should raise_error Encoding::CompatibilityError + end end diff --git a/spec/ruby/core/file/realpath_spec.rb b/spec/ruby/core/file/realpath_spec.rb index bd27e09da6..bd25bfdecf 100644 --- a/spec/ruby/core/file/realpath_spec.rb +++ b/spec/ruby/core/file/realpath_spec.rb @@ -54,6 +54,10 @@ platform_is_not :windows do File.realpath(@relative_symlink).should == @file end + it "removes the file element when going one level up" do + File.realpath('../', @file).should == @real_dir + end + it "raises an Errno::ELOOP if the symlink points to itself" do File.unlink @link File.symlink(@link, @link) diff --git a/spec/ruby/core/file/setuid_spec.rb b/spec/ruby/core/file/setuid_spec.rb index 281ef01ab9..9e5e86df61 100644 --- a/spec/ruby/core/file/setuid_spec.rb +++ b/spec/ruby/core/file/setuid_spec.rb @@ -26,10 +26,6 @@ describe "File.setuid?" do platform_is_not :windows do it "returns true when the gid bit is set" do - platform_is :solaris do - # Solaris requires execute bit before setting suid - system "chmod u+x #{@name}" - end system "chmod u+s #{@name}" File.setuid?(@name).should == true diff --git a/spec/ruby/core/file/shared/fnmatch.rb b/spec/ruby/core/file/shared/fnmatch.rb index 94f22144b0..db4b5c5d8c 100644 --- a/spec/ruby/core/file/shared/fnmatch.rb +++ b/spec/ruby/core/file/shared/fnmatch.rb @@ -102,6 +102,7 @@ describe :file_fnmatch, shared: true do it "matches ranges of characters using exclusive bracket expression (e.g. [^t] or [!t])" do File.send(@method, 'ca[^t]', 'cat').should == false + File.send(@method, 'ca[^t]', 'cas').should == true File.send(@method, 'ca[!t]', 'cat').should == false end @@ -125,6 +126,13 @@ describe :file_fnmatch, shared: true do end end + it "matches wildcard with characters when flags includes FNM_PATHNAME" do + File.send(@method, '*a', 'aa', File::FNM_PATHNAME).should == true + File.send(@method, 'a*', 'aa', File::FNM_PATHNAME).should == true + File.send(@method, 'a*', 'aaa', File::FNM_PATHNAME).should == true + File.send(@method, '*a', 'aaa', File::FNM_PATHNAME).should == true + end + it "does not match '/' characters with ? or * when flags includes FNM_PATHNAME" do File.send(@method, '?', '/', File::FNM_PATHNAME).should == false File.send(@method, '*', '/', File::FNM_PATHNAME).should == false @@ -165,9 +173,19 @@ describe :file_fnmatch, shared: true do File.should_not.send(@method, '*/*', 'dave/.profile', File::FNM_PATHNAME) end - it "matches patterns with leading periods to dotfiles by default" do + it "matches patterns with leading periods to dotfiles" do File.send(@method, '.*', '.profile').should == true + File.send(@method, '.*', '.profile', File::FNM_PATHNAME).should == true File.send(@method, ".*file", "nondotfile").should == false + File.send(@method, ".*file", "nondotfile", File::FNM_PATHNAME).should == false + end + + it "does not match directories with leading periods by default with FNM_PATHNAME" do + File.send(@method, '.*', '.directory/nondotfile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', '.directory/.profile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', 'foo/.directory/nondotfile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', 'foo/.directory/.profile', File::FNM_PATHNAME).should == false + File.send(@method, '**/.dotfile', '.dotsubdir/.dotfile', File::FNM_PATHNAME).should == false end it "matches leading periods in filenames when flags includes FNM_DOTMATCH" do @@ -221,6 +239,33 @@ describe :file_fnmatch, shared: true do File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true end + it "has special handling for ./ when using * and FNM_PATHNAME" do + File.send(@method, './*', '.', File::FNM_PATHNAME).should be_false + File.send(@method, './*', './', File::FNM_PATHNAME).should be_true + File.send(@method, './*/', './', File::FNM_PATHNAME).should be_false + File.send(@method, './**', './', File::FNM_PATHNAME).should be_true + File.send(@method, './**/', './', File::FNM_PATHNAME).should be_true + File.send(@method, './*', '.', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + File.send(@method, './*', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, './*/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + File.send(@method, './**', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, './**/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + end + + it "matches **/* with FNM_PATHNAME to recurse directories" do + File.send(@method, 'nested/**/*', 'nested/subdir', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**/*', 'nested/subdir/file', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**/*', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, 'nested/**/*', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + end + + it "matches ** with FNM_PATHNAME only in current directory" do + File.send(@method, 'nested/**', 'nested/subdir', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**', 'nested/subdir/file', File::FNM_PATHNAME).should be_false + File.send(@method, 'nested/**', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, 'nested/**', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + end + it "accepts an object that has a #to_path method" do File.send(@method, '\*', mock_to_path('a')).should == false end diff --git a/spec/ruby/core/file/shared/path.rb b/spec/ruby/core/file/shared/path.rb index ee8109ba05..5a9fe1b0c5 100644 --- a/spec/ruby/core/file/shared/path.rb +++ b/spec/ruby/core/file/shared/path.rb @@ -1,7 +1,7 @@ describe :file_path, shared: true do before :each do - @name = "file_to_path" - @path = tmp(@name) + @path = tmp("file_to_path") + @name = File.basename(@path) touch @path end @@ -77,18 +77,6 @@ describe :file_path, shared: true do after :each do rm_r @dir end - - ruby_version_is ""..."3.1" do - it "raises IOError if file was opened with File::TMPFILE" do - begin - File.open(@dir, File::RDWR | File::TMPFILE) do |f| - -> { f.send(@method) }.should raise_error(IOError) - end - rescue Errno::EOPNOTSUPP, Errno::EINVAL, Errno::EISDIR - skip "no support from the filesystem" - end - end - end end end end diff --git a/spec/ruby/core/file/shared/update_time.rb b/spec/ruby/core/file/shared/update_time.rb new file mode 100644 index 0000000000..3fe7266a00 --- /dev/null +++ b/spec/ruby/core/file/shared/update_time.rb @@ -0,0 +1,105 @@ +describe :update_time, shared: true do + before :all do + @time_is_float = platform_is :windows + end + + before :each do + @atime = Time.now + @mtime = Time.now + @file1 = tmp("specs_file_utime1") + @file2 = tmp("specs_file_utime2") + touch @file1 + touch @file2 + end + + after :each do + rm_r @file1, @file2 + end + + it "sets the access and modification time of each file" do + File.send(@method, @atime, @mtime, @file1, @file2) + + if @time_is_float + File.atime(@file1).should be_close(@atime, 0.0001) + File.mtime(@file1).should be_close(@mtime, 0.0001) + File.atime(@file2).should be_close(@atime, 0.0001) + File.mtime(@file2).should be_close(@mtime, 0.0001) + else + File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) + File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) + File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) + File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) + end + end + + it "uses the current times if two nil values are passed" do + tn = Time.now + File.send(@method, nil, nil, @file1, @file2) + + if @time_is_float + File.atime(@file1).should be_close(tn, 0.050) + File.mtime(@file1).should be_close(tn, 0.050) + File.atime(@file2).should be_close(tn, 0.050) + File.mtime(@file2).should be_close(tn, 0.050) + else + File.atime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) + File.mtime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) + File.atime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) + File.mtime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) + end + end + + it "accepts an object that has a #to_path method" do + File.send(@method, @atime, @mtime, mock_to_path(@file1), mock_to_path(@file2)) + end + + it "accepts numeric atime and mtime arguments" do + if @time_is_float + File.send(@method, @atime.to_f, @mtime.to_f, @file1, @file2) + + File.atime(@file1).should be_close(@atime, 0.0001) + File.mtime(@file1).should be_close(@mtime, 0.0001) + File.atime(@file2).should be_close(@atime, 0.0001) + File.mtime(@file2).should be_close(@mtime, 0.0001) + else + File.send(@method, @atime.to_i, @mtime.to_i, @file1, @file2) + + File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) + File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) + File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) + File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) + end + end + + it "may set nanosecond precision" do + t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r) + File.send(@method, t, t, @file1) + + File.atime(@file1).nsec.should.between?(0, 123500000) + File.mtime(@file1).nsec.should.between?(0, 123500000) + end + + it "returns the number of filenames in the arguments" do + File.send(@method, @atime.to_f, @mtime.to_f, @file1, @file2).should == 2 + end + + platform_is :linux do + platform_is pointer_size: 64 do + it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19 or 2486-07-02)" do + # https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps + # "Therefore, timestamps should not overflow until May 2446." + # https://lwn.net/Articles/804382/ + # "On-disk timestamps hitting the y2038 limit..." + # The problem seems to be being improved, but currently it actually fails on XFS on RHEL8 + # https://rubyci.org/logs/rubyci.s3.amazonaws.com/rhel8/ruby-master/log/20201112T123004Z.fail.html.gz + # Amazon Linux 2023 returns 2486-07-02 in this example + # http://rubyci.s3.amazonaws.com/amazon2023/ruby-master/log/20230322T063004Z.fail.html.gz + time = Time.at(1<<44) + File.send(@method, time, time, @file1) + + [559444, 2486, 2446, 2038].should.include? File.atime(@file1).year + [559444, 2486, 2446, 2038].should.include? File.mtime(@file1).year + end + end + end +end diff --git a/spec/ruby/core/file/socket_spec.rb b/spec/ruby/core/file/socket_spec.rb index 5d12e21f55..d3f4eb013a 100644 --- a/spec/ruby/core/file/socket_spec.rb +++ b/spec/ruby/core/file/socket_spec.rb @@ -1,42 +1,10 @@ require_relative '../../spec_helper' require_relative '../../shared/file/socket' -require 'socket' describe "File.socket?" do it_behaves_like :file_socket, :socket?, File -end -describe "File.socket?" do it "returns false if file does not exist" do File.socket?("I_am_a_bogus_file").should == false end - - it "returns false if the file is not a socket" do - filename = tmp("i_exist") - touch(filename) - - File.socket?(filename).should == false - - rm_r filename - end -end - -platform_is_not :windows do - describe "File.socket?" do - before :each do - # We need a really short name here. - # On Linux the path length is limited to 107, see unix(7). - @name = tmp("s") - @server = UNIXServer.new @name - end - - after :each do - @server.close - rm_r @name - end - - it "returns true if the file is a socket" do - File.socket?(@name).should == true - end - end end diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb index a727bbe566..9aa39297b2 100644 --- a/spec/ruby/core/file/stat/birthtime_spec.rb +++ b/spec/ruby/core/file/stat/birthtime_spec.rb @@ -1,27 +1,29 @@ require_relative '../../../spec_helper' -describe "File::Stat#birthtime" do - before :each do - @file = tmp('i_exist') - touch(@file) { |f| f.write "rubinius" } - end +platform_is(:windows, :darwin, :freebsd, :netbsd, + *ruby_version_is("4.0") { :linux }, + ) do + not_implemented_messages = [ + "birthtime() function is unimplemented", # unsupported OS/version + "birthtime is unimplemented", # unsupported filesystem + ] - after :each do - rm_r @file - end + describe "File::Stat#birthtime" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end - platform_is :windows, :darwin, :freebsd, :netbsd do it "returns the birthtime of a File::Stat object" do st = File.stat(@file) st.birthtime.should be_kind_of(Time) st.birthtime.should <= Time.now - end - end - - platform_is :linux, :openbsd do - it "raises an NotImplementedError" do - st = File.stat(@file) - -> { st.birthtime }.should raise_error(NotImplementedError) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) end end end diff --git a/spec/ruby/core/file/stat/rdev_major_spec.rb b/spec/ruby/core/file/stat/rdev_major_spec.rb index f8a8d1b107..e08d19c03a 100644 --- a/spec/ruby/core/file/stat/rdev_major_spec.rb +++ b/spec/ruby/core/file/stat/rdev_major_spec.rb @@ -2,19 +2,12 @@ require_relative '../../../spec_helper' describe "File::Stat#rdev_major" do before :each do - platform_is :solaris do - @name = "/dev/zfs" - end - platform_is_not :solaris do - @name = tmp("file.txt") - touch(@name) - end + @name = tmp("file.txt") + touch(@name) end after :each do - platform_is_not :solaris do - rm_r @name - end + rm_r @name end platform_is_not :windows do diff --git a/spec/ruby/core/file/stat/rdev_minor_spec.rb b/spec/ruby/core/file/stat/rdev_minor_spec.rb index dc30c1f56c..ace5b8a732 100644 --- a/spec/ruby/core/file/stat/rdev_minor_spec.rb +++ b/spec/ruby/core/file/stat/rdev_minor_spec.rb @@ -2,19 +2,12 @@ require_relative '../../../spec_helper' describe "File::Stat#rdev_minor" do before :each do - platform_is :solaris do - @name = "/dev/zfs" - end - platform_is_not :solaris do - @name = tmp("file.txt") - touch(@name) - end + @name = tmp("file.txt") + touch(@name) end after :each do - platform_is_not :solaris do - rm_r @name - end + rm_r @name end platform_is_not :windows do diff --git a/spec/ruby/core/file/utime_spec.rb b/spec/ruby/core/file/utime_spec.rb index 0b0e4f979c..d87626be50 100644 --- a/spec/ruby/core/file/utime_spec.rb +++ b/spec/ruby/core/file/utime_spec.rb @@ -1,102 +1,6 @@ require_relative '../../spec_helper' +require_relative 'shared/update_time' describe "File.utime" do - - before :all do - @time_is_float = platform_is :windows - end - - before :each do - @atime = Time.now - @mtime = Time.now - @file1 = tmp("specs_file_utime1") - @file2 = tmp("specs_file_utime2") - touch @file1 - touch @file2 - end - - after :each do - rm_r @file1, @file2 - end - - it "sets the access and modification time of each file" do - File.utime(@atime, @mtime, @file1, @file2) - if @time_is_float - File.atime(@file1).should be_close(@atime, 0.0001) - File.mtime(@file1).should be_close(@mtime, 0.0001) - File.atime(@file2).should be_close(@atime, 0.0001) - File.mtime(@file2).should be_close(@mtime, 0.0001) - else - File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) - File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) - File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) - File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) - end - end - - it "uses the current times if two nil values are passed" do - tn = Time.now - File.utime(nil, nil, @file1, @file2) - if @time_is_float - File.atime(@file1).should be_close(tn, 0.050) - File.mtime(@file1).should be_close(tn, 0.050) - File.atime(@file2).should be_close(tn, 0.050) - File.mtime(@file2).should be_close(tn, 0.050) - else - File.atime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) - File.mtime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) - File.atime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) - File.mtime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) - end - end - - it "accepts an object that has a #to_path method" do - File.utime(@atime, @mtime, mock_to_path(@file1), mock_to_path(@file2)) - end - - it "accepts numeric atime and mtime arguments" do - if @time_is_float - File.utime(@atime.to_f, @mtime.to_f, @file1, @file2) - File.atime(@file1).should be_close(@atime, 0.0001) - File.mtime(@file1).should be_close(@mtime, 0.0001) - File.atime(@file2).should be_close(@atime, 0.0001) - File.mtime(@file2).should be_close(@mtime, 0.0001) - else - File.utime(@atime.to_i, @mtime.to_i, @file1, @file2) - File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) - File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) - File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) - File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) - end - end - - it "may set nanosecond precision" do - t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r) - File.utime(t, t, @file1) - File.atime(@file1).nsec.should.between?(0, 123500000) - File.mtime(@file1).nsec.should.between?(0, 123500000) - end - - it "returns the number of filenames in the arguments" do - File.utime(@atime.to_f, @mtime.to_f, @file1, @file2).should == 2 - end - - platform_is :linux do - platform_is wordsize: 64 do - it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19 or 2486-07-02)" do - # https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps - # "Therefore, timestamps should not overflow until May 2446." - # https://lwn.net/Articles/804382/ - # "On-disk timestamps hitting the y2038 limit..." - # The problem seems to be being improved, but currently it actually fails on XFS on RHEL8 - # https://rubyci.org/logs/rubyci.s3.amazonaws.com/rhel8/ruby-master/log/20201112T123004Z.fail.html.gz - # Amazon Linux 2023 returns 2486-07-02 in this example - # http://rubyci.s3.amazonaws.com/amazon2023/ruby-master/log/20230322T063004Z.fail.html.gz - time = Time.at(1<<44) - File.utime(time, time, @file1) - [559444, 2486, 2446, 2038].should.include? File.atime(@file1).year - [559444, 2486, 2446, 2038].should.include? File.mtime(@file1).year - end - end - end + it_behaves_like :update_time, :utime end diff --git a/spec/ruby/core/file/zero_spec.rb b/spec/ruby/core/file/zero_spec.rb index 63dd85ee46..01c7505ef2 100644 --- a/spec/ruby/core/file/zero_spec.rb +++ b/spec/ruby/core/file/zero_spec.rb @@ -4,10 +4,4 @@ require_relative '../../shared/file/zero' describe "File.zero?" do it_behaves_like :file_zero, :zero?, File it_behaves_like :file_zero_missing, :zero?, File - - platform_is :solaris do - it "returns false for /dev/null" do - File.zero?('/dev/null').should == true - end - end end diff --git a/spec/ruby/core/filetest/exist_spec.rb b/spec/ruby/core/filetest/exist_spec.rb index 4d14bea231..612ffa9fcb 100644 --- a/spec/ruby/core/filetest/exist_spec.rb +++ b/spec/ruby/core/filetest/exist_spec.rb @@ -4,3 +4,9 @@ require_relative '../../shared/file/exist' describe "FileTest.exist?" do it_behaves_like :file_exist, :exist?, FileTest end + +describe "FileTest.exists?" do + it "has been removed" do + FileTest.should_not.respond_to?(:exists?) + end +end diff --git a/spec/ruby/core/filetest/socket_spec.rb b/spec/ruby/core/filetest/socket_spec.rb index 63a6a31ecb..f274be6318 100644 --- a/spec/ruby/core/filetest/socket_spec.rb +++ b/spec/ruby/core/filetest/socket_spec.rb @@ -3,4 +3,8 @@ require_relative '../../shared/file/socket' describe "FileTest.socket?" do it_behaves_like :file_socket, :socket?, FileTest + + it "returns false if file does not exist" do + FileTest.socket?("I_am_a_bogus_file").should == false + end end diff --git a/spec/ruby/core/filetest/zero_spec.rb b/spec/ruby/core/filetest/zero_spec.rb index dd6a164ec9..92cab67f1b 100644 --- a/spec/ruby/core/filetest/zero_spec.rb +++ b/spec/ruby/core/filetest/zero_spec.rb @@ -4,10 +4,4 @@ require_relative '../../shared/file/zero' describe "FileTest.zero?" do it_behaves_like :file_zero, :zero?, FileTest it_behaves_like :file_zero_missing, :zero?, FileTest - - platform_is :solaris do - it "returns false for /dev/null" do - File.zero?('/dev/null').should == true - end - end end diff --git a/spec/ruby/core/float/ceil_spec.rb b/spec/ruby/core/float/ceil_spec.rb index 7fc18d304c..75f5610292 100644 --- a/spec/ruby/core/float/ceil_spec.rb +++ b/spec/ruby/core/float/ceil_spec.rb @@ -1,6 +1,11 @@ require_relative '../../spec_helper' +require_relative '../integer/shared/integer_ceil_precision' describe "Float#ceil" do + context "with precision" do + it_behaves_like :integer_ceil_precision, :Float + end + it "returns the smallest Integer greater than or equal to self" do -1.2.ceil.should eql( -1) -1.0.ceil.should eql( -1) diff --git a/spec/ruby/core/float/comparison_spec.rb b/spec/ruby/core/float/comparison_spec.rb index 1373b3a1fb..0cd290f4ad 100644 --- a/spec/ruby/core/float/comparison_spec.rb +++ b/spec/ruby/core/float/comparison_spec.rb @@ -72,7 +72,7 @@ describe "Float#<=>" do (-Float::MAX.to_i*2 <=> -infinity_value).should == 1 end - it "returns 0 when self is Infinity and other other is infinite?=1" do + it "returns 0 when self is Infinity and other is infinite?=1" do obj = Object.new def obj.infinite? 1 @@ -91,7 +91,7 @@ describe "Float#<=>" do it "returns 1 when self is Infinity and other is infinite?=nil (which means finite)" do obj = Object.new def obj.infinite? - nil + nil end (infinity_value <=> obj).should == 1 end diff --git a/spec/ruby/core/float/floor_spec.rb b/spec/ruby/core/float/floor_spec.rb index 046216d36d..8b492ef473 100644 --- a/spec/ruby/core/float/floor_spec.rb +++ b/spec/ruby/core/float/floor_spec.rb @@ -1,6 +1,11 @@ require_relative '../../spec_helper' +require_relative '../integer/shared/integer_floor_precision' describe "Float#floor" do + context "with precision" do + it_behaves_like :integer_floor_precision, :Float + end + it "returns the largest Integer less than or equal to self" do -1.2.floor.should eql( -2) -1.0.floor.should eql( -1) diff --git a/spec/ruby/core/float/round_spec.rb b/spec/ruby/core/float/round_spec.rb index 9b4c307f9d..7e8c792051 100644 --- a/spec/ruby/core/float/round_spec.rb +++ b/spec/ruby/core/float/round_spec.rb @@ -30,6 +30,11 @@ describe "Float#round" do 12.345678.round(3.999).should == 12.346 end + it "correctly rounds exact floats with a numerous digits in a fraction part" do + 0.8241000000000004.round(10).should == 0.8241 + 0.8241000000000002.round(10).should == 0.8241 + end + it "returns zero when passed a negative argument with magnitude greater than magnitude of the whole number portion of the Float" do 0.8346268.round(-1).should eql(0) end @@ -68,6 +73,10 @@ describe "Float#round" do 0.42.round(2.0**30).should == 0.42 end + it "returns rounded values for not so big argument" do + 0.42.round(2.0**23).should == 0.42 + end + it "returns big values rounded to nearest" do +2.5e20.round(-20).should eql( +3 * 10 ** 20 ) -2.5e20.round(-20).should eql( -3 * 10 ** 20 ) diff --git a/spec/ruby/core/gc/config_spec.rb b/spec/ruby/core/gc/config_spec.rb new file mode 100644 index 0000000000..e20e8e4a16 --- /dev/null +++ b/spec/ruby/core/gc/config_spec.rb @@ -0,0 +1,83 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.4" do + describe "GC.config" do + context "without arguments" do + it "returns a hash of current settings" do + GC.config.should be_kind_of(Hash) + end + + it "includes the name of currently loaded GC implementation as a global key" do + GC.config.should include(:implementation) + GC.config[:implementation].should be_kind_of(String) + end + end + + context "with a hash of options" do + it "allows to set GC implementation's options, returning the new config" do + config = GC.config({}) + # Try to find a boolean setting to reliably test changing it. + key, _value = config.find { |_k, v| v == true } + skip unless key + + GC.config(key => false).should == config.merge(key => false) + GC.config[key].should == false + GC.config(key => true).should == config + GC.config[key].should == true + ensure + GC.config(config.except(:implementation)) + end + + it "does not change settings that aren't present in the hash" do + previous = GC.config + GC.config({}) + GC.config.should == previous + end + + it "ignores unknown keys" do + previous = GC.config + GC.config(foo: "bar") + GC.config.should == previous + end + + it "raises an ArgumentError if options include global keys" do + -> { GC.config(implementation: "default") }.should raise_error(ArgumentError, 'Attempting to set read-only key "Implementation"') + end + end + + context "with a non-hash argument" do + it "returns current settings if argument is nil" do + GC.config(nil).should == GC.config + end + + it "raises ArgumentError for all other arguments" do + -> { GC.config([]) }.should raise_error(ArgumentError) + -> { GC.config("default") }.should raise_error(ArgumentError) + -> { GC.config(1) }.should raise_error(ArgumentError) + end + end + + guard -> { PlatformGuard.standard? && GC.config[:implementation] == "default" } do + context "with default GC implementation on MRI" do + before do + @default_config = GC.config({}) + end + + after do + GC.config(@default_config.except(:implementation)) + end + + it "includes :rgengc_allow_full_mark option, true by default" do + GC.config.should include(:rgengc_allow_full_mark) + GC.config[:rgengc_allow_full_mark].should be_true + end + + it "allows to set :rgengc_allow_full_mark" do + # This key maps truthy and falsey values to true and false. + GC.config(rgengc_allow_full_mark: nil).should == @default_config.merge(rgengc_allow_full_mark: false) + GC.config(rgengc_allow_full_mark: 1.23).should == @default_config.merge(rgengc_allow_full_mark: true) + end + end + end + end +end diff --git a/spec/ruby/core/gc/measure_total_time_spec.rb b/spec/ruby/core/gc/measure_total_time_spec.rb index 05d4598ebc..f5377c18fd 100644 --- a/spec/ruby/core/gc/measure_total_time_spec.rb +++ b/spec/ruby/core/gc/measure_total_time_spec.rb @@ -1,19 +1,17 @@ require_relative '../../spec_helper' -ruby_version_is "3.1" do - describe "GC.measure_total_time" do - before :each do - @default = GC.measure_total_time - end +describe "GC.measure_total_time" do + before :each do + @default = GC.measure_total_time + end - after :each do - GC.measure_total_time = @default - end + after :each do + GC.measure_total_time = @default + end - it "can set and get a boolean value" do - original = GC.measure_total_time - GC.measure_total_time = !original - GC.measure_total_time.should == !original - end + it "can set and get a boolean value" do + original = GC.measure_total_time + GC.measure_total_time = !original + GC.measure_total_time.should == !original end end diff --git a/spec/ruby/core/gc/total_time_spec.rb b/spec/ruby/core/gc/total_time_spec.rb index fcc8f45a83..a8430fbe9a 100644 --- a/spec/ruby/core/gc/total_time_spec.rb +++ b/spec/ruby/core/gc/total_time_spec.rb @@ -1,15 +1,13 @@ require_relative '../../spec_helper' -ruby_version_is "3.1" do - describe "GC.total_time" do - it "returns an Integer" do - GC.total_time.should be_kind_of(Integer) - end +describe "GC.total_time" do + it "returns an Integer" do + GC.total_time.should be_kind_of(Integer) + end - it "increases as collections are run" do - time_before = GC.total_time - GC.start - GC.total_time.should >= time_before - end + it "increases as collections are run" do + time_before = GC.total_time + GC.start + GC.total_time.should >= time_before end end diff --git a/spec/ruby/core/hash/assoc_spec.rb b/spec/ruby/core/hash/assoc_spec.rb index 64442918d1..62b2a11b30 100644 --- a/spec/ruby/core/hash/assoc_spec.rb +++ b/spec/ruby/core/hash/assoc_spec.rb @@ -22,11 +22,11 @@ describe "Hash#assoc" do end it "only returns the first matching key-value pair for identity hashes" do - # Avoid literal String keys in Hash#[]= due to https://bugs.ruby-lang.org/issues/12855 + # Avoid literal String keys since string literals can be frozen and interned e.g. with --enable-frozen-string-literal h = {}.compare_by_identity - k1 = 'pear' + k1 = 'pear'.dup h[k1] = :red - k2 = 'pear' + k2 = 'pear'.dup h[k2] = :green h.size.should == 2 h.keys.grep(/pear/).size.should == 2 diff --git a/spec/ruby/core/hash/compact_spec.rb b/spec/ruby/core/hash/compact_spec.rb index 76aa43949d..13371bce43 100644 --- a/spec/ruby/core/hash/compact_spec.rb +++ b/spec/ruby/core/hash/compact_spec.rb @@ -35,7 +35,7 @@ describe "Hash#compact" do hash.compact.default_proc.should == pr end - it "retains compare_by_identity_flag" do + it "retains compare_by_identity flag" do hash = {}.compare_by_identity hash.compact.compare_by_identity?.should == true hash[:a] = 1 diff --git a/spec/ruby/core/hash/compare_by_identity_spec.rb b/spec/ruby/core/hash/compare_by_identity_spec.rb index 874cd46eb7..2975526a97 100644 --- a/spec/ruby/core/hash/compare_by_identity_spec.rb +++ b/spec/ruby/core/hash/compare_by_identity_spec.rb @@ -85,19 +85,21 @@ describe "Hash#compare_by_identity" do -> { @h.compare_by_identity }.should raise_error(FrozenError) end - # Behaviour confirmed in bug #1871 + # Behaviour confirmed in https://bugs.ruby-lang.org/issues/1871 it "persists over #dups" do - @idh['foo'] = :bar - @idh['foo'] = :glark + @idh['foo'.dup] = :bar + @idh['foo'.dup] = :glark @idh.dup.should == @idh @idh.dup.size.should == @idh.size + @idh.dup.should.compare_by_identity? end it "persists over #clones" do - @idh['foo'] = :bar - @idh['foo'] = :glark + @idh['foo'.dup] = :bar + @idh['foo'.dup] = :glark @idh.clone.should == @idh @idh.clone.size.should == @idh.size + @idh.dup.should.compare_by_identity? end it "does not copy string keys" do @@ -108,9 +110,16 @@ describe "Hash#compare_by_identity" do @idh.keys.first.should equal foo end + # Check `#[]=` call with a String literal. + # Don't use `#+` because with `#+` it's no longer a String literal. + # + # See https://bugs.ruby-lang.org/issues/12855 it "gives different identity for string literals" do + eval <<~RUBY + # frozen_string_literal: false @idh['foo'] = 1 @idh['foo'] = 2 + RUBY @idh.values.should == [1, 2] @idh.size.should == 2 end diff --git a/spec/ruby/core/hash/constructor_spec.rb b/spec/ruby/core/hash/constructor_spec.rb index 8d29773909..0f97f7b40e 100644 --- a/spec/ruby/core/hash/constructor_spec.rb +++ b/spec/ruby/core/hash/constructor_spec.rb @@ -103,14 +103,14 @@ describe "Hash.[]" do HashSpecs::MyInitializerHash[Hash[1, 2]].should be_an_instance_of(HashSpecs::MyInitializerHash) end - it "removes the default value" do + it "does not retain the default value" do hash = Hash.new(1) Hash[hash].default.should be_nil hash[:a] = 1 Hash[hash].default.should be_nil end - it "removes the default_proc" do + it "does not retain the default_proc" do hash = Hash.new { |h, k| h[k] = [] } Hash[hash].default_proc.should be_nil hash[:a] = 1 @@ -118,10 +118,11 @@ describe "Hash.[]" do end ruby_version_is '3.3' do - it "does not retain compare_by_identity_flag" do - hash = {}.compare_by_identity + it "does not retain compare_by_identity flag" do + hash = { a: 1 }.compare_by_identity Hash[hash].compare_by_identity?.should == false - hash[:a] = 1 + + hash = {}.compare_by_identity Hash[hash].compare_by_identity?.should == false end end diff --git a/spec/ruby/core/hash/delete_spec.rb b/spec/ruby/core/hash/delete_spec.rb index b262e8846b..3e3479c69c 100644 --- a/spec/ruby/core/hash/delete_spec.rb +++ b/spec/ruby/core/hash/delete_spec.rb @@ -24,7 +24,7 @@ describe "Hash#delete" do it "allows removing a key while iterating" do h = { a: 1, b: 2 } visited = [] - h.each_pair { |k,v| + h.each_pair { |k, v| visited << k h.delete(k) } @@ -32,13 +32,27 @@ describe "Hash#delete" do h.should == {} end + it "allows removing a key while iterating for big hashes" do + h = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, + k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, + u: 21, v: 22, w: 23, x: 24, y: 25, z: 26 } + visited = [] + h.each_pair { |k, v| + visited << k + h.delete(k) + } + visited.should == [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, + :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z] + h.should == {} + end + it "accepts keys with private #hash method" do key = HashSpecs::KeyWithPrivateHash.new { key => 5 }.delete(key).should == 5 end it "raises a FrozenError if called on a frozen instance" do - -> { HashSpecs.frozen_hash.delete("foo") }.should raise_error(FrozenError) + -> { HashSpecs.frozen_hash.delete("foo") }.should raise_error(FrozenError) -> { HashSpecs.empty_frozen_hash.delete("foo") }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/hash/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb index e271f37ea6..d5859cb342 100644 --- a/spec/ruby/core/hash/element_reference_spec.rb +++ b/spec/ruby/core/hash/element_reference_spec.rb @@ -12,7 +12,7 @@ describe "Hash#[]" do h[[]].should == "baz" end - it "returns nil as default default value" do + it "returns nil as default value" do { 0 => 0 }[5].should == nil end @@ -30,7 +30,7 @@ describe "Hash#[]" do end it "does not create copies of the immediate default value" do - str = "foo" + str = +"foo" h = Hash.new(str) a = h[:a] b = h[:b] diff --git a/spec/ruby/core/hash/except_spec.rb b/spec/ruby/core/hash/except_spec.rb index ac84f9975c..026e454b13 100644 --- a/spec/ruby/core/hash/except_spec.rb +++ b/spec/ruby/core/hash/except_spec.rb @@ -19,14 +19,24 @@ describe "Hash#except" do @hash.except(:a, :chunky_bacon).should == { b: 2, c: 3 } end - it "always returns a Hash without a default" do - klass = Class.new(Hash) - h = klass.new(:default) - h[:bar] = 12 - h[:foo] = 42 - r = h.except(:foo) - r.should == {bar: 12} - r.class.should == Hash - r.default.should == nil + it "does not retain the default value" do + h = Hash.new(1) + h.except(:a).default.should be_nil + h[:a] = 1 + h.except(:a).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.except(:a).default_proc.should be_nil + h[:a] = 1 + h.except(:a).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.except(:a) + h2.compare_by_identity?.should == true end end diff --git a/spec/ruby/core/hash/hash_spec.rb b/spec/ruby/core/hash/hash_spec.rb index 2ccb483120..cd67f7a652 100644 --- a/spec/ruby/core/hash/hash_spec.rb +++ b/spec/ruby/core/hash/hash_spec.rb @@ -42,12 +42,10 @@ describe "Hash#hash" do # Like above, because h.eql?(x: [h]) end - ruby_version_is "3.1" do - it "allows ommiting values" do - a = 1 - b = 2 + it "allows omitting values" do + a = 1 + b = 2 - eval('{a:, b:}.should == { a: 1, b: 2 }') - end + {a:, b:}.should == { a: 1, b: 2 } end end diff --git a/spec/ruby/core/hash/invert_spec.rb b/spec/ruby/core/hash/invert_spec.rb index 73377a9e97..c06e15ff7c 100644 --- a/spec/ruby/core/hash/invert_spec.rb +++ b/spec/ruby/core/hash/invert_spec.rb @@ -24,4 +24,25 @@ describe "Hash#invert" do HashSpecs::MyHash[1 => 2, 3 => 4].invert.class.should == Hash HashSpecs::MyHash[].invert.class.should == Hash end + + it "does not retain the default value" do + h = Hash.new(1) + h.invert.default.should be_nil + h[:a] = 1 + h.invert.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.invert.default_proc.should be_nil + h[:a] = 1 + h.invert.default_proc.should be_nil + end + + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.invert + h2.compare_by_identity?.should == false + end end diff --git a/spec/ruby/core/hash/merge_spec.rb b/spec/ruby/core/hash/merge_spec.rb index 5521864297..6710d121ef 100644 --- a/spec/ruby/core/hash/merge_spec.rb +++ b/spec/ruby/core/hash/merge_spec.rb @@ -93,6 +93,29 @@ describe "Hash#merge" do merged.should eql(hash) merged.should_not equal(hash) end + + it "retains the default value" do + h = Hash.new(1) + h.merge(b: 1, d: 2).default.should == 1 + end + + it "retains the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.merge(b: 1, d: 2).default_proc.should == pr + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.merge(b: 1, d: 2) + h2.compare_by_identity?.should == true + end + + it "ignores compare_by_identity flag of an argument" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = { b: 1, d: 2 }.merge(h) + h2.compare_by_identity?.should == false + end end describe "Hash#merge!" do diff --git a/spec/ruby/core/hash/new_spec.rb b/spec/ruby/core/hash/new_spec.rb index 6279815fd6..5ae3e1f98d 100644 --- a/spec/ruby/core/hash/new_spec.rb +++ b/spec/ruby/core/hash/new_spec.rb @@ -34,7 +34,7 @@ describe "Hash.new" do -> { Hash.new(nil) { 0 } }.should raise_error(ArgumentError) end - ruby_version_is "3.3" do + ruby_version_is "3.3"..."3.4" do it "emits a deprecation warning if keyword arguments are passed" do -> { Hash.new(unknown: true) }.should complain( Regexp.new(Regexp.escape("Calling Hash.new with keyword arguments is deprecated and will be removed in Ruby 3.4; use Hash.new({ key: value }) instead")) @@ -46,4 +46,22 @@ describe "Hash.new" do Hash.new({ unknown: true }).default.should == { unknown: true } end end + + ruby_version_is "3.4" do + it "accepts a capacity: argument" do + Hash.new(5, capacity: 42).default.should == 5 + Hash.new(capacity: 42).default.should == nil + (Hash.new(capacity: 42) { 1 }).default_proc.should_not == nil + end + + it "ignores negative capacity" do + -> { Hash.new(capacity: -42) }.should_not raise_error + end + + it "raises an error if unknown keyword arguments are passed" do + -> { Hash.new(unknown: true) }.should raise_error(ArgumentError) + -> { Hash.new(1, unknown: true) }.should raise_error(ArgumentError) + -> { Hash.new(unknown: true) { 0 } }.should raise_error(ArgumentError) + end + end end diff --git a/spec/ruby/core/hash/rehash_spec.rb b/spec/ruby/core/hash/rehash_spec.rb index 0049080456..db3e91b166 100644 --- a/spec/ruby/core/hash/rehash_spec.rb +++ b/spec/ruby/core/hash/rehash_spec.rb @@ -77,6 +77,36 @@ describe "Hash#rehash" do h.keys.should_not.include? [1] end + it "iterates keys in insertion order" do + key = Class.new do + attr_reader :name + + def initialize(name) + @name = name + end + + def hash + 123 + end + end + + a, b, c, d = key.new('a'), key.new('b'), key.new('c'), key.new('d') + h = { a => 1, b => 2, c => 3, d => 4 } + h.size.should == 4 + + key.class_exec do + def eql?(other) + true + end + end + + h.rehash + h.size.should == 1 + k, v = h.first + k.name.should == 'a' + v.should == 4 + end + it "raises a FrozenError if called on a frozen instance" do -> { HashSpecs.frozen_hash.rehash }.should raise_error(FrozenError) -> { HashSpecs.empty_frozen_hash.rehash }.should raise_error(FrozenError) diff --git a/spec/ruby/core/hash/reject_spec.rb b/spec/ruby/core/hash/reject_spec.rb index dd8e817237..8381fc7fc1 100644 --- a/spec/ruby/core/hash/reject_spec.rb +++ b/spec/ruby/core/hash/reject_spec.rb @@ -44,6 +44,27 @@ describe "Hash#reject" do reject_pairs.should == reject_bang_pairs end + it "does not retain the default value" do + h = Hash.new(1) + h.reject { false }.default.should be_nil + h[:a] = 1 + h.reject { false }.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.reject { false }.default_proc.should be_nil + h[:a] = 1 + h.reject { false }.default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.reject { |k, _| k == :a } + h2.compare_by_identity?.should == true + end + it_behaves_like :hash_iteration_no_block, :reject it_behaves_like :enumeratorized_with_origin_size, :reject, { 1 => 2, 3 => 4, 5 => 6 } end diff --git a/spec/ruby/core/hash/replace_spec.rb b/spec/ruby/core/hash/replace_spec.rb index 92b2118fd3..db30145e1a 100644 --- a/spec/ruby/core/hash/replace_spec.rb +++ b/spec/ruby/core/hash/replace_spec.rb @@ -1,7 +1,79 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'shared/replace' describe "Hash#replace" do - it_behaves_like :hash_replace, :replace + it "replaces the contents of self with other" do + h = { a: 1, b: 2 } + h.replace(c: -1, d: -2).should equal(h) + h.should == { c: -1, d: -2 } + end + + it "tries to convert the passed argument to a hash using #to_hash" do + obj = mock('{1=>2,3=>4}') + obj.should_receive(:to_hash).and_return({ 1 => 2, 3 => 4 }) + + h = {} + h.replace(obj) + h.should == { 1 => 2, 3 => 4 } + end + + it "calls to_hash on hash subclasses" do + h = {} + h.replace(HashSpecs::ToHashHash[1 => 2]) + h.should == { 1 => 2 } + end + + it "does not retain the default value" do + hash = Hash.new(1) + hash.replace(b: 2).default.should be_nil + end + + it "transfers the default value of an argument" do + hash = Hash.new(1) + { a: 1 }.replace(hash).default.should == 1 + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + hash.replace(b: 2).default_proc.should be_nil + end + + it "transfers the default_proc of an argument" do + pr = proc { |h, k| h[k] = [] } + hash = Hash.new(&pr) + { a: 1 }.replace(hash).default_proc.should == pr + end + + it "does not call the default_proc of an argument" do + hash_a = Hash.new { |h, k| k * 5 } + hash_b = Hash.new(-> { raise "Should not invoke lambda" }) + hash_a.replace(hash_b) + hash_a.default.should == hash_b.default + end + + it "transfers compare_by_identity flag of an argument" do + h = { a: 1, c: 3 } + h2 = { b: 2, d: 4 }.compare_by_identity + h.replace(h2) + h.compare_by_identity?.should == true + end + + it "does not retain compare_by_identity flag" do + h = { a: 1, c: 3 }.compare_by_identity + h.replace(b: 2, d: 4) + h.compare_by_identity?.should == false + end + + it "raises a FrozenError if called on a frozen instance that would not be modified" do + -> do + HashSpecs.frozen_hash.replace(HashSpecs.frozen_hash) + end.should raise_error(FrozenError) + end + + it "raises a FrozenError if called on a frozen instance that is modified" do + -> do + HashSpecs.frozen_hash.replace(HashSpecs.empty_frozen_hash) + end.should raise_error(FrozenError) + end end diff --git a/spec/ruby/core/hash/shared/replace.rb b/spec/ruby/core/hash/shared/replace.rb deleted file mode 100644 index bea64384bb..0000000000 --- a/spec/ruby/core/hash/shared/replace.rb +++ /dev/null @@ -1,51 +0,0 @@ -describe :hash_replace, shared: true do - it "replaces the contents of self with other" do - h = { a: 1, b: 2 } - h.send(@method, c: -1, d: -2).should equal(h) - h.should == { c: -1, d: -2 } - end - - it "tries to convert the passed argument to a hash using #to_hash" do - obj = mock('{1=>2,3=>4}') - obj.should_receive(:to_hash).and_return({ 1 => 2, 3 => 4 }) - - h = {} - h.send(@method, obj) - h.should == { 1 => 2, 3 => 4 } - end - - it "calls to_hash on hash subclasses" do - h = {} - h.send(@method, HashSpecs::ToHashHash[1 => 2]) - h.should == { 1 => 2 } - end - - it "does not transfer default values" do - hash_a = {} - hash_b = Hash.new(5) - hash_a.send(@method, hash_b) - hash_a.default.should == 5 - - hash_a = {} - hash_b = Hash.new { |h, k| k * 2 } - hash_a.send(@method, hash_b) - hash_a.default(5).should == 10 - - hash_a = Hash.new { |h, k| k * 5 } - hash_b = Hash.new(-> { raise "Should not invoke lambda" }) - hash_a.send(@method, hash_b) - hash_a.default.should == hash_b.default - end - - it "raises a FrozenError if called on a frozen instance that would not be modified" do - -> do - HashSpecs.frozen_hash.send(@method, HashSpecs.frozen_hash) - end.should raise_error(FrozenError) - end - - it "raises a FrozenError if called on a frozen instance that is modified" do - -> do - HashSpecs.frozen_hash.send(@method, HashSpecs.empty_frozen_hash) - end.should raise_error(FrozenError) - end -end diff --git a/spec/ruby/core/hash/shared/select.rb b/spec/ruby/core/hash/shared/select.rb index 5170af50d6..fbeff07330 100644 --- a/spec/ruby/core/hash/shared/select.rb +++ b/spec/ruby/core/hash/shared/select.rb @@ -40,6 +40,27 @@ describe :hash_select, shared: true do @empty.send(@method).should be_an_instance_of(Enumerator) end + it "does not retain the default value" do + h = Hash.new(1) + h.send(@method) { true }.default.should be_nil + h[:a] = 1 + h.send(@method) { true }.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.send(@method) { true }.default_proc.should be_nil + h[:a] = 1 + h.send(@method) { true }.default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.send(@method) { |k, _| k == :a } + h2.compare_by_identity?.should == true + end + it_should_behave_like :hash_iteration_no_block before :each do diff --git a/spec/ruby/core/hash/shared/store.rb b/spec/ruby/core/hash/shared/store.rb index b823ea45ca..72a462a42f 100644 --- a/spec/ruby/core/hash/shared/store.rb +++ b/spec/ruby/core/hash/shared/store.rb @@ -9,7 +9,7 @@ describe :hash_store, shared: true do it "duplicates string keys using dup semantics" do # dup doesn't copy singleton methods - key = "foo" + key = +"foo" def key.reverse() "bar" end h = {} h.send(@method, key, 0) @@ -44,7 +44,7 @@ describe :hash_store, shared: true do end it "duplicates and freezes string keys" do - key = "foo" + key = +"foo" h = {} h.send(@method, key, 0) key << "bar" @@ -75,8 +75,8 @@ describe :hash_store, shared: true do it "keeps the existing String key in the hash if there is a matching one" do h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 } - key1 = "foo" - key2 = "foo" + key1 = "foo".dup + key2 = "foo".dup key1.should_not equal(key2) h[key1] = 41 frozen_key = h.keys.last @@ -91,9 +91,9 @@ describe :hash_store, shared: true do end it "does not raise an exception if changing the value of an existing key during iteration" do - hash = {1 => 2, 3 => 4, 5 => 6} - hash.each { hash.send(@method, 1, :foo) } - hash.should == {1 => :foo, 3 => 4, 5 => 6} + hash = {1 => 2, 3 => 4, 5 => 6} + hash.each { hash.send(@method, 1, :foo) } + hash.should == {1 => :foo, 3 => 4, 5 => 6} end it "does not dispatch to hash for Boolean, Integer, Float, String, or Symbol" do diff --git a/spec/ruby/core/hash/shared/to_s.rb b/spec/ruby/core/hash/shared/to_s.rb index 2db3a96583..e116b8878b 100644 --- a/spec/ruby/core/hash/shared/to_s.rb +++ b/spec/ruby/core/hash/shared/to_s.rb @@ -4,14 +4,8 @@ require_relative '../fixtures/classes' describe :hash_to_s, shared: true do it "returns a string representation with same order as each()" do h = { a: [1, 2], b: -2, d: -6, nil => nil } - - pairs = [] - h.each do |key, value| - pairs << key.inspect + '=>' + value.inspect - end - - str = '{' + pairs.join(', ') + '}' - h.send(@method).should == str + expected = ruby_version_is("3.4") ? "{a: [1, 2], b: -2, d: -6, nil => nil}" : "{:a=>[1, 2], :b=>-2, :d=>-6, nil=>nil}" + h.send(@method).should == expected end it "calls #inspect on keys and values" do @@ -19,31 +13,31 @@ describe :hash_to_s, shared: true do val = mock('val') key.should_receive(:inspect).and_return('key') val.should_receive(:inspect).and_return('val') - - { key => val }.send(@method).should == '{key=>val}' + expected = ruby_version_is("3.4") ? "{key => val}" : "{key=>val}" + { key => val }.send(@method).should == expected end it "does not call #to_s on a String returned from #inspect" do - str = "abc" + str = +"abc" str.should_not_receive(:to_s) - - { a: str }.send(@method).should == '{:a=>"abc"}' + expected = ruby_version_is("3.4") ? '{a: "abc"}' : '{:a=>"abc"}' + { a: str }.send(@method).should == expected end it "calls #to_s on the object returned from #inspect if the Object isn't a String" do obj = mock("Hash#inspect/to_s calls #to_s") obj.should_receive(:inspect).and_return(obj) obj.should_receive(:to_s).and_return("abc") - - { a: obj }.send(@method).should == "{:a=>abc}" + expected = ruby_version_is("3.4") ? "{a: abc}" : "{:a=>abc}" + { a: obj }.send(@method).should == expected end it "does not call #to_str on the object returned from #inspect when it is not a String" do obj = mock("Hash#inspect/to_s does not call #to_str") obj.should_receive(:inspect).and_return(obj) obj.should_not_receive(:to_str) - - { a: obj }.send(@method).should =~ /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + { a: obj }.send(@method).should =~ expected_pattern end it "does not call #to_str on the object returned from #to_s when it is not a String" do @@ -51,8 +45,8 @@ describe :hash_to_s, shared: true do obj.should_receive(:inspect).and_return(obj) obj.should_receive(:to_s).and_return(obj) obj.should_not_receive(:to_str) - - { a: obj }.send(@method).should =~ /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/ + { a: obj }.send(@method).should =~ expected_pattern end it "does not swallow exceptions raised by #to_s" do @@ -66,24 +60,34 @@ describe :hash_to_s, shared: true do it "handles hashes with recursive values" do x = {} x[0] = x - x.send(@method).should == '{0=>{...}}' + expected = ruby_version_is("3.4") ? '{0 => {...}}' : '{0=>{...}}' + x.send(@method).should == expected x = {} y = {} x[0] = y y[1] = x - x.send(@method).should == "{0=>{1=>{...}}}" - y.send(@method).should == "{1=>{0=>{...}}}" + expected_x = ruby_version_is("3.4") ? '{0 => {1 => {...}}}' : '{0=>{1=>{...}}}' + expected_y = ruby_version_is("3.4") ? '{1 => {0 => {...}}}' : '{1=>{0=>{...}}}' + x.send(@method).should == expected_x + y.send(@method).should == expected_y end it "does not raise if inspected result is not default external encoding" do utf_16be = mock("utf_16be") - utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode!(Encoding::UTF_16BE)) - - {a: utf_16be}.send(@method).should == '{:a=>"utf_16be \u3042"}' + utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode(Encoding::UTF_16BE)) + expected = ruby_version_is("3.4") ? '{a: "utf_16be \u3042"}' : '{:a=>"utf_16be \u3042"}' + {a: utf_16be}.send(@method).should == expected end it "works for keys and values whose #inspect return a frozen String" do - { true => false }.to_s.should == "{true=>false}" + expected = ruby_version_is("3.4") ? "{true => false}" : "{true=>false}" + { true => false }.to_s.should == expected + end + + ruby_version_is "3.4" do + it "adds quotes to symbol keys that are not valid symbol literals" do + { "needs-quotes": 1 }.send(@method).should == '{"needs-quotes": 1}' + end end end diff --git a/spec/ruby/core/hash/shift_spec.rb b/spec/ruby/core/hash/shift_spec.rb index ea36488a04..3f31b9864c 100644 --- a/spec/ruby/core/hash/shift_spec.rb +++ b/spec/ruby/core/hash/shift_spec.rb @@ -30,45 +30,22 @@ describe "Hash#shift" do h.should == {} end - ruby_version_is '3.2' do - it "returns nil if the Hash is empty" do - h = {} - def h.default(key) - raise - end - h.shift.should == nil - end - end - - ruby_version_is ''...'3.2' do - it "calls #default with nil if the Hash is empty" do - h = {} - def h.default(key) - key.should == nil - :foo - end - h.shift.should == :foo + it "returns nil if the Hash is empty" do + h = {} + def h.default(key) + raise end + h.shift.should == nil end it "returns nil from an empty hash" do {}.shift.should == nil end - ruby_version_is '3.2' do - it "returns nil for empty hashes with defaults and default procs" do - Hash.new(5).shift.should == nil - h = Hash.new { |*args| args } - h.shift.should == nil - end - end - - ruby_version_is ''...'3.2' do - it "returns (computed) default for empty hashes" do - Hash.new(5).shift.should == 5 - h = Hash.new { |*args| args } - h.shift.should == [h, nil] - end + it "returns nil for empty hashes with defaults and default procs" do + Hash.new(5).shift.should == nil + h = Hash.new { |*args| args } + h.shift.should == nil end it "preserves Hash invariants when removing the last item" do diff --git a/spec/ruby/core/hash/slice_spec.rb b/spec/ruby/core/hash/slice_spec.rb index e3046d83d7..4fcc01f9a6 100644 --- a/spec/ruby/core/hash/slice_spec.rb +++ b/spec/ruby/core/hash/slice_spec.rb @@ -50,4 +50,25 @@ describe "Hash#slice" do ScratchPad.recorded.should == [] end + + it "does not retain the default value" do + h = Hash.new(1) + h.slice(:a).default.should be_nil + h[:a] = 1 + h.slice(:a).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.slice(:a).default_proc.should be_nil + h[:a] = 1 + h.slice(:a).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.slice(:a) + h2.compare_by_identity?.should == true + end end diff --git a/spec/ruby/core/hash/to_h_spec.rb b/spec/ruby/core/hash/to_h_spec.rb index 75ebce68b1..f84fd7b503 100644 --- a/spec/ruby/core/hash/to_h_spec.rb +++ b/spec/ruby/core/hash/to_h_spec.rb @@ -19,17 +19,22 @@ describe "Hash#to_h" do @h[:foo].should == :bar end - it "copies the default" do + it "retains the default" do @h.default = 42 @h.to_h.default.should == 42 @h[:hello].should == 42 end - it "copies the default_proc" do + it "retains the default_proc" do @h.default_proc = prc = Proc.new{ |h, k| h[k] = 2 * k } @h.to_h.default_proc.should == prc @h[42].should == 84 end + + it "retains compare_by_identity flag" do + @h.compare_by_identity + @h.to_h.compare_by_identity?.should == true + end end context "with block" do @@ -37,6 +42,16 @@ describe "Hash#to_h" do { a: 1, b: 2 }.to_h { |k, v| [k.to_s, v*v]}.should == { "a" => 1, "b" => 4 } end + it "passes to a block each pair's key and value as separate arguments" do + ScratchPad.record [] + { a: 1, b: 2 }.to_h { |k, v| ScratchPad << [k, v]; [k, v] } + ScratchPad.recorded.sort.should == [[:a, 1], [:b, 2]] + + ScratchPad.record [] + { a: 1, b: 2 }.to_h { |*args| ScratchPad << args; [args[0], args[1]] } + ScratchPad.recorded.sort.should == [[:a, 1], [:b, 2]] + end + it "raises ArgumentError if block returns longer or shorter array" do -> do { a: 1, b: 2 }.to_h { |k, v| [k.to_s, v*v, 1] } @@ -68,5 +83,24 @@ describe "Hash#to_h" do { a: 1 }.to_h { |k| x } end.should raise_error(TypeError, /wrong element type MockObject/) end + + it "does not retain the default value" do + h = Hash.new(1) + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.default_proc.should be_nil + end + + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.to_h { |k, v| [k.to_s, v*v]} + h2.compare_by_identity?.should == false + end end end diff --git a/spec/ruby/core/hash/transform_keys_spec.rb b/spec/ruby/core/hash/transform_keys_spec.rb index 2fbb17a8e2..e2eeab1813 100644 --- a/spec/ruby/core/hash/transform_keys_spec.rb +++ b/spec/ruby/core/hash/transform_keys_spec.rb @@ -54,6 +54,27 @@ describe "Hash#transform_keys" do it "allows a combination of hash and block argument" do @hash.transform_keys({ a: :A }, &:to_s).should == { A: 1, 'b' => 2, 'c' => 3 } end + + it "does not retain the default value" do + h = Hash.new(1) + h.transform_keys(&:succ).default.should be_nil + h[:a] = 1 + h.transform_keys(&:succ).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.transform_values(&:succ).default_proc.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default_proc.should be_nil + end + + it "does not retain compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.transform_keys(&:succ) + h2.compare_by_identity?.should == false + end end describe "Hash#transform_keys!" do @@ -76,24 +97,12 @@ describe "Hash#transform_keys!" do @hash.should == { b: 1, c: 2, d: 3, e: 4 } end - ruby_version_is ""..."3.0.2" do # https://bugs.ruby-lang.org/issues/17735 - it "returns the processed keys if we break from the block" do - @hash.transform_keys! do |v| - break if v == :c - v.succ - end - @hash.should == { b: 1, c: 2 } - end - end - - ruby_version_is "3.0.2" do - it "returns the processed keys and non evaluated keys if we break from the block" do - @hash.transform_keys! do |v| - break if v == :c - v.succ - end - @hash.should == { b: 1, c: 2, d: 4 } + it "returns the processed keys and non evaluated keys if we break from the block" do + @hash.transform_keys! do |v| + break if v == :c + v.succ end + @hash.should == { b: 1, c: 2, d: 4 } end it "keeps later pair if new keys conflict" do @@ -129,7 +138,7 @@ describe "Hash#transform_keys!" do end it "raises a FrozenError on hash argument" do - ->{ @hash.transform_keys!({ a: :A, b: :B, c: :C }) }.should raise_error(FrozenError) + ->{ @hash.transform_keys!({ a: :A, b: :B, c: :C }) }.should raise_error(FrozenError) end context "when no block is given" do diff --git a/spec/ruby/core/hash/transform_values_spec.rb b/spec/ruby/core/hash/transform_values_spec.rb index acb469416a..4a0ae8a5a5 100644 --- a/spec/ruby/core/hash/transform_values_spec.rb +++ b/spec/ruby/core/hash/transform_values_spec.rb @@ -39,6 +39,27 @@ describe "Hash#transform_values" do r[:foo].should == 84 r.class.should == Hash end + + it "does not retain the default value" do + h = Hash.new(1) + h.transform_values(&:succ).default.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default.should be_nil + end + + it "does not retain the default_proc" do + pr = proc { |h, k| h[k] = [] } + h = Hash.new(&pr) + h.transform_values(&:succ).default_proc.should be_nil + h[:a] = 1 + h.transform_values(&:succ).default_proc.should be_nil + end + + it "retains compare_by_identity flag" do + h = { a: 9, c: 4 }.compare_by_identity + h2 = h.transform_values(&:succ) + h2.compare_by_identity?.should == true + end end describe "Hash#transform_values!" do diff --git a/spec/ruby/core/integer/ceil_spec.rb b/spec/ruby/core/integer/ceil_spec.rb index 13bdaf838d..eb633fba78 100644 --- a/spec/ruby/core/integer/ceil_spec.rb +++ b/spec/ruby/core/integer/ceil_spec.rb @@ -1,11 +1,16 @@ require_relative '../../spec_helper' require_relative 'shared/to_i' require_relative 'shared/integer_rounding' +require_relative 'shared/integer_ceil_precision' describe "Integer#ceil" do it_behaves_like :integer_to_i, :ceil it_behaves_like :integer_rounding_positive_precision, :ceil + context "with precision" do + it_behaves_like :integer_ceil_precision, :Integer + end + context "precision argument specified as part of the ceil method is negative" do it "returns the smallest integer greater than self with at least precision.abs trailing zeros" do 18.ceil(-1).should eql(20) diff --git a/spec/ruby/core/integer/ceildiv_spec.rb b/spec/ruby/core/integer/ceildiv_spec.rb index 18d07c66d0..c6e22a457d 100644 --- a/spec/ruby/core/integer/ceildiv_spec.rb +++ b/spec/ruby/core/integer/ceildiv_spec.rb @@ -1,22 +1,20 @@ require_relative '../../spec_helper' describe "Integer#ceildiv" do - ruby_version_is '3.2' do - it "returns a quotient of division which is rounded up to the nearest integer" do - 0.ceildiv(3).should eql(0) - 1.ceildiv(3).should eql(1) - 3.ceildiv(3).should eql(1) - 4.ceildiv(3).should eql(2) + it "returns a quotient of division which is rounded up to the nearest integer" do + 0.ceildiv(3).should eql(0) + 1.ceildiv(3).should eql(1) + 3.ceildiv(3).should eql(1) + 4.ceildiv(3).should eql(2) - 4.ceildiv(-3).should eql(-1) - -4.ceildiv(3).should eql(-1) - -4.ceildiv(-3).should eql(2) + 4.ceildiv(-3).should eql(-1) + -4.ceildiv(3).should eql(-1) + -4.ceildiv(-3).should eql(2) - 3.ceildiv(1.2).should eql(3) - 3.ceildiv(6/5r).should eql(3) + 3.ceildiv(1.2).should eql(3) + 3.ceildiv(6/5r).should eql(3) - (10**100-11).ceildiv(10**99-1).should eql(10) - (10**100-9).ceildiv(10**99-1).should eql(11) - end + (10**100-11).ceildiv(10**99-1).should eql(10) + (10**100-9).ceildiv(10**99-1).should eql(11) end end diff --git a/spec/ruby/core/integer/chr_spec.rb b/spec/ruby/core/integer/chr_spec.rb index 8fe20ff812..39cafe2874 100644 --- a/spec/ruby/core/integer/chr_spec.rb +++ b/spec/ruby/core/integer/chr_spec.rb @@ -10,12 +10,12 @@ describe "Integer#chr without argument" do end it "raises a RangeError is self is less than 0" do - -> { -1.chr }.should raise_error(RangeError) - -> { (-bignum_value).chr }.should raise_error(RangeError) + -> { -1.chr }.should raise_error(RangeError, /-1 out of char range/) + -> { (-bignum_value).chr }.should raise_error(RangeError, /bignum out of char range/) end it "raises a RangeError if self is too large" do - -> { 2206368128.chr(Encoding::UTF_8) }.should raise_error(RangeError) + -> { 2206368128.chr(Encoding::UTF_8) }.should raise_error(RangeError, /2206368128 out of char range/) end describe "when Encoding.default_internal is nil" do @@ -48,8 +48,8 @@ describe "Integer#chr without argument" do end it "raises a RangeError is self is greater than 255" do - -> { 256.chr }.should raise_error(RangeError) - -> { bignum_value.chr }.should raise_error(RangeError) + -> { 256.chr }.should raise_error(RangeError, /256 out of char range/) + -> { bignum_value.chr }.should raise_error(RangeError, /bignum out of char range/) end end @@ -137,7 +137,7 @@ describe "Integer#chr without argument" do [620, "TIS-620"] ].each do |integer, encoding_name| Encoding.default_internal = Encoding.find(encoding_name) - -> { integer.chr }.should raise_error(RangeError) + -> { integer.chr }.should raise_error(RangeError, /(invalid codepoint|out of char range)/) end end end @@ -165,12 +165,12 @@ describe "Integer#chr with an encoding argument" do # http://redmine.ruby-lang.org/issues/4869 it "raises a RangeError is self is less than 0" do - -> { -1.chr(Encoding::UTF_8) }.should raise_error(RangeError) - -> { (-bignum_value).chr(Encoding::EUC_JP) }.should raise_error(RangeError) + -> { -1.chr(Encoding::UTF_8) }.should raise_error(RangeError, /-1 out of char range/) + -> { (-bignum_value).chr(Encoding::EUC_JP) }.should raise_error(RangeError, /bignum out of char range/) end it "raises a RangeError if self is too large" do - -> { 2206368128.chr(Encoding::UTF_8) }.should raise_error(RangeError) + -> { 2206368128.chr(Encoding::UTF_8) }.should raise_error(RangeError, /2206368128 out of char range/) end it "returns a String with the specified encoding" do diff --git a/spec/ruby/core/integer/coerce_spec.rb b/spec/ruby/core/integer/coerce_spec.rb index f1f3256032..1d6dc9713f 100644 --- a/spec/ruby/core/integer/coerce_spec.rb +++ b/spec/ruby/core/integer/coerce_spec.rb @@ -1,7 +1,5 @@ require_relative '../../spec_helper' -require 'bigdecimal' - describe "Integer#coerce" do context "fixnum" do describe "when given a Fixnum" do @@ -90,15 +88,4 @@ describe "Integer#coerce" do ary.should == [1.2, a.to_f] end end - - context "bigdecimal" do - it "produces Floats" do - x, y = 3.coerce(BigDecimal("3.4")) - x.class.should == Float - x.should == 3.4 - y.class.should == Float - y.should == 3.0 - end - end - end diff --git a/spec/ruby/core/integer/constants_spec.rb b/spec/ruby/core/integer/constants_spec.rb index 2077ad451e..937806c72f 100644 --- a/spec/ruby/core/integer/constants_spec.rb +++ b/spec/ruby/core/integer/constants_spec.rb @@ -1,41 +1,13 @@ require_relative '../../spec_helper' describe "Fixnum" do - ruby_version_is ""..."3.2" do - it "is unified into Integer" do - suppress_warning do - Fixnum.should equal(Integer) - end - end - - it "is deprecated" do - -> { Fixnum }.should complain(/constant ::Fixnum is deprecated/) - end - end - - ruby_version_is "3.2" do - it "is no longer defined" do - Object.should_not.const_defined?(:Fixnum) - end + it "is no longer defined" do + Object.should_not.const_defined?(:Fixnum) end end describe "Bignum" do - ruby_version_is ""..."3.2" do - it "is unified into Integer" do - suppress_warning do - Bignum.should equal(Integer) - end - end - - it "is deprecated" do - -> { Bignum }.should complain(/constant ::Bignum is deprecated/) - end - end - - ruby_version_is "3.2" do - it "is no longer defined" do - Object.should_not.const_defined?(:Bignum) - end + it "is no longer defined" do + Object.should_not.const_defined?(:Bignum) end end diff --git a/spec/ruby/core/integer/div_spec.rb b/spec/ruby/core/integer/div_spec.rb index 344e095179..2eb9c0623b 100644 --- a/spec/ruby/core/integer/div_spec.rb +++ b/spec/ruby/core/integer/div_spec.rb @@ -143,4 +143,12 @@ describe "Integer#div" do -> { @bignum.div(-0) }.should raise_error(ZeroDivisionError) end end + + context "rational" do + it "returns self divided by the given argument as an Integer" do + 2.div(6/5r).should == 1 + 1.div(6/5r).should == 0 + 5.div(6/5r).should == 4 + end + end end diff --git a/spec/ruby/core/integer/divide_spec.rb b/spec/ruby/core/integer/divide_spec.rb index a878c4668c..0d5e16e986 100644 --- a/spec/ruby/core/integer/divide_spec.rb +++ b/spec/ruby/core/integer/divide_spec.rb @@ -12,6 +12,17 @@ describe "Integer#/" do it "supports dividing negative numbers" do (-1 / 10).should == -1 + (-1 / 10**10).should == -1 + (-1 / 10**20).should == -1 + end + + it "preservers sign correctly" do + (4 / 3).should == 1 + (4 / -3).should == -2 + (-4 / 3).should == -2 + (-4 / -3).should == 1 + (0 / -3).should == 0 + (0 / 3).should == 0 end it "returns result the same class as the argument" do @@ -58,6 +69,15 @@ describe "Integer#/" do ((10**50) / -(10**40 + 1)).should == -10000000000 end + it "preservers sign correctly" do + (4 / bignum_value).should == 0 + (4 / -bignum_value).should == -1 + (-4 / bignum_value).should == -1 + (-4 / -bignum_value).should == 0 + (0 / bignum_value).should == 0 + (0 / -bignum_value).should == 0 + end + it "returns self divided by Float" do not_supported_on :opal do (bignum_value(88) / 4294967295.0).should be_close(4294967297.0, TOLERANCE) @@ -86,4 +106,21 @@ describe "Integer#/" do -> { @bignum / :symbol }.should raise_error(TypeError) end end + + it "coerces the RHS and calls #coerce" do + obj = mock("integer plus") + obj.should_receive(:coerce).with(6).and_return([6, 3]) + (6 / obj).should == 2 + end + + it "coerces the RHS and calls #coerce even if it's private" do + obj = Object.new + class << obj + private def coerce(n) + [n, 3] + end + end + + (6 / obj).should == 2 + end end diff --git a/spec/ruby/core/integer/floor_spec.rb b/spec/ruby/core/integer/floor_spec.rb index aaa816fdc5..8fb84d58cb 100644 --- a/spec/ruby/core/integer/floor_spec.rb +++ b/spec/ruby/core/integer/floor_spec.rb @@ -1,19 +1,13 @@ require_relative '../../spec_helper' require_relative 'shared/to_i' require_relative 'shared/integer_rounding' +require_relative 'shared/integer_floor_precision' describe "Integer#floor" do it_behaves_like :integer_to_i, :floor it_behaves_like :integer_rounding_positive_precision, :floor - context "precision argument specified as part of the floor method is negative" do - it "returns the largest integer less than self with at least precision.abs trailing zeros" do - 1832.floor(-1).should eql(1830) - 1832.floor(-2).should eql(1800) - 1832.floor(-3).should eql(1000) - -1832.floor(-1).should eql(-1840) - -1832.floor(-2).should eql(-1900) - -1832.floor(-3).should eql(-2000) - end + context "with precision" do + it_behaves_like :integer_floor_precision, :Integer end end diff --git a/spec/ruby/core/integer/left_shift_spec.rb b/spec/ruby/core/integer/left_shift_spec.rb index 0781371d93..86c2b18ae2 100644 --- a/spec/ruby/core/integer/left_shift_spec.rb +++ b/spec/ruby/core/integer/left_shift_spec.rb @@ -181,10 +181,8 @@ describe "Integer#<< (with n << m)" do (bignum_value << -(2**40)).should == 0 end - ruby_bug "#18517", ""..."3.2" do - it "returns 0 when m > 0 long and n == 0" do - (0 << (2**40)).should == 0 - end + it "returns 0 when m > 0 long and n == 0" do + (0 << (2**40)).should == 0 end it "returns 0 when m > 0 bignum and n == 0" do diff --git a/spec/ruby/core/integer/minus_spec.rb b/spec/ruby/core/integer/minus_spec.rb index aadf416a05..6072ba7c8b 100644 --- a/spec/ruby/core/integer/minus_spec.rb +++ b/spec/ruby/core/integer/minus_spec.rb @@ -40,4 +40,21 @@ describe "Integer#-" do -> { @bignum - :symbol }.should raise_error(TypeError) end end + + it "coerces the RHS and calls #coerce" do + obj = mock("integer plus") + obj.should_receive(:coerce).with(5).and_return([5, 10]) + (5 - obj).should == -5 + end + + it "coerces the RHS and calls #coerce even if it's private" do + obj = Object.new + class << obj + private def coerce(n) + [n, 10] + end + end + + (5 - obj).should == -5 + end end diff --git a/spec/ruby/core/integer/plus_spec.rb b/spec/ruby/core/integer/plus_spec.rb index d01a76ab58..38428e56c5 100644 --- a/spec/ruby/core/integer/plus_spec.rb +++ b/spec/ruby/core/integer/plus_spec.rb @@ -55,4 +55,21 @@ describe "Integer#+" do RUBY ruby_exe(code).should == "-1" end + + it "coerces the RHS and calls #coerce" do + obj = mock("integer plus") + obj.should_receive(:coerce).with(6).and_return([6, 3]) + (6 + obj).should == 9 + end + + it "coerces the RHS and calls #coerce even if it's private" do + obj = Object.new + class << obj + private def coerce(n) + [n, 3] + end + end + + (6 + obj).should == 9 + end end diff --git a/spec/ruby/core/integer/pow_spec.rb b/spec/ruby/core/integer/pow_spec.rb index 4712911095..ecaca01eff 100644 --- a/spec/ruby/core/integer/pow_spec.rb +++ b/spec/ruby/core/integer/pow_spec.rb @@ -19,13 +19,13 @@ describe "Integer#pow" do 2.pow(61, 5843009213693951).should eql 3697379018277258 2.pow(62, 5843009213693952).should eql 1551748822859776 2.pow(63, 5843009213693953).should eql 3103497645717974 - 2.pow(64, 5843009213693954).should eql 363986077738838 + 2.pow(64, 5843009213693954).should eql 363986077738838 end it "handles sign like #divmod does" do - 2.pow(5, 12).should == 8 - 2.pow(5, -12).should == -4 - -2.pow(5, 12).should == 4 + 2.pow(5, 12).should == 8 + 2.pow(5, -12).should == -4 + -2.pow(5, 12).should == 4 -2.pow(5, -12).should == -8 end diff --git a/spec/ruby/core/integer/remainder_spec.rb b/spec/ruby/core/integer/remainder_spec.rb index 96268b3af5..757e42fbe8 100644 --- a/spec/ruby/core/integer/remainder_spec.rb +++ b/spec/ruby/core/integer/remainder_spec.rb @@ -15,8 +15,8 @@ describe "Integer#remainder" do end it "keeps sign of self" do - 5.remainder( 3).should == 2 - 5.remainder(-3).should == 2 + 5.remainder( 3).should == 2 + 5.remainder(-3).should == 2 -5.remainder( 3).should == -2 -5.remainder(-3).should == -2 end diff --git a/spec/ruby/core/integer/right_shift_spec.rb b/spec/ruby/core/integer/right_shift_spec.rb index e91613d8d1..c902674e2f 100644 --- a/spec/ruby/core/integer/right_shift_spec.rb +++ b/spec/ruby/core/integer/right_shift_spec.rb @@ -203,10 +203,8 @@ describe "Integer#>> (with n >> m)" do (bignum_value >> (2**40)).should == 0 end - ruby_bug "#18517", ""..."3.2" do - it "returns 0 when m < 0 long and n == 0" do - (0 >> -(2**40)).should == 0 - end + it "returns 0 when m < 0 long and n == 0" do + (0 >> -(2**40)).should == 0 end it "returns 0 when m < 0 bignum and n == 0" do diff --git a/spec/ruby/core/integer/round_spec.rb b/spec/ruby/core/integer/round_spec.rb index 45ac126fd3..189384f11a 100644 --- a/spec/ruby/core/integer/round_spec.rb +++ b/spec/ruby/core/integer/round_spec.rb @@ -21,10 +21,8 @@ describe "Integer#round" do (-25 * 10**70).round(-71).should eql(-30 * 10**70) end - platform_is_not wordsize: 32 do - it "raises a RangeError when passed a big negative value" do - -> { 42.round(fixnum_min) }.should raise_error(RangeError) - end + it "raises a RangeError when passed a big negative value" do + -> { 42.round(min_long - 1) }.should raise_error(RangeError) end it "raises a RangeError when passed Float::INFINITY" do diff --git a/spec/ruby/core/integer/shared/exponent.rb b/spec/ruby/core/integer/shared/exponent.rb index 15df518b7e..5ef6d686d8 100644 --- a/spec/ruby/core/integer/shared/exponent.rb +++ b/spec/ruby/core/integer/shared/exponent.rb @@ -48,10 +48,18 @@ describe :integer_exponent, shared: true do (-1).send(@method, 4611686018427387905).should eql(-1) end - it "returns Float::INFINITY when the number is too big" do - -> { - 2.send(@method, 427387904).should == Float::INFINITY - }.should complain(/warning: in a\*\*b, b may be too big/) + ruby_version_is ""..."3.4" do + it "returns Float::INFINITY when the number is too big" do + -> { + 2.send(@method, 427387904).should == Float::INFINITY + }.should complain(/warning: in a\*\*b, b may be too big/) + end + end + + ruby_version_is "3.4" do + it "raises an ArgumentError when the number is too big" do + -> { 100000000.send(@method, 1000000000) }.should raise_error(ArgumentError) + end end it "raises a ZeroDivisionError for 0 ** -1" do @@ -108,13 +116,23 @@ describe :integer_exponent, shared: true do -> { @bignum.send(@method, :symbol) }.should raise_error(TypeError) end - it "switch to a Float when the values is too big" do - flt = nil - -> { - flt = @bignum.send(@method, @bignum) - }.should complain(/warning: in a\*\*b, b may be too big/) - flt.should be_kind_of(Float) - flt.infinite?.should == 1 + ruby_version_is ""..."3.4" do + it "switch to a Float when the values is too big" do + flt = nil + -> { + flt = @bignum.send(@method, @bignum) + }.should complain(/warning: in a\*\*b, b may be too big/) + flt.should be_kind_of(Float) + flt.infinite?.should == 1 + end + end + + ruby_version_is "3.4" do + it "does not switch to a Float when the values is too big" do + -> { + @bignum.send(@method, @bignum) + }.should raise_error(ArgumentError) + end end it "returns a complex number when negative and raised to a fractional power" do diff --git a/spec/ruby/core/integer/shared/integer_ceil_precision.rb b/spec/ruby/core/integer/shared/integer_ceil_precision.rb new file mode 100644 index 0000000000..9f31c2cf61 --- /dev/null +++ b/spec/ruby/core/integer/shared/integer_ceil_precision.rb @@ -0,0 +1,43 @@ +describe :integer_ceil_precision, shared: true do + context "precision is zero" do + it "returns integer self" do + send(@method, 0).ceil(0).should.eql?(0) + send(@method, 123).ceil(0).should.eql?(123) + send(@method, -123).ceil(0).should.eql?(-123) + end + end + + context "precision is positive" do + it "returns self" do + send(@method, 0).ceil(1).should.eql?(send(@method, 0)) + send(@method, 0).ceil(10).should.eql?(send(@method, 0)) + + send(@method, 123).ceil(10).should.eql?(send(@method, 123)) + send(@method, -123).ceil(10).should.eql?(send(@method, -123)) + end + end + + context "precision is negative" do + it "always returns 0 when self is 0" do + send(@method, 0).ceil(-1).should.eql?(0) + send(@method, 0).ceil(-10).should.eql?(0) + end + + it "returns largest integer less than self with at least precision.abs trailing zeros" do + send(@method, 123).ceil(-1).should.eql?(130) + send(@method, 123).ceil(-2).should.eql?(200) + send(@method, 123).ceil(-3).should.eql?(1000) + + send(@method, -123).ceil(-1).should.eql?(-120) + send(@method, -123).ceil(-2).should.eql?(-100) + send(@method, -123).ceil(-3).should.eql?(0) + end + + ruby_bug "#20654", ""..."3.4" do + it "returns 10**precision.abs when precision.abs is larger than the number digits of self" do + send(@method, 123).ceil(-20).should.eql?(100000000000000000000) + send(@method, 123).ceil(-50).should.eql?(100000000000000000000000000000000000000000000000000) + end + end + end +end diff --git a/spec/ruby/core/integer/shared/integer_floor_precision.rb b/spec/ruby/core/integer/shared/integer_floor_precision.rb new file mode 100644 index 0000000000..4c5888c6c4 --- /dev/null +++ b/spec/ruby/core/integer/shared/integer_floor_precision.rb @@ -0,0 +1,43 @@ +describe :integer_floor_precision, shared: true do + context "precision is zero" do + it "returns integer self" do + send(@method, 0).floor(0).should.eql?(0) + send(@method, 123).floor(0).should.eql?(123) + send(@method, -123).floor(0).should.eql?(-123) + end + end + + context "precision is positive" do + it "returns self" do + send(@method, 0).floor(1).should.eql?(send(@method, 0)) + send(@method, 0).floor(10).should.eql?(send(@method, 0)) + + send(@method, 123).floor(10).should.eql?(send(@method, 123)) + send(@method, -123).floor(10).should.eql?(send(@method, -123)) + end + end + + context "precision is negative" do + it "always returns 0 when self is 0" do + send(@method, 0).floor(-1).should.eql?(0) + send(@method, 0).floor(-10).should.eql?(0) + end + + it "returns largest integer less than self with at least precision.abs trailing zeros" do + send(@method, 123).floor(-1).should.eql?(120) + send(@method, 123).floor(-2).should.eql?(100) + send(@method, 123).floor(-3).should.eql?(0) + + send(@method, -123).floor(-1).should.eql?(-130) + send(@method, -123).floor(-2).should.eql?(-200) + send(@method, -123).floor(-3).should.eql?(-1000) + end + + ruby_bug "#20654", ""..."3.4" do + it "returns -(10**precision.abs) when self is negative and precision.abs is larger than the number digits of self" do + send(@method, -123).floor(-20).should.eql?(-100000000000000000000) + send(@method, -123).floor(-50).should.eql?(-100000000000000000000000000000000000000000000000000) + end + end + end +end diff --git a/spec/ruby/core/integer/shared/modulo.rb b/spec/ruby/core/integer/shared/modulo.rb index f678a10806..d91af1e924 100644 --- a/spec/ruby/core/integer/shared/modulo.rb +++ b/spec/ruby/core/integer/shared/modulo.rb @@ -1,6 +1,12 @@ describe :integer_modulo, shared: true do context "fixnum" do it "returns the modulus obtained from dividing self by the given argument" do + # test all possible combinations: + # - integer/double/bignum argument + # - positive/negative argument + # - positive/negative self + # - self greater/smaller than argument + 13.send(@method, 4).should == 1 4.send(@method, 13).should == 4 @@ -16,8 +22,22 @@ describe :integer_modulo, shared: true do (200).send(@method, -256).should == -56 (1000).send(@method, -512).should == -24 + 13.send(@method, -4.0).should == -3.0 + 4.send(@method, -13.0).should == -9.0 + + -13.send(@method, -4.0).should == -1.0 + -4.send(@method, -13.0).should == -4.0 + + -13.send(@method, 4.0).should == 3.0 + -4.send(@method, 13.0).should == 9.0 + 1.send(@method, 2.0).should == 1.0 200.send(@method, bignum_value).should == 200 + + 4.send(@method, bignum_value(10)).should == 4 + 4.send(@method, -bignum_value(10)).should == -18446744073709551622 + -4.send(@method, bignum_value(10)).should == 18446744073709551622 + -4.send(@method, -bignum_value(10)).should == -4 end it "raises a ZeroDivisionError when the given argument is 0" do @@ -44,15 +64,35 @@ describe :integer_modulo, shared: true do context "bignum" do before :each do - @bignum = bignum_value + @bignum = bignum_value(10) end it "returns the modulus obtained from dividing self by the given argument" do + # test all possible combinations: + # - integer/double/bignum argument + # - positive/negative argument + # - positive/negative self + # - self greater/smaller than argument + @bignum.send(@method, 5).should == 1 @bignum.send(@method, -5).should == -4 - @bignum.send(@method, -100).should == -84 + (-@bignum).send(@method, 5).should == 4 + (-@bignum).send(@method, -5).should == -1 + @bignum.send(@method, 2.22).should be_close(1.5603603603605034, TOLERANCE) - @bignum.send(@method, bignum_value(10)).should == 18446744073709551616 + @bignum.send(@method, -2.22).should be_close(-0.6596396396394968, TOLERANCE) + (-@bignum).send(@method, 2.22).should be_close(0.6596396396394968, TOLERANCE) + (-@bignum).send(@method, -2.22).should be_close(-1.5603603603605034, TOLERANCE) + + @bignum.send(@method, @bignum + 10).should == 18446744073709551626 + @bignum.send(@method, -(@bignum + 10)).should == -10 + (-@bignum).send(@method, @bignum + 10).should == 10 + (-@bignum).send(@method, -(@bignum + 10)).should == -18446744073709551626 + + (@bignum + 10).send(@method, @bignum).should == 10 + (@bignum + 10).send(@method, -@bignum).should == -18446744073709551616 + (-(@bignum + 10)).send(@method, @bignum).should == 18446744073709551616 + (-(@bignum + 10)).send(@method, -@bignum).should == -10 end it "raises a ZeroDivisionError when the given argument is 0" do diff --git a/spec/ruby/core/integer/size_spec.rb b/spec/ruby/core/integer/size_spec.rb index a134e82384..725e9eb062 100644 --- a/spec/ruby/core/integer/size_spec.rb +++ b/spec/ruby/core/integer/size_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' describe "Integer#size" do - platform_is wordsize: 32 do + platform_is c_long_size: 32 do it "returns the number of bytes in the machine representation of self" do -1.size.should == 4 0.size.should == 4 @@ -9,7 +9,7 @@ describe "Integer#size" do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "returns the number of bytes in the machine representation of self" do -1.size.should == 8 0.size.should == 8 diff --git a/spec/ruby/core/integer/try_convert_spec.rb b/spec/ruby/core/integer/try_convert_spec.rb index 4bc7d3851a..8a0ca671a9 100644 --- a/spec/ruby/core/integer/try_convert_spec.rb +++ b/spec/ruby/core/integer/try_convert_spec.rb @@ -1,50 +1,48 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is "3.1" do - describe "Integer.try_convert" do - it "returns the argument if it's an Integer" do - x = 42 - Integer.try_convert(x).should equal(x) - end +describe "Integer.try_convert" do + it "returns the argument if it's an Integer" do + x = 42 + Integer.try_convert(x).should equal(x) + end - it "returns nil when the argument does not respond to #to_int" do - Integer.try_convert(Object.new).should be_nil - end + it "returns nil when the argument does not respond to #to_int" do + Integer.try_convert(Object.new).should be_nil + end - it "sends #to_int to the argument and returns the result if it's nil" do - obj = mock("to_int") - obj.should_receive(:to_int).and_return(nil) - Integer.try_convert(obj).should be_nil - end + it "sends #to_int to the argument and returns the result if it's nil" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(nil) + Integer.try_convert(obj).should be_nil + end - it "sends #to_int to the argument and returns the result if it's an Integer" do - x = 234 - obj = mock("to_int") - obj.should_receive(:to_int).and_return(x) - Integer.try_convert(obj).should equal(x) - end + it "sends #to_int to the argument and returns the result if it's an Integer" do + x = 234 + obj = mock("to_int") + obj.should_receive(:to_int).and_return(x) + Integer.try_convert(obj).should equal(x) + end - it "sends #to_int to the argument and raises TypeError if it's not a kind of Integer" do - obj = mock("to_int") - obj.should_receive(:to_int).and_return(Object.new) - -> { - Integer.try_convert obj - }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives Object)") - end + it "sends #to_int to the argument and raises TypeError if it's not a kind of Integer" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return(Object.new) + -> { + Integer.try_convert obj + }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives Object)") + end - it "responds with a different error message when it raises a TypeError, depending on the type of the non-Integer object :to_int returns" do - obj = mock("to_int") - obj.should_receive(:to_int).and_return("A String") - -> { - Integer.try_convert obj - }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives String)") - end + it "responds with a different error message when it raises a TypeError, depending on the type of the non-Integer object :to_int returns" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return("A String") + -> { + Integer.try_convert obj + }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives String)") + end - it "does not rescue exceptions raised by #to_int" do - obj = mock("to_int") - obj.should_receive(:to_int).and_raise(RuntimeError) - -> { Integer.try_convert obj }.should raise_error(RuntimeError) - end + it "does not rescue exceptions raised by #to_int" do + obj = mock("to_int") + obj.should_receive(:to_int).and_raise(RuntimeError) + -> { Integer.try_convert obj }.should raise_error(RuntimeError) end end diff --git a/spec/ruby/core/io/autoclose_spec.rb b/spec/ruby/core/io/autoclose_spec.rb new file mode 100644 index 0000000000..715ada7c93 --- /dev/null +++ b/spec/ruby/core/io/autoclose_spec.rb @@ -0,0 +1,77 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "IO#autoclose?" do + before :each do + @io = IOSpecs.io_fixture "lines.txt" + end + + after :each do + @io.autoclose = true unless @io.closed? + @io.close unless @io.closed? + end + + it "is set to true by default" do + @io.should.autoclose? + end + + it "cannot be queried on a closed IO object" do + @io.close + -> { @io.autoclose? }.should raise_error(IOError, /closed stream/) + end +end + +describe "IO#autoclose=" do + before :each do + @io = IOSpecs.io_fixture "lines.txt" + end + + after :each do + @io.autoclose = true unless @io.closed? + @io.close unless @io.closed? + end + + it "can be set to true" do + @io.autoclose = false + @io.autoclose = true + @io.should.autoclose? + end + + it "can be set to false" do + @io.autoclose = true + @io.autoclose = false + @io.should_not.autoclose? + end + + it "can be set to any truthy value" do + @io.autoclose = false + @io.autoclose = 42 + @io.should.autoclose? + + @io.autoclose = false + @io.autoclose = Object.new + @io.should.autoclose? + end + + it "can be set to any falsy value" do + @io.autoclose = true + @io.autoclose = nil + @io.should_not.autoclose? + end + + it "can be set multiple times" do + @io.autoclose = true + @io.should.autoclose? + + @io.autoclose = false + @io.should_not.autoclose? + + @io.autoclose = true + @io.should.autoclose? + end + + it "cannot be set on a closed IO object" do + @io.close + -> { @io.autoclose = false }.should raise_error(IOError, /closed stream/) + end +end diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb index a3f752d8f9..9e36b84da9 100644 --- a/spec/ruby/core/io/binread_spec.rb +++ b/spec/ruby/core/io/binread_spec.rb @@ -44,4 +44,14 @@ describe "IO.binread" do it "raises an Errno::EINVAL when not passed a valid offset" do -> { IO.binread @fname, 0, -1 }.should raise_error(Errno::EINVAL) end + + ruby_version_is "3.3"..."4.0" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + IO.binread(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end + end end diff --git a/spec/ruby/core/io/buffer/empty_spec.rb b/spec/ruby/core/io/buffer/empty_spec.rb new file mode 100644 index 0000000000..e1fd4ab6a2 --- /dev/null +++ b/spec/ruby/core/io/buffer/empty_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' +require_relative 'shared/null_and_empty' + +describe "IO::Buffer#empty?" do + after :each do + @buffer&.free + @buffer = nil + end + + it_behaves_like :io_buffer_null_and_empty, :empty? + + it "is true for a 0-length String-backed buffer created with .for" do + @buffer = IO::Buffer.for("") + @buffer.empty?.should be_true + end + + ruby_version_is "3.3" do + it "is true for a 0-length String-backed buffer created with .string" do + IO::Buffer.string(0) do |buffer| + buffer.empty?.should be_true + end + end + end + + it "is true for a 0-length slice of a buffer with size > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(3, 0).empty?.should be_true + end +end diff --git a/spec/ruby/core/io/buffer/external_spec.rb b/spec/ruby/core/io/buffer/external_spec.rb new file mode 100644 index 0000000000..4377a38357 --- /dev/null +++ b/spec/ruby/core/io/buffer/external_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#external?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.external?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.external?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.external?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.external?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is true for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.external?.should be_true + end + + it "is true for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.external?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is true" do + IO::Buffer.string(4) do |buffer| + buffer.external?.should be_true + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.external?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.external?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.external?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.external?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.external?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.external?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/free_spec.rb b/spec/ruby/core/io/buffer/free_spec.rb new file mode 100644 index 0000000000..f3a4918978 --- /dev/null +++ b/spec/ruby/core/io/buffer/free_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#free" do + context "with a buffer created with .new" do + it "frees internal memory and nullifies the buffer" do + buffer = IO::Buffer.new(4) + buffer.free + buffer.null?.should be_true + end + + it "frees mapped memory and nullifies the buffer" do + buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + buffer.free + buffer.null?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "frees mapped memory and nullifies the buffer" do + File.open(__FILE__, "r") do |file| + buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + buffer.free + buffer.null?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = +"test" + buffer = IO::Buffer.for(string) + # Read-only buffer, can't modify the string. + buffer.free + buffer.null?.should be_true + end + end + + context "with a block" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = +"test" + IO::Buffer.for(string) do |buffer| + buffer.set_string("meat") + buffer.free + buffer.null?.should be_true + end + string.should == "meat" + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "disassociates the buffer from the string and nullifies the buffer" do + string = + IO::Buffer.string(4) do |buffer| + buffer.set_string("meat") + buffer.free + buffer.null?.should be_true + end + string.should == "meat" + end + end + end + + it "can be called repeatedly without an error" do + buffer = IO::Buffer.new(4) + buffer.free + buffer.null?.should be_true + buffer.free + buffer.null?.should be_true + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + buffer = IO::Buffer.new(4) + buffer.locked do + -> { buffer.free }.should raise_error(IO::Buffer::LockedError, "Buffer is locked!") + end + buffer.free + buffer.null?.should be_true + end + + context "with a slice of a buffer" do + it "nullifies the slice, not touching the buffer" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + + slice.free + slice.null?.should be_true + buffer.null?.should be_false + + buffer.free + end + + it "nullifies buffer, invalidating the slice" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + + buffer.free + slice.null?.should be_false + slice.valid?.should be_false + end + end +end diff --git a/spec/ruby/core/io/buffer/initialize_spec.rb b/spec/ruby/core/io/buffer/initialize_spec.rb new file mode 100644 index 0000000000..c86d1e7f1d --- /dev/null +++ b/spec/ruby/core/io/buffer/initialize_spec.rb @@ -0,0 +1,103 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#initialize" do + after :each do + @buffer&.free + @buffer = nil + end + + it "creates a new zero-filled buffer with default size" do + @buffer = IO::Buffer.new + @buffer.size.should == IO::Buffer::DEFAULT_SIZE + @buffer.each(:U8).should.all? { |_offset, value| value.eql?(0) } + end + + it "creates a buffer with default state" do + @buffer = IO::Buffer.new + @buffer.should_not.shared? + @buffer.should_not.readonly? + + @buffer.should_not.empty? + @buffer.should_not.null? + + # This is run-time state, set by #locked. + @buffer.should_not.locked? + end + + context "with size argument" do + it "creates a new internal buffer if size is less than IO::Buffer::PAGE_SIZE" do + size = IO::Buffer::PAGE_SIZE - 1 + @buffer = IO::Buffer.new(size) + @buffer.size.should == size + @buffer.should.internal? + @buffer.should_not.mapped? + @buffer.should_not.empty? + end + + it "creates a new mapped buffer if size is greater than or equal to IO::Buffer::PAGE_SIZE" do + size = IO::Buffer::PAGE_SIZE + @buffer = IO::Buffer.new(size) + @buffer.size.should == size + @buffer.should_not.internal? + @buffer.should.mapped? + @buffer.should_not.empty? + end + + it "creates a null buffer if size is 0" do + @buffer = IO::Buffer.new(0) + @buffer.size.should.zero? + @buffer.should_not.internal? + @buffer.should_not.mapped? + @buffer.should.null? + @buffer.should.empty? + end + + it "raises TypeError if size is not an Integer" do + -> { IO::Buffer.new(nil) }.should raise_error(TypeError, "not an Integer") + -> { IO::Buffer.new(10.0) }.should raise_error(TypeError, "not an Integer") + end + + it "raises ArgumentError if size is negative" do + -> { IO::Buffer.new(-1) }.should raise_error(ArgumentError, "Size can't be negative!") + end + end + + context "with size and flags arguments" do + it "forces mapped buffer with IO::Buffer::MAPPED flag" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE - 1, IO::Buffer::MAPPED) + @buffer.should.mapped? + @buffer.should_not.internal? + @buffer.should_not.empty? + end + + it "forces internal buffer with IO::Buffer::INTERNAL flag" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::INTERNAL) + @buffer.should.internal? + @buffer.should_not.mapped? + @buffer.should_not.empty? + end + + it "raises IO::Buffer::AllocationError if neither IO::Buffer::MAPPED nor IO::Buffer::INTERNAL is given" do + -> { IO::Buffer.new(10, IO::Buffer::READONLY) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + -> { IO::Buffer.new(10, 0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + end + + ruby_version_is "3.3" do + it "raises ArgumentError if flags is negative" do + -> { IO::Buffer.new(10, -1) }.should raise_error(ArgumentError, "Flags can't be negative!") + end + end + + ruby_version_is ""..."3.3" do + it "raises IO::Buffer::AllocationError with non-Integer flags" do + -> { IO::Buffer.new(10, 0.0) }.should raise_error(IO::Buffer::AllocationError, "Could not allocate buffer!") + end + end + + ruby_version_is "3.3" do + it "raises TypeError with non-Integer flags" do + -> { IO::Buffer.new(10, 0.0) }.should raise_error(TypeError, "not an Integer") + end + end + end +end diff --git a/spec/ruby/core/io/buffer/internal_spec.rb b/spec/ruby/core/io/buffer/internal_spec.rb new file mode 100644 index 0000000000..409699cc3c --- /dev/null +++ b/spec/ruby/core/io/buffer/internal_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#internal?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is true for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.internal?.should be_true + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.internal?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.internal?.should be_false + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.internal?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.internal?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.internal?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.internal?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.internal?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.internal?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.internal?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.internal?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.internal?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.internal?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/locked_spec.rb b/spec/ruby/core/io/buffer/locked_spec.rb new file mode 100644 index 0000000000..4ffa569fd2 --- /dev/null +++ b/spec/ruby/core/io/buffer/locked_spec.rb @@ -0,0 +1,75 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#locked" do + after :each do + @buffer&.free + @buffer = nil + end + + context "when buffer is locked" do + it "allows reading and writing operations on the buffer" do + @buffer = IO::Buffer.new(4) + @buffer.set_string("test") + @buffer.locked do + @buffer.get_string.should == "test" + @buffer.set_string("meat") + end + @buffer.get_string.should == "meat" + end + + it "disallows operations changing buffer itself, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + # Just an example, each method is responsible for checking the lock state. + -> { @buffer.resize(8) }.should raise_error(IO::Buffer::LockedError) + end + end + end + + it "disallows reentrant locking, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.locked {} }.should raise_error(IO::Buffer::LockedError, "Buffer already locked!") + end + end + + it "does not propagate to buffer's slices" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.locked do + @buffer.locked?.should be_true + slice.locked?.should be_false + slice.locked { slice.locked?.should be_true } + end + end + + it "does not propagate backwards from buffer's slices" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + slice.locked do + slice.locked?.should be_true + @buffer.locked?.should be_false + @buffer.locked { @buffer.locked?.should be_true } + end + end +end + +describe "IO::Buffer#locked?" do + after :each do + @buffer&.free + @buffer = nil + end + + it "is false by default" do + @buffer = IO::Buffer.new(4) + @buffer.locked?.should be_false + end + + it "is true only inside of #locked block" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + @buffer.locked?.should be_true + end + @buffer.locked?.should be_false + end +end diff --git a/spec/ruby/core/io/buffer/mapped_spec.rb b/spec/ruby/core/io/buffer/mapped_spec.rb new file mode 100644 index 0000000000..b3610207ff --- /dev/null +++ b/spec/ruby/core/io/buffer/mapped_spec.rb @@ -0,0 +1,108 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#mapped?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.mapped?.should be_false + end + + it "is true for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.mapped?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.mapped?.should be_true + end + end + + ruby_version_is "3.3" do + it "is true for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.mapped?.should be_true + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.mapped?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.mapped?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.mapped?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.mapped?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.mapped?.should be_false + end + end + + context "created with .map" do + it "is false" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.mapped?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.mapped?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.mapped?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.mapped?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/null_spec.rb b/spec/ruby/core/io/buffer/null_spec.rb new file mode 100644 index 0000000000..3fb1144d0e --- /dev/null +++ b/spec/ruby/core/io/buffer/null_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' +require_relative 'shared/null_and_empty' + +describe "IO::Buffer#null?" do + after :each do + @buffer&.free + @buffer = nil + end + + it_behaves_like :io_buffer_null_and_empty, :null? + + it "is false for a 0-length String-backed buffer created with .for" do + @buffer = IO::Buffer.for("") + @buffer.null?.should be_false + end + + ruby_version_is "3.3" do + it "is false for a 0-length String-backed buffer created with .string" do + IO::Buffer.string(0) do |buffer| + buffer.null?.should be_false + end + end + end + + it "is false for a 0-length slice of a buffer with size > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(3, 0).null?.should be_false + end +end diff --git a/spec/ruby/core/io/buffer/private_spec.rb b/spec/ruby/core/io/buffer/private_spec.rb new file mode 100644 index 0000000000..7aa308997b --- /dev/null +++ b/spec/ruby/core/io/buffer/private_spec.rb @@ -0,0 +1,111 @@ +require_relative '../../../spec_helper' + +ruby_version_is "3.3" do + describe "IO::Buffer#private?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.private?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.private?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.private?.should be_false + end + end + + it "is true for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.private?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.private?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.private?.should be_false + end + end + end + + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.private?.should be_false + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.private?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.private?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a regular file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.private?.should be_false + end + end + + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.slice.private?.should be_false + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.private?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.private?.should be_false + end + end + end + + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.private?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/readonly_spec.rb b/spec/ruby/core/io/buffer/readonly_spec.rb new file mode 100644 index 0000000000..0014a876ed --- /dev/null +++ b/spec/ruby/core/io/buffer/readonly_spec.rb @@ -0,0 +1,143 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#readonly?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.readonly?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.readonly?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is false for a writable mapping" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + @buffer.readonly?.should be_false + end + end + + it "is true for a readonly mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.readonly?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.readonly?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is true for a buffer created without a block" do + @buffer = IO::Buffer.for(+"test") + @buffer.readonly?.should be_true + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.readonly?.should be_false + end + end + + it "is true for a buffer created with a block from a frozen string" do + IO::Buffer.for(-"test") do |buffer| + buffer.readonly?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.readonly?.should be_false + end + end + end + end + + # This seems to be the only flag propagated from the source buffer to the slice. + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.readonly?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.readonly?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a read-write file-backed buffer" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + @buffer.slice.readonly?.should be_false + end + end + + it "is true when slicing a readonly file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.readonly?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.slice.readonly?.should be_false + end + end + end + end + + context "created with .for" do + it "is true when slicing a buffer created without a block" do + @buffer = IO::Buffer.for(+"test") + @buffer.slice.readonly?.should be_true + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.readonly?.should be_false + end + end + + it "is true when slicing a buffer created with a block from a frozen string" do + IO::Buffer.for(-"test") do |buffer| + buffer.slice.readonly?.should be_true + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.readonly?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/resize_spec.rb b/spec/ruby/core/io/buffer/resize_spec.rb new file mode 100644 index 0000000000..0da3a23356 --- /dev/null +++ b/spec/ruby/core/io/buffer/resize_spec.rb @@ -0,0 +1,155 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#resize" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "resizes internal buffer, preserving type" do + @buffer = IO::Buffer.new(4) + @buffer.resize(IO::Buffer::PAGE_SIZE) + @buffer.size.should == IO::Buffer::PAGE_SIZE + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + + platform_is :linux do + it "resizes mapped buffer, preserving type" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::MAPPED) + @buffer.resize(4) + @buffer.size.should == 4 + @buffer.internal?.should be_false + @buffer.mapped?.should be_true + end + end + + platform_is_not :linux do + it "resizes mapped buffer, changing type to internal" do + @buffer = IO::Buffer.new(IO::Buffer::PAGE_SIZE, IO::Buffer::MAPPED) + @buffer.resize(4) + @buffer.size.should == 4 + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + end + end + + context "with a file-backed buffer created with .map" do + it "disallows resizing shared buffer, raising IO::Buffer::AccessError" do + File.open(__FILE__, "r+") do |file| + @buffer = IO::Buffer.map(file) + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + + ruby_version_is "3.3" do + it "resizes private buffer, discarding excess contents" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + @buffer.resize(10) + @buffer.size.should == 10 + @buffer.get_string.should == "require_re" + @buffer.resize(12) + @buffer.size.should == 12 + @buffer.get_string.should == "require_re\0\0" + end + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "disallows resizing, raising IO::Buffer::AccessError" do + @buffer = IO::Buffer.for(+"test") + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + + context "with a block" do + it "disallows resizing, raising IO::Buffer::AccessError" do + IO::Buffer.for(+'test') do |buffer| + -> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "disallows resizing, raising IO::Buffer::AccessError" do + IO::Buffer.string(4) do |buffer| + -> { buffer.resize(10) }.should raise_error(IO::Buffer::AccessError, "Cannot resize external buffer!") + end + end + end + end + + context "with a null buffer" do + it "allows resizing a 0-sized buffer, creating a regular buffer according to new size" do + @buffer = IO::Buffer.new(0) + @buffer.resize(IO::Buffer::PAGE_SIZE) + @buffer.size.should == IO::Buffer::PAGE_SIZE + @buffer.internal?.should be_false + @buffer.mapped?.should be_true + end + + it "allows resizing after a free, creating a regular buffer according to new size" do + @buffer = IO::Buffer.for("test") + @buffer.free + @buffer.resize(10) + @buffer.size.should == 10 + @buffer.internal?.should be_true + @buffer.mapped?.should be_false + end + end + + it "allows resizing to 0, freeing memory" do + @buffer = IO::Buffer.new(4) + @buffer.resize(0) + @buffer.null?.should be_true + end + + it "can be called repeatedly" do + @buffer = IO::Buffer.new(4) + @buffer.resize(10) + @buffer.resize(27) + @buffer.resize(1) + @buffer.size.should == 1 + end + + it "always clears extra memory" do + @buffer = IO::Buffer.new(4) + @buffer.set_string("test") + # This should not cause a re-allocation, just a technical resizing, + # even with very aggressive memory allocation. + @buffer.resize(2) + @buffer.resize(4) + @buffer.get_string.should == "te\0\0" + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.resize(10) }.should raise_error(IO::Buffer::LockedError, "Cannot resize locked buffer!") + end + end + + it "raises ArgumentError if size is negative" do + @buffer = IO::Buffer.new(4) + -> { @buffer.resize(-1) }.should raise_error(ArgumentError, "Size can't be negative!") + end + + it "raises TypeError if size is not an Integer" do + @buffer = IO::Buffer.new(4) + -> { @buffer.resize(nil) }.should raise_error(TypeError, "not an Integer") + -> { @buffer.resize(10.0) }.should raise_error(TypeError, "not an Integer") + end + + context "with a slice of a buffer" do + # Current behavior of slice resizing seems unintended (it's undocumented, too). + # It either creates a completely new buffer, or breaks the slice on size 0. + it "needs to be reviewed for spec completeness" + end +end diff --git a/spec/ruby/core/io/buffer/shared/null_and_empty.rb b/spec/ruby/core/io/buffer/shared/null_and_empty.rb new file mode 100644 index 0000000000..c8fe9e5e46 --- /dev/null +++ b/spec/ruby/core/io/buffer/shared/null_and_empty.rb @@ -0,0 +1,59 @@ +describe :io_buffer_null_and_empty, shared: true do + it "is false for a buffer with size > 0" do + @buffer = IO::Buffer.new(1) + @buffer.send(@method).should be_false + end + + it "is false for a slice with length > 0" do + @buffer = IO::Buffer.new(4) + @buffer.slice(1, 2).send(@method).should be_false + end + + it "is false for a file-mapped buffer" do + File.open(__FILE__, "rb") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.send(@method).should be_false + end + end + + it "is false for a non-empty String-backed buffer created with .for" do + @buffer = IO::Buffer.for("test") + @buffer.send(@method).should be_false + end + + ruby_version_is "3.3" do + it "is false for a non-empty String-backed buffer created with .string" do + IO::Buffer.string(4) do |buffer| + buffer.send(@method).should be_false + end + end + end + + it "is true for a 0-sized buffer" do + @buffer = IO::Buffer.new(0) + @buffer.send(@method).should be_true + end + + it "is true for a slice of a 0-sized buffer" do + @buffer = IO::Buffer.new(0) + @buffer.slice(0, 0).send(@method).should be_true + end + + it "is true for a freed buffer" do + @buffer = IO::Buffer.new(1) + @buffer.free + @buffer.send(@method).should be_true + end + + it "is true for a buffer resized to 0" do + @buffer = IO::Buffer.new(1) + @buffer.resize(0) + @buffer.send(@method).should be_true + end + + it "is true for a buffer whose memory was transferred" do + buffer = IO::Buffer.new(1) + @buffer = buffer.transfer + buffer.send(@method).should be_true + end +end diff --git a/spec/ruby/core/io/buffer/shared_spec.rb b/spec/ruby/core/io/buffer/shared_spec.rb new file mode 100644 index 0000000000..f2a638cf39 --- /dev/null +++ b/spec/ruby/core/io/buffer/shared_spec.rb @@ -0,0 +1,117 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#shared?" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "is false for an internal buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::INTERNAL) + @buffer.shared?.should be_false + end + + it "is false for a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.shared?.should be_false + end + end + + context "with a file-backed buffer created with .map" do + it "is true for a regular mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.shared?.should be_true + end + end + + ruby_version_is "3.3" do + it "is false for a private mapping" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.shared?.should be_false + end + end + end + end + + context "with a String-backed buffer created with .for" do + it "is false for a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.shared?.should be_false + end + + it "is false for a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.shared?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.shared?.should be_false + end + end + end + end + + # Always false for slices + context "with a slice of a buffer" do + context "created with .new" do + it "is false when slicing an internal buffer" do + @buffer = IO::Buffer.new(4) + @buffer.slice.shared?.should be_false + end + + it "is false when slicing a mapped buffer" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + @buffer.slice.shared?.should be_false + end + end + + context "created with .map" do + it "is false when slicing a regular file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.slice.shared?.should be_false + end + end + + ruby_version_is "3.3" do + it "is false when slicing a private file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY | IO::Buffer::PRIVATE) + @buffer.slice.shared?.should be_false + end + end + end + end + + context "created with .for" do + it "is false when slicing a buffer created without a block" do + @buffer = IO::Buffer.for("test") + @buffer.slice.shared?.should be_false + end + + it "is false when slicing a buffer created with a block" do + IO::Buffer.for(+"test") do |buffer| + buffer.slice.shared?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "created with .string" do + it "is false" do + IO::Buffer.string(4) do |buffer| + buffer.slice.shared?.should be_false + end + end + end + end + end +end diff --git a/spec/ruby/core/io/buffer/transfer_spec.rb b/spec/ruby/core/io/buffer/transfer_spec.rb new file mode 100644 index 0000000000..cb8c843ff2 --- /dev/null +++ b/spec/ruby/core/io/buffer/transfer_spec.rb @@ -0,0 +1,118 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#transfer" do + after :each do + @buffer&.free + @buffer = nil + end + + context "with a buffer created with .new" do + it "transfers internal memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.new(4) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + + it "transfers mapped memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + + context "with a file-backed buffer created with .map" do + it "transfers mapped memory to a new buffer, nullifying the original" do + File.open(__FILE__, "r") do |file| + buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + end + + context "with a String-backed buffer created with .for" do + context "without a block" do + it "transfers memory to a new buffer, nullifying the original" do + buffer = IO::Buffer.for("test") + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + end + + context "with a block" do + it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do + IO::Buffer.for(+"test") do |buffer| + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + @buffer.null?.should be_false + end + end + end + + ruby_version_is "3.3" do + context "with a String-backed buffer created with .string" do + it "transfers memory to a new buffer, breaking the transaction by nullifying the original" do + IO::Buffer.string(4) do |buffer| + info = buffer.to_s + @buffer = buffer.transfer + @buffer.to_s.should == info + buffer.null?.should be_true + end + @buffer.null?.should be_false + end + end + end + + it "allows multiple transfers" do + buffer_1 = IO::Buffer.new(4) + buffer_2 = buffer_1.transfer + @buffer = buffer_2.transfer + buffer_1.null?.should be_true + buffer_2.null?.should be_true + @buffer.null?.should be_false + end + + it "is disallowed while locked, raising IO::Buffer::LockedError" do + @buffer = IO::Buffer.new(4) + @buffer.locked do + -> { @buffer.transfer }.should raise_error(IO::Buffer::LockedError, "Cannot transfer ownership of locked buffer!") + end + end + + context "with a slice of a buffer" do + it "transfers source to a new slice, not touching the buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.set_string("test") + + new_slice = slice.transfer + slice.null?.should be_true + new_slice.null?.should be_false + @buffer.null?.should be_false + + new_slice.set_string("ea") + @buffer.get_string.should == "east" + end + + it "nullifies buffer, invalidating the slice" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + @buffer = buffer.transfer + + slice.null?.should be_false + slice.valid?.should be_false + -> { slice.get_string }.should raise_error(IO::Buffer::InvalidatedError, "Buffer has been invalidated!") + end + end +end diff --git a/spec/ruby/core/io/buffer/valid_spec.rb b/spec/ruby/core/io/buffer/valid_spec.rb new file mode 100644 index 0000000000..680a35ae9a --- /dev/null +++ b/spec/ruby/core/io/buffer/valid_spec.rb @@ -0,0 +1,110 @@ +require_relative '../../../spec_helper' + +describe "IO::Buffer#valid?" do + after :each do + @buffer&.free + @buffer = nil + end + + # Non-slices are always valid + context "with a non-slice buffer" do + it "is true for a regular buffer" do + @buffer = IO::Buffer.new(4) + @buffer.valid?.should be_true + end + + it "is true for a 0-size buffer" do + @buffer = IO::Buffer.new(0) + @buffer.valid?.should be_true + end + + it "is true for a freed buffer" do + @buffer = IO::Buffer.new(4) + @buffer.free + @buffer.valid?.should be_true + end + + it "is true for a freed file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + @buffer.valid?.should be_true + @buffer.free + @buffer.valid?.should be_true + end + end + + it "is true for a freed string-backed buffer" do + @buffer = IO::Buffer.for("hello") + @buffer.valid?.should be_true + @buffer.free + @buffer.valid?.should be_true + end + end + + # "A buffer becomes invalid if it is a slice of another buffer (or string) + # which has been freed or re-allocated at a different address." + context "with a slice" do + it "is true for a slice of a live buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + end + + context "when buffer is resized" do + it "is false when slice becomes outside the buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(2, 2) + @buffer.resize(3) + slice.valid?.should be_false + end + + platform_is_not :linux do + # This test does not cause a copy-resize on Linux. + # `#resize` MAY cause the buffer to move, but there is no guarantee. + it "is false when buffer is copied on resize" do + @buffer = IO::Buffer.new(4, IO::Buffer::MAPPED) + slice = @buffer.slice(0, 2) + @buffer.resize(8) + slice.valid?.should be_false + end + end + end + + it "is false for a slice of a transferred buffer" do + buffer = IO::Buffer.new(4) + slice = buffer.slice(0, 2) + @buffer = buffer.transfer + slice.valid?.should be_false + end + + it "is false for a slice of a freed buffer" do + @buffer = IO::Buffer.new(4) + slice = @buffer.slice(0, 2) + @buffer.free + slice.valid?.should be_false + end + + it "is false for a slice of a freed file-backed buffer" do + File.open(__FILE__, "r") do |file| + @buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY) + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + @buffer.free + slice.valid?.should be_false + end + end + + it "is true for a slice of a freed string-backed buffer while string is alive" do + @buffer = IO::Buffer.for("alive") + slice = @buffer.slice(0, 2) + slice.valid?.should be_true + @buffer.free + slice.valid?.should be_true + end + + # There probably should be a test with a garbage-collected string, + # but it's not clear how to force that. + + it "needs to be reviewed for spec completeness" + end +end diff --git a/spec/ruby/core/io/close_read_spec.rb b/spec/ruby/core/io/close_read_spec.rb index 26454bfddd..e700e85bd9 100644 --- a/spec/ruby/core/io/close_read_spec.rb +++ b/spec/ruby/core/io/close_read_spec.rb @@ -4,7 +4,8 @@ require_relative 'fixtures/classes' describe "IO#close_read" do before :each do - @io = IO.popen 'cat', "r+" + cmd = platform_is(:windows) ? 'rem' : 'cat' + @io = IO.popen cmd, "r+" @path = tmp('io.close.txt') end diff --git a/spec/ruby/core/io/close_write_spec.rb b/spec/ruby/core/io/close_write_spec.rb index 14835e4e2c..70610a3e9d 100644 --- a/spec/ruby/core/io/close_write_spec.rb +++ b/spec/ruby/core/io/close_write_spec.rb @@ -3,7 +3,8 @@ require_relative 'fixtures/classes' describe "IO#close_write" do before :each do - @io = IO.popen 'cat', 'r+' + cmd = platform_is(:windows) ? 'rem' : 'cat' + @io = IO.popen cmd, 'r+' @path = tmp('io.close.txt') end @@ -48,12 +49,15 @@ describe "IO#close_write" do io.should.closed? end - it "flushes and closes the write stream" do - @io.puts '12345' + # Windows didn't have command like cat + platform_is_not :windows do + it "flushes and closes the write stream" do + @io.puts '12345' - @io.close_write + @io.close_write - @io.read.should == "12345\n" + @io.read.should == "12345\n" + end end it "does nothing on closed stream" do diff --git a/spec/ruby/core/io/copy_stream_spec.rb b/spec/ruby/core/io/copy_stream_spec.rb index df9c5c7390..ffa2ea992c 100644 --- a/spec/ruby/core/io/copy_stream_spec.rb +++ b/spec/ruby/core/io/copy_stream_spec.rb @@ -69,9 +69,12 @@ describe :io_copy_stream_to_io, shared: true do end it "raises an IOError if the destination IO is not open for writing" do - @to_io.close - @to_io = new_io @to_name, "r" - -> { IO.copy_stream @object.from, @to_io }.should raise_error(IOError) + to_io = new_io __FILE__, "r" + begin + -> { IO.copy_stream @object.from, to_io }.should raise_error(IOError) + ensure + to_io.close + end end it "does not close the destination IO" do @@ -109,7 +112,8 @@ describe "IO.copy_stream" do end after :each do - rm_r @to_name, @from_bigfile + rm_r @to_name if @to_name + rm_r @from_bigfile end describe "from an IO" do @@ -164,6 +168,25 @@ describe "IO.copy_stream" do it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream end + + describe "to a Tempfile" do + before :all do + require 'tempfile' + end + + before :each do + @to_io = Tempfile.new("rubyspec_copy_stream", encoding: Encoding::BINARY, mode: File::RDONLY) + @to_name = @to_io.path + end + + after :each do + @to_io.close! + @to_name = nil # do not rm_r it, already done by Tempfile#close! + end + + it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream + it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream + end end describe "from a file name" do @@ -277,10 +300,8 @@ describe "IO.copy_stream" do @io.should_not_receive(:pos) IO.copy_stream(@io, @to_name) end - end - describe "with a destination that does partial reads" do before do @from_out, @from_in = IO.pipe diff --git a/spec/ruby/core/io/dup_spec.rb b/spec/ruby/core/io/dup_spec.rb index 68d538377f..564e007438 100644 --- a/spec/ruby/core/io/dup_spec.rb +++ b/spec/ruby/core/io/dup_spec.rb @@ -25,27 +25,27 @@ describe "IO#dup" do @i.fileno.should_not == @f.fileno end -quarantine! do # This does not appear to be consistent across platforms - it "shares the original stream between the two IOs" do - start = @f.pos - @i.pos.should == start + quarantine! do # This does not appear to be consistent across platforms + it "shares the original stream between the two IOs" do + start = @f.pos + @i.pos.should == start - s = "Hello, wo.. wait, where am I?\n" - s2 = "<evil voice> Muhahahaa!" + s = "Hello, wo.. wait, where am I?\n" + s2 = "<evil voice> Muhahahaa!" - @f.write s - @i.pos.should == @f.pos + @f.write s + @i.pos.should == @f.pos - @i.rewind - @i.gets.should == s + @i.rewind + @i.gets.should == s - @i.rewind - @i.write s2 + @i.rewind + @i.write s2 - @f.rewind - @f.gets.should == "#{s2}\n" + @f.rewind + @f.gets.should == "#{s2}\n" + end end -end it "allows closing the new IO without affecting the original" do @i.close diff --git a/spec/ruby/core/io/eof_spec.rb b/spec/ruby/core/io/eof_spec.rb index 315345d942..b4850df437 100644 --- a/spec/ruby/core/io/eof_spec.rb +++ b/spec/ruby/core/io/eof_spec.rb @@ -76,7 +76,7 @@ describe "IO#eof?" do end it "returns true on one-byte stream after single-byte read" do - File.open(File.dirname(__FILE__) + '/fixtures/one_byte.txt') { |one_byte| + File.open(__dir__ + '/fixtures/one_byte.txt') { |one_byte| one_byte.read(1) one_byte.should.eof? } diff --git a/spec/ruby/core/io/external_encoding_spec.rb b/spec/ruby/core/io/external_encoding_spec.rb index 2fcf1c7218..7765c6c0f5 100644 --- a/spec/ruby/core/io/external_encoding_spec.rb +++ b/spec/ruby/core/io/external_encoding_spec.rb @@ -94,12 +94,10 @@ describe "IO#external_encoding" do rm_r @name end - ruby_version_is '3.1' do - it "can be retrieved from a closed stream" do - io = IOSpecs.io_fixture("lines.txt", "r") - io.close - io.external_encoding.should equal(Encoding.default_external) - end + it "can be retrieved from a closed stream" do + io = IOSpecs.io_fixture("lines.txt", "r") + io.close + io.external_encoding.should equal(Encoding.default_external) end describe "with 'r' mode" do diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb index c2276cf544..6abe8901ba 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -14,31 +14,48 @@ describe "IO.foreach" do IO.foreach(@name) { $..should == @count += 1 } end - describe "when the filename starts with |" do - it "gets data from the standard out of the subprocess" do - cmd = "|sh -c 'echo hello;echo line2'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello&echo line2" + ruby_version_is ""..."4.0" do + describe "when the filename starts with |" do + it "gets data from the standard out of the subprocess" do + cmd = "|sh -c 'echo hello;echo line2'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello&echo line2" + end + + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.foreach(cmd) { |l| ScratchPad << l } + end + ScratchPad.recorded.should == ["hello\n", "line2\n"] end - IO.foreach(cmd) { |l| ScratchPad << l } - ScratchPad.recorded.should == ["hello\n", "line2\n"] - end - platform_is_not :windows do - it "gets data from a fork when passed -" do - parent_pid = $$ + platform_is_not :windows do + it "gets data from a fork when passed -" do + parent_pid = $$ - IO.foreach("|-") { |l| ScratchPad << l } + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.foreach("|-") { |l| ScratchPad << l } + end - if $$ == parent_pid - ScratchPad.recorded.should == ["hello\n", "from a fork\n"] - else # child - puts "hello" - puts "from a fork" - exit! + if $$ == parent_pid + ScratchPad.recorded.should == ["hello\n", "from a fork\n"] + else # child + puts "hello" + puts "from a fork" + exit! + end end end end + + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + IO.foreach(cmd).to_a + }.should complain(/IO process creation with a leading '\|'/) + end + end end end diff --git a/spec/ruby/core/io/getbyte_spec.rb b/spec/ruby/core/io/getbyte_spec.rb index 6ba8f0a3e0..b4351160e6 100644 --- a/spec/ruby/core/io/getbyte_spec.rb +++ b/spec/ruby/core/io/getbyte_spec.rb @@ -40,3 +40,19 @@ describe "IO#getbyte" do @io.getbyte.should == nil end end + +describe "IO#getbyte" do + before :each do + @name = tmp("io_getbyte.txt") + @io = new_io(@name, 'w') + end + + after :each do + @io.close if @io + rm_r @name if @name + end + + it "raises an IOError if the stream is not readable" do + -> { @io.getbyte }.should raise_error(IOError) + end +end diff --git a/spec/ruby/core/io/gets_spec.rb b/spec/ruby/core/io/gets_spec.rb index 73d76b3abd..ca64bf860e 100644 --- a/spec/ruby/core/io/gets_spec.rb +++ b/spec/ruby/core/io/gets_spec.rb @@ -24,6 +24,12 @@ describe "IO#gets" do end end + it "sets $_ to nil after the last line has been read" do + while @io.gets + end + $_.should be_nil + end + it "returns nil if called at the end of the stream" do IOSpecs.lines.length.times { @io.gets } @io.gets.should == nil diff --git a/spec/ruby/core/io/internal_encoding_spec.rb b/spec/ruby/core/io/internal_encoding_spec.rb index 60afaf2ebd..7a583d4bcb 100644 --- a/spec/ruby/core/io/internal_encoding_spec.rb +++ b/spec/ruby/core/io/internal_encoding_spec.rb @@ -113,12 +113,10 @@ describe "IO#internal_encoding" do Encoding.default_internal = @internal end - ruby_version_is '3.1' do - it "can be retrieved from a closed stream" do - io = IOSpecs.io_fixture("lines.txt", "r") - io.close - io.internal_encoding.should equal(Encoding.default_internal) - end + it "can be retrieved from a closed stream" do + io = IOSpecs.io_fixture("lines.txt", "r") + io.close + io.internal_encoding.should equal(Encoding.default_internal) end describe "with 'r' mode" do diff --git a/spec/ruby/core/io/ioctl_spec.rb b/spec/ruby/core/io/ioctl_spec.rb index 8dcd9eb2c6..3f7b5ad5d7 100644 --- a/spec/ruby/core/io/ioctl_spec.rb +++ b/spec/ruby/core/io/ioctl_spec.rb @@ -12,7 +12,7 @@ describe "IO#ioctl" do guard -> { RUBY_PLATFORM.include?("86") } do # x86 / x86_64 it "resizes an empty String to match the output size" do File.open(__FILE__, 'r') do |f| - buffer = '' + buffer = +'' # FIONREAD in /usr/include/asm-generic/ioctls.h f.ioctl 0x541B, buffer buffer.unpack('I').first.should be_kind_of(Integer) diff --git a/spec/ruby/core/io/lineno_spec.rb b/spec/ruby/core/io/lineno_spec.rb index 9a4ad90880..e82cdd9f17 100644 --- a/spec/ruby/core/io/lineno_spec.rb +++ b/spec/ruby/core/io/lineno_spec.rb @@ -26,7 +26,8 @@ describe "IO#lineno" do end it "raises an IOError on a duplexed stream with the read side closed" do - IO.popen('cat', 'r+') do |p| + cmd = platform_is(:windows) ? 'rem' : 'cat' + IO.popen(cmd, 'r+') do |p| p.close_read -> { p.lineno }.should raise_error(IOError) end @@ -70,7 +71,8 @@ describe "IO#lineno=" do end it "raises an IOError on a duplexed stream with the read side closed" do - IO.popen('cat', 'r+') do |p| + cmd = platform_is(:windows) ? 'rem' : 'cat' + IO.popen(cmd, 'r+') do |p| p.close_read -> { p.lineno = 0 }.should raise_error(IOError) end diff --git a/spec/ruby/core/io/new_spec.rb b/spec/ruby/core/io/new_spec.rb index 0ef30991fd..979ac0efcb 100644 --- a/spec/ruby/core/io/new_spec.rb +++ b/spec/ruby/core/io/new_spec.rb @@ -1,10 +1,16 @@ require_relative '../../spec_helper' require_relative 'shared/new' -# NOTE: should be syncronized with library/stringio/initialize_spec.rb +# NOTE: should be synchronized with library/stringio/initialize_spec.rb describe "IO.new" do it_behaves_like :io_new, :new + + it "does not use the given block and warns to use IO::open" do + -> { + @io = IO.send(@method, @fd) { raise } + }.should complain(/warning: IO::new\(\) does not take block; use IO::open\(\) instead/) + end end describe "IO.new" do diff --git a/spec/ruby/core/io/open_spec.rb b/spec/ruby/core/io/open_spec.rb index d3a3961df7..d151da9ce5 100644 --- a/spec/ruby/core/io/open_spec.rb +++ b/spec/ruby/core/io/open_spec.rb @@ -37,6 +37,19 @@ describe "IO.open" do ScratchPad.recorded.should == :called end + it "propagate an exception in the block after calling #close" do + -> do + IO.open(@fd, "w") do |io| + IOSpecs.io_mock(io, :close) do + super() + ScratchPad.record :called + end + raise Exception + end + end.should raise_error(Exception) + ScratchPad.recorded.should == :called + end + it "propagates an exception raised by #close that is not a StandardError" do -> do IO.open(@fd, "w") do |io| diff --git a/spec/ruby/core/io/path_spec.rb b/spec/ruby/core/io/path_spec.rb index 8145c32f39..798adb2163 100644 --- a/spec/ruby/core/io/path_spec.rb +++ b/spec/ruby/core/io/path_spec.rb @@ -1,14 +1,12 @@ require_relative '../../spec_helper' describe "IO#path" do - ruby_version_is "3.2" do - it "returns the path of the file associated with the IO object" do - path = tmp("io_path.txt") - File.open(path, "w") do |file| - IO.new(file.fileno, path: file.path, autoclose: false).path.should == file.path - end - ensure - File.unlink(path) + it "returns the path of the file associated with the IO object" do + path = tmp("io_path.txt") + File.open(path, "w") do |file| + IO.new(file.fileno, path: file.path, autoclose: false).path.should == file.path end + ensure + File.unlink(path) end end diff --git a/spec/ruby/core/io/popen_spec.rb b/spec/ruby/core/io/popen_spec.rb index e9d32c5c7d..6043862614 100644 --- a/spec/ruby/core/io/popen_spec.rb +++ b/spec/ruby/core/io/popen_spec.rb @@ -95,6 +95,22 @@ describe "IO.popen" do @io = IO.popen(ruby_cmd('exit 0'), mode) end + it "accepts a path using the chdir: keyword argument" do + path = File.dirname(@fname) + + @io = IO.popen(ruby_cmd("puts Dir.pwd"), "r", chdir: path) + @io.read.chomp.should == path + end + + it "accepts a path using the chdir: keyword argument and a coercible path" do + path = File.dirname(@fname) + object = mock("path") + object.should_receive(:to_path).and_return(path) + + @io = IO.popen(ruby_cmd("puts Dir.pwd"), "r", chdir: object) + @io.read.chomp.should == path + end + describe "with a block" do it "yields an open IO to the block" do IO.popen(ruby_cmd('exit'), "r") do |io| diff --git a/spec/ruby/core/io/pread_spec.rb b/spec/ruby/core/io/pread_spec.rb index fb0645dec6..dc7bcedf3e 100644 --- a/spec/ruby/core/io/pread_spec.rb +++ b/spec/ruby/core/io/pread_spec.rb @@ -21,16 +21,106 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do end it "accepts a length, an offset, and an output buffer" do - buffer = "foo" - @file.pread(3, 4, buffer) + buffer = +"foo" + @file.pread(3, 4, buffer).should.equal?(buffer) buffer.should == "567" end + it "shrinks the buffer in case of less bytes read" do + buffer = +"foo" + @file.pread(1, 0, buffer) + buffer.should == "1" + end + + it "grows the buffer in case of more bytes read" do + buffer = +"foo" + @file.pread(5, 0, buffer) + buffer.should == "12345" + end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @file.pread(10, 0, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + it "does not advance the file pointer" do @file.pread(4, 0).should == "1234" @file.read.should == "1234567890" end + it "ignores the current offset" do + @file.pos = 3 + @file.pread(4, 0).should == "1234" + end + + it "returns an empty string for maxlen = 0" do + @file.pread(0, 4).should == "" + end + + it "returns a buffer for maxlen = 0 when buffer specified" do + buffer = +"foo" + @file.pread(0, 4, buffer).should.equal?(buffer) + buffer.should == "foo" + end + + it "ignores the offset for maxlen = 0, even if it is out of file bounds" do + @file.pread(0, 400).should == "" + end + + it "does not reset the buffer when reading with maxlen = 0" do + buffer = +"foo" + @file.pread(0, 4, buffer) + buffer.should == "foo" + + @file.pread(0, 400, buffer) + buffer.should == "foo" + end + + it "converts maxlen to Integer using #to_int" do + maxlen = mock('maxlen') + maxlen.should_receive(:to_int).and_return(4) + @file.pread(maxlen, 0).should == "1234" + end + + it "converts offset to Integer using #to_int" do + offset = mock('offset') + offset.should_receive(:to_int).and_return(0) + @file.pread(4, offset).should == "1234" + end + + it "converts a buffer to String using to_str" do + buffer = mock('buffer') + buffer.should_receive(:to_str).at_least(1).and_return(+"foo") + @file.pread(4, 0, buffer) + buffer.should_not.is_a?(String) + buffer.to_str.should == "1234" + end + + it "raises TypeError if maxlen is not an Integer and cannot be coerced into Integer" do + maxlen = Object.new + -> { @file.pread(maxlen, 0) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') + end + + it "raises TypeError if offset is not an Integer and cannot be coerced into Integer" do + offset = Object.new + -> { @file.pread(4, offset) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') + end + + it "raises ArgumentError for negative values of maxlen" do + -> { @file.pread(-4, 0) }.should raise_error(ArgumentError, 'negative string size (or size too big)') + end + + it "raised Errno::EINVAL for negative values of offset" do + -> { @file.pread(4, -1) }.should raise_error(Errno::EINVAL, /Invalid argument/) + end + + it "raises TypeError if the buffer is not a String and cannot be coerced into String" do + buffer = Object.new + -> { @file.pread(4, 0, buffer) }.should raise_error(TypeError, 'no implicit conversion of Object into String') + end + it "raises EOFError if end-of-file is reached" do -> { @file.pread(1, 10) }.should raise_error(EOFError) end diff --git a/spec/ruby/core/io/puts_spec.rb b/spec/ruby/core/io/puts_spec.rb index 9a708fffef..a186ddaa5d 100644 --- a/spec/ruby/core/io/puts_spec.rb +++ b/spec/ruby/core/io/puts_spec.rb @@ -6,7 +6,7 @@ describe "IO#puts" do @before_separator = $/ @name = tmp("io_puts.txt") @io = new_io @name - ScratchPad.record "" + ScratchPad.record(+"") def @io.write(str) ScratchPad << str end @@ -33,7 +33,7 @@ describe "IO#puts" do ScratchPad.recorded.should == "\n" end - it "writes empty string with a newline when when given nil as multiple args" do + it "writes empty string with a newline when given nil as multiple args" do @io.puts(nil, nil).should == nil ScratchPad.recorded.should == "\n\n" end diff --git a/spec/ruby/core/io/pwrite_spec.rb b/spec/ruby/core/io/pwrite_spec.rb index c10578a8eb..2bc508b37d 100644 --- a/spec/ruby/core/io/pwrite_spec.rb +++ b/spec/ruby/core/io/pwrite_spec.rb @@ -28,16 +28,42 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do @file.pread(6, 0).should == "foobar" end + it "calls #to_s on the object to be written" do + object = mock("to_s") + object.should_receive(:to_s).and_return("foo") + @file.pwrite(object, 0) + @file.pread(3, 0).should == "foo" + end + + it "calls #to_int on the offset" do + offset = mock("to_int") + offset.should_receive(:to_int).and_return(2) + @file.pwrite("foo", offset) + @file.pread(3, 2).should == "foo" + end + it "raises IOError when file is not open in write mode" do File.open(@fname, "r") do |file| - -> { file.pwrite("foo", 1) }.should raise_error(IOError) + -> { file.pwrite("foo", 1) }.should raise_error(IOError, "not opened for writing") end end it "raises IOError when file is closed" do file = File.open(@fname, "w+") file.close - -> { file.pwrite("foo", 1) }.should raise_error(IOError) + -> { file.pwrite("foo", 1) }.should raise_error(IOError, "closed stream") + end + + it "raises a NoMethodError if object does not respond to #to_s" do + -> { + @file.pwrite(BasicObject.new, 0) + }.should raise_error(NoMethodError, /undefined method [`']to_s'/) + end + + it "raises a TypeError if the offset cannot be converted to an Integer" do + -> { + @file.pwrite("foo", Object.new) + }.should raise_error(TypeError, "no implicit conversion of Object into Integer") end end end diff --git a/spec/ruby/core/io/read_nonblock_spec.rb b/spec/ruby/core/io/read_nonblock_spec.rb index a62b75274c..51e7cd6bd2 100644 --- a/spec/ruby/core/io/read_nonblock_spec.rb +++ b/spec/ruby/core/io/read_nonblock_spec.rb @@ -96,21 +96,21 @@ describe "IO#read_nonblock" do end it "reads into the passed buffer" do - buffer = "" + buffer = +"" @write.write("1") @read.read_nonblock(1, buffer) buffer.should == "1" end it "returns the passed buffer" do - buffer = "" + buffer = +"" @write.write("1") output = @read.read_nonblock(1, buffer) output.should equal(buffer) end it "discards the existing buffer content upon successful read" do - buffer = "existing content" + buffer = +"existing content" @write.write("hello world") @write.close @read.read_nonblock(11, buffer) @@ -118,7 +118,7 @@ describe "IO#read_nonblock" do end it "discards the existing buffer content upon error" do - buffer = "existing content" + buffer = +"existing content" @write.close -> { @read.read_nonblock(1, buffer) }.should raise_error(EOFError) buffer.should be_empty diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index a829ffd18c..988ec2ce30 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -113,6 +113,15 @@ describe "IO.read" do IO.read(@fname, 1, 10).should == nil end + it "returns an empty string when reading zero bytes" do + IO.read(@fname, 0).should == '' + end + + it "returns a String in BINARY when passed a size" do + IO.read(@fname, 1).encoding.should == Encoding::BINARY + IO.read(@fname, 0).encoding.should == Encoding::BINARY + end + it "raises an Errno::ENOENT when the requested file does not exist" do rm_r @fname -> { IO.read @fname }.should raise_error(Errno::ENOENT) @@ -159,55 +168,81 @@ describe "IO.read" do end end -describe "IO.read from a pipe" do - it "runs the rest as a subprocess and returns the standard output" do - cmd = "|sh -c 'echo hello'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello" +ruby_version_is ""..."4.0" do + describe "IO.read from a pipe" do + it "runs the rest as a subprocess and returns the standard output" do + cmd = "|sh -c 'echo hello'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello" + end + + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd).should == "hello\n" + end end - IO.read(cmd).should == "hello\n" - end - platform_is_not :windows do - it "opens a pipe to a fork if the rest is -" do - str = IO.read("|-") - if str # parent - str.should == "hello from child\n" - else #child - puts "hello from child" - exit! + platform_is_not :windows do + it "opens a pipe to a fork if the rest is -" do + str = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + str = IO.read("|-") + end + + if str # parent + str.should == "hello from child\n" + else #child + puts "hello from child" + exit! + end end end - end - it "reads only the specified number of bytes requested" do - cmd = "|sh -c 'echo hello'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello" + it "reads only the specified number of bytes requested" do + cmd = "|sh -c 'echo hello'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello" + end + + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd, 1).should == "h" + end end - IO.read(cmd, 1).should == "h" - end - platform_is_not :windows do - it "raises Errno::ESPIPE if passed an offset" do - -> { - IO.read("|sh -c 'echo hello'", 1, 1) - }.should raise_error(Errno::ESPIPE) + platform_is_not :windows do + it "raises Errno::ESPIPE if passed an offset" do + -> { + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read("|sh -c 'echo hello'", 1, 1) + end + }.should raise_error(Errno::ESPIPE) + end end - end -quarantine! do # The process tried to write to a nonexistent pipe. - platform_is :windows do - # TODO: It should raise Errno::ESPIPE on Windows as well - # once https://bugs.ruby-lang.org/issues/12230 is fixed. - it "raises Errno::EINVAL if passed an offset" do - -> { - IO.read("|cmd.exe /C echo hello", 1, 1) - }.should raise_error(Errno::EINVAL) + quarantine! do # The process tried to write to a nonexistent pipe. + platform_is :windows do + # TODO: It should raise Errno::ESPIPE on Windows as well + # once https://bugs.ruby-lang.org/issues/12230 is fixed. + it "raises Errno::EINVAL if passed an offset" do + -> { + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read("|cmd.exe /C echo hello", 1, 1) + end + }.should raise_error(Errno::EINVAL) + end + end + end + + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation" do + cmd = "|echo ok" + -> { + IO.read(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end end end end -end describe "IO.read on an empty file" do before :each do @@ -250,21 +285,55 @@ describe "IO#read" do @io.read(4).should == '7890' end + it "treats first nil argument as no length limit" do + @io.read(nil).should == @contents + end + + it "raises an ArgumentError when not passed a valid length" do + -> { @io.read(-1) }.should raise_error(ArgumentError) + end + it "clears the output buffer if there is nothing to read" do @io.pos = 10 - buf = 'non-empty string' + buf = +'non-empty string' @io.read(10, buf).should == nil buf.should == '' + + buf = +'non-empty string' + + @io.read(nil, buf).should == "" + + buf.should == '' + + buf = +'non-empty string' + + @io.read(0, buf).should == "" + + buf.should == '' + end + + it "raise FrozenError if the output buffer is frozen" do + @io.read + -> { @io.read(0, 'frozen-string'.freeze) }.should raise_error(FrozenError) + -> { @io.read(1, 'frozen-string'.freeze) }.should raise_error(FrozenError) + -> { @io.read(nil, 'frozen-string'.freeze) }.should raise_error(FrozenError) + end + + ruby_bug "", ""..."3.3" do + it "raise FrozenError if the output buffer is frozen (2)" do + @io.read + -> { @io.read(1, ''.freeze) }.should raise_error(FrozenError) + end end it "consumes zero bytes when reading zero bytes" do @io.read(0).should == '' @io.pos.should == 0 - @io.getc.chr.should == '1' + @io.getc.should == '1' end it "is at end-of-file when everything has been read" do @@ -277,53 +346,68 @@ describe "IO#read" do end it "places the specified number of bytes in the buffer" do - buf = "" + buf = +"" @io.read 5, buf buf.should == "12345" end it "expands the buffer when too small" do - buf = "ABCDE" + buf = +"ABCDE" @io.read nil, buf buf.should == @contents end it "overwrites the buffer" do - buf = "ABCDEFGHIJ" + buf = +"ABCDEFGHIJ" @io.read nil, buf buf.should == @contents end it "truncates the buffer when too big" do - buf = "ABCDEFGHIJKLMNO" + buf = +"ABCDEFGHIJKLMNO" @io.read nil, buf buf.should == @contents @io.rewind - buf = "ABCDEFGHIJKLMNO" + buf = +"ABCDEFGHIJKLMNO" @io.read 5, buf buf.should == @contents[0..4] end + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + @io.read(10, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end + + # https://bugs.ruby-lang.org/issues/20416 + it "does not preserve the encoding of the given buffer when max length is not provided" do + buffer = ''.encode(Encoding::ISO_8859_1) + @io.read(nil, buffer) + + buffer.encoding.should_not == Encoding::ISO_8859_1 + end + it "returns the given buffer" do - buf = "" + buf = +"" @io.read(nil, buf).should equal buf end it "returns the given buffer when there is nothing to read" do - buf = "" + buf = +"" @io.read @io.read(nil, buf).should equal buf end it "coerces the second argument to string and uses it as a buffer" do - buf = "ABCDE" + buf = +"ABCDE" obj = mock("buff") obj.should_receive(:to_str).any_number_of_times.and_return(buf) @@ -521,13 +605,13 @@ describe :io_read_internal_encoding, shared: true do describe "when passed nil for limit" do it "sets the buffer to a transcoded String" do - result = @io.read(nil, buf = "") + result = @io.read(nil, buf = +"") buf.should equal(result) buf.should == "ã‚りãŒã¨ã†\n" end it "sets the buffer's encoding to the internal encoding" do - buf = "".force_encoding Encoding::ISO_8859_1 + buf = "".dup.force_encoding Encoding::ISO_8859_1 @io.read(nil, buf) buf.encoding.should equal(Encoding::UTF_8) end @@ -541,17 +625,18 @@ describe :io_read_size_internal_encoding, shared: true do it "returns a String in BINARY when passed a size" do @io.read(4).encoding.should equal(Encoding::BINARY) + @io.read(0).encoding.should equal(Encoding::BINARY) end it "does not change the buffer's encoding when passed a limit" do - buf = "".force_encoding Encoding::ISO_8859_1 + buf = "".dup.force_encoding Encoding::ISO_8859_1 @io.read(4, buf) buf.should == [164, 162, 164, 234].pack('C*').force_encoding(Encoding::ISO_8859_1) buf.encoding.should equal(Encoding::ISO_8859_1) end it "truncates the buffer but does not change the buffer's encoding when no data remains" do - buf = "abc".force_encoding Encoding::ISO_8859_1 + buf = "abc".dup.force_encoding Encoding::ISO_8859_1 @io.read @io.read(1, buf).should be_nil diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb index d6c4d539cf..b4770775d1 100644 --- a/spec/ruby/core/io/readlines_spec.rb +++ b/spec/ruby/core/io/readlines_spec.rb @@ -174,29 +174,48 @@ describe "IO.readlines" do $_.should == "test" end - describe "when passed a string that starts with a |" do - it "gets data from the standard out of the subprocess" do - cmd = "|sh -c 'echo hello;echo line2'" - platform_is :windows do - cmd = "|cmd.exe /C echo hello&echo line2" - end - lines = IO.readlines(cmd) - lines.should == ["hello\n", "line2\n"] - end + ruby_version_is ""..."4.0" do + describe "when passed a string that starts with a |" do + it "gets data from the standard out of the subprocess" do + cmd = "|sh -c 'echo hello;echo line2'" + platform_is :windows do + cmd = "|cmd.exe /C echo hello&echo line2" + end - platform_is_not :windows do - it "gets data from a fork when passed -" do - lines = IO.readlines("|-") + lines = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + lines = IO.readlines(cmd) + end + lines.should == ["hello\n", "line2\n"] + end - if lines # parent - lines.should == ["hello\n", "from a fork\n"] - else - puts "hello" - puts "from a fork" - exit! + platform_is_not :windows do + it "gets data from a fork when passed -" do + lines = nil + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + lines = IO.readlines("|-") + end + + if lines # parent + lines.should == ["hello\n", "from a fork\n"] + else + puts "hello" + puts "from a fork" + exit! + end end end end + + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + IO.readlines(cmd) + }.should complain(/IO process creation with a leading '\|'/) + end + end end it_behaves_like :io_readlines, :readlines diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb index 2901b429c2..176c33cf9e 100644 --- a/spec/ruby/core/io/readpartial_spec.rb +++ b/spec/ruby/core/io/readpartial_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' @@ -59,10 +59,10 @@ describe "IO#readpartial" do end it "discards the existing buffer content upon successful read" do - buffer = "existing content" + buffer = +"existing content" @wr.write("hello world") @wr.close - @rd.readpartial(11, buffer) + @rd.readpartial(11, buffer).should.equal?(buffer) buffer.should == "hello world" end @@ -74,7 +74,7 @@ describe "IO#readpartial" do end it "discards the existing buffer content upon error" do - buffer = 'hello' + buffer = +'hello' @wr.close -> { @rd.readpartial(1, buffer) }.should raise_error(EOFError) buffer.should be_empty @@ -93,12 +93,15 @@ describe "IO#readpartial" do @rd.readpartial(0).should == "" end - ruby_bug "#18421", ""..."3.0.4" do - it "clears and returns the given buffer if the length argument is 0" do - buffer = "existing content" - @rd.readpartial(0, buffer).should == buffer - buffer.should == "" - end + it "raises IOError if the stream is closed and the length argument is 0" do + @rd.close + -> { @rd.readpartial(0) }.should raise_error(IOError, "closed stream") + end + + it "clears and returns the given buffer if the length argument is 0" do + buffer = +"existing content" + @rd.readpartial(0, buffer).should == buffer + buffer.should == "" end it "preserves the encoding of the given buffer" do @@ -106,6 +109,7 @@ describe "IO#readpartial" do @wr.write("abc") @wr.close @rd.readpartial(10, buffer) + buffer.encoding.should == Encoding::ISO_8859_1 end end diff --git a/spec/ruby/core/io/select_spec.rb b/spec/ruby/core/io/select_spec.rb index 4603c1fbbc..3893e7620f 100644 --- a/spec/ruby/core/io/select_spec.rb +++ b/spec/ruby/core/io/select_spec.rb @@ -55,8 +55,8 @@ describe "IO.select" do end end - it "returns supplied objects correctly even when monitoring the same object in different arrays" do - filename = tmp("IO_select_pipe_file") + $$.to_s + it "returns supplied objects correctly when monitoring the same object in different arrays" do + filename = tmp("IO_select_pipe_file") io = File.open(filename, 'w+') result = IO.select [io], [io], nil, 0 result.should == [[io], [io], []] @@ -64,6 +64,17 @@ describe "IO.select" do rm_r filename end + it "returns the pipe read end in read set if the pipe write end is closed concurrently" do + main = Thread.current + t = Thread.new { + Thread.pass until main.stop? + @wr.close + } + IO.select([@rd]).should == [[@rd], [], []] + ensure + t.join + end + it "invokes to_io on supplied objects that are not IO and returns the supplied objects" do # make some data available @wr.write("foobar") @@ -103,6 +114,39 @@ describe "IO.select" do it "raises an ArgumentError when passed a negative timeout" do -> { IO.select(nil, nil, nil, -5)}.should raise_error(ArgumentError) end + + describe "returns the available descriptors when the file descriptor" do + it "is in both read and error arrays" do + @wr.write("foobar") + result = IO.select([@rd], nil, [@rd]) + result.should == [[@rd], [], []] + end + + it "is in both write and error arrays" do + result = IO.select(nil, [@wr], [@wr]) + result.should == [[], [@wr], []] + end + + it "is in both read and write arrays" do + filename = tmp("IO_select_read_write_file") + w = File.open(filename, 'w+') + begin + IO.select([w], [w], []).should == [[w], [w], []] + ensure + w.close + rm_r filename + end + + IO.select([@wr], [@wr], []).should == [[], [@wr], []] + + @wr.write("foobar") + # CRuby on macOS returns [[@rd], [@rd], []], weird but we accept it here, probably only for pipe read-end + [ + [[@rd], [], []], + [[@rd], [@rd], []] + ].should.include? IO.select([@rd], [@rd], []) + end + end end describe "IO.select when passed nil for timeout" do diff --git a/spec/ruby/core/io/set_encoding_by_bom_spec.rb b/spec/ruby/core/io/set_encoding_by_bom_spec.rb index 92433d6640..30d5ce5a5a 100644 --- a/spec/ruby/core/io/set_encoding_by_bom_spec.rb +++ b/spec/ruby/core/io/set_encoding_by_bom_spec.rb @@ -62,11 +62,11 @@ describe "IO#set_encoding_by_bom" do @io.rewind @io.set_encoding(Encoding::ASCII_8BIT) - File.binwrite(@name, "\xFE\xFFabc") + File.binwrite(@name, "\xFE\xFFabcd") @io.set_encoding_by_bom.should == Encoding::UTF_16BE @io.external_encoding.should == Encoding::UTF_16BE - @io.read.b.should == "abc".b + @io.read.b.should == "abcd".b end it "returns the result encoding if found BOM UTF_32LE sequence" do @@ -94,11 +94,11 @@ describe "IO#set_encoding_by_bom" do @io.rewind @io.set_encoding(Encoding::ASCII_8BIT) - File.binwrite(@name, "\x00\x00\xFE\xFFabc") + File.binwrite(@name, "\x00\x00\xFE\xFFabcd") @io.set_encoding_by_bom.should == Encoding::UTF_32BE @io.external_encoding.should == Encoding::UTF_32BE - @io.read.b.should == "abc".b + @io.read.b.should == "abcd".b end it "returns nil if io is empty" do diff --git a/spec/ruby/core/io/shared/each.rb b/spec/ruby/core/io/shared/each.rb index dbc0178dd6..0747f31b8a 100644 --- a/spec/ruby/core/io/shared/each.rb +++ b/spec/ruby/core/io/shared/each.rb @@ -33,10 +33,6 @@ describe :io_each, shared: true do $_.should == "test" end - it "returns self" do - @io.send(@method) { |l| l }.should equal(@io) - end - it "raises an IOError when self is not readable" do -> { IOSpecs.closed_io.send(@method) {} }.should raise_error(IOError) end @@ -206,20 +202,10 @@ describe :io_each, shared: true do end describe "when passed chomp and nil as a separator" do - ruby_version_is "3.2" do - it "yields self's content" do - @io.pos = 100 - @io.send(@method, nil, chomp: true) { |s| ScratchPad << s } - ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] - end - end - - ruby_version_is ""..."3.2" do - it "yields self's content without trailing new line character" do - @io.pos = 100 - @io.send(@method, nil, chomp: true) { |s| ScratchPad << s } - ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six."] - end + it "yields self's content" do + @io.pos = 100 + @io.send(@method, nil, chomp: true) { |s| ScratchPad << s } + ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"] end end diff --git a/spec/ruby/core/io/shared/gets_ascii.rb b/spec/ruby/core/io/shared/gets_ascii.rb index 2a8fe3c9a5..2bd5470d99 100644 --- a/spec/ruby/core/io/shared/gets_ascii.rb +++ b/spec/ruby/core/io/shared/gets_ascii.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :io_gets_ascii, shared: true do describe "with ASCII separator" do before :each do diff --git a/spec/ruby/core/io/shared/new.rb b/spec/ruby/core/io/shared/new.rb index 99b19e6a1b..e84133493c 100644 --- a/spec/ruby/core/io/shared/new.rb +++ b/spec/ruby/core/io/shared/new.rb @@ -1,6 +1,6 @@ require_relative '../fixtures/classes' -# NOTE: should be syncronized with library/stringio/initialize_spec.rb +# NOTE: should be synchronized with library/stringio/initialize_spec.rb # This group of specs may ONLY contain specs that do successfully create # an IO instance from the file descriptor returned by #new_fd helper. @@ -208,6 +208,26 @@ describe :io_new, shared: true do @io.internal_encoding.to_s.should == 'IBM866' end + it "does not use binary encoding when mode encoding is specified along with binmode: true option" do + @io = IO.send(@method, @fd, 'w:iso-8859-1', binmode: true) + @io.external_encoding.to_s.should == 'ISO-8859-1' + end + + it "does not use textmode argument when mode encoding is specified" do + @io = IO.send(@method, @fd, 'w:ascii-8bit', textmode: true) + @io.external_encoding.to_s.should == 'ASCII-8BIT' + end + + it "does not use binmode argument when external encoding is specified via the :external_encoding option" do + @io = IO.send(@method, @fd, 'w', binmode: true, external_encoding: 'iso-8859-1') + @io.external_encoding.to_s.should == 'ISO-8859-1' + end + + it "does not use textmode argument when external encoding is specified via the :external_encoding option" do + @io = IO.send(@method, @fd, 'w', textmode: true, external_encoding: 'ascii-8bit') + @io.external_encoding.to_s.should == 'ASCII-8BIT' + end + it "raises ArgumentError for nil options" do -> { IO.send(@method, @fd, 'w', nil) @@ -325,6 +345,9 @@ describe :io_new_errors, shared: true do @io = IO.send(@method, @fd, 'w:ISO-8859-1', external_encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) -> { + @io = IO.send(@method, @fd, 'w:ISO-8859-1', internal_encoding: 'ISO-8859-1') + }.should raise_error(ArgumentError) + -> { @io = IO.send(@method, @fd, 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1') }.should raise_error(ArgumentError) end diff --git a/spec/ruby/core/io/shared/readlines.rb b/spec/ruby/core/io/shared/readlines.rb index d2b604bba3..6c1fa11a59 100644 --- a/spec/ruby/core/io/shared/readlines.rb +++ b/spec/ruby/core/io/shared/readlines.rb @@ -99,7 +99,7 @@ describe :io_readlines_options_19, shared: true do end it "accepts non-ASCII data as separator" do - result = IO.send(@method, @name, "\303\250".force_encoding("utf-8"), &@object) + result = IO.send(@method, @name, "\303\250".dup.force_encoding("utf-8"), &@object) (result ? result : ScratchPad.recorded).should == IOSpecs.lines_arbitrary_separator end end diff --git a/spec/ruby/core/io/stat_spec.rb b/spec/ruby/core/io/stat_spec.rb index 58eba02b8f..717c45d0a3 100644 --- a/spec/ruby/core/io/stat_spec.rb +++ b/spec/ruby/core/io/stat_spec.rb @@ -3,7 +3,8 @@ require_relative 'fixtures/classes' describe "IO#stat" do before :each do - @io = IO.popen 'cat', "r+" + cmd = platform_is(:windows) ? 'rem' : 'cat' + @io = IO.popen cmd, "r+" end after :each do diff --git a/spec/ruby/core/io/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb index e7f63cefec..d56a27b3af 100644 --- a/spec/ruby/core/io/sysread_spec.rb +++ b/spec/ruby/core/io/sysread_spec.rb @@ -21,25 +21,25 @@ describe "IO#sysread on a file" do end it "reads the specified number of bytes from the file to the buffer" do - buf = "" # empty buffer + buf = +"" # empty buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" @file.rewind - buf = "ABCDE" # small buffer + buf = +"ABCDE" # small buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" @file.rewind - buf = "ABCDE" * 5 # large buffer + buf = +"ABCDE" * 5 # large buffer @file.sysread(15, buf).should == buf buf.should == "012345678901234" end it "coerces the second argument to string and uses it as a buffer" do - buf = "ABCDE" + buf = +"ABCDE" (obj = mock("buff")).should_receive(:to_str).any_number_of_times.and_return(buf) @file.sysread(15, obj).should == buf buf.should == "012345678901234" @@ -90,23 +90,30 @@ describe "IO#sysread on a file" do end it "immediately returns the given buffer if the length argument is 0" do - buffer = "existing content" + buffer = +"existing content" @file.sysread(0, buffer).should == buffer buffer.should == "existing content" end it "discards the existing buffer content upon successful read" do - buffer = "existing content" - @file.sysread(11, buffer) + buffer = +"existing content" + @file.sysread(11, buffer).should.equal?(buffer) buffer.should == "01234567890" end it "discards the existing buffer content upon error" do - buffer = "existing content" + buffer = +"existing content" @file.seek(0, :END) -> { @file.sysread(1, buffer) }.should raise_error(EOFError) buffer.should be_empty end + + it "preserves the encoding of the given buffer" do + buffer = ''.encode(Encoding::ISO_8859_1) + string = @file.sysread(10, buffer) + + buffer.encoding.should == Encoding::ISO_8859_1 + end end describe "IO#sysread" do @@ -124,9 +131,7 @@ describe "IO#sysread" do @read.sysread(3).should == "ab" end - guard_not -> { platform_is :windows and ruby_version_is ""..."3.2" } do # https://bugs.ruby-lang.org/issues/18880 - it "raises ArgumentError when length is less than 0" do - -> { @read.sysread(-1) }.should raise_error(ArgumentError) - end + it "raises ArgumentError when length is less than 0" do + -> { @read.sysread(-1) }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/core/io/write_nonblock_spec.rb b/spec/ruby/core/io/write_nonblock_spec.rb index c403c864fd..5bfc690f9b 100644 --- a/spec/ruby/core/io/write_nonblock_spec.rb +++ b/spec/ruby/core/io/write_nonblock_spec.rb @@ -43,7 +43,7 @@ platform_is_not :windows do it "checks if the file is writable if writing zero bytes" do -> { - @readonly_file.write_nonblock("") + @readonly_file.write_nonblock("") }.should raise_error(IOError) end end diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index 3f04fb053f..e58100f846 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -203,18 +203,33 @@ describe "IO.write" do rm_r @fifo end - it "writes correctly" do - thr = Thread.new do - IO.read(@fifo) - end - begin - string = "hi" - IO.write(@fifo, string).should == string.length - ensure - thr.join + # rb_cloexec_open() is currently missing a retry on EINTR. + # @ioquatix is looking into fixing it. Quarantined until it's done. + quarantine! do + it "writes correctly" do + thr = Thread.new do + IO.read(@fifo) + end + begin + string = "hi" + IO.write(@fifo, string).should == string.length + ensure + thr.join + end end end end + + ruby_version_is "3.3"..."4.0" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + -> { + -> { + IO.write("|cat", "xxx") + }.should output_to_fd("xxx") + }.should complain(/IO process creation with a leading '\|'/) + end + end end end diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index 015bcb33d6..9c436b05f7 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -41,7 +41,7 @@ describe :kernel_float, shared: true do end it "converts Strings to floats without calling #to_f" do - string = "10" + string = +"10" string.should_not_receive(:to_f) @object.send(:Float, string).should == 10.0 end @@ -157,6 +157,26 @@ describe :kernel_float, shared: true do @object.send(:Float, "1\t\n").should == 1.0 end + ruby_version_is ""..."3.4" do + it "raises ArgumentError if a fractional part is missing" do + -> { @object.send(:Float, "1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "+1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "-1.") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1.e+0") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "1.e-2") }.should raise_error(ArgumentError) + end + end + + ruby_version_is "3.4" do + it "allows String representation without a fractional part" do + @object.send(:Float, "1.").should == 1.0 + @object.send(:Float, "+1.").should == 1.0 + @object.send(:Float, "-1.").should == -1.0 + @object.send(:Float, "1.e+0").should == 1.0 + @object.send(:Float, "1.e-2").should be_close(0.01, TOLERANCE) + end + end + %w(e E).each do |e| it "raises an ArgumentError if #{e} is the trailing character" do -> { @object.send(:Float, "2#{e}") }.should raise_error(ArgumentError) @@ -204,59 +224,107 @@ describe :kernel_float, shared: true do end end - describe "for hexadecimal literals with binary exponent" do - %w(p P).each do |p| - it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do - @object.send(:Float, "0x10#{p}0").should == 16.0 - end - - it "interprets the exponent (on the right of '#{p}') in decimal" do - @object.send(:Float, "0x1#{p}10").should == 1024.0 - end - - it "raises an ArgumentError if #{p} is the trailing character" do - -> { @object.send(:Float, "0x1#{p}") }.should raise_error(ArgumentError) - end - - it "raises an ArgumentError if #{p} is the leading character" do - -> { @object.send(:Float, "0x#{p}1") }.should raise_error(ArgumentError) - end + context "for hexadecimal literals" do + it "interprets the 0x prefix as hexadecimal" do + @object.send(:Float, "0x10").should == 16.0 + @object.send(:Float, "0x0F").should == 15.0 + @object.send(:Float, "0x0f").should == 15.0 + end - it "returns Infinity for '0x1#{p}10000'" do - @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY - end + it "interprets negative hex value" do + @object.send(:Float, "-0x10").should == -16.0 + end - it "returns 0 for '0x1#{p}-10000'" do - @object.send(:Float, "0x1#{p}-10000").should == 0 - end + it "accepts embedded _ if the number does not contain a-f" do + @object.send(:Float, "0x1_0").should == 16.0 + end - it "allows embedded _ in a number on either side of the #{p}" do - @object.send(:Float, "0x1_0#{p}10").should == 16384.0 - @object.send(:Float, "0x10#{p}1_0").should == 16384.0 - @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0 + ruby_version_is ""..."3.4.3" do + it "does not accept embedded _ if the number contains a-f" do + -> { @object.send(:Float, "0x1_0a") }.should raise_error(ArgumentError) + @object.send(:Float, "0x1_0a", exception: false).should be_nil end + end - it "raises an exception if a space is embedded on either side of the '#{p}'" do - -> { @object.send(:Float, "0x1 0#{p}10") }.should raise_error(ArgumentError) - -> { @object.send(:Float, "0x10#{p}1 0") }.should raise_error(ArgumentError) + ruby_version_is "3.4.3" do + it "accepts embedded _ if the number contains a-f" do + @object.send(:Float, "0x1_0a").should == 0x10a.to_f end + end - it "raises an exception if there's a leading _ on either side of the '#{p}'" do - -> { @object.send(:Float, "0x_10#{p}10") }.should raise_error(ArgumentError) - -> { @object.send(:Float, "0x10#{p}_10") }.should raise_error(ArgumentError) - end + it "does not accept _ before, after or inside the 0x prefix" do + -> { @object.send(:Float, "_0x10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0_x10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x_10") }.should raise_error(ArgumentError) + @object.send(:Float, "_0x10", exception: false).should be_nil + @object.send(:Float, "0_x10", exception: false).should be_nil + @object.send(:Float, "0x_10", exception: false).should be_nil + end - it "raises an exception if there's a trailing _ on either side of the '#{p}'" do - -> { @object.send(:Float, "0x10_#{p}10") }.should raise_error(ArgumentError) - -> { @object.send(:Float, "0x10#{p}10_") }.should raise_error(ArgumentError) - end + it "parses negative hexadecimal string as negative float" do + @object.send(:Float, "-0x7b").should == -123.0 + end - it "allows hexadecimal points on the left side of the '#{p}'" do - @object.send(:Float, "0x1.8#{p}0").should == 1.5 + ruby_version_is "3.4" do + it "accepts a fractional part" do + @object.send(:Float, "0x0.8").should == 0.5 end + end - it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do - -> { @object.send(:Float, "0x1#{p}1.0") }.should raise_error(ArgumentError) + describe "with binary exponent" do + %w(p P).each do |p| + it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do + @object.send(:Float, "0x10#{p}0").should == 16.0 + end + + it "interprets the exponent (on the right of '#{p}') in decimal" do + @object.send(:Float, "0x1#{p}10").should == 1024.0 + end + + it "raises an ArgumentError if #{p} is the trailing character" do + -> { @object.send(:Float, "0x1#{p}") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if #{p} is the leading character" do + -> { @object.send(:Float, "0x#{p}1") }.should raise_error(ArgumentError) + end + + it "returns Infinity for '0x1#{p}10000'" do + @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY + end + + it "returns 0 for '0x1#{p}-10000'" do + @object.send(:Float, "0x1#{p}-10000").should == 0 + end + + it "allows embedded _ in a number on either side of the #{p}" do + @object.send(:Float, "0x1_0#{p}10").should == 16384.0 + @object.send(:Float, "0x10#{p}1_0").should == 16384.0 + @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0 + end + + it "raises an exception if a space is embedded on either side of the '#{p}'" do + -> { @object.send(:Float, "0x1 0#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}1 0") }.should raise_error(ArgumentError) + end + + it "raises an exception if there's a leading _ on either side of the '#{p}'" do + -> { @object.send(:Float, "0x_10#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}_10") }.should raise_error(ArgumentError) + end + + it "raises an exception if there's a trailing _ on either side of the '#{p}'" do + -> { @object.send(:Float, "0x10_#{p}10") }.should raise_error(ArgumentError) + -> { @object.send(:Float, "0x10#{p}10_") }.should raise_error(ArgumentError) + end + + it "allows hexadecimal points on the left side of the '#{p}'" do + @object.send(:Float, "0x1.8#{p}0").should == 1.5 + end + + it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do + -> { @object.send(:Float, "0x1#{p}1.0") }.should raise_error(ArgumentError) + end end end end @@ -280,7 +348,7 @@ describe :kernel_float, shared: true do nan2.should equal(nan) end - it "returns the identical Infinity if to_f is called and it returns Infinity" do + it "returns the identical Infinity if #to_f is called and it returns Infinity" do infinity = infinity_value (infinity_to_f = mock('Infinity')).should_receive(:to_f).once.and_return(infinity) infinity2 = @object.send(:Float, infinity_to_f) diff --git a/spec/ruby/core/kernel/Integer_spec.rb b/spec/ruby/core/kernel/Integer_spec.rb index c37733e88d..74dd3e0dd2 100644 --- a/spec/ruby/core/kernel/Integer_spec.rb +++ b/spec/ruby/core/kernel/Integer_spec.rb @@ -586,6 +586,21 @@ describe :kernel_integer_string_base, shared: true do Integer("777", obj).should == 0777 end + # https://bugs.ruby-lang.org/issues/19349 + ruby_version_is ''...'3.3' do + it "ignores the base if it is not an integer and does not respond to #to_i" do + Integer("777", "8").should == 777 + end + end + + ruby_version_is '3.3' do + it "raises a TypeError if it is not an integer and does not respond to #to_i" do + -> { + Integer("777", "8") + }.should raise_error(TypeError, "no implicit conversion of String into Integer") + end + end + describe "when passed exception: false" do describe "and valid argument" do it "returns an Integer number" do diff --git a/spec/ruby/core/kernel/Rational_spec.rb b/spec/ruby/core/kernel/Rational_spec.rb index 2d1051db7f..cc11a35451 100644 --- a/spec/ruby/core/kernel/Rational_spec.rb +++ b/spec/ruby/core/kernel/Rational_spec.rb @@ -1,6 +1,236 @@ require_relative '../../spec_helper' -require_relative '../../shared/rational/Rational' +require_relative '../rational/fixtures/rational' describe "Kernel.Rational" do - it_behaves_like :kernel_Rational, :Rational + describe "passed Integer" do + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns a new Rational number with 1 as the denominator" do + Rational(1).should eql(Rational(1, 1)) + Rational(-3).should eql(Rational(-3, 1)) + Rational(bignum_value).should eql(Rational(bignum_value, 1)) + end + end + end + + describe "passed two integers" do + it "returns a new Rational number" do + rat = Rational(1, 2) + rat.numerator.should == 1 + rat.denominator.should == 2 + rat.should be_an_instance_of(Rational) + + rat = Rational(-3, -5) + rat.numerator.should == 3 + rat.denominator.should == 5 + rat.should be_an_instance_of(Rational) + + rat = Rational(bignum_value, 3) + rat.numerator.should == bignum_value + rat.denominator.should == 3 + rat.should be_an_instance_of(Rational) + end + + it "reduces the Rational" do + rat = Rational(2, 4) + rat.numerator.should == 1 + rat.denominator.should == 2 + + rat = Rational(3, 9) + rat.numerator.should == 1 + rat.denominator.should == 3 + end + end + + describe "when passed a String" do + it "converts the String to a Rational using the same method as String#to_r" do + r = Rational(13, 25) + s_r = ".52".to_r + r_s = Rational(".52") + + r_s.should == r + r_s.should == s_r + end + + it "scales the Rational value of the first argument by the Rational value of the second" do + Rational(".52", ".6").should == Rational(13, 15) + Rational(".52", "1.6").should == Rational(13, 40) + end + + it "does not use the same method as Float#to_r" do + r = Rational(3, 5) + f_r = 0.6.to_r + r_s = Rational("0.6") + + r_s.should == r + r_s.should_not == f_r + end + end + + describe "when passed a Numeric" do + it "calls #to_r to convert the first argument to a Rational" do + num = RationalSpecs::SubNumeric.new(2) + + Rational(num).should == Rational(2) + end + end + + describe "when passed a Complex" do + context "[Complex]" do + it "returns a Rational from the real part if the imaginary part is 0" do + Rational(Complex(1, 0)).should == Rational(1) + end + + it "raises a RangeError if the imaginary part is not 0" do + -> { Rational(Complex(1, 2)) }.should raise_error(RangeError, "can't convert 1+2i into Rational") + end + end + + context "[Numeric, Complex]" do + it "uses the real part if the imaginary part is 0" do + Rational(1, Complex(2, 0)).should == Rational(1, 2) + end + + it "divides a numerator by the Complex denominator if the imaginary part is not 0" do + Rational(1, Complex(2, 1)).should == Complex(2/5r, -1/5r) + end + end + end + + context "when passed neither a Numeric nor a String" do + it "converts to Rational with #to_r method" do + obj = Object.new + def obj.to_r; 1/2r; end + + Rational(obj).should == 1/2r + end + + it "tries to convert to Integer with #to_int method if it does not respond to #to_r" do + obj = Object.new + def obj.to_int; 1; end + + Rational(obj).should == 1r + end + + it "raises TypeError if it neither responds to #to_r nor #to_int method" do + -> { Rational([]) }.should raise_error(TypeError, "can't convert Array into Rational") + -> { Rational({}) }.should raise_error(TypeError, "can't convert Hash into Rational") + -> { Rational(nil) }.should raise_error(TypeError, "can't convert nil into Rational") + end + + it "swallows exception raised in #to_int method" do + object = Object.new + def object.to_int() raise NoMethodError; end + + -> { Rational(object) }.should raise_error(TypeError) + -> { Rational(object, 1) }.should raise_error(TypeError) + -> { Rational(1, object) }.should raise_error(TypeError) + end + + it "raises TypeError if #to_r does not return Rational" do + obj = Object.new + def obj.to_r; []; end + + -> { Rational(obj) }.should raise_error(TypeError, "can't convert Object to Rational (Object#to_r gives Array)") + end + end + + it "raises a ZeroDivisionError if the second argument is 0" do + -> { Rational(1, 0) }.should raise_error(ZeroDivisionError, "divided by 0") + -> { Rational(1, 0.0) }.should raise_error(ZeroDivisionError, "divided by 0") + end + + it "raises a TypeError if the first argument is nil" do + -> { Rational(nil) }.should raise_error(TypeError, "can't convert nil into Rational") + end + + it "raises a TypeError if the second argument is nil" do + -> { Rational(1, nil) }.should raise_error(TypeError, "can't convert nil into Rational") + end + + it "raises a TypeError if the first argument is a Symbol" do + -> { Rational(:sym) }.should raise_error(TypeError) + end + + it "raises a TypeError if the second argument is a Symbol" do + -> { Rational(1, :sym) }.should raise_error(TypeError) + end + + describe "when passed exception: false" do + describe "and [non-Numeric]" do + it "swallows an error" do + Rational(:sym, exception: false).should == nil + Rational("abc", exception: false).should == nil + end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, exception: false).should == nil + end + end + + describe "and [non-Numeric, Numeric]" do + it "swallows an error" do + Rational(:sym, 1, exception: false).should == nil + Rational("abc", 1, exception: false).should == nil + end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, 1, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, 1, exception: false).should == nil + end + end + + describe "and [anything, non-Numeric]" do + it "swallows an error" do + Rational(:sym, :sym, exception: false).should == nil + Rational("abc", :sym, exception: false).should == nil + end + + it "swallows an exception raised in #to_r" do + obj = Object.new + def obj.to_r; raise; end + Rational(obj, obj, exception: false).should == nil + end + + it "swallows an exception raised in #to_int" do + obj = Object.new + def obj.to_int; raise; end + Rational(obj, obj, exception: false).should == nil + end + end + + describe "and non-Numeric String arguments" do + it "swallows an error" do + Rational("a", "b", exception: false).should == nil + Rational("a", 0, exception: false).should == nil + Rational(0, "b", exception: false).should == nil + end + end + + describe "and nil arguments" do + it "swallows an error" do + Rational(nil, exception: false).should == nil + Rational(nil, nil, exception: false).should == nil + end + end + end + + it "freezes its result" do + Rational(1).frozen?.should == true + end end diff --git a/spec/ruby/core/kernel/String_spec.rb b/spec/ruby/core/kernel/String_spec.rb index 47ee797be5..7caec6eda5 100644 --- a/spec/ruby/core/kernel/String_spec.rb +++ b/spec/ruby/core/kernel/String_spec.rb @@ -78,7 +78,7 @@ describe :kernel_String, shared: true do end it "returns the same object if it is already a String" do - string = "Hello" + string = +"Hello" string.should_not_receive(:to_s) string2 = @object.send(@method, string) string.should equal(string2) diff --git a/spec/ruby/core/kernel/autoload_spec.rb b/spec/ruby/core/kernel/autoload_spec.rb index 0404caec6d..5edb70541d 100644 --- a/spec/ruby/core/kernel/autoload_spec.rb +++ b/spec/ruby/core/kernel/autoload_spec.rb @@ -7,7 +7,9 @@ require_relative 'fixtures/classes' autoload :KSAutoloadA, "autoload_a.rb" autoload :KSAutoloadB, fixture(__FILE__, "autoload_b.rb") -autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb" +define_autoload_KSAutoloadCallsRequire = -> { + autoload :KSAutoloadCallsRequire, "main_autoload_not_exist.rb" +} def check_autoload(const) autoload? const @@ -43,6 +45,7 @@ describe "Kernel#autoload" do end it "calls main.require(path) to load the file" do + define_autoload_KSAutoloadCallsRequire.call main = TOPLEVEL_BINDING.eval("self") main.should_receive(:require).with("main_autoload_not_exist.rb") # The constant won't be defined since require is mocked to do nothing diff --git a/spec/ruby/core/kernel/block_given_spec.rb b/spec/ruby/core/kernel/block_given_spec.rb index b00bfabfc3..aece4c821d 100644 --- a/spec/ruby/core/kernel/block_given_spec.rb +++ b/spec/ruby/core/kernel/block_given_spec.rb @@ -5,15 +5,20 @@ describe :kernel_block_given, shared: true do it "returns true if and only if a block is supplied" do @object.accept_block {}.should == true @object.accept_block_as_argument {}.should == true + @object.accept_block_inside_block {}.should == true + @object.accept_block_as_argument_inside_block {}.should == true @object.accept_block.should == false @object.accept_block_as_argument.should == false + @object.accept_block_inside_block.should == false + @object.accept_block_as_argument_inside_block.should == false end # Clarify: Based on http://www.ruby-forum.com/topic/137822 it appears # that Matz wanted this to be true in 1.9. it "returns false when a method defined by define_method is called with a block" do @object.defined_block {}.should == false + @object.defined_block_inside_block {}.should == false end end diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb index 5994b28fa3..a917dba504 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -71,14 +71,40 @@ describe 'Kernel#caller_locations' do end guard -> { Kernel.instance_method(:tap).source_location } do - it "includes core library methods defined in Ruby" do - file, line = Kernel.instance_method(:tap).source_location - file.should.start_with?('<internal:') - - loc = nil - tap { loc = caller_locations(1, 1)[0] } - loc.label.should == "tap" - loc.path.should.start_with? "<internal:" + ruby_version_is ""..."3.4" do + it "includes core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller_locations(1, 1)[0] } + loc.label.should == "tap" + loc.path.should.start_with? "<internal:" + end + end + + ruby_version_is "3.4"..."4.0" do + it "includes core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller_locations(1, 1)[0] } + loc.label.should == "Kernel#tap" + loc.path.should.start_with? "<internal:" + end + end + + ruby_version_is "4.0" do + it "does not include core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller_locations(1, 1)[0] } + loc.label.should == "Kernel#tap" + loc.path.should == __FILE__ + end end end end diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb index f1ff7044b8..7cd703de5a 100644 --- a/spec/ruby/core/kernel/caller_spec.rb +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -38,33 +38,72 @@ describe 'Kernel#caller' do it "returns an Array with the block given to #at_exit at the base of the stack" do path = fixture(__FILE__, "caller_at_exit.rb") lines = ruby_exe(path).lines - lines.should == [ - "#{path}:6:in `foo'\n", - "#{path}:2:in `block in <main>'\n" - ] + lines.size.should == 2 + lines[0].should =~ /\A#{path}:6:in [`'](?:Object#)?foo'\n\z/ + lines[1].should =~ /\A#{path}:2:in [`']block in <main>'\n\z/ + end + + it "can be called with a range" do + locations1 = caller(0) + locations2 = caller(2..4) + locations1[2..4].should == locations2 end it "works with endless ranges" do locations1 = KernelSpecs::CallerTest.locations(0) locations2 = KernelSpecs::CallerTest.locations(eval("(2..)")) - locations2.map(&:to_s).should == locations1[2..-1].map(&:to_s) + locations2.should == locations1[2..-1] end it "works with beginless ranges" do locations1 = KernelSpecs::CallerTest.locations(0) locations2 = KernelSpecs::CallerTest.locations((..5)) - locations2.map(&:to_s)[eval("(2..)")].should == locations1[(..5)].map(&:to_s)[eval("(2..)")] + locations2[eval("(2..)")].should == locations1[(..5)][eval("(2..)")] + end + + it "can be called with a range whose end is negative" do + locations1 = caller(0) + locations2 = caller(2..-1) + locations3 = caller(2..-2) + locations1[2..-1].should == locations2 + locations1[2..-2].should == locations3 + end + + it "must return nil if omitting more locations than available" do + caller(100).should == nil + caller(100..-1).should == nil + end + + it "must return [] if omitting exactly the number of locations available" do + omit = caller(0).length + caller(omit).should == [] + end + + it "must return the same locations when called with 1..-1 and when called with no arguments" do + caller.should == caller(1..-1) end guard -> { Kernel.instance_method(:tap).source_location } do - it "includes core library methods defined in Ruby" do - file, line = Kernel.instance_method(:tap).source_location - file.should.start_with?('<internal:') - - loc = nil - tap { loc = caller(1, 1)[0] } - loc.should.end_with? "in `tap'" - loc.should.start_with? "<internal:" + ruby_version_is ""..."4.0" do + it "includes core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller(1, 1)[0] } + loc.should =~ /\A<internal:.*in [`'](?:Kernel#)?tap'\z/ + end + end + + ruby_version_is "4.0" do + it "includes core library methods defined in Ruby" do + file, line = Kernel.instance_method(:tap).source_location + file.should.start_with?('<internal:') + + loc = nil + tap { loc = caller(1, 1)[0] } + loc.should =~ /\A#{ __FILE__ }:.*in [`'](?:Kernel#)?tap'\z/ + end end end end diff --git a/spec/ruby/core/kernel/catch_spec.rb b/spec/ruby/core/kernel/catch_spec.rb index 4060172429..9f59d3b384 100644 --- a/spec/ruby/core/kernel/catch_spec.rb +++ b/spec/ruby/core/kernel/catch_spec.rb @@ -35,7 +35,7 @@ describe "Kernel.catch" do end it "raises an ArgumentError if a String with different identity is thrown" do - -> { catch("exit") { throw "exit" } }.should raise_error(ArgumentError) + -> { catch("exit".dup) { throw "exit".dup } }.should raise_error(ArgumentError) end it "catches a Symbol when thrown a matching Symbol" do diff --git a/spec/ruby/core/kernel/class_spec.rb b/spec/ruby/core/kernel/class_spec.rb index 2725bde19b..b1d9df1671 100644 --- a/spec/ruby/core/kernel/class_spec.rb +++ b/spec/ruby/core/kernel/class_spec.rb @@ -19,7 +19,7 @@ describe "Kernel#class" do end it "returns the first non-singleton class" do - a = "hello" + a = +"hello" def a.my_singleton_method; end a.class.should equal(String) end diff --git a/spec/ruby/core/kernel/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb index 3dfc863368..e027294347 100644 --- a/spec/ruby/core/kernel/eval_spec.rb +++ b/spec/ruby/core/kernel/eval_spec.rb @@ -135,7 +135,7 @@ describe "Kernel#eval" do it "includes file and line information in syntax error" do expected = 'speccing.rb' -> { - eval('if true',TOPLEVEL_BINDING, expected) + eval('if true', TOPLEVEL_BINDING, expected) }.should raise_error(SyntaxError) { |e| e.message.should =~ /#{expected}:1:.+/ } @@ -144,7 +144,7 @@ describe "Kernel#eval" do it "evaluates string with given filename and negative linenumber" do expected_file = 'speccing.rb' -> { - eval('if true',TOPLEVEL_BINDING, expected_file, -100) + eval('if true', TOPLEVEL_BINDING, expected_file, -100) }.should raise_error(SyntaxError) { |e| e.message.should =~ /#{expected_file}:-100:.+/ } @@ -175,6 +175,75 @@ describe "Kernel#eval" do end end + context "parameter forwarding" do + it "allows anonymous rest parameter forwarding" do + object = Object.new + def object.foo(a, b, c) + [a, b, c] + end + def object.bar(*) + eval "foo(*)" + end + + object.bar(1, 2, 3).should == [1, 2, 3] + end + + it "allows anonymous keyword parameters forwarding" do + object = Object.new + def object.foo(a:, b:, c:) + [a, b, c] + end + def object.bar(**) + eval "foo(**)" + end + + object.bar(a: 1, b: 2, c: 3).should == [1, 2, 3] + end + + it "allows anonymous block parameter forwarding" do + object = Object.new + def object.foo(&block) + block.call + end + def object.bar(&) + eval "foo(&)" + end + + object.bar { :foobar }.should == :foobar + end + + it "allows ... forwarding" do + object = Object.new + def object.foo(a, b:, &block) + [a, b, block.call] + end + def object.bar(...) + eval "foo(...)" + end + + object.bar(1, b: 2) { 3 }.should == [1, 2, 3] + end + + it "allows parameter forwarding to super" do + m = Module.new do + def foo(a, b:, &block) + [a, b, block.call] + end + end + + c = Class.new do + include m + + def foo(a, b:, &block) + eval "super" + end + end + + object = c.new + object.foo(1, b: 2) { 3 }.should == [1, 2, 3] + end + end + ruby_version_is "3.3" do it "uses (eval at __FILE__:__LINE__) if none is provided" do eval("__FILE__").should == "(eval at #{__FILE__}:#{__LINE__})" @@ -261,6 +330,39 @@ describe "Kernel#eval" do end end + it "makes flip-flop operator work correctly" do + ScratchPad.record [] + + eval "10.times { |i| ScratchPad << i if (i == 4)...(i == 4) }" + ScratchPad.recorded.should == [4, 5, 6, 7, 8, 9] + + ScratchPad.clear + end + + it "returns nil if given an empty string" do + eval("").should == nil + end + + context "with shebang" do + it "ignores shebang with ruby interpreter" do + pid = eval(<<~CODE.b) + #!/usr/bin/env ruby + Process.pid + CODE + + pid.should == Process.pid + end + + it "ignores shebang with non-ruby interpreter" do + pid = eval(<<~CODE.b) + #!/usr/bin/env puma + Process.pid + CODE + + pid.should == Process.pid + end + end + # See language/magic_comment_spec.rb for more magic comments specs describe "with a magic encoding comment" do it "uses the magic comment encoding for the encoding of literal strings" do @@ -280,6 +382,8 @@ CODE eval(code) EvalSpecs.constants(false).should include(:"VÏ€") EvalSpecs::VÏ€.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :VÏ€) end it "allows an emacs-style magic comment encoding" do @@ -293,6 +397,8 @@ CODE eval(code) EvalSpecs.constants(false).should include(:"VÏ€emacs") EvalSpecs::VÏ€emacs.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :VÏ€emacs) end it "allows spaces before the magic encoding comment" do @@ -306,6 +412,8 @@ CODE eval(code) EvalSpecs.constants(false).should include(:"VÏ€spaces") EvalSpecs::VÏ€spaces.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :VÏ€spaces) end it "allows a shebang line before the magic encoding comment" do @@ -320,6 +428,8 @@ CODE eval(code) EvalSpecs.constants(false).should include(:"VÏ€shebang") EvalSpecs::VÏ€shebang.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :VÏ€shebang) end it "allows a shebang line and some spaces before the magic encoding comment" do @@ -334,15 +444,16 @@ CODE eval(code) EvalSpecs.constants(false).should include(:"VÏ€shebang_spaces") EvalSpecs::VÏ€shebang_spaces.should == 3.14 + ensure + EvalSpecs.send(:remove_const, :VÏ€shebang_spaces) end it "allows a magic encoding comment and a subsequent frozen_string_literal magic comment" do - # Make sure frozen_string_literal is not default true - eval("'foo'".b).frozen?.should be_false + frozen_string_default = "test".frozen? code = <<CODE.b # encoding: UTF-8 -# frozen_string_literal: true +# frozen_string_literal: #{!frozen_string_default} class EvalSpecs VÏ€string = "frozen" end @@ -352,7 +463,9 @@ CODE EvalSpecs.constants(false).should include(:"VÏ€string") EvalSpecs::VÏ€string.should == "frozen" EvalSpecs::VÏ€string.encoding.should == Encoding::UTF_8 - EvalSpecs::VÏ€string.frozen?.should be_true + EvalSpecs::VÏ€string.frozen?.should == !frozen_string_default + ensure + EvalSpecs.send(:remove_const, :VÏ€string) end it "allows a magic encoding comment and a frozen_string_literal magic comment on the same line in emacs style" do @@ -368,11 +481,14 @@ CODE EvalSpecs::VÏ€same_line.should == "frozen" EvalSpecs::VÏ€same_line.encoding.should == Encoding::UTF_8 EvalSpecs::VÏ€same_line.frozen?.should be_true + ensure + EvalSpecs.send(:remove_const, :VÏ€same_line) end it "ignores the magic encoding comment if it is after a frozen_string_literal magic comment" do + frozen_string_default = "test".frozen? code = <<CODE.b -# frozen_string_literal: true +# frozen_string_literal: #{!frozen_string_default} # encoding: UTF-8 class EvalSpecs VÏ€frozen_first = "frozen" @@ -386,23 +502,26 @@ CODE value = EvalSpecs.const_get(binary_constant) value.should == "frozen" value.encoding.should == Encoding::BINARY - value.frozen?.should be_true + value.frozen?.should == !frozen_string_default + ensure + EvalSpecs.send(:remove_const, binary_constant) end it "ignores the frozen_string_literal magic comment if it appears after a token and warns if $VERBOSE is true" do + frozen_string_default = "test".frozen? code = <<CODE some_token_before_magic_comment = :anything -# frozen_string_literal: true +# frozen_string_literal: #{!frozen_string_default} class EvalSpecs VÏ€string_not_frozen = "not frozen" end CODE - -> { eval(code) }.should complain(/warning: `frozen_string_literal' is ignored after any tokens/, verbose: true) - EvalSpecs::VÏ€string_not_frozen.frozen?.should be_false + -> { eval(code) }.should complain(/warning: [`']frozen_string_literal' is ignored after any tokens/, verbose: true) + EvalSpecs::VÏ€string_not_frozen.frozen?.should == frozen_string_default EvalSpecs.send :remove_const, :VÏ€string_not_frozen -> { eval(code) }.should_not complain(verbose: false) - EvalSpecs::VÏ€string_not_frozen.frozen?.should be_false + EvalSpecs::VÏ€string_not_frozen.frozen?.should == frozen_string_default EvalSpecs.send :remove_const, :VÏ€string_not_frozen end end diff --git a/spec/ruby/core/kernel/exec_spec.rb b/spec/ruby/core/kernel/exec_spec.rb index 1b4a7ae6f4..3d9520ad67 100644 --- a/spec/ruby/core/kernel/exec_spec.rb +++ b/spec/ruby/core/kernel/exec_spec.rb @@ -7,12 +7,12 @@ describe "Kernel#exec" do end it "runs the specified command, replacing current process" do - ruby_exe('exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + ruby_exe('exec "echo hello"; puts "fail"').should == "hello\n" end end describe "Kernel.exec" do it "runs the specified command, replacing current process" do - ruby_exe('Kernel.exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + ruby_exe('Kernel.exec "echo hello"; puts "fail"').should == "hello\n" end end diff --git a/spec/ruby/core/kernel/extend_spec.rb b/spec/ruby/core/kernel/extend_spec.rb index 47b22f3a18..6342d8cae1 100644 --- a/spec/ruby/core/kernel/extend_spec.rb +++ b/spec/ruby/core/kernel/extend_spec.rb @@ -76,4 +76,16 @@ describe "Kernel#extend" do -> { @frozen.extend @module }.should raise_error(FrozenError) end end + + it "updated class methods of a module when it extends self and includes another module" do + a = Module.new do + extend self + end + b = Module.new do + def foo; :foo; end + end + + a.include b + a.foo.should == :foo + end end diff --git a/spec/ruby/core/kernel/fixtures/classes.rb b/spec/ruby/core/kernel/fixtures/classes.rb index 541a4c075e..0e2b81988f 100644 --- a/spec/ruby/core/kernel/fixtures/classes.rb +++ b/spec/ruby/core/kernel/fixtures/classes.rb @@ -219,10 +219,28 @@ module KernelSpecs block_given? end + def self.accept_block_inside_block() + yield_self { + block_given? + } + end + + def self.accept_block_as_argument_inside_block(&block) + yield_self { + block_given? + } + end + class << self define_method(:defined_block) do block_given? end + + define_method(:defined_block_inside_block) do + yield_self { + block_given? + } + end end end @@ -235,10 +253,28 @@ module KernelSpecs self.send(:block_given?) end + def self.accept_block_inside_block + yield_self { + self.send(:block_given?) + } + end + + def self.accept_block_as_argument_inside_block(&block) + yield_self { + self.send(:block_given?) + } + end + class << self define_method(:defined_block) do self.send(:block_given?) end + + define_method(:defined_block_inside_block) do + yield_self { + self.send(:block_given?) + } + end end end @@ -251,10 +287,28 @@ module KernelSpecs Kernel.block_given? end + def self.accept_block_inside_block + yield_self { + Kernel.block_given? + } + end + + def self.accept_block_as_argument_inside_block(&block) + yield_self { + Kernel.block_given? + } + end + class << self define_method(:defined_block) do Kernel.block_given? end + + define_method(:defined_block_inside_block) do + yield_self { + Kernel.block_given? + } + end end end diff --git a/spec/ruby/core/kernel/format_spec.rb b/spec/ruby/core/kernel/format_spec.rb index e8b031e480..1d0c000c15 100644 --- a/spec/ruby/core/kernel/format_spec.rb +++ b/spec/ruby/core/kernel/format_spec.rb @@ -12,4 +12,36 @@ describe "Kernel.format" do it "is accessible as a module function" do Kernel.format("%s", "hello").should == "hello" end + + describe "when $VERBOSE is true" do + it "warns if too many arguments are passed" do + code = <<~RUBY + $VERBOSE = true + format("test", 1) + RUBY + + ruby_exe(code, args: "2>&1").should include("warning: too many arguments for format string") + end + + it "does not warns if too many keyword arguments are passed" do + code = <<~RUBY + $VERBOSE = true + format("test %{test}", test: 1, unused: 2) + RUBY + + ruby_exe(code, args: "2>&1").should_not include("warning") + end + + ruby_bug "#20593", ""..."3.4" do + it "doesn't warns if keyword arguments are passed and none are used" do + code = <<~RUBY + $VERBOSE = true + format("test", test: 1) + format("test", {}) + RUBY + + ruby_exe(code, args: "2>&1").should_not include("warning") + end + end + end end diff --git a/spec/ruby/core/kernel/initialize_copy_spec.rb b/spec/ruby/core/kernel/initialize_copy_spec.rb index fe08d184ad..d71ca9f60f 100644 --- a/spec/ruby/core/kernel/initialize_copy_spec.rb +++ b/spec/ruby/core/kernel/initialize_copy_spec.rb @@ -1,11 +1,18 @@ require_relative '../../spec_helper' describe "Kernel#initialize_copy" do + it "returns self" do + obj = Object.new + obj.send(:initialize_copy, obj).should.equal?(obj) + end + it "does nothing if the argument is the same as the receiver" do obj = Object.new obj.send(:initialize_copy, obj).should.equal?(obj) - obj.freeze + + obj = Object.new.freeze obj.send(:initialize_copy, obj).should.equal?(obj) + 1.send(:initialize_copy, 1).should.equal?(1) end diff --git a/spec/ruby/core/kernel/inspect_spec.rb b/spec/ruby/core/kernel/inspect_spec.rb index 1f9ce834ab..1fa66cab98 100644 --- a/spec/ruby/core/kernel/inspect_spec.rb +++ b/spec/ruby/core/kernel/inspect_spec.rb @@ -28,4 +28,63 @@ describe "Kernel#inspect" do end obj.inspect.should be_kind_of(String) end + + ruby_version_is "4.0" do + it "calls #instance_variables_to_inspect private method to know which variables to display" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = %i[@host @user @does_not_exist] + end + + inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00') + inspected.should == '#<Object:0x00 @host="localhost", @user="root">' + + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = [] + end + + inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00') + inspected.should == "#<Object:0x00>" + end + + it "displays all instance variables if #instance_variables_to_inspect returns nil" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = nil + end + + inspected = obj.inspect.sub(/^#<Object:0x[0-9a-f]+/, '#<Object:0x00') + inspected.should == %{#<Object:0x00 @host="localhost", @user="root", @password="hunter2">} + end + + it "raises an error if #instance_variables_to_inspect returns an invalid value" do + obj = Object.new + obj.instance_eval do + @host = "localhost" + @user = "root" + @password = "hunter2" + end + obj.singleton_class.class_eval do + private def instance_variables_to_inspect = {} + end + + ->{ obj.inspect }.should raise_error(TypeError, "Expected #instance_variables_to_inspect to return an Array or nil, but it returned Hash") + end + end end diff --git a/spec/ruby/core/kernel/is_a_spec.rb b/spec/ruby/core/kernel/is_a_spec.rb index dc69766f83..bd8c96529a 100644 --- a/spec/ruby/core/kernel/is_a_spec.rb +++ b/spec/ruby/core/kernel/is_a_spec.rb @@ -2,5 +2,5 @@ require_relative '../../spec_helper' require_relative 'shared/kind_of' describe "Kernel#is_a?" do - it_behaves_like :kernel_kind_of , :is_a? + it_behaves_like :kernel_kind_of, :is_a? end diff --git a/spec/ruby/core/kernel/kind_of_spec.rb b/spec/ruby/core/kernel/kind_of_spec.rb index 734035620c..c988edccb5 100644 --- a/spec/ruby/core/kernel/kind_of_spec.rb +++ b/spec/ruby/core/kernel/kind_of_spec.rb @@ -2,5 +2,5 @@ require_relative '../../spec_helper' require_relative 'shared/kind_of' describe "Kernel#kind_of?" do - it_behaves_like :kernel_kind_of , :kind_of? + it_behaves_like :kernel_kind_of, :kind_of? end diff --git a/spec/ruby/core/kernel/lambda_spec.rb b/spec/ruby/core/kernel/lambda_spec.rb index 2f7abc749c..565536ac0d 100644 --- a/spec/ruby/core/kernel/lambda_spec.rb +++ b/spec/ruby/core/kernel/lambda_spec.rb @@ -26,42 +26,44 @@ describe "Kernel.lambda" do l.lambda?.should be_true end - it "creates a lambda-style Proc if given a literal block via Kernel.public_send" do - suppress_warning do - l = Kernel.public_send(:lambda) { 42 } - l.lambda?.should be_true + ruby_version_is ""..."3.3" do + it "creates a lambda-style Proc if given a literal block via Kernel.public_send" do + suppress_warning do + l = Kernel.public_send(:lambda) { 42 } + l.lambda?.should be_true + end end - end - it "returns the passed Proc if given an existing Proc" do - some_proc = proc {} - l = suppress_warning {lambda(&some_proc)} - l.should equal(some_proc) - l.lambda?.should be_false - end + it "returns the passed Proc if given an existing Proc" do + some_proc = proc {} + l = suppress_warning {lambda(&some_proc)} + l.should equal(some_proc) + l.lambda?.should be_false + end - it "creates a lambda-style Proc when called with zsuper" do - suppress_warning do - l = KernelSpecs::LambdaSpecs::ForwardBlockWithZSuper.new.lambda { 42 } - l.lambda?.should be_true - l.call.should == 42 + it "creates a lambda-style Proc when called with zsuper" do + suppress_warning do + l = KernelSpecs::LambdaSpecs::ForwardBlockWithZSuper.new.lambda { 42 } + l.lambda?.should be_true + l.call.should == 42 - lambda { l.call(:extra) }.should raise_error(ArgumentError) + lambda { l.call(:extra) }.should raise_error(ArgumentError) + end end - end - it "returns the passed Proc if given an existing Proc through super" do - some_proc = proc { } - l = KernelSpecs::LambdaSpecs::SuperAmpersand.new.lambda(&some_proc) - l.should equal(some_proc) - l.lambda?.should be_false - end + it "returns the passed Proc if given an existing Proc through super" do + some_proc = proc { } + l = KernelSpecs::LambdaSpecs::SuperAmpersand.new.lambda(&some_proc) + l.should equal(some_proc) + l.lambda?.should be_false + end - it "does not create lambda-style Procs when captured with #method" do - kernel_lambda = method(:lambda) - l = suppress_warning {kernel_lambda.call { 42 }} - l.lambda?.should be_false - l.call(:extra).should == 42 + it "does not create lambda-style Procs when captured with #method" do + kernel_lambda = method(:lambda) + l = suppress_warning {kernel_lambda.call { 42 }} + l.lambda?.should be_false + l.call(:extra).should == 42 + end end it "checks the arity of the call when no args are specified" do @@ -137,8 +139,16 @@ describe "Kernel.lambda" do end context "when called without a literal block" do - it "warns when proc isn't a lambda" do - -> { lambda(&proc{}) }.should complain("#{__FILE__}:#{__LINE__}: warning: lambda without a literal block is deprecated; use the proc without lambda instead\n") + ruby_version_is ""..."3.3" do + it "warns when proc isn't a lambda" do + -> { lambda(&proc{}) }.should complain("#{__FILE__}:#{__LINE__}: warning: lambda without a literal block is deprecated; use the proc without lambda instead\n") + end + end + + ruby_version_is "3.3" do + it "raises when proc isn't a lambda" do + -> { lambda(&proc{}) }.should raise_error(ArgumentError, /the lambda method requires a literal block/) + end end it "doesn't warn when proc is lambda" do diff --git a/spec/ruby/core/kernel/match_spec.rb b/spec/ruby/core/kernel/match_spec.rb index aa25006163..cd6330fe91 100644 --- a/spec/ruby/core/kernel/match_spec.rb +++ b/spec/ruby/core/kernel/match_spec.rb @@ -1,30 +1,7 @@ require_relative '../../spec_helper' describe "Kernel#=~" do - ruby_version_is ''...'3.2' do - it "returns nil matching any object" do - o = Object.new - - suppress_warning do - (o =~ /Object/).should be_nil - (o =~ 'Object').should be_nil - (o =~ Object).should be_nil - (o =~ Object.new).should be_nil - (o =~ nil).should be_nil - (o =~ true).should be_nil - end - end - - it "is deprecated" do - -> do - Object.new =~ /regexp/ - end.should complain(/deprecated Object#=~ is called on Object/, verbose: true) - end - end - - ruby_version_is '3.2' do - it "is no longer defined" do - Object.new.should_not.respond_to?(:=~) - end + it "is no longer defined" do + Object.new.should_not.respond_to?(:=~) end end diff --git a/spec/ruby/core/kernel/not_match_spec.rb b/spec/ruby/core/kernel/not_match_spec.rb index 906f18df2c..082e56fed7 100644 --- a/spec/ruby/core/kernel/not_match_spec.rb +++ b/spec/ruby/core/kernel/not_match_spec.rb @@ -14,6 +14,10 @@ describe "Kernel#!~" do (obj !~ :foo).should == false end + it "raises NoMethodError if self does not respond to #=~" do + -> { Object.new !~ :foo }.should raise_error(NoMethodError) + end + it 'can be overridden in subclasses' do obj = KernelSpecs::NotMatch.new (obj !~ :bar).should == :foo diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index 99793294c5..b967d5044b 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -27,44 +27,66 @@ describe "Kernel#open" do open(@name, "r") { |f| f.gets }.should == @content end - platform_is_not :windows, :wasi do - it "opens an io when path starts with a pipe" do - @io = open("|date") - begin - @io.should be_kind_of(IO) - @io.read - ensure - @io.close + ruby_version_is ""..."4.0" do + platform_is_not :windows, :wasi do + it "opens an io when path starts with a pipe" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @io = open("|date") + end + begin + @io.should be_kind_of(IO) + @io.read + ensure + @io.close + end end - end - it "opens an io when called with a block" do - @output = open("|date") { |f| f.read } - @output.should_not == '' - end + it "opens an io when called with a block" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @output = open("|date") { |f| f.read } + end + @output.should_not == '' + end - it "opens an io for writing" do - -> do - bytes = open("|cat", "w") { |io| io.write(".") } - bytes.should == 1 - end.should output_to_fd(".") + it "opens an io for writing" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + -> { + bytes = open("|cat", "w") { |io| io.write(".") } + bytes.should == 1 + }.should output_to_fd(".") + end + end end - end - platform_is :windows do - it "opens an io when path starts with a pipe" do - @io = open("|date /t") - begin - @io.should be_kind_of(IO) - @io.read - ensure - @io.close + platform_is :windows do + it "opens an io when path starts with a pipe" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @io = open("|date /t") + end + begin + @io.should be_kind_of(IO) + @io.read + ensure + @io.close + end + end + + it "opens an io when called with a block" do + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + @output = open("|date /t") { |f| f.read } + end + @output.should_not == '' end end - it "opens an io when called with a block" do - @output = open("|date /t") { |f| f.read } - @output.should_not == '' + ruby_version_is "3.3" do + # https://bugs.ruby-lang.org/issues/19630 + it "warns about deprecation given a path with a pipe" do + cmd = "|echo ok" + -> { + open(cmd) { |f| f.read } + }.should complain(/Kernel#open with a leading '\|'/) + end end end @@ -86,7 +108,7 @@ describe "Kernel#open" do ScratchPad.clear end - it "calls #to_path to covert the argument to a String before calling #to_str" do + it "calls #to_path to convert the argument to a String before calling #to_str" do obj = mock("open to_path") obj.should_receive(:to_path).at_least(1).times.and_return(@name) obj.should_not_receive(:to_str) diff --git a/spec/ruby/core/kernel/printf_spec.rb b/spec/ruby/core/kernel/printf_spec.rb index d8f93ce429..61bf955c25 100644 --- a/spec/ruby/core/kernel/printf_spec.rb +++ b/spec/ruby/core/kernel/printf_spec.rb @@ -31,6 +31,13 @@ describe "Kernel.printf" do object.should_receive(:write).with("string") Kernel.printf(object, "%s", "string") end + + it "calls #to_str to convert the format object to a String" do + object = mock('format string') + object.should_receive(:to_str).and_return("to_str: %i") + $stdout.should_receive(:write).with("to_str: 42") + Kernel.printf($stdout, object, 42) + end end describe "Kernel.printf" do diff --git a/spec/ruby/core/kernel/public_send_spec.rb b/spec/ruby/core/kernel/public_send_spec.rb index 4dae419ff9..b684b1729c 100644 --- a/spec/ruby/core/kernel/public_send_spec.rb +++ b/spec/ruby/core/kernel/public_send_spec.rb @@ -105,11 +105,11 @@ describe "Kernel#public_send" do end it "includes `public_send` in the backtrace when passed not enough arguments" do - -> { public_send() }.should raise_error(ArgumentError) { |e| e.backtrace[0].should.include?("`public_send'") } + -> { public_send() }.should raise_error(ArgumentError) { |e| e.backtrace[0].should =~ /[`'](?:Kernel#)?public_send'/ } end it "includes `public_send` in the backtrace when passed a single incorrect argument" do - -> { public_send(Object.new) }.should raise_error(TypeError) { |e| e.backtrace[0].should.include?("`public_send'") } + -> { public_send(Object.new) }.should raise_error(TypeError) { |e| e.backtrace[0].should =~ /[`'](?:Kernel#)?public_send'/ } end it_behaves_like :basicobject_send, :public_send diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb index 4f190c120b..fcd011d4e6 100644 --- a/spec/ruby/core/kernel/raise_spec.rb +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -44,7 +44,242 @@ describe "Kernel#raise" do it "raises an ArgumentError when only cause is given" do cause = StandardError.new - -> { raise(cause: cause) }.should raise_error(ArgumentError) + -> { raise(cause: cause) }.should raise_error(ArgumentError, "only cause is given with no arguments") + end + + it "raises an ArgumentError when only cause is given even if it has nil value" do + -> { raise(cause: nil) }.should raise_error(ArgumentError, "only cause is given with no arguments") + end + + it "raises a TypeError when given cause is not an instance of Exception" do + -> { raise "message", cause: Object.new }.should raise_error(TypeError, "exception object expected") + end + + it "doesn't raise a TypeError when given cause is nil" do + -> { raise "message", cause: nil }.should raise_error(RuntimeError, "message") + end + + it "allows cause equal an exception" do + e = RuntimeError.new("message") + -> { raise e, cause: e }.should raise_error(e) + end + + it "doesn't set given cause when it equals an exception" do + e = RuntimeError.new("message") + + begin + raise e, cause: e + rescue + end + + e.cause.should == nil + end + + it "raises ArgumentError when exception is part of the cause chain" do + -> { + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + begin + raise "Error 3" + rescue => e3 + raise e1, cause: e3 + end + end + end + }.should raise_error(ArgumentError, "circular causes") + end + + it "re-raises a rescued exception" do + -> do + begin + raise StandardError, "aaa" + rescue Exception + begin + raise ArgumentError + rescue ArgumentError + end + + # should raise StandardError "aaa" + raise + end + end.should raise_error(StandardError, "aaa") + end + + it "re-raises a previously rescued exception without overwriting the cause" do + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2 + end + rescue => e + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception with overwriting the cause when it's explicitly specified with :cause option" do + e4 = RuntimeError.new("Error 4") + + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2, cause: e4 + end + rescue => e + e.cause.should == e4 + end + end + + it "re-raises a previously rescued exception without overwriting the cause when it's explicitly specified with :cause option and has nil value" do + begin + begin + begin + begin + raise "Error 1" + rescue => e1 + raise "Error 2" + end + rescue => e2 + raise "Error 3" + end + rescue + e2.cause.should == e1 + raise e2, cause: nil + end + rescue => e + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + raise + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that has a cause without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + raise + end + end + rescue => e + e.should == e2 + e.cause.should == e1 + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and isn't a cause of any other exception with setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + raise "Error 3" + end + end + rescue => e + e.message.should == "Error 3" + e.cause.should == e2 + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and is a cause of other exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + e1.cause.should == nil + e2.cause.should == e1 + raise e1 + end + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that doesn't have a cause and is a cause of other exception (that wasn't raised explicitly) without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + foo # raises NameError + rescue => e2 + e1.cause.should == nil + e2.cause.should == e1 + raise e1 + end + end + rescue => e + e.should == e1 + e.cause.should == nil + end + end + + it "re-raises a previously rescued exception that has a cause but isn't a cause of any other exception without setting a cause implicitly" do + begin + begin + raise "Error 1" + rescue => e1 + begin + raise "Error 2" + rescue => e2 + begin + raise "Error 3", cause: RuntimeError.new("Error 4") + rescue => e3 + e2.cause.should == e1 + e3.cause.should_not == e2 + raise e2 + end + end + end + rescue => e + e.should == e2 + e.cause.should == e1 + end end end diff --git a/spec/ruby/core/kernel/require_relative_spec.rb b/spec/ruby/core/kernel/require_relative_spec.rb index 5999855de6..6188d13a4e 100644 --- a/spec/ruby/core/kernel/require_relative_spec.rb +++ b/spec/ruby/core/kernel/require_relative_spec.rb @@ -5,9 +5,9 @@ describe "Kernel#require_relative with a relative path" do before :each do CodeLoadingSpecs.spec_setup @dir = "../../fixtures/code" - @abs_dir = File.realpath(@dir, File.dirname(__FILE__)) + @abs_dir = File.realpath(@dir, __dir__) @path = "#{@dir}/load_fixture.rb" - @abs_path = File.realpath(@path, File.dirname(__FILE__)) + @abs_path = File.realpath(@path, __dir__) end after :each do @@ -92,7 +92,7 @@ describe "Kernel#require_relative with a relative path" do it "raises a LoadError that includes the missing path" do missing_path = "#{@dir}/nonexistent.rb" - expanded_missing_path = File.expand_path(missing_path, File.dirname(__FILE__)) + expanded_missing_path = File.expand_path(missing_path, __dir__) -> { require_relative(missing_path) }.should raise_error(LoadError) { |e| e.message.should include(expanded_missing_path) e.path.should == expanded_missing_path @@ -277,7 +277,7 @@ end describe "Kernel#require_relative with an absolute path" do before :each do CodeLoadingSpecs.spec_setup - @dir = File.expand_path "../../fixtures/code", File.dirname(__FILE__) + @dir = File.expand_path "../../fixtures/code", __dir__ @abs_dir = @dir @path = File.join @dir, "load_fixture.rb" @abs_path = @path diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 896afb840a..60d17242fe 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -16,9 +16,10 @@ describe "Kernel#require" do Kernel.should have_private_instance_method(:require) end - provided = %w[complex enumerator rational thread ruby2_keywords] - ruby_version_is "3.1" do - provided << "fiber" + provided = %w[complex enumerator fiber rational thread ruby2_keywords] + ruby_version_is "4.0" do + provided << "set" + provided << "pathname" end it "#{provided.join(', ')} are already required" do @@ -26,14 +27,19 @@ describe "Kernel#require" do features = out.lines.map { |line| File.basename(line.chomp, '.*') } # Ignore CRuby internals - features -= %w[encdb transdb windows_1252] + features -= %w[encdb transdb windows_1252 windows_31j] features.reject! { |feature| feature.end_with?('-fake') } features.sort.should == provided.sort - code = provided.map { |f| "puts require #{f.inspect}\n" }.join + requires = provided + ruby_version_is "4.0" do + requires = requires.map { |f| f == "pathname" ? "pathname.so" : f } + end + + code = requires.map { |f| "puts require #{f.inspect}\n" }.join required = ruby_exe(code, options: '--disable-gems') - required.should == "false\n" * provided.size + required.should == "false\n" * requires.size end it_behaves_like :kernel_require_basic, :require, CodeLoadingSpecs::Method.new diff --git a/spec/ruby/core/kernel/select_spec.rb b/spec/ruby/core/kernel/select_spec.rb index e0d82f3079..df23414b28 100644 --- a/spec/ruby/core/kernel/select_spec.rb +++ b/spec/ruby/core/kernel/select_spec.rb @@ -10,9 +10,9 @@ end describe "Kernel.select" do it 'does not block when timeout is 0' do IO.pipe do |read, write| - IO.select([read], [], [], 0).should == nil + select([read], [], [], 0).should == nil write.write 'data' - IO.select([read], [], [], 0).should == [[read], [], []] + select([read], [], [], 0).should == [[read], [], []] end end end diff --git a/spec/ruby/core/kernel/shared/load.rb b/spec/ruby/core/kernel/shared/load.rb index 5c41c19bf6..62c5c7be9b 100644 --- a/spec/ruby/core/kernel/shared/load.rb +++ b/spec/ruby/core/kernel/shared/load.rb @@ -1,5 +1,6 @@ main = self +# The big difference is Kernel#load does not attempt to add an extension to the passed path, unlike Kernel#require describe :kernel_load, shared: true do before :each do CodeLoadingSpecs.spec_setup @@ -10,22 +11,31 @@ describe :kernel_load, shared: true do CodeLoadingSpecs.spec_cleanup end - it "loads a non-extensioned file as a Ruby source file" do - path = File.expand_path "load_fixture", CODE_LOADING_DIR - @object.load(path).should be_true - ScratchPad.recorded.should == [:no_ext] - end + describe "(path resolution)" do + # This behavior is specific to Kernel#load, it differs for Kernel#require + it "loads a non-extensioned file as a Ruby source file" do + path = File.expand_path "load_fixture", CODE_LOADING_DIR + @object.load(path).should be_true + ScratchPad.recorded.should == [:no_ext] + end - it "loads a non .rb extensioned file as a Ruby source file" do - path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR - @object.load(path).should be_true - ScratchPad.recorded.should == [:no_rb_ext] - end + it "loads a non .rb extensioned file as a Ruby source file" do + path = File.expand_path "load_fixture.ext", CODE_LOADING_DIR + @object.load(path).should be_true + ScratchPad.recorded.should == [:no_rb_ext] + end - it "loads from the current working directory" do - Dir.chdir CODE_LOADING_DIR do - @object.load("load_fixture.rb").should be_true - ScratchPad.recorded.should == [:loaded] + it "loads from the current working directory" do + Dir.chdir CODE_LOADING_DIR do + @object.load("load_fixture.rb").should be_true + ScratchPad.recorded.should == [:loaded] + end + end + + # This behavior is specific to Kernel#load, it differs for Kernel#require + it "does not look for a c-extension file when passed a path without extension (when no .rb is present)" do + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(LoadError) end end @@ -155,37 +165,35 @@ describe :kernel_load, shared: true do end describe "when passed a module for 'wrap'" do - ruby_version_is "3.1" do - it "sets the enclosing scope to the supplied module" do - path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR - mod = Module.new - @object.load(path, mod) + it "sets the enclosing scope to the supplied module" do + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR + mod = Module.new + @object.load(path, mod) - Object.const_defined?(:LoadSpecWrap).should be_false - mod.const_defined?(:LoadSpecWrap).should be_true + Object.const_defined?(:LoadSpecWrap).should be_false + mod.const_defined?(:LoadSpecWrap).should be_true - wrap_module = ScratchPad.recorded[1] - wrap_module.should == mod - end + wrap_module = ScratchPad.recorded[1] + wrap_module.should == mod + end - it "makes constants and instance methods in the source file reachable with the supplied module" do - path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR - mod = Module.new - @object.load(path, mod) + it "makes constants and instance methods in the source file reachable with the supplied module" do + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR + mod = Module.new + @object.load(path, mod) - mod::LOAD_WRAP_SPECS_TOP_LEVEL_CONSTANT.should == 1 - obj = Object.new - obj.extend(mod) - obj.send(:load_wrap_specs_top_level_method).should == :load_wrap_specs_top_level_method - end + mod::LOAD_WRAP_SPECS_TOP_LEVEL_CONSTANT.should == 1 + obj = Object.new + obj.extend(mod) + obj.send(:load_wrap_specs_top_level_method).should == :load_wrap_specs_top_level_method + end - it "makes instance methods in the source file private" do - path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR - mod = Module.new - @object.load(path, mod) + it "makes instance methods in the source file private" do + path = File.expand_path "load_wrap_fixture.rb", CODE_LOADING_DIR + mod = Module.new + @object.load(path, mod) - mod.private_instance_methods.include?(:load_wrap_specs_top_level_method).should == true - end + mod.private_instance_methods.include?(:load_wrap_specs_top_level_method).should == true end end diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb index 61081b200c..52f86f73e5 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -212,6 +212,34 @@ end describe :kernel_require, shared: true do describe "(path resolution)" do + it "loads .rb file when passed absolute path without extension" do + path = File.expand_path "load_fixture", CODE_LOADING_DIR + @object.send(@method, path).should be_true + # This should _not_ be [:no_ext] + ScratchPad.recorded.should == [:loaded] + end + + platform_is :linux, :darwin do + it "loads c-extension file when passed absolute path without extension when no .rb is present" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + end + + platform_is :darwin do + it "loads .bundle file when passed absolute path with .so" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture.so" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + end + + it "does not try an extra .rb if the path already ends in .rb" do + path = File.join CODE_LOADING_DIR, "d", "load_fixture.rb" + -> { @object.send(@method, path) }.should raise_error(LoadError) + end + # For reference see [ruby-core:24155] in which matz confirms this feature is # intentional for security reasons. it "does not load a bare filename unless the current working directory is in $LOAD_PATH" do @@ -268,6 +296,16 @@ describe :kernel_require, shared: true do $LOAD_PATH.replace [File.expand_path("b", CODE_LOADING_DIR), CODE_LOADING_DIR] @object.require("load_fixture").should be_false end + + it "stores the missing path in a LoadError object" do + path = "abcd1234" + + -> { + @object.send(@method, path) + }.should raise_error(LoadError) { |e| + e.path.should == path + } + end end describe "(file extensions)" do @@ -787,4 +825,24 @@ describe :kernel_require, shared: true do e.path.should == path } end + + platform_is :linux, :darwin do + it "does not store the missing path in a LoadError object when c-extension file exists but loading fails and passed absolute path without extension" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture" + -> { @object.send(@method, path) }.should raise_error(LoadError) { |e| + e.path.should == nil + } + end + end + + platform_is :darwin do + it "does not store the missing path in a LoadError object when c-extension file exists but loading fails and passed absolute path with extension" do + # the error message is specific to what dlerror() returns + path = File.join CODE_LOADING_DIR, "a", "load_fixture.bundle" + -> { @object.send(@method, path) }.should raise_error(LoadError) { |e| + e.path.should == nil + } + end + end end diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb index 2db50bd686..2b2c6c9b63 100644 --- a/spec/ruby/core/kernel/shared/sprintf.rb +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -22,6 +22,7 @@ describe :kernel_sprintf, shared: true do @method.call("%d", "112").should == "112" @method.call("%d", "0127").should == "87" @method.call("%d", "0xc4").should == "196" + @method.call("%d", "0").should == "0" end it "raises TypeError exception if cannot convert to Integer" do @@ -57,6 +58,11 @@ describe :kernel_sprintf, shared: true do it "works well with large numbers" do @method.call("%#{f}", 1234567890987654321).should == "1234567890987654321" end + + it "converts to the empty string if precision is 0 and value is 0" do + @method.call("%.#{f}", 0).should == "" + @method.call("%.0#{f}", 0).should == "" + end end end @@ -289,28 +295,12 @@ describe :kernel_sprintf, shared: true do @method.call("%c", "a").should == "a" end - ruby_version_is ""..."3.2" do - it "raises ArgumentError if argument is a string of several characters" do - -> { - @method.call("%c", "abc") - }.should raise_error(ArgumentError, /%c requires a character/) - end - - it "raises ArgumentError if argument is an empty string" do - -> { - @method.call("%c", "") - }.should raise_error(ArgumentError, /%c requires a character/) - end + it "displays only the first character if argument is a string of several characters" do + @method.call("%c", "abc").should == "a" end - ruby_version_is "3.2" do - it "displays only the first character if argument is a string of several characters" do - @method.call("%c", "abc").should == "a" - end - - it "displays no characters if argument is an empty string" do - @method.call("%c", "").should == "" - end + it "displays no characters if argument is an empty string" do + @method.call("%c", "").should == "" end it "raises TypeError if argument is not String or Integer and cannot be converted to them" do @@ -356,13 +346,13 @@ describe :kernel_sprintf, shared: true do it "raises TypeError if converting to Integer with to_int returns non-Integer" do obj = BasicObject.new - def obj.to_str + def obj.to_int :foo end -> { @method.call("%c", obj) - }.should raise_error(TypeError, /can't convert BasicObject to String/) + }.should raise_error(TypeError, /can't convert BasicObject to Integer/) end end @@ -372,6 +362,10 @@ describe :kernel_sprintf, shared: true do obj.should_receive(:inspect).and_return("<inspect-result>") @method.call("%p", obj).should == "<inspect-result>" end + + it "substitutes 'nil' for nil" do + @method.call("%p", nil).should == "nil" + end end describe "s" do @@ -455,7 +449,7 @@ describe :kernel_sprintf, shared: true do it "is escaped by %" do @method.call("%%").should == "%" - @method.call("%%d", 10).should == "%d" + @method.call("%%d").should == "%d" end end end diff --git a/spec/ruby/core/kernel/shared/sprintf_encoding.rb b/spec/ruby/core/kernel/shared/sprintf_encoding.rb index 9cedb8b662..7ec0fe4c48 100644 --- a/spec/ruby/core/kernel/shared/sprintf_encoding.rb +++ b/spec/ruby/core/kernel/shared/sprintf_encoding.rb @@ -14,14 +14,14 @@ describe :kernel_sprintf_encoding, shared: true do end it "returns a String in the same encoding as the format String if compatible" do - string = "%s".force_encoding(Encoding::KOI8_U) + string = "%s".dup.force_encoding(Encoding::KOI8_U) result = @method.call(string, "dogs") result.encoding.should equal(Encoding::KOI8_U) end it "returns a String in the argument's encoding if format encoding is more restrictive" do - string = "foo %s".force_encoding(Encoding::US_ASCII) - argument = "b\303\274r".force_encoding(Encoding::UTF_8) + string = "foo %s".dup.force_encoding(Encoding::US_ASCII) + argument = "b\303\274r".dup.force_encoding(Encoding::UTF_8) result = @method.call(string, argument) result.encoding.should equal(Encoding::UTF_8) @@ -56,7 +56,7 @@ describe :kernel_sprintf_encoding, shared: true do end it "uses the encoding of the format string to interpret codepoints" do - format = "%c".force_encoding("euc-jp") + format = "%c".dup.force_encoding("euc-jp") result = @method.call(format, 9415601) result.encoding.should == Encoding::EUC_JP diff --git a/spec/ruby/core/kernel/singleton_method_spec.rb b/spec/ruby/core/kernel/singleton_method_spec.rb index 0bdf125ad8..7d63fa7cc6 100644 --- a/spec/ruby/core/kernel/singleton_method_spec.rb +++ b/spec/ruby/core/kernel/singleton_method_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' describe "Kernel#singleton_method" do - it "find a method defined on the singleton class" do + it "finds a method defined on the singleton class" do obj = Object.new def obj.foo; end obj.singleton_method(:foo).should be_an_instance_of(Method) @@ -38,4 +38,48 @@ describe "Kernel#singleton_method" do e.class.should == NameError } end + + ruby_bug "#20620", ""..."3.4" do + it "finds a method defined in a module included in the singleton class" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.singleton_class.include(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + + it "finds a method defined in a module prepended in the singleton class" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.singleton_class.prepend(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + + it "finds a method defined in a module that an object is extended with" do + m = Module.new do + def foo + :foo + end + end + + obj = Object.new + obj.extend(m) + + obj.singleton_method(:foo).should be_an_instance_of(Method) + obj.singleton_method(:foo).call.should == :foo + end + end end diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb index 44b417a92e..e9c600aac4 100644 --- a/spec/ruby/core/kernel/sleep_spec.rb +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -1,5 +1,5 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' +require_relative '../fiber/fixtures/scheduler' describe "Kernel#sleep" do it "is a private method" do @@ -22,7 +22,7 @@ describe "Kernel#sleep" do sleep(Rational(1, 999)).should >= 0 end - it "accepts any Object that reponds to divmod" do + it "accepts any Object that responds to divmod" do o = Object.new def o.divmod(*); [0, 0.001]; end sleep(o).should >= 0 @@ -52,6 +52,17 @@ describe "Kernel#sleep" do t.value.should == 5 end + it "sleeps with nanosecond precision" do + start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 100.times do + sleep(0.0001) + end + end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + actual_duration = end_time - start_time + actual_duration.should > 0.01 # 100 * 0.0001 => 0.01 + end + ruby_version_is ""..."3.3" do it "raises a TypeError when passed nil" do -> { sleep(nil) }.should raise_error(TypeError) @@ -74,6 +85,40 @@ describe "Kernel#sleep" do t.value.should == 5 end end + + context "Kernel.sleep with Fiber scheduler" do + before :each do + Fiber.set_scheduler(FiberSpecs::LoggingScheduler.new) + end + + after :each do + Fiber.set_scheduler(nil) + end + + it "calls the scheduler without arguments when no duration is given" do + sleeper = Fiber.new(blocking: false) do + sleep + end + sleeper.resume + Fiber.scheduler.events.should == [{ event: :kernel_sleep, fiber: sleeper, args: [] }] + end + + it "calls the scheduler with the given duration" do + sleeper = Fiber.new(blocking: false) do + sleep(0.01) + end + sleeper.resume + Fiber.scheduler.events.should == [{ event: :kernel_sleep, fiber: sleeper, args: [0.01] }] + end + + it "does not call the scheduler if the fiber is blocking" do + sleeper = Fiber.new(blocking: true) do + sleep(0.01) + end + sleeper.resume + Fiber.scheduler.events.should == [] + end + end end describe "Kernel.sleep" do diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb index 7adf71be76..5a4a90ff7a 100644 --- a/spec/ruby/core/kernel/sprintf_spec.rb +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -3,22 +3,62 @@ require_relative 'fixtures/classes' require_relative 'shared/sprintf' require_relative 'shared/sprintf_encoding' +describe :kernel_sprintf_to_str, shared: true do + it "calls #to_str to convert the format object to a String" do + obj = mock('format string') + obj.should_receive(:to_str).and_return("to_str: %i") + @method.call(obj, 42).should == "to_str: 42" + end +end + describe "Kernel#sprintf" do it_behaves_like :kernel_sprintf, -> format, *args { - sprintf(format, *args) + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r } it_behaves_like :kernel_sprintf_encoding, -> format, *args { - sprintf(format, *args) + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r + } + + it_behaves_like :kernel_sprintf_to_str, -> format, *args { + r = nil + -> { + r = sprintf(format, *args) + }.should_not complain(verbose: true) + r } end describe "Kernel.sprintf" do it_behaves_like :kernel_sprintf, -> format, *args { - Kernel.sprintf(format, *args) + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r } it_behaves_like :kernel_sprintf_encoding, -> format, *args { - Kernel.sprintf(format, *args) + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r + } + + it_behaves_like :kernel_sprintf_to_str, -> format, *args { + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r } end diff --git a/spec/ruby/core/kernel/system_spec.rb b/spec/ruby/core/kernel/system_spec.rb index 9671a650cc..9bc03924dd 100644 --- a/spec/ruby/core/kernel/system_spec.rb +++ b/spec/ruby/core/kernel/system_spec.rb @@ -64,6 +64,23 @@ describe :kernel_system, shared: true do end end + platform_is_not :windows do + before :each do + require 'tmpdir' + @shell_command = File.join(Dir.mktmpdir, "noshebang.cmd") + File.write(@shell_command, %[echo "$PATH"\n], perm: 0o700) + end + + after :each do + File.unlink(@shell_command) + Dir.rmdir(File.dirname(@shell_command)) + end + + it "executes with `sh` if the command is executable but not binary and there is no shebang" do + -> { @object.system(@shell_command) }.should output_to_fd(ENV['PATH'] + "\n") + end + end + before :each do ENV['TEST_SH_EXPANSION'] = 'foo' @shell_var = '$TEST_SH_EXPANSION' diff --git a/spec/ruby/core/kernel/taint_spec.rb b/spec/ruby/core/kernel/taint_spec.rb index 0c16b1dbbf..9a2efbaea0 100644 --- a/spec/ruby/core/kernel/taint_spec.rb +++ b/spec/ruby/core/kernel/taint_spec.rb @@ -2,26 +2,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Kernel#taint" do - ruby_version_is ""..."3.2" do - it "is a no-op" do - suppress_warning do - o = Object.new - o.taint - o.should_not.tainted? - end - end - - it "warns in verbose mode" do - -> { - obj = mock("tainted") - obj.taint - }.should complain(/Object#taint is deprecated and will be removed in Ruby 3.2/, verbose: true) - end - end - - ruby_version_is "3.2" do - it "has been removed" do - Object.new.should_not.respond_to?(:taint) - end + it "has been removed" do + Object.new.should_not.respond_to?(:taint) end end diff --git a/spec/ruby/core/kernel/tainted_spec.rb b/spec/ruby/core/kernel/tainted_spec.rb index fcae433069..837eb1dafb 100644 --- a/spec/ruby/core/kernel/tainted_spec.rb +++ b/spec/ruby/core/kernel/tainted_spec.rb @@ -2,28 +2,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Kernel#tainted?" do - ruby_version_is ""..."3.2" do - it "is a no-op" do - suppress_warning do - o = mock('o') - p = mock('p') - p.taint - o.should_not.tainted? - p.should_not.tainted? - end - end - - it "warns in verbose mode" do - -> { - o = mock('o') - o.tainted? - }.should complain(/Object#tainted\? is deprecated and will be removed in Ruby 3.2/, verbose: true) - end - end - - ruby_version_is "3.2" do - it "has been removed" do - Object.new.should_not.respond_to?(:tainted?) - end + it "has been removed" do + Object.new.should_not.respond_to?(:tainted?) end end diff --git a/spec/ruby/core/kernel/test_spec.rb b/spec/ruby/core/kernel/test_spec.rb index abb365aed2..d26dc06361 100644 --- a/spec/ruby/core/kernel/test_spec.rb +++ b/spec/ruby/core/kernel/test_spec.rb @@ -3,8 +3,8 @@ require_relative 'fixtures/classes' describe "Kernel#test" do before :all do - @file = File.dirname(__FILE__) + '/fixtures/classes.rb' - @dir = File.dirname(__FILE__) + '/fixtures' + @file = __dir__ + '/fixtures/classes.rb' + @dir = __dir__ + '/fixtures' end it "is a private method" do diff --git a/spec/ruby/core/kernel/trust_spec.rb b/spec/ruby/core/kernel/trust_spec.rb index db6f17e0fb..ef3fa9a3e1 100644 --- a/spec/ruby/core/kernel/trust_spec.rb +++ b/spec/ruby/core/kernel/trust_spec.rb @@ -2,27 +2,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Kernel#trust" do - ruby_version_is ""..."3.2" do - it "is a no-op" do - suppress_warning do - o = Object.new.untrust - o.should_not.untrusted? - o.trust - o.should_not.untrusted? - end - end - - it "warns in verbose mode" do - -> { - o = Object.new.untrust - o.trust - }.should complain(/Object#trust is deprecated and will be removed in Ruby 3.2/, verbose: true) - end - end - - ruby_version_is "3.2" do - it "has been removed" do - Object.new.should_not.respond_to?(:trust) - end + it "has been removed" do + Object.new.should_not.respond_to?(:trust) end end diff --git a/spec/ruby/core/kernel/untaint_spec.rb b/spec/ruby/core/kernel/untaint_spec.rb index 26b2aabbe9..47e8544bd4 100644 --- a/spec/ruby/core/kernel/untaint_spec.rb +++ b/spec/ruby/core/kernel/untaint_spec.rb @@ -2,27 +2,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Kernel#untaint" do - ruby_version_is ""..."3.2" do - it "is a no-op" do - suppress_warning do - o = Object.new.taint - o.should_not.tainted? - o.untaint - o.should_not.tainted? - end - end - - it "warns in verbose mode" do - -> { - o = Object.new.taint - o.untaint - }.should complain(/Object#untaint is deprecated and will be removed in Ruby 3.2/, verbose: true) - end - end - - ruby_version_is "3.2" do - it "has been removed" do - Object.new.should_not.respond_to?(:untaint) - end + it "has been removed" do + Object.new.should_not.respond_to?(:untaint) end end diff --git a/spec/ruby/core/kernel/untrust_spec.rb b/spec/ruby/core/kernel/untrust_spec.rb index 5310cd8eb4..8787ab3fc9 100644 --- a/spec/ruby/core/kernel/untrust_spec.rb +++ b/spec/ruby/core/kernel/untrust_spec.rb @@ -2,26 +2,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Kernel#untrust" do - ruby_version_is ""..."3.2" do - it "is a no-op" do - suppress_warning do - o = Object.new - o.untrust - o.should_not.untrusted? - end - end - - it "warns in verbose mode" do - -> { - o = Object.new - o.untrust - }.should complain(/Object#untrust is deprecated and will be removed in Ruby 3.2/, verbose: true) - end - end - - ruby_version_is "3.2" do - it "has been removed" do - Object.new.should_not.respond_to?(:untrust) - end + it "has been removed" do + Object.new.should_not.respond_to?(:untrust) end end diff --git a/spec/ruby/core/kernel/untrusted_spec.rb b/spec/ruby/core/kernel/untrusted_spec.rb index ea36d6c98c..29261be9c4 100644 --- a/spec/ruby/core/kernel/untrusted_spec.rb +++ b/spec/ruby/core/kernel/untrusted_spec.rb @@ -2,27 +2,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Kernel#untrusted?" do - ruby_version_is ""..."3.2" do - it "is a no-op" do - suppress_warning do - o = mock('o') - o.should_not.untrusted? - o.untrust - o.should_not.untrusted? - end - end - - it "warns in verbose mode" do - -> { - o = mock('o') - o.untrusted? - }.should complain(/Object#untrusted\? is deprecated and will be removed in Ruby 3.2/, verbose: true) - end - end - - ruby_version_is "3.2" do - it "has been removed" do - Object.new.should_not.respond_to?(:untrusted?) - end + it "has been removed" do + Object.new.should_not.respond_to?(:untrusted?) end end diff --git a/spec/ruby/core/kernel/warn_spec.rb b/spec/ruby/core/kernel/warn_spec.rb index 00164ad90b..e03498c6dc 100644 --- a/spec/ruby/core/kernel/warn_spec.rb +++ b/spec/ruby/core/kernel/warn_spec.rb @@ -112,6 +112,12 @@ describe "Kernel#warn" do ruby_exe(file, options: "-rrubygems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n" end + it "doesn't show the caller when the uplevel is `nil`" do + w = KernelSpecs::WarnInNestedCall.new + + -> { w.f4("foo", nil) }.should output(nil, "foo\n") + end + guard -> { Kernel.instance_method(:tap).source_location } do it "skips <internal: core library methods defined in Ruby" do file, line = Kernel.instance_method(:tap).source_location diff --git a/spec/ruby/core/main/private_spec.rb b/spec/ruby/core/main/private_spec.rb index e8c1f3f44c..76895fd649 100644 --- a/spec/ruby/core/main/private_spec.rb +++ b/spec/ruby/core/main/private_spec.rb @@ -30,16 +30,8 @@ describe "main#private" do end end - ruby_version_is ''...'3.1' do - it "returns Object" do - eval("private :main_public_method", TOPLEVEL_BINDING).should equal(Object) - end - end - - ruby_version_is '3.1' do - it "returns argument" do - eval("private :main_public_method", TOPLEVEL_BINDING).should equal(:main_public_method) - end + it "returns argument" do + eval("private :main_public_method", TOPLEVEL_BINDING).should equal(:main_public_method) end it "raises a NameError when at least one of given method names is undefined" do diff --git a/spec/ruby/core/main/public_spec.rb b/spec/ruby/core/main/public_spec.rb index 31baad4583..3c77798fbc 100644 --- a/spec/ruby/core/main/public_spec.rb +++ b/spec/ruby/core/main/public_spec.rb @@ -30,16 +30,8 @@ describe "main#public" do end end - ruby_version_is ''...'3.1' do - it "returns Object" do - eval("public :main_private_method", TOPLEVEL_BINDING).should equal(Object) - end - end - - ruby_version_is '3.1' do - it "returns argument" do - eval("public :main_private_method", TOPLEVEL_BINDING).should equal(:main_private_method) - end + it "returns argument" do + eval("public :main_private_method", TOPLEVEL_BINDING).should equal(:main_private_method) end diff --git a/spec/ruby/core/main/using_spec.rb b/spec/ruby/core/main/using_spec.rb index 8a23970c4b..5b9a751595 100644 --- a/spec/ruby/core/main/using_spec.rb +++ b/spec/ruby/core/main/using_spec.rb @@ -142,11 +142,9 @@ describe "main.using" do end.should raise_error(RuntimeError) end - ruby_version_is "3.2" do - it "does not raise error when wrapped with module" do - -> do - load File.expand_path('../fixtures/using.rb', __FILE__), true - end.should_not raise_error - end + it "does not raise error when wrapped with module" do + -> do + load File.expand_path('../fixtures/using.rb', __FILE__), true + end.should_not raise_error end end diff --git a/spec/ruby/core/marshal/dump_spec.rb b/spec/ruby/core/marshal/dump_spec.rb index c694b48bab..ff9b9214fa 100644 --- a/spec/ruby/core/marshal/dump_spec.rb +++ b/spec/ruby/core/marshal/dump_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'fixtures/marshal_data' @@ -38,7 +38,7 @@ describe "Marshal.dump" do ].should be_computed_by(:dump) end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "dumps a positive Fixnum > 31 bits as a Bignum" do Marshal.dump(2**31 + 1).should == "\x04\bl+\a\x01\x00\x00\x80" end @@ -47,6 +47,11 @@ describe "Marshal.dump" do Marshal.dump(-2**31 - 1).should == "\x04\bl-\a\x01\x00\x00\x80" end end + + it "does not use object links for objects repeatedly dumped" do + Marshal.dump([0, 0]).should == "\x04\b[\ai\x00i\x00" + Marshal.dump([2**16, 2**16]).should == "\x04\b[\ai\x03\x00\x00\x01i\x03\x00\x00\x01" + end end describe "with a Symbol" do @@ -76,7 +81,7 @@ describe "Marshal.dump" do end it "dumps a binary encoded Symbol" do - s = "\u2192".force_encoding("binary").to_sym + s = "\u2192".dup.force_encoding("binary").to_sym Marshal.dump(s).should == "\x04\b:\b\xE2\x86\x92" end @@ -85,14 +90,19 @@ describe "Marshal.dump" do symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET" symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T" value = [ - "€a".force_encoding(Encoding::UTF_8).to_sym, - "€b".force_encoding(Encoding::UTF_8).to_sym + "€a".dup.force_encoding(Encoding::UTF_8).to_sym, + "€b".dup.force_encoding(Encoding::UTF_8).to_sym ] Marshal.dump(value).should == "\x04\b[\a#{symbol1}#{symbol2}" value = [*value, value[0]] Marshal.dump(value).should == "\x04\b[\b#{symbol1}#{symbol2};\x00" end + + it "uses symbol links for objects repeatedly dumped" do + symbol = :foo + Marshal.dump([symbol, symbol]).should == "\x04\b[\a:\bfoo;\x00" # ;\x00 is a link to the symbol object + end end describe "with an object responding to #marshal_dump" do @@ -108,6 +118,20 @@ describe "Marshal.dump" do it "raises TypeError if an Object is an instance of an anonymous class" do -> { Marshal.dump(Class.new(UserMarshal).new) }.should raise_error(TypeError, /can't dump anonymous class/) end + + it "uses object links for objects repeatedly dumped" do + obj = UserMarshal.new + Marshal.dump([obj, obj]).should == "\x04\b[\aU:\x10UserMarshal:\tdata@\x06" # @\x06 is a link to the object + end + + it "adds instance variables of a dumped object after the object itself into the objects table" do + value = "<foo>" + obj = MarshalSpec::UserMarshalDumpWithIvar.new("string", value) + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\t, that means Integer 4) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bU:)MarshalSpec::UserMarshalDumpWithIvarI[\x06\"\vstring\x06:\t@foo\"\n<foo>@\x06@\t" + end end describe "with an object responding to #_dump" do @@ -150,7 +174,7 @@ describe "Marshal.dump" do it "indexes instance variables of a String returned by #_dump at first and then indexes the object itself" do class MarshalSpec::M1::A def _dump(level) - s = "<dump>" + s = +"<dump>" s.instance_variable_set(:@foo, "bar") s end @@ -166,6 +190,20 @@ describe "Marshal.dump" do Marshal.dump([a, a]).should == "\x04\b[\aIu:\x17MarshalSpec::M1::A\v<dump>\x06:\t@foo\"\bbar#{reference}" end + it "uses object links for objects repeatedly dumped" do + obj = UserDefined.new + Marshal.dump([obj, obj]).should == "\x04\b[\au:\x10UserDefined\x12\x04\b[\a:\nstuff;\x00@\x06" # @\x06 is a link to the object + end + + it "adds instance variables of a dumped String before the object itself into the objects table" do + value = "<foo>" + obj = MarshalSpec::UserDefinedDumpWithIVars.new(+"string", value) + + # expect a link to the object (@\a, that means Integer 2) is greater than a link + # to the instance variable value (@\x06, that means Integer 1) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bIu:*MarshalSpec::UserDefinedDumpWithIVars\vstring\x06:\t@foo\"\n<foo>@\a@\x06" + end + describe "Core library classes with #_dump returning a String with instance variables" do it "indexes instance variables and then a Time object itself" do t = Time.utc(2022) @@ -193,9 +231,16 @@ describe "Marshal.dump" do Marshal.dump(MarshalSpec::ClassWithOverriddenName).should == "\x04\bc)MarshalSpec::ClassWithOverriddenName" end - it "dumps a class with multibyte characters in name" do - source_object = eval("MarshalSpec::Multibyteãã‚ãƒã„Class".force_encoding(Encoding::UTF_8)) - Marshal.dump(source_object).should == "\x04\bc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class" + ruby_version_is "4.0" do + it "dumps a class with multibyte characters in name" do + source_object = eval("MarshalSpec::Multibyteãã‚ãƒã„Class".dup.force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bIc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class\x06:\x06ET" + Marshal.load(Marshal.dump(source_object)) == source_object + end + end + + it "uses object links for objects repeatedly dumped" do + Marshal.dump([String, String]).should == "\x04\b[\ac\vString@\x06" # @\x06 is a link to the object end it "raises TypeError with an anonymous Class" do @@ -216,9 +261,16 @@ describe "Marshal.dump" do Marshal.dump(MarshalSpec::ModuleWithOverriddenName).should == "\x04\bc*MarshalSpec::ModuleWithOverriddenName" end - it "dumps a module with multibyte characters in name" do - source_object = eval("MarshalSpec::Multibyteã‘ã’ã“ã”Module".force_encoding(Encoding::UTF_8)) - Marshal.dump(source_object).should == "\x04\bm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module" + ruby_version_is "4.0" do + it "dumps a module with multibyte characters in name" do + source_object = eval("MarshalSpec::Multibyteã‘ã’ã“ã”Module".dup.force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bIm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module\x06:\x06ET" + Marshal.load(Marshal.dump(source_object)) == source_object + end + end + + it "uses object links for objects repeatedly dumped" do + Marshal.dump([Marshal, Marshal]).should == "\x04\b[\am\fMarshal@\x06" # @\x06 is a link to the object end it "raises TypeError with an anonymous Module" do @@ -239,6 +291,23 @@ describe "Marshal.dump" do [Marshal, nan_value, "\004\bf\bnan"], ].should be_computed_by(:dump) end + + it "may or may not use object links for objects repeatedly dumped" do + # it's an MRI implementation detail - on x86 architecture object links + # aren't used for Float values but on amd64 - object links are used + + dump = Marshal.dump([0.0, 0.0]) + ["\x04\b[\af\x060@\x06", "\x04\b[\af\x060f\x060"].should.include?(dump) + + # if object links aren't used - entries in the objects table are still + # occupied by Float values + if dump == "\x04\b[\af\x060f\x060" + s = "string" + # an index of "string" ("@\b") in the object table equals 3 (`"\b".ord - 5`), + # so `0.0, 0,0` elements occupied indices 1 and 2 + Marshal.dump([0.0, 0.0, s, s]).should == "\x04\b[\tf\x060f\x060\"\vstring@\b" + end + end end describe "with a Bignum" do @@ -256,6 +325,11 @@ describe "Marshal.dump" do ].should be_computed_by(:dump) end + it "uses object links for objects repeatedly dumped" do + n = 2**64 + Marshal.dump([n, n]).should == "\x04\b[\al+\n\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00@\x06" # @\x06 is a link to the object + end + it "increases the object links counter" do obj = Object.new object_1_link = "\x06" # representing of (0-based) index=1 (by adding 5 for small Integers) @@ -271,13 +345,60 @@ describe "Marshal.dump" do end end + describe "with a Rational" do + it "dumps a Rational" do + Marshal.dump(Rational(2, 3)).should == "\x04\bU:\rRational[\ai\ai\b" + end + + it "uses object links for objects repeatedly dumped" do + r = Rational(2, 3) + Marshal.dump([r, r]).should == "\x04\b[\aU:\rRational[\ai\ai\b@\x06" # @\x06 is a link to the object + end + end + + describe "with a Complex" do + it "dumps a Complex" do + Marshal.dump(Complex(2, 3)).should == "\x04\bU:\fComplex[\ai\ai\b" + end + + it "uses object links for objects repeatedly dumped" do + c = Complex(2, 3) + Marshal.dump([c, c]).should == "\x04\b[\aU:\fComplex[\ai\ai\b@\x06" # @\x06 is a link to the object + end + end + + describe "with a Data" do + it "dumps a Data" do + Marshal.dump(MarshalSpec::DataSpec::Measure.new(100, 'km')).should == "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm" + end + + it "dumps an extended Data" do + obj = MarshalSpec::DataSpec::MeasureExtended.new(100, "km") + Marshal.dump(obj).should == "\x04\bS:+MarshalSpec::DataSpec::MeasureExtended\a:\vamountii:\tunit\"\akm" + end + + it "ignores overridden name method" do + obj = MarshalSpec::DataSpec::MeasureWithOverriddenName.new(100, "km") + Marshal.dump(obj).should == "\x04\bS:5MarshalSpec::DataSpec::MeasureWithOverriddenName\a:\vamountii:\tunit\"\akm" + end + + it "uses object links for objects repeatedly dumped" do + d = MarshalSpec::DataSpec::Measure.new(100, 'km') + Marshal.dump([d, d]).should == "\x04\b[\aS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm@\x06" # @\x06 is a link to the object + end + + it "raises TypeError with an anonymous Struct" do + -> { Marshal.dump(Data.define(:a).new(1)) }.should raise_error(TypeError, /can't dump anonymous class/) + end + end + describe "with a String" do it "dumps a blank String" do - Marshal.dump("".force_encoding("binary")).should == "\004\b\"\000" + Marshal.dump("".dup.force_encoding("binary")).should == "\004\b\"\000" end it "dumps a short String" do - Marshal.dump("short".force_encoding("binary")).should == "\004\b\"\012short" + Marshal.dump("short".dup.force_encoding("binary")).should == "\004\b\"\012short" end it "dumps a long String" do @@ -285,7 +406,7 @@ describe "Marshal.dump" do end it "dumps a String extended with a Module" do - Marshal.dump("".extend(Meths).force_encoding("binary")).should == "\004\be:\nMeths\"\000" + Marshal.dump("".dup.extend(Meths).force_encoding("binary")).should == "\004\be:\nMeths\"\000" end it "dumps a String subclass" do @@ -302,23 +423,23 @@ describe "Marshal.dump" do end it "dumps a String with instance variables" do - str = "" + str = +"" str.instance_variable_set("@foo", "bar") Marshal.dump(str.force_encoding("binary")).should == "\x04\bI\"\x00\x06:\t@foo\"\bbar" end it "dumps a US-ASCII String" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") Marshal.dump(str).should == "\x04\bI\"\babc\x06:\x06EF" end it "dumps a UTF-8 String" do - str = "\x6d\xc3\xb6\x68\x72\x65".force_encoding("utf-8") + str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8") Marshal.dump(str).should == "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET" end it "dumps a String in another encoding" do - str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".force_encoding("utf-16le") + str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le") result = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE" Marshal.dump(str).should == result end @@ -326,6 +447,21 @@ describe "Marshal.dump" do it "dumps multiple strings using symlinks for the :E (encoding) symbol" do Marshal.dump(["".encode("us-ascii"), "".encode("utf-8")]).should == "\x04\b[\aI\"\x00\x06:\x06EFI\"\x00\x06;\x00T" end + + it "uses object links for objects repeatedly dumped" do + s = "string" + Marshal.dump([s, s]).should == "\x04\b[\a\"\vstring@\x06" # @\x06 is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = +"string" + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bI\"\vstring\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with a Regexp" do @@ -352,7 +488,7 @@ describe "Marshal.dump" do end it "dumps a binary Regexp" do - o = Regexp.new("".force_encoding("binary"), Regexp::FIXEDENCODING) + o = Regexp.new("".dup.force_encoding("binary"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\b/\x00\x10" end @@ -371,18 +507,18 @@ describe "Marshal.dump" do end it "dumps a UTF-8 Regexp" do - o = Regexp.new("".force_encoding("utf-8"), Regexp::FIXEDENCODING) + o = Regexp.new("".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\x06ET" - o = Regexp.new("a".force_encoding("utf-8"), Regexp::FIXEDENCODING) + o = Regexp.new("a".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\x06a\x10\x06:\x06ET" - o = Regexp.new("\u3042".force_encoding("utf-8"), Regexp::FIXEDENCODING) + o = Regexp.new("\u3042".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\b\xE3\x81\x82\x10\x06:\x06ET" end it "dumps a Regexp in another encoding" do - o = Regexp.new("".force_encoding("utf-16le"), Regexp::FIXEDENCODING) + o = Regexp.new("".dup.force_encoding("utf-16le"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\rencoding\"\rUTF-16LE" o = Regexp.new("a".encode("utf-16le"), Regexp::FIXEDENCODING) @@ -393,6 +529,11 @@ describe "Marshal.dump" do obj = MarshalSpec::RegexpWithOverriddenName.new("") Marshal.dump(obj).should == "\x04\bIC:*MarshalSpec::RegexpWithOverriddenName/\x00\x00\x06:\x06EF" end + + it "uses object links for objects repeatedly dumped" do + r = /\A.\Z/ + Marshal.dump([r, r]).should == "\x04\b[\aI/\n\\A.\\Z\x00\x06:\x06EF@\x06" # @\x06 is a link to the object + end end describe "with an Array" do @@ -428,6 +569,21 @@ describe "Marshal.dump" do obj = MarshalSpec::ArrayWithOverriddenName.new Marshal.dump(obj).should == "\x04\bC:)MarshalSpec::ArrayWithOverriddenName[\x00" end + + it "uses object links for objects repeatedly dumped" do + a = [1] + Marshal.dump([a, a]).should == "\x04\b[\a[\x06i\x06@\x06" # @\x06 is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = [] + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bI[\x00\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with a Hash" do @@ -447,20 +603,18 @@ describe "Marshal.dump" do Marshal.dump(Hash.new(1)).should == "\004\b}\000i\006" end - ruby_version_is "3.1" do - it "dumps a Hash with compare_by_identity" do - h = {} - h.compare_by_identity + it "dumps a Hash with compare_by_identity" do + h = {} + h.compare_by_identity - Marshal.dump(h).should == "\004\bC:\tHash{\x00" - end + Marshal.dump(h).should == "\004\bC:\tHash{\x00" + end - it "dumps a Hash subclass with compare_by_identity" do - h = UserHash.new - h.compare_by_identity + it "dumps a Hash subclass with compare_by_identity" do + h = UserHash.new + h.compare_by_identity - Marshal.dump(h).should == "\x04\bC:\rUserHashC:\tHash{\x00" - end + Marshal.dump(h).should == "\x04\bC:\rUserHashC:\tHash{\x00" end it "raises a TypeError with hash having default proc" do @@ -485,6 +639,21 @@ describe "Marshal.dump" do obj = MarshalSpec::HashWithOverriddenName.new Marshal.dump(obj).should == "\x04\bC:(MarshalSpec::HashWithOverriddenName{\x00" end + + it "uses object links for objects repeatedly dumped" do + h = {a: 1} + Marshal.dump([h, h]).should == "\x04\b[\a{\x06:\x06ai\x06@\x06" # @\x06 is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = {} + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bI{\x00\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with a Struct" do @@ -519,9 +688,24 @@ describe "Marshal.dump" do Marshal.dump(obj).should == "\x04\bS:*MarshalSpec::StructWithOverriddenName\x06:\x06a\"\vmember" end + it "uses object links for objects repeatedly dumped" do + s = Struct::Pyramid.new + Marshal.dump([s, s]).should == "\x04\b[\aS:\x14Struct::Pyramid\x00@\x06" # @\x06 is a link to the object + end + it "raises TypeError with an anonymous Struct" do -> { Marshal.dump(Struct.new(:a).new(1)) }.should raise_error(TypeError, /can't dump anonymous class/) end + + it "adds instance variables after the object itself into the objects table" do + obj = Struct::Pyramid.new + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bIS:\x14Struct::Pyramid\x00\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with an Object" do @@ -541,7 +725,7 @@ describe "Marshal.dump" do it "dumps an Object with a non-US-ASCII instance variable" do obj = Object.new - ivar = "@é".force_encoding(Encoding::UTF_8).to_sym + ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym obj.instance_variable_set(ivar, 1) Marshal.dump(obj).should == "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06" end @@ -611,21 +795,45 @@ describe "Marshal.dump" do ObjectSpace.define_finalizer(obj, finalizer.method(:noop)) Marshal.load(Marshal.dump(obj)).class.should == Object end + + it "uses object links for objects repeatedly dumped" do + obj = Object.new + Marshal.dump([obj, obj]).should == "\x04\b[\ao:\vObject\x00@\x06" # @\x06 is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = Object.new + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bo:\vObject\x06:\t@foo\"\n<foo>@\x06@\a" + end end describe "with a Range" do - it "dumps a Range inclusive of end (with indeterminant order)" do + it "dumps a Range inclusive of end" do dump = Marshal.dump(1..2) + dump.should == "\x04\bo:\nRange\b:\texclF:\nbegini\x06:\bendi\a" + load = Marshal.load(dump) load.should == (1..2) end - it "dumps a Range exclusive of end (with indeterminant order)" do + it "dumps a Range exclusive of end" do dump = Marshal.dump(1...2) + dump.should == "\x04\bo:\nRange\b:\texclT:\nbegini\x06:\bendi\a" + load = Marshal.load(dump) load.should == (1...2) end + it "uses object links for objects repeatedly dumped" do + r = 1..2 + Marshal.dump([r, r]).should == "\x04\b[\ao:\nRange\b:\texclF:\nbegini\x06:\bendi\a@\x06" # @\x06 is a link to the object + end + it "raises TypeError with an anonymous Range subclass" do -> { Marshal.dump(Class.new(Range).new(1, 2)) }.should raise_error(TypeError, /can't dump anonymous class/) end @@ -672,9 +880,32 @@ describe "Marshal.dump" do Marshal.dump(obj).should include("MarshalSpec::TimeWithOverriddenName") end - it "dumps a Time subclass with multibyte characters in name" do - source_object = eval("MarshalSpec::Multibyteãã‚ãƒã„Time".force_encoding(Encoding::UTF_8)) - Marshal.dump(source_object).should == "\x04\bc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time" + ruby_version_is "4.0" do + it "dumps a Time subclass with multibyte characters in name" do + source_object = eval("MarshalSpec::Multibyteãã‚ãƒã„Time".dup.force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bIc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time\x06:\x06ET" + Marshal.load(Marshal.dump(source_object)) == source_object + end + end + + it "uses object links for objects repeatedly dumped" do + # order of the offset and zone instance variables is a subject to change + # and may be different on different CRuby versions + base = Regexp.quote("\x04\b[\aIu:\tTime\r\xF5\xEF\e\x80\x00\x00\x00\x00\a") + offset = Regexp.quote(":\voffseti\x020*:\tzoneI\"\bAST\x06:\x06EF") + zone = Regexp.quote(":\tzoneI\"\bAST\x06:\x06EF:\voffseti\x020*") + instance_variables = /#{offset}|#{zone}/ + Marshal.dump([@t, @t]).should =~ /\A#{base}#{instance_variables}@\a\Z/ # @\a is a link to the object + end + + it "adds instance variables before the object itself into the objects table" do + obj = @utc + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\b, that means Integer 3) is greater than a link + # to the instance variable value (@\x06, that means Integer 1) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bIu:\tTime\r \x00\x1C\xC0\x00\x00\x00\x00\a:\t@foo\"\n<foo>:\tzoneI\"\bUTC\x06:\x06EF@\b@\x06" end it "raises TypeError with an anonymous Time subclass" do @@ -728,7 +959,22 @@ describe "Marshal.dump" do rescue => e end - Marshal.dump(e).should =~ /undefined method `foo' for ("":String|an instance of String)/ + Marshal.dump(e).should =~ /undefined method [`']foo' for ("":String|an instance of String)/ + end + + it "uses object links for objects repeatedly dumped" do + e = Exception.new + Marshal.dump([e, e]).should == "\x04\b[\ao:\x0EException\a:\tmesg0:\abt0@\x06" # @\x\a is a link to the object + end + + it "adds instance variables after the object itself into the objects table" do + obj = Exception.new + value = "<foo>" + obj.instance_variable_set :@foo, value + + # expect a link to the object (@\x06, that means Integer 1) is smaller than a link + # to the instance variable value (@\a, that means Integer 2) + Marshal.dump([obj, obj, value]).should == "\x04\b[\bo:\x0EException\b:\tmesg0:\abt0:\t@foo\"\n<foo>@\x06@\a" end it "raises TypeError if an Object is an instance of an anonymous class" do @@ -769,7 +1015,6 @@ describe "Marshal.dump" do end describe "when passed an IO" do - it "writes the serialized data to the IO-Object" do (obj = mock('test')).should_receive(:write).at_least(1) Marshal.dump("test", obj) @@ -792,8 +1037,6 @@ describe "Marshal.dump" do obj.should_receive(:binmode).at_least(1) Marshal.dump("test", obj) end - - end describe "when passed a StringIO" do diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb index 680cb08ac7..c16d9e4bb6 100644 --- a/spec/ruby/core/marshal/fixtures/marshal_data.rb +++ b/spec/ruby/core/marshal/fixtures/marshal_data.rb @@ -1,4 +1,7 @@ -# -*- encoding: binary -*- +# encoding: binary + +require_relative 'marshal_multibyte_data' + class UserDefined class Nested def ==(other) @@ -38,7 +41,7 @@ class UserDefinedWithIvar attr_reader :a, :b, :c def initialize - @a = 'stuff' + @a = +'stuff' @a.instance_variable_set :@foo, :UserDefinedWithIvar @b = 'more' @c = @b @@ -94,6 +97,25 @@ class UserDefinedString end end +module MarshalSpec + class UserDefinedDumpWithIVars + attr_reader :string + + def initialize(string, ivar_value) + @string = string + @string.instance_variable_set(:@foo, ivar_value) + end + + def _dump(depth) + @string + end + + def self._load(data) + new(data) + end + end +end + class UserPreviouslyDefinedWithInitializedIvar attr_accessor :field1, :field2 end @@ -136,6 +158,32 @@ class UserMarshalWithIvar end end +module MarshalSpec + class UserMarshalDumpWithIvar + attr_reader :data + + def initialize(data, ivar_value) + @data = data + @ivar_value = ivar_value + end + + def marshal_dump + obj = [data] + obj.instance_variable_set(:@foo, @ivar_value) + obj + end + + def marshal_load(o) + @data = o[0] + end + + def ==(other) + self.class === other and + @data = other.data + end + end +end + class UserArray < Array end @@ -267,17 +315,6 @@ module MarshalSpec end end - module_eval(<<~ruby.force_encoding(Encoding::UTF_8)) - class Multibyteãã‚ãƒã„Class - end - - module Multibyteã‘ã’ã“ã”Module - end - - class Multibyteãã‚ãƒã„Time < Time - end - ruby - class ObjectWithFreezeRaisingException < Object def freeze raise @@ -313,7 +350,7 @@ module MarshalSpec "\004\b\"\012small"], "String big" => ['big' * 100, "\004\b\"\002,\001#{'big' * 100}"], - "String extended" => [''.extend(Meths), # TODO: check for module on load + "String extended" => [''.dup.extend(Meths), # TODO: check for module on load "\004\be:\nMeths\"\000"], "String subclass" => [UserString.new, "\004\bC:\017UserString\"\000"], @@ -420,7 +457,7 @@ module MarshalSpec "\x04\bI\"\nsmall\x06:\x06EF"], "String big" => ['big' * 100, "\x04\bI\"\x02,\x01bigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbig\x06:\x06EF"], - "String extended" => [''.extend(Meths), # TODO: check for module on load + "String extended" => [''.dup.extend(Meths), # TODO: check for module on load "\x04\bIe:\nMeths\"\x00\x06:\x06EF"], "String subclass" => [UserString.new, "\004\bC:\017UserString\"\000"], @@ -494,6 +531,20 @@ module MarshalSpec "\004\bS:\024Struct::Pyramid\000"], "Random" => random_data, } + + module DataSpec + Measure = Data.define(:amount, :unit) + Empty = Data.define + + MeasureExtended = Class.new(Measure) + MeasureExtended.extend(Enumerable) + + class MeasureWithOverriddenName < Measure + def self.name + "Foo" + end + end + end end class ArraySub < Array diff --git a/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb b/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb new file mode 100644 index 0000000000..98a0d43392 --- /dev/null +++ b/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb @@ -0,0 +1,12 @@ +# -*- encoding: utf-8 -*- + +module MarshalSpec + class Multibyteãã‚ãƒã„Class + end + + module Multibyteã‘ã’ã“ã”Module + end + + class Multibyteãã‚ãƒã„Time < Time + end +end diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb index b37e65071d..204a4d34e3 100644 --- a/spec/ruby/core/marshal/shared/load.rb +++ b/spec/ruby/core/marshal/shared/load.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../fixtures/marshal_data' describe :marshal_load, shared: true do @@ -23,252 +23,166 @@ describe :marshal_load, shared: true do -> { Marshal.send(@method, kaboom) }.should raise_error(ArgumentError) end - ruby_version_is "3.1" do - describe "when called with freeze: true" do - it "returns frozen strings" do - string = Marshal.send(@method, Marshal.dump("foo"), freeze: true) - string.should == "foo" - string.should.frozen? - - utf8_string = "foo".encode(Encoding::UTF_8) - string = Marshal.send(@method, Marshal.dump(utf8_string), freeze: true) - string.should == utf8_string - string.should.frozen? - end - - it "returns frozen arrays" do - array = Marshal.send(@method, Marshal.dump([1, 2, 3]), freeze: true) - array.should == [1, 2, 3] - array.should.frozen? - end - - it "returns frozen hashes" do - hash = Marshal.send(@method, Marshal.dump({foo: 42}), freeze: true) - hash.should == {foo: 42} - hash.should.frozen? - end - - it "returns frozen regexps" do - regexp = Marshal.send(@method, Marshal.dump(/foo/), freeze: true) - regexp.should == /foo/ - regexp.should.frozen? - end + describe "when called with freeze: true" do + it "returns frozen strings" do + string = Marshal.send(@method, Marshal.dump("foo"), freeze: true) + string.should == "foo" + string.should.frozen? - it "returns frozen structs" do - struct = Marshal.send(@method, Marshal.dump(MarshalSpec::StructToDump.new(1, 2)), freeze: true) - struct.should == MarshalSpec::StructToDump.new(1, 2) - struct.should.frozen? - end + utf8_string = "foo".encode(Encoding::UTF_8) + string = Marshal.send(@method, Marshal.dump(utf8_string), freeze: true) + string.should == utf8_string + string.should.frozen? + end - it "returns frozen objects" do - source_object = Object.new + it "returns frozen arrays" do + array = Marshal.send(@method, Marshal.dump([1, 2, 3]), freeze: true) + array.should == [1, 2, 3] + array.should.frozen? + end - object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - object.should.frozen? - end + it "returns frozen hashes" do + hash = Marshal.send(@method, Marshal.dump({foo: 42}), freeze: true) + hash.should == {foo: 42} + hash.should.frozen? + end - describe "deep freezing" do - it "returns hashes with frozen keys and values" do - key = Object.new - value = Object.new - source_object = {key => value} + it "returns frozen regexps" do + regexp = Marshal.send(@method, Marshal.dump(/foo/), freeze: true) + regexp.should == /foo/ + regexp.should.frozen? + end - hash = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - hash.size.should == 1 - hash.keys[0].should.frozen? - hash.values[0].should.frozen? - end + it "returns frozen structs" do + struct = Marshal.send(@method, Marshal.dump(MarshalSpec::StructToDump.new(1, 2)), freeze: true) + struct.should == MarshalSpec::StructToDump.new(1, 2) + struct.should.frozen? + end - it "returns arrays with frozen elements" do - object = Object.new - source_object = [object] + it "returns frozen objects" do + source_object = Object.new - array = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - array.size.should == 1 - array[0].should.frozen? - end + object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + object.should.frozen? + end - it "returns structs with frozen members" do - object1 = Object.new - object2 = Object.new - source_object = MarshalSpec::StructToDump.new(object1, object2) + describe "deep freezing" do + it "returns hashes with frozen keys and values" do + key = Object.new + value = Object.new + source_object = {key => value} - struct = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - struct.a.should.frozen? - struct.b.should.frozen? - end + hash = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + hash.size.should == 1 + hash.keys[0].should.frozen? + hash.values[0].should.frozen? + end - it "returns objects with frozen instance variables" do - source_object = Object.new - instance_variable = Object.new - source_object.instance_variable_set(:@a, instance_variable) + it "returns arrays with frozen elements" do + object = Object.new + source_object = [object] - object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - object.instance_variable_get(:@a).should != nil - object.instance_variable_get(:@a).should.frozen? - end + array = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + array.size.should == 1 + array[0].should.frozen? + end - it "deduplicates frozen strings" do - source_object = ["foo" + "bar", "foobar"] - object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + it "returns structs with frozen members" do + object1 = Object.new + object2 = Object.new + source_object = MarshalSpec::StructToDump.new(object1, object2) - object[0].should equal(object[1]) - end + struct = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + struct.a.should.frozen? + struct.b.should.frozen? end - it "does not freeze modules" do - object = Marshal.send(@method, Marshal.dump(Kernel), freeze: true) - object.should_not.frozen? - Kernel.should_not.frozen? - end + it "returns objects with frozen instance variables" do + source_object = Object.new + instance_variable = Object.new + source_object.instance_variable_set(:@a, instance_variable) - it "does not freeze classes" do - object = Marshal.send(@method, Marshal.dump(Object), freeze: true) - object.should_not.frozen? - Object.should_not.frozen? + object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + object.instance_variable_get(:@a).should != nil + object.instance_variable_get(:@a).should.frozen? end - ruby_bug "#19427", ""..."3.3" do - it "does freeze extended objects" do - object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true) - object.should.frozen? - end + it "deduplicates frozen strings" do + source_object = ["foo" + "bar", "foobar"] + object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) - it "does freeze extended objects with instance variables" do - object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true) - object.should.frozen? - end + object[0].should equal(object[1]) end + end - ruby_bug "#19427", "3.1"..."3.3" do - it "returns frozen object having #_dump method" do - object = Marshal.send(@method, Marshal.dump(UserDefined.new), freeze: true) - object.should.frozen? - end - - it "returns frozen object responding to #marshal_dump and #marshal_load" do - object = Marshal.send(@method, Marshal.dump(UserMarshal.new), freeze: true) - object.should.frozen? - end + it "does not freeze modules" do + object = Marshal.send(@method, Marshal.dump(Kernel), freeze: true) + object.should_not.frozen? + Kernel.should_not.frozen? + end - it "returns frozen object extended by a module" do - object = Object.new - object.extend(MarshalSpec::ModuleToExtendBy) + it "does not freeze classes" do + object = Marshal.send(@method, Marshal.dump(Object), freeze: true) + object.should_not.frozen? + Object.should_not.frozen? + end - object = Marshal.send(@method, Marshal.dump(object), freeze: true) - object.should.frozen? - end + ruby_bug "#19427", ""..."3.3" do + it "does freeze extended objects" do + object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true) + object.should.frozen? end - it "does not call freeze method" do - object = MarshalSpec::ObjectWithFreezeRaisingException.new - object = Marshal.send(@method, Marshal.dump(object), freeze: true) + it "does freeze extended objects with instance variables" do + object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true) object.should.frozen? end + end - it "returns frozen object even if object does not respond to freeze method" do - object = MarshalSpec::ObjectWithoutFreeze.new - object = Marshal.send(@method, Marshal.dump(object), freeze: true) + ruby_bug "#19427", ""..."3.3" do + it "returns frozen object having #_dump method" do + object = Marshal.send(@method, Marshal.dump(UserDefined.new), freeze: true) object.should.frozen? end - it "returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables" do - source_object = UserString.new - source_object.instance_variable_set(:@foo, "bar") - - object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + it "returns frozen object responding to #marshal_dump and #marshal_load" do + object = Marshal.send(@method, Marshal.dump(UserMarshal.new), freeze: true) object.should.frozen? end - describe "when called with a proc" do - it "call the proc with frozen objects" do - arr = [] - s = 'hi' - s.instance_variable_set(:@foo, 5) - st = Struct.new("Brittle", :a).new - st.instance_variable_set(:@clue, 'none') - st.a = 0.0 - h = Hash.new('def') - h['nine'] = 9 - a = [:a, :b, :c] - a.instance_variable_set(:@two, 2) - obj = [s, 10, s, s, st, a] - obj.instance_variable_set(:@zoo, 'ant') - proc = Proc.new { |o| arr << o; o} - - Marshal.send( - @method, - "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", - proc, - freeze: true, - ) - - arr.should == [ - false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st, - :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]], - ] - - arr.each do |v| - v.should.frozen? - end - - Struct.send(:remove_const, :Brittle) - end + it "returns frozen object extended by a module" do + object = Object.new + object.extend(MarshalSpec::ModuleToExtendBy) - it "does not freeze the object returned by the proc" do - string = Marshal.send(@method, Marshal.dump("foo"), proc { |o| o.upcase }, freeze: true) - string.should == "FOO" - string.should_not.frozen? - end + object = Marshal.send(@method, Marshal.dump(object), freeze: true) + object.should.frozen? end end - end - describe "when called with a proc" do - ruby_bug "#18141", ""..."3.1" do - it "call the proc with fully initialized strings" do - utf8_string = "foo".encode(Encoding::UTF_8) - Marshal.send(@method, Marshal.dump(utf8_string), proc { |arg| - if arg.is_a?(String) - arg.should == utf8_string - arg.encoding.should == Encoding::UTF_8 - end - arg - }) - end - - it "no longer mutate the object after it was passed to the proc" do - string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc) - string.should.frozen? - end + it "does not call freeze method" do + object = MarshalSpec::ObjectWithFreezeRaisingException.new + object = Marshal.send(@method, Marshal.dump(object), freeze: true) + object.should.frozen? end - ruby_bug "#19427", ""..."3.3" do - it "call the proc with extended objects" do - objs = [] - obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o }) - objs.should == [obj] - end + it "returns frozen object even if object does not respond to freeze method" do + object = MarshalSpec::ObjectWithoutFreeze.new + object = Marshal.send(@method, Marshal.dump(object), freeze: true) + object.should.frozen? end - it "returns the value of the proc" do - Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should == [3,4] - end + it "returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables" do + source_object = UserString.new + source_object.instance_variable_set(:@foo, "bar") - ruby_bug "#18141", ""..."3.1" do - it "calls the proc for recursively visited data" do - a = [1] - a << a - ret = [] - Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg.inspect; arg }) - ret[0].should == 1.inspect - ret[1].should == a.inspect - ret.size.should == 2 - end + object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + object.should.frozen? + end - it "loads an Array with proc" do + describe "when called with a proc" do + it "call the proc with frozen objects" do arr = [] - s = 'hi' + s = +'hi' s.instance_variable_set(:@foo, 5) st = Struct.new("Brittle", :a).new st.instance_variable_set(:@clue, 'none') @@ -279,16 +193,96 @@ describe :marshal_load, shared: true do a.instance_variable_set(:@two, 2) obj = [s, 10, s, s, st, a] obj.instance_variable_set(:@zoo, 'ant') - proc = Proc.new { |o| arr << o.dup; o} + proc = Proc.new { |o| arr << o; o} - Marshal.send(@method, "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc) + Marshal.send( + @method, + "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", + proc, + freeze: true, + ) arr.should == [ false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st, :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]], ] + + arr.each do |v| + v.should.frozen? + end + Struct.send(:remove_const, :Brittle) end + + it "does not freeze the object returned by the proc" do + string = Marshal.send(@method, Marshal.dump("foo"), proc { |o| o.upcase }, freeze: true) + string.should == "FOO" + string.should_not.frozen? + end + end + end + + describe "when called with a proc" do + it "call the proc with fully initialized strings" do + utf8_string = "foo".encode(Encoding::UTF_8) + Marshal.send(@method, Marshal.dump(utf8_string), proc { |arg| + if arg.is_a?(String) + arg.should == utf8_string + arg.encoding.should == Encoding::UTF_8 + end + arg + }) + end + + it "no longer mutate the object after it was passed to the proc" do + string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc) + string.should.frozen? + end + + ruby_bug "#19427", ""..."3.3" do + it "call the proc with extended objects" do + objs = [] + obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o }) + objs.should == [obj] + end + end + + it "returns the value of the proc" do + Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should == [3,4] + end + + it "calls the proc for recursively visited data" do + a = [1] + a << a + ret = [] + Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg.inspect; arg }) + ret[0].should == 1.inspect + ret[1].should == a.inspect + ret.size.should == 2 + end + + it "loads an Array with proc" do + arr = [] + s = +'hi' + s.instance_variable_set(:@foo, 5) + st = Struct.new("Brittle", :a).new + st.instance_variable_set(:@clue, 'none') + st.a = 0.0 + h = Hash.new('def') + h['nine'] = 9 + a = [:a, :b, :c] + a.instance_variable_set(:@two, 2) + obj = [s, 10, s, s, st, a] + obj.instance_variable_set(:@zoo, 'ant') + proc = Proc.new { |o| arr << o.dup; o} + + Marshal.send(@method, "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc) + + arr.should == [ + false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st, + :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]], + ] + Struct.send(:remove_const, :Brittle) end end @@ -364,39 +358,37 @@ describe :marshal_load, shared: true do end end - ruby_bug "#18141", ""..."3.1" do - it "loads an array containing objects having _dump method, and with proc" do - arr = [] - myproc = Proc.new { |o| arr << o.dup; o } - o1 = UserDefined.new; - o2 = UserDefinedWithIvar.new - obj = [o1, o2, o1, o2] + it "loads an array containing objects having _dump method, and with proc" do + arr = [] + myproc = Proc.new { |o| arr << o.dup; o } + o1 = UserDefined.new; + o2 = UserDefinedWithIvar.new + obj = [o1, o2, o1, o2] - Marshal.send(@method, "\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc) + Marshal.send(@method, "\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc) - arr[0].should == o1 - arr[1].should == o2 - arr[2].should == obj - arr.size.should == 3 - end + arr[0].should == o1 + arr[1].should == o2 + arr[2].should == obj + arr.size.should == 3 + end - it "loads an array containing objects having marshal_dump method, and with proc" do - arr = [] - proc = Proc.new { |o| arr << o.dup; o } - o1 = UserMarshal.new - o2 = UserMarshalWithIvar.new + it "loads an array containing objects having marshal_dump method, and with proc" do + arr = [] + proc = Proc.new { |o| arr << o.dup; o } + o1 = UserMarshal.new + o2 = UserMarshalWithIvar.new - Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc) + Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc) - arr[0].should == 'stuff' - arr[1].should == o1 - arr[2].should == 'my data' - arr[3].should == ['my data'] - arr[4].should == o2 - arr[5].should == [o1, o2, o1, o2] + arr[0].should == 'stuff' + arr[1].should == o1 + arr[2].should == 'my data' + arr[3].should == ['my data'] + arr[4].should == o2 + arr[5].should == [o1, o2, o1, o2] - arr.size.should == 6 - end + arr.size.should == 6 end it "assigns classes to nested subclasses of Array correctly" do @@ -413,13 +405,13 @@ describe :marshal_load, shared: true do end it "raises a TypeError with bad Marshal version" do - marshal_data = '\xff\xff' + marshal_data = +'\xff\xff' marshal_data[0] = (Marshal::MAJOR_VERSION).chr marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr -> { Marshal.send(@method, marshal_data) }.should raise_error(TypeError) - marshal_data = '\xff\xff' + marshal_data = +'\xff\xff' marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr marshal_data[1] = (Marshal::MINOR_VERSION).chr @@ -470,7 +462,7 @@ describe :marshal_load, shared: true do end it "loads an array having ivar" do - s = 'well' + s = +'well' s.instance_variable_set(:@foo, 10) obj = ['5', s, 'hi'].extend(Meths, MethsMore) obj.instance_variable_set(:@mix, s) @@ -516,7 +508,7 @@ describe :marshal_load, shared: true do end it "preserves hash ivars when hash contains a string having ivar" do - s = 'string' + s = +'string' s.instance_variable_set :@string_ivar, 'string ivar' h = { key: s } h.instance_variable_set :@hash_ivar, 'hash ivar' @@ -526,28 +518,26 @@ describe :marshal_load, shared: true do unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar' end - ruby_version_is "3.1" do - it "preserves compare_by_identity behaviour" do - h = { a: 1 } - h.compare_by_identity - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should.compare_by_identity? + it "preserves compare_by_identity behaviour" do + h = { a: 1 } + h.compare_by_identity + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should.compare_by_identity? - h = { a: 1 } - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should_not.compare_by_identity? - end + h = { a: 1 } + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should_not.compare_by_identity? + end - it "preserves compare_by_identity behaviour for a Hash subclass" do - h = UserHash.new({ a: 1 }) - h.compare_by_identity - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should.compare_by_identity? + it "preserves compare_by_identity behaviour for a Hash subclass" do + h = UserHash.new({ a: 1 }) + h.compare_by_identity + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should.compare_by_identity? - h = UserHash.new({ a: 1 }) - unmarshalled = Marshal.send(@method, Marshal.dump(h)) - unmarshalled.should_not.compare_by_identity? - end + h = UserHash.new({ a: 1 }) + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should_not.compare_by_identity? end it "allocates an instance of the proper class when Hash subclass with compare_by_identity behaviour" do @@ -600,7 +590,7 @@ describe :marshal_load, shared: true do end it "loads a binary encoded Symbol" do - s = "\u2192".force_encoding("binary").to_sym + s = "\u2192".dup.force_encoding("binary").to_sym sym = Marshal.send(@method, "\x04\b:\b\xE2\x86\x92") sym.should == s sym.encoding.should == Encoding::BINARY @@ -614,8 +604,8 @@ describe :marshal_load, shared: true do value = Marshal.send(@method, dump) value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8] expected = [ - "€a".force_encoding(Encoding::UTF_8).to_sym, - "€b".force_encoding(Encoding::UTF_8).to_sym + "€a".dup.force_encoding(Encoding::UTF_8).to_sym, + "€b".dup.force_encoding(Encoding::UTF_8).to_sym ] value.should == expected @@ -635,7 +625,7 @@ describe :marshal_load, shared: true do describe "for a String" do it "loads a string having ivar with ref to self" do - obj = 'hi' + obj = +'hi' obj.instance_variable_set(:@self, obj) Marshal.send(@method, "\004\bI\"\ahi\006:\n@self@\000").should == obj end @@ -663,7 +653,7 @@ describe :marshal_load, shared: true do end it "loads a US-ASCII String" do - str = "abc".force_encoding("us-ascii") + str = "abc".dup.force_encoding("us-ascii") data = "\x04\bI\"\babc\x06:\x06EF" result = Marshal.send(@method, data) result.should == str @@ -671,7 +661,7 @@ describe :marshal_load, shared: true do end it "loads a UTF-8 String" do - str = "\x6d\xc3\xb6\x68\x72\x65".force_encoding("utf-8") + str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8") data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET" result = Marshal.send(@method, data) result.should == str @@ -679,7 +669,7 @@ describe :marshal_load, shared: true do end it "loads a String in another encoding" do - str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".force_encoding("utf-16le") + str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le") data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE" result = Marshal.send(@method, data) result.should == str @@ -687,8 +677,8 @@ describe :marshal_load, shared: true do end it "loads a String as BINARY if no encoding is specified at the end" do - str = "\xC3\xB8".force_encoding("BINARY") - data = "\x04\b\"\a\xC3\xB8".force_encoding("UTF-8") + str = "\xC3\xB8".dup.force_encoding("BINARY") + data = "\x04\b\"\a\xC3\xB8".dup.force_encoding("UTF-8") result = Marshal.send(@method, data) result.encoding.should == Encoding::BINARY result.should == str @@ -749,6 +739,32 @@ describe :marshal_load, shared: true do end end + describe "for a Data" do + it "loads a Data" do + obj = MarshalSpec::DataSpec::Measure.new(100, 'km') + dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm" + Marshal.dump(obj).should == dumped + + Marshal.send(@method, dumped).should == obj + end + + it "loads an extended Data" do + obj = MarshalSpec::DataSpec::MeasureExtended.new(100, "km") + dumped = "\x04\bS:+MarshalSpec::DataSpec::MeasureExtended\a:\vamountii:\tunit\"\akm" + Marshal.dump(obj).should == dumped + + Marshal.send(@method, dumped).should == obj + end + + it "returns a frozen object" do + obj = MarshalSpec::DataSpec::Measure.new(100, 'km') + dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm" + Marshal.dump(obj).should == dumped + + Marshal.send(@method, dumped).should.frozen? + end + end + describe "for an Exception" do it "loads a marshalled exception with no message" do obj = Exception.new @@ -823,7 +839,7 @@ describe :marshal_load, shared: true do end it "loads an Object with a non-US-ASCII instance variable" do - ivar = "@é".force_encoding(Encoding::UTF_8).to_sym + ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym obj = Marshal.send(@method, "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06") obj.instance_variables.should == [ivar] obj.instance_variables[0].encoding.should == Encoding::UTF_8 @@ -1034,18 +1050,22 @@ describe :marshal_load, shared: true do describe "for a Rational" do it "loads" do - Marshal.send(@method, Marshal.dump(Rational(1, 3))).should == Rational(1, 3) + r = Marshal.send(@method, Marshal.dump(Rational(1, 3))) + r.should == Rational(1, 3) + r.should.frozen? end end describe "for a Complex" do it "loads" do - Marshal.send(@method, Marshal.dump(Complex(4, 3))).should == Complex(4, 3) + c = Marshal.send(@method, Marshal.dump(Complex(4, 3))) + c.should == Complex(4, 3) + c.should.frozen? end end describe "for a Bignum" do - platform_is wordsize: 64 do + platform_is c_long_size: 64 do context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do it "dumps a Fixnum" do val = Marshal.send(@method, "\004\bl+\ab:wU") diff --git a/spec/ruby/core/matchdata/begin_spec.rb b/spec/ruby/core/matchdata/begin_spec.rb index 85c454da56..54b4e0a33f 100644 --- a/spec/ruby/core/matchdata/begin_spec.rb +++ b/spec/ruby/core/matchdata/begin_spec.rb @@ -36,6 +36,18 @@ describe "MatchData#begin" do match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") match_data.begin(obj).should == 2 end + + it "raises IndexError if index is out of bounds" do + match_data = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + match_data.begin(-1) + }.should raise_error(IndexError, "index -1 out of matches") + + -> { + match_data.begin(3) + }.should raise_error(IndexError, "index 3 out of matches") + end end context "when passed a String argument" do @@ -68,6 +80,14 @@ describe "MatchData#begin" do match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.") match_data.begin("æ").should == 1 end + + it "raises IndexError if there is no group with the provided name" do + match_data = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + match_data.begin("y") + }.should raise_error(IndexError, "undefined group name reference: y") + end end context "when passed a Symbol argument" do @@ -100,5 +120,13 @@ describe "MatchData#begin" do match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.") match_data.begin(:æ).should == 1 end + + it "raises IndexError if there is no group with the provided name" do + match_data = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + match_data.begin(:y) + }.should raise_error(IndexError, "undefined group name reference: y") + end end end diff --git a/spec/ruby/core/matchdata/bytebegin_spec.rb b/spec/ruby/core/matchdata/bytebegin_spec.rb new file mode 100644 index 0000000000..08c1fd6d1e --- /dev/null +++ b/spec/ruby/core/matchdata/bytebegin_spec.rb @@ -0,0 +1,132 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.4" do + describe "MatchData#bytebegin" do + context "when passed an integer argument" do + it "returns the byte-based offset of the start of the nth element" do + match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") + match_data.bytebegin(0).should == 1 + match_data.bytebegin(2).should == 2 + end + + it "returns nil when the nth match isn't found" do + match_data = /something is( not)? (right)/.match("something is right") + match_data.bytebegin(1).should be_nil + end + + it "returns the byte-based offset for multi-byte strings" do + match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.") + match_data.bytebegin(0).should == 1 + match_data.bytebegin(2).should == 3 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi-byte strings with unicode regexp" do + match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.") + match_data.bytebegin(0).should == 1 + match_data.bytebegin(2).should == 3 + end + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + + match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") + match_data.bytebegin(obj).should == 2 + end + + it "raises IndexError if index is out of bounds" do + match_data = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + match_data.bytebegin(-1) + }.should raise_error(IndexError, "index -1 out of matches") + + -> { + match_data.bytebegin(3) + }.should raise_error(IndexError, "index 3 out of matches") + end + end + + context "when passed a String argument" do + it "return the byte-based offset of the start of the named capture" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.") + match_data.bytebegin("a").should == 1 + match_data.bytebegin("b").should == 3 + end + + it "returns the byte-based offset for multi byte strings" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.") + match_data.bytebegin("a").should == 1 + match_data.bytebegin("b").should == 4 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi byte strings with unicode regexp" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.") + match_data.bytebegin("a").should == 1 + match_data.bytebegin("b").should == 4 + end + end + + it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do + match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.") + match_data.bytebegin("a").should == 3 + end + + it "returns the byte-based offset for multi-byte names" do + match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.") + match_data.bytebegin("æ").should == 1 + end + + it "raises IndexError if there is no group with the provided name" do + match_data = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + match_data.bytebegin("y") + }.should raise_error(IndexError, "undefined group name reference: y") + end + end + + context "when passed a Symbol argument" do + it "return the byte-based offset of the start of the named capture" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.") + match_data.bytebegin(:a).should == 1 + match_data.bytebegin(:b).should == 3 + end + + it "returns the byte-based offset for multi byte strings" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.") + match_data.bytebegin(:a).should == 1 + match_data.bytebegin(:b).should == 4 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi byte strings with unicode regexp" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.") + match_data.bytebegin(:a).should == 1 + match_data.bytebegin(:b).should == 4 + end + end + + it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do + match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.") + match_data.bytebegin(:a).should == 3 + end + + it "returns the byte-based offset for multi-byte names" do + match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.") + match_data.bytebegin(:æ).should == 1 + end + + it "raises IndexError if there is no group with the provided name" do + match_data = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + match_data.bytebegin(:y) + }.should raise_error(IndexError, "undefined group name reference: y") + end + end + end +end diff --git a/spec/ruby/core/matchdata/byteend_spec.rb b/spec/ruby/core/matchdata/byteend_spec.rb new file mode 100644 index 0000000000..98015e287d --- /dev/null +++ b/spec/ruby/core/matchdata/byteend_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.4" do + describe "MatchData#byteend" do + context "when passed an integer argument" do + it "returns the byte-based offset of the end of the nth element" do + match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") + match_data.byteend(0).should == 7 + match_data.byteend(2).should == 3 + end + + it "returns nil when the nth match isn't found" do + match_data = /something is( not)? (right)/.match("something is right") + match_data.byteend(1).should be_nil + end + + it "returns the byte-based offset for multi-byte strings" do + match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.") + match_data.byteend(0).should == 8 + match_data.byteend(2).should == 4 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi-byte strings with unicode regexp" do + match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.") + match_data.byteend(0).should == 8 + match_data.byteend(2).should == 4 + end + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + + match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") + match_data.byteend(obj).should == 3 + end + end + + context "when passed a String argument" do + it "return the byte-based offset of the start of the named capture" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.") + match_data.byteend("a").should == 2 + match_data.byteend("b").should == 6 + end + + it "returns the byte-based offset for multi byte strings" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.") + match_data.byteend("a").should == 3 + match_data.byteend("b").should == 7 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi byte strings with unicode regexp" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.") + match_data.byteend("a").should == 3 + match_data.byteend("b").should == 7 + end + end + + it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do + match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.") + match_data.byteend("a").should == 6 + end + + it "returns the byte-based offset for multi-byte names" do + match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.") + match_data.byteend("æ").should == 2 + end + end + + context "when passed a Symbol argument" do + it "return the byte-based offset of the start of the named capture" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("THX1138.") + match_data.byteend(:a).should == 2 + match_data.byteend(:b).should == 6 + end + + it "returns the byte-based offset for multi byte strings" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/.match("TñX1138.") + match_data.byteend(:a).should == 3 + match_data.byteend(:b).should == 7 + end + + not_supported_on :opal do + it "returns the byte-based offset for multi byte strings with unicode regexp" do + match_data = /(?<a>.)(.)(?<b>\d+)(\d)/u.match("TñX1138.") + match_data.byteend(:a).should == 3 + match_data.byteend(:b).should == 7 + end + end + + it "returns the byte-based offset for the farthest match when multiple named captures use the same name" do + match_data = /(?<a>.)(.)(?<a>\d+)(\d)/.match("THX1138.") + match_data.byteend(:a).should == 6 + end + + it "returns the byte-based offset for multi-byte names" do + match_data = /(?<æ>.)(.)(?<b>\d+)(\d)/.match("THX1138.") + match_data.byteend(:æ).should == 2 + end + end + end +end diff --git a/spec/ruby/core/matchdata/byteoffset_spec.rb b/spec/ruby/core/matchdata/byteoffset_spec.rb index 6036097834..fb8f5fb67d 100644 --- a/spec/ruby/core/matchdata/byteoffset_spec.rb +++ b/spec/ruby/core/matchdata/byteoffset_spec.rb @@ -1,95 +1,93 @@ require_relative '../../spec_helper' describe "MatchData#byteoffset" do - ruby_version_is "3.2" do - it "returns beginning and ending byte-based offset of whole matched substring for 0 element" do - m = /(.)(.)(\d+)(\d)/.match("THX1138.") - m.byteoffset(0).should == [1, 7] - end + it "returns beginning and ending byte-based offset of whole matched substring for 0 element" do + m = /(.)(.)(\d+)(\d)/.match("THX1138.") + m.byteoffset(0).should == [1, 7] + end - it "returns beginning and ending byte-based offset of n-th match, all the subsequent elements are capturing groups" do - m = /(.)(.)(\d+)(\d)/.match("THX1138.") + it "returns beginning and ending byte-based offset of n-th match, all the subsequent elements are capturing groups" do + m = /(.)(.)(\d+)(\d)/.match("THX1138.") - m.byteoffset(2).should == [2, 3] - m.byteoffset(3).should == [3, 6] - m.byteoffset(4).should == [6, 7] - end + m.byteoffset(2).should == [2, 3] + m.byteoffset(3).should == [3, 6] + m.byteoffset(4).should == [6, 7] + end - it "accepts String as a reference to a named capture" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "accepts String as a reference to a named capture" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - m.byteoffset("f").should == [0, 3] - m.byteoffset("b").should == [3, 6] - end + m.byteoffset("f").should == [0, 3] + m.byteoffset("b").should == [3, 6] + end - it "accepts Symbol as a reference to a named capture" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "accepts Symbol as a reference to a named capture" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - m.byteoffset(:f).should == [0, 3] - m.byteoffset(:b).should == [3, 6] - end + m.byteoffset(:f).should == [0, 3] + m.byteoffset(:b).should == [3, 6] + end - it "returns [nil, nil] if a capturing group is optional and doesn't match" do - m = /(?<x>q..)?/.match("foobarbaz") + it "returns [nil, nil] if a capturing group is optional and doesn't match" do + m = /(?<x>q..)?/.match("foobarbaz") - m.byteoffset("x").should == [nil, nil] - m.byteoffset(1).should == [nil, nil] - end + m.byteoffset("x").should == [nil, nil] + m.byteoffset(1).should == [nil, nil] + end - it "returns correct beginning and ending byte-based offset for multi-byte strings" do - m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + it "returns correct beginning and ending byte-based offset for multi-byte strings" do + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") - m.byteoffset(1).should == [3, 6] - m.byteoffset(3).should == [6, 9] - end + m.byteoffset(1).should == [3, 6] + m.byteoffset(3).should == [6, 9] + end - it "returns [nil, nil] if a capturing group is optional and doesn't match for multi-byte string" do - m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + it "returns [nil, nil] if a capturing group is optional and doesn't match for multi-byte string" do + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") - m.byteoffset(2).should == [nil, nil] - end + m.byteoffset(2).should == [nil, nil] + end - it "converts argument into integer if is not String nor Symbol" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "converts argument into integer if is not String nor Symbol" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - obj = Object.new - def obj.to_int; 2; end + obj = Object.new + def obj.to_int; 2; end - m.byteoffset(1r).should == [0, 3] - m.byteoffset(1.1).should == [0, 3] - m.byteoffset(obj).should == [3, 6] - end + m.byteoffset(1r).should == [0, 3] + m.byteoffset(1.1).should == [0, 3] + m.byteoffset(obj).should == [3, 6] + end - it "raises IndexError if there is no group with provided name" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "raises IndexError if there is no group with the provided name" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - -> { - m.byteoffset("y") - }.should raise_error(IndexError, "undefined group name reference: y") + -> { + m.byteoffset("y") + }.should raise_error(IndexError, "undefined group name reference: y") - -> { - m.byteoffset(:y) - }.should raise_error(IndexError, "undefined group name reference: y") - end + -> { + m.byteoffset(:y) + }.should raise_error(IndexError, "undefined group name reference: y") + end - it "raises IndexError if index is out of matches" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "raises IndexError if index is out of bounds" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - -> { - m.byteoffset(-1) - }.should raise_error(IndexError, "index -1 out of matches") + -> { + m.byteoffset(-1) + }.should raise_error(IndexError, "index -1 out of matches") - -> { - m.byteoffset(3) - }.should raise_error(IndexError, "index 3 out of matches") - end + -> { + m.byteoffset(3) + }.should raise_error(IndexError, "index 3 out of matches") + end - it "raises TypeError if can't convert argument into Integer" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "raises TypeError if can't convert argument into Integer" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - -> { - m.byteoffset([]) - }.should raise_error(TypeError, "no implicit conversion of Array into Integer") - end + -> { + m.byteoffset([]) + }.should raise_error(TypeError, "no implicit conversion of Array into Integer") end end diff --git a/spec/ruby/core/matchdata/deconstruct_keys_spec.rb b/spec/ruby/core/matchdata/deconstruct_keys_spec.rb index 5b68f886c7..bf22bc33ff 100644 --- a/spec/ruby/core/matchdata/deconstruct_keys_spec.rb +++ b/spec/ruby/core/matchdata/deconstruct_keys_spec.rb @@ -1,65 +1,63 @@ require_relative '../../spec_helper' describe "MatchData#deconstruct_keys" do - ruby_version_is "3.2" do - it "returns whole hash for nil as an argument" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "returns whole hash for nil as an argument" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - m.deconstruct_keys(nil).should == { f: "foo", b: "bar" } - end + m.deconstruct_keys(nil).should == { f: "foo", b: "bar" } + end - it "returns only specified keys" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "returns only specified keys" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - m.deconstruct_keys([:f]).should == { f: "foo" } - end + m.deconstruct_keys([:f]).should == { f: "foo" } + end - it "requires one argument" do - m = /l/.match("l") + it "requires one argument" do + m = /l/.match("l") - -> { - m.deconstruct_keys - }.should raise_error(ArgumentError, "wrong number of arguments (given 0, expected 1)") - end + -> { + m.deconstruct_keys + }.should raise_error(ArgumentError, "wrong number of arguments (given 0, expected 1)") + end - it "it raises error when argument is neither nil nor array" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "it raises error when argument is neither nil nor array" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - -> { m.deconstruct_keys(1) }.should raise_error(TypeError, "wrong argument type Integer (expected Array)") - -> { m.deconstruct_keys("asd") }.should raise_error(TypeError, "wrong argument type String (expected Array)") - -> { m.deconstruct_keys(:x) }.should raise_error(TypeError, "wrong argument type Symbol (expected Array)") - -> { m.deconstruct_keys({}) }.should raise_error(TypeError, "wrong argument type Hash (expected Array)") - end + -> { m.deconstruct_keys(1) }.should raise_error(TypeError, "wrong argument type Integer (expected Array)") + -> { m.deconstruct_keys("asd") }.should raise_error(TypeError, "wrong argument type String (expected Array)") + -> { m.deconstruct_keys(:x) }.should raise_error(TypeError, "wrong argument type Symbol (expected Array)") + -> { m.deconstruct_keys({}) }.should raise_error(TypeError, "wrong argument type Hash (expected Array)") + end - it "returns {} when passed []" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "returns {} when passed []" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - m.deconstruct_keys([]).should == {} - end + m.deconstruct_keys([]).should == {} + end - it "does not accept non-Symbol keys" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") + it "does not accept non-Symbol keys" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") - -> { - m.deconstruct_keys(['year', :foo]) - }.should raise_error(TypeError, "wrong argument type String (expected Symbol)") - end + -> { + m.deconstruct_keys(['year', :foo]) + }.should raise_error(TypeError, "wrong argument type String (expected Symbol)") + end - it "process keys till the first non-existing one" do - m = /(?<f>foo)(?<b>bar)(?<c>baz)/.match("foobarbaz") + it "process keys till the first non-existing one" do + m = /(?<f>foo)(?<b>bar)(?<c>baz)/.match("foobarbaz") - m.deconstruct_keys([:f, :a, :b]).should == { f: "foo" } - end + m.deconstruct_keys([:f, :a, :b]).should == { f: "foo" } + end - it "returns {} when there are no named captured groups at all" do - m = /foo.+/.match("foobar") + it "returns {} when there are no named captured groups at all" do + m = /foo.+/.match("foobar") - m.deconstruct_keys(nil).should == {} - end + m.deconstruct_keys(nil).should == {} + end - it "returns {} when passed more keys than named captured groups" do - m = /(?<f>foo)(?<b>bar)/.match("foobar") - m.deconstruct_keys([:f, :b, :c]).should == {} - end + it "returns {} when passed more keys than named captured groups" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") + m.deconstruct_keys([:f, :b, :c]).should == {} end end diff --git a/spec/ruby/core/matchdata/deconstruct_spec.rb b/spec/ruby/core/matchdata/deconstruct_spec.rb index 6af55113b6..c55095665d 100644 --- a/spec/ruby/core/matchdata/deconstruct_spec.rb +++ b/spec/ruby/core/matchdata/deconstruct_spec.rb @@ -2,7 +2,5 @@ require_relative '../../spec_helper' require_relative 'shared/captures' describe "MatchData#deconstruct" do - ruby_version_is "3.2" do - it_behaves_like :matchdata_captures, :deconstruct - end + it_behaves_like :matchdata_captures, :deconstruct end diff --git a/spec/ruby/core/matchdata/element_reference_spec.rb b/spec/ruby/core/matchdata/element_reference_spec.rb index 1be399cfe1..0924be0aae 100644 --- a/spec/ruby/core/matchdata/element_reference_spec.rb +++ b/spec/ruby/core/matchdata/element_reference_spec.rb @@ -20,6 +20,11 @@ describe "MatchData#[]" do # negative index is larger than the number of match values /(.)(.)(\d+)(\d)/.match("THX1138.")[-30, 2].should == nil + # positive index larger than number of match values + /(.)(.)(\d+)(\d)/.match("THX1138.")[5, 2].should == [] + /(.)(.)(\d+)(\d)/.match("THX1138.")[6, 2].should == nil + /(.)(.)(\d+)(\d)/.match("THX1138.")[30, 2].should == nil + # length argument larger than number of match values is capped to match value length /(.)(.)(\d+)(\d)/.match("THX1138.")[3, 10].should == %w|113 8| @@ -113,7 +118,7 @@ describe "MatchData#[Symbol]" do it "returns matches in the String's encoding" do rex = /(?<t>t(?<a>ack))/u - md = 'haystack'.force_encoding('euc-jp').match(rex) + md = 'haystack'.dup.force_encoding('euc-jp').match(rex) md[:t].encoding.should == Encoding::EUC_JP end end diff --git a/spec/ruby/core/matchdata/match_length_spec.rb b/spec/ruby/core/matchdata/match_length_spec.rb index f7785ab1a0..824f94a397 100644 --- a/spec/ruby/core/matchdata/match_length_spec.rb +++ b/spec/ruby/core/matchdata/match_length_spec.rb @@ -2,33 +2,31 @@ require_relative '../../spec_helper' -ruby_version_is "3.1" do - describe "MatchData#match_length" do - it "returns the length of the corresponding match when given an Integer" do - md = /(.)(.)(\d+)(\d)/.match("THX1138.") +describe "MatchData#match_length" do + it "returns the length of the corresponding match when given an Integer" do + md = /(.)(.)(\d+)(\d)/.match("THX1138.") - md.match_length(0).should == 6 - md.match_length(1).should == 1 - md.match_length(2).should == 1 - md.match_length(3).should == 3 - md.match_length(4).should == 1 - end + md.match_length(0).should == 6 + md.match_length(1).should == 1 + md.match_length(2).should == 1 + md.match_length(3).should == 3 + md.match_length(4).should == 1 + end - it "returns nil on non-matching index matches" do - md = /\d+(\w)?/.match("THX1138.") - md.match_length(1).should == nil - end + it "returns nil on non-matching index matches" do + md = /\d+(\w)?/.match("THX1138.") + md.match_length(1).should == nil + end - it "returns the length of the corresponding named match when given a Symbol" do - md = 'haystack'.match(/(?<t>t(?<a>ack))/) - md.match_length(:a).should == 3 - md.match_length(:t).should == 4 - end + it "returns the length of the corresponding named match when given a Symbol" do + md = 'haystack'.match(/(?<t>t(?<a>ack))/) + md.match_length(:a).should == 3 + md.match_length(:t).should == 4 + end - it "returns nil on non-matching index matches" do - md = 'haystack'.match(/(?<t>t)(?<a>all)?/) - md.match_length(:t).should == 1 - md.match_length(:a).should == nil - end + it "returns nil on non-matching index matches" do + md = 'haystack'.match(/(?<t>t)(?<a>all)?/) + md.match_length(:t).should == 1 + md.match_length(:a).should == nil end end diff --git a/spec/ruby/core/matchdata/match_spec.rb b/spec/ruby/core/matchdata/match_spec.rb index 545de6f93f..a16914ea15 100644 --- a/spec/ruby/core/matchdata/match_spec.rb +++ b/spec/ruby/core/matchdata/match_spec.rb @@ -2,33 +2,31 @@ require_relative '../../spec_helper' -ruby_version_is "3.1" do - describe "MatchData#match" do - it "returns the corresponding match when given an Integer" do - md = /(.)(.)(\d+)(\d)/.match("THX1138.") +describe "MatchData#match" do + it "returns the corresponding match when given an Integer" do + md = /(.)(.)(\d+)(\d)/.match("THX1138.") - md.match(0).should == 'HX1138' - md.match(1).should == 'H' - md.match(2).should == 'X' - md.match(3).should == '113' - md.match(4).should == '8' - end + md.match(0).should == 'HX1138' + md.match(1).should == 'H' + md.match(2).should == 'X' + md.match(3).should == '113' + md.match(4).should == '8' + end - it "returns nil on non-matching index matches" do - md = /\d+(\w)?/.match("THX1138.") - md.match(1).should == nil - end + it "returns nil on non-matching index matches" do + md = /\d+(\w)?/.match("THX1138.") + md.match(1).should == nil + end - it "returns the corresponding named match when given a Symbol" do - md = 'haystack'.match(/(?<t>t(?<a>ack))/) - md.match(:a).should == 'ack' - md.match(:t).should == 'tack' - end + it "returns the corresponding named match when given a Symbol" do + md = 'haystack'.match(/(?<t>t(?<a>ack))/) + md.match(:a).should == 'ack' + md.match(:t).should == 'tack' + end - it "returns nil on non-matching index matches" do - md = 'haystack'.match(/(?<t>t)(?<a>all)?/) - md.match(:t).should == 't' - md.match(:a).should == nil - end + it "returns nil on non-matching index matches" do + md = 'haystack'.match(/(?<t>t)(?<a>all)?/) + md.match(:t).should == 't' + md.match(:a).should == nil end end diff --git a/spec/ruby/core/matchdata/offset_spec.rb b/spec/ruby/core/matchdata/offset_spec.rb index 1ccb54b7a7..a03d58aad1 100644 --- a/spec/ruby/core/matchdata/offset_spec.rb +++ b/spec/ruby/core/matchdata/offset_spec.rb @@ -1,30 +1,102 @@ -# -*- encoding: utf-8 -*- - require_relative '../../spec_helper' describe "MatchData#offset" do - it "returns a two element array with the begin and end of the nth match" do - match_data = /(.)(.)(\d+)(\d)/.match("THX1138.") - match_data.offset(0).should == [1, 7] - match_data.offset(4).should == [6, 7] + it "returns beginning and ending character offset of whole matched substring for 0 element" do + m = /(.)(.)(\d+)(\d)/.match("THX1138.") + m.offset(0).should == [1, 7] + end + + it "returns beginning and ending character offset of n-th match, all the subsequent elements are capturing groups" do + m = /(.)(.)(\d+)(\d)/.match("THX1138.") + + m.offset(2).should == [2, 3] + m.offset(3).should == [3, 6] + m.offset(4).should == [6, 7] + end + + it "accepts String as a reference to a named capture" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") + + m.offset("f").should == [0, 3] + m.offset("b").should == [3, 6] + end + + it "accepts Symbol as a reference to a named capture" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") + + m.offset(:f).should == [0, 3] + m.offset(:b).should == [3, 6] end - it "returns [nil, nil] when the nth match isn't found" do - match_data = /something is( not)? (right)/.match("something is right") - match_data.offset(1).should == [nil, nil] + it "returns [nil, nil] if a capturing group is optional and doesn't match" do + m = /(?<x>q..)?/.match("foobarbaz") + + m.offset("x").should == [nil, nil] + m.offset(1).should == [nil, nil] end - it "returns the offset for multi byte strings" do - match_data = /(.)(.)(\d+)(\d)/.match("TñX1138.") - match_data.offset(0).should == [1, 7] - match_data.offset(4).should == [6, 7] + it "returns correct beginning and ending character offset for multi-byte strings" do + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + + m.offset(1).should == [1, 2] + m.offset(3).should == [2, 3] end not_supported_on :opal do - it "returns the offset for multi byte strings with unicode regexp" do - match_data = /(.)(.)(\d+)(\d)/u.match("TñX1138.") - match_data.offset(0).should == [1, 7] - match_data.offset(4).should == [6, 7] + it "returns correct character offset for multi-byte strings with unicode regexp" do + m = /\A\u3042(.)(.)?(.)\z/u.match("\u3042\u3043\u3044") + + m.offset(1).should == [1, 2] + m.offset(3).should == [2, 3] end end + + it "returns [nil, nil] if a capturing group is optional and doesn't match for multi-byte string" do + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + + m.offset(2).should == [nil, nil] + end + + it "converts argument into integer if is not String nor Symbol" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") + + obj = Object.new + def obj.to_int; 2; end + + m.offset(1r).should == [0, 3] + m.offset(1.1).should == [0, 3] + m.offset(obj).should == [3, 6] + end + + it "raises IndexError if there is no group with the provided name" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + m.offset("y") + }.should raise_error(IndexError, "undefined group name reference: y") + + -> { + m.offset(:y) + }.should raise_error(IndexError, "undefined group name reference: y") + end + + it "raises IndexError if index is out of bounds" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + m.offset(-1) + }.should raise_error(IndexError, "index -1 out of matches") + + -> { + m.offset(3) + }.should raise_error(IndexError, "index 3 out of matches") + end + + it "raises TypeError if can't convert argument into Integer" do + m = /(?<f>foo)(?<b>bar)/.match("foobar") + + -> { + m.offset([]) + }.should raise_error(TypeError, "no implicit conversion of Array into Integer") + end end diff --git a/spec/ruby/core/matchdata/post_match_spec.rb b/spec/ruby/core/matchdata/post_match_spec.rb index b8d1e032eb..7bfe6df119 100644 --- a/spec/ruby/core/matchdata/post_match_spec.rb +++ b/spec/ruby/core/matchdata/post_match_spec.rb @@ -8,12 +8,12 @@ describe "MatchData#post_match" do end it "sets the encoding to the encoding of the source String" do - str = "abc".force_encoding Encoding::EUC_JP + str = "abc".dup.force_encoding Encoding::EUC_JP str.match(/b/).post_match.encoding.should equal(Encoding::EUC_JP) end it "sets an empty result to the encoding of the source String" do - str = "abc".force_encoding Encoding::ISO_8859_1 + str = "abc".dup.force_encoding Encoding::ISO_8859_1 str.match(/c/).post_match.encoding.should equal(Encoding::ISO_8859_1) end diff --git a/spec/ruby/core/matchdata/pre_match_spec.rb b/spec/ruby/core/matchdata/pre_match_spec.rb index 741cb6e923..2f1ba9b8f6 100644 --- a/spec/ruby/core/matchdata/pre_match_spec.rb +++ b/spec/ruby/core/matchdata/pre_match_spec.rb @@ -8,12 +8,12 @@ describe "MatchData#pre_match" do end it "sets the encoding to the encoding of the source String" do - str = "abc".force_encoding Encoding::EUC_JP + str = "abc".dup.force_encoding Encoding::EUC_JP str.match(/b/).pre_match.encoding.should equal(Encoding::EUC_JP) end it "sets an empty result to the encoding of the source String" do - str = "abc".force_encoding Encoding::ISO_8859_1 + str = "abc".dup.force_encoding Encoding::ISO_8859_1 str.match(/a/).pre_match.encoding.should equal(Encoding::ISO_8859_1) end diff --git a/spec/ruby/core/matchdata/string_spec.rb b/spec/ruby/core/matchdata/string_spec.rb index 420233e1f3..952e953318 100644 --- a/spec/ruby/core/matchdata/string_spec.rb +++ b/spec/ruby/core/matchdata/string_spec.rb @@ -17,8 +17,9 @@ describe "MatchData#string" do md.string.should equal(md.string) end - it "returns a frozen copy of the matched string for gsub(String)" do - 'he[[o'.gsub!('[', ']') + it "returns a frozen copy of the matched string for gsub!(String)" do + s = +'he[[o' + s.gsub!('[', ']') $~.string.should == 'he[[o' $~.string.should.frozen? end diff --git a/spec/ruby/core/matchdata/values_at_spec.rb b/spec/ruby/core/matchdata/values_at_spec.rb index 4fd0bfc42a..535719a2ee 100644 --- a/spec/ruby/core/matchdata/values_at_spec.rb +++ b/spec/ruby/core/matchdata/values_at_spec.rb @@ -1,6 +1,6 @@ require_relative '../../spec_helper' -describe "Struct#values_at" do +describe "MatchData#values_at" do # Should be synchronized with core/array/values_at_spec.rb and core/struct/values_at_spec.rb # # /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").to_a # => ["HX1138", "H", "X", "113", "8"] @@ -34,7 +34,7 @@ describe "Struct#values_at" do end it "supports beginningless Range" do - /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(0..2).should == ["HX1138", "H", "X"] + /(.)(.)(\d+)(\d)/.match("THX1138: The Movie").values_at(..2).should == ["HX1138", "H", "X"] end it "returns an empty Array when Range is empty" do diff --git a/spec/ruby/core/math/atanh_spec.rb b/spec/ruby/core/math/atanh_spec.rb index 21fb209941..edcb8f2e52 100644 --- a/spec/ruby/core/math/atanh_spec.rb +++ b/spec/ruby/core/math/atanh_spec.rb @@ -1,6 +1,6 @@ require_relative '../../spec_helper' -require_relative '../../fixtures/math/common' -require_relative '../../shared/math/atanh' +require_relative 'fixtures/common' +require_relative 'shared/atanh' describe "Math.atanh" do it_behaves_like :math_atanh_base, :atanh, Math diff --git a/spec/ruby/core/math/expm1_spec.rb b/spec/ruby/core/math/expm1_spec.rb new file mode 100644 index 0000000000..5725319abb --- /dev/null +++ b/spec/ruby/core/math/expm1_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "4.0" do + describe "Math.expm1" do + it "calculates Math.exp(arg) - 1" do + Math.expm1(3).should == Math.exp(3) - 1 + end + + it "preserves precision that can be lost otherwise" do + Math.expm1(1.0e-16).should be_close(1.0e-16, TOLERANCE) + Math.expm1(1.0e-16).should != 0.0 + end + + it "raises a TypeError if the argument cannot be coerced with Float()" do + -> { Math.expm1("test") }.should raise_error(TypeError, "can't convert String into Float") + end + + it "returns NaN given NaN" do + Math.expm1(nan_value).nan?.should be_true + end + + it "raises a TypeError if the argument is nil" do + -> { Math.expm1(nil) }.should raise_error(TypeError, "can't convert nil into Float") + end + + it "accepts any argument that can be coerced with Float()" do + Math.expm1(MathSpecs::Float.new).should be_close(Math::E - 1, TOLERANCE) + end + end + + describe "Math#expm1" do + it "is accessible as a private instance method" do + IncludesMath.new.send(:expm1, 23.1415).should be_close(11226018483.0012, TOLERANCE) + end + end +end diff --git a/spec/ruby/core/math/fixtures/common.rb b/spec/ruby/core/math/fixtures/common.rb new file mode 100644 index 0000000000..024732fa7a --- /dev/null +++ b/spec/ruby/core/math/fixtures/common.rb @@ -0,0 +1,3 @@ +class IncludesMath + include Math +end diff --git a/spec/ruby/core/math/lgamma_spec.rb b/spec/ruby/core/math/lgamma_spec.rb index 33e7836448..2bf350993e 100644 --- a/spec/ruby/core/math/lgamma_spec.rb +++ b/spec/ruby/core/math/lgamma_spec.rb @@ -5,10 +5,8 @@ describe "Math.lgamma" do Math.lgamma(0).should == [infinity_value, 1] end - platform_is_not :windows do - it "returns [Infinity, 1] when passed -1" do - Math.lgamma(-1).should == [infinity_value, 1] - end + it "returns [Infinity, ...] when passed -1" do + Math.lgamma(-1)[0].should == infinity_value end it "returns [Infinity, -1] when passed -0.0" do @@ -47,8 +45,7 @@ describe "Math.lgamma" do Math.lgamma(infinity_value).should == [infinity_value, 1] end - it "returns [NaN, 1] when passed NaN" do - Math.lgamma(nan_value)[0].nan?.should be_true - Math.lgamma(nan_value)[1].should == 1 + it "returns [NaN, ...] when passed NaN" do + Math.lgamma(nan_value)[0].should.nan? end end diff --git a/spec/ruby/core/math/log1p_spec.rb b/spec/ruby/core/math/log1p_spec.rb new file mode 100644 index 0000000000..216358a3c4 --- /dev/null +++ b/spec/ruby/core/math/log1p_spec.rb @@ -0,0 +1,49 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "4.0" do + describe "Math.log1p" do + it "calculates Math.log(1 + arg)" do + Math.log1p(3).should == Math.log(1 + 3) + end + + it "preserves precision that can be lost otherwise" do + Math.log1p(1e-16).should be_close(1.0e-16, TOLERANCE) + Math.log1p(1e-16).should != 0.0 + end + + it "raises an Math::DomainError if the argument is less than 1" do + -> { Math.log1p(-1-1e-15) }.should raise_error(Math::DomainError, "Numerical argument is out of domain - log1p") + end + + it "raises a TypeError if the argument cannot be coerced with Float()" do + -> { Math.log1p("test") }.should raise_error(TypeError, "can't convert String into Float") + end + + it "raises a TypeError for numerical values passed as string" do + -> { Math.log1p("10") }.should raise_error(TypeError, "can't convert String into Float") + end + + it "does not accept a second argument for the base" do + -> { Math.log1p(9, 3) }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 1)") + end + + it "returns NaN given NaN" do + Math.log1p(nan_value).nan?.should be_true + end + + it "raises a TypeError if the argument is nil" do + -> { Math.log1p(nil) }.should raise_error(TypeError, "can't convert nil into Float") + end + + it "accepts any argument that can be coerced with Float()" do + Math.log1p(MathSpecs::Float.new).should be_close(0.6931471805599453, TOLERANCE) + end + end + + describe "Math#log1p" do + it "is accessible as a private instance method" do + IncludesMath.new.send(:log1p, 4.21).should be_close(1.65057985576528, TOLERANCE) + end + end +end diff --git a/spec/ruby/core/math/shared/atanh.rb b/spec/ruby/core/math/shared/atanh.rb new file mode 100644 index 0000000000..3fb64153a0 --- /dev/null +++ b/spec/ruby/core/math/shared/atanh.rb @@ -0,0 +1,44 @@ +describe :math_atanh_base, shared: true do + it "returns a float" do + @object.send(@method, 0.5).should be_an_instance_of(Float) + end + + it "returns the inverse hyperbolic tangent of the argument" do + @object.send(@method, 0.0).should == 0.0 + @object.send(@method, -0.0).should == -0.0 + @object.send(@method, 0.5).should be_close(0.549306144334055, TOLERANCE) + @object.send(@method, -0.2).should be_close(-0.202732554054082, TOLERANCE) + end + + it "raises a TypeError if the argument is nil" do + -> { @object.send(@method, nil) }.should raise_error(TypeError) + end + + it "raises a TypeError if the argument is not a Numeric" do + -> { @object.send(@method, "test") }.should raise_error(TypeError) + end + + it "returns Infinity if x == 1.0" do + @object.send(@method, 1.0).should == Float::INFINITY + end + + it "return -Infinity if x == -1.0" do + @object.send(@method, -1.0).should == -Float::INFINITY + end +end + +describe :math_atanh_private, shared: true do + it "is a private instance method" do + Math.should have_private_instance_method(@method) + end +end + +describe :math_atanh_no_complex, shared: true do + it "raises a Math::DomainError for arguments greater than 1.0" do + -> { @object.send(@method, 1.0 + Float::EPSILON) }.should raise_error(Math::DomainError) + end + + it "raises a Math::DomainError for arguments less than -1.0" do + -> { @object.send(@method, -1.0 - Float::EPSILON) }.should raise_error(Math::DomainError) + end +end diff --git a/spec/ruby/core/method/clone_spec.rb b/spec/ruby/core/method/clone_spec.rb index 3fe4000fb7..b0eb5751a9 100644 --- a/spec/ruby/core/method/clone_spec.rb +++ b/spec/ruby/core/method/clone_spec.rb @@ -1,14 +1,13 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' +require_relative 'shared/dup' describe "Method#clone" do - it "returns a copy of the method" do - m1 = MethodSpecs::Methods.new.method(:foo) - m2 = m1.clone + it_behaves_like :method_dup, :clone - m1.should == m2 - m1.should_not equal(m2) - - m1.call.should == m2.call + it "preserves frozen status" do + method = Object.new.method(:method) + method.freeze + method.frozen?.should == true + method.clone.frozen?.should == true end end diff --git a/spec/ruby/core/method/dup_spec.rb b/spec/ruby/core/method/dup_spec.rb new file mode 100644 index 0000000000..e3e29d8a68 --- /dev/null +++ b/spec/ruby/core/method/dup_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'shared/dup' + +describe "Method#dup" do + ruby_version_is "3.4" do + it_behaves_like :method_dup, :dup + + it "resets frozen status" do + method = Object.new.method(:method) + method.freeze + method.frozen?.should == true + method.dup.frozen?.should == false + end + end +end diff --git a/spec/ruby/core/method/owner_spec.rb b/spec/ruby/core/method/owner_spec.rb index 05422f1697..1cdc4edfa7 100644 --- a/spec/ruby/core/method/owner_spec.rb +++ b/spec/ruby/core/method/owner_spec.rb @@ -24,9 +24,7 @@ describe "Method#owner" do end end - ruby_version_is "3.2" do - it "returns the class on which public was called for a private method in ancestor" do - MethodSpecs::InheritedMethods::C.new.method(:derp).owner.should == MethodSpecs::InheritedMethods::C - end + it "returns the class on which public was called for a private method in ancestor" do + MethodSpecs::InheritedMethods::C.new.method(:derp).owner.should == MethodSpecs::InheritedMethods::C end end diff --git a/spec/ruby/core/method/parameters_spec.rb b/spec/ruby/core/method/parameters_spec.rb index 0f730fe013..f1c2523cf0 100644 --- a/spec/ruby/core/method/parameters_spec.rb +++ b/spec/ruby/core/method/parameters_spec.rb @@ -7,6 +7,7 @@ describe "Method#parameters" do def one_keyrest(**a); end def one_keyreq(a:); end + def one_nokey(**nil); end def one_splat_one_req(*a,b); end def one_splat_two_req(*a,b,c); end @@ -15,11 +16,14 @@ describe "Method#parameters" do def one_opt_with_stabby(a=-> b { true }); end def one_unnamed_splat(*); end + def one_unnamed_keyrest(**); end def one_splat_one_block(*args, &block) local_is_not_parameter = {} end + def forward_parameters(...) end + def underscore_parameters(_, _, _ = 1, *_, _:, _: 2, **_, &_); end define_method(:one_optional_defined_method) {|x = 1|} @@ -178,6 +182,11 @@ describe "Method#parameters" do m.parameters.should == [[:keyreq,:a]] end + it "returns [[:nokey]] for a method with a single **nil parameter" do + m = MethodSpecs::Methods.instance_method(:one_nokey) + m.parameters.should == [[:nokey]] + end + it "works with ->(){} as the value of an optional argument" do m = MethodSpecs::Methods.instance_method(:one_opt_with_stabby) m.parameters.should == [[:opt,:a]] @@ -224,18 +233,27 @@ describe "Method#parameters" do m.method(:handled_via_method_missing).parameters.should == [[:rest]] end - ruby_version_is '3.2' do - it "adds * rest arg for \"star\" argument" do - m = MethodSpecs::Methods.new - m.method(:one_unnamed_splat).parameters.should == [[:rest, :*]] - end + it "adds rest arg with name * for \"star\" argument" do + m = MethodSpecs::Methods.new + m.method(:one_unnamed_splat).parameters.should == [[:rest, :*]] + end + + it "adds keyrest arg with ** as a name for \"double star\" argument" do + m = MethodSpecs::Methods.new + m.method(:one_unnamed_keyrest).parameters.should == [[:keyrest, :**]] end - ruby_version_is ''...'3.2' do - it "adds nameless rest arg for \"star\" argument" do - m = MethodSpecs::Methods.new - m.method(:one_unnamed_splat).parameters.should == [[:rest]] + it "adds block arg with name & for anonymous block argument" do + object = Object.new + def object.foo(&) end + + object.method(:foo).parameters.should == [[:block, :&]] + end + + it "returns [:rest, :*], [:keyrest, :**], [:block, :&] for forward parameters operator" do + m = MethodSpecs::Methods.new + m.method(:forward_parameters).parameters.should == [[:rest, :*], [:keyrest, :**], [:block, :&]] end it "returns the args and block for a splat and block argument" do diff --git a/spec/ruby/core/method/private_spec.rb b/spec/ruby/core/method/private_spec.rb index fd550036a3..e708542b2e 100644 --- a/spec/ruby/core/method/private_spec.rb +++ b/spec/ruby/core/method/private_spec.rb @@ -2,27 +2,8 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Method#private?" do - ruby_version_is "3.1"..."3.2" do - it "returns false when the method is public" do - obj = MethodSpecs::Methods.new - obj.method(:my_public_method).private?.should == false - end - - it "returns false when the method is protected" do - obj = MethodSpecs::Methods.new - obj.method(:my_protected_method).private?.should == false - end - - it "returns true when the method is private" do - obj = MethodSpecs::Methods.new - obj.method(:my_private_method).private?.should == true - end - end - - ruby_version_is "3.2" do - it "has been removed" do - obj = MethodSpecs::Methods.new - obj.method(:my_private_method).should_not.respond_to?(:private?) - end + it "has been removed" do + obj = MethodSpecs::Methods.new + obj.method(:my_private_method).should_not.respond_to?(:private?) end end diff --git a/spec/ruby/core/method/protected_spec.rb b/spec/ruby/core/method/protected_spec.rb index 8423e8c64c..f9e422ae3d 100644 --- a/spec/ruby/core/method/protected_spec.rb +++ b/spec/ruby/core/method/protected_spec.rb @@ -2,27 +2,8 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Method#protected?" do - ruby_version_is "3.1"..."3.2" do - it "returns false when the method is public" do - obj = MethodSpecs::Methods.new - obj.method(:my_public_method).protected?.should == false - end - - it "returns true when the method is protected" do - obj = MethodSpecs::Methods.new - obj.method(:my_protected_method).protected?.should == true - end - - it "returns false when the method is private" do - obj = MethodSpecs::Methods.new - obj.method(:my_private_method).protected?.should == false - end - end - - ruby_version_is "3.2" do - it "has been removed" do - obj = MethodSpecs::Methods.new - obj.method(:my_protected_method).should_not.respond_to?(:protected?) - end + it "has been removed" do + obj = MethodSpecs::Methods.new + obj.method(:my_protected_method).should_not.respond_to?(:protected?) end end diff --git a/spec/ruby/core/method/public_spec.rb b/spec/ruby/core/method/public_spec.rb index 20d0081a27..4cb23f4cf1 100644 --- a/spec/ruby/core/method/public_spec.rb +++ b/spec/ruby/core/method/public_spec.rb @@ -2,27 +2,8 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Method#public?" do - ruby_version_is "3.1"..."3.2" do - it "returns true when the method is public" do - obj = MethodSpecs::Methods.new - obj.method(:my_public_method).public?.should == true - end - - it "returns false when the method is protected" do - obj = MethodSpecs::Methods.new - obj.method(:my_protected_method).public?.should == false - end - - it "returns false when the method is private" do - obj = MethodSpecs::Methods.new - obj.method(:my_private_method).public?.should == false - end - end - - ruby_version_is "3.2" do - it "has been removed" do - obj = MethodSpecs::Methods.new - obj.method(:my_public_method).should_not.respond_to?(:public?) - end + it "has been removed" do + obj = MethodSpecs::Methods.new + obj.method(:my_public_method).should_not.respond_to?(:public?) end end diff --git a/spec/ruby/core/method/shared/dup.rb b/spec/ruby/core/method/shared/dup.rb new file mode 100644 index 0000000000..c74847083f --- /dev/null +++ b/spec/ruby/core/method/shared/dup.rb @@ -0,0 +1,32 @@ +describe :method_dup, shared: true do + it "returns a copy of self" do + a = Object.new.method(:method) + b = a.send(@method) + + a.should == b + a.should_not equal(b) + end + + ruby_version_is "3.4" do + it "copies instance variables" do + method = Object.new.method(:method) + method.instance_variable_set(:@ivar, 1) + cl = method.send(@method) + cl.instance_variables.should == [:@ivar] + end + + it "copies the finalizer" do + code = <<-'RUBY' + obj = Object.new.method(:method) + + ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) + + obj.clone + + exit 0 + RUBY + + ruby_exe(code).lines.sort.should == ["finalized\n", "finalized\n"] + end + end +end diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb index 4cfb21c5d0..87413a2ab6 100644 --- a/spec/ruby/core/method/source_location_spec.rb +++ b/spec/ruby/core/method/source_location_spec.rb @@ -11,23 +11,23 @@ describe "Method#source_location" do end it "sets the first value to the path of the file in which the method was defined" do - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) - file.should == File.realpath('../fixtures/classes.rb', __FILE__) + file.should == File.realpath('fixtures/classes.rb', __dir__) end it "sets the last value to an Integer representing the line on which the method was defined" do - line = @method.source_location.last + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 5 end it "returns the last place the method was defined" do - MethodSpecs::SourceLocation.method(:redefined).source_location.last.should == 13 + MethodSpecs::SourceLocation.method(:redefined).source_location[1].should == 13 end it "returns the location of the original method even if it was aliased" do - MethodSpecs::SourceLocation.new.method(:aka).source_location.last.should == 17 + MethodSpecs::SourceLocation.new.method(:aka).source_location[1].should == 17 end it "works for methods defined with a block" do @@ -108,7 +108,13 @@ describe "Method#source_location" do c = Class.new do eval('def self.m; end', nil, "foo", 100) end - c.method(:m).source_location.should == ["foo", 100] + location = c.method(:m).source_location + ruby_version_is(""..."4.1") do + location.should == ["foo", 100] + end + ruby_version_is("4.1") do + location.should == ["foo", 100, 0, 100, 15] + end end describe "for a Method generated by respond_to_missing?" do diff --git a/spec/ruby/core/method/super_method_spec.rb b/spec/ruby/core/method/super_method_spec.rb index f9a18f3878..c63a7aaa0f 100644 --- a/spec/ruby/core/method/super_method_spec.rb +++ b/spec/ruby/core/method/super_method_spec.rb @@ -55,12 +55,10 @@ describe "Method#super_method" do end end - ruby_version_is "2.7.3" do - context "after aliasing an inherited method" do - it "returns the expected super_method" do - method = MethodSpecs::InheritedMethods::C.new.method(:meow) - method.super_method.owner.should == MethodSpecs::InheritedMethods::A - end + context "after aliasing an inherited method" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.new.method(:meow) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A end end end diff --git a/spec/ruby/core/method/to_proc_spec.rb b/spec/ruby/core/method/to_proc_spec.rb index 29b7bec2b3..4993cce239 100644 --- a/spec/ruby/core/method/to_proc_spec.rb +++ b/spec/ruby/core/method/to_proc_spec.rb @@ -35,7 +35,7 @@ describe "Method#to_proc" do end it "returns a proc that can be used by define_method" do - x = 'test' + x = +'test' to_s = class << x define_method :foo, method(:to_s).to_proc to_s diff --git a/spec/ruby/core/method/unbind_spec.rb b/spec/ruby/core/method/unbind_spec.rb index bdedd513ce..0b630e4d88 100644 --- a/spec/ruby/core/method/unbind_spec.rb +++ b/spec/ruby/core/method/unbind_spec.rb @@ -27,16 +27,8 @@ describe "Method#unbind" do @string.should =~ /MethodSpecs::MyMod/ end - ruby_version_is ""..."3.2" do - it "returns a String containing the Module the method is referenced from" do - @string.should =~ /MethodSpecs::MySub/ - end - end - - ruby_version_is "3.2" do - it "returns a String containing the Module the method is referenced from" do - @string.should =~ /MethodSpecs::MyMod/ - end + it "returns a String containing the Module the method is referenced from" do + @string.should =~ /MethodSpecs::MyMod/ end end diff --git a/spec/ruby/core/module/ancestors_spec.rb b/spec/ruby/core/module/ancestors_spec.rb index 5e4c196206..90c26941d1 100644 --- a/spec/ruby/core/module/ancestors_spec.rb +++ b/spec/ruby/core/module/ancestors_spec.rb @@ -7,10 +7,17 @@ describe "Module#ancestors" do ModuleSpecs.ancestors.should == [ModuleSpecs] ModuleSpecs::Basic.ancestors.should == [ModuleSpecs::Basic] ModuleSpecs::Super.ancestors.should == [ModuleSpecs::Super, ModuleSpecs::Basic] - ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should == - [ModuleSpecs::Parent, Object, Kernel, BasicObject] - ModuleSpecs.without_test_modules(ModuleSpecs::Child.ancestors).should == - [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Kernel, BasicObject] + if defined?(Ruby::Box) && Ruby::Box.enabled? + ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should == + [ModuleSpecs::Parent, Object, Ruby::Box::Loader, Kernel, BasicObject] + ModuleSpecs.without_test_modules(ModuleSpecs::Child.ancestors).should == + [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Ruby::Box::Loader, Kernel, BasicObject] + else + ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should == + [ModuleSpecs::Parent, Object, Kernel, BasicObject] + ModuleSpecs.without_test_modules(ModuleSpecs::Child.ancestors).should == + [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Kernel, BasicObject] + end end it "returns only modules and classes" do @@ -21,6 +28,17 @@ describe "Module#ancestors" do ModuleSpecs::Parent.ancestors.should == ModuleSpecs::Parent.ancestors.uniq end + it "returns a module that is included later into a nested module as well" do + m1 = Module.new + m2 = Module.new + m3 = Module.new do + include m2 + end + m2.include m1 # should be after m3 includes m2 + + m3.ancestors.should == [m3, m2, m1] + end + describe "when called on a singleton class" do it "includes the singleton classes of ancestors" do parent = Class.new diff --git a/spec/ruby/core/module/attr_accessor_spec.rb b/spec/ruby/core/module/attr_accessor_spec.rb index eea5b4b64b..503dccc61e 100644 --- a/spec/ruby/core/module/attr_accessor_spec.rb +++ b/spec/ruby/core/module/attr_accessor_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_accessor" do it "creates a getter and setter for each given attribute name" do @@ -106,4 +107,6 @@ describe "Module#attr_accessor" do 1.foobar.should be_nil end end + + it_behaves_like :module_attr_added, :attr_accessor end diff --git a/spec/ruby/core/module/attr_reader_spec.rb b/spec/ruby/core/module/attr_reader_spec.rb index 0b6d996719..37fd537ff5 100644 --- a/spec/ruby/core/module/attr_reader_spec.rb +++ b/spec/ruby/core/module/attr_reader_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_reader" do it "creates a getter for each given attribute name" do @@ -67,4 +68,6 @@ describe "Module#attr_reader" do (attr_reader :foo, 'bar').should == [:foo, :bar] end end + + it_behaves_like :module_attr_added, :attr_reader end diff --git a/spec/ruby/core/module/attr_spec.rb b/spec/ruby/core/module/attr_spec.rb index 72a6646b1e..2f9f4e26dc 100644 --- a/spec/ruby/core/module/attr_spec.rb +++ b/spec/ruby/core/module/attr_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr" do before :each do @@ -153,4 +154,6 @@ describe "Module#attr" do (attr :qux, true).should == [:qux, :qux=] end end + + it_behaves_like :module_attr_added, :attr end diff --git a/spec/ruby/core/module/attr_writer_spec.rb b/spec/ruby/core/module/attr_writer_spec.rb index aaea0ce43f..5b863ef88c 100644 --- a/spec/ruby/core/module/attr_writer_spec.rb +++ b/spec/ruby/core/module/attr_writer_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_writer" do it "creates a setter for each given attribute name" do @@ -77,4 +78,6 @@ describe "Module#attr_writer" do (attr_writer :foo, 'bar').should == [:foo=, :bar=] end end + + it_behaves_like :module_attr_added, :attr_writer end diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb index af04ab26c8..625d945686 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -1,7 +1,6 @@ require_relative '../../spec_helper' require_relative '../../fixtures/code_loading' require_relative 'fixtures/classes' -require 'thread' describe "Module#autoload?" do it "returns the name of the file that will be autoloaded" do @@ -343,6 +342,29 @@ describe "Module#autoload" do end end + def check_before_during_thread_after(const, &check) + before = check.call + to_autoload_thread, from_autoload_thread = Queue.new, Queue.new + ScratchPad.record -> { + from_autoload_thread.push check.call + to_autoload_thread.pop + } + t = Thread.new { + in_loading_thread = from_autoload_thread.pop + in_other_thread = check.call + to_autoload_thread.push :done + [in_loading_thread, in_other_thread] + } + in_loading_thread, in_other_thread = nil + begin + ModuleSpecs::Autoload.const_get(const) + ensure + in_loading_thread, in_other_thread = t.value + end + after = check.call + [before, in_loading_thread, in_other_thread, after] + end + describe "during the autoload before the constant is assigned" do before :each do @path = fixture(__FILE__, "autoload_during_autoload.rb") @@ -351,58 +373,83 @@ describe "Module#autoload" do raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoload) == @path end - def check_before_during_thread_after(&check) - before = check.call - to_autoload_thread, from_autoload_thread = Queue.new, Queue.new - ScratchPad.record -> { - from_autoload_thread.push check.call - to_autoload_thread.pop - } - t = Thread.new { - in_loading_thread = from_autoload_thread.pop - in_other_thread = check.call - to_autoload_thread.push :done - [in_loading_thread, in_other_thread] - } - in_loading_thread, in_other_thread = nil - begin - ModuleSpecs::Autoload::DuringAutoload - ensure - in_loading_thread, in_other_thread = t.value - end - after = check.call - [before, in_loading_thread, in_other_thread, after] - end - it "returns nil in autoload thread and 'constant' otherwise for defined?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { defined?(ModuleSpecs::Autoload::DuringAutoload) } results.should == ['constant', nil, 'constant', 'constant'] end it "keeps the constant in Module#constants" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.constants(false).include?(:DuringAutoload) } results.should == [true, true, true, true] end it "returns false in autoload thread and true otherwise for Module#const_defined?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.const_defined?(:DuringAutoload, false) } results.should == [true, false, true, true] end it "returns nil in autoload thread and returns the path in other threads for Module#autoload?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.autoload?(:DuringAutoload) } results.should == [@path, nil, @path, nil] end end + describe "during the autoload after the constant is assigned" do + before :each do + @path = fixture(__FILE__, "autoload_during_autoload_after_define.rb") + ModuleSpecs::Autoload.autoload :DuringAutoloadAfterDefine, @path + @autoload_location = [__FILE__, __LINE__ - 1] + @const_location = [@path, 2] + @remove << :DuringAutoloadAfterDefine + raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) == @path + end + + it "returns 'constant' in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + defined?(ModuleSpecs::Autoload::DuringAutoloadAfterDefine) + } + results.should == ['constant', 'constant', 'constant', 'constant'] + end + + it "Module#constants include the autoloaded in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.constants(false).include?(:DuringAutoloadAfterDefine) + } + results.should == [true, true, true, true] + end + + it "Module#const_defined? returns true in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_defined?(:DuringAutoloadAfterDefine, false) + } + results.should == [true, true, true, true] + end + + it "returns nil in autoload thread and returns the path in other threads for Module#autoload?" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) + } + results.should == [@path, nil, @path, nil] + end + + ruby_bug("#20188", ""..."3.4") do + it "returns the real constant location in autoload thread and returns the autoload location in other threads for Module#const_source_location" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_source_location(:DuringAutoloadAfterDefine) + } + results.should == [@autoload_location, @const_location, @autoload_location, @const_location] + end + end + end + it "does not remove the constant from Module#constants if load fails and keeps it as an autoload" do ModuleSpecs::Autoload.autoload :Fail, @non_existent @@ -439,42 +486,21 @@ describe "Module#autoload" do ScratchPad.recorded.should == [:raise, :raise] end - ruby_version_is "3.1" do - it "removes the constant from Module#constants if the loaded file does not define it" do - path = fixture(__FILE__, "autoload_o.rb") - ScratchPad.record [] - ModuleSpecs::Autoload.autoload :O, path - - ModuleSpecs::Autoload.const_defined?(:O).should == true - ModuleSpecs::Autoload.should have_constant(:O) - ModuleSpecs::Autoload.autoload?(:O).should == path - - -> { ModuleSpecs::Autoload::O }.should raise_error(NameError) - - ModuleSpecs::Autoload.const_defined?(:O).should == false - ModuleSpecs::Autoload.should_not have_constant(:O) - ModuleSpecs::Autoload.autoload?(:O).should == nil - -> { ModuleSpecs::Autoload.const_get(:O) }.should raise_error(NameError) - end - end - - ruby_version_is ""..."3.1" do - it "does not remove the constant from Module#constants if the loaded file does not define it, but leaves it as 'undefined'" do - path = fixture(__FILE__, "autoload_o.rb") - ScratchPad.record [] - ModuleSpecs::Autoload.autoload :O, path + it "removes the constant from Module#constants if the loaded file does not define it" do + path = fixture(__FILE__, "autoload_o.rb") + ScratchPad.record [] + ModuleSpecs::Autoload.autoload :O, path - ModuleSpecs::Autoload.const_defined?(:O).should == true - ModuleSpecs::Autoload.should have_constant(:O) - ModuleSpecs::Autoload.autoload?(:O).should == path + ModuleSpecs::Autoload.const_defined?(:O).should == true + ModuleSpecs::Autoload.should have_constant(:O) + ModuleSpecs::Autoload.autoload?(:O).should == path - -> { ModuleSpecs::Autoload::O }.should raise_error(NameError) + -> { ModuleSpecs::Autoload::O }.should raise_error(NameError) - ModuleSpecs::Autoload.const_defined?(:O).should == false - ModuleSpecs::Autoload.should have_constant(:O) - ModuleSpecs::Autoload.autoload?(:O).should == nil - -> { ModuleSpecs::Autoload.const_get(:O) }.should raise_error(NameError) - end + ModuleSpecs::Autoload.const_defined?(:O).should == false + ModuleSpecs::Autoload.should_not have_constant(:O) + ModuleSpecs::Autoload.autoload?(:O).should == nil + -> { ModuleSpecs::Autoload.const_get(:O) }.should raise_error(NameError) end it "does not try to load the file again if the loaded file did not define the constant" do @@ -577,78 +603,51 @@ describe "Module#autoload" do end end - ruby_version_is "3.2" do - it "warns once in verbose mode if the constant was defined in a parent scope" do - ScratchPad.record -> { - ModuleSpecs::DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent - } + it "warns once in verbose mode if the constant was defined in a parent scope" do + ScratchPad.record -> { + ModuleSpecs::DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent + } - module ModuleSpecs - module Autoload - autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") - self.autoload?(:DeclaredInCurrentDefinedInParent).should == fixture(__FILE__, "autoload_callback.rb") - const_defined?(:DeclaredInCurrentDefinedInParent).should == true - - -> { - DeclaredInCurrentDefinedInParent - }.should complain( - /Expected .*autoload_callback.rb to define ModuleSpecs::Autoload::DeclaredInCurrentDefinedInParent but it didn't/, - verbose: true, - ) - - -> { - DeclaredInCurrentDefinedInParent - }.should_not complain(/.*/, verbose: true) - self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil - const_defined?(:DeclaredInCurrentDefinedInParent).should == false - ModuleSpecs.const_defined?(:DeclaredInCurrentDefinedInParent).should == true - end + module ModuleSpecs + module Autoload + autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") + self.autoload?(:DeclaredInCurrentDefinedInParent).should == fixture(__FILE__, "autoload_callback.rb") + const_defined?(:DeclaredInCurrentDefinedInParent).should == true + + -> { + DeclaredInCurrentDefinedInParent + }.should complain( + /Expected .*autoload_callback.rb to define ModuleSpecs::Autoload::DeclaredInCurrentDefinedInParent but it didn't/, + verbose: true, + ) + + -> { + DeclaredInCurrentDefinedInParent + }.should_not complain(/.*/, verbose: true) + self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil + const_defined?(:DeclaredInCurrentDefinedInParent).should == false + ModuleSpecs.const_defined?(:DeclaredInCurrentDefinedInParent).should == true end end end - ruby_version_is "3.1" do - it "looks up in parent scope after failed autoload" do - @remove << :DeclaredInCurrentDefinedInParent - module ModuleSpecs::Autoload - ScratchPad.record -> { - DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent - } - - class LexicalScope - autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") - -> { DeclaredInCurrentDefinedInParent }.should_not raise_error(NameError) - # Basically, the autoload constant remains in a "undefined" state - self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil - const_defined?(:DeclaredInCurrentDefinedInParent).should == false - -> { const_get(:DeclaredInCurrentDefinedInParent) }.should raise_error(NameError) - end + it "looks up in parent scope after failed autoload" do + @remove << :DeclaredInCurrentDefinedInParent + module ModuleSpecs::Autoload + ScratchPad.record -> { + DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent + } - DeclaredInCurrentDefinedInParent.should == :declared_in_current_defined_in_parent + class LexicalScope + autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") + -> { DeclaredInCurrentDefinedInParent }.should_not raise_error(NameError) + # Basically, the autoload constant remains in a "undefined" state + self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil + const_defined?(:DeclaredInCurrentDefinedInParent).should == false + -> { const_get(:DeclaredInCurrentDefinedInParent) }.should raise_error(NameError) end - end - end - - ruby_version_is ""..."3.1" do - it "and fails when finding the undefined autoload constant in the current scope when declared in current and defined in parent" do - @remove << :DeclaredInCurrentDefinedInParent - module ModuleSpecs::Autoload - ScratchPad.record -> { - DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent - } - class LexicalScope - autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") - -> { DeclaredInCurrentDefinedInParent }.should raise_error(NameError) - # Basically, the autoload constant remains in a "undefined" state - self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil - const_defined?(:DeclaredInCurrentDefinedInParent).should == false - self.should have_constant(:DeclaredInCurrentDefinedInParent) - -> { const_get(:DeclaredInCurrentDefinedInParent) }.should raise_error(NameError) - end - - DeclaredInCurrentDefinedInParent.should == :declared_in_current_defined_in_parent - end + DeclaredInCurrentDefinedInParent.should == :declared_in_current_defined_in_parent end end @@ -719,6 +718,21 @@ describe "Module#autoload" do end end + it "should trigger the autoload when using `private_constant`" do + @remove << :DynClass + module ModuleSpecs::Autoload + autoload :DynClass, fixture(__FILE__, "autoload_c.rb") + private_constant :DynClass + + ScratchPad.recorded.should be_nil + + DynClass::C.new.loaded.should == :dynclass_c + ScratchPad.recorded.should == :loaded + end + + -> { ModuleSpecs::Autoload::DynClass }.should raise_error(NameError, /private constant/) + end + # [ruby-core:19127] [ruby-core:29941] it "does NOT raise a NameError when the autoload file did not define the constant and a module is opened with the same name" do module ModuleSpecs::Autoload diff --git a/spec/ruby/core/module/const_added_spec.rb b/spec/ruby/core/module/const_added_spec.rb index 31ac6eb105..90cd36551a 100644 --- a/spec/ruby/core/module/const_added_spec.rb +++ b/spec/ruby/core/module/const_added_spec.rb @@ -1,125 +1,238 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'fixtures/const_added' describe "Module#const_added" do - ruby_version_is "3.2" do - it "is a private instance method" do - Module.should have_private_instance_method(:const_added) + it "is a private instance method" do + Module.should have_private_instance_method(:const_added) + end + + it "returns nil in the default implementation" do + Module.new do + const_added(:TEST).should == nil + end + end + + it "for a class defined with the `class` keyword, const_added runs before inherited" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(_) + ScratchPad << :const_added + end end - it "returns nil in the default implementation" do - Module.new do - const_added(:TEST).should == nil + parent = Class.new do + def self.inherited(_) + ScratchPad << :inherited end end - it "is called when a new constant is assigned on self" do - ScratchPad.record [] + class mod::C < parent; end - mod = Module.new do - def self.const_added(name) - ScratchPad << name - end + ScratchPad.recorded.should == [:const_added, :inherited] + end + + it "the superclass of a class assigned to a constant is set before const_added is called" do + ScratchPad.record [] + + parent = Class.new do + def self.const_added(name) + ScratchPad << name + ScratchPad << const_get(name).superclass end + end - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - TEST = 1 - RUBY + class parent::C < parent; end + + ScratchPad.recorded.should == [:C, parent] + end - ScratchPad.recorded.should == [:TEST] + it "is called when a new constant is assigned on self" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end end - it "is called when a new constant is assigned on self through const_set" do - ScratchPad.record [] + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 1 + RUBY - mod = Module.new do - def self.const_added(name) - ScratchPad << name - end + ScratchPad.recorded.should == [:TEST] + end + + it "is called when a new constant is assigned on self through const_set" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name end + end + + mod.const_set(:TEST, 1) + + ScratchPad.recorded.should == [:TEST] + end - mod.const_set(:TEST, 1) + it "is called when a new module is defined under self" do + ScratchPad.record [] - ScratchPad.recorded.should == [:TEST] + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end end - it "is called when a new module is defined under self" do - ScratchPad.record [] + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + module SubModule + end + + module SubModule + end + RUBY - mod = Module.new do + ScratchPad.recorded.should == [:SubModule] + end + + it "is called when a new module is defined under a named module (assigned to a constant)" do + ScratchPad.record [] + + ModuleSpecs::ConstAddedSpecs::NamedModule = Module.new do + def self.const_added(name) + ScratchPad << name + end + + module self::A def self.const_added(name) ScratchPad << name end - end - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - module SubModule + module self::B end + end + end - module SubModule - end - RUBY + ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModule + end - ScratchPad.recorded.should == [:SubModule] + it "is called when a new class is defined under self" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end end - it "is called when a new class is defined under self" do - ScratchPad.record [] + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + class SubClass + end + + class SubClass + end + RUBY + + ScratchPad.recorded.should == [:SubClass] + end + + it "is called when a new class is defined under a named module (assigned to a constant)" do + ScratchPad.record [] + + ModuleSpecs::ConstAddedSpecs::NamedModuleB = Module.new do + def self.const_added(name) + ScratchPad << name + end - mod = Module.new do + class self::A def self.const_added(name) ScratchPad << name end - end - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - class SubClass + class self::B end + end + end - class SubClass - end - RUBY + ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModuleB + end + + it "is called when an autoload is defined" do + ScratchPad.record [] - ScratchPad.recorded.should == [:SubClass] + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end end - it "is called when an autoload is defined" do - ScratchPad.record [] + mod.autoload :Autoload, "foo" + ScratchPad.recorded.should == [:Autoload] + end - mod = Module.new do - def self.const_added(name) - ScratchPad << name - end - end + it "is called with a precise caller location with the line of definition" do + ScratchPad.record [] - mod.autoload :Autoload, "foo" - ScratchPad.recorded.should == [:Autoload] + mod = Module.new do + def self.const_added(name) + location = caller_locations(1, 1)[0] + ScratchPad << location.lineno + end end - it "is called with a precise caller location with the line of definition" do - ScratchPad.record [] + line = __LINE__ + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 1 - mod = Module.new do - def self.const_added(name) - location = caller_locations(1, 1)[0] - ScratchPad << location.lineno - end + module SubModule end - line = __LINE__ - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - TEST = 1 + class SubClass + end + RUBY - module SubModule - end + mod.const_set(:CONST_SET, 1) - class SubClass - end - RUBY + ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11] + end + + it "is called when the constant is already assigned a value" do + ScratchPad.record [] - mod.const_set(:CONST_SET, 1) + mod = Module.new do + def self.const_added(name) + ScratchPad.record const_get(name) + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 123 + RUBY + + ScratchPad.recorded.should == 123 + end + + it "records re-definition of existing constants" do + ScratchPad.record [] - ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11] + mod = Module.new do + def self.const_added(name) + ScratchPad << const_get(name) + end end + + -> { + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 123 + TEST = 456 + RUBY + }.should complain(/warning: already initialized constant .+::TEST/) + + ScratchPad.recorded.should == [123, 456] end end diff --git a/spec/ruby/core/module/const_defined_spec.rb b/spec/ruby/core/module/const_defined_spec.rb index 027cf5a245..8b137cd134 100644 --- a/spec/ruby/core/module/const_defined_spec.rb +++ b/spec/ruby/core/module/const_defined_spec.rb @@ -65,6 +65,8 @@ describe "Module#const_defined?" do str = "CS_CONSTλ".encode("euc-jp") ConstantSpecs.const_set str, 1 ConstantSpecs.const_defined?(str).should be_true + ensure + ConstantSpecs.send(:remove_const, str) end it "returns false if the constant is not defined in the receiver, its superclass, or any included modules" do diff --git a/spec/ruby/core/module/const_get_spec.rb b/spec/ruby/core/module/const_get_spec.rb index 0233118f4b..4b53cbe7b3 100644 --- a/spec/ruby/core/module/const_get_spec.rb +++ b/spec/ruby/core/module/const_get_spec.rb @@ -131,7 +131,7 @@ describe "Module#const_get" do end it "does read private constants" do - ConstantSpecs.const_get(:CS_PRIVATE).should == :cs_private + ConstantSpecs.const_get(:CS_PRIVATE).should == :cs_private end it 'does autoload a constant' do @@ -202,40 +202,60 @@ describe "Module#const_get" do ConstantSpecs::ContainerA::ChildA::CS_CONST301 = :const301_5 ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST301).should == :const301_5 + ensure + ConstantSpecs::ClassA.send(:remove_const, :CS_CONST301) + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST301) + ConstantSpecs::ParentA.send(:remove_const, :CS_CONST301) + ConstantSpecs::ContainerA::ChildA.send(:remove_const, :CS_CONST301) end it "searches a module included in the immediate class before the superclass" do ConstantSpecs::ParentB::CS_CONST302 = :const302_1 ConstantSpecs::ModuleF::CS_CONST302 = :const302_2 ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST302).should == :const302_2 + ensure + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST302) + ConstantSpecs::ModuleF.send(:remove_const, :CS_CONST302) end it "searches the superclass before a module included in the superclass" do ConstantSpecs::ModuleE::CS_CONST303 = :const303_1 ConstantSpecs::ParentB::CS_CONST303 = :const303_2 ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST303).should == :const303_2 + ensure + ConstantSpecs::ModuleE.send(:remove_const, :CS_CONST303) + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST303) end it "searches a module included in the superclass" do ConstantSpecs::ModuleA::CS_CONST304 = :const304_1 ConstantSpecs::ModuleE::CS_CONST304 = :const304_2 ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST304).should == :const304_2 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST304) + ConstantSpecs::ModuleE.send(:remove_const, :CS_CONST304) end it "searches the superclass chain" do ConstantSpecs::ModuleA::CS_CONST305 = :const305 ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST305).should == :const305 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST305) end it "returns a toplevel constant when the receiver is a Class" do Object::CS_CONST306 = :const306 ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST306).should == :const306 + ensure + Object.send(:remove_const, :CS_CONST306) end it "returns a toplevel constant when the receiver is a Module" do Object::CS_CONST308 = :const308 ConstantSpecs.const_get(:CS_CONST308).should == :const308 ConstantSpecs::ModuleA.const_get(:CS_CONST308).should == :const308 + ensure + Object.send(:remove_const, :CS_CONST308) end it "returns the updated value of a constant" do @@ -246,6 +266,8 @@ describe "Module#const_get" do ConstantSpecs::ClassB::CS_CONST309 = :const309_2 }.should complain(/already initialized constant/) ConstantSpecs::ClassB.const_get(:CS_CONST309).should == :const309_2 + ensure + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST309) end end end diff --git a/spec/ruby/core/module/const_set_spec.rb b/spec/ruby/core/module/const_set_spec.rb index 5bdfd7b68f..823768b882 100644 --- a/spec/ruby/core/module/const_set_spec.rb +++ b/spec/ruby/core/module/const_set_spec.rb @@ -8,16 +8,23 @@ describe "Module#const_set" do ConstantSpecs.const_set "CS_CONST402", :const402 ConstantSpecs.const_get(:CS_CONST402).should == :const402 + ensure + ConstantSpecs.send(:remove_const, :CS_CONST401) + ConstantSpecs.send(:remove_const, :CS_CONST402) end it "returns the value set" do ConstantSpecs.const_set(:CS_CONST403, :const403).should == :const403 + ensure + ConstantSpecs.send(:remove_const, :CS_CONST403) end it "sets the name of an anonymous module" do m = Module.new ConstantSpecs.const_set(:CS_CONST1000, m) m.name.should == "ConstantSpecs::CS_CONST1000" + ensure + ConstantSpecs.send(:remove_const, :CS_CONST1000) end it "sets the name of a module scoped by an anonymous module" do @@ -38,6 +45,8 @@ describe "Module#const_set" do b.name.should == "ModuleSpecs_CS3::B" c.name.should == "ModuleSpecs_CS3::B::C" d.name.should == "ModuleSpecs_CS3::D" + ensure + Object.send(:remove_const, :ModuleSpecs_CS3) end it "raises a NameError if the name does not start with a capital letter" do @@ -55,6 +64,8 @@ describe "Module#const_set" do ConstantSpecs.const_set("CS_CONST404", :const404).should == :const404 -> { ConstantSpecs.const_set "Name=", 1 }.should raise_error(NameError) -> { ConstantSpecs.const_set "Name?", 1 }.should raise_error(NameError) + ensure + ConstantSpecs.send(:remove_const, :CS_CONST404) end it "calls #to_str to convert the given name to a String" do @@ -62,6 +73,8 @@ describe "Module#const_set" do name.should_receive(:to_str).and_return("CS_CONST405") ConstantSpecs.const_set(name, :const405).should == :const405 ConstantSpecs::CS_CONST405.should == :const405 + ensure + ConstantSpecs.send(:remove_const, :CS_CONST405) end it "raises a TypeError if conversion to a String by calling #to_str fails" do diff --git a/spec/ruby/core/module/const_source_location_spec.rb b/spec/ruby/core/module/const_source_location_spec.rb index ded2aa51d7..96649ea10b 100644 --- a/spec/ruby/core/module/const_source_location_spec.rb +++ b/spec/ruby/core/module/const_source_location_spec.rb @@ -19,40 +19,60 @@ describe "Module#const_source_location" do ConstantSpecs::ContainerA::ChildA::CSL_CONST301 = :const301_5 ConstantSpecs::ContainerA::ChildA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ClassA.send(:remove_const, :CSL_CONST301) + ConstantSpecs::ModuleA.send(:remove_const, :CSL_CONST301) + ConstantSpecs::ParentA.send(:remove_const, :CSL_CONST301) + ConstantSpecs::ContainerA::ChildA.send(:remove_const, :CSL_CONST301) end it "searches a path in a module included in the immediate class before the superclass" do ConstantSpecs::ParentB::CSL_CONST302 = :const302_1 ConstantSpecs::ModuleF::CSL_CONST302 = :const302_2 ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST302).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ParentB.send(:remove_const, :CSL_CONST302) + ConstantSpecs::ModuleF.send(:remove_const, :CSL_CONST302) end it "searches a path in the superclass before a module included in the superclass" do ConstantSpecs::ModuleE::CSL_CONST303 = :const303_1 ConstantSpecs::ParentB::CSL_CONST303 = :const303_2 ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST303).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ModuleE.send(:remove_const, :CSL_CONST303) + ConstantSpecs::ParentB.send(:remove_const, :CSL_CONST303) end it "searches a path in a module included in the superclass" do ConstantSpecs::ModuleA::CSL_CONST304 = :const304_1 ConstantSpecs::ModuleE::CSL_CONST304 = :const304_2 ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST304).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CSL_CONST304) + ConstantSpecs::ModuleE.send(:remove_const, :CSL_CONST304) end it "searches a path in the superclass chain" do ConstantSpecs::ModuleA::CSL_CONST305 = :const305 ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST305).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CSL_CONST305) end it "returns path to a toplevel constant when the receiver is a Class" do Object::CSL_CONST306 = :const306 ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST306).should == [__FILE__, __LINE__ - 1] + ensure + Object.send(:remove_const, :CSL_CONST306) end it "returns path to a toplevel constant when the receiver is a Module" do Object::CSL_CONST308 = :const308 ConstantSpecs.const_source_location(:CSL_CONST308).should == [__FILE__, __LINE__ - 1] ConstantSpecs::ModuleA.const_source_location(:CSL_CONST308).should == [__FILE__, __LINE__ - 2] + ensure + Object.send(:remove_const, :CSL_CONST308) end it "returns path to the updated value of a constant" do @@ -63,6 +83,8 @@ describe "Module#const_source_location" do ConstantSpecs::ClassB::CSL_CONST309 = :const309_2 }.should complain(/already initialized constant/) ConstantSpecs::ClassB.const_source_location(:CSL_CONST309).should == [__FILE__, __LINE__ - 2] + ensure + ConstantSpecs::ClassB.send(:remove_const, :CSL_CONST309) end end @@ -207,7 +229,7 @@ describe "Module#const_source_location" do end it "does search private constants path" do - ConstantSpecs.const_source_location(:CS_PRIVATE).should == [@constants_fixture_path, ConstantSpecs::CS_PRIVATE_LINE] + ConstantSpecs.const_source_location(:CS_PRIVATE).should == [@constants_fixture_path, ConstantSpecs::CS_PRIVATE_LINE] end it "works for eval with a given line" do @@ -223,6 +245,14 @@ describe "Module#const_source_location" do @line = __LINE__ - 1 end + before :each do + @loaded_features = $".dup + end + + after :each do + $".replace @loaded_features + end + it 'returns the autoload location while not resolved' do ConstantSpecs.const_source_location('CSL_CONST1').should == [__FILE__, @line] end @@ -233,5 +263,19 @@ describe "Module#const_source_location" do line = ConstantSpecs::CONST_LOCATION ConstantSpecs.const_source_location('CONST_LOCATION').should == [file, line] end + + ruby_bug("#20188", ""..."3.4") do + it 'returns the real constant location as soon as it is defined' do + file = fixture(__FILE__, 'autoload_const_source_location.rb') + ConstantSpecs.autoload :ConstSource, file + autoload_location = [__FILE__, __LINE__ - 1] + + ConstantSpecs.const_source_location(:ConstSource).should == autoload_location + ConstantSpecs::ConstSource::LOCATION.should == ConstantSpecs.const_source_location(:ConstSource) + ConstantSpecs::BEFORE_DEFINE_LOCATION.should == autoload_location + ConstantSpecs.send :remove_const, :ConstSource + ConstantSpecs.send :remove_const, :BEFORE_DEFINE_LOCATION + end + end end end diff --git a/spec/ruby/core/module/define_method_spec.rb b/spec/ruby/core/module/define_method_spec.rb index 381aacadb7..c5dfc53764 100644 --- a/spec/ruby/core/module/define_method_spec.rb +++ b/spec/ruby/core/module/define_method_spec.rb @@ -476,6 +476,9 @@ describe "Module#define_method" do ChildClass = Class.new(ParentClass) { define_method(:foo) { :baz } } ParentClass.send :define_method, :foo, ChildClass.instance_method(:foo) }.should raise_error(TypeError, /bind argument must be a subclass of ChildClass/) + ensure + Object.send(:remove_const, :ParentClass) + Object.send(:remove_const, :ChildClass) end it "raises a TypeError when an UnboundMethod from one class is defined on an unrelated class" do @@ -499,6 +502,33 @@ describe "Module#define_method" do Class.new { define_method :bar, m } }.should raise_error(TypeError, /can't bind singleton method to a different class/) end + + it "defines a new method with public visibility when a Method passed and the class/module of the context isn't equal to the receiver of #define_method" do + c = Class.new do + private def foo + "public" + end + end + + object = c.new + object.singleton_class.define_method(:bar, object.method(:foo)) + + object.bar.should == "public" + end + + it "defines the new method according to the scope visibility when a Method passed and the class/module of the context is equal to the receiver of #define_method" do + c = Class.new do + def foo; end + end + + object = c.new + object.singleton_class.class_eval do + private + define_method(:bar, c.new.method(:foo)) + end + + -> { object.bar }.should raise_error(NoMethodError) + end end describe "Module#define_method" do @@ -697,7 +727,7 @@ describe "Module#define_method" do end end -describe "Method#define_method when passed a Method object" do +describe "Module#define_method when passed a Method object" do before :each do @klass = Class.new do def m(a, b, *c) @@ -722,7 +752,7 @@ describe "Method#define_method when passed a Method object" do end end -describe "Method#define_method when passed an UnboundMethod object" do +describe "Module#define_method when passed an UnboundMethod object" do before :each do @klass = Class.new do def m(a, b, *c) @@ -747,7 +777,7 @@ describe "Method#define_method when passed an UnboundMethod object" do end end -describe "Method#define_method when passed a Proc object" do +describe "Module#define_method when passed a Proc object" do describe "and a method is defined inside" do it "defines the nested method in the default definee where the Proc was created" do prc = nil @@ -772,7 +802,7 @@ describe "Method#define_method when passed a Proc object" do end end -describe "Method#define_method when passed a block" do +describe "Module#define_method when passed a block" do describe "behaves exactly like a lambda" do it "for return" do Class.new do diff --git a/spec/ruby/core/module/deprecate_constant_spec.rb b/spec/ruby/core/module/deprecate_constant_spec.rb index aabef934c4..ec0de6782f 100644 --- a/spec/ruby/core/module/deprecate_constant_spec.rb +++ b/spec/ruby/core/module/deprecate_constant_spec.rb @@ -44,6 +44,15 @@ describe "Module#deprecate_constant" do end end + ruby_bug '#20900', ''...'3.4' do + describe "when removing the deprecated module" do + it "warns with a message" do + @module.deprecate_constant :PUBLIC1 + -> { @module.module_eval {remove_const :PUBLIC1} }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) + end + end + end + it "accepts multiple symbols and strings as constant names" do @module.deprecate_constant "PUBLIC1", :PUBLIC2 diff --git a/spec/ruby/core/module/fixtures/autoload_const_source_location.rb b/spec/ruby/core/module/fixtures/autoload_const_source_location.rb new file mode 100644 index 0000000000..ee0e5a689f --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_const_source_location.rb @@ -0,0 +1,6 @@ +module ConstantSpecs + BEFORE_DEFINE_LOCATION = const_source_location(:ConstSource) + module ConstSource + LOCATION = Object.const_source_location(name) + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_during_autoload_after_define.rb b/spec/ruby/core/module/fixtures/autoload_during_autoload_after_define.rb new file mode 100644 index 0000000000..a9d886dfd6 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_during_autoload_after_define.rb @@ -0,0 +1,6 @@ +module ModuleSpecs::Autoload + class DuringAutoloadAfterDefine + block = ScratchPad.recorded + ScratchPad.record(block.call) + end +end diff --git a/spec/ruby/core/module/fixtures/classes.rb b/spec/ruby/core/module/fixtures/classes.rb index a434e7b0b8..964f64c593 100644 --- a/spec/ruby/core/module/fixtures/classes.rb +++ b/spec/ruby/core/module/fixtures/classes.rb @@ -1,6 +1,6 @@ module ModuleSpecs def self.without_test_modules(modules) - ignore = %w[MSpecRSpecAdapter PP::ObjectMixin ModuleSpecs::IncludedInObject MainSpecs::Module ConstantSpecs::ModuleA] + ignore = %w[MSpecRSpecAdapter PP::ObjectMixin MainSpecs::Module ConstantSpecs::ModuleA] modules.reject { |k| ignore.include?(k.name) } end diff --git a/spec/ruby/core/module/fixtures/const_added.rb b/spec/ruby/core/module/fixtures/const_added.rb new file mode 100644 index 0000000000..0f5baad65d --- /dev/null +++ b/spec/ruby/core/module/fixtures/const_added.rb @@ -0,0 +1,4 @@ +module ModuleSpecs + module ConstAddedSpecs + end +end diff --git a/spec/ruby/core/module/fixtures/name.rb b/spec/ruby/core/module/fixtures/name.rb index fb9e66c309..25c74d3944 100644 --- a/spec/ruby/core/module/fixtures/name.rb +++ b/spec/ruby/core/module/fixtures/name.rb @@ -7,4 +7,7 @@ module ModuleSpecs Cß.name end end + + module NameSpecs + end end diff --git a/spec/ruby/core/module/fixtures/set_temporary_name.rb b/spec/ruby/core/module/fixtures/set_temporary_name.rb new file mode 100644 index 0000000000..901b3b94d1 --- /dev/null +++ b/spec/ruby/core/module/fixtures/set_temporary_name.rb @@ -0,0 +1,4 @@ +module ModuleSpecs + module SetTemporaryNameSpec + end +end diff --git a/spec/ruby/core/module/include_spec.rb b/spec/ruby/core/module/include_spec.rb index c073bc31ca..210918b2e7 100644 --- a/spec/ruby/core/module/include_spec.rb +++ b/spec/ruby/core/module/include_spec.rb @@ -44,7 +44,23 @@ describe "Module#include" do end it "does not raise a TypeError when the argument is an instance of a subclass of Module" do - -> { ModuleSpecs::SubclassSpec.include(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + class ModuleSpecs::SubclassSpec::AClass + end + -> { ModuleSpecs::SubclassSpec::AClass.include(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + ensure + ModuleSpecs::SubclassSpec.send(:remove_const, :AClass) + end + + it "raises a TypeError when the argument is a refinement" do + refinement = nil + + Module.new do + refine String do + refinement = self + end + end + + -> { ModuleSpecs::Basic.include(refinement) }.should raise_error(TypeError, "Cannot include refinement") end it "imports constants to modules and classes" do @@ -399,6 +415,8 @@ describe "Module#include" do M.const_set(:FOO, 'm') B.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdated) end it "updates the constant when a module included after a call is later updated" do @@ -425,6 +443,8 @@ describe "Module#include" do M.const_set(:FOO, 'm') B.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstLaterUpdated) end it "updates the constant when a module included in another module after a call is later updated" do @@ -451,6 +471,8 @@ describe "Module#include" do M.const_set(:FOO, 'm') B.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstModuleLaterUpdated) end it "updates the constant when a nested included module is updated" do @@ -479,6 +501,8 @@ describe "Module#include" do N.const_set(:FOO, 'n') B.foo.should == 'n' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNestedIncludeUpdated) end it "updates the constant when a new module is included" do @@ -503,6 +527,8 @@ describe "Module#include" do B.include(M) B.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNewInclude) end it "updates the constant when a new module with nested module is included" do @@ -531,6 +557,8 @@ describe "Module#include" do B.include M B.foo.should == 'n' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNestedIncluded) end it "overrides a previous super method call" do @@ -553,6 +581,29 @@ describe "Module#include" do c2.include(m) c2.new.foo.should == [:c2, :m1] end + + it "update a module when a nested module is updated and includes a module on its own" do + m1 = Module.new + m2 = Module.new do + def m2; [:m2]; end + end + m3 = Module.new do + def m3; [:m3]; end + end + m4 = Module.new do + def m4; [:m4]; end + end + c = Class.new + + c.include(m1) + m1.include(m2) + m2.include(m3) + m3.include(m4) + + c.new.m2.should == [:m2] + c.new.m3.should == [:m3] + c.new.m4.should == [:m4] + end end describe "Module#include?" do diff --git a/spec/ruby/core/module/instance_method_spec.rb b/spec/ruby/core/module/instance_method_spec.rb index 8d006e647e..182cdf5c54 100644 --- a/spec/ruby/core/module/instance_method_spec.rb +++ b/spec/ruby/core/module/instance_method_spec.rb @@ -48,11 +48,6 @@ describe "Module#instance_method" do @mod_um.inspect.should =~ /\bbar\b/ @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethMod\b/ - - ruby_version_is ""..."3.2" do - @child_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/ - @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/ - end end it "raises a TypeError if the given name is not a String/Symbol" do diff --git a/spec/ruby/core/module/module_function_spec.rb b/spec/ruby/core/module/module_function_spec.rb index 1c3ec5471b..51f647142e 100644 --- a/spec/ruby/core/module/module_function_spec.rb +++ b/spec/ruby/core/module/module_function_spec.rb @@ -38,22 +38,11 @@ describe "Module#module_function with specific method names" do m.respond_to?(:test3).should == false end - ruby_version_is ""..."3.1" do - it "returns self" do - Module.new do - def foo; end - module_function(:foo).should equal(self) - end - end - end - - ruby_version_is "3.1" do - it "returns argument or arguments if given" do - Module.new do - def foo; end - module_function(:foo).should equal(:foo) - module_function(:foo, :foo).should == [:foo, :foo] - end + it "returns argument or arguments if given" do + Module.new do + def foo; end + module_function(:foo).should equal(:foo) + module_function(:foo, :foo).should == [:foo, :foo] end end @@ -216,19 +205,9 @@ describe "Module#module_function as a toggle (no arguments) in a Module body" do m.respond_to?(:test2).should == true end - ruby_version_is ""..."3.1" do - it "returns self" do - Module.new do - module_function.should equal(self) - end - end - end - - ruby_version_is "3.1" do - it "returns nil" do - Module.new do - module_function.should equal(nil) - end + it "returns nil" do + Module.new do + module_function.should equal(nil) end end diff --git a/spec/ruby/core/module/name_spec.rb b/spec/ruby/core/module/name_spec.rb index 0d1f4e24d5..d3318e1645 100644 --- a/spec/ruby/core/module/name_spec.rb +++ b/spec/ruby/core/module/name_spec.rb @@ -30,6 +30,8 @@ describe "Module#name" do m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ ModuleSpecs::Anonymous::WasAnnon = m::N m::N.name.should == "ModuleSpecs::Anonymous::WasAnnon" + ensure + ModuleSpecs::Anonymous.send(:remove_const, :WasAnnon) end it "may be the repeated in different module objects" do @@ -76,12 +78,16 @@ describe "Module#name" do m = Module.new ModuleSpecs::Anonymous::A = m m.name.should == "ModuleSpecs::Anonymous::A" + ensure + ModuleSpecs::Anonymous.send(:remove_const, :A) end it "is set when assigning to a constant (constant path does not match outer module name)" do m = Module.new ModuleSpecs::Anonymous::SameChild::A = m m.name.should == "ModuleSpecs::Anonymous::Child::A" + ensure + ModuleSpecs::Anonymous::SameChild.send(:remove_const, :A) end it "is not modified when assigning to a new constant after it has been accessed" do @@ -90,6 +96,9 @@ describe "Module#name" do m.name.should == "ModuleSpecs::Anonymous::B" ModuleSpecs::Anonymous::C = m m.name.should == "ModuleSpecs::Anonymous::B" + ensure + ModuleSpecs::Anonymous.send(:remove_const, :B) + ModuleSpecs::Anonymous.send(:remove_const, :C) end it "is not modified when assigned to a different anonymous module" do @@ -125,6 +134,8 @@ describe "Module#name" do m::N = Module.new ModuleSpecs::Anonymous::E = m m::N.name.should == "ModuleSpecs::Anonymous::E::N" + ensure + ModuleSpecs::Anonymous.send(:remove_const, :E) end # https://bugs.ruby-lang.org/issues/19681 @@ -138,6 +149,48 @@ describe "Module#name" do "ModuleSpecs::Anonymous::StoredInMultiplePlaces::O" ] valid_names.should include(m::N.name) # You get one of the two, but you don't know which one. + ensure + ModuleSpecs::Anonymous.send(:remove_const, :StoredInMultiplePlaces) + end + + it "is set in #const_added callback when a module defined in the top-level scope" do + ruby_exe(<<~RUBY, args: "2>&1").chomp.should == "TEST1\nTEST2" + class Module + def const_added(name) + puts const_get(name).name + end + end + + # module with name + module TEST1 + end + + # anonymous module + TEST2 = Module.new + RUBY + end + + it "is set in #const_added callback for a nested module when an outer module defined in the top-level scope" do + ScratchPad.record [] + + ModuleSpecs::NameSpecs::NamedModule = Module.new do + def self.const_added(name) + ScratchPad << const_get(name).name + end + + module self::A + def self.const_added(name) + ScratchPad << const_get(name).name + end + + module self::B + end + end + end + + ScratchPad.recorded.should.one?(/#<Module.+>::A$/) + ScratchPad.recorded.should.one?(/#<Module.+>::A::B$/) + ModuleSpecs::NameSpecs.send :remove_const, :NamedModule end it "returns a frozen String" do diff --git a/spec/ruby/core/module/new_spec.rb b/spec/ruby/core/module/new_spec.rb index da7f3b8720..ec521360bd 100644 --- a/spec/ruby/core/module/new_spec.rb +++ b/spec/ruby/core/module/new_spec.rb @@ -6,6 +6,10 @@ describe "Module.new" do Module.new.is_a?(Module).should == true end + it "creates a module without a name" do + Module.new.name.should be_nil + end + it "creates a new Module and passes it to the provided block" do test_mod = nil m = Module.new do |mod| diff --git a/spec/ruby/core/module/prepend_spec.rb b/spec/ruby/core/module/prepend_spec.rb index 96598e7209..71e82c513e 100644 --- a/spec/ruby/core/module/prepend_spec.rb +++ b/spec/ruby/core/module/prepend_spec.rb @@ -75,6 +75,26 @@ describe "Module#prepend" do foo.call.should == 'm' end + it "updates the optimized method when a prepended module is updated" do + out = ruby_exe(<<~RUBY) + module M; end + class Integer + prepend M + end + l = -> { 1 + 2 } + p l.call + M.module_eval do + def +(o) + $called = true + super(o) + end + end + p l.call + p $called + RUBY + out.should == "3\n3\ntrue\n" + end + it "updates the method when there is a base included method and the prepended module overrides it" do base_module = Module.new do def foo @@ -241,6 +261,8 @@ describe "Module#prepend" do B.prepend M B.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatePrepended) end it "updates the constant when a prepended module is updated" do @@ -261,6 +283,8 @@ describe "Module#prepend" do M.const_set(:FOO, 'm') B.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstPrependedUpdated) end it "updates the constant when there is a base included constant and the prepended module overrides it" do @@ -282,6 +306,8 @@ describe "Module#prepend" do A.prepend M A.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstIncludedPrependedOverride) end it "updates the constant when there is a base included constant and the prepended module is later updated" do @@ -305,6 +331,8 @@ describe "Module#prepend" do M.const_set(:FOO, 'm') A.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstIncludedPrependedLaterUpdated) end it "updates the constant when a module prepended after a constant is later updated" do @@ -328,6 +356,8 @@ describe "Module#prepend" do M.const_set(:FOO, 'm') B.foo.should == 'm' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedPrependedAfterLaterUpdated) end it "updates the constant when a module is prepended after another and the constant is defined later on that module" do @@ -352,6 +382,8 @@ describe "Module#prepend" do N.const_set(:FOO, 'n') A.foo.should == 'n' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedPrependedAfterConstDefined) end it "updates the constant when a module is included in a prepended module and the constant is defined later" do @@ -379,6 +411,8 @@ describe "Module#prepend" do N.const_set(:FOO, 'n') A.foo.should == 'n' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedIncludedInPrependedConstDefinedLater) end it "updates the constant when a new module with an included module is prepended" do @@ -405,6 +439,8 @@ describe "Module#prepend" do B.prepend M B.foo.should == 'n' end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNewModuleIncludedPrepended) end it "raises a TypeError when the argument is not a Module" do @@ -412,7 +448,23 @@ describe "Module#prepend" do end it "does not raise a TypeError when the argument is an instance of a subclass of Module" do - -> { ModuleSpecs::SubclassSpec.prepend(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + class ModuleSpecs::SubclassSpec::AClass + end + -> { ModuleSpecs::SubclassSpec::AClass.prepend(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + ensure + ModuleSpecs::SubclassSpec.send(:remove_const, :AClass) + end + + it "raises a TypeError when the argument is a refinement" do + refinement = nil + + Module.new do + refine String do + refinement = self + end + end + + -> { ModuleSpecs::Basic.prepend(refinement) }.should raise_error(TypeError, "Cannot prepend refinement") end it "imports constants" do @@ -726,6 +778,33 @@ describe "Module#prepend" do ary.should == [3, 2, 1] end + it "does not prepend a second copy if the module already indirectly exists in the hierarchy" do + mod = Module.new do; end + submod = Module.new do; end + klass = Class.new do; end + klass.include(mod) + mod.prepend(submod) + klass.include(mod) + + klass.ancestors.take(4).should == [klass, submod, mod, Object] + end + + # https://bugs.ruby-lang.org/issues/17423 + describe "when module already exists in ancestor chain" do + it "modifies the ancestor chain" do + m = Module.new do; end + a = Module.new do; end + b = Class.new do; end + + b.include(a) + a.prepend(m) + b.ancestors.take(4).should == [b, m, a, Object] + + b.prepend(m) + b.ancestors.take(5).should == [m, b, m, a, Object] + end + end + describe "called on a module" do describe "included into a class" it "does not obscure the module's methods from reflective access" do diff --git a/spec/ruby/core/module/private_spec.rb b/spec/ruby/core/module/private_spec.rb index ead806637c..9e1a297eea 100644 --- a/spec/ruby/core/module/private_spec.rb +++ b/spec/ruby/core/module/private_spec.rb @@ -38,25 +38,13 @@ describe "Module#private" do :module_specs_public_method_on_object_for_kernel_private) end - ruby_version_is ""..."3.1" do - it "returns self" do - (class << Object.new; self; end).class_eval do - def foo; end - private(:foo).should equal(self) - private.should equal(self) - end - end - end - - ruby_version_is "3.1" do - it "returns argument or arguments if given" do - (class << Object.new; self; end).class_eval do - def foo; end - private(:foo).should equal(:foo) - private([:foo, :foo]).should == [:foo, :foo] - private(:foo, :foo).should == [:foo, :foo] - private.should equal(nil) - end + it "returns argument or arguments if given" do + (class << Object.new; self; end).class_eval do + def foo; end + private(:foo).should equal(:foo) + private([:foo, :foo]).should == [:foo, :foo] + private(:foo, :foo).should == [:foo, :foo] + private.should equal(nil) end end diff --git a/spec/ruby/core/module/protected_spec.rb b/spec/ruby/core/module/protected_spec.rb index 058d49d751..9e37223e18 100644 --- a/spec/ruby/core/module/protected_spec.rb +++ b/spec/ruby/core/module/protected_spec.rb @@ -39,25 +39,13 @@ describe "Module#protected" do :module_specs_public_method_on_object_for_kernel_protected) end - ruby_version_is ""..."3.1" do - it "returns self" do - (class << Object.new; self; end).class_eval do - def foo; end - protected(:foo).should equal(self) - protected.should equal(self) - end - end - end - - ruby_version_is "3.1" do - it "returns argument or arguments if given" do - (class << Object.new; self; end).class_eval do - def foo; end - protected(:foo).should equal(:foo) - protected([:foo, :foo]).should == [:foo, :foo] - protected(:foo, :foo).should == [:foo, :foo] - protected.should equal(nil) - end + it "returns argument or arguments if given" do + (class << Object.new; self; end).class_eval do + def foo; end + protected(:foo).should equal(:foo) + protected([:foo, :foo]).should == [:foo, :foo] + protected(:foo, :foo).should == [:foo, :foo] + protected.should equal(nil) end end diff --git a/spec/ruby/core/module/public_spec.rb b/spec/ruby/core/module/public_spec.rb index e3b183f228..ce31eb5d0e 100644 --- a/spec/ruby/core/module/public_spec.rb +++ b/spec/ruby/core/module/public_spec.rb @@ -27,25 +27,13 @@ describe "Module#public" do :module_specs_private_method_on_object_for_kernel_public) end - ruby_version_is ""..."3.1" do - it "returns self" do - (class << Object.new; self; end).class_eval do - def foo; end - public(:foo).should equal(self) - public.should equal(self) - end - end - end - - ruby_version_is "3.1" do - it "returns argument or arguments if given" do - (class << Object.new; self; end).class_eval do - def foo; end - public(:foo).should equal(:foo) - public([:foo, :foo]).should == [:foo, :foo] - public(:foo, :foo).should == [:foo, :foo] - public.should equal(nil) - end + it "returns argument or arguments if given" do + (class << Object.new; self; end).class_eval do + def foo; end + public(:foo).should equal(:foo) + public([:foo, :foo]).should == [:foo, :foo] + public(:foo, :foo).should == [:foo, :foo] + public.should equal(nil) end end diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb index 11085c325b..d219b98825 100644 --- a/spec/ruby/core/module/refine_spec.rb +++ b/spec/ruby/core/module/refine_spec.rb @@ -243,36 +243,10 @@ describe "Module#refine" do result.should == "foo from singleton class" end - ruby_version_is ""..."3.2" do - it "looks in the included modules for builtin methods" do - result = ruby_exe(<<-RUBY) - a = Module.new do - def /(other) quo(other) end - end - - refinement = Module.new do - refine Integer do - include a - end - end - - result = nil - Module.new do - using refinement - result = 1 / 2 - end - - print result.class - RUBY - - result.should == 'Rational' - end - end - it "looks in later included modules of the refined module first" do a = Module.new do def foo - "foo from A" + "foo from A" end end @@ -300,67 +274,6 @@ describe "Module#refine" do result.should == "foo from IncludeMeLater" end - ruby_version_is ""..."3.1" do - it "looks in prepended modules from the refinement first" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine refined_class do - include ModuleSpecs::IncludedModule - prepend ModuleSpecs::PrependedModule - - def foo; "foo from refinement"; end - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.foo - end - - result.should == "foo from prepended module" - end - - it "looks in refinement then" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine(refined_class) do - include ModuleSpecs::IncludedModule - - def foo; "foo from refinement"; end - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.foo - end - - result.should == "foo from refinement" - end - - it "looks in included modules from the refinement then" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine refined_class do - include ModuleSpecs::IncludedModule - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.foo - end - - result.should == "foo from included module" - end - end - it "looks in the class then" do refined_class = ModuleSpecs.build_refined_class @@ -606,30 +519,6 @@ describe "Module#refine" do end context "when super is called in a refinement" do - ruby_version_is ""..."3.1" do - it "looks in the included to refinery module" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine refined_class do - include ModuleSpecs::IncludedModule - - def foo - super - end - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.foo - end - - result.should == "foo from included module" - end - end - it "looks in the refined class" do refined_class = ModuleSpecs.build_refined_class @@ -650,59 +539,6 @@ describe "Module#refine" do result.should == "foo" end - ruby_version_is ""..."3.1" do - it "looks in the refined class from included module" do - refined_class = ModuleSpecs.build_refined_class(for_super: true) - - a = Module.new do - def foo - [:A] + super - end - end - - refinement = Module.new do - refine refined_class do - include a - end - end - - result = nil - Module.new do - using refinement - - result = refined_class.new.foo - end - - result.should == [:A, :C] - end - - it "looks in the refined ancestors from included module" do - refined_class = ModuleSpecs.build_refined_class(for_super: true) - subclass = Class.new(refined_class) - - a = Module.new do - def foo - [:A] + super - end - end - - refinement = Module.new do - refine refined_class do - include a - end - end - - result = nil - Module.new do - using refinement - - result = subclass.new.foo - end - - result.should == [:A, :C] - end - end - # super in a method of a refinement invokes the method in the refined # class even if there is another refinement which has been activated # in the same context. @@ -763,179 +599,6 @@ describe "Module#refine" do }.should raise_error(NoMethodError) end end - - ruby_version_is ""..."3.1" do - it "does't have access to active refinements for C from included module" do - refined_class = ModuleSpecs.build_refined_class - - a = Module.new do - def foo - super + bar - end - end - - refinement = Module.new do - refine refined_class do - include a - - def bar - "bar is not seen from A methods" - end - end - end - - Module.new do - using refinement - -> { - refined_class.new.foo - }.should raise_error(NameError) { |e| e.name.should == :bar } - end - end - - it "does't have access to other active refinements from included module" do - refined_class = ModuleSpecs.build_refined_class - - refinement_integer = Module.new do - refine Integer do - def bar - "bar is not seen from A methods" - end - end - end - - a = Module.new do - def foo - super + 1.bar - end - end - - refinement = Module.new do - refine refined_class do - include a - end - end - - Module.new do - using refinement - using refinement_integer - -> { - refined_class.new.foo - }.should raise_error(NameError) { |e| e.name.should == :bar } - end - end - - # https://bugs.ruby-lang.org/issues/16977 - it "looks in the another active refinement if super called from included modules" do - refined_class = ModuleSpecs.build_refined_class(for_super: true) - - a = Module.new do - def foo - [:A] + super - end - end - - b = Module.new do - def foo - [:B] + super - end - end - - refinement_a = Module.new do - refine refined_class do - include a - end - end - - refinement_b = Module.new do - refine refined_class do - include b - end - end - - result = nil - Module.new do - using refinement_a - using refinement_b - result = refined_class.new.foo - end - - result.should == [:B, :A, :C] - end - - it "looks in the current active refinement from included modules" do - refined_class = ModuleSpecs.build_refined_class(for_super: true) - - a = Module.new do - def foo - [:A] + super - end - end - - b = Module.new do - def foo - [:B] + super - end - end - - refinement = Module.new do - refine refined_class do - def foo - [:LAST] + super - end - end - end - - refinement_a_b = Module.new do - refine refined_class do - include a - include b - end - end - - result = nil - Module.new do - using refinement - using refinement_a_b - result = refined_class.new.foo - end - - result.should == [:B, :A, :LAST, :C] - end - - it "looks in the lexical scope refinements before other active refinements" do - refined_class = ModuleSpecs.build_refined_class(for_super: true) - - refinement_local = Module.new do - refine refined_class do - def foo - [:LOCAL] + super - end - end - end - - a = Module.new do - using refinement_local - - def foo - [:A] + super - end - end - - refinement = Module.new do - refine refined_class do - include a - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.foo - end - - result.should == [:A, :LOCAL, :C] - end - end end it 'and alias aliases a method within a refinement module, but not outside it' do diff --git a/spec/ruby/core/module/refinements_spec.rb b/spec/ruby/core/module/refinements_spec.rb index 5648fcbd6f..05658a8b0e 100644 --- a/spec/ruby/core/module/refinements_spec.rb +++ b/spec/ruby/core/module/refinements_spec.rb @@ -1,45 +1,43 @@ require_relative '../../spec_helper' describe "Module#refinements" do - ruby_version_is "3.2" do - it "returns refinements defined in a module" do - ScratchPad.record [] - - m = Module.new do - refine String do - ScratchPad << self - end - - refine Array do - ScratchPad << self - end + it "returns refinements defined in a module" do + ScratchPad.record [] + + m = Module.new do + refine String do + ScratchPad << self end - m.refinements.sort_by(&:object_id).should == ScratchPad.recorded.sort_by(&:object_id) + refine Array do + ScratchPad << self + end end - it "does not return refinements defined in the included module" do - ScratchPad.record [] + m.refinements.sort_by(&:object_id).should == ScratchPad.recorded.sort_by(&:object_id) + end - m1 = Module.new do - refine Integer do - nil - end + it "does not return refinements defined in the included module" do + ScratchPad.record [] + + m1 = Module.new do + refine Integer do + nil end + end - m2 = Module.new do - include m1 + m2 = Module.new do + include m1 - refine String do - ScratchPad << self - end + refine String do + ScratchPad << self end - - m2.refinements.should == ScratchPad.recorded end - it "returns an empty array if no refinements defined in a module" do - Module.new.refinements.should == [] - end + m2.refinements.should == ScratchPad.recorded + end + + it "returns an empty array if no refinements defined in a module" do + Module.new.refinements.should == [] end end diff --git a/spec/ruby/core/module/remove_const_spec.rb b/spec/ruby/core/module/remove_const_spec.rb index 0ac23f05a5..35a9d65105 100644 --- a/spec/ruby/core/module/remove_const_spec.rb +++ b/spec/ruby/core/module/remove_const_spec.rb @@ -101,5 +101,7 @@ describe "Module#remove_const" do A.send(:remove_const,:FOO) A.foo.should == 'm' end + ensure + ConstantSpecs.send(:remove_const, :RemovedConstantUpdate) end end diff --git a/spec/ruby/core/module/ruby2_keywords_spec.rb b/spec/ruby/core/module/ruby2_keywords_spec.rb index dc16d712c7..652f9f7083 100644 --- a/spec/ruby/core/module/ruby2_keywords_spec.rb +++ b/spec/ruby/core/module/ruby2_keywords_spec.rb @@ -22,7 +22,6 @@ describe "Module#ruby2_keywords" do end h = {a: 1} - obj.regular(**h).should.equal?(h) last = mark(**h).last Hash.ruby2_keywords_hash?(last).should == true @@ -77,122 +76,60 @@ describe "Module#ruby2_keywords" do Hash.ruby2_keywords_hash?(marked).should == true end - ruby_version_is "3.2" do - it "makes a copy and unmark the Hash when calling a method taking (*args)" do - obj = Object.new - obj.singleton_class.class_exec do - def splat(*args) - args.last - end - - def splat1(arg, *args) - args.last - end + it "makes a copy and unmark the Hash when calling a method taking (*args)" do + obj = Object.new + obj.singleton_class.class_exec do + def splat(*args) + args.last + end - def proc_call(*args) - -> *a { a.last }.call(*args) - end + def splat1(arg, *args) + args.last end - h = { a: 1 } - args = mark(**h) - marked = args.last - Hash.ruby2_keywords_hash?(marked).should == true - - after_usage = obj.splat(*args) - after_usage.should == h - after_usage.should_not.equal?(h) - after_usage.should_not.equal?(marked) - Hash.ruby2_keywords_hash?(after_usage).should == false - Hash.ruby2_keywords_hash?(marked).should == true - - args = mark(1, **h) - marked = args.last - after_usage = obj.splat1(*args) - after_usage.should == h - after_usage.should_not.equal?(h) - after_usage.should_not.equal?(marked) - Hash.ruby2_keywords_hash?(after_usage).should == false - Hash.ruby2_keywords_hash?(marked).should == true - - args = mark(**h) - marked = args.last - after_usage = obj.proc_call(*args) - after_usage.should == h - after_usage.should_not.equal?(h) - after_usage.should_not.equal?(marked) - Hash.ruby2_keywords_hash?(after_usage).should == false - Hash.ruby2_keywords_hash?(marked).should == true - - args = mark(**h) - marked = args.last - after_usage = obj.send(:splat, *args) - after_usage.should == h - after_usage.should_not.equal?(h) - after_usage.should_not.equal?(marked) - Hash.ruby2_keywords_hash?(after_usage).should == false - Hash.ruby2_keywords_hash?(marked).should == true + def proc_call(*args) + -> *a { a.last }.call(*args) + end end - end - ruby_version_is ""..."3.2" do - # https://bugs.ruby-lang.org/issues/18625 - it "does NOT copy the Hash when calling a method taking (*args)" do - obj = Object.new - obj.singleton_class.class_exec do - def splat(*args) - args.last - end + h = { a: 1 } + args = mark(**h) + marked = args.last + Hash.ruby2_keywords_hash?(marked).should == true - def splat1(arg, *args) - args.last - end + after_usage = obj.splat(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true - def proc_call(*args) - -> *a { a.last }.call(*args) - end - end + args = mark(1, **h) + marked = args.last + after_usage = obj.splat1(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true - h = { a: 1 } - args = mark(**h) - marked = args.last - Hash.ruby2_keywords_hash?(marked).should == true - - after_usage = obj.splat(*args) - after_usage.should == h - after_usage.should_not.equal?(h) - after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625 - Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625 - Hash.ruby2_keywords_hash?(marked).should == true - - args = mark(1, **h) - marked = args.last - after_usage = obj.splat1(*args) - after_usage.should == h - after_usage.should_not.equal?(h) - after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625 - Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625 - Hash.ruby2_keywords_hash?(marked).should == true - - args = mark(**h) - marked = args.last - after_usage = obj.proc_call(*args) - after_usage.should == h - after_usage.should_not.equal?(h) - after_usage.should.equal?(marked) # https://bugs.ruby-lang.org/issues/18625 - Hash.ruby2_keywords_hash?(after_usage).should == true # https://bugs.ruby-lang.org/issues/18625 - Hash.ruby2_keywords_hash?(marked).should == true - - args = mark(**h) - marked = args.last - after_usage = obj.send(:splat, *args) - after_usage.should == h - after_usage.should_not.equal?(h) - send_copies = RUBY_ENGINE == "ruby" # inconsistent with Proc#call above for CRuby - after_usage.equal?(marked).should == !send_copies - Hash.ruby2_keywords_hash?(after_usage).should == !send_copies - Hash.ruby2_keywords_hash?(marked).should == true - end + args = mark(**h) + marked = args.last + after_usage = obj.proc_call(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + + args = mark(**h) + marked = args.last + after_usage = obj.send(:splat, *args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true end it "applies to the underlying method and applies across aliasing" do @@ -238,7 +175,7 @@ describe "Module#ruby2_keywords" do obj.singleton_class.class_exec do ruby2_keywords :not_existing end - }.should raise_error(NameError, /undefined method `not_existing'/) + }.should raise_error(NameError, /undefined method [`']not_existing'/) end it "accepts String as well" do @@ -276,7 +213,7 @@ describe "Module#ruby2_keywords" do it "prints warning when a method accepts keywords" do obj = Object.new - def obj.foo(a:, b:) end + def obj.foo(*a, b:) end -> { obj.singleton_class.class_exec do @@ -287,7 +224,7 @@ describe "Module#ruby2_keywords" do it "prints warning when a method accepts keyword splat" do obj = Object.new - def obj.foo(**a) end + def obj.foo(*a, **b) end -> { obj.singleton_class.class_exec do @@ -295,4 +232,17 @@ describe "Module#ruby2_keywords" do end }.should complain(/Skipping set of ruby2_keywords flag for/) end + + ruby_version_is "4.0" do + it "prints warning when a method accepts post arguments" do + obj = Object.new + def obj.foo(*a, b) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end end diff --git a/spec/ruby/core/module/set_temporary_name_spec.rb b/spec/ruby/core/module/set_temporary_name_spec.rb index f5886a3398..46605ed675 100644 --- a/spec/ruby/core/module/set_temporary_name_spec.rb +++ b/spec/ruby/core/module/set_temporary_name_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/set_temporary_name' ruby_version_is "3.3" do describe "Module#set_temporary_name" do @@ -13,13 +14,34 @@ ruby_version_is "3.3" do m.name.should be_nil end + it "returns self" do + m = Module.new + m.set_temporary_name("fake_name").should.equal? m + end + it "can assign a temporary name which is not a valid constant path" do m = Module.new - m.set_temporary_name("a::B") - m.name.should == "a::B" + + m.set_temporary_name("name") + m.name.should == "name" m.set_temporary_name("Template['foo.rb']") m.name.should == "Template['foo.rb']" + + m.set_temporary_name("a::B") + m.name.should == "a::B" + + m.set_temporary_name("A::b") + m.name.should == "A::b" + + m.set_temporary_name("A::B::") + m.name.should == "A::B::" + + m.set_temporary_name("A::::B") + m.name.should == "A::::B" + + m.set_temporary_name("A=") + m.name.should == "A=" end it "can't assign empty string as name" do @@ -43,7 +65,7 @@ ruby_version_is "3.3" do -> { Object.set_temporary_name("fake_name") }.should raise_error(RuntimeError, "can't change permanent name") end - it "can assign a temporary name to a nested module" do + it "can assign a temporary name to a module nested into an anonymous module" do m = Module.new module m::N; end m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ @@ -55,6 +77,18 @@ ruby_version_is "3.3" do m::N.name.should be_nil end + it "discards a temporary name when an outer anonymous module gets a permanent name" do + m = Module.new + module m::N; end + + m::N.set_temporary_name("fake_name") + m::N.name.should == "fake_name" + + ModuleSpecs::SetTemporaryNameSpec::M = m + m::N.name.should == "ModuleSpecs::SetTemporaryNameSpec::M::N" + ModuleSpecs::SetTemporaryNameSpec.send :remove_const, :M + end + it "can update the name when assigned to a constant" do m = Module.new m::N = Module.new @@ -64,5 +98,50 @@ ruby_version_is "3.3" do m::M = m::N m::M.name.should =~ /\A#<Module:0x\h+>::M\z/m end + + it "can reassign a temporary name repeatedly" do + m = Module.new + + m.set_temporary_name("fake_name") + m.name.should == "fake_name" + + m.set_temporary_name("fake_name_2") + m.name.should == "fake_name_2" + end + + ruby_bug "#21094", ""..."4.0" do + it "also updates a name of a nested module" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + + m.set_temporary_name "m" + m::N.name.should == "m::N" + + m.set_temporary_name nil + m::N.name.should == nil + end + end + + it "keeps temporary name when assigned in an anonymous module" do + outer = Module.new + m = Module.new + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end + + it "keeps temporary name when assigned in an anonymous module and nested before" do + outer = Module.new + m = Module.new + outer::A = m + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end end end diff --git a/spec/ruby/core/module/shared/attr_added.rb b/spec/ruby/core/module/shared/attr_added.rb new file mode 100644 index 0000000000..ce832cdcff --- /dev/null +++ b/spec/ruby/core/module/shared/attr_added.rb @@ -0,0 +1,34 @@ +describe :module_attr_added, shared: true do + it "calls method_added for normal classes" do + ScratchPad.record [] + + cls = Class.new do + class << self + def method_added(name) + ScratchPad.recorded << name + end + end + end + + cls.send(@method, :foo) + + ScratchPad.recorded.each {|name| name.to_s.should =~ /foo[=]?/} + end + + it "calls singleton_method_added for singleton classes" do + ScratchPad.record [] + cls = Class.new do + class << self + def singleton_method_added(name) + # called for this def so ignore it + return if name == :singleton_method_added + ScratchPad.recorded << name + end + end + end + + cls.singleton_class.send(@method, :foo) + + ScratchPad.recorded.each {|name| name.to_s.should =~ /foo[=]?/} + end +end diff --git a/spec/ruby/core/module/to_s_spec.rb b/spec/ruby/core/module/to_s_spec.rb index 6b1a615ef9..83c0ae0825 100644 --- a/spec/ruby/core/module/to_s_spec.rb +++ b/spec/ruby/core/module/to_s_spec.rb @@ -51,6 +51,8 @@ describe "Module#to_s" do ModuleSpecs::RefinementInspect::R.name.should == 'ModuleSpecs::RefinementInspect::R' ModuleSpecs::RefinementInspect::R.to_s.should == '#<refinement:String@ModuleSpecs::RefinementInspect>' + ensure + ModuleSpecs.send(:remove_const, :RefinementInspect) end it 'does not call #inspect or #to_s for singleton classes' do diff --git a/spec/ruby/core/module/undef_method_spec.rb b/spec/ruby/core/module/undef_method_spec.rb index c2ad200536..d4efcd51cb 100644 --- a/spec/ruby/core/module/undef_method_spec.rb +++ b/spec/ruby/core/module/undef_method_spec.rb @@ -50,7 +50,7 @@ describe "Module#undef_method" do end it "raises a NameError when passed a missing name for a module" do - -> { @module.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for module `#{@module}'/) { |e| + -> { @module.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for module [`']#{@module}'/) { |e| # a NameError and not a NoMethodError e.class.should == NameError } @@ -58,7 +58,7 @@ describe "Module#undef_method" do it "raises a NameError when passed a missing name for a class" do klass = Class.new - -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `#{klass}'/) { |e| + -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for class [`']#{klass}'/) { |e| # a NameError and not a NoMethodError e.class.should == NameError } @@ -69,8 +69,8 @@ describe "Module#undef_method" do obj = klass.new sclass = obj.singleton_class - -> { sclass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `#{sclass}'/) { |e| - e.message.should include('`#<Class:#<#<Class:') + -> { sclass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for class [`']#{sclass}'/) { |e| + e.message.should =~ /[`']#<Class:#<#<Class:/ # a NameError and not a NoMethodError e.class.should == NameError @@ -79,7 +79,7 @@ describe "Module#undef_method" do it "raises a NameError when passed a missing name for a metaclass" do klass = String.singleton_class - -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `String'/) { |e| + -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for class [`']String'/) { |e| # a NameError and not a NoMethodError e.class.should == NameError } diff --git a/spec/ruby/core/module/undefined_instance_methods_spec.rb b/spec/ruby/core/module/undefined_instance_methods_spec.rb index 3be860d053..d33ee93fc1 100644 --- a/spec/ruby/core/module/undefined_instance_methods_spec.rb +++ b/spec/ruby/core/module/undefined_instance_methods_spec.rb @@ -2,25 +2,23 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Module#undefined_instance_methods" do - ruby_version_is "3.2" do - it "returns methods undefined in the class" do - methods = ModuleSpecs::UndefinedInstanceMethods::Parent.undefined_instance_methods - methods.should == [:undefed_method] - end + it "returns methods undefined in the class" do + methods = ModuleSpecs::UndefinedInstanceMethods::Parent.undefined_instance_methods + methods.should == [:undefed_method] + end - it "returns inherited methods undefined in the class" do - methods = ModuleSpecs::UndefinedInstanceMethods::Child.undefined_instance_methods - methods.should include(:parent_method, :another_parent_method) - end + it "returns inherited methods undefined in the class" do + methods = ModuleSpecs::UndefinedInstanceMethods::Child.undefined_instance_methods + methods.should include(:parent_method, :another_parent_method) + end - it "returns methods from an included module that are undefined in the class" do - methods = ModuleSpecs::UndefinedInstanceMethods::Grandchild.undefined_instance_methods - methods.should include(:super_included_method) - end + it "returns methods from an included module that are undefined in the class" do + methods = ModuleSpecs::UndefinedInstanceMethods::Grandchild.undefined_instance_methods + methods.should include(:super_included_method) + end - it "does not returns ancestors undefined methods" do - methods = ModuleSpecs::UndefinedInstanceMethods::Grandchild.undefined_instance_methods - methods.should_not include(:parent_method, :another_parent_method) - end + it "does not returns ancestors undefined methods" do + methods = ModuleSpecs::UndefinedInstanceMethods::Grandchild.undefined_instance_methods + methods.should_not include(:parent_method, :another_parent_method) end end diff --git a/spec/ruby/core/module/used_refinements_spec.rb b/spec/ruby/core/module/used_refinements_spec.rb index c16cab0e3c..40dd4a444e 100644 --- a/spec/ruby/core/module/used_refinements_spec.rb +++ b/spec/ruby/core/module/used_refinements_spec.rb @@ -1,87 +1,85 @@ require_relative '../../spec_helper' describe "Module.used_refinements" do - ruby_version_is "3.2" do - it "returns list of all refinements imported in the current scope" do - refinement_int = nil - refinement_str = nil - ScratchPad.record [] - - m1 = Module.new do - refine Integer do - refinement_int = self - end + it "returns list of all refinements imported in the current scope" do + refinement_int = nil + refinement_str = nil + ScratchPad.record [] + + m1 = Module.new do + refine Integer do + refinement_int = self end + end - m2 = Module.new do - refine String do - refinement_str = self - end + m2 = Module.new do + refine String do + refinement_str = self end + end - Module.new do - using m1 - using m2 + Module.new do + using m1 + using m2 - Module.used_refinements.each { |r| ScratchPad << r } - end - - ScratchPad.recorded.sort_by(&:object_id).should == [refinement_int, refinement_str].sort_by(&:object_id) + Module.used_refinements.each { |r| ScratchPad << r } end - it "returns empty array if does not have any refinements imported" do - used_refinements = nil + ScratchPad.recorded.sort_by(&:object_id).should == [refinement_int, refinement_str].sort_by(&:object_id) + end - Module.new do - used_refinements = Module.used_refinements - end + it "returns empty array if does not have any refinements imported" do + used_refinements = nil - used_refinements.should == [] + Module.new do + used_refinements = Module.used_refinements end - it "ignores refinements imported in a module that is included into the current one" do - used_refinements = nil + used_refinements.should == [] + end - m1 = Module.new do - refine Integer do - nil - end - end + it "ignores refinements imported in a module that is included into the current one" do + used_refinements = nil - m2 = Module.new do - using m1 + m1 = Module.new do + refine Integer do + nil end + end - Module.new do - include m2 + m2 = Module.new do + using m1 + end - used_refinements = Module.used_refinements - end + Module.new do + include m2 - used_refinements.should == [] + used_refinements = Module.used_refinements end - it "returns refinements even not defined directly in a module refinements are imported from" do - used_refinements = nil - ScratchPad.record [] + used_refinements.should == [] + end - m1 = Module.new do - refine Integer do - ScratchPad << self - end - end + it "returns refinements even not defined directly in a module refinements are imported from" do + used_refinements = nil + ScratchPad.record [] - m2 = Module.new do - include m1 + m1 = Module.new do + refine Integer do + ScratchPad << self end + end - Module.new do - using m2 + m2 = Module.new do + include m1 + end - used_refinements = Module.used_refinements - end + Module.new do + using m2 - used_refinements.should == ScratchPad.recorded + used_refinements = Module.used_refinements end + + used_refinements.should == ScratchPad.recorded end end diff --git a/spec/ruby/core/module/using_spec.rb b/spec/ruby/core/module/using_spec.rb index 4781b99bb7..a908363c96 100644 --- a/spec/ruby/core/module/using_spec.rb +++ b/spec/ruby/core/module/using_spec.rb @@ -316,7 +316,7 @@ describe "Module#using" do using refinement def initialize - @a = "1703" + @a = +"1703" @a.instance_eval do def abc diff --git a/spec/ruby/core/mutex/lock_spec.rb b/spec/ruby/core/mutex/lock_spec.rb index 7a39817b11..e9d33f5fd9 100644 --- a/spec/ruby/core/mutex/lock_spec.rb +++ b/spec/ruby/core/mutex/lock_spec.rb @@ -1,10 +1,6 @@ require_relative '../../spec_helper' describe "Mutex#lock" do - before :each do - ScratchPad.clear - end - it "returns self" do m = Mutex.new m.lock.should == m diff --git a/spec/ruby/core/nil/singleton_method_spec.rb b/spec/ruby/core/nil/singleton_method_spec.rb index f9163da3f1..8d898b1cc9 100644 --- a/spec/ruby/core/nil/singleton_method_spec.rb +++ b/spec/ruby/core/nil/singleton_method_spec.rb @@ -3,10 +3,10 @@ require_relative '../../spec_helper' describe "NilClass#singleton_method" do ruby_version_is '3.3' do it "raises regardless of whether NilClass defines the method" do - proc{nil.singleton_method(:foo)}.should raise_error(NameError) + -> { nil.singleton_method(:foo) }.should raise_error(NameError) begin - def nil.foo; end - proc{nil.singleton_method(:foo)}.should raise_error(NameError) + def (nil).foo; end + -> { nil.singleton_method(:foo) }.should raise_error(NameError) ensure NilClass.send(:remove_method, :foo) end diff --git a/spec/ruby/core/numeric/clone_spec.rb b/spec/ruby/core/numeric/clone_spec.rb index 5834b17089..423cec85dd 100644 --- a/spec/ruby/core/numeric/clone_spec.rb +++ b/spec/ruby/core/numeric/clone_spec.rb @@ -14,7 +14,7 @@ describe "Numeric#clone" do 1.clone.frozen?.should == true end - it "accepts optonal keyword argument :freeze" do + it "accepts optional keyword argument :freeze" do value = 1 value.clone(freeze: true).should equal(value) end diff --git a/spec/ruby/core/numeric/shared/imag.rb b/spec/ruby/core/numeric/shared/imag.rb index ac2da40a3b..4f117e243a 100644 --- a/spec/ruby/core/numeric/shared/imag.rb +++ b/spec/ruby/core/numeric/shared/imag.rb @@ -19,8 +19,8 @@ describe :numeric_imag, shared: true do end it "raises an ArgumentError if given any arguments" do - @numbers.each do |number| - -> { number.send(@method, number) }.should raise_error(ArgumentError) - end + @numbers.each do |number| + -> { number.send(@method, number) }.should raise_error(ArgumentError) + end end end diff --git a/spec/ruby/core/numeric/shared/rect.rb b/spec/ruby/core/numeric/shared/rect.rb index 9cde19a398..120a69b1c4 100644 --- a/spec/ruby/core/numeric/shared/rect.rb +++ b/spec/ruby/core/numeric/shared/rect.rb @@ -25,24 +25,24 @@ describe :numeric_rect, shared: true do end it "returns self as the first element" do - @numbers.each do |number| - if Float === number and number.nan? - number.send(@method).first.nan?.should be_true - else - number.send(@method).first.should == number - end - end + @numbers.each do |number| + if Float === number and number.nan? + number.send(@method).first.nan?.should be_true + else + number.send(@method).first.should == number + end + end end it "returns 0 as the last element" do - @numbers.each do |number| - number.send(@method).last.should == 0 - end + @numbers.each do |number| + number.send(@method).last.should == 0 + end end it "raises an ArgumentError if given any arguments" do - @numbers.each do |number| - -> { number.send(@method, number) }.should raise_error(ArgumentError) - end + @numbers.each do |number| + -> { number.send(@method, number) }.should raise_error(ArgumentError) + end end end diff --git a/spec/ruby/core/objectspace/_id2ref_spec.rb b/spec/ruby/core/objectspace/_id2ref_spec.rb index c088ae2743..1ae3230bdf 100644 --- a/spec/ruby/core/objectspace/_id2ref_spec.rb +++ b/spec/ruby/core/objectspace/_id2ref_spec.rb @@ -1,52 +1,65 @@ require_relative '../../spec_helper' -describe "ObjectSpace._id2ref" do - it "converts an object id to a reference to the object" do - s = "I am a string" - r = ObjectSpace._id2ref(s.object_id) - r.should == s +ruby_version_is "4.0" do + describe "ObjectSpace._id2ref" do + it "is deprecated" do + id = nil.object_id + -> { + ObjectSpace._id2ref(id) + }.should complain(/warning: ObjectSpace\._id2ref is deprecated/) + end end +end - it "retrieves true by object_id" do - ObjectSpace._id2ref(true.object_id).should == true - end +ruby_version_is ""..."4.0" do + describe "ObjectSpace._id2ref" do + it "converts an object id to a reference to the object" do + s = "I am a string" + r = ObjectSpace._id2ref(s.object_id) + r.should == s + end - it "retrieves false by object_id" do - ObjectSpace._id2ref(false.object_id).should == false - end + it "retrieves true by object_id" do + ObjectSpace._id2ref(true.object_id).should == true + end - it "retrieves nil by object_id" do - ObjectSpace._id2ref(nil.object_id).should == nil - end + it "retrieves false by object_id" do + ObjectSpace._id2ref(false.object_id).should == false + end - it "retrieves a small Integer by object_id" do - ObjectSpace._id2ref(1.object_id).should == 1 - ObjectSpace._id2ref((-42).object_id).should == -42 - end + it "retrieves nil by object_id" do + ObjectSpace._id2ref(nil.object_id).should == nil + end - it "retrieves a large Integer by object_id" do - obj = 1 << 88 - ObjectSpace._id2ref(obj.object_id).should.equal?(obj) - end + it "retrieves a small Integer by object_id" do + ObjectSpace._id2ref(1.object_id).should == 1 + ObjectSpace._id2ref((-42).object_id).should == -42 + end - it "retrieves a Symbol by object_id" do - ObjectSpace._id2ref(:sym.object_id).should.equal?(:sym) - end + it "retrieves a large Integer by object_id" do + obj = 1 << 88 + ObjectSpace._id2ref(obj.object_id).should.equal?(obj) + end - it "retrieves a String by object_id" do - obj = "str" - ObjectSpace._id2ref(obj.object_id).should.equal?(obj) - end + it "retrieves a Symbol by object_id" do + ObjectSpace._id2ref(:sym.object_id).should.equal?(:sym) + end - it "retrieves a frozen literal String by object_id" do - ObjectSpace._id2ref("frozen string literal _id2ref".freeze.object_id).should.equal?("frozen string literal _id2ref".freeze) - end + it "retrieves a String by object_id" do + obj = "str" + ObjectSpace._id2ref(obj.object_id).should.equal?(obj) + end - it "retrieves an Encoding by object_id" do - ObjectSpace._id2ref(Encoding::UTF_8.object_id).should.equal?(Encoding::UTF_8) - end + it "retrieves a frozen literal String by object_id" do + ObjectSpace._id2ref("frozen string literal _id2ref".freeze.object_id).should.equal?("frozen string literal _id2ref".freeze) + end + + it "retrieves an Encoding by object_id" do + ObjectSpace._id2ref(Encoding::UTF_8.object_id).should.equal?(Encoding::UTF_8) + end - it 'raises RangeError when an object could not be found' do - proc { ObjectSpace._id2ref(1 << 60) }.should raise_error(RangeError) + it 'raises RangeError when an object could not be found' do + proc { ObjectSpace._id2ref(1 << 60) }.should raise_error(RangeError) + end end end diff --git a/spec/ruby/core/objectspace/add_finalizer_spec.rb b/spec/ruby/core/objectspace/add_finalizer_spec.rb deleted file mode 100644 index 3540ac0413..0000000000 --- a/spec/ruby/core/objectspace/add_finalizer_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -describe "ObjectSpace.add_finalizer" do - it "needs to be reviewed for spec completeness" -end diff --git a/spec/ruby/core/objectspace/call_finalizer_spec.rb b/spec/ruby/core/objectspace/call_finalizer_spec.rb deleted file mode 100644 index 6dce92ddd6..0000000000 --- a/spec/ruby/core/objectspace/call_finalizer_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -describe "ObjectSpace.call_finalizer" do - it "needs to be reviewed for spec completeness" -end diff --git a/spec/ruby/core/objectspace/define_finalizer_spec.rb b/spec/ruby/core/objectspace/define_finalizer_spec.rb index 6be83e518e..0f4b54c345 100644 --- a/spec/ruby/core/objectspace/define_finalizer_spec.rb +++ b/spec/ruby/core/objectspace/define_finalizer_spec.rb @@ -52,7 +52,7 @@ describe "ObjectSpace.define_finalizer" do Proc.new { puts "finalizer run" } end handler = scoped - obj = "Test" + obj = +"Test" ObjectSpace.define_finalizer(obj, handler) exit 0 RUBY @@ -111,7 +111,7 @@ describe "ObjectSpace.define_finalizer" do it "calls a finalizer at exit even if it is self-referencing" do code = <<-RUBY - obj = "Test" + obj = +"Test" handler = Proc.new { puts "finalizer run" } ObjectSpace.define_finalizer(obj, handler) exit 0 @@ -141,9 +141,9 @@ describe "ObjectSpace.define_finalizer" do it "calls a finalizer defined in a finalizer running at exit" do code = <<-RUBY - obj = "Test" + obj = +"Test" handler = Proc.new do - obj2 = "Test" + obj2 = +"Test" handler2 = Proc.new { puts "finalizer 2 run" } ObjectSpace.define_finalizer(obj2, handler2) exit 0 @@ -156,7 +156,7 @@ describe "ObjectSpace.define_finalizer" do end it "allows multiple finalizers with different 'callables' to be defined" do - code = <<-RUBY + code = <<-'RUBY' obj = Object.new ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized1\n" }) @@ -168,25 +168,48 @@ describe "ObjectSpace.define_finalizer" do ruby_exe(code).lines.sort.should == ["finalized1\n", "finalized2\n"] end - ruby_version_is "3.1" do - describe "when $VERBOSE is not nil" do - it "warns if an exception is raised in finalizer" do - code = <<-RUBY - ObjectSpace.define_finalizer(Object.new) { raise "finalizing" } - RUBY + it "defines same finalizer only once" do + code = <<~RUBY + obj = Object.new + p = proc { |id| print "ok" } + ObjectSpace.define_finalizer(obj, p.dup) + ObjectSpace.define_finalizer(obj, p.dup) + RUBY - ruby_exe(code, args: "2>&1").should include("warning: Exception in finalizer", "finalizing") - end + ruby_exe(code).should == "ok" + end + + it "returns the defined finalizer" do + obj = Object.new + p = proc { |id| } + p2 = p.dup + + ret = ObjectSpace.define_finalizer(obj, p) + ret.should == [0, p] + ret[1].should.equal?(p) + + ret = ObjectSpace.define_finalizer(obj, p2) + ret.should == [0, p] + ret[1].should.equal?(p) + end + + describe "when $VERBOSE is not nil" do + it "warns if an exception is raised in finalizer" do + code = <<-RUBY + ObjectSpace.define_finalizer(Object.new) { raise "finalizing" } + RUBY + + ruby_exe(code, args: "2>&1").should include("warning: Exception in finalizer", "finalizing") end + end - describe "when $VERBOSE is nil" do - it "does not warn even if an exception is raised in finalizer" do - code = <<-RUBY - ObjectSpace.define_finalizer(Object.new) { raise "finalizing" } - RUBY + describe "when $VERBOSE is nil" do + it "does not warn even if an exception is raised in finalizer" do + code = <<-RUBY + ObjectSpace.define_finalizer(Object.new) { raise "finalizing" } + RUBY - ruby_exe(code, args: "2>&1", options: "-W0").should == "" - end + ruby_exe(code, args: "2>&1", options: "-W0").should == "" end end end diff --git a/spec/ruby/core/objectspace/finalizers_spec.rb b/spec/ruby/core/objectspace/finalizers_spec.rb deleted file mode 100644 index e7f20fc8a0..0000000000 --- a/spec/ruby/core/objectspace/finalizers_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -describe "ObjectSpace.finalizers" do - it "needs to be reviewed for spec completeness" -end diff --git a/spec/ruby/core/objectspace/remove_finalizer_spec.rb b/spec/ruby/core/objectspace/remove_finalizer_spec.rb deleted file mode 100644 index 0b2b8cf16b..0000000000 --- a/spec/ruby/core/objectspace/remove_finalizer_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -describe "ObjectSpace.remove_finalizer" do - it "needs to be reviewed for spec completeness" -end diff --git a/spec/ruby/core/objectspace/undefine_finalizer_spec.rb b/spec/ruby/core/objectspace/undefine_finalizer_spec.rb index 11d43121f8..f57d5a7845 100644 --- a/spec/ruby/core/objectspace/undefine_finalizer_spec.rb +++ b/spec/ruby/core/objectspace/undefine_finalizer_spec.rb @@ -1,5 +1,33 @@ require_relative '../../spec_helper' describe "ObjectSpace.undefine_finalizer" do - it "needs to be reviewed for spec completeness" + it "removes finalizers for an object" do + code = <<~RUBY + obj = Object.new + ObjectSpace.define_finalizer(obj, proc { |id| puts "hello" }) + ObjectSpace.undefine_finalizer(obj) + RUBY + + ruby_exe(code).should.empty? + end + + it "should not remove finalizers for a frozen object" do + code = <<~RUBY + obj = Object.new + ObjectSpace.define_finalizer(obj, proc { |id| print "ok" }) + obj.freeze + begin + ObjectSpace.undefine_finalizer(obj) + rescue + end + RUBY + + ruby_exe(code).should == "ok" + end + + it "should raise when removing finalizers for a frozen object" do + obj = Object.new + obj.freeze + -> { ObjectSpace.undefine_finalizer(obj) }.should raise_error(FrozenError) + end end diff --git a/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb b/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb new file mode 100644 index 0000000000..8050e2c307 --- /dev/null +++ b/spec/ruby/core/objectspace/weakkeymap/clear_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../../spec_helper' + +ruby_version_is '3.3' do + describe "ObjectSpace::WeakKeyMap#clear" do + it "removes all the entries" do + m = ObjectSpace::WeakKeyMap.new + + key = Object.new + value = Object.new + m[key] = value + + key2 = Object.new + value2 = Object.new + m[key2] = value2 + + m.clear + + m.key?(key).should == false + m.key?(key2).should == false + end + + it "returns self" do + m = ObjectSpace::WeakKeyMap.new + m.clear.should.equal?(m) + end + end +end diff --git a/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb b/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb index 6e534b8ea8..3cd61355d6 100644 --- a/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/delete_spec.rb @@ -33,8 +33,19 @@ ruby_version_is '3.3' do end it "returns nil if the key is not found when no block is given" do - m = ObjectSpace::WeakMap.new + m = ObjectSpace::WeakKeyMap.new m.delete(Object.new).should == nil end + + it "returns nil when a key cannot be garbage collected" do + map = ObjectSpace::WeakKeyMap.new + + map.delete(1).should == nil + map.delete(1.0).should == nil + map.delete(:a).should == nil + map.delete(true).should == nil + map.delete(false).should == nil + map.delete(nil).should == nil + end end end diff --git a/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb b/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb index 862480cd02..51368e8d3b 100644 --- a/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb @@ -1,4 +1,5 @@ require_relative '../../../spec_helper' +require_relative 'fixtures/classes' ruby_version_is "3.3" do describe "ObjectSpace::WeakKeyMap#[]" do @@ -15,12 +16,92 @@ ruby_version_is "3.3" do map[key2].should == ref2 end - it "matches using equality semantics" do + it "compares keys with #eql? semantics" do + map = ObjectSpace::WeakKeyMap.new + key = [1.0] + map[key] = "x" + map[[1]].should == nil + map[[1.0]].should == "x" + key.should == [1.0] # keep the key alive until here to keep the map entry + + map = ObjectSpace::WeakKeyMap.new + key = [1] + map[key] = "x" + map[[1.0]].should == nil + map[[1]].should == "x" + key.should == [1] # keep the key alive until here to keep the map entry + map = ObjectSpace::WeakKeyMap.new key1, key2 = %w[a a].map(&:upcase) ref = "x" map[key1] = ref map[key2].should == ref end + + it "compares key via #hash first" do + x = mock('0') + x.should_receive(:hash).and_return(0) + + map = ObjectSpace::WeakKeyMap.new + key = 'foo' + map[key] = :bar + map[x].should == nil + end + + it "does not compare keys with different #hash values via #eql?" do + x = mock('x') + x.should_not_receive(:eql?) + x.stub!(:hash).and_return(0) + + y = mock('y') + y.should_not_receive(:eql?) + y.stub!(:hash).and_return(1) + + map = ObjectSpace::WeakKeyMap.new + map[y] = 1 + map[x].should == nil + end + + it "compares keys with the same #hash value via #eql?" do + x = mock('x') + x.should_receive(:eql?).and_return(true) + x.stub!(:hash).and_return(42) + + y = mock('y') + y.should_not_receive(:eql?) + y.stub!(:hash).and_return(42) + + map = ObjectSpace::WeakKeyMap.new + map[y] = 1 + map[x].should == 1 + end + + it "finds a value via an identical key even when its #eql? isn't reflexive" do + x = mock('x') + x.should_receive(:hash).at_least(1).and_return(42) + x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI. + + map = ObjectSpace::WeakKeyMap.new + map[x] = :x + map[x].should == :x + end + + it "supports keys with private #hash method" do + key = WeakKeyMapSpecs::KeyWithPrivateHash.new + map = ObjectSpace::WeakKeyMap.new + map[key] = 42 + map[key].should == 42 + end + + it "returns nil and does not raise error when a key cannot be garbage collected" do + map = ObjectSpace::WeakKeyMap.new + + map[1].should == nil + map[1.0].should == nil + map[:a].should == nil + map[true].should == nil + map[false].should == nil + map[nil].should == nil + end end end diff --git a/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb b/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb index 689509d820..8db8d780c7 100644 --- a/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb @@ -8,10 +8,6 @@ ruby_version_is "3.3" do map[key].should == value end - def should_not_accept(map, key, value) - -> { map[key] = value }.should raise_error(ArgumentError) - end - it "is correct" do map = ObjectSpace::WeakKeyMap.new key1, key2 = %w[a b].map(&:upcase) @@ -24,7 +20,7 @@ ruby_version_is "3.3" do it "requires the keys to implement #hash" do map = ObjectSpace::WeakKeyMap.new - -> { map[BasicObject.new] = 1 }.should raise_error(NoMethodError, "undefined method `hash' for an instance of BasicObject") + -> { map[BasicObject.new] = 1 }.should raise_error(NoMethodError, /undefined method [`']hash' for an instance of BasicObject/) end it "accepts frozen keys or values" do @@ -40,32 +36,47 @@ ruby_version_is "3.3" do should_accept(map, y, x) end - it "rejects symbols as keys" do + it "does not duplicate and freeze String keys (like Hash#[]= does)" do map = ObjectSpace::WeakKeyMap.new - should_not_accept(map, :foo, true) - should_not_accept(map, rand.to_s.to_sym, true) - end + key = +"a" + map[key] = 1 - it "rejects integers as keys" do - map = ObjectSpace::WeakKeyMap.new - should_not_accept(map, 42, true) - should_not_accept(map, 2 ** 68, true) - end + map.getkey("a").should.equal? key + map.getkey("a").should_not.frozen? - it "rejects floats as keys" do - map = ObjectSpace::WeakKeyMap.new - should_not_accept(map, 4.2, true) + key.should == "a" # keep the key alive until here to keep the map entry end - it "rejects booleans as keys" do - map = ObjectSpace::WeakKeyMap.new - should_not_accept(map, true, true) - should_not_accept(map, false, true) - end + context "a key cannot be garbage collected" do + it "raises ArgumentError when Integer is used as a key" do + map = ObjectSpace::WeakKeyMap.new + -> { map[1] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) + end - it "rejects nil as keys" do - map = ObjectSpace::WeakKeyMap.new - should_not_accept(map, nil, true) + it "raises ArgumentError when Float is used as a key" do + map = ObjectSpace::WeakKeyMap.new + -> { map[1.0] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) + end + + it "raises ArgumentError when Symbol is used as a key" do + map = ObjectSpace::WeakKeyMap.new + -> { map[:a] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) + end + + it "raises ArgumentError when true is used as a key" do + map = ObjectSpace::WeakKeyMap.new + -> { map[true] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) + end + + it "raises ArgumentError when false is used as a key" do + map = ObjectSpace::WeakKeyMap.new + -> { map[false] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) + end + + it "raises ArgumentError when nil is used as a key" do + map = ObjectSpace::WeakKeyMap.new + -> { map[nil] = "x" }.should raise_error(ArgumentError, /WeakKeyMap (keys )?must be garbage collectable/) + end end end end diff --git a/spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb b/spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb new file mode 100644 index 0000000000..0fd04551b5 --- /dev/null +++ b/spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb @@ -0,0 +1,5 @@ +module WeakKeyMapSpecs + class KeyWithPrivateHash + private :hash + end +end diff --git a/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb b/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb index 3af0186f27..8a2dbf809d 100644 --- a/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb @@ -9,6 +9,20 @@ ruby_version_is "3.3" do map[key1] = true map.getkey(key2).should equal(key1) map.getkey("X").should == nil + + key1.should == "A" # keep the key alive until here to keep the map entry + key2.should == "A" # keep the key alive until here to keep the map entry + end + + it "returns nil when a key cannot be garbage collected" do + map = ObjectSpace::WeakKeyMap.new + + map.getkey(1).should == nil + map.getkey(1.0).should == nil + map.getkey(:a).should == nil + map.getkey(true).should == nil + map.getkey(false).should == nil + map.getkey(nil).should == nil end end end diff --git a/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb b/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb index 557fbc8733..319f050970 100644 --- a/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb @@ -12,6 +12,10 @@ ruby_version_is "3.3" do map.inspect.should =~ /\A\#<ObjectSpace::WeakKeyMap:0x\h+ size=2>\z/ map[key3] = 3 map.inspect.should =~ /\A\#<ObjectSpace::WeakKeyMap:0x\h+ size=2>\z/ + + key1.should == "foo" # keep the key alive until here to keep the map entry + key2.should == "bar" # keep the key alive until here to keep the map entry + key3.should == "bar" # keep the key alive until here to keep the map entry end end end diff --git a/spec/ruby/core/objectspace/weakkeymap/key_spec.rb b/spec/ruby/core/objectspace/weakkeymap/key_spec.rb index 2af9c2b8e7..a9a2e12432 100644 --- a/spec/ruby/core/objectspace/weakkeymap/key_spec.rb +++ b/spec/ruby/core/objectspace/weakkeymap/key_spec.rb @@ -29,5 +29,16 @@ ruby_version_is "3.3" do map[key] = nil map.key?(key).should == true end + + it "returns false when a key cannot be garbage collected" do + map = ObjectSpace::WeakKeyMap.new + + map.key?(1).should == false + map.key?(1.0).should == false + map.key?(:a).should == false + map.key?(true).should == false + map.key?(false).should == false + map.key?(nil).should == false + end end end diff --git a/spec/ruby/core/proc/arity_spec.rb b/spec/ruby/core/proc/arity_spec.rb index f7cb5ad0f8..5c7728cb30 100644 --- a/spec/ruby/core/proc/arity_spec.rb +++ b/spec/ruby/core/proc/arity_spec.rb @@ -268,6 +268,14 @@ describe "Proc#arity" do @a.arity.should == 3 @b.arity.should == 3 end + + # implicit rest + evaluate <<-ruby do + @a = lambda { |a, | } + ruby + + @a.arity.should == 1 + end end context "returns negative values" do @@ -530,6 +538,14 @@ describe "Proc#arity" do @a.arity.should == 1 @b.arity.should == 5 end + + # implicit rest + evaluate <<-ruby do + @a = proc { |a, | } + ruby + + @a.arity.should == 1 + end end context "returns negative values" do diff --git a/spec/ruby/core/proc/clone_spec.rb b/spec/ruby/core/proc/clone_spec.rb index a1a1292654..730dc421a8 100644 --- a/spec/ruby/core/proc/clone_spec.rb +++ b/spec/ruby/core/proc/clone_spec.rb @@ -1,6 +1,30 @@ require_relative '../../spec_helper' +require_relative 'fixtures/common' require_relative 'shared/dup' describe "Proc#clone" do it_behaves_like :proc_dup, :clone + + ruby_bug "cloning a frozen proc is broken on Ruby 3.3", "3.3"..."3.4" do + it "preserves frozen status" do + proc = Proc.new { } + proc.freeze + proc.frozen?.should == true + proc.clone.frozen?.should == true + end + end + + ruby_version_is "3.3" do + it "calls #initialize_clone on subclass" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + dup = obj.clone + + dup.should_not equal(obj) + dup.class.should == ProcSpecs::MyProc2 + + dup.first.should == :a + dup.second.should == 2 + dup.initializer.should == :clone + end + end end diff --git a/spec/ruby/core/proc/curry_spec.rb b/spec/ruby/core/proc/curry_spec.rb index 24df2a8a72..6daabe0ee1 100644 --- a/spec/ruby/core/proc/curry_spec.rb +++ b/spec/ruby/core/proc/curry_spec.rb @@ -159,15 +159,14 @@ describe "Proc#curry with arity argument" do end it "can be passed more than _arity_ arguments if created from a proc" do - -> { @proc_add.curry(3)[1,2,3,4].should == 6 }.should_not - raise_error(ArgumentError) - -> { @proc_add.curry(1)[1,2].curry(3)[3,4,5,6].should == 6 }.should_not - raise_error(ArgumentError) + @proc_add.curry(3)[1,2,3,4].should == 6 + + @proc_add.curry(3)[1,2].curry(3)[3,4,5,6].should == 6 end it "raises an ArgumentError if passed more than _arity_ arguments when created from a lambda" do -> { @lambda_add.curry(3)[1,2,3,4] }.should raise_error(ArgumentError) - -> { @lambda_add.curry(1)[1,2].curry(3)[3,4,5,6] }.should raise_error(ArgumentError) + -> { @lambda_add.curry(3)[1,2].curry(3)[3,4,5,6] }.should raise_error(ArgumentError) end it "returns Procs with arities of -1 regardless of the value of _arity_" do diff --git a/spec/ruby/core/proc/dup_spec.rb b/spec/ruby/core/proc/dup_spec.rb index 6da2f3080c..716357d1f0 100644 --- a/spec/ruby/core/proc/dup_spec.rb +++ b/spec/ruby/core/proc/dup_spec.rb @@ -1,6 +1,28 @@ require_relative '../../spec_helper' +require_relative 'fixtures/common' require_relative 'shared/dup' describe "Proc#dup" do it_behaves_like :proc_dup, :dup + + it "resets frozen status" do + proc = Proc.new { } + proc.freeze + proc.frozen?.should == true + proc.dup.frozen?.should == false + end + + ruby_version_is "3.3" do + it "calls #initialize_dup on subclass" do + obj = ProcSpecs::MyProc2.new(:a, 2) { } + dup = obj.dup + + dup.should_not equal(obj) + dup.class.should == ProcSpecs::MyProc2 + + dup.first.should == :a + dup.second.should == 2 + dup.initializer.should == :dup + end + end end diff --git a/spec/ruby/core/proc/element_reference_spec.rb b/spec/ruby/core/proc/element_reference_spec.rb index 9077e44c34..81ceb91af5 100644 --- a/spec/ruby/core/proc/element_reference_spec.rb +++ b/spec/ruby/core/proc/element_reference_spec.rb @@ -17,7 +17,7 @@ describe "Proc#call on a Proc created with Kernel#lambda or Kernel#proc" do it_behaves_like :proc_call_on_proc_or_lambda, :call end -describe "Proc#[] with frozen_string_literals" do +describe "Proc#[] with frozen_string_literal: true/false" do it "doesn't duplicate frozen strings" do ProcArefSpecs.aref.frozen?.should be_false ProcArefSpecs.aref_freeze.frozen?.should be_true diff --git a/spec/ruby/core/proc/fixtures/common.rb b/spec/ruby/core/proc/fixtures/common.rb index 6e27a2dee7..dfe67d7ba8 100644 --- a/spec/ruby/core/proc/fixtures/common.rb +++ b/spec/ruby/core/proc/fixtures/common.rb @@ -32,7 +32,28 @@ module ProcSpecs @second = b end - attr_reader :first, :second + attr_reader :first, :second, :initializer + + def initialize_copy(other) + super + @initializer = :copy + @first = other.first + @second = other.second + end + + def initialize_dup(other) + super + @initializer = :dup + @first = other.first + @second = other.second + end + + def initialize_clone(other, **options) + super + @initializer = :clone + @first = other.first + @second = other.second + end end class Arity diff --git a/spec/ruby/core/proc/fixtures/proc_aref.rb b/spec/ruby/core/proc/fixtures/proc_aref.rb index a305667797..8ee355b14c 100644 --- a/spec/ruby/core/proc/fixtures/proc_aref.rb +++ b/spec/ruby/core/proc/fixtures/proc_aref.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false module ProcArefSpecs def self.aref proc {|a| a }["sometext"] diff --git a/spec/ruby/core/proc/lambda_spec.rb b/spec/ruby/core/proc/lambda_spec.rb index b2d3f50350..5c3c38fc2a 100644 --- a/spec/ruby/core/proc/lambda_spec.rb +++ b/spec/ruby/core/proc/lambda_spec.rb @@ -14,9 +14,11 @@ describe "Proc#lambda?" do Proc.new {}.lambda?.should be_false end - it "is preserved when passing a Proc with & to the lambda keyword" do - suppress_warning {lambda(&->{})}.lambda?.should be_true - suppress_warning {lambda(&proc{})}.lambda?.should be_false + ruby_version_is ""..."3.3" do + it "is preserved when passing a Proc with & to the lambda keyword" do + suppress_warning {lambda(&->{})}.lambda?.should be_true + suppress_warning {lambda(&proc{})}.lambda?.should be_false + end end it "is preserved when passing a Proc with & to the proc keyword" do diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index 1ffaf17315..cf8a8f5b12 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -20,19 +20,27 @@ describe "Proc#parameters" do proc {|x| }.parameters.first.first.should == :opt end - ruby_version_is "3.2" do - it "sets the first element of each sub-Array to :req if argument would be required if a lambda if lambda keyword used" do - proc {|x| }.parameters(lambda: true).first.first.should == :req - proc {|y,*x| }.parameters(lambda: true).first.first.should == :req - end + it "sets the first element of each sub-Array to :req for required argument if lambda keyword used" do + proc {|x| }.parameters(lambda: true).first.first.should == :req + proc {|y,*x| }.parameters(lambda: true).first.first.should == :req + end - it "regards named parameters in procs as required if lambda keyword used" do - proc {|x| }.parameters(lambda: true).first.first.should == :req - end + it "regards named parameters in procs as required if lambda keyword used" do + proc {|x| }.parameters(lambda: true).first.first.should == :req + end - it "regards named parameters in lambda as optional if lambda: false keyword used" do - -> x { }.parameters(lambda: false).first.first.should == :opt - end + it "regards named parameters in lambda as optional if lambda: false keyword used" do + -> x { }.parameters(lambda: false).first.first.should == :opt + end + + it "regards named parameters in procs and lambdas as required if lambda keyword is truthy" do + proc {|x| }.parameters(lambda: 123).first.first.should == :req + -> x { }.parameters(lambda: 123).first.first.should == :req + end + + it "ignores the lambda keyword if it is nil" do + proc {|x|}.parameters(lambda: nil).first.first.should == :opt + -> x { }.parameters(lambda: nil).first.first.should == :req end it "regards optional keyword parameters in procs as optional" do @@ -54,7 +62,7 @@ describe "Proc#parameters" do end it "regards keyword parameters in lambdas as required" do - eval("lambda {|x:| }").parameters.first.first.should == :keyreq + -> x: { }.parameters.first.first.should == :keyreq end it "sets the first element of each sub-Array to :rest for parameters prefixed with asterisks" do @@ -91,20 +99,25 @@ describe "Proc#parameters" do proc {|&block| }.parameters.first.last.should == :block end - it "ignores unnamed rest args" do + it "ignores unnamed rest arguments" do -> x {}.parameters.should == [[:req, :x]] end - ruby_version_is '3.2' do - it "adds * rest arg for \"star\" argument" do - -> x, * {}.parameters.should == [[:req, :x], [:rest, :*]] - end + it "ignores implicit rest arguments" do + proc { |x, | }.parameters.should == [[:opt, :x]] + -> x { }.parameters.should == [[:req, :x]] end - ruby_version_is ''...'3.2' do - it "adds nameless rest arg for \"star\" argument" do - -> x, * {}.parameters.should == [[:req, :x], [:rest]] - end + it "adds rest arg with name * for \"star\" argument" do + -> * {}.parameters.should == [[:rest, :*]] + end + + it "adds keyrest arg with ** as a name for \"double star\" argument" do + -> ** {}.parameters.should == [[:keyrest, :**]] + end + + it "adds block arg with name & for anonymous block argument" do + -> & {}.parameters.should == [[:block, :&]] end it "does not add locals as block options with a block and splat" do @@ -141,4 +154,22 @@ describe "Proc#parameters" do [:block, :_] ] end + + it "returns :nokey for **nil parameter" do + proc { |**nil| }.parameters.should == [[:nokey]] + end + + ruby_version_is "3.4"..."4.0" do + it "handles the usage of `it` as a parameter" do + eval("proc { it }").parameters.should == [[:opt, nil]] + eval("lambda { it }").parameters.should == [[:req]] + end + end + + ruby_version_is "4.0" do + it "handles the usage of `it` as a parameter" do + eval("proc { it }").parameters.should == [[:opt]] + eval("lambda { it }").parameters.should == [[:req]] + end + end end diff --git a/spec/ruby/core/proc/ruby2_keywords_spec.rb b/spec/ruby/core/proc/ruby2_keywords_spec.rb index ab67302231..d7f8f592e1 100644 --- a/spec/ruby/core/proc/ruby2_keywords_spec.rb +++ b/spec/ruby/core/proc/ruby2_keywords_spec.rb @@ -39,7 +39,7 @@ describe "Proc#ruby2_keywords" do end it "prints warning when a proc accepts keywords" do - f = -> a:, b: { } + f = -> *a, b: { } -> { f.ruby2_keywords @@ -47,10 +47,20 @@ describe "Proc#ruby2_keywords" do end it "prints warning when a proc accepts keyword splat" do - f = -> **a { } + f = -> *a, **b { } -> { f.ruby2_keywords }.should complain(/Skipping set of ruby2_keywords flag for/) end + + ruby_version_is "4.0" do + it "prints warning when a proc accepts post arguments" do + f = -> *a, b { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end end diff --git a/spec/ruby/core/proc/shared/dup.rb b/spec/ruby/core/proc/shared/dup.rb index 4480f3d0c9..1266337f94 100644 --- a/spec/ruby/core/proc/shared/dup.rb +++ b/spec/ruby/core/proc/shared/dup.rb @@ -8,11 +8,32 @@ describe :proc_dup, shared: true do a.call.should == b.call end - ruby_version_is "3.2" do - it "returns an instance of subclass" do - cl = Class.new(Proc) + it "returns an instance of subclass" do + cl = Class.new(Proc) - cl.new{}.send(@method).class.should == cl + cl.new{}.send(@method).class.should == cl + end + + ruby_version_is "3.4" do + it "copies instance variables" do + proc = -> { "hello" } + proc.instance_variable_set(:@ivar, 1) + cl = proc.send(@method) + cl.instance_variables.should == [:@ivar] + end + + it "copies the finalizer" do + code = <<-'RUBY' + obj = Proc.new { } + + ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) + + obj.clone + + exit 0 + RUBY + + ruby_exe(code).lines.sort.should == ["finalized\n", "finalized\n"] end end end diff --git a/spec/ruby/core/proc/shared/to_s.rb b/spec/ruby/core/proc/shared/to_s.rb index f1e2f416fc..a52688a89f 100644 --- a/spec/ruby/core/proc/shared/to_s.rb +++ b/spec/ruby/core/proc/shared/to_s.rb @@ -31,13 +31,13 @@ describe :proc_to_s, shared: true do describe "for a proc created with UnboundMethod#to_proc" do it "returns a description including '(lambda)' and optionally including file and line number" do - def hello; end - s = method("hello").to_proc.send(@method) - if s.include? __FILE__ - s.should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ - 3} \(lambda\)>$/ - else - s.should =~ /^#<Proc:([^ ]*?) \(lambda\)>$/ - end + def hello; end + s = method("hello").to_proc.send(@method) + if s.include? __FILE__ + s.should =~ /^#<Proc:([^ ]*?) #{Regexp.escape __FILE__}:#{__LINE__ - 3} \(lambda\)>$/ + else + s.should =~ /^#<Proc:([^ ]*?) \(lambda\)>$/ + end end it "has a binary encoding" do diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index a5895a7fcb..fd33f21a26 100644 --- a/spec/ruby/core/proc/source_location_spec.rb +++ b/spec/ruby/core/proc/source_location_spec.rb @@ -17,57 +17,64 @@ describe "Proc#source_location" do end it "sets the first value to the path of the file in which the proc was defined" do - file = @proc.source_location.first + file = @proc.source_location[0] file.should be_an_instance_of(String) - file.should == File.realpath('../fixtures/source_location.rb', __FILE__) + file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @proc_new.source_location.first + file = @proc_new.source_location[0] file.should be_an_instance_of(String) - file.should == File.realpath('../fixtures/source_location.rb', __FILE__) + file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @lambda.source_location.first + file = @lambda.source_location[0] file.should be_an_instance_of(String) - file.should == File.realpath('../fixtures/source_location.rb', __FILE__) + file.should == File.realpath('fixtures/source_location.rb', __dir__) - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) - file.should == File.realpath('../fixtures/source_location.rb', __FILE__) + file.should == File.realpath('fixtures/source_location.rb', __dir__) end - it "sets the last value to an Integer representing the line on which the proc was defined" do - line = @proc.source_location.last + it "sets the second value to an Integer representing the line on which the proc was defined" do + line = @proc.source_location[1] line.should be_an_instance_of(Integer) line.should == 4 - line = @proc_new.source_location.last + line = @proc_new.source_location[1] line.should be_an_instance_of(Integer) line.should == 12 - line = @lambda.source_location.last + line = @lambda.source_location[1] line.should be_an_instance_of(Integer) line.should == 8 - line = @method.source_location.last + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 15 end it "works even if the proc was created on the same line" do - proc { true }.source_location.should == [__FILE__, __LINE__] - Proc.new { true }.source_location.should == [__FILE__, __LINE__] - -> { true }.source_location.should == [__FILE__, __LINE__] + ruby_version_is(""..."4.1") do + proc { true }.source_location.should == [__FILE__, __LINE__] + Proc.new { true }.source_location.should == [__FILE__, __LINE__] + -> { true }.source_location.should == [__FILE__, __LINE__] + end + ruby_version_is("4.1") do + proc { true }.source_location.should == [__FILE__, __LINE__, 11, __LINE__, 19] + Proc.new { true }.source_location.should == [__FILE__, __LINE__, 15, __LINE__, 23] + -> { true }.source_location.should == [__FILE__, __LINE__, 6, __LINE__, 17] + end end it "returns the first line of a multi-line proc (i.e. the line containing 'proc do')" do - ProcSpecs::SourceLocation.my_multiline_proc.source_location.last.should == 20 - ProcSpecs::SourceLocation.my_multiline_proc_new.source_location.last.should == 34 - ProcSpecs::SourceLocation.my_multiline_lambda.source_location.last.should == 27 + ProcSpecs::SourceLocation.my_multiline_proc.source_location[1].should == 20 + ProcSpecs::SourceLocation.my_multiline_proc_new.source_location[1].should == 34 + ProcSpecs::SourceLocation.my_multiline_lambda.source_location[1].should == 27 end it "returns the location of the proc's body; not necessarily the proc itself" do - ProcSpecs::SourceLocation.my_detached_proc.source_location.last.should == 41 - ProcSpecs::SourceLocation.my_detached_proc_new.source_location.last.should == 51 - ProcSpecs::SourceLocation.my_detached_lambda.source_location.last.should == 46 + ProcSpecs::SourceLocation.my_detached_proc.source_location[1].should == 41 + ProcSpecs::SourceLocation.my_detached_proc_new.source_location[1].should == 51 + ProcSpecs::SourceLocation.my_detached_lambda.source_location[1].should == 46 end it "returns the same value for a proc-ified method as the method reports" do @@ -86,6 +93,12 @@ describe "Proc#source_location" do it "works for eval with a given line" do proc = eval('-> {}', nil, "foo", 100) - proc.source_location.should == ["foo", 100] + location = proc.source_location + ruby_version_is(""..."4.1") do + location.should == ["foo", 100] + end + ruby_version_is("4.1") do + location.should == ["foo", 100, 0, 100, 5] + end end end diff --git a/spec/ruby/core/process/_fork_spec.rb b/spec/ruby/core/process/_fork_spec.rb index 6f711ad2dd..e1f45e2656 100644 --- a/spec/ruby/core/process/_fork_spec.rb +++ b/spec/ruby/core/process/_fork_spec.rb @@ -1,24 +1,24 @@ require_relative '../../spec_helper' -ruby_version_is "3.1" do - describe "Process._fork" do - it "for #respond_to? returns the same as Process.respond_to?(:fork)" do - Process.respond_to?(:_fork).should == Process.respond_to?(:fork) - end +describe "Process._fork" do + it "for #respond_to? returns the same as Process.respond_to?(:fork)" do + Process.respond_to?(:_fork).should == Process.respond_to?(:fork) + end - guard_not -> { Process.respond_to?(:fork) } do - it "raises a NotImplementedError when called" do - -> { Process._fork }.should raise_error(NotImplementedError) - end + # Using respond_to? in a guard here is OK because the correct semantics + # are that _fork is implemented if and only if fork is (see above). + guard_not -> { Process.respond_to?(:fork) } do + it "raises a NotImplementedError when called" do + -> { Process._fork }.should raise_error(NotImplementedError) end + end - guard -> { Process.respond_to?(:fork) } do - it "is called by Process#fork" do - Process.should_receive(:_fork).once.and_return(42) + guard -> { Process.respond_to?(:fork) } do + it "is called by Process#fork" do + Process.should_receive(:_fork).once.and_return(42) - pid = Process.fork {} - pid.should equal(42) - end + pid = Process.fork {} + pid.should equal(42) end end end diff --git a/spec/ruby/core/process/argv0_spec.rb b/spec/ruby/core/process/argv0_spec.rb index 7c2342f959..f5aba719e9 100644 --- a/spec/ruby/core/process/argv0_spec.rb +++ b/spec/ruby/core/process/argv0_spec.rb @@ -8,7 +8,7 @@ describe "Process.argv0" do it "is the path given as the main script and the same as __FILE__" do script = "fixtures/argv0.rb" - Dir.chdir(File.dirname(__FILE__)) do + Dir.chdir(__dir__) do ruby_exe(script).should == "#{script}\n#{script}\nOK" end end diff --git a/spec/ruby/core/process/constants_spec.rb b/spec/ruby/core/process/constants_spec.rb index 4130bb58a5..57cacadef2 100644 --- a/spec/ruby/core/process/constants_spec.rb +++ b/spec/ruby/core/process/constants_spec.rb @@ -2,63 +2,91 @@ require_relative '../../spec_helper' describe "Process::Constants" do platform_is :darwin, :netbsd, :freebsd do - it "has the correct constant values on BSD-like systems" do - Process::WNOHANG.should == 1 - Process::WUNTRACED.should == 2 - Process::PRIO_PROCESS.should == 0 - Process::PRIO_PGRP.should == 1 - Process::PRIO_USER.should == 2 - Process::RLIM_INFINITY.should == 9223372036854775807 - Process::RLIMIT_CPU.should == 0 - Process::RLIMIT_FSIZE.should == 1 - Process::RLIMIT_DATA.should == 2 - Process::RLIMIT_STACK.should == 3 - Process::RLIMIT_CORE.should == 4 - Process::RLIMIT_RSS.should == 5 - Process::RLIMIT_MEMLOCK.should == 6 - Process::RLIMIT_NPROC.should == 7 - Process::RLIMIT_NOFILE.should == 8 + it "are all present on BSD-like systems" do + %i[ + WNOHANG + WUNTRACED + PRIO_PROCESS + PRIO_PGRP + PRIO_USER + RLIM_INFINITY + RLIMIT_CPU + RLIMIT_FSIZE + RLIMIT_DATA + RLIMIT_STACK + RLIMIT_CORE + RLIMIT_RSS + RLIMIT_MEMLOCK + RLIMIT_NPROC + RLIMIT_NOFILE + ].each do |const| + Process.const_defined?(const).should be_true + Process.const_get(const).should be_an_instance_of(Integer) + end end end platform_is :darwin do - it "has the correct constant values on Darwin" do - Process::RLIM_SAVED_MAX.should == 9223372036854775807 - Process::RLIM_SAVED_CUR.should == 9223372036854775807 - Process::RLIMIT_AS.should == 5 + it "are all present on Darwin" do + %i[ + RLIM_SAVED_MAX + RLIM_SAVED_CUR + RLIMIT_AS + ].each do |const| + Process.const_defined?(const).should be_true + Process.const_get(const).should be_an_instance_of(Integer) + end end end platform_is :linux do - it "has the correct constant values on Linux" do - Process::WNOHANG.should == 1 - Process::WUNTRACED.should == 2 - Process::PRIO_PROCESS.should == 0 - Process::PRIO_PGRP.should == 1 - Process::PRIO_USER.should == 2 - Process::RLIMIT_CPU.should == 0 - Process::RLIMIT_FSIZE.should == 1 - Process::RLIMIT_DATA.should == 2 - Process::RLIMIT_STACK.should == 3 - Process::RLIMIT_CORE.should == 4 - Process::RLIMIT_RSS.should == 5 - Process::RLIMIT_NPROC.should == 6 - Process::RLIMIT_NOFILE.should == 7 - Process::RLIMIT_MEMLOCK.should == 8 - Process::RLIMIT_AS.should == 9 - - # These values appear to change according to the platform. - values = [4294967295, 9223372036854775807, 18446744073709551615] - values.include?(Process::RLIM_INFINITY).should be_true - values.include?(Process::RLIM_SAVED_MAX).should be_true - values.include?(Process::RLIM_SAVED_CUR).should be_true + it "are all present on Linux" do + %i[ + WNOHANG + WUNTRACED + PRIO_PROCESS + PRIO_PGRP + PRIO_USER + RLIMIT_CPU + RLIMIT_FSIZE + RLIMIT_DATA + RLIMIT_STACK + RLIMIT_CORE + RLIMIT_RSS + RLIMIT_NPROC + RLIMIT_NOFILE + RLIMIT_MEMLOCK + RLIMIT_AS + RLIM_INFINITY + RLIM_SAVED_MAX + RLIM_SAVED_CUR + ].each do |const| + Process.const_defined?(const).should be_true + Process.const_get(const).should be_an_instance_of(Integer) + end end end platform_is :netbsd, :freebsd do - it "Process::RLIMIT_SBSIZE" do - Process::RLIMIT_SBSIZE.should == 9 # FIXME: what's it equal? - Process::RLIMIT_AS.should == 10 + it "are all present on NetBSD and FreeBSD" do + %i[ + RLIMIT_SBSIZE + RLIMIT_AS + ].each do |const| + Process.const_defined?(const).should be_true + Process.const_get(const).should be_an_instance_of(Integer) + end + end + end + + platform_is :freebsd do + it "are all present on FreeBSD" do + %i[ + RLIMIT_NPTS + ].each do |const| + Process.const_defined?(const).should be_true + Process.const_get(const).should be_an_instance_of(Integer) + end end end diff --git a/spec/ruby/core/process/daemon_spec.rb b/spec/ruby/core/process/daemon_spec.rb index 70ffd1b320..20b0d743b9 100644 --- a/spec/ruby/core/process/daemon_spec.rb +++ b/spec/ruby/core/process/daemon_spec.rb @@ -2,6 +2,9 @@ require_relative '../../spec_helper' require_relative 'fixtures/common' platform_is_not :windows do + # macOS 15 is not working this examples + return if /darwin/ =~ RUBY_PLATFORM && /15/ =~ `sw_vers -productVersion` + describe :process_daemon_keep_stdio_open_false, shared: true do it "redirects stdout to /dev/null" do @daemon.invoke("keep_stdio_open_false_stdout", @object).should == "" diff --git a/spec/ruby/core/process/exec_spec.rb b/spec/ruby/core/process/exec_spec.rb index deb8913b6b..0f371b39c8 100644 --- a/spec/ruby/core/process/exec_spec.rb +++ b/spec/ruby/core/process/exec_spec.rb @@ -30,20 +30,20 @@ describe "Process.exec" do end it "raises Errno::EACCES when passed a directory" do - -> { Process.exec File.dirname(__FILE__) }.should raise_error(Errno::EACCES) + -> { Process.exec __dir__ }.should raise_error(Errno::EACCES) end it "runs the specified command, replacing current process" do - ruby_exe('Process.exec "echo hello"; puts "fail"', escape: true).should == "hello\n" + ruby_exe('Process.exec "echo hello"; puts "fail"').should == "hello\n" end it "sets the current directory when given the :chdir option" do tmpdir = tmp("")[0..-2] platform_is_not :windows do - ruby_exe("Process.exec(\"pwd\", chdir: #{tmpdir.inspect})", escape: true).should == "#{tmpdir}\n" + ruby_exe("Process.exec(\"pwd\", chdir: #{tmpdir.inspect})").should == "#{tmpdir}\n" end platform_is :windows do - ruby_exe("Process.exec(\"cd\", chdir: #{tmpdir.inspect})", escape: true).tr('\\', '/').should == "#{tmpdir}\n" + ruby_exe("Process.exec(\"cd\", chdir: #{tmpdir.inspect})").tr('\\', '/').should == "#{tmpdir}\n" end end @@ -73,13 +73,13 @@ describe "Process.exec" do platform_is_not :windows do it "subjects the specified command to shell expansion" do result = Dir.chdir(@dir) do - ruby_exe('Process.exec "echo *"', escape: true) + ruby_exe('Process.exec "echo *"') end result.chomp.should == @name end it "creates an argument array with shell parsing semantics for whitespace" do - ruby_exe('Process.exec "echo a b c d"', escape: true).should == "a b c d\n" + ruby_exe('Process.exec "echo a b c d"').should == "a b c d\n" end end @@ -87,13 +87,13 @@ describe "Process.exec" do # There is no shell expansion on Windows it "does not subject the specified command to shell expansion on Windows" do result = Dir.chdir(@dir) do - ruby_exe('Process.exec "echo *"', escape: true) + ruby_exe('Process.exec "echo *"') end result.should == "*\n" end it "does not create an argument array with shell parsing semantics for whitespace on Windows" do - ruby_exe('Process.exec "echo a b c d"', escape: true).should == "a b c d\n" + ruby_exe('Process.exec "echo a b c d"').should == "a b c d\n" end end @@ -105,7 +105,7 @@ describe "Process.exec" do platform_is :windows do cmd = '"cmd.exe", "/C", "echo", "*"' end - ruby_exe("Process.exec #{cmd}", escape: true).should == "*\n" + ruby_exe("Process.exec #{cmd}").should == "*\n" end end @@ -124,29 +124,29 @@ describe "Process.exec" do end it "sets environment variables in the child environment" do - ruby_exe('Process.exec({"FOO" => "BAR"}, "echo ' + var + '")', escape: true).should == "BAR\n" + ruby_exe('Process.exec({"FOO" => "BAR"}, "echo ' + var + '")').should == "BAR\n" end it "unsets environment variables whose value is nil" do platform_is_not :windows do - ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")', escape: true).should == "\n" + ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")').should == "\n" end platform_is :windows do # On Windows, echo-ing a non-existent env var is treated as echo-ing any other string of text - ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")', escape: true).should == var + "\n" + ruby_exe('Process.exec({"FOO" => nil}, "echo ' + var + '")').should == var + "\n" end end it "coerces environment argument using to_hash" do - ruby_exe('o = Object.new; def o.to_hash; {"FOO" => "BAR"}; end; Process.exec(o, "echo ' + var + '")', escape: true).should == "BAR\n" + ruby_exe('o = Object.new; def o.to_hash; {"FOO" => "BAR"}; end; Process.exec(o, "echo ' + var + '")').should == "BAR\n" end it "unsets other environment variables when given a true :unsetenv_others option" do platform_is_not :windows do - ruby_exe('Process.exec("echo ' + var + '", unsetenv_others: true)', escape: true).should == "\n" + ruby_exe('Process.exec("echo ' + var + '", unsetenv_others: true)').should == "\n" end platform_is :windows do - ruby_exe('Process.exec("' + ENV['COMSPEC'].gsub('\\', '\\\\\\') + ' /C echo ' + var + '", unsetenv_others: true)', escape: true).should == var + "\n" + ruby_exe('Process.exec("' + ENV['COMSPEC'].gsub('\\', '\\\\\\') + ' /C echo ' + var + '", unsetenv_others: true)').should == var + "\n" end end end @@ -154,19 +154,19 @@ describe "Process.exec" do describe "with a command array" do it "uses the first element as the command name and the second as the argv[0] value" do platform_is_not :windows do - ruby_exe('Process.exec(["/bin/sh", "argv_zero"], "-c", "echo $0")', escape: true).should == "argv_zero\n" + ruby_exe('Process.exec(["/bin/sh", "argv_zero"], "-c", "echo $0")').should == "argv_zero\n" end platform_is :windows do - ruby_exe('Process.exec(["cmd.exe", "/C"], "/C", "echo", "argv_zero")', escape: true).should == "argv_zero\n" + ruby_exe('Process.exec(["cmd.exe", "/C"], "/C", "echo", "argv_zero")').should == "argv_zero\n" end end it "coerces the argument using to_ary" do platform_is_not :windows do - ruby_exe('o = Object.new; def o.to_ary; ["/bin/sh", "argv_zero"]; end; Process.exec(o, "-c", "echo $0")', escape: true).should == "argv_zero\n" + ruby_exe('o = Object.new; def o.to_ary; ["/bin/sh", "argv_zero"]; end; Process.exec(o, "-c", "echo $0")').should == "argv_zero\n" end platform_is :windows do - ruby_exe('o = Object.new; def o.to_ary; ["cmd.exe", "/C"]; end; Process.exec(o, "/C", "echo", "argv_zero")', escape: true).should == "argv_zero\n" + ruby_exe('o = Object.new; def o.to_ary; ["cmd.exe", "/C"]; end; Process.exec(o, "/C", "echo", "argv_zero")').should == "argv_zero\n" end end @@ -200,7 +200,7 @@ describe "Process.exec" do end EOC - ruby_exe(cmd, escape: true) + ruby_exe(cmd) child_fd = IO.read(@child_fd_file).to_i child_fd.to_i.should > STDERR.fileno @@ -216,7 +216,7 @@ describe "Process.exec" do Process.exec("#{ruby_cmd(map_fd_fixture)} \#{f.fileno}", f.fileno => f.fileno) EOC - output = ruby_exe(cmd, escape: true) + output = ruby_exe(cmd) child_fd, close_on_exec = output.split child_fd.to_i.should > STDERR.fileno @@ -232,7 +232,7 @@ describe "Process.exec" do puts(f.close_on_exec?) EOC - output = ruby_exe(cmd, escape: true) + output = ruby_exe(cmd) output.split.should == ['true', 'false'] end end diff --git a/spec/ruby/core/process/fixtures/clocks.rb b/spec/ruby/core/process/fixtures/clocks.rb index f043f6ac1f..5757e280be 100644 --- a/spec/ruby/core/process/fixtures/clocks.rb +++ b/spec/ruby/core/process/fixtures/clocks.rb @@ -2,7 +2,7 @@ module ProcessSpecs def self.clock_constants clocks = [] - platform_is_not :windows, :solaris do + platform_is_not :windows do clocks += Process.constants.select { |c| c.to_s.start_with?('CLOCK_') } # These require CAP_WAKE_ALARM and are not documented in diff --git a/spec/ruby/core/process/fixtures/kill.rb b/spec/ruby/core/process/fixtures/kill.rb index 0b88f8ee1f..b922a043f1 100644 --- a/spec/ruby/core/process/fixtures/kill.rb +++ b/spec/ruby/core/process/fixtures/kill.rb @@ -1,5 +1,3 @@ -require 'thread' - pid_file = ARGV.shift scenario = ARGV.shift diff --git a/spec/ruby/core/process/gid_spec.rb b/spec/ruby/core/process/gid_spec.rb index 07221da420..ca935ed520 100644 --- a/spec/ruby/core/process/gid_spec.rb +++ b/spec/ruby/core/process/gid_spec.rb @@ -3,8 +3,8 @@ require_relative '../../spec_helper' describe "Process.gid" do platform_is_not :windows do it "returns the correct gid for the user executing this process" do - current_gid_according_to_unix = `id -gr`.to_i - Process.gid.should == current_gid_according_to_unix + current_gid_according_to_unix = `id -gr`.to_i + Process.gid.should == current_gid_according_to_unix end end diff --git a/spec/ruby/core/process/setrlimit_spec.rb b/spec/ruby/core/process/setrlimit_spec.rb index b92f98fd40..ba8d1e04ca 100644 --- a/spec/ruby/core/process/setrlimit_spec.rb +++ b/spec/ruby/core/process/setrlimit_spec.rb @@ -73,20 +73,18 @@ describe "Process.setrlimit" do Process.setrlimit(:STACK, *Process.getrlimit(Process::RLIMIT_STACK)).should be_nil end - platform_is_not :solaris, :aix do + platform_is_not :aix do it "coerces :MEMLOCK into RLIMIT_MEMLOCK" do Process.setrlimit(:MEMLOCK, *Process.getrlimit(Process::RLIMIT_MEMLOCK)).should be_nil end end - platform_is_not :solaris do - it "coerces :NPROC into RLIMIT_NPROC" do - Process.setrlimit(:NPROC, *Process.getrlimit(Process::RLIMIT_NPROC)).should be_nil - end + it "coerces :NPROC into RLIMIT_NPROC" do + Process.setrlimit(:NPROC, *Process.getrlimit(Process::RLIMIT_NPROC)).should be_nil + end - it "coerces :RSS into RLIMIT_RSS" do - Process.setrlimit(:RSS, *Process.getrlimit(Process::RLIMIT_RSS)).should be_nil - end + it "coerces :RSS into RLIMIT_RSS" do + Process.setrlimit(:RSS, *Process.getrlimit(Process::RLIMIT_RSS)).should be_nil end platform_is :netbsd, :freebsd do @@ -155,20 +153,18 @@ describe "Process.setrlimit" do Process.setrlimit("STACK", *Process.getrlimit(Process::RLIMIT_STACK)).should be_nil end - platform_is_not :solaris, :aix do + platform_is_not :aix do it "coerces 'MEMLOCK' into RLIMIT_MEMLOCK" do Process.setrlimit("MEMLOCK", *Process.getrlimit(Process::RLIMIT_MEMLOCK)).should be_nil end end - platform_is_not :solaris do - it "coerces 'NPROC' into RLIMIT_NPROC" do - Process.setrlimit("NPROC", *Process.getrlimit(Process::RLIMIT_NPROC)).should be_nil - end + it "coerces 'NPROC' into RLIMIT_NPROC" do + Process.setrlimit("NPROC", *Process.getrlimit(Process::RLIMIT_NPROC)).should be_nil + end - it "coerces 'RSS' into RLIMIT_RSS" do - Process.setrlimit("RSS", *Process.getrlimit(Process::RLIMIT_RSS)).should be_nil - end + it "coerces 'RSS' into RLIMIT_RSS" do + Process.setrlimit("RSS", *Process.getrlimit(Process::RLIMIT_RSS)).should be_nil end platform_is :netbsd, :freebsd do diff --git a/spec/ruby/core/process/spawn_spec.rb b/spec/ruby/core/process/spawn_spec.rb index c8a58c4d04..283a7f033d 100644 --- a/spec/ruby/core/process/spawn_spec.rb +++ b/spec/ruby/core/process/spawn_spec.rb @@ -714,7 +714,7 @@ describe "Process.spawn" do end it "raises an Errno::EACCES or Errno::EISDIR when passed a directory" do - -> { Process.spawn File.dirname(__FILE__) }.should raise_error(SystemCallError) { |e| + -> { Process.spawn __dir__ }.should raise_error(SystemCallError) { |e| [Errno::EACCES, Errno::EISDIR].should include(e.class) } end diff --git a/spec/ruby/core/process/status/bit_and_spec.rb b/spec/ruby/core/process/status/bit_and_spec.rb index 97f768fdc1..a805364629 100644 --- a/spec/ruby/core/process/status/bit_and_spec.rb +++ b/spec/ruby/core/process/status/bit_and_spec.rb @@ -1,5 +1,38 @@ require_relative '../../../spec_helper' -describe "Process::Status#&" do - it "needs to be reviewed for spec completeness" +ruby_version_is ""..."4.0" do + + describe "Process::Status#&" do + it "returns a bitwise and of the integer status of an exited child" do + suppress_warning do + ruby_exe("exit(29)", exit_status: 29) + ($? & 0).should == 0 + ($? & $?.to_i).should == $?.to_i + + # Actual value is implementation specific + platform_is :linux do + # 29 == 0b11101 + ($? & 0b1011100000000).should == 0b1010100000000 + end + end + end + + ruby_version_is "3.3"..."4.0" do + it "raises an ArgumentError if mask is negative" do + suppress_warning do + ruby_exe("exit(0)") + -> { + $? & -1 + }.should raise_error(ArgumentError, 'negative mask value: -1') + end + end + + it "shows a deprecation warning" do + ruby_exe("exit(0)") + -> { + $? & 0 + }.should complain(/warning: Process::Status#& is deprecated and will be removed .*use other Process::Status predicates instead/) + end + end + end end diff --git a/spec/ruby/core/process/status/right_shift_spec.rb b/spec/ruby/core/process/status/right_shift_spec.rb index e9dda437e8..355aaf4c95 100644 --- a/spec/ruby/core/process/status/right_shift_spec.rb +++ b/spec/ruby/core/process/status/right_shift_spec.rb @@ -1,5 +1,37 @@ require_relative '../../../spec_helper' -describe "Process::Status#>>" do - it "needs to be reviewed for spec completeness" +ruby_version_is ""..."4.0" do + + describe "Process::Status#>>" do + it "returns a right shift of the integer status of an exited child" do + suppress_warning do + ruby_exe("exit(29)", exit_status: 29) + ($? >> 0).should == $?.to_i + ($? >> 1).should == $?.to_i >> 1 + + # Actual value is implementation specific + platform_is :linux do + ($? >> 8).should == 29 + end + end + end + + ruby_version_is "3.3"..."4.0" do + it "raises an ArgumentError if shift value is negative" do + suppress_warning do + ruby_exe("exit(0)") + -> { + $? >> -1 + }.should raise_error(ArgumentError, 'negative shift value: -1') + end + end + + it "shows a deprecation warning" do + ruby_exe("exit(0)") + -> { + $? >> 0 + }.should complain(/warning: Process::Status#>> is deprecated and will be removed .*use other Process::Status attributes instead/) + end + end + end end diff --git a/spec/ruby/core/process/status/termsig_spec.rb b/spec/ruby/core/process/status/termsig_spec.rb index 5d286950f8..9a22dbea71 100644 --- a/spec/ruby/core/process/status/termsig_spec.rb +++ b/spec/ruby/core/process/status/termsig_spec.rb @@ -6,7 +6,7 @@ describe "Process::Status#termsig" do ruby_exe("exit(0)") end - it "returns true" do + it "returns nil" do $?.termsig.should be_nil end end diff --git a/spec/ruby/core/process/times_spec.rb b/spec/ruby/core/process/times_spec.rb index d2610f6415..d3bff2cda9 100644 --- a/spec/ruby/core/process/times_spec.rb +++ b/spec/ruby/core/process/times_spec.rb @@ -16,31 +16,4 @@ describe "Process.times" do Process.times.utime.should > user end end - - # TODO: The precision of `getrusage` depends on platforms (OpenBSD - # seems not supporting under-milliseconds in fact); this example is - # very questionable as an example of Ruby, and it just repeats the - # guard condition. - guard -> do - 1000.times.any? do - # If getrusage has precision beyond milliseconds, there will be - # very likely at least one non-zero microsecond results when - # repeating enough. - time = Process.clock_gettime(:GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID) - not ('%.6f' % time).end_with?('000') - end - rescue Errno::EINVAL - false - end do - it "uses getrusage when available to improve precision beyond milliseconds" do - max = 10_000 - - found = (max * 100).times.find do - time = Process.times.utime - !('%.6f' % time).end_with?('000') - end - - found.should_not == nil - end - end end diff --git a/spec/ruby/core/process/tms/cstime_spec.rb b/spec/ruby/core/process/tms/cstime_spec.rb new file mode 100644 index 0000000000..9c2d9e8632 --- /dev/null +++ b/spec/ruby/core/process/tms/cstime_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +describe "Process::Tms#cstime" do + it "returns cstime attribute" do + cstime = Object.new + Process::Tms.new(nil, nil, nil, cstime).cstime.should == cstime + end +end + +describe "Process::Tms#cstime=" do + it "assigns a value to the cstime attribute" do + cstime = Object.new + tms = Process::Tms.new + tms.cstime = cstime + tms.cstime.should == cstime + end +end diff --git a/spec/ruby/core/process/tms/cutime_spec.rb b/spec/ruby/core/process/tms/cutime_spec.rb new file mode 100644 index 0000000000..0ac3ff1964 --- /dev/null +++ b/spec/ruby/core/process/tms/cutime_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +describe "Process::Tms#cutime" do + it "returns cutime attribute" do + cutime = Object.new + Process::Tms.new(nil, nil, cutime, nil).cutime.should == cutime + end +end + +describe "Process::Tms#cutime=" do + it "assigns a value to the cutime attribute" do + cutime = Object.new + tms = Process::Tms.new + tms.cutime = cutime + tms.cutime.should == cutime + end +end diff --git a/spec/ruby/core/process/tms/stime_spec.rb b/spec/ruby/core/process/tms/stime_spec.rb new file mode 100644 index 0000000000..1e8371475f --- /dev/null +++ b/spec/ruby/core/process/tms/stime_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +describe "Process::Tms#stime" do + it "returns stime attribute" do + stime = Object.new + Process::Tms.new(nil, stime, nil, nil).stime.should == stime + end +end + +describe "Process::Tms#stime=" do + it "assigns a value to the stime attribute" do + stime = Object.new + tms = Process::Tms.new + tms.stime = stime + tms.stime.should == stime + end +end diff --git a/spec/ruby/core/process/tms/utime_spec.rb b/spec/ruby/core/process/tms/utime_spec.rb new file mode 100644 index 0000000000..403a31e2e6 --- /dev/null +++ b/spec/ruby/core/process/tms/utime_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../../spec_helper' + +describe "Process::Tms#utime" do + it "returns utime attribute" do + utime = Object.new + Process::Tms.new(utime, nil, nil, nil).utime.should == utime + end +end + +describe "Process::Tms#utime=" do + it "assigns a value to the ctime attribute" do + utime = Object.new + tms = Process::Tms.new + tms.utime = utime + tms.utime.should == utime + end +end diff --git a/spec/ruby/core/process/waitpid_spec.rb b/spec/ruby/core/process/waitpid_spec.rb index f7cf1a45a8..a02147b663 100644 --- a/spec/ruby/core/process/waitpid_spec.rb +++ b/spec/ruby/core/process/waitpid_spec.rb @@ -2,7 +2,8 @@ require_relative '../../spec_helper' describe "Process.waitpid" do it "returns nil when the process has not yet completed and WNOHANG is specified" do - pid = spawn("sleep 5") + cmd = platform_is(:windows) ? "timeout" : "sleep" + pid = spawn("#{cmd} 5") begin Process.waitpid(pid, Process::WNOHANG).should == nil Process.kill("KILL", pid) diff --git a/spec/ruby/core/queue/deq_spec.rb b/spec/ruby/core/queue/deq_spec.rb index f84d4220ea..a2784e6a63 100644 --- a/spec/ruby/core/queue/deq_spec.rb +++ b/spec/ruby/core/queue/deq_spec.rb @@ -7,7 +7,5 @@ describe "Queue#deq" do end describe "Queue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = Queue.new; q.push(1); q.deq(timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = Queue.new; q.push(1); q.deq(timeout: v) } end diff --git a/spec/ruby/core/queue/freeze_spec.rb b/spec/ruby/core/queue/freeze_spec.rb new file mode 100644 index 0000000000..ced2cc52dd --- /dev/null +++ b/spec/ruby/core/queue/freeze_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative '../../shared/queue/freeze' + +describe "Queue#freeze" do + it_behaves_like :queue_freeze, :freeze, -> { Queue.new } +end diff --git a/spec/ruby/core/queue/initialize_spec.rb b/spec/ruby/core/queue/initialize_spec.rb index c6c1ae63c5..592fbe2487 100644 --- a/spec/ruby/core/queue/initialize_spec.rb +++ b/spec/ruby/core/queue/initialize_spec.rb @@ -11,9 +11,21 @@ describe "Queue#initialize" do Queue.private_instance_methods.include?(:initialize).should == true end - ruby_version_is '3.1' do - it "adds all elements of the passed Enumerable to self" do - q = Queue.new([1, 2, 3]) + it "adds all elements of the passed Enumerable to self" do + q = Queue.new([1, 2, 3]) + q.size.should == 3 + q.should_not.empty? + q.pop.should == 1 + q.pop.should == 2 + q.pop.should == 3 + q.should.empty? + end + + describe "converts the given argument to an Array using #to_a" do + it "uses #to_a on the provided Enumerable" do + enumerable = MockObject.new('mock-enumerable') + enumerable.should_receive(:to_a).and_return([1, 2, 3]) + q = Queue.new(enumerable) q.size.should == 3 q.should_not.empty? q.pop.should == 1 @@ -22,41 +34,27 @@ describe "Queue#initialize" do q.should.empty? end - describe "converts the given argument to an Array using #to_a" do - it "uses #to_a on the provided Enumerable" do - enumerable = MockObject.new('mock-enumerable') - enumerable.should_receive(:to_a).and_return([1, 2, 3]) - q = Queue.new(enumerable) - q.size.should == 3 - q.should_not.empty? - q.pop.should == 1 - q.pop.should == 2 - q.pop.should == 3 - q.should.empty? - end - - it "raises a TypeError if the given argument can't be converted to an Array" do - -> { Queue.new(42) }.should raise_error(TypeError) - -> { Queue.new(:abc) }.should raise_error(TypeError) - end - - it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to an Array" do - enumerable = MockObject.new('mock-enumerable') - enumerable.should_receive(:to_a).and_raise(NoMethodError) - -> { Queue.new(enumerable) }.should raise_error(NoMethodError) - end + it "raises a TypeError if the given argument can't be converted to an Array" do + -> { Queue.new(42) }.should raise_error(TypeError) + -> { Queue.new(:abc) }.should raise_error(TypeError) end - it "raises TypeError if the provided Enumerable does not respond to #to_a" do + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to an Array" do enumerable = MockObject.new('mock-enumerable') - -> { Queue.new(enumerable) }.should raise_error(TypeError, "can't convert MockObject into Array") + enumerable.should_receive(:to_a).and_raise(NoMethodError) + -> { Queue.new(enumerable) }.should raise_error(NoMethodError) end + end - it "raises TypeError if #to_a does not return Array" do - enumerable = MockObject.new('mock-enumerable') - enumerable.should_receive(:to_a).and_return("string") + it "raises TypeError if the provided Enumerable does not respond to #to_a" do + enumerable = MockObject.new('mock-enumerable') + -> { Queue.new(enumerable) }.should raise_error(TypeError, "can't convert MockObject into Array") + end - -> { Queue.new(enumerable) }.should raise_error(TypeError, "can't convert MockObject to Array (MockObject#to_a gives String)") - end + it "raises TypeError if #to_a does not return Array" do + enumerable = MockObject.new('mock-enumerable') + enumerable.should_receive(:to_a).and_return("string") + + -> { Queue.new(enumerable) }.should raise_error(TypeError, "can't convert MockObject to Array (MockObject#to_a gives String)") end end diff --git a/spec/ruby/core/queue/pop_spec.rb b/spec/ruby/core/queue/pop_spec.rb index d344740834..3dff7db242 100644 --- a/spec/ruby/core/queue/pop_spec.rb +++ b/spec/ruby/core/queue/pop_spec.rb @@ -7,7 +7,5 @@ describe "Queue#pop" do end describe "Queue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = Queue.new; q.push(1); q.pop(timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = Queue.new; q.push(1); q.pop(timeout: v) } end diff --git a/spec/ruby/core/queue/shift_spec.rb b/spec/ruby/core/queue/shift_spec.rb index 64165e0b61..c105da74b2 100644 --- a/spec/ruby/core/queue/shift_spec.rb +++ b/spec/ruby/core/queue/shift_spec.rb @@ -7,7 +7,5 @@ describe "Queue#shift" do end describe "Queue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = Queue.new; q.push(1); q.shift(timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = Queue.new; q.push(1); q.shift(timeout: v) } end diff --git a/spec/ruby/core/random/bytes_spec.rb b/spec/ruby/core/random/bytes_spec.rb index ed1b3a7b41..c9be07cd3f 100644 --- a/spec/ruby/core/random/bytes_spec.rb +++ b/spec/ruby/core/random/bytes_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/bytes' @@ -9,7 +9,6 @@ describe "Random#bytes" do Random.new(33).bytes(2).should == Random.new(33).bytes(2) end - # Should double check this is official spec it "returns the same numeric output for a given seed across all implementations and platforms" do rnd = Random.new(33) rnd.bytes(2).should == "\x14\\" diff --git a/spec/ruby/core/random/default_spec.rb b/spec/ruby/core/random/default_spec.rb index 01d7430df8..9e4845986d 100644 --- a/spec/ruby/core/random/default_spec.rb +++ b/spec/ruby/core/random/default_spec.rb @@ -1,35 +1,7 @@ require_relative '../../spec_helper' describe "Random::DEFAULT" do - ruby_version_is ''...'3.2' do - it "returns a random number generator" do - suppress_warning do - Random::DEFAULT.should respond_to(:rand) - end - end - - it "changes seed on reboot" do - seed1 = ruby_exe('p Random::DEFAULT.seed', options: '--disable-gems') - seed2 = ruby_exe('p Random::DEFAULT.seed', options: '--disable-gems') - seed1.should != seed2 - end - - it "refers to the Random class" do - suppress_warning do - Random::DEFAULT.should.equal?(Random) - end - end - - it "is deprecated" do - -> { - Random::DEFAULT.should.equal?(Random) - }.should complain(/constant Random::DEFAULT is deprecated/) - end - end - - ruby_version_is '3.2' do - it "is no longer defined" do - Random.should_not.const_defined?(:DEFAULT) - end + it "is no longer defined" do + Random.should_not.const_defined?(:DEFAULT) end end diff --git a/spec/ruby/core/random/new_spec.rb b/spec/ruby/core/random/new_spec.rb index 90e2a9d6f2..69210cef03 100644 --- a/spec/ruby/core/random/new_spec.rb +++ b/spec/ruby/core/random/new_spec.rb @@ -11,7 +11,7 @@ describe "Random.new" do it "returns Random instances initialized with different seeds" do first = Random.new second = Random.new - (0..20).map { first.rand } .should_not == (0..20).map { second.rand } + (0..20).map { first.rand }.should_not == (0..20).map { second.rand } end it "accepts an Integer seed value as an argument" do diff --git a/spec/ruby/core/range/bsearch_spec.rb b/spec/ruby/core/range/bsearch_spec.rb index 9c93671d85..5254ab756c 100644 --- a/spec/ruby/core/range/bsearch_spec.rb +++ b/spec/ruby/core/range/bsearch_spec.rb @@ -9,22 +9,30 @@ describe "Range#bsearch" do it_behaves_like :enumeratorized_with_unknown_size, :bsearch, (1..3) it "raises a TypeError if the block returns an Object" do - -> { (0..1).bsearch { Object.new } }.should raise_error(TypeError) + -> { (0..1).bsearch { Object.new } }.should raise_error(TypeError, "wrong argument type Object (must be numeric, true, false or nil)") end - it "raises a TypeError if the block returns a String" do - -> { (0..1).bsearch { "1" } }.should raise_error(TypeError) + it "raises a TypeError if the block returns a String and boundaries are Integer values" do + -> { (0..1).bsearch { "1" } }.should raise_error(TypeError, "wrong argument type String (must be numeric, true, false or nil)") + end + + it "raises a TypeError if the block returns a String and boundaries are Float values" do + -> { (0.0..1.0).bsearch { "1" } }.should raise_error(TypeError, "wrong argument type String (must be numeric, true, false or nil)") end it "raises a TypeError if the Range has Object values" do value = mock("range bsearch") r = Range.new value, value - -> { r.bsearch { true } }.should raise_error(TypeError) + -> { r.bsearch { true } }.should raise_error(TypeError, "can't do binary search for MockObject") end it "raises a TypeError if the Range has String values" do - -> { ("a".."e").bsearch { true } }.should raise_error(TypeError) + -> { ("a".."e").bsearch { true } }.should raise_error(TypeError, "can't do binary search for String") + end + + it "raises TypeError when non-Numeric begin/end and block not passed" do + -> { ("a".."e").bsearch }.should raise_error(TypeError, "can't do binary search for String") end context "with Integer values" do @@ -94,6 +102,10 @@ describe "Range#bsearch" do (4..2).bsearch { 0 }.should == nil (4..2).bsearch { -1 }.should == nil end + + it "returns enumerator when block not passed" do + (0...3).bsearch.kind_of?(Enumerator).should == true + end end context "with Float values" do @@ -156,7 +168,6 @@ describe "Range#bsearch" do it "returns nil if the block returns greater than zero for every element" do (0.3..3.0).bsearch { |x| x <=> -1 }.should be_nil - end it "returns nil if the block never returns zero" do @@ -213,6 +224,10 @@ describe "Range#bsearch" do (0...inf).bsearch { |x| x >= Float::MAX ? 0 : 1 }.should == Float::MAX end end + + it "returns enumerator when block not passed" do + (0.1...2.3).bsearch.kind_of?(Enumerator).should == true + end end context "with endless ranges and Integer values" do @@ -250,6 +265,10 @@ describe "Range#bsearch" do [1, 2, 3].should include(result) end end + + it "returns enumerator when block not passed" do + eval("(-2..)").bsearch.kind_of?(Enumerator).should == true + end end context "with endless ranges and Float values" do @@ -327,8 +346,11 @@ describe "Range#bsearch" do eval("(0.0...)").bsearch { 0 }.should != inf end end - end + it "returns enumerator when block not passed" do + eval("(0.1..)").bsearch.kind_of?(Enumerator).should == true + end + end context "with beginless ranges and Integer values" do context "with a block returning true or false" do @@ -361,6 +383,10 @@ describe "Range#bsearch" do [1, 2, 3].should include(result) end end + + it "returns enumerator when block not passed" do + (..10).bsearch.kind_of?(Enumerator).should == true + end end context "with beginless ranges and Float values" do @@ -432,5 +458,9 @@ describe "Range#bsearch" do (...inf).bsearch { |x| 3 - x }.should == 3 end end + + it "returns enumerator when block not passed" do + (..-0.1).bsearch.kind_of?(Enumerator).should == true + end end end diff --git a/spec/ruby/core/range/case_compare_spec.rb b/spec/ruby/core/range/case_compare_spec.rb index 65878aaabe..c9b253f0a5 100644 --- a/spec/ruby/core/range/case_compare_spec.rb +++ b/spec/ruby/core/range/case_compare_spec.rb @@ -11,7 +11,7 @@ describe "Range#===" do it_behaves_like :range_cover_and_include, :=== it_behaves_like :range_cover, :=== - ruby_bug "#19533", "3.2"..."3.3" do + ruby_bug "#19533", ""..."3.3" do it "returns true on any value if begin and end are both nil" do (nil..nil).should === 1 end diff --git a/spec/ruby/core/range/cover_spec.rb b/spec/ruby/core/range/cover_spec.rb index fa881607e9..c05bb50614 100644 --- a/spec/ruby/core/range/cover_spec.rb +++ b/spec/ruby/core/range/cover_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/cover_and_include' require_relative 'shared/cover' @@ -7,4 +7,8 @@ describe "Range#cover?" do it_behaves_like :range_cover_and_include, :cover? it_behaves_like :range_cover, :cover? it_behaves_like :range_cover_subrange, :cover? + + it "covers U+9995 in the range U+0999..U+9999" do + ("\u{999}".."\u{9999}").cover?("\u{9995}").should be_true + end end diff --git a/spec/ruby/core/range/each_spec.rb b/spec/ruby/core/range/each_spec.rb index ecae17c881..f10330d61d 100644 --- a/spec/ruby/core/range/each_spec.rb +++ b/spec/ruby/core/range/each_spec.rb @@ -40,21 +40,21 @@ describe "Range#each" do it "works with endless ranges" do a = [] - eval("(-2..)").each { |x| break if x > 2; a << x } + (-2..).each { |x| break if x > 2; a << x } a.should == [-2, -1, 0, 1, 2] a = [] - eval("(-2...)").each { |x| break if x > 2; a << x } + (-2...).each { |x| break if x > 2; a << x } a.should == [-2, -1, 0, 1, 2] end it "works with String endless ranges" do a = [] - eval("('A'..)").each { |x| break if x > "D"; a << x } + ('A'..).each { |x| break if x > "D"; a << x } a.should == ["A", "B", "C", "D"] a = [] - eval("('A'...)").each { |x| break if x > "D"; a << x } + ('A'...).each { |x| break if x > "D"; a << x } a.should == ["A", "B", "C", "D"] end @@ -82,27 +82,14 @@ describe "Range#each" do enum.to_a.should == [1, 2, 3] end - ruby_version_is "3.1" do - it "supports Time objects that respond to #succ" do - t = Time.utc(1970) - def t.succ; self + 1 end - t_succ = t.succ - def t_succ.succ; self + 1; end + it "supports Time objects that respond to #succ" do + t = Time.utc(1970) + def t.succ; self + 1 end + t_succ = t.succ + def t_succ.succ; self + 1; end - (t..t_succ).to_a.should == [Time.utc(1970), Time.utc(1970, nil, nil, nil, nil, 1)] - (t...t_succ).to_a.should == [Time.utc(1970)] - end - end - - ruby_version_is ""..."3.1" do - it "raises a TypeError if the first element is a Time object even if it responds to #succ" do - t = Time.utc(1970) - def t.succ; self + 1 end - t_succ = t.succ - def t_succ.succ; self + 1; end - - -> { (t..t_succ).each { |i| i } }.should raise_error(TypeError) - end + (t..t_succ).to_a.should == [Time.utc(1970), Time.utc(1970, nil, nil, nil, nil, 1)] + (t...t_succ).to_a.should == [Time.utc(1970)] end it "passes each Symbol element by using #succ" do diff --git a/spec/ruby/core/range/include_spec.rb b/spec/ruby/core/range/include_spec.rb index b2c7a54545..449e18985b 100644 --- a/spec/ruby/core/range/include_spec.rb +++ b/spec/ruby/core/range/include_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/cover_and_include' require_relative 'shared/include' @@ -7,4 +7,8 @@ require_relative 'shared/cover' describe "Range#include?" do it_behaves_like :range_cover_and_include, :include? it_behaves_like :range_include, :include? + + it "does not include U+9995 in the range U+0999..U+9999" do + ("\u{999}".."\u{9999}").include?("\u{9995}").should be_false + end end diff --git a/spec/ruby/core/range/last_spec.rb b/spec/ruby/core/range/last_spec.rb index 6698686dd5..82b3e2ff53 100644 --- a/spec/ruby/core/range/last_spec.rb +++ b/spec/ruby/core/range/last_spec.rb @@ -8,10 +8,8 @@ describe "Range#last" do (1..5).last(3).should == [3, 4, 5] end - ruby_bug '#18994', '2.7'...'3.2' do - it "returns the specified number if elements for single element inclusive range" do - (1..1).last(1).should == [1] - end + it "returns the specified number if elements for single element inclusive range" do + (1..1).last(1).should == [1] end it "returns an empty array for an empty Range" do diff --git a/spec/ruby/core/range/max_spec.rb b/spec/ruby/core/range/max_spec.rb index a3bbc31e7d..09371f5298 100644 --- a/spec/ruby/core/range/max_spec.rb +++ b/spec/ruby/core/range/max_spec.rb @@ -55,10 +55,24 @@ describe "Range#max" do (..1.0).max.should == 1.0 end - it "raises for an exclusive beginless range" do + ruby_version_is ""..."4.0" do + it "raises for an exclusive beginless Integer range" do + -> { + (...1).max + }.should raise_error(TypeError, 'cannot exclude end value with non Integer begin value') + end + end + + ruby_version_is "4.0" do + it "returns the end point for exclusive beginless Integer ranges" do + (...1).max.should == 0 + end + end + + it "raises for an exclusive beginless non Integer range" do -> { - (...1).max - }.should raise_error(TypeError, 'cannot exclude end value with non Integer begin value') + (...1.0).max + }.should raise_error(TypeError, 'cannot exclude non Integer end value') end end diff --git a/spec/ruby/core/range/member_spec.rb b/spec/ruby/core/range/member_spec.rb index ab61f92951..78299ae9e5 100644 --- a/spec/ruby/core/range/member_spec.rb +++ b/spec/ruby/core/range/member_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/cover_and_include' require_relative 'shared/include' diff --git a/spec/ruby/core/range/overlap_spec.rb b/spec/ruby/core/range/overlap_spec.rb new file mode 100644 index 0000000000..9b6fc13493 --- /dev/null +++ b/spec/ruby/core/range/overlap_spec.rb @@ -0,0 +1,89 @@ +require_relative '../../spec_helper' + +ruby_version_is '3.3' do + describe "Range#overlap?" do + it "returns true if other Range overlaps self" do + (0..2).overlap?(1..3).should == true + (1..3).overlap?(0..2).should == true + (0..2).overlap?(0..2).should == true + (0..3).overlap?(1..2).should == true + (1..2).overlap?(0..3).should == true + + ('a'..'c').overlap?('b'..'d').should == true + end + + it "returns false if other Range does not overlap self" do + (0..2).overlap?(3..4).should == false + (0..2).overlap?(-4..-1).should == false + + ('a'..'c').overlap?('d'..'f').should == false + end + + it "raises TypeError when called with non-Range argument" do + -> { + (0..2).overlap?(1) + }.should raise_error(TypeError, "wrong argument type Integer (expected Range)") + end + + it "returns true when beginningless and endless Ranges overlap" do + (0..2).overlap?(..3).should == true + (0..2).overlap?(..1).should == true + (0..2).overlap?(..0).should == true + + (..3).overlap?(0..2).should == true + (..1).overlap?(0..2).should == true + (..0).overlap?(0..2).should == true + + (0..2).overlap?(-1..).should == true + (0..2).overlap?(1..).should == true + (0..2).overlap?(2..).should == true + + (-1..).overlap?(0..2).should == true + (1..).overlap?(0..2).should == true + (2..).overlap?(0..2).should == true + + (0..).overlap?(2..).should == true + (..0).overlap?(..2).should == true + end + + it "returns false when beginningless and endless Ranges do not overlap" do + (0..2).overlap?(..-1).should == false + (0..2).overlap?(3..).should == false + + (..-1).overlap?(0..2).should == false + (3..).overlap?(0..2).should == false + end + + it "returns false when Ranges are not compatible" do + (0..2).overlap?('a'..'d').should == false + end + + it "return false when self is empty" do + (2..0).overlap?(1..3).should == false + (2...2).overlap?(1..3).should == false + (1...1).overlap?(1...1).should == false + (2..0).overlap?(2..0).should == false + + ('c'..'a').overlap?('b'..'d').should == false + ('a'...'a').overlap?('b'..'d').should == false + ('b'...'b').overlap?('b'...'b').should == false + ('c'...'a').overlap?('c'...'a').should == false + end + + it "return false when other Range is empty" do + (1..3).overlap?(2..0).should == false + (1..3).overlap?(2...2).should == false + + ('b'..'d').overlap?('c'..'a').should == false + ('b'..'d').overlap?('c'...'c').should == false + end + + it "takes into account exclusive end" do + (0...2).overlap?(2..4).should == false + (2..4).overlap?(0...2).should == false + + ('a'...'c').overlap?('c'..'e').should == false + ('c'..'e').overlap?('a'...'c').should == false + end + end +end diff --git a/spec/ruby/core/range/reverse_each_spec.rb b/spec/ruby/core/range/reverse_each_spec.rb new file mode 100644 index 0000000000..56390cc0da --- /dev/null +++ b/spec/ruby/core/range/reverse_each_spec.rb @@ -0,0 +1,103 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.3" do + describe "Range#reverse_each" do + it "traverses the Range in reverse order and passes each element to block" do + a = [] + (1..3).reverse_each { |i| a << i } + a.should == [3, 2, 1] + + a = [] + (1...3).reverse_each { |i| a << i } + a.should == [2, 1] + end + + it "returns self" do + r = (1..3) + r.reverse_each { |x| }.should equal(r) + end + + it "returns an Enumerator if no block given" do + enum = (1..3).reverse_each + enum.should be_an_instance_of(Enumerator) + enum.to_a.should == [3, 2, 1] + end + + it "raises a TypeError for endless Ranges of Integers" do + -> { + (1..).reverse_each.take(3) + }.should raise_error(TypeError, "can't iterate from NilClass") + end + + it "raises a TypeError for endless Ranges of non-Integers" do + -> { + ("a"..).reverse_each.take(3) + }.should raise_error(TypeError, "can't iterate from NilClass") + end + + context "Integer boundaries" do + it "supports beginningless Ranges" do + (..5).reverse_each.take(3).should == [5, 4, 3] + end + end + + context "non-Integer boundaries" do + it "uses #succ to iterate a Range of non-Integer elements" do + y = mock('y') + x = mock('x') + + x.should_receive(:succ).any_number_of_times.and_return(y) + x.should_receive(:<=>).with(y).any_number_of_times.and_return(-1) + x.should_receive(:<=>).with(x).any_number_of_times.and_return(0) + y.should_receive(:<=>).with(x).any_number_of_times.and_return(1) + y.should_receive(:<=>).with(y).any_number_of_times.and_return(0) + + a = [] + (x..y).each { |i| a << i } + a.should == [x, y] + end + + it "uses #succ to iterate a Range of Strings" do + a = [] + ('A'..'D').reverse_each { |i| a << i } + a.should == ['D','C','B','A'] + end + + it "uses #succ to iterate a Range of Symbols" do + a = [] + (:A..:D).reverse_each { |i| a << i } + a.should == [:D, :C, :B, :A] + end + + it "raises a TypeError when `begin` value does not respond to #succ" do + -> { (Time.now..Time.now).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Time/) + -> { (//..//).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Regexp/) + -> { ([]..[]).reverse_each { |x| x } }.should raise_error(TypeError, /can't iterate from Array/) + end + + it "does not support beginningless Ranges" do + -> { + (..'a').reverse_each { |x| x } + }.should raise_error(TypeError, /can't iterate from NilClass/) + end + end + + context "when no block is given" do + describe "returned Enumerator size" do + it "returns the Range size when Range size is finite" do + (1..3).reverse_each.size.should == 3 + end + + ruby_bug "#20936", "3.4"..."4.0" do + it "returns Infinity when Range size is infinite" do + (..3).reverse_each.size.should == Float::INFINITY + end + end + + it "returns nil when Range size is unknown" do + ('a'..'z').reverse_each.size.should == nil + end + end + end + end +end diff --git a/spec/ruby/core/range/shared/cover.rb b/spec/ruby/core/range/shared/cover.rb index 0b41a26455..eaefb45942 100644 --- a/spec/ruby/core/range/shared/cover.rb +++ b/spec/ruby/core/range/shared/cover.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/range/shared/cover_and_include.rb b/spec/ruby/core/range/shared/cover_and_include.rb index 7028afaa89..13fc5e1790 100644 --- a/spec/ruby/core/range/shared/cover_and_include.rb +++ b/spec/ruby/core/range/shared/cover_and_include.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe :range_cover_and_include, shared: true do @@ -20,8 +20,8 @@ describe :range_cover_and_include, shared: true do end it "returns true if other is an element of self for endless ranges" do - eval("(1..)").send(@method, 2.4).should == true - eval("(0.5...)").send(@method, 2.4).should == true + (1..).send(@method, 2.4).should == true + (0.5...).send(@method, 2.4).should == true end it "returns true if other is an element of self for beginless ranges" do @@ -29,6 +29,17 @@ describe :range_cover_and_include, shared: true do (...10.5).send(@method, 2.4).should == true end + it "returns false if values are not comparable" do + (1..10).send(@method, nil).should == false + (1...10).send(@method, nil).should == false + + (..10).send(@method, nil).should == false + (...10).send(@method, nil).should == false + + (1..).send(@method, nil).should == false + (1...).send(@method, nil).should == false + end + it "compares values using <=>" do rng = (1..5) m = mock("int") @@ -57,7 +68,6 @@ describe :range_cover_and_include, shared: true do it "returns true if argument is less than the last value of the range and greater than the first value" do (20..30).send(@method, 28).should be_true ('e'..'h').send(@method, 'g').should be_true - ("\u{999}".."\u{9999}").send @method, "\u{9995}" end it "returns true if argument is sole element in the range" do diff --git a/spec/ruby/core/range/shared/include.rb b/spec/ruby/core/range/shared/include.rb index c6c5c2becf..15a0e5fb9f 100644 --- a/spec/ruby/core/range/shared/include.rb +++ b/spec/ruby/core/range/shared/include.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/range/size_spec.rb b/spec/ruby/core/range/size_spec.rb index 81ea5a3846..1a3ddd197e 100644 --- a/spec/ruby/core/range/size_spec.rb +++ b/spec/ruby/core/range/size_spec.rb @@ -4,46 +4,25 @@ describe "Range#size" do it "returns the number of elements in the range" do (1..16).size.should == 16 (1...16).size.should == 15 - - (1.0..16.0).size.should == 16 - (1.0...16.0).size.should == 15 - (1.0..15.9).size.should == 15 - (1.1..16.0).size.should == 15 - (1.1..15.9).size.should == 15 end it "returns 0 if last is less than first" do (16..0).size.should == 0 - (16.0..0.0).size.should == 0 - (Float::INFINITY..0).size.should == 0 end it 'returns Float::INFINITY for increasing, infinite ranges' do (0..Float::INFINITY).size.should == Float::INFINITY - (-Float::INFINITY..0).size.should == Float::INFINITY - (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY end it 'returns Float::INFINITY for endless ranges if the start is numeric' do eval("(1..)").size.should == Float::INFINITY - eval("(0.5...)").size.should == Float::INFINITY end it 'returns nil for endless ranges if the start is not numeric' do eval("('z'..)").size.should == nil - eval("([]...)").size.should == nil - end - - ruby_version_is ""..."3.2" do - it 'returns Float::INFINITY for all beginless ranges' do - (..1).size.should == Float::INFINITY - (...0.5).size.should == Float::INFINITY - (..nil).size.should == Float::INFINITY - (...'o').size.should == Float::INFINITY - end end - ruby_version_is "3.2" do + ruby_version_is ""..."3.4" do it 'returns Float::INFINITY for all beginless ranges if the end is numeric' do (..1).size.should == Float::INFINITY (...0.5).size.should == Float::INFINITY @@ -58,6 +37,54 @@ describe "Range#size" do end end + ruby_version_is ""..."3.4" do + it "returns the number of elements in the range" do + (1.0..16.0).size.should == 16 + (1.0...16.0).size.should == 15 + (1.0..15.9).size.should == 15 + (1.1..16.0).size.should == 15 + (1.1..15.9).size.should == 15 + end + + it "returns 0 if last is less than first" do + (16.0..0.0).size.should == 0 + (Float::INFINITY..0).size.should == 0 + end + + it 'returns Float::INFINITY for increasing, infinite ranges' do + (-Float::INFINITY..0).size.should == Float::INFINITY + (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY + end + + it 'returns Float::INFINITY for endless ranges if the start is numeric' do + eval("(0.5...)").size.should == Float::INFINITY + end + + it 'returns nil for endless ranges if the start is not numeric' do + eval("([]...)").size.should == nil + end + end + + ruby_version_is "3.4" do + it 'raises TypeError if a range is not iterable' do + -> { (1.0..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0...16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (16.0..0.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..Float::INFINITY).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..1).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...0.5).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..nil).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...'o').size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("(0.5...)").size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("([]...)").size }.should raise_error(TypeError, /can't iterate from/) + end + end + it "returns nil if first and last are not Numeric" do (:a..:z).size.should be_nil ('a'..'z').size.should be_nil diff --git a/spec/ruby/core/range/step_spec.rb b/spec/ruby/core/range/step_spec.rb index 64ea3de4ed..0d0caf746d 100644 --- a/spec/ruby/core/range/step_spec.rb +++ b/spec/ruby/core/range/step_spec.rb @@ -10,44 +10,50 @@ describe "Range#step" do r.step { }.should equal(r) end - it "raises TypeError if step" do - obj = mock("mock") - -> { (1..10).step(obj) { } }.should raise_error(TypeError) - end + ruby_version_is ""..."3.4" do + it "calls #to_int to coerce step to an Integer" do + obj = mock("Range#step") + obj.should_receive(:to_int).and_return(1) - it "calls #to_int to coerce step to an Integer" do - obj = mock("Range#step") - obj.should_receive(:to_int).and_return(1) + (1..2).step(obj) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([1, 2]) + end - (1..2).step(obj) { |x| ScratchPad << x } - ScratchPad.recorded.should eql([1, 2]) - end + it "raises a TypeError if step does not respond to #to_int" do + obj = mock("Range#step non-integer") - it "raises a TypeError if step does not respond to #to_int" do - obj = mock("Range#step non-integer") + -> { (1..2).step(obj) { } }.should raise_error(TypeError) + end - -> { (1..2).step(obj) { } }.should raise_error(TypeError) - end + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("Range#step non-integer") + obj.should_receive(:to_int).and_return("1") - it "raises a TypeError if #to_int does not return an Integer" do - obj = mock("Range#step non-integer") - obj.should_receive(:to_int).and_return("1") + -> { (1..2).step(obj) { } }.should raise_error(TypeError) + end - -> { (1..2).step(obj) { } }.should raise_error(TypeError) - end + it "raises a TypeError if the first element does not respond to #succ" do + obj = mock("Range#step non-comparable") + obj.should_receive(:<=>).with(obj).and_return(1) - it "coerces the argument to integer by invoking to_int" do - (obj = mock("2")).should_receive(:to_int).and_return(2) - res = [] - (1..10).step(obj) {|x| res << x} - res.should == [1, 3, 5, 7, 9] + -> { (obj..obj).step { |x| x } }.should raise_error(TypeError) + end end - it "raises a TypeError if the first element does not respond to #succ" do - obj = mock("Range#step non-comparable") - obj.should_receive(:<=>).with(obj).and_return(1) + ruby_version_is "3.4" do + it "calls #coerce to coerce step to an Integer" do + obj = mock("Range#step") + obj.should_receive(:coerce).at_least(:once).and_return([1, 2]) + + (1..3).step(obj) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([1, 3]) + end - -> { (obj..obj).step { |x| x } }.should raise_error(TypeError) + it "raises a TypeError if step does not respond to #coerce" do + obj = mock("Range#step non-coercible") + + -> { (1..2).step(obj) { } }.should raise_error(TypeError) + end end it "raises an ArgumentError if step is 0" do @@ -58,8 +64,17 @@ describe "Range#step" do -> { (-1..1).step(0.0) { |x| x } }.should raise_error(ArgumentError) end - it "raises an ArgumentError if step is negative" do - -> { (-1..1).step(-2) { |x| x } }.should raise_error(ArgumentError) + ruby_version_is "3.4" do + it "does not raise an ArgumentError if step is 0 for non-numeric ranges" do + t = Time.utc(2023, 2, 24) + -> { (t..t+1).step(0) { break } }.should_not raise_error(ArgumentError) + end + end + + ruby_version_is ""..."3.4" do + it "raises an ArgumentError if step is negative" do + -> { (-1..1).step(-2) { |x| x } }.should raise_error(ArgumentError) + end end describe "with inclusive end" do @@ -78,6 +93,18 @@ describe "Range#step" do (-2..2).step(1.5) { |x| ScratchPad << x } ScratchPad.recorded.should eql([-2.0, -0.5, 1.0]) end + + ruby_version_is "3.4" do + it "does not iterate if step is negative for forward range" do + (-1..1).step(-1) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([]) + end + + it "iterates backward if step is negative for backward range" do + (1..-1).step(-1) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([1, 0, -1]) + end + end end describe "and Float values" do @@ -162,13 +189,96 @@ describe "Range#step" do -> { ("A".."G").step(2.0) { } }.should raise_error(TypeError) end - it "calls #succ on begin and each element returned by #succ" do - obj = mock("Range#step String start") - obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0) - obj.should_receive(:succ).exactly(2).times.and_return(obj) + ruby_version_is ""..."3.4" do + it "calls #succ on begin and each element returned by #succ" do + obj = mock("Range#step String start") + obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0) + obj.should_receive(:succ).exactly(2).times.and_return(obj) + + (obj..obj).step { |x| ScratchPad << x } + ScratchPad.recorded.should == [obj, obj, obj] + end + end + + ruby_version_is "3.4" do + it "yields String values adjusted by step and less than or equal to end" do + ("A".."AAA").step("A") { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "AA", "AAA"] + end + + it "raises a TypeError when passed an incompatible type step" do + -> { ("A".."G").step([]) { } }.should raise_error(TypeError) + end + + it "calls #+ on begin and each element returned by #+" do + start = mock("Range#step String start") + stop = mock("Range#step String stop") + + mid1 = mock("Range#step String mid1") + mid2 = mock("Range#step String mid2") + + step = mock("Range#step String step") + + # Deciding on the direction of iteration + start.should_receive(:<=>).with(stop).at_least(:twice).and_return(-1) + # Deciding whether the step moves iteration in the right direction + start.should_receive(:<=>).with(mid1).and_return(-1) + # Iteration 1 + start.should_receive(:+).at_least(:once).with(step).and_return(mid1) + # Iteration 2 + mid1.should_receive(:<=>).with(stop).and_return(-1) + mid1.should_receive(:+).with(step).and_return(mid2) + # Iteration 3 + mid2.should_receive(:<=>).with(stop).and_return(0) + + (start..stop).step(step) { |x| ScratchPad << x } + ScratchPad.recorded.should == [start, mid1, mid2] + end + + it "iterates backward if the step is decreasing values, and the range is backward" do + start = mock("Range#step String start") + stop = mock("Range#step String stop") + + mid1 = mock("Range#step String mid1") + mid2 = mock("Range#step String mid2") + + step = mock("Range#step String step") + + # Deciding on the direction of iteration + start.should_receive(:<=>).with(stop).at_least(:twice).and_return(1) + # Deciding whether the step moves iteration in the right direction + start.should_receive(:<=>).with(mid1).and_return(1) + # Iteration 1 + start.should_receive(:+).at_least(:once).with(step).and_return(mid1) + # Iteration 2 + mid1.should_receive(:<=>).with(stop).and_return(1) + mid1.should_receive(:+).with(step).and_return(mid2) + # Iteration 3 + mid2.should_receive(:<=>).with(stop).and_return(0) + + (start..stop).step(step) { |x| ScratchPad << x } + ScratchPad.recorded.should == [start, mid1, mid2] + end + + it "does no iteration of the direction of the range and of the step don't match" do + start = mock("Range#step String start") + stop = mock("Range#step String stop") - (obj..obj).step { |x| ScratchPad << x } - ScratchPad.recorded.should == [obj, obj, obj] + mid1 = mock("Range#step String mid1") + mid2 = mock("Range#step String mid2") + + step = mock("Range#step String step") + + # Deciding on the direction of iteration: stop > start + start.should_receive(:<=>).with(stop).at_least(:twice).and_return(1) + # Deciding whether the step moves iteration in the right direction + # start + step < start, the direction is opposite to the range's + start.should_receive(:+).with(step).and_return(mid1) + start.should_receive(:<=>).with(mid1).and_return(-1) + + (start..stop).step(step) { |x| ScratchPad << x } + ScratchPad.recorded.should == [] + end end end end @@ -212,13 +322,11 @@ describe "Range#step" do ScratchPad.recorded.should eql([1.0, 2.8, 4.6]) end - ruby_version_is '3.1' do - it "correctly handles values near the upper limit" do # https://bugs.ruby-lang.org/issues/16612 - (1.0...55.6).step(18.2) { |x| ScratchPad << x } - ScratchPad.recorded.should eql([1.0, 19.2, 37.4, 55.599999999999994]) + it "correctly handles values near the upper limit" do # https://bugs.ruby-lang.org/issues/16612 + (1.0...55.6).step(18.2) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([1.0, 19.2, 37.4, 55.599999999999994]) - (1.0...55.6).step(18.2).size.should == 4 - end + (1.0...55.6).step(18.2).size.should == 4 end it "handles infinite values at either end" do @@ -266,18 +374,31 @@ describe "Range#step" do end describe "and String values" do - it "yields String values incremented by #succ and less than or equal to end when not passed a step" do - ("A"..."E").step { |x| ScratchPad << x } - ScratchPad.recorded.should == ["A", "B", "C", "D"] - end + ruby_version_is ""..."3.4" do + it "yields String values incremented by #succ and less than or equal to end when not passed a step" do + ("A"..."E").step { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "B", "C", "D"] + end - it "yields String values incremented by #succ called Integer step times" do - ("A"..."G").step(2) { |x| ScratchPad << x } - ScratchPad.recorded.should == ["A", "C", "E"] + it "yields String values incremented by #succ called Integer step times" do + ("A"..."G").step(2) { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "C", "E"] + end + + it "raises a TypeError when passed a Float step" do + -> { ("A"..."G").step(2.0) { } }.should raise_error(TypeError) + end end - it "raises a TypeError when passed a Float step" do - -> { ("A"..."G").step(2.0) { } }.should raise_error(TypeError) + ruby_version_is "3.4" do + it "yields String values adjusted by step and less than or equal to end" do + ("A"..."AAA").step("A") { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "AA"] + end + + it "raises a TypeError when passed an incompatible type step" do + -> { ("A".."G").step([]) { } }.should raise_error(TypeError) + end end end end @@ -285,93 +406,109 @@ describe "Range#step" do describe "with an endless range" do describe "and Integer values" do it "yield Integer values incremented by 1 when not passed a step" do - eval("(-2..)").step { |x| break if x > 2; ScratchPad << x } + (-2..).step { |x| break if x > 2; ScratchPad << x } ScratchPad.recorded.should eql([-2, -1, 0, 1, 2]) ScratchPad.record [] - eval("(-2...)").step { |x| break if x > 2; ScratchPad << x } + (-2...).step { |x| break if x > 2; ScratchPad << x } ScratchPad.recorded.should eql([-2, -1, 0, 1, 2]) end it "yields Integer values incremented by an Integer step" do - eval("(-5..)").step(2) { |x| break if x > 3; ScratchPad << x } + (-5..).step(2) { |x| break if x > 3; ScratchPad << x } ScratchPad.recorded.should eql([-5, -3, -1, 1, 3]) ScratchPad.record [] - eval("(-5...)").step(2) { |x| break if x > 3; ScratchPad << x } + (-5...).step(2) { |x| break if x > 3; ScratchPad << x } ScratchPad.recorded.should eql([-5, -3, -1, 1, 3]) end it "yields Float values incremented by a Float step" do - eval("(-2..)").step(1.5) { |x| break if x > 1.0; ScratchPad << x } + (-2..).step(1.5) { |x| break if x > 1.0; ScratchPad << x } ScratchPad.recorded.should eql([-2.0, -0.5, 1.0]) ScratchPad.record [] - eval("(-2..)").step(1.5) { |x| break if x > 1.0; ScratchPad << x } + (-2..).step(1.5) { |x| break if x > 1.0; ScratchPad << x } ScratchPad.recorded.should eql([-2.0, -0.5, 1.0]) end end describe "and Float values" do it "yields Float values incremented by 1 and less than end when not passed a step" do - eval("(-2.0..)").step { |x| break if x > 1.5; ScratchPad << x } + (-2.0..).step { |x| break if x > 1.5; ScratchPad << x } ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0]) ScratchPad.record [] - eval("(-2.0...)").step { |x| break if x > 1.5; ScratchPad << x } + (-2.0...).step { |x| break if x > 1.5; ScratchPad << x } ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0]) end it "yields Float values incremented by an Integer step" do - eval("(-5.0..)").step(2) { |x| break if x > 3.5; ScratchPad << x } + (-5.0..).step(2) { |x| break if x > 3.5; ScratchPad << x } ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0]) ScratchPad.record [] - eval("(-5.0...)").step(2) { |x| break if x > 3.5; ScratchPad << x } + (-5.0...).step(2) { |x| break if x > 3.5; ScratchPad << x } ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0]) end it "yields Float values incremented by a Float step" do - eval("(-1.0..)").step(0.5) { |x| break if x > 0.6; ScratchPad << x } + (-1.0..).step(0.5) { |x| break if x > 0.6; ScratchPad << x } ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5]) ScratchPad.record [] - eval("(-1.0...)").step(0.5) { |x| break if x > 0.6; ScratchPad << x } + (-1.0...).step(0.5) { |x| break if x > 0.6; ScratchPad << x } ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5]) end it "handles infinite values at the start" do - eval("(-Float::INFINITY..)").step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 } + (-Float::INFINITY..).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 } ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY]) ScratchPad.record [] - eval("(-Float::INFINITY...)").step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 } + (-Float::INFINITY...).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 } ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY]) end end describe "and String values" do it "yields String values incremented by #succ and less than or equal to end when not passed a step" do - eval("('A'..)").step { |x| break if x > "D"; ScratchPad << x } + ('A'..).step { |x| break if x > "D"; ScratchPad << x } ScratchPad.recorded.should == ["A", "B", "C", "D"] ScratchPad.record [] - eval("('A'...)").step { |x| break if x > "D"; ScratchPad << x } + ('A'...).step { |x| break if x > "D"; ScratchPad << x } ScratchPad.recorded.should == ["A", "B", "C", "D"] end it "yields String values incremented by #succ called Integer step times" do - eval("('A'..)").step(2) { |x| break if x > "F"; ScratchPad << x } + ('A'..).step(2) { |x| break if x > "F"; ScratchPad << x } ScratchPad.recorded.should == ["A", "C", "E"] ScratchPad.record [] - eval("('A'...)").step(2) { |x| break if x > "F"; ScratchPad << x } + ('A'...).step(2) { |x| break if x > "F"; ScratchPad << x } ScratchPad.recorded.should == ["A", "C", "E"] end it "raises a TypeError when passed a Float step" do - -> { eval("('A'..)").step(2.0) { } }.should raise_error(TypeError) - -> { eval("('A'...)").step(2.0) { } }.should raise_error(TypeError) + -> { ('A'..).step(2.0) { } }.should raise_error(TypeError) + -> { ('A'...).step(2.0) { } }.should raise_error(TypeError) + end + + ruby_version_is "3.4" do + it "yields String values adjusted by step" do + ('A'..).step("A") { |x| break if x > "AAA"; ScratchPad << x } + ScratchPad.recorded.should == ["A", "AA", "AAA"] + + ScratchPad.record [] + ('A'...).step("A") { |x| break if x > "AAA"; ScratchPad << x } + ScratchPad.recorded.should == ["A", "AA", "AAA"] + end + + it "raises a TypeError when passed an incompatible type step" do + -> { ('A'..).step([]) { } }.should raise_error(TypeError) + -> { ('A'...).step([]) { } }.should raise_error(TypeError) + end end end end @@ -383,15 +520,24 @@ describe "Range#step" do describe "returned Enumerator" do describe "size" do - it "raises a TypeError if step does not respond to #to_int" do - obj = mock("Range#step non-integer") - -> { (1..2).step(obj) }.should raise_error(TypeError) + ruby_version_is ""..."3.4" do + it "raises a TypeError if step does not respond to #to_int" do + obj = mock("Range#step non-integer") + -> { (1..2).step(obj) }.should raise_error(TypeError) + end + + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("Range#step non-integer") + obj.should_receive(:to_int).and_return("1") + -> { (1..2).step(obj) }.should raise_error(TypeError) + end end - it "raises a TypeError if #to_int does not return an Integer" do - obj = mock("Range#step non-integer") - obj.should_receive(:to_int).and_return("1") - -> { (1..2).step(obj) }.should raise_error(TypeError) + ruby_version_is "3.4" do + it "does not raise if step is incompatible" do + obj = mock("Range#step non-integer") + -> { (1..2).step(obj) }.should_not raise_error + end end it "returns the ceil of range size divided by the number of steps" do @@ -431,19 +577,36 @@ describe "Range#step" do (1.0...6.4).step(1.8).size.should == 3 end - it "returns nil with begin and end are String" do - ("A".."E").step(2).size.should == nil - ("A"..."E").step(2).size.should == nil - ("A".."E").step.size.should == nil - ("A"..."E").step.size.should == nil + ruby_version_is ""..."3.4" do + it "returns nil with begin and end are String" do + ("A".."E").step(2).size.should == nil + ("A"..."E").step(2).size.should == nil + ("A".."E").step.size.should == nil + ("A"..."E").step.size.should == nil + end + + it "return nil and not raises a TypeError if the first element does not respond to #succ" do + obj = mock("Range#step non-comparable") + obj.should_receive(:<=>).with(obj).and_return(1) + enum = (obj..obj).step + -> { enum.size }.should_not raise_error + enum.size.should == nil + end end - it "return nil and not raises a TypeError if the first element does not respond to #succ" do - obj = mock("Range#step non-comparable") - obj.should_receive(:<=>).with(obj).and_return(1) - enum = (obj..obj).step - -> { enum.size }.should_not raise_error - enum.size.should == nil + ruby_version_is "3.4" do + it "returns nil with begin and end are String" do + ("A".."E").step("A").size.should == nil + ("A"..."E").step("A").size.should == nil + end + + it "return nil and not raises a TypeError if the first element is not of compatible type" do + obj = mock("Range#step non-comparable") + obj.should_receive(:<=>).with(obj).and_return(1) + enum = (obj..obj).step(obj) + -> { enum.size }.should_not raise_error + enum.size.should == nil + end end end @@ -470,22 +633,48 @@ describe "Range#step" do (1..).step(2).take(3).should == [1, 3, 5] end - it "returns an instance of Enumerator when begin is not numeric" do - ("a"..).step.class.should == Enumerator - ("a"..).step(2).take(3).should == %w[a c e] + ruby_version_is ""..."3.4" do + it "returns an instance of Enumerator when begin is not numeric" do + ("a"..).step.class.should == Enumerator + ("a"..).step(2).take(3).should == %w[a c e] + end + end + + ruby_version_is "3.4" do + it "returns an instance of Enumerator when begin is not numeric" do + ("a"..).step("a").class.should == Enumerator + ("a"..).step("a").take(3).should == %w[a aa aaa] + end end end context "when range is beginless and endless" do - it "returns an instance of Enumerator" do - Range.new(nil, nil).step.class.should == Enumerator + ruby_version_is ""..."3.4" do + it "returns an instance of Enumerator" do + Range.new(nil, nil).step.class.should == Enumerator + end + end + + ruby_version_is "3.4" do + it "raises an ArgumentError" do + -> { Range.new(nil, nil).step(1) }.should raise_error(ArgumentError) + end end end context "when begin and end are not numerics" do - it "returns an instance of Enumerator" do - ("a".."z").step.class.should == Enumerator - ("a".."z").step(3).take(4).should == %w[a d g j] + ruby_version_is ""..."3.4" do + it "returns an instance of Enumerator" do + ("a".."z").step.class.should == Enumerator + ("a".."z").step(3).take(4).should == %w[a d g j] + end + end + + ruby_version_is "3.4" do + it "returns an instance of Enumerator" do + ("a".."z").step("a").class.should == Enumerator + ("a".."z").step("a").take(4).should == %w[a aa aaa aaaa] + end end end end diff --git a/spec/ruby/core/range/to_set_spec.rb b/spec/ruby/core/range/to_set_spec.rb new file mode 100644 index 0000000000..589c0e9aed --- /dev/null +++ b/spec/ruby/core/range/to_set_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require_relative '../enumerable/fixtures/classes' + +describe "Enumerable#to_set" do + it "returns a new Set created from self" do + (1..4).to_set.should == Set[1, 2, 3, 4] + (1...4).to_set.should == Set[1, 2, 3] + end + + it "passes down passed blocks" do + (1..3).to_set { |x| x * x }.should == Set[1, 4, 9] + end + + ruby_version_is "4.0" do + it "raises a RangeError if the range is infinite" do + -> { (1..).to_set }.should raise_error(RangeError, "cannot convert endless range to a set") + -> { (1...).to_set }.should raise_error(RangeError, "cannot convert endless range to a set") + end + end + + ruby_version_is ""..."4.0" do + it "instantiates an object of provided as the first argument set class" do + set = (1..3).to_set(EnumerableSpecs::SetSubclass) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end + end + + ruby_version_is "4.0"..."4.1" do + it "instantiates an object of provided as the first argument set class and warns" do + set = nil + proc { + set = (1..3).to_set(EnumerableSpecs::SetSubclass) + }.should complain(/Enumerable#to_set/) + set.should be_kind_of(EnumerableSpecs::SetSubclass) + set.to_a.sort.should == [1, 2, 3] + end + end + + ruby_version_is "4.1" do + it "does not accept any positional argument" do + -> { + (1..3).to_set(EnumerableSpecs::SetSubclass) + }.should raise_error(ArgumentError, 'wrong number of arguments (given 1, expected 0)') + end + end + + it "does not need explicit `require 'set'`" do + output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1') + puts (1..3).to_set.to_a.inspect + RUBY + + output.chomp.should == "[1, 2, 3]" + end +end diff --git a/spec/ruby/core/rational/abs_spec.rb b/spec/ruby/core/rational/abs_spec.rb index 7272ad2422..54099aa14d 100644 --- a/spec/ruby/core/rational/abs_spec.rb +++ b/spec/ruby/core/rational/abs_spec.rb @@ -1,5 +1,5 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/abs' +require_relative 'shared/abs' describe "Rational#abs" do it_behaves_like :rational_abs, :abs diff --git a/spec/ruby/core/rational/ceil_spec.rb b/spec/ruby/core/rational/ceil_spec.rb index e736351604..d5bdadf3b6 100644 --- a/spec/ruby/core/rational/ceil_spec.rb +++ b/spec/ruby/core/rational/ceil_spec.rb @@ -1,6 +1,45 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/ceil' describe "Rational#ceil" do - it_behaves_like :rational_ceil, :ceil + before do + @rational = Rational(2200, 7) + end + + describe "with no arguments (precision = 0)" do + it "returns an Integer" do + @rational.ceil.should be_kind_of(Integer) + end + + it "returns the truncated value toward positive infinity" do + @rational.ceil.should == 315 + Rational(1, 2).ceil.should == 1 + Rational(-1, 2).ceil.should == 0 + end + end + + describe "with a precision < 0" do + it "returns an Integer" do + @rational.ceil(-2).should be_kind_of(Integer) + @rational.ceil(-1).should be_kind_of(Integer) + end + + it "moves the truncation point n decimal places left" do + @rational.ceil(-3).should == 1000 + @rational.ceil(-2).should == 400 + @rational.ceil(-1).should == 320 + end + end + + describe "with precision > 0" do + it "returns a Rational" do + @rational.ceil(1).should be_kind_of(Rational) + @rational.ceil(2).should be_kind_of(Rational) + end + + it "moves the truncation point n decimal places right" do + @rational.ceil(1).should == Rational(3143, 10) + @rational.ceil(2).should == Rational(31429, 100) + @rational.ceil(3).should == Rational(157143, 500) + end + end end diff --git a/spec/ruby/core/rational/coerce_spec.rb b/spec/ruby/core/rational/coerce_spec.rb deleted file mode 100644 index 9c0f05829b..0000000000 --- a/spec/ruby/core/rational/coerce_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -require_relative "../../spec_helper" -require_relative '../../shared/rational/coerce' - -describe "Rational#coerce" do - it_behaves_like :rational_coerce, :coerce -end diff --git a/spec/ruby/core/rational/comparison_spec.rb b/spec/ruby/core/rational/comparison_spec.rb index 877069fb8f..c9db60d5c7 100644 --- a/spec/ruby/core/rational/comparison_spec.rb +++ b/spec/ruby/core/rational/comparison_spec.rb @@ -1,23 +1,93 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/comparison' +require_relative 'fixtures/rational' describe "Rational#<=> when passed a Rational object" do - it_behaves_like :rational_cmp_rat, :<=> + it "returns 1 when self is greater than the passed argument" do + (Rational(4, 4) <=> Rational(3, 4)).should equal(1) + (Rational(-3, 4) <=> Rational(-4, 4)).should equal(1) + end + + it "returns 0 when self is equal to the passed argument" do + (Rational(4, 4) <=> Rational(4, 4)).should equal(0) + (Rational(-3, 4) <=> Rational(-3, 4)).should equal(0) + end + + it "returns -1 when self is less than the passed argument" do + (Rational(3, 4) <=> Rational(4, 4)).should equal(-1) + (Rational(-4, 4) <=> Rational(-3, 4)).should equal(-1) + end end describe "Rational#<=> when passed an Integer object" do - it_behaves_like :rational_cmp_int, :<=> + it "returns 1 when self is greater than the passed argument" do + (Rational(4, 4) <=> 0).should equal(1) + (Rational(4, 4) <=> -10).should equal(1) + (Rational(-3, 4) <=> -1).should equal(1) + end + + it "returns 0 when self is equal to the passed argument" do + (Rational(4, 4) <=> 1).should equal(0) + (Rational(-8, 4) <=> -2).should equal(0) + end + + it "returns -1 when self is less than the passed argument" do + (Rational(3, 4) <=> 1).should equal(-1) + (Rational(-4, 4) <=> 0).should equal(-1) + end end describe "Rational#<=> when passed a Float object" do - it_behaves_like :rational_cmp_float, :<=> + it "returns 1 when self is greater than the passed argument" do + (Rational(4, 4) <=> 0.5).should equal(1) + (Rational(4, 4) <=> -1.5).should equal(1) + (Rational(-3, 4) <=> -0.8).should equal(1) + end + + it "returns 0 when self is equal to the passed argument" do + (Rational(4, 4) <=> 1.0).should equal(0) + (Rational(-6, 4) <=> -1.5).should equal(0) + end + + it "returns -1 when self is less than the passed argument" do + (Rational(3, 4) <=> 1.2).should equal(-1) + (Rational(-4, 4) <=> 0.5).should equal(-1) + end end describe "Rational#<=> when passed an Object that responds to #coerce" do - it_behaves_like :rational_cmp_coerce, :<=> - it_behaves_like :rational_cmp_coerce_exception, :<=> + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational <=> obj + end + + it "calls #<=> on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:<=>).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational <=> obj).should == :result + end + + it "does not rescue exception raised in other#coerce" do + b = mock("numeric with failed #coerce") + b.should_receive(:coerce).and_raise(RationalSpecs::CoerceError) + + -> { Rational(3, 4) <=> b }.should raise_error(RationalSpecs::CoerceError) + end end describe "Rational#<=> when passed a non-Numeric Object that doesn't respond to #coerce" do - it_behaves_like :rational_cmp_other, :<=> + it "returns nil" do + (Rational <=> mock("Object")).should be_nil + end end diff --git a/spec/ruby/core/rational/denominator_spec.rb b/spec/ruby/core/rational/denominator_spec.rb index c2f49b4190..4687244893 100644 --- a/spec/ruby/core/rational/denominator_spec.rb +++ b/spec/ruby/core/rational/denominator_spec.rb @@ -1,6 +1,14 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/denominator' describe "Rational#denominator" do - it_behaves_like :rational_denominator, :denominator + it "returns the denominator" do + Rational(3, 4).denominator.should equal(4) + Rational(3, -4).denominator.should equal(4) + + Rational(1, bignum_value).denominator.should == bignum_value + end + + it "returns 1 if no denominator was given" do + Rational(80).denominator.should == 1 + end end diff --git a/spec/ruby/core/rational/div_spec.rb b/spec/ruby/core/rational/div_spec.rb index bee7d01a67..d3adb9b536 100644 --- a/spec/ruby/core/rational/div_spec.rb +++ b/spec/ruby/core/rational/div_spec.rb @@ -1,18 +1,54 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/div' describe "Rational#div" do - it_behaves_like :rational_div, :div + it "returns an Integer" do + Rational(229, 21).div(82).should be_kind_of(Integer) + end + + it "raises an ArgumentError if passed more than one argument" do + -> { Rational(3, 4).div(2,3) }.should raise_error(ArgumentError) + end + + # See http://redmine.ruby-lang.org/issues/show/1648 + it "raises a TypeError if passed a non-numeric argument" do + -> { Rational(3, 4).div([]) }.should raise_error(TypeError) + end end describe "Rational#div passed a Rational" do - it_behaves_like :rational_div_rat, :div + it "performs integer division and returns the result" do + Rational(2, 3).div(Rational(2, 3)).should == 1 + Rational(-2, 9).div(Rational(-9, 2)).should == 0 + end + + it "raises a ZeroDivisionError when the argument has a numerator of 0" do + -> { Rational(3, 4).div(Rational(0, 3)) }.should raise_error(ZeroDivisionError) + end + + it "raises a ZeroDivisionError when the argument has a numerator of 0.0" do + -> { Rational(3, 4).div(Rational(0.0, 3)) }.should raise_error(ZeroDivisionError) + end end describe "Rational#div passed an Integer" do - it_behaves_like :rational_div_int, :div + it "performs integer division and returns the result" do + Rational(2, 1).div(1).should == 2 + Rational(25, 5).div(-50).should == -1 + end + + it "raises a ZeroDivisionError when the argument is 0" do + -> { Rational(3, 4).div(0) }.should raise_error(ZeroDivisionError) + end end describe "Rational#div passed a Float" do - it_behaves_like :rational_div_float, :div + it "performs integer division and returns the result" do + Rational(2, 3).div(30.333).should == 0 + Rational(2, 9).div(Rational(-8.6)).should == -1 + Rational(3.12).div(0.5).should == 6 + end + + it "raises a ZeroDivisionError when the argument is 0.0" do + -> { Rational(3, 4).div(0.0) }.should raise_error(ZeroDivisionError) + end end diff --git a/spec/ruby/core/rational/divide_spec.rb b/spec/ruby/core/rational/divide_spec.rb index 14e8c4c195..8f5ca1fdec 100644 --- a/spec/ruby/core/rational/divide_spec.rb +++ b/spec/ruby/core/rational/divide_spec.rb @@ -1,20 +1,74 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/divide' -require_relative '../../shared/rational/arithmetic_exception_in_coerce' +require_relative 'shared/arithmetic_exception_in_coerce' describe "Rational#/" do - it_behaves_like :rational_divide, :/ + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational / obj + end + + it "calls #/ on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:/).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational / obj).should == :result + end + it_behaves_like :rational_arithmetic_exception_in_coerce, :/ end describe "Rational#/ when passed an Integer" do - it_behaves_like :rational_divide_int, :/ + it "returns self divided by other as a Rational" do + (Rational(3, 4) / 2).should eql(Rational(3, 8)) + (Rational(2, 4) / 2).should eql(Rational(1, 4)) + (Rational(6, 7) / -2).should eql(Rational(-3, 7)) + end + + it "raises a ZeroDivisionError when passed 0" do + -> { Rational(3, 4) / 0 }.should raise_error(ZeroDivisionError) + end end describe "Rational#/ when passed a Rational" do - it_behaves_like :rational_divide_rat, :/ + it "returns self divided by other as a Rational" do + (Rational(3, 4) / Rational(3, 4)).should eql(Rational(1, 1)) + (Rational(2, 4) / Rational(1, 4)).should eql(Rational(2, 1)) + + (Rational(2, 4) / 2).should == Rational(1, 4) + (Rational(6, 7) / -2).should == Rational(-3, 7) + end + + it "raises a ZeroDivisionError when passed a Rational with a numerator of 0" do + -> { Rational(3, 4) / Rational(0, 1) }.should raise_error(ZeroDivisionError) + end end describe "Rational#/ when passed a Float" do - it_behaves_like :rational_divide_float, :/ + it "returns self divided by other as a Float" do + (Rational(3, 4) / 0.75).should eql(1.0) + (Rational(3, 4) / 0.25).should eql(3.0) + (Rational(3, 4) / 0.3).should eql(2.5) + + (Rational(-3, 4) / 0.3).should eql(-2.5) + (Rational(3, -4) / 0.3).should eql(-2.5) + (Rational(3, 4) / -0.3).should eql(-2.5) + end + + it "returns infinity when passed 0" do + (Rational(3, 4) / 0.0).infinite?.should eql(1) + (Rational(-3, -4) / 0.0).infinite?.should eql(1) + + (Rational(-3, 4) / 0.0).infinite?.should eql(-1) + (Rational(3, -4) / 0.0).infinite?.should eql(-1) + end end diff --git a/spec/ruby/core/rational/divmod_spec.rb b/spec/ruby/core/rational/divmod_spec.rb index 7ffdde74f4..f0555294a3 100644 --- a/spec/ruby/core/rational/divmod_spec.rb +++ b/spec/ruby/core/rational/divmod_spec.rb @@ -1,14 +1,42 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/divmod' describe "Rational#divmod when passed a Rational" do - it_behaves_like :rational_divmod_rat, :divmod + it "returns the quotient as Integer and the remainder as Rational" do + Rational(7, 4).divmod(Rational(1, 2)).should eql([3, Rational(1, 4)]) + Rational(7, 4).divmod(Rational(-1, 2)).should eql([-4, Rational(-1, 4)]) + Rational(0, 4).divmod(Rational(4, 3)).should eql([0, Rational(0, 1)]) + + Rational(bignum_value, 4).divmod(Rational(4, 3)).should eql([3458764513820540928, Rational(0, 1)]) + end + + it "raises a ZeroDivisionError when passed a Rational with a numerator of 0" do + -> { Rational(7, 4).divmod(Rational(0, 3)) }.should raise_error(ZeroDivisionError) + end end describe "Rational#divmod when passed an Integer" do - it_behaves_like :rational_divmod_int, :divmod + it "returns the quotient as Integer and the remainder as Rational" do + Rational(7, 4).divmod(2).should eql([0, Rational(7, 4)]) + Rational(7, 4).divmod(-2).should eql([-1, Rational(-1, 4)]) + + Rational(bignum_value, 4).divmod(3).should eql([1537228672809129301, Rational(1, 1)]) + end + + it "raises a ZeroDivisionError when passed 0" do + -> { Rational(7, 4).divmod(0) }.should raise_error(ZeroDivisionError) + end end describe "Rational#divmod when passed a Float" do - it_behaves_like :rational_divmod_float, :divmod + it "returns the quotient as Integer and the remainder as Float" do + Rational(7, 4).divmod(0.5).should eql([3, 0.25]) + end + + it "returns the quotient as Integer and the remainder as Float" do + Rational(7, 4).divmod(-0.5).should eql([-4, -0.25]) + end + + it "raises a ZeroDivisionError when passed 0" do + -> { Rational(7, 4).divmod(0.0) }.should raise_error(ZeroDivisionError) + end end diff --git a/spec/ruby/core/rational/equal_value_spec.rb b/spec/ruby/core/rational/equal_value_spec.rb index c6f7f4c6a2..ba40d29c3b 100644 --- a/spec/ruby/core/rational/equal_value_spec.rb +++ b/spec/ruby/core/rational/equal_value_spec.rb @@ -1,18 +1,39 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/equal_value' describe "Rational#==" do - it_behaves_like :rational_equal_value, :== + it "returns the result of calling #== with self on the passed argument" do + obj = mock("Object") + obj.should_receive(:==).and_return(:result) + + (Rational(3, 4) == obj).should_not be_false + end end describe "Rational#== when passed a Rational" do - it_behaves_like :rational_equal_value_rat, :== + it "returns true if self has the same numerator and denominator as the passed argument" do + (Rational(3, 4) == Rational(3, 4)).should be_true + (Rational(-3, -4) == Rational(3, 4)).should be_true + (Rational(-4, 5) == Rational(4, -5)).should be_true + + (Rational(bignum_value, 3) == Rational(bignum_value, 3)).should be_true + (Rational(-bignum_value, 3) == Rational(bignum_value, -3)).should be_true + end end describe "Rational#== when passed a Float" do - it_behaves_like :rational_equal_value_float, :== + it "converts self to a Float and compares it with the passed argument" do + (Rational(3, 4) == 0.75).should be_true + (Rational(4, 2) == 2.0).should be_true + (Rational(-4, 2) == -2.0).should be_true + (Rational(4, -2) == -2.0).should be_true + end end describe "Rational#== when passed an Integer" do - it_behaves_like :rational_equal_value_int, :== + it "returns true if self has the passed argument as numerator and a denominator of 1" do + # Rational(x, y) reduces x and y automatically + (Rational(4, 2) == 2).should be_true + (Rational(-4, 2) == -2).should be_true + (Rational(4, -2) == -2).should be_true + end end diff --git a/spec/ruby/core/rational/exponent_spec.rb b/spec/ruby/core/rational/exponent_spec.rb index 7e35b4ebc1..65fbf2ed1c 100644 --- a/spec/ruby/core/rational/exponent_spec.rb +++ b/spec/ruby/core/rational/exponent_spec.rb @@ -1,6 +1,236 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/exponent' describe "Rational#**" do - it_behaves_like :rational_exponent, :** + describe "when passed Rational" do + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns Rational(1) if the exponent is Rational(0)" do + (Rational(0) ** Rational(0)).should eql(Rational(1)) + (Rational(1) ** Rational(0)).should eql(Rational(1)) + (Rational(3, 4) ** Rational(0)).should eql(Rational(1)) + (Rational(-1) ** Rational(0)).should eql(Rational(1)) + (Rational(-3, 4) ** Rational(0)).should eql(Rational(1)) + (Rational(bignum_value) ** Rational(0)).should eql(Rational(1)) + (Rational(-bignum_value) ** Rational(0)).should eql(Rational(1)) + end + + it "returns self raised to the argument as a Rational if the exponent's denominator is 1" do + (Rational(3, 4) ** Rational(1, 1)).should eql(Rational(3, 4)) + (Rational(3, 4) ** Rational(2, 1)).should eql(Rational(9, 16)) + (Rational(3, 4) ** Rational(-1, 1)).should eql(Rational(4, 3)) + (Rational(3, 4) ** Rational(-2, 1)).should eql(Rational(16, 9)) + end + + it "returns self raised to the argument as a Float if the exponent's denominator is not 1" do + (Rational(3, 4) ** Rational(4, 3)).should be_close(0.681420222312052, TOLERANCE) + (Rational(3, 4) ** Rational(-4, 3)).should be_close(1.46752322173095, TOLERANCE) + (Rational(3, 4) ** Rational(4, -3)).should be_close(1.46752322173095, TOLERANCE) + end + + it "returns a complex number when self is negative and the passed argument is not 0" do + (Rational(-3, 4) ** Rational(-4, 3)).should be_close(Complex(-0.7337616108654732, 1.2709123906625817), TOLERANCE) + end + end + end + + describe "when passed Integer" do + it "returns the Rational value of self raised to the passed argument" do + (Rational(3, 4) ** 4).should == Rational(81, 256) + (Rational(3, 4) ** -4).should == Rational(256, 81) + (Rational(-3, 4) ** -4).should == Rational(256, 81) + (Rational(3, -4) ** -4).should == Rational(256, 81) + + (Rational(bignum_value, 4) ** 4).should == Rational(452312848583266388373324160190187140051835877600158453279131187530910662656, 1) + (Rational(3, bignum_value) ** -4).should == Rational(115792089237316195423570985008687907853269984665640564039457584007913129639936, 81) + (Rational(-bignum_value, 4) ** -4).should == Rational(1, 452312848583266388373324160190187140051835877600158453279131187530910662656) + (Rational(3, -bignum_value) ** -4).should == Rational(115792089237316195423570985008687907853269984665640564039457584007913129639936, 81) + end + + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + it "returns Rational(1, 1) when the passed argument is 0" do + (Rational(3, 4) ** 0).should eql(Rational(1, 1)) + (Rational(-3, 4) ** 0).should eql(Rational(1, 1)) + (Rational(3, -4) ** 0).should eql(Rational(1, 1)) + + (Rational(bignum_value, 4) ** 0).should eql(Rational(1, 1)) + (Rational(3, -bignum_value) ** 0).should eql(Rational(1, 1)) + end + end + end + + describe "when passed Bignum" do + # #5713 + it "returns Rational(0) when self is Rational(0) and the exponent is positive" do + (Rational(0) ** bignum_value).should eql(Rational(0)) + end + + it "raises ZeroDivisionError when self is Rational(0) and the exponent is negative" do + -> { Rational(0) ** -bignum_value }.should raise_error(ZeroDivisionError) + end + + it "returns Rational(1) when self is Rational(1)" do + (Rational(1) ** bignum_value).should eql(Rational(1)) + (Rational(1) ** -bignum_value).should eql(Rational(1)) + end + + it "returns Rational(1) when self is Rational(-1) and the exponent is positive and even" do + (Rational(-1) ** bignum_value(0)).should eql(Rational(1)) + (Rational(-1) ** bignum_value(2)).should eql(Rational(1)) + end + + it "returns Rational(-1) when self is Rational(-1) and the exponent is positive and odd" do + (Rational(-1) ** bignum_value(1)).should eql(Rational(-1)) + (Rational(-1) ** bignum_value(3)).should eql(Rational(-1)) + end + + ruby_version_is ""..."3.4" do + it "returns positive Infinity when self is > 1" do + -> { + (Rational(2) ** bignum_value).infinite?.should == 1 + }.should complain(/warning: in a\*\*b, b may be too big/) + -> { + (Rational(fixnum_max) ** bignum_value).infinite?.should == 1 + }.should complain(/warning: in a\*\*b, b may be too big/) + end + + it "returns 0.0 when self is > 1 and the exponent is negative" do + -> { + (Rational(2) ** -bignum_value).should eql(0.0) + }.should complain(/warning: in a\*\*b, b may be too big/) + -> { + (Rational(fixnum_max) ** -bignum_value).should eql(0.0) + }.should complain(/warning: in a\*\*b, b may be too big/) + end + end + + ruby_version_is "3.4" do + it "raises an ArgumentError when self is > 1" do + -> { + (Rational(2) ** bignum_value) + }.should raise_error(ArgumentError) + -> { + (Rational(fixnum_max) ** bignum_value) + }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when self is > 1 and the exponent is negative" do + -> { + (Rational(2) ** -bignum_value) + }.should raise_error(ArgumentError) + -> { + (Rational(fixnum_max) ** -bignum_value) + }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when self is < -1" do + -> { + (Rational(-2) ** bignum_value) + }.should raise_error(ArgumentError) + -> { + (Rational(fixnum_min) ** bignum_value) + }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when self is < -1 and the exponent is negative" do + -> { + (Rational(-2) ** -bignum_value) + }.should raise_error(ArgumentError) + -> { + (Rational(fixnum_min) ** -bignum_value) + }.should raise_error(ArgumentError) + end + end + + # Fails on linux due to pow() bugs in glibc: http://sources.redhat.com/bugzilla/show_bug.cgi?id=3866 + platform_is_not :linux do + ruby_version_is ""..."3.4" do + it "returns positive Infinity when self < -1" do + -> { + (Rational(-2) ** bignum_value).infinite?.should == 1 + }.should complain(/warning: in a\*\*b, b may be too big/) + -> { + (Rational(-2) ** (bignum_value + 1)).infinite?.should == 1 + }.should complain(/warning: in a\*\*b, b may be too big/) + -> { + (Rational(fixnum_min) ** bignum_value).infinite?.should == 1 + }.should complain(/warning: in a\*\*b, b may be too big/) + end + + it "returns 0.0 when self is < -1 and the exponent is negative" do + -> { + (Rational(-2) ** -bignum_value).should eql(0.0) + }.should complain(/warning: in a\*\*b, b may be too big/) + -> { + (Rational(fixnum_min) ** -bignum_value).should eql(0.0) + }.should complain(/warning: in a\*\*b, b may be too big/) + end + end + end + end + + describe "when passed Float" do + it "returns self converted to Float and raised to the passed argument" do + (Rational(3, 1) ** 3.0).should eql(27.0) + (Rational(3, 1) ** 1.5).should be_close(5.19615242270663, TOLERANCE) + (Rational(3, 1) ** -1.5).should be_close(0.192450089729875, TOLERANCE) + end + + it "returns a complex number if self is negative and the passed argument is not 0" do + (Rational(-3, 2) ** 1.5).should be_close(Complex(0.0, -1.8371173070873836), TOLERANCE) + (Rational(3, -2) ** 1.5).should be_close(Complex(0.0, -1.8371173070873836), TOLERANCE) + (Rational(3, -2) ** -1.5).should be_close(Complex(0.0, 0.5443310539518174), TOLERANCE) + end + + it "returns Complex(1.0) when the passed argument is 0.0" do + (Rational(3, 4) ** 0.0).should == Complex(1.0) + (Rational(-3, 4) ** 0.0).should == Complex(1.0) + (Rational(-3, 4) ** 0.0).should == Complex(1.0) + end + end + + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational ** obj + end + + it "calls #** on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:**).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational ** obj).should == :result + end + + it "raises ZeroDivisionError for Rational(0, 1) passed a negative Integer" do + [-1, -4, -9999].each do |exponent| + -> { Rational(0, 1) ** exponent }.should raise_error(ZeroDivisionError, "divided by 0") + end + end + + it "raises ZeroDivisionError for Rational(0, 1) passed a negative Rational with denominator 1" do + [Rational(-1, 1), Rational(-3, 1)].each do |exponent| + -> { Rational(0, 1) ** exponent }.should raise_error(ZeroDivisionError, "divided by 0") + end + end + + # #7513 + it "raises ZeroDivisionError for Rational(0, 1) passed a negative Rational" do + -> { Rational(0, 1) ** Rational(-3, 2) }.should raise_error(ZeroDivisionError, "divided by 0") + end + + it "returns Infinity for Rational(0, 1) passed a negative Float" do + [-1.0, -3.0, -3.14].each do |exponent| + (Rational(0, 1) ** exponent).infinite?.should == 1 + end + end end diff --git a/spec/ruby/core/rational/fdiv_spec.rb b/spec/ruby/core/rational/fdiv_spec.rb index b75f39abd5..118d93dbe7 100644 --- a/spec/ruby/core/rational/fdiv_spec.rb +++ b/spec/ruby/core/rational/fdiv_spec.rb @@ -1,6 +1,5 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/fdiv' describe "Rational#fdiv" do - it_behaves_like :rational_fdiv, :fdiv + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/core/rational/fixtures/rational.rb b/spec/ruby/core/rational/fixtures/rational.rb new file mode 100644 index 0000000000..844d7f9820 --- /dev/null +++ b/spec/ruby/core/rational/fixtures/rational.rb @@ -0,0 +1,14 @@ +module RationalSpecs + class SubNumeric < Numeric + def initialize(value) + @value = Rational(value) + end + + def to_r + @value + end + end + + class CoerceError < StandardError + end +end diff --git a/spec/ruby/core/rational/floor_spec.rb b/spec/ruby/core/rational/floor_spec.rb index 70db0499d0..8068aaf119 100644 --- a/spec/ruby/core/rational/floor_spec.rb +++ b/spec/ruby/core/rational/floor_spec.rb @@ -1,6 +1,45 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/floor' describe "Rational#floor" do - it_behaves_like :rational_floor, :floor + before do + @rational = Rational(2200, 7) + end + + describe "with no arguments (precision = 0)" do + it "returns an integer" do + @rational.floor.should be_kind_of(Integer) + end + + it "returns the truncated value toward negative infinity" do + @rational.floor.should == 314 + Rational(1, 2).floor.should == 0 + Rational(-1, 2).floor.should == -1 + end + end + + describe "with a precision < 0" do + it "returns an integer" do + @rational.floor(-2).should be_kind_of(Integer) + @rational.floor(-1).should be_kind_of(Integer) + end + + it "moves the truncation point n decimal places left" do + @rational.floor(-3).should == 0 + @rational.floor(-2).should == 300 + @rational.floor(-1).should == 310 + end + end + + describe "with a precision > 0" do + it "returns a Rational" do + @rational.floor(1).should be_kind_of(Rational) + @rational.floor(2).should be_kind_of(Rational) + end + + it "moves the truncation point n decimal places right" do + @rational.floor(1).should == Rational(1571, 5) + @rational.floor(2).should == Rational(7857, 25) + @rational.floor(3).should == Rational(62857, 200) + end + end end diff --git a/spec/ruby/core/rational/hash_spec.rb b/spec/ruby/core/rational/hash_spec.rb index 7e8d30049b..528638056a 100644 --- a/spec/ruby/core/rational/hash_spec.rb +++ b/spec/ruby/core/rational/hash_spec.rb @@ -1,6 +1,9 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/hash' describe "Rational#hash" do - it_behaves_like :rational_hash, :hash + # BUG: Rational(2, 3).hash == Rational(3, 2).hash + it "is static" do + Rational(2, 3).hash.should == Rational(2, 3).hash + Rational(2, 4).hash.should_not == Rational(2, 3).hash + end end diff --git a/spec/ruby/core/rational/inspect_spec.rb b/spec/ruby/core/rational/inspect_spec.rb index 2cbf6cadc1..edc5cffee9 100644 --- a/spec/ruby/core/rational/inspect_spec.rb +++ b/spec/ruby/core/rational/inspect_spec.rb @@ -1,6 +1,14 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/inspect' describe "Rational#inspect" do - it_behaves_like :rational_inspect, :inspect + it "returns a string representation of self" do + Rational(3, 4).inspect.should == "(3/4)" + Rational(-5, 8).inspect.should == "(-5/8)" + Rational(-1, -2).inspect.should == "(1/2)" + + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + Rational(bignum_value, 1).inspect.should == "(#{bignum_value}/1)" + end + end end diff --git a/spec/ruby/core/rational/magnitude_spec.rb b/spec/ruby/core/rational/magnitude_spec.rb index 27d9af6a81..f5f667edb1 100644 --- a/spec/ruby/core/rational/magnitude_spec.rb +++ b/spec/ruby/core/rational/magnitude_spec.rb @@ -1,5 +1,5 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/abs' +require_relative 'shared/abs' describe "Rational#abs" do it_behaves_like :rational_abs, :magnitude diff --git a/spec/ruby/core/rational/minus_spec.rb b/spec/ruby/core/rational/minus_spec.rb index a61b62ebe6..8aee85f9dd 100644 --- a/spec/ruby/core/rational/minus_spec.rb +++ b/spec/ruby/core/rational/minus_spec.rb @@ -1,5 +1,5 @@ require_relative '../../spec_helper' -require_relative '../../shared/rational/arithmetic_exception_in_coerce' +require_relative 'shared/arithmetic_exception_in_coerce' describe "Rational#-" do it_behaves_like :rational_arithmetic_exception_in_coerce, :- diff --git a/spec/ruby/core/rational/modulo_spec.rb b/spec/ruby/core/rational/modulo_spec.rb index 7a60c176ac..23ed93e118 100644 --- a/spec/ruby/core/rational/modulo_spec.rb +++ b/spec/ruby/core/rational/modulo_spec.rb @@ -1,6 +1,43 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/modulo' describe "Rational#%" do - it_behaves_like :rational_modulo, :% + it "returns the remainder when this value is divided by other" do + (Rational(2, 3) % Rational(2, 3)).should == Rational(0, 1) + (Rational(4, 3) % Rational(2, 3)).should == Rational(0, 1) + (Rational(2, -3) % Rational(-2, 3)).should == Rational(0, 1) + (Rational(0, -1) % -1).should == Rational(0, 1) + + (Rational(7, 4) % Rational(1, 2)).should == Rational(1, 4) + (Rational(7, 4) % 1).should == Rational(3, 4) + (Rational(7, 4) % Rational(1, 7)).should == Rational(1, 28) + + (Rational(3, 4) % -1).should == Rational(-1, 4) + (Rational(1, -5) % -1).should == Rational(-1, 5) + end + + it "returns a Float value when the argument is Float" do + (Rational(7, 4) % 1.0).should be_kind_of(Float) + (Rational(7, 4) % 1.0).should == 0.75 + (Rational(7, 4) % 0.26).should be_close(0.19, 0.0001) + end + + it "raises ZeroDivisionError on zero denominator" do + -> { + Rational(3, 5) % Rational(0, 1) + }.should raise_error(ZeroDivisionError) + + -> { + Rational(0, 1) % Rational(0, 1) + }.should raise_error(ZeroDivisionError) + + -> { + Rational(3, 5) % 0 + }.should raise_error(ZeroDivisionError) + end + + it "raises a ZeroDivisionError when the argument is 0.0" do + -> { + Rational(3, 5) % 0.0 + }.should raise_error(ZeroDivisionError) + end end diff --git a/spec/ruby/core/rational/multiply_spec.rb b/spec/ruby/core/rational/multiply_spec.rb index 7413376bb1..87fb4de2b4 100644 --- a/spec/ruby/core/rational/multiply_spec.rb +++ b/spec/ruby/core/rational/multiply_spec.rb @@ -1,20 +1,65 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/multiply' -require_relative '../../shared/rational/arithmetic_exception_in_coerce' +require_relative 'shared/arithmetic_exception_in_coerce' describe "Rational#*" do - it_behaves_like :rational_multiply, :* + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational * obj + end + + it "calls #* on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:*).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational * obj).should == :result + end + it_behaves_like :rational_arithmetic_exception_in_coerce, :* end describe "Rational#* passed a Rational" do - it_behaves_like :rational_multiply_rat, :* + it "returns self divided by other as a Rational" do + (Rational(3, 4) * Rational(3, 4)).should eql(Rational(9, 16)) + (Rational(2, 4) * Rational(1, 4)).should eql(Rational(1, 8)) + + (Rational(3, 4) * Rational(0, 1)).should eql(Rational(0, 4)) + end end describe "Rational#* passed a Float" do - it_behaves_like :rational_multiply_float, :* + it "returns self divided by other as a Float" do + (Rational(3, 4) * 0.75).should eql(0.5625) + (Rational(3, 4) * 0.25).should eql(0.1875) + (Rational(3, 4) * 0.3).should be_close(0.225, TOLERANCE) + + (Rational(-3, 4) * 0.3).should be_close(-0.225, TOLERANCE) + (Rational(3, -4) * 0.3).should be_close(-0.225, TOLERANCE) + (Rational(3, 4) * -0.3).should be_close(-0.225, TOLERANCE) + + (Rational(3, 4) * 0.0).should eql(0.0) + (Rational(-3, -4) * 0.0).should eql(0.0) + + (Rational(-3, 4) * 0.0).should eql(0.0) + (Rational(3, -4) * 0.0).should eql(0.0) + end end describe "Rational#* passed an Integer" do - it_behaves_like :rational_multiply_int, :* + it "returns self divided by other as a Rational" do + (Rational(3, 4) * 2).should eql(Rational(3, 2)) + (Rational(2, 4) * 2).should eql(Rational(1, 1)) + (Rational(6, 7) * -2).should eql(Rational(-12, 7)) + + (Rational(3, 4) * 0).should eql(Rational(0, 4)) + end end diff --git a/spec/ruby/core/rational/numerator_spec.rb b/spec/ruby/core/rational/numerator_spec.rb index 6f9a9c0e3b..2b9fe2ff5c 100644 --- a/spec/ruby/core/rational/numerator_spec.rb +++ b/spec/ruby/core/rational/numerator_spec.rb @@ -1,6 +1,10 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/numerator' describe "Rational#numerator" do - it_behaves_like :rational_numerator, :numerator + it "returns the numerator" do + Rational(3, 4).numerator.should equal(3) + Rational(3, -4).numerator.should equal(-3) + + Rational(bignum_value, 1).numerator.should == bignum_value + end end diff --git a/spec/ruby/core/rational/plus_spec.rb b/spec/ruby/core/rational/plus_spec.rb index 67c0ff63d2..01df5f6719 100644 --- a/spec/ruby/core/rational/plus_spec.rb +++ b/spec/ruby/core/rational/plus_spec.rb @@ -1,19 +1,50 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/plus' -require_relative '../../shared/rational/arithmetic_exception_in_coerce' +require_relative 'shared/arithmetic_exception_in_coerce' describe "Rational#+" do - it_behaves_like :rational_plus, :+ + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational + obj + end + + it "calls #+ on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:+).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + (rational + obj).should == :result + end + it_behaves_like :rational_arithmetic_exception_in_coerce, :+ end describe "Rational#+ with a Rational" do - it_behaves_like :rational_plus_rat, :+ + it "returns the result of subtracting other from self as a Rational" do + (Rational(3, 4) + Rational(0, 1)).should eql(Rational(3, 4)) + (Rational(3, 4) + Rational(1, 4)).should eql(Rational(1, 1)) + + (Rational(3, 4) + Rational(2, 1)).should eql(Rational(11, 4)) + end end describe "Rational#+ with a Float" do - it_behaves_like :rational_plus_float, :+ + it "returns the result of subtracting other from self as a Float" do + (Rational(3, 4) + 0.2).should eql(0.95) + (Rational(3, 4) + 2.5).should eql(3.25) + end end describe "Rational#+ with an Integer" do - it_behaves_like :rational_plus_int, :+ + it "returns the result of subtracting other from self as a Rational" do + (Rational(3, 4) + 1).should eql(Rational(7, 4)) + (Rational(3, 4) + 2).should eql(Rational(11, 4)) + end end diff --git a/spec/ruby/core/rational/quo_spec.rb b/spec/ruby/core/rational/quo_spec.rb index 181f091f7c..907898ad34 100644 --- a/spec/ruby/core/rational/quo_spec.rb +++ b/spec/ruby/core/rational/quo_spec.rb @@ -1,6 +1,25 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/divide' describe "Rational#quo" do - it_behaves_like :rational_divide, :quo + it "calls #coerce on the passed argument with self" do + rational = Rational(3, 4) + obj = mock("Object") + obj.should_receive(:coerce).with(rational).and_return([1, 2]) + + rational.quo(obj) + end + + it "calls #/ on the coerced Rational with the coerced Object" do + rational = Rational(3, 4) + + coerced_rational = mock("Coerced Rational") + coerced_rational.should_receive(:/).and_return(:result) + + coerced_obj = mock("Coerced Object") + + obj = mock("Object") + obj.should_receive(:coerce).and_return([coerced_rational, coerced_obj]) + + rational.quo(obj).should == :result + end end diff --git a/spec/ruby/core/rational/remainder_spec.rb b/spec/ruby/core/rational/remainder_spec.rb index 1c0035e5f4..86ba4674e6 100644 --- a/spec/ruby/core/rational/remainder_spec.rb +++ b/spec/ruby/core/rational/remainder_spec.rb @@ -1,6 +1,5 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/remainder' describe "Rational#remainder" do - it_behaves_like :rational_remainder, :remainder + it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/core/rational/round_spec.rb b/spec/ruby/core/rational/round_spec.rb index 36614a552d..ac3dcafe7b 100644 --- a/spec/ruby/core/rational/round_spec.rb +++ b/spec/ruby/core/rational/round_spec.rb @@ -1,6 +1,106 @@ require_relative '../../spec_helper' -require_relative '../../shared/rational/round' describe "Rational#round" do - it_behaves_like :rational_round, :round + before do + @rational = Rational(2200, 7) + end + + describe "with no arguments (precision = 0)" do + it "returns an integer" do + @rational.round.should be_kind_of(Integer) + Rational(0, 1).round(0).should be_kind_of(Integer) + Rational(124, 1).round(0).should be_kind_of(Integer) + end + + it "returns the truncated value toward the nearest integer" do + @rational.round.should == 314 + Rational(0, 1).round(0).should == 0 + Rational(2, 1).round(0).should == 2 + end + + it "returns the rounded value toward the nearest integer" do + Rational(1, 2).round.should == 1 + Rational(-1, 2).round.should == -1 + Rational(3, 2).round.should == 2 + Rational(-3, 2).round.should == -2 + Rational(5, 2).round.should == 3 + Rational(-5, 2).round.should == -3 + end + end + + describe "with a precision < 0" do + it "returns an integer" do + @rational.round(-2).should be_kind_of(Integer) + @rational.round(-1).should be_kind_of(Integer) + Rational(0, 1).round(-1).should be_kind_of(Integer) + Rational(2, 1).round(-1).should be_kind_of(Integer) + end + + it "moves the truncation point n decimal places left" do + @rational.round(-3).should == 0 + @rational.round(-2).should == 300 + @rational.round(-1).should == 310 + end + end + + describe "with a precision > 0" do + it "returns a Rational" do + @rational.round(1).should be_kind_of(Rational) + @rational.round(2).should be_kind_of(Rational) + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + Rational(0, 1).round(1).should be_kind_of(Rational) + Rational(2, 1).round(1).should be_kind_of(Rational) + end + end + + it "moves the truncation point n decimal places right" do + @rational.round(1).should == Rational(3143, 10) + @rational.round(2).should == Rational(31429, 100) + @rational.round(3).should == Rational(157143, 500) + Rational(0, 1).round(1).should == Rational(0, 1) + Rational(2, 1).round(1).should == Rational(2, 1) + end + + it "doesn't alter the value if the precision is too great" do + Rational(3, 2).round(10).should == Rational(3, 2).round(20) + end + + # #6605 + it "doesn't fail when rounding to an absurdly large positive precision" do + Rational(3, 2).round(2_097_171).should == Rational(3, 2) + end + end + + describe "with half option" do + it "returns an Integer when precision is not passed" do + Rational(10, 4).round(half: nil).should == 3 + Rational(10, 4).round(half: :up).should == 3 + Rational(10, 4).round(half: :down).should == 2 + Rational(10, 4).round(half: :even).should == 2 + Rational(-10, 4).round(half: nil).should == -3 + Rational(-10, 4).round(half: :up).should == -3 + Rational(-10, 4).round(half: :down).should == -2 + Rational(-10, 4).round(half: :even).should == -2 + end + + it "returns a Rational when the precision is greater than 0" do + Rational(25, 100).round(1, half: nil).should == Rational(3, 10) + Rational(25, 100).round(1, half: :up).should == Rational(3, 10) + Rational(25, 100).round(1, half: :down).should == Rational(1, 5) + Rational(25, 100).round(1, half: :even).should == Rational(1, 5) + Rational(35, 100).round(1, half: nil).should == Rational(2, 5) + Rational(35, 100).round(1, half: :up).should == Rational(2, 5) + Rational(35, 100).round(1, half: :down).should == Rational(3, 10) + Rational(35, 100).round(1, half: :even).should == Rational(2, 5) + Rational(-25, 100).round(1, half: nil).should == Rational(-3, 10) + Rational(-25, 100).round(1, half: :up).should == Rational(-3, 10) + Rational(-25, 100).round(1, half: :down).should == Rational(-1, 5) + Rational(-25, 100).round(1, half: :even).should == Rational(-1, 5) + end + + it "raise for a non-existent round mode" do + -> { Rational(10, 4).round(half: :nonsense) }.should raise_error(ArgumentError, "invalid rounding mode: nonsense") + end + end end diff --git a/spec/ruby/core/rational/shared/abs.rb b/spec/ruby/core/rational/shared/abs.rb new file mode 100644 index 0000000000..3d64bcc1a0 --- /dev/null +++ b/spec/ruby/core/rational/shared/abs.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' + +describe :rational_abs, shared: true do + it "returns self's absolute value" do + Rational(3, 4).send(@method).should == Rational(3, 4) + Rational(-3, 4).send(@method).should == Rational(3, 4) + Rational(3, -4).send(@method).should == Rational(3, 4) + + Rational(bignum_value, -bignum_value).send(@method).should == Rational(bignum_value, bignum_value) + end +end diff --git a/spec/ruby/core/rational/shared/arithmetic_exception_in_coerce.rb b/spec/ruby/core/rational/shared/arithmetic_exception_in_coerce.rb new file mode 100644 index 0000000000..f4cf70d147 --- /dev/null +++ b/spec/ruby/core/rational/shared/arithmetic_exception_in_coerce.rb @@ -0,0 +1,11 @@ +require_relative '../fixtures/rational' + +describe :rational_arithmetic_exception_in_coerce, shared: true do + it "does not rescue exception raised in other#coerce" do + b = mock("numeric with failed #coerce") + b.should_receive(:coerce).and_raise(RationalSpecs::CoerceError) + + # e.g. Rational(3, 4) + b + -> { Rational(3, 4).send(@method, b) }.should raise_error(RationalSpecs::CoerceError) + end +end diff --git a/spec/ruby/core/rational/to_f_spec.rb b/spec/ruby/core/rational/to_f_spec.rb index a9cd1be3b5..d0da49d377 100644 --- a/spec/ruby/core/rational/to_f_spec.rb +++ b/spec/ruby/core/rational/to_f_spec.rb @@ -1,6 +1,16 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/to_f' describe "Rational#to_f" do - it_behaves_like :rational_to_f, :to_f + it "returns self converted to a Float" do + Rational(3, 4).to_f.should eql(0.75) + Rational(3, -4).to_f.should eql(-0.75) + Rational(-1, 4).to_f.should eql(-0.25) + Rational(-1, -4).to_f.should eql(0.25) + end + + it "converts to a Float for large numerator and denominator" do + num = 1000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146010000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146010000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146009 + den = 2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + Rational(num, den).to_f.should == 500.0 + end end diff --git a/spec/ruby/core/rational/to_i_spec.rb b/spec/ruby/core/rational/to_i_spec.rb index 22cf02b4da..520a380b2a 100644 --- a/spec/ruby/core/rational/to_i_spec.rb +++ b/spec/ruby/core/rational/to_i_spec.rb @@ -1,6 +1,12 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/to_i' describe "Rational#to_i" do - it_behaves_like :rational_to_i, :to_i + it "converts self to an Integer by truncation" do + Rational(7, 4).to_i.should eql(1) + Rational(11, 4).to_i.should eql(2) + end + + it "converts self to an Integer by truncation" do + Rational(-7, 4).to_i.should eql(-1) + end end diff --git a/spec/ruby/core/rational/to_r_spec.rb b/spec/ruby/core/rational/to_r_spec.rb index 03f204daf1..34f16d7890 100644 --- a/spec/ruby/core/rational/to_r_spec.rb +++ b/spec/ruby/core/rational/to_r_spec.rb @@ -1,8 +1,13 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/to_r' describe "Rational#to_r" do - it_behaves_like :rational_to_r, :to_r + it "returns self" do + a = Rational(3, 4) + a.to_r.should equal(a) + + a = Rational(bignum_value, 4) + a.to_r.should equal(a) + end it "raises TypeError trying to convert BasicObject" do obj = BasicObject.new diff --git a/spec/ruby/core/rational/to_s_spec.rb b/spec/ruby/core/rational/to_s_spec.rb index 5d90c7d80b..24e30778e5 100644 --- a/spec/ruby/core/rational/to_s_spec.rb +++ b/spec/ruby/core/rational/to_s_spec.rb @@ -1,6 +1,14 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/to_s' describe "Rational#to_s" do - it_behaves_like :rational_to_s, :to_s + it "returns a string representation of self" do + # Guard against the Mathn library + guard -> { !defined?(Math.rsqrt) } do + Rational(1, 1).to_s.should == "1/1" + Rational(2, 1).to_s.should == "2/1" + end + Rational(1, 2).to_s.should == "1/2" + Rational(-1, 3).to_s.should == "-1/3" + Rational(1, -3).to_s.should == "-1/3" + end end diff --git a/spec/ruby/core/rational/truncate_spec.rb b/spec/ruby/core/rational/truncate_spec.rb index 47a7cdf17c..728fca34ea 100644 --- a/spec/ruby/core/rational/truncate_spec.rb +++ b/spec/ruby/core/rational/truncate_spec.rb @@ -1,6 +1,71 @@ require_relative "../../spec_helper" -require_relative '../../shared/rational/truncate' describe "Rational#truncate" do - it_behaves_like :rational_truncate, :truncate + before do + @rational = Rational(2200, 7) + end + + describe "with no arguments (precision = 0)" do + it "returns an integer" do + @rational.truncate.should be_kind_of(Integer) + end + + it "returns the truncated value toward 0" do + @rational.truncate.should == 314 + Rational(1, 2).truncate.should == 0 + Rational(-1, 2).truncate.should == 0 + end + end + + describe "with an explicit precision = 0" do + it "returns an integer" do + @rational.truncate(0).should be_kind_of(Integer) + end + + it "returns the truncated value toward 0" do + @rational.truncate(0).should == 314 + Rational(1, 2).truncate(0).should == 0 + Rational(-1, 2).truncate(0).should == 0 + end + end + + describe "with a precision < 0" do + it "returns an integer" do + @rational.truncate(-2).should be_kind_of(Integer) + @rational.truncate(-1).should be_kind_of(Integer) + end + + it "moves the truncation point n decimal places left" do + @rational.truncate(-3).should == 0 + @rational.truncate(-2).should == 300 + @rational.truncate(-1).should == 310 + end + end + + describe "with a precision > 0" do + it "returns a Rational" do + @rational.truncate(1).should be_kind_of(Rational) + @rational.truncate(2).should be_kind_of(Rational) + end + + it "moves the truncation point n decimal places right" do + @rational.truncate(1).should == Rational(1571, 5) + @rational.truncate(2).should == Rational(7857, 25) + @rational.truncate(3).should == Rational(62857, 200) + end + end + + describe "with an invalid value for precision" do + it "raises a TypeError" do + -> { @rational.truncate(nil) }.should raise_error(TypeError, "not an integer") + -> { @rational.truncate(1.0) }.should raise_error(TypeError, "not an integer") + -> { @rational.truncate('') }.should raise_error(TypeError, "not an integer") + end + + it "does not call to_int on the argument" do + object = Object.new + object.should_not_receive(:to_int) + -> { @rational.truncate(object) }.should raise_error(TypeError, "not an integer") + end + end end diff --git a/spec/ruby/core/refinement/append_features_spec.rb b/spec/ruby/core/refinement/append_features_spec.rb index fb84f245bd..f7e5f32bc1 100644 --- a/spec/ruby/core/refinement/append_features_spec.rb +++ b/spec/ruby/core/refinement/append_features_spec.rb @@ -1,20 +1,18 @@ require_relative '../../spec_helper' describe "Refinement#append_features" do - ruby_version_is "3.2" do - it "is not defined" do - Refinement.should_not have_private_instance_method(:append_features) - end + it "is not defined" do + Refinement.should_not have_private_instance_method(:append_features) + end - it "is not called by Module#include" do - c = Class.new - Module.new do - refine c do - called = false - define_method(:append_features){called = true} - proc{c.include(self)}.should raise_error(TypeError) - called.should == false - end + it "is not called by Module#include" do + c = Class.new + Module.new do + refine c do + called = false + define_method(:append_features){called = true} + proc{c.include(self)}.should raise_error(TypeError) + called.should == false end end end diff --git a/spec/ruby/core/refinement/extend_object_spec.rb b/spec/ruby/core/refinement/extend_object_spec.rb index e44e9f46d8..4da8b359cc 100644 --- a/spec/ruby/core/refinement/extend_object_spec.rb +++ b/spec/ruby/core/refinement/extend_object_spec.rb @@ -1,20 +1,20 @@ require_relative '../../spec_helper' describe "Refinement#extend_object" do - ruby_version_is "3.2" do - it "is not defined" do - Refinement.should_not have_private_instance_method(:extend_object) - end + it "is not defined" do + Refinement.should_not have_private_instance_method(:extend_object) + end - it "is not called by Object#extend" do - c = Class.new - Module.new do - refine c do - called = false - define_method(:extend_object){called = true} - proc{c.extend(self)}.should raise_error(TypeError) - called.should == false - end + it "is not called by Object#extend" do + c = Class.new + Module.new do + refine c do + called = false + define_method(:extend_object) { called = true } + -> { + c.extend(self) + }.should raise_error(TypeError) + called.should == false end end end diff --git a/spec/ruby/core/refinement/import_methods_spec.rb b/spec/ruby/core/refinement/import_methods_spec.rb index 05973b2380..13c0b1004c 100644 --- a/spec/ruby/core/refinement/import_methods_spec.rb +++ b/spec/ruby/core/refinement/import_methods_spec.rb @@ -2,137 +2,76 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Refinement#import_methods" do - ruby_version_is "3.1" do - context "when methods are defined in Ruby code" do - it "imports methods" do - str_utils = Module.new do - def indent(level) - " " * level + self - end - end - - Module.new do - refine String do - import_methods str_utils - "foo".indent(3).should == " foo" - end + context "when methods are defined in Ruby code" do + it "imports methods" do + str_utils = Module.new do + def indent(level) + " " * level + self end end - it "throws an exception when argument is not a module" do - Module.new do - refine String do - -> { - import_methods Integer - }.should raise_error(TypeError, "wrong argument type Class (expected Module)") - end + Module.new do + refine String do + import_methods str_utils + "foo".indent(3).should == " foo" end end + end - it "imports methods from multiple modules" do - str_utils = Module.new do - def indent(level) - " " * level + self - end - end - - str_utils_fancy = Module.new do - def indent_star(level) - "*" * level + self - end - end - - Module.new do - refine String do - import_methods str_utils, str_utils_fancy - "foo".indent(3).should == " foo" - "foo".indent_star(3).should == "***foo" - end + it "throws an exception when argument is not a module" do + Module.new do + refine String do + -> { + import_methods Integer + }.should raise_error(TypeError, "wrong argument type Class (expected Module)") end end + end - it "imports a method defined in the last module if method with same name is defined in multiple modules" do - str_utils = Module.new do - def indent(level) - " " * level + self - end - end - - str_utils_fancy = Module.new do - def indent(level) - "*" * level + self - end - end - - Module.new do - refine String do - import_methods str_utils, str_utils_fancy - "foo".indent(3).should == "***foo" - end + it "imports methods from multiple modules" do + str_utils = Module.new do + def indent(level) + " " * level + self end end - it "still imports methods of modules listed before a module that contains method not defined in Ruby" do - str_utils = Module.new do - def indent(level) - " " * level + self - end - end - - string_refined = Module.new do - refine String do - -> { - import_methods str_utils, Kernel - }.should raise_error(ArgumentError) - end + str_utils_fancy = Module.new do + def indent_star(level) + "*" * level + self end + end - Module.new do - using string_refined + Module.new do + refine String do + import_methods str_utils, str_utils_fancy "foo".indent(3).should == " foo" + "foo".indent_star(3).should == "***foo" end end end - it "warns if a module includes/prepends some other module" do - module1 = Module.new do - end - - module2 = Module.new do - include module1 - end - - Module.new do - refine String do - -> { - import_methods module2 - }.should complain(/warning: #<Module:\w*> has ancestors, but Refinement#import_methods doesn't import their methods/) + it "imports a method defined in the last module if method with same name is defined in multiple modules" do + str_utils = Module.new do + def indent(level) + " " * level + self end end - Module.new do - refine String do - -> { - import_methods RefinementSpec::ModuleWithAncestors - }.should complain(/warning: RefinementSpec::ModuleWithAncestors has ancestors, but Refinement#import_methods doesn't import their methods/) + str_utils_fancy = Module.new do + def indent(level) + "*" * level + self end end - end - it "doesn't import methods from included/prepended modules" do Module.new do refine String do - suppress_warning { import_methods RefinementSpec::ModuleWithAncestors } + import_methods str_utils, str_utils_fancy + "foo".indent(3).should == "***foo" end - - using self - -> { - "foo".indent(3) - }.should raise_error(NoMethodError, /undefined method `indent' for ("foo":String|an instance of String)/) end end - it "doesn't import any methods if one of the arguments is not a module" do + it "still imports methods of modules listed before a module that contains method not defined in Ruby" do str_utils = Module.new do def indent(level) " " * level + self @@ -142,126 +81,185 @@ describe "Refinement#import_methods" do string_refined = Module.new do refine String do -> { - import_methods str_utils, Integer - }.should raise_error(TypeError) + import_methods str_utils, Kernel + }.should raise_error(ArgumentError) end end Module.new do using string_refined + "foo".indent(3).should == " foo" + end + end + end + + it "warns if a module includes/prepends some other module" do + module1 = Module.new do + end + + module2 = Module.new do + include module1 + end + + Module.new do + refine String do -> { - "foo".indent(3) - }.should raise_error(NoMethodError) + import_methods module2 + }.should complain(/warning: #<Module:\w*> has ancestors, but Refinement#import_methods doesn't import their methods/) end end - it "imports methods from multiple modules so that methods see other's module's methods" do - str_utils = Module.new do - def indent(level) - " " * level + self - end + Module.new do + refine String do + -> { + import_methods RefinementSpec::ModuleWithAncestors + }.should complain(/warning: RefinementSpec::ModuleWithAncestors has ancestors, but Refinement#import_methods doesn't import their methods/) + end + end + end + + it "doesn't import methods from included/prepended modules" do + Module.new do + refine String do + suppress_warning { import_methods RefinementSpec::ModuleWithAncestors } end - str_utils_normal = Module.new do - def indent_normal(level) - self.indent(level) - end + using self + -> { + "foo".indent(3) + }.should raise_error(NoMethodError, /undefined method [`']indent' for ("foo":String|an instance of String)/) + end + end + + it "doesn't import any methods if one of the arguments is not a module" do + str_utils = Module.new do + def indent(level) + " " * level + self end + end - Module.new do - refine String do - import_methods str_utils, str_utils_normal - end + string_refined = Module.new do + refine String do + -> { + import_methods str_utils, Integer + }.should raise_error(TypeError) + end + end - using self - "foo".indent_normal(3).should == " foo" + Module.new do + using string_refined + -> { + "foo".indent(3) + }.should raise_error(NoMethodError) + end + end + + it "imports methods from multiple modules so that methods see other's module's methods" do + str_utils = Module.new do + def indent(level) + " " * level + self end end - it "imports methods from module so that methods can see each other" do - str_utils = Module.new do - def indent(level) - " " * level + self - end + str_utils_normal = Module.new do + def indent_normal(level) + self.indent(level) + end + end - def indent_with_dot(level) - self.indent(level) + "." - end + Module.new do + refine String do + import_methods str_utils, str_utils_normal end - Module.new do - refine String do - import_methods str_utils - end + using self + "foo".indent_normal(3).should == " foo" + end + end - using self - "foo".indent_with_dot(3).should == " foo." + it "imports methods from module so that methods can see each other" do + str_utils = Module.new do + def indent(level) + " " * level + self + end + + def indent_with_dot(level) + self.indent(level) + "." end end - it "doesn't import module's class methods" do - str_utils = Module.new do - def self.indent(level) - " " * level + self - end + Module.new do + refine String do + import_methods str_utils end - Module.new do - refine String do - import_methods str_utils - end + using self + "foo".indent_with_dot(3).should == " foo." + end + end - using self - -> { - String.indent(3) - }.should raise_error(NoMethodError, /undefined method `indent' for (String:Class|class String)/) + it "doesn't import module's class methods" do + str_utils = Module.new do + def self.indent(level) + " " * level + self end end - it "imports module methods with super" do - class_to_refine = Class.new do - def foo(number) - 2 * number - end + Module.new do + refine String do + import_methods str_utils end - extension = Module.new do - def foo(number) - super * 2 - end + using self + -> { + String.indent(3) + }.should raise_error(NoMethodError, /undefined method [`']indent' for (String:Class|class String)/) + end + end + + it "imports module methods with super" do + class_to_refine = Class.new do + def foo(number) + 2 * number end + end - refinement = Module.new do - refine class_to_refine do - import_methods extension - end + extension = Module.new do + def foo(number) + super * 2 end + end - Module.new do - using refinement - class_to_refine.new.foo(2).should == 8 + refinement = Module.new do + refine class_to_refine do + import_methods extension end end - context "when methods are not defined in Ruby code" do - it "raises ArgumentError" do - Module.new do - refine String do - -> { - import_methods Kernel - }.should raise_error(ArgumentError) - end + Module.new do + using refinement + class_to_refine.new.foo(2).should == 8 + end + end + + context "when methods are not defined in Ruby code" do + it "raises ArgumentError" do + Module.new do + refine String do + -> { + import_methods Kernel + }.should raise_error(ArgumentError) end end + end - it "raises ArgumentError when importing methods from C extension" do - require 'zlib' - Module.new do - refine String do - -> { - import_methods Zlib - }.should raise_error(ArgumentError, /Can't import method which is not defined with Ruby code: Zlib#*/) - end + it "raises ArgumentError when importing methods from C extension" do + require 'zlib' + Module.new do + refine String do + -> { + import_methods Zlib + }.should raise_error(ArgumentError, /Can't import method which is not defined with Ruby code: Zlib#*/) end end end diff --git a/spec/ruby/core/refinement/include_spec.rb b/spec/ruby/core/refinement/include_spec.rb index 25a53f0ec7..57451bd9bc 100644 --- a/spec/ruby/core/refinement/include_spec.rb +++ b/spec/ruby/core/refinement/include_spec.rb @@ -1,26 +1,12 @@ require_relative '../../spec_helper' describe "Refinement#include" do - ruby_version_is "3.1"..."3.2" do - it "warns about deprecation" do - Module.new do - refine String do - -> { - include Module.new - }.should complain(/warning: Refinement#include is deprecated and will be removed in Ruby 3.2/) - end - end - end - end - - ruby_version_is "3.2" do - it "raises a TypeError" do - Module.new do - refine String do - -> { - include Module.new - }.should raise_error(TypeError, "Refinement#include has been removed") - end + it "raises a TypeError" do + Module.new do + refine String do + -> { + include Module.new + }.should raise_error(TypeError, "Refinement#include has been removed") end end end diff --git a/spec/ruby/core/refinement/prepend_features_spec.rb b/spec/ruby/core/refinement/prepend_features_spec.rb index 9fdea199a2..fbc431bbd2 100644 --- a/spec/ruby/core/refinement/prepend_features_spec.rb +++ b/spec/ruby/core/refinement/prepend_features_spec.rb @@ -1,20 +1,18 @@ require_relative '../../spec_helper' describe "Refinement#prepend_features" do - ruby_version_is "3.2" do - it "is not defined" do - Refinement.should_not have_private_instance_method(:prepend_features) - end + it "is not defined" do + Refinement.should_not have_private_instance_method(:prepend_features) + end - it "is not called by Module#prepend" do - c = Class.new - Module.new do - refine c do - called = false - define_method(:prepend_features){called = true} - proc{c.prepend(self)}.should raise_error(TypeError) - called.should == false - end + it "is not called by Module#prepend" do + c = Class.new + Module.new do + refine c do + called = false + define_method(:prepend_features){called = true} + proc{c.prepend(self)}.should raise_error(TypeError) + called.should == false end end end diff --git a/spec/ruby/core/refinement/prepend_spec.rb b/spec/ruby/core/refinement/prepend_spec.rb index 27b70d392a..64cf7cd17f 100644 --- a/spec/ruby/core/refinement/prepend_spec.rb +++ b/spec/ruby/core/refinement/prepend_spec.rb @@ -1,26 +1,12 @@ require_relative '../../spec_helper' describe "Refinement#prepend" do - ruby_version_is "3.1"..."3.2" do - it "warns about deprecation" do - Module.new do - refine String do - -> { - prepend Module.new - }.should complain(/warning: Refinement#prepend is deprecated and will be removed in Ruby 3.2/) - end - end - end - end - - ruby_version_is "3.2" do - it "raises a TypeError" do - Module.new do - refine String do - -> { - prepend Module.new - }.should raise_error(TypeError, "Refinement#prepend has been removed") - end + it "raises a TypeError" do + Module.new do + refine String do + -> { + prepend Module.new + }.should raise_error(TypeError, "Refinement#prepend has been removed") end end end diff --git a/spec/ruby/core/refinement/refined_class_spec.rb b/spec/ruby/core/refinement/refined_class_spec.rb index 00b65d0895..60a58380cc 100644 --- a/spec/ruby/core/refinement/refined_class_spec.rb +++ b/spec/ruby/core/refinement/refined_class_spec.rb @@ -1,8 +1,29 @@ -require_relative '../../spec_helper' +require_relative "../../spec_helper" +require_relative 'shared/target' describe "Refinement#refined_class" do - ruby_version_is "3.2"..."3.3" do - it "returns the class refined by the receiver" do + ruby_version_is ""..."3.3" do + it_behaves_like :refinement_target, :refined_class + end + + ruby_version_is "3.3"..."3.4" do + it "has been deprecated in favour of Refinement#target" do + refinement_int = nil + + Module.new do + refine Integer do + refinement_int = self + end + end + + -> { + refinement_int.refined_class + }.should complain(/warning: Refinement#refined_class is deprecated and will be removed in Ruby 3.4; use Refinement#target instead/) + end + end + + ruby_version_is "3.4" do + it "has been removed" do refinement_int = nil Module.new do @@ -11,7 +32,7 @@ describe "Refinement#refined_class" do end end - refinement_int.refined_class.should == Integer + refinement_int.should_not.respond_to?(:refined_class) end end end diff --git a/spec/ruby/core/refinement/shared/target.rb b/spec/ruby/core/refinement/shared/target.rb new file mode 100644 index 0000000000..79557bea0b --- /dev/null +++ b/spec/ruby/core/refinement/shared/target.rb @@ -0,0 +1,13 @@ +describe :refinement_target, shared: true do + it "returns the class refined by the receiver" do + refinement_int = nil + + Module.new do + refine Integer do + refinement_int = self + end + end + + refinement_int.send(@method).should == Integer + end +end diff --git a/spec/ruby/core/refinement/target_spec.rb b/spec/ruby/core/refinement/target_spec.rb new file mode 100644 index 0000000000..fee9588a96 --- /dev/null +++ b/spec/ruby/core/refinement/target_spec.rb @@ -0,0 +1,8 @@ +require_relative "../../spec_helper" +require_relative 'shared/target' + +describe "Refinement#target" do + ruby_version_is "3.3" do + it_behaves_like :refinement_target, :target + end +end diff --git a/spec/ruby/core/regexp/compile_spec.rb b/spec/ruby/core/regexp/compile_spec.rb index c41399cfbb..887c8d77dc 100644 --- a/spec/ruby/core/regexp/compile_spec.rb +++ b/spec/ruby/core/regexp/compile_spec.rb @@ -14,6 +14,6 @@ describe "Regexp.compile given a Regexp" do it_behaves_like :regexp_new_regexp, :compile end -describe "Regexp.new given a non-String/Regexp" do +describe "Regexp.compile given a non-String/Regexp" do it_behaves_like :regexp_new_non_string_or_regexp, :compile end diff --git a/spec/ruby/core/regexp/linear_time_spec.rb b/spec/ruby/core/regexp/linear_time_spec.rb index 4dc436264f..cf9e73c37c 100644 --- a/spec/ruby/core/regexp/linear_time_spec.rb +++ b/spec/ruby/core/regexp/linear_time_spec.rb @@ -1,25 +1,33 @@ require_relative '../../spec_helper' -ruby_version_is "3.2" do - describe "Regexp.linear_time?" do - it "returns true if matching can be done in linear time" do - Regexp.linear_time?(/a/).should == true - Regexp.linear_time?('a').should == true - end +describe "Regexp.linear_time?" do + it "returns true if matching can be done in linear time" do + Regexp.linear_time?(/a/).should == true + Regexp.linear_time?('a').should == true + end - it "return false if matching can't be done in linear time" do - Regexp.linear_time?(/(a)\1/).should == false - Regexp.linear_time?("(a)\\1").should == false - end + it "returns true if matching can be done in linear time for a binary Regexp" do + Regexp.linear_time?(/[\x80-\xff]/n).should == true + end - it "accepts flags for string argument" do - Regexp.linear_time?('a', Regexp::IGNORECASE).should == true - end + it "return false if matching can't be done in linear time" do + Regexp.linear_time?(/(a)\1/).should == false + Regexp.linear_time?("(a)\\1").should == false + end + + it "accepts flags for string argument" do + Regexp.linear_time?('a', Regexp::IGNORECASE).should == true + end + + it "warns about flags being ignored for regexp arguments" do + -> { + Regexp.linear_time?(/a/, Regexp::IGNORECASE) + }.should complain(/warning: flags ignored/) + end - it "warns about flags being ignored for regexp arguments" do - -> { - Regexp.linear_time?(/a/, Regexp::IGNORECASE) - }.should complain(/warning: flags ignored/) + ruby_version_is "3.3" do + it "returns true for positive lookarounds" do + Regexp.linear_time?(/(?:(?=a*)a)*/).should == true end end end diff --git a/spec/ruby/core/regexp/new_spec.rb b/spec/ruby/core/regexp/new_spec.rb index 65f612df55..79210e9a23 100644 --- a/spec/ruby/core/regexp/new_spec.rb +++ b/spec/ruby/core/regexp/new_spec.rb @@ -7,11 +7,11 @@ end describe "Regexp.new given a String" do it_behaves_like :regexp_new_string, :new + it_behaves_like :regexp_new_string_binary, :new end describe "Regexp.new given a Regexp" do it_behaves_like :regexp_new_regexp, :new - it_behaves_like :regexp_new_string_binary, :new end describe "Regexp.new given a non-String/Regexp" do diff --git a/spec/ruby/core/regexp/shared/new.rb b/spec/ruby/core/regexp/shared/new.rb index 773882e495..12c3d7c9c2 100644 --- a/spec/ruby/core/regexp/shared/new.rb +++ b/spec/ruby/core/regexp/shared/new.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :regexp_new, shared: true do it "requires one argument and creates a new regular expression object" do @@ -56,7 +56,7 @@ describe :regexp_new_string, shared: true do end it "raises a RegexpError when passed an incorrect regexp" do - -> { Regexp.send(@method, "^[$", 0) }.should raise_error(RegexpError) + -> { Regexp.send(@method, "^[$", 0) }.should raise_error(RegexpError, Regexp.new(Regexp.escape("premature end of char-class: /^[$/"))) end it "does not set Regexp options if only given one argument" do @@ -128,324 +128,95 @@ describe :regexp_new_string, shared: true do obj = Object.new def obj.to_int() ScratchPad.record(:called) end - Regexp.send(@method, "Hi", obj) + -> { + Regexp.send(@method, "Hi", obj) + }.should complain(/expected true or false as ignorecase/, {verbose: true}) ScratchPad.recorded.should == nil end - ruby_version_is ""..."3.2" do - it "treats any non-Integer, non-nil, non-false second argument as IGNORECASE" do + it "warns any non-Integer, non-nil, non-false second argument" do + r = nil + -> { r = Regexp.send(@method, 'Hi', Object.new) - (r.options & Regexp::IGNORECASE).should_not == 0 - (r.options & Regexp::MULTILINE).should == 0 - not_supported_on :opal do - (r.options & Regexp::EXTENDED).should == 0 - end - end - end - - ruby_version_is "3.2" do - it "warns any non-Integer, non-nil, non-false second argument" do - r = nil - -> { - r = Regexp.send(@method, 'Hi', Object.new) - }.should complain(/expected true or false as ignorecase/, {verbose: true}) - (r.options & Regexp::IGNORECASE).should_not == 0 - (r.options & Regexp::MULTILINE).should == 0 - not_supported_on :opal do - (r.options & Regexp::EXTENDED).should == 0 - end - end - - it "accepts a String of supported flags as the second argument" do - r = Regexp.send(@method, 'Hi', 'i') - (r.options & Regexp::IGNORECASE).should_not == 0 - (r.options & Regexp::MULTILINE).should == 0 - not_supported_on :opal do - (r.options & Regexp::EXTENDED).should == 0 - end - - r = Regexp.send(@method, 'Hi', 'imx') - (r.options & Regexp::IGNORECASE).should_not == 0 - (r.options & Regexp::MULTILINE).should_not == 0 - not_supported_on :opal do - (r.options & Regexp::EXTENDED).should_not == 0 - end - - r = Regexp.send(@method, 'Hi', 'mimi') - (r.options & Regexp::IGNORECASE).should_not == 0 - (r.options & Regexp::MULTILINE).should_not == 0 - not_supported_on :opal do - (r.options & Regexp::EXTENDED).should == 0 - end - - r = Regexp.send(@method, 'Hi', '') - (r.options & Regexp::IGNORECASE).should == 0 - (r.options & Regexp::MULTILINE).should == 0 - not_supported_on :opal do - (r.options & Regexp::EXTENDED).should == 0 - end - end - - it "raises an Argument error if the second argument contains unsupported chars" do - -> { Regexp.send(@method, 'Hi', 'e') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 'n') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 's') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 'u') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 'j') }.should raise_error(ArgumentError) - -> { Regexp.send(@method, 'Hi', 'mjx') }.should raise_error(ArgumentError) + }.should complain(/expected true or false as ignorecase/, {verbose: true}) + (r.options & Regexp::IGNORECASE).should_not == 0 + (r.options & Regexp::MULTILINE).should == 0 + not_supported_on :opal do + (r.options & Regexp::EXTENDED).should == 0 end end - ruby_version_is ""..."3.2" do - it "ignores the third argument if it is 'e' or 'euc' (case-insensitive)" do - -> { - Regexp.send(@method, 'Hi', nil, 'e').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'euc').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'E').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'EUC').encoding.should == Encoding::US_ASCII - }.should complain(/encoding option is ignored/) + it "accepts a String of supported flags as the second argument" do + r = Regexp.send(@method, 'Hi', 'i') + (r.options & Regexp::IGNORECASE).should_not == 0 + (r.options & Regexp::MULTILINE).should == 0 + not_supported_on :opal do + (r.options & Regexp::EXTENDED).should == 0 end - it "ignores the third argument if it is 's' or 'sjis' (case-insensitive)" do - -> { - Regexp.send(@method, 'Hi', nil, 's').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'sjis').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'S').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'SJIS').encoding.should == Encoding::US_ASCII - }.should complain(/encoding option is ignored/) + r = Regexp.send(@method, 'Hi', 'imx') + (r.options & Regexp::IGNORECASE).should_not == 0 + (r.options & Regexp::MULTILINE).should_not == 0 + not_supported_on :opal do + (r.options & Regexp::EXTENDED).should_not == 0 end - it "ignores the third argument if it is 'u' or 'utf8' (case-insensitive)" do - -> { - Regexp.send(@method, 'Hi', nil, 'u').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'utf8').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'U').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'UTF8').encoding.should == Encoding::US_ASCII - }.should complain(/encoding option is ignored/) + r = Regexp.send(@method, 'Hi', 'mimi') + (r.options & Regexp::IGNORECASE).should_not == 0 + (r.options & Regexp::MULTILINE).should_not == 0 + not_supported_on :opal do + (r.options & Regexp::EXTENDED).should == 0 end - it "uses US_ASCII encoding if third argument is 'n' or 'none' (case insensitive) and only ascii characters" do - Regexp.send(@method, 'Hi', nil, 'n').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'none').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'N').encoding.should == Encoding::US_ASCII - Regexp.send(@method, 'Hi', nil, 'NONE').encoding.should == Encoding::US_ASCII + r = Regexp.send(@method, 'Hi', '') + (r.options & Regexp::IGNORECASE).should == 0 + (r.options & Regexp::MULTILINE).should == 0 + not_supported_on :opal do + (r.options & Regexp::EXTENDED).should == 0 end + end - it "uses ASCII_8BIT encoding if third argument is 'n' or 'none' (case insensitive) and non-ascii characters" do - a = "(?:[\x8E\xA1-\xFE])" - str = "\A(?:#{a}|x*)\z" - - Regexp.send(@method, str, nil, 'N').encoding.should == Encoding::BINARY - Regexp.send(@method, str, nil, 'n').encoding.should == Encoding::BINARY - Regexp.send(@method, str, nil, 'none').encoding.should == Encoding::BINARY - Regexp.send(@method, str, nil, 'NONE').encoding.should == Encoding::BINARY - end + it "raises an Argument error if the second argument contains unsupported chars" do + -> { Regexp.send(@method, 'Hi', 'e') }.should raise_error(ArgumentError, "unknown regexp option: e") + -> { Regexp.send(@method, 'Hi', 'n') }.should raise_error(ArgumentError, "unknown regexp option: n") + -> { Regexp.send(@method, 'Hi', 's') }.should raise_error(ArgumentError, "unknown regexp option: s") + -> { Regexp.send(@method, 'Hi', 'u') }.should raise_error(ArgumentError, "unknown regexp option: u") + -> { Regexp.send(@method, 'Hi', 'j') }.should raise_error(ArgumentError, "unknown regexp option: j") + -> { Regexp.send(@method, 'Hi', 'mjx') }.should raise_error(ArgumentError, /unknown regexp option: mjx\b/) end describe "with escaped characters" do it "raises a Regexp error if there is a trailing backslash" do - -> { Regexp.send(@method, "\\") }.should raise_error(RegexpError) + -> { Regexp.send(@method, "\\") }.should raise_error(RegexpError, Regexp.new(Regexp.escape("too short escape sequence: /\\/"))) end it "does not raise a Regexp error if there is an escaped trailing backslash" do -> { Regexp.send(@method, "\\\\") }.should_not raise_error(RegexpError) end - it "accepts a backspace followed by a character" do + it "accepts a backspace followed by a non-special character" do Regexp.send(@method, "\\N").should == /#{"\x5c"+"N"}/ end - it "accepts a one-digit octal value" do - Regexp.send(@method, "\0").should == /#{"\x00"}/ - end - - it "accepts a two-digit octal value" do - Regexp.send(@method, "\11").should == /#{"\x09"}/ - end - - it "accepts a one-digit hexadecimal value" do - Regexp.send(@method, "\x9n").should == /#{"\x09n"}/ - end - - it "accepts a two-digit hexadecimal value" do - Regexp.send(@method, "\x23").should == /#{"\x23"}/ - end - - it "interprets a digit following a two-digit hexadecimal value as a character" do - Regexp.send(@method, "\x420").should == /#{"\x420"}/ - end - it "raises a RegexpError if \\x is not followed by any hexadecimal digits" do - -> { Regexp.send(@method, "\\" + "xn") }.should raise_error(RegexpError) - end - - it "accepts an escaped string interpolation" do - Regexp.send(@method, "\#{abc}").should == /#{"\#{abc}"}/ - end - - it "accepts '\\n'" do - Regexp.send(@method, "\n").should == /#{"\x0a"}/ - end - - it "accepts '\\t'" do - Regexp.send(@method, "\t").should == /#{"\x09"}/ - end - - it "accepts '\\r'" do - Regexp.send(@method, "\r").should == /#{"\x0d"}/ - end - - it "accepts '\\f'" do - Regexp.send(@method, "\f").should == /#{"\x0c"}/ - end - - it "accepts '\\v'" do - Regexp.send(@method, "\v").should == /#{"\x0b"}/ - end - - it "accepts '\\a'" do - Regexp.send(@method, "\a").should == /#{"\x07"}/ - end - - it "accepts '\\e'" do - Regexp.send(@method, "\e").should == /#{"\x1b"}/ - end - - it "accepts '\\C-\\n'" do - Regexp.send(@method, "\C-\n").should == /#{"\x0a"}/ - end - - it "accepts '\\C-\\t'" do - Regexp.send(@method, "\C-\t").should == /#{"\x09"}/ - end - - it "accepts '\\C-\\r'" do - Regexp.send(@method, "\C-\r").should == /#{"\x0d"}/ - end - - it "accepts '\\C-\\f'" do - Regexp.send(@method, "\C-\f").should == /#{"\x0c"}/ - end - - it "accepts '\\C-\\v'" do - Regexp.send(@method, "\C-\v").should == /#{"\x0b"}/ - end - - it "accepts '\\C-\\a'" do - Regexp.send(@method, "\C-\a").should == /#{"\x07"}/ - end - - it "accepts '\\C-\\e'" do - Regexp.send(@method, "\C-\e").should == /#{"\x1b"}/ - end - - it "accepts multiple consecutive '\\' characters" do - Regexp.send(@method, "\\\\\\N").should == /#{"\\\\\\"+"N"}/ - end - - it "accepts characters and escaped octal digits" do - Regexp.send(@method, "abc\076").should == /#{"abc\x3e"}/ - end - - it "accepts escaped octal digits and characters" do - Regexp.send(@method, "\076abc").should == /#{"\x3eabc"}/ - end - - it "accepts characters and escaped hexadecimal digits" do - Regexp.send(@method, "abc\x42").should == /#{"abc\x42"}/ - end - - it "accepts escaped hexadecimal digits and characters" do - Regexp.send(@method, "\x3eabc").should == /#{"\x3eabc"}/ - end - - it "accepts escaped hexadecimal and octal digits" do - Regexp.send(@method, "\061\x42").should == /#{"\x31\x42"}/ - end - - it "accepts \\u{H} for a single Unicode codepoint" do - Regexp.send(@method, "\u{f}").should == /#{"\x0f"}/ - end - - it "accepts \\u{HH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{7f}").should == /#{"\x7f"}/ - end - - it "accepts \\u{HHH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{07f}").should == /#{"\x7f"}/ - end - - it "accepts \\u{HHHH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{0000}").should == /#{"\x00"}/ - end - - it "accepts \\u{HHHHH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{00001}").should == /#{"\x01"}/ - end - - it "accepts \\u{HHHHHH} for a single Unicode codepoint" do - Regexp.send(@method, "\u{000000}").should == /#{"\x00"}/ - end - - it "accepts characters followed by \\u{HHHH}" do - Regexp.send(@method, "abc\u{3042}").should == /#{"abc\u3042"}/ - end - - it "accepts \\u{HHHH} followed by characters" do - Regexp.send(@method, "\u{3042}abc").should == /#{"\u3042abc"}/ - end - - it "accepts escaped hexadecimal digits followed by \\u{HHHH}" do - Regexp.send(@method, "\x42\u{3042}").should == /#{"\x42\u3042"}/ - end - - it "accepts escaped octal digits followed by \\u{HHHH}" do - Regexp.send(@method, "\056\u{3042}").should == /#{"\x2e\u3042"}/ - end - - it "accepts a combination of escaped octal and hexadecimal digits and \\u{HHHH}" do - Regexp.send(@method, "\056\x42\u{3042}\x52\076").should == /#{"\x2e\x42\u3042\x52\x3e"}/ - end - - it "accepts \\uHHHH for a single Unicode codepoint" do - Regexp.send(@method, "\u3042").should == /#{"\u3042"}/ - end - - it "accepts characters followed by \\uHHHH" do - Regexp.send(@method, "abc\u3042").should == /#{"abc\u3042"}/ - end - - it "accepts \\uHHHH followed by characters" do - Regexp.send(@method, "\u3042abc").should == /#{"\u3042abc"}/ - end - - it "accepts escaped hexadecimal digits followed by \\uHHHH" do - Regexp.send(@method, "\x42\u3042").should == /#{"\x42\u3042"}/ - end - - it "accepts escaped octal digits followed by \\uHHHH" do - Regexp.send(@method, "\056\u3042").should == /#{"\x2e\u3042"}/ - end - - it "accepts a combination of escaped octal and hexadecimal digits and \\uHHHH" do - Regexp.send(@method, "\056\x42\u3042\x52\076").should == /#{"\x2e\x42\u3042\x52\x3e"}/ - end - - it "accepts a multiple byte character which need not be escaped" do - Regexp.send(@method, "\§").should == /#{"§"}/ + -> { Regexp.send(@method, "\\" + "xn") }.should raise_error(RegexpError, Regexp.new(Regexp.escape("invalid hex escape: /\\xn/"))) end it "raises a RegexpError if less than four digits are given for \\uHHHH" do - -> { Regexp.send(@method, "\\" + "u304") }.should raise_error(RegexpError) + -> { Regexp.send(@method, "\\" + "u304") }.should raise_error(RegexpError, Regexp.new(Regexp.escape("invalid Unicode escape: /\\u304/"))) end it "raises a RegexpError if the \\u{} escape is empty" do - -> { Regexp.send(@method, "\\" + "u{}") }.should raise_error(RegexpError) + -> { Regexp.send(@method, "\\" + "u{}") }.should raise_error(RegexpError, Regexp.new(Regexp.escape("invalid Unicode list: /\\u{}/"))) + end + + it "raises a RegexpError if the \\u{} escape contains non hexadecimal digits" do + -> { Regexp.send(@method, "\\" + "u{abcX}") }.should raise_error(RegexpError, Regexp.new(Regexp.escape("invalid Unicode list: /\\u{abcX}/"))) end it "raises a RegexpError if more than six hexadecimal digits are given" do - -> { Regexp.send(@method, "\\" + "u{0ffffff}") }.should raise_error(RegexpError) + -> { Regexp.send(@method, "\\" + "u{0ffffff}") }.should raise_error(RegexpError, Regexp.new(Regexp.escape("invalid Unicode range: /\\u{0ffffff}/"))) end it "returns a Regexp with US-ASCII encoding if only 7-bit ASCII characters are present regardless of the input String's encoding" do @@ -473,12 +244,12 @@ describe :regexp_new_string, shared: true do end it "returns a Regexp with the input String's encoding" do - str = "\x82\xa0".force_encoding(Encoding::Shift_JIS) + str = "\x82\xa0".dup.force_encoding(Encoding::Shift_JIS) Regexp.send(@method, str).encoding.should == Encoding::Shift_JIS end it "returns a Regexp with source String having the input String's encoding" do - str = "\x82\xa0".force_encoding(Encoding::Shift_JIS) + str = "\x82\xa0".dup.force_encoding(Encoding::Shift_JIS) Regexp.send(@method, str).source.encoding.should == Encoding::Shift_JIS end end @@ -486,69 +257,6 @@ end describe :regexp_new_string_binary, shared: true do describe "with escaped characters" do - it "accepts a three-digit octal value" do - Regexp.send(@method, "\315").should == /#{"\xcd"}/ - end - - it "interprets a digit following a three-digit octal value as a character" do - Regexp.send(@method, "\3762").should == /#{"\xfe2"}/ - end - - it "accepts '\\M-\\n'" do - Regexp.send(@method, "\M-\n").should == /#{"\x8a"}/ - end - - it "accepts '\\M-\\t'" do - Regexp.send(@method, "\M-\t").should == /#{"\x89"}/ - end - - it "accepts '\\M-\\r'" do - Regexp.send(@method, "\M-\r").should == /#{"\x8d"}/ - end - - it "accepts '\\M-\\f'" do - Regexp.send(@method, "\M-\f").should == /#{"\x8c"}/ - end - - it "accepts '\\M-\\v'" do - Regexp.send(@method, "\M-\v").should == /#{"\x8b"}/ - end - - it "accepts '\\M-\\a'" do - Regexp.send(@method, "\M-\a").should == /#{"\x87"}/ - end - - it "accepts '\\M-\\e'" do - Regexp.send(@method, "\M-\e").should == /#{"\x9b"}/ - end - - it "accepts '\\M-\\C-\\n'" do - Regexp.send(@method, "\M-\C-\n").should == /#{"\x8a"}/ - end - - it "accepts '\\M-\\C-\\t'" do - Regexp.send(@method, "\M-\C-\t").should == /#{"\x89"}/ - end - - it "accepts '\\M-\\C-\\r'" do - Regexp.send(@method, "\M-\C-\r").should == /#{"\x8d"}/ - end - - it "accepts '\\M-\\C-\\f'" do - Regexp.send(@method, "\M-\C-\f").should == /#{"\x8c"}/ - end - - it "accepts '\\M-\\C-\\v'" do - Regexp.send(@method, "\M-\C-\v").should == /#{"\x8b"}/ - end - - it "accepts '\\M-\\C-\\a'" do - Regexp.send(@method, "\M-\C-\a").should == /#{"\x87"}/ - end - - it "accepts '\\M-\\C-\\e'" do - Regexp.send(@method, "\M-\C-\e").should == /#{"\x9b"}/ - end end end @@ -603,11 +311,5 @@ describe :regexp_new_regexp, shared: true do it "sets the encoding to US-ASCII if the Regexp literal has the 'n' option and the source String is ASCII only" do Regexp.send(@method, /Hi/n).encoding.should == Encoding::US_ASCII end - - ruby_version_is ''...'3.2' do - it "sets the encoding to source String's encoding if the Regexp literal has the 'n' option and the source String is not ASCII only" do - Regexp.send(@method, Regexp.new("\\xff", nil, 'n')).encoding.should == Encoding::BINARY - end - end end end diff --git a/spec/ruby/core/regexp/shared/quote.rb b/spec/ruby/core/regexp/shared/quote.rb index 9533102766..3f46a18b5b 100644 --- a/spec/ruby/core/regexp/shared/quote.rb +++ b/spec/ruby/core/regexp/shared/quote.rb @@ -1,9 +1,9 @@ -# -*- encoding: binary -*- +# encoding: binary describe :regexp_quote, shared: true do it "escapes any characters with special meaning in a regular expression" do - Regexp.send(@method, '\*?{}.+^[]()- ').should == '\\\\\*\?\{\}\.\+\^\[\]\(\)\-\\ ' - Regexp.send(@method, "\*?{}.+^[]()- ").should == '\\*\\?\\{\\}\\.\\+\\^\\[\\]\\(\\)\\-\\ ' + Regexp.send(@method, '\*?{}.+^$[]()- ').should == '\\\\\*\?\{\}\.\+\^\$\[\]\(\)\-\\ ' + Regexp.send(@method, "\*?{}.+^$[]()- ").should == '\\*\\?\\{\\}\\.\\+\\^\\$\\[\\]\\(\\)\\-\\ ' Regexp.send(@method, '\n\r\f\t').should == '\\\\n\\\\r\\\\f\\\\t' Regexp.send(@method, "\n\r\f\t").should == '\\n\\r\\f\\t' end @@ -18,23 +18,23 @@ describe :regexp_quote, shared: true do end it "works for broken strings" do - Regexp.send(@method, "a.\x85b.".force_encoding("US-ASCII")).should =="a\\.\x85b\\.".force_encoding("US-ASCII") - Regexp.send(@method, "a.\x80".force_encoding("UTF-8")).should == "a\\.\x80".force_encoding("UTF-8") + Regexp.send(@method, "a.\x85b.".dup.force_encoding("US-ASCII")).should =="a\\.\x85b\\.".dup.force_encoding("US-ASCII") + Regexp.send(@method, "a.\x80".dup.force_encoding("UTF-8")).should == "a\\.\x80".dup.force_encoding("UTF-8") end it "sets the encoding of the result to US-ASCII if there are only US-ASCII characters present in the input String" do - str = "abc".force_encoding("euc-jp") + str = "abc".dup.force_encoding("euc-jp") Regexp.send(@method, str).encoding.should == Encoding::US_ASCII end it "sets the encoding of the result to the encoding of the String if any non-US-ASCII characters are present in an input String with valid encoding" do - str = "ã‚りãŒã¨ã†".force_encoding("utf-8") + str = "ã‚りãŒã¨ã†".dup.force_encoding("utf-8") str.valid_encoding?.should be_true Regexp.send(@method, str).encoding.should == Encoding::UTF_8 end it "sets the encoding of the result to BINARY if any non-US-ASCII characters are present in an input String with invalid encoding" do - str = "\xff".force_encoding "us-ascii" + str = "\xff".dup.force_encoding "us-ascii" str.valid_encoding?.should be_false Regexp.send(@method, "\xff").encoding.should == Encoding::BINARY end diff --git a/spec/ruby/core/regexp/timeout_spec.rb b/spec/ruby/core/regexp/timeout_spec.rb index 6fce261814..c64103c82c 100644 --- a/spec/ruby/core/regexp/timeout_spec.rb +++ b/spec/ruby/core/regexp/timeout_spec.rb @@ -1,35 +1,33 @@ require_relative '../../spec_helper' -ruby_version_is "3.2" do - describe "Regexp.timeout" do - after :each do - Regexp.timeout = nil - end +describe "Regexp.timeout" do + after :each do + Regexp.timeout = nil + end - it "returns global timeout" do - Regexp.timeout = 3 - Regexp.timeout.should == 3 - end + it "returns global timeout" do + Regexp.timeout = 3 + Regexp.timeout.should == 3 + end - it "raises Regexp::TimeoutError after global timeout elapsed" do - Regexp.timeout = 0.001 - Regexp.timeout.should == 0.001 + it "raises Regexp::TimeoutError after global timeout elapsed" do + Regexp.timeout = 0.001 + Regexp.timeout.should == 0.001 - -> { - # A typical ReDoS case - /^(a*)*$/ =~ "a" * 1000000 + "x" - }.should raise_error(Regexp::TimeoutError, "regexp match timeout") - end + -> { + # A typical ReDoS case + /^(a*)*$/ =~ "a" * 1000000 + "x" + }.should raise_error(Regexp::TimeoutError, "regexp match timeout") + end - it "raises Regexp::TimeoutError after timeout keyword value elapsed" do - Regexp.timeout = 3 # This should be ignored - Regexp.timeout.should == 3 + it "raises Regexp::TimeoutError after timeout keyword value elapsed" do + Regexp.timeout = 3 # This should be ignored + Regexp.timeout.should == 3 - re = Regexp.new("^a*b?a*$", timeout: 0.001) + re = Regexp.new("^a*b?a*$", timeout: 0.001) - -> { - re =~ "a" * 1000000 + "x" - }.should raise_error(Regexp::TimeoutError, "regexp match timeout") - end + -> { + re =~ "a" * 1000000 + "x" + }.should raise_error(Regexp::TimeoutError, "regexp match timeout") end end diff --git a/spec/ruby/core/regexp/union_spec.rb b/spec/ruby/core/regexp/union_spec.rb index 8076836471..ea5a5053f7 100644 --- a/spec/ruby/core/regexp/union_spec.rb +++ b/spec/ruby/core/regexp/union_spec.rb @@ -43,6 +43,27 @@ describe "Regexp.union" do Regexp.union("\u00A9".encode("ISO-8859-1"), "a".encode("UTF-8")).encoding.should == Encoding::ISO_8859_1 end + it "returns ASCII-8BIT if the regexp encodings are ASCII-8BIT and at least one has non-ASCII characters" do + us_ascii_implicit, us_ascii_explicit, binary = /abc/, /[\x00-\x7f]/n, /[\x80-\xBF]/n + us_ascii_implicit.encoding.should == Encoding::US_ASCII + us_ascii_explicit.encoding.should == Encoding::US_ASCII + binary.encoding.should == Encoding::BINARY + + Regexp.union(us_ascii_implicit, us_ascii_explicit, binary).encoding.should == Encoding::BINARY + Regexp.union(us_ascii_implicit, binary, us_ascii_explicit).encoding.should == Encoding::BINARY + Regexp.union(us_ascii_explicit, us_ascii_implicit, binary).encoding.should == Encoding::BINARY + Regexp.union(us_ascii_explicit, binary, us_ascii_implicit).encoding.should == Encoding::BINARY + Regexp.union(binary, us_ascii_implicit, us_ascii_explicit).encoding.should == Encoding::BINARY + Regexp.union(binary, us_ascii_explicit, us_ascii_implicit).encoding.should == Encoding::BINARY + end + + it "return US-ASCII if all patterns are ASCII-only" do + Regexp.union(/abc/e, /def/e).encoding.should == Encoding::US_ASCII + Regexp.union(/abc/n, /def/n).encoding.should == Encoding::US_ASCII + Regexp.union(/abc/s, /def/s).encoding.should == Encoding::US_ASCII + Regexp.union(/abc/u, /def/u).encoding.should == Encoding::US_ASCII + end + it "returns a Regexp with UTF-8 if one part is UTF-8" do Regexp.union(/probl[éeè]me/i, /help/i).encoding.should == Encoding::UTF_8 end @@ -54,83 +75,83 @@ describe "Regexp.union" do it "raises ArgumentError if the arguments include conflicting ASCII-incompatible Strings" do -> { Regexp.union("a".encode("UTF-16LE"), "b".encode("UTF-16BE")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and UTF-16BE') end it "raises ArgumentError if the arguments include conflicting ASCII-incompatible Regexps" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), Regexp.new("b".encode("UTF-16BE"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and UTF-16BE') end it "raises ArgumentError if the arguments include conflicting fixed encoding Regexps" do -> { Regexp.union(Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING), Regexp.new("b".encode("US-ASCII"), Regexp::FIXEDENCODING)) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-8 and US-ASCII') end it "raises ArgumentError if the arguments include a fixed encoding Regexp and a String containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union(Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING), "\u00A9".encode("ISO-8859-1")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-8 and ISO-8859-1') end it "raises ArgumentError if the arguments include a String containing non-ASCII-compatible characters and a fixed encoding Regexp in a different encoding" do -> { Regexp.union("\u00A9".encode("ISO-8859-1"), Regexp.new("a".encode("UTF-8"), Regexp::FIXEDENCODING)) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: ISO-8859-1 and UTF-8') end it "raises ArgumentError if the arguments include an ASCII-incompatible String and an ASCII-only String" do -> { Regexp.union("a".encode("UTF-16LE"), "b".encode("UTF-8")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, /ASCII incompatible encoding: UTF-16LE|incompatible encodings: UTF-16LE and US-ASCII/) end it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and an ASCII-only String" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), "b".encode("UTF-8")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, /ASCII incompatible encoding: UTF-16LE|incompatible encodings: UTF-16LE and US-ASCII/) end it "raises ArgumentError if the arguments include an ASCII-incompatible String and an ASCII-only Regexp" do -> { Regexp.union("a".encode("UTF-16LE"), Regexp.new("b".encode("UTF-8"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, /ASCII incompatible encoding: UTF-16LE|incompatible encodings: UTF-16LE and US-ASCII/) end it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and an ASCII-only Regexp" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), Regexp.new("b".encode("UTF-8"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, /ASCII incompatible encoding: UTF-16LE|incompatible encodings: UTF-16LE and US-ASCII/) end it "raises ArgumentError if the arguments include an ASCII-incompatible String and a String containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union("a".encode("UTF-16LE"), "\u00A9".encode("ISO-8859-1")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and ISO-8859-1') end it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and a String containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), "\u00A9".encode("ISO-8859-1")) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and ISO-8859-1') end it "raises ArgumentError if the arguments include an ASCII-incompatible String and a Regexp containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union("a".encode("UTF-16LE"), Regexp.new("\u00A9".encode("ISO-8859-1"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and ISO-8859-1') end it "raises ArgumentError if the arguments include an ASCII-incompatible Regexp and a Regexp containing non-ASCII-compatible characters in a different encoding" do -> { Regexp.union(Regexp.new("a".encode("UTF-16LE")), Regexp.new("\u00A9".encode("ISO-8859-1"))) - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, 'incompatible encodings: UTF-16LE and ISO-8859-1') end it "uses to_str to convert arguments (if not Regexp)" do @@ -154,6 +175,8 @@ describe "Regexp.union" do not_supported_on :opal do Regexp.union([/dogs/, /cats/i]).should == /(?-mix:dogs)|(?i-mx:cats)/ end - ->{Regexp.union(["skiing", "sledding"], [/dogs/, /cats/i])}.should raise_error(TypeError) + -> { + Regexp.union(["skiing", "sledding"], [/dogs/, /cats/i]) + }.should raise_error(TypeError, 'no implicit conversion of Array into String') end end diff --git a/spec/ruby/core/set/add_spec.rb b/spec/ruby/core/set/add_spec.rb new file mode 100644 index 0000000000..0fe1a0926c --- /dev/null +++ b/spec/ruby/core/set/add_spec.rb @@ -0,0 +1,34 @@ +require_relative '../../spec_helper' +require_relative 'shared/add' + +describe "Set#add" do + it_behaves_like :set_add, :add +end + +describe "Set#add?" do + before :each do + @set = Set.new + end + + it "adds the passed Object to self" do + @set.add?("cat") + @set.should include("cat") + end + + it "returns self when the Object has not yet been added to self" do + @set.add?("cat").should equal(@set) + end + + it "returns nil when the Object has already been added to self" do + @set.add?("cat") + @set.add?("cat").should be_nil + end + + it "raises RuntimeError when called during iteration" do + set = Set[:a, :b, :c, :d, :e, :f] + set.each do |_m| + -> { set << 1 }.should raise_error(RuntimeError, /iteration/) + end + set.should == Set[:a, :b, :c, :d, :e, :f] + end +end diff --git a/spec/ruby/core/set/append_spec.rb b/spec/ruby/core/set/append_spec.rb new file mode 100644 index 0000000000..82d34d9130 --- /dev/null +++ b/spec/ruby/core/set/append_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/add' + +describe "Set#<<" do + it_behaves_like :set_add, :<< +end diff --git a/spec/ruby/core/set/case_compare_spec.rb b/spec/ruby/core/set/case_compare_spec.rb new file mode 100644 index 0000000000..3781b1b963 --- /dev/null +++ b/spec/ruby/core/set/case_compare_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../spec_helper' +require_relative 'shared/include' + +describe "Set#===" do + it_behaves_like :set_include, :=== + + it "is an alias for include?" do + set = Set.new + set.method(:===).should == set.method(:include?) + end +end diff --git a/spec/ruby/core/set/case_equality_spec.rb b/spec/ruby/core/set/case_equality_spec.rb new file mode 100644 index 0000000000..19c1fb6b9c --- /dev/null +++ b/spec/ruby/core/set/case_equality_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/include' + +describe "Set#===" do + it_behaves_like :set_include, :=== +end diff --git a/spec/ruby/core/set/classify_spec.rb b/spec/ruby/core/set/classify_spec.rb new file mode 100644 index 0000000000..d86ea2722d --- /dev/null +++ b/spec/ruby/core/set/classify_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' + +describe "Set#classify" do + before :each do + @set = Set["one", "two", "three", "four"] + end + + it "yields each Object in self" do + res = [] + @set.classify { |x| res << x } + res.sort.should == ["one", "two", "three", "four"].sort + end + + it "returns an Enumerator when passed no block" do + enum = @set.classify + enum.should be_an_instance_of(Enumerator) + + classified = enum.each { |x| x.length } + classified.should == { 3 => Set["one", "two"], 4 => Set["four"], 5 => Set["three"] } + end + + it "classifies the Objects in self based on the block's return value" do + classified = @set.classify { |x| x.length } + classified.should == { 3 => Set["one", "two"], 4 => Set["four"], 5 => Set["three"] } + end +end diff --git a/spec/ruby/core/set/clear_spec.rb b/spec/ruby/core/set/clear_spec.rb new file mode 100644 index 0000000000..ebeac211d3 --- /dev/null +++ b/spec/ruby/core/set/clear_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' + +describe "Set#clear" do + before :each do + @set = Set["one", "two", "three", "four"] + end + + it "removes all elements from self" do + @set.clear + @set.should be_empty + end + + it "returns self" do + @set.clear.should equal(@set) + end +end diff --git a/spec/ruby/core/set/collect_spec.rb b/spec/ruby/core/set/collect_spec.rb new file mode 100644 index 0000000000..d186f1a0d9 --- /dev/null +++ b/spec/ruby/core/set/collect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/collect' + +describe "Set#collect!" do + it_behaves_like :set_collect_bang, :collect! +end diff --git a/spec/ruby/core/set/compare_by_identity_spec.rb b/spec/ruby/core/set/compare_by_identity_spec.rb new file mode 100644 index 0000000000..238dc117a6 --- /dev/null +++ b/spec/ruby/core/set/compare_by_identity_spec.rb @@ -0,0 +1,153 @@ +require_relative '../../spec_helper' + +describe "Set#compare_by_identity" do + it "compares its members by identity" do + a = "a" + b1 = "b" + b2 = b1.dup + + set = Set.new + set.compare_by_identity + set.merge([a, a, b1, b2]) + set.to_a.sort.should == [a, b1, b2].sort + end + + it "causes future comparisons on the receiver to be made by identity" do + elt = [1] + set = Set.new + set << elt + set.member?(elt.dup).should be_true + set.compare_by_identity + set.member?(elt.dup).should be_false + end + + it "rehashes internally so that old members can be looked up" do + set = Set.new + (1..10).each { |k| set << k } + o = Object.new + def o.hash; 123; end + set << o + set.compare_by_identity + set.member?(o).should be_true + end + + it "returns self" do + set = Set.new + result = set.compare_by_identity + result.should equal(set) + end + + it "is idempotent and has no effect on an already compare_by_identity set" do + set = Set.new.compare_by_identity + set << :foo + set.compare_by_identity.should equal(set) + set.should.compare_by_identity? + set.to_a.should == [:foo] + end + + it "uses the semantics of BasicObject#equal? to determine members identity" do + :a.equal?(:a).should == true + Set.new.compare_by_identity.merge([:a, :a]).to_a.should == [:a] + + ary1 = [1] + ary2 = [1] + ary1.equal?(ary2).should == false + Set.new.compare_by_identity.merge([ary1, ary2]).to_a.sort.should == [ary1, ary2].sort + end + + it "uses #equal? semantics, but doesn't actually call #equal? to determine identity" do + set = Set.new.compare_by_identity + obj = mock("equal") + obj.should_not_receive(:equal?) + set << :foo + set << obj + set.to_a.should == [:foo, obj] + end + + it "does not call #hash on members" do + elt = mock("element") + elt.should_not_receive(:hash) + set = Set.new.compare_by_identity + set << elt + set.member?(elt).should be_true + end + + it "regards #dup'd objects as having different identities" do + a1 = "a" + a2 = a1.dup + + set = Set.new.compare_by_identity + set.merge([a1, a2]) + set.to_a.sort.should == [a1, a2].sort + end + + it "regards #clone'd objects as having different identities" do + a1 = "a" + a2 = a1.clone + + set = Set.new.compare_by_identity + set.merge([a1, a2]) + set.to_a.sort.should == [a1, a2].sort + end + + ruby_version_is "4.0" do + it "raises a FrozenError on frozen sets" do + set = Set.new.freeze + -> { + set.compare_by_identity + }.should raise_error(FrozenError, /can't modify frozen Set: (#<)?Set(\[|: {)[\]}]>?/) + end + end + + ruby_version_is ""..."4.0" do + it "raises a FrozenError on frozen sets" do + set = Set.new.freeze + -> { + set.compare_by_identity + }.should raise_error(FrozenError, /frozen Hash/) + end + end + + it "persists over #dups" do + set = Set.new.compare_by_identity + set << :a + set_dup = set.dup + set_dup.should == set + set_dup << :a + set_dup.to_a.should == [:a] + end + + it "persists over #clones" do + set = Set.new.compare_by_identity + set << :a + set_clone = set.clone + set_clone.should == set + set_clone << :a + set_clone.to_a.should == [:a] + end + + it "is not equal to set what does not compare by identity" do + Set.new([1, 2]).should == Set.new([1, 2]) + Set.new([1, 2]).should_not == Set.new([1, 2]).compare_by_identity + end +end + +describe "Set#compare_by_identity?" do + it "returns false by default" do + Set.new.should_not.compare_by_identity? + end + + it "returns true once #compare_by_identity has been invoked on self" do + set = Set.new + set.compare_by_identity + set.should.compare_by_identity? + end + + it "returns true when called multiple times on the same set" do + set = Set.new + set.compare_by_identity + set.should.compare_by_identity? + set.should.compare_by_identity? + set.should.compare_by_identity? + end +end diff --git a/spec/ruby/core/set/comparison_spec.rb b/spec/ruby/core/set/comparison_spec.rb new file mode 100644 index 0000000000..62059b70b3 --- /dev/null +++ b/spec/ruby/core/set/comparison_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' + +describe "Set#<=>" do + it "returns 0 if the sets are equal" do + (Set[] <=> Set[]).should == 0 + (Set[:a, :b, :c] <=> Set[:a, :b, :c]).should == 0 + end + + it "returns -1 if the set is a proper subset of the other set" do + (Set[] <=> Set[1]).should == -1 + (Set[1, 2] <=> Set[1, 2, 3]).should == -1 + end + + it "returns +1 if the set is a proper superset of other set" do + (Set[1] <=> Set[]).should == +1 + (Set[1, 2, 3] <=> Set[1, 2]).should == +1 + end + + it "returns nil if the set has unique elements" do + (Set[1, 2, 3] <=> Set[:a, :b, :c]).should be_nil + end + + it "returns nil when the argument is not set-like" do + (Set[] <=> false).should be_nil + end +end diff --git a/spec/ruby/core/set/constructor_spec.rb b/spec/ruby/core/set/constructor_spec.rb new file mode 100644 index 0000000000..365081ad39 --- /dev/null +++ b/spec/ruby/core/set/constructor_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' + +describe "Set[]" do + it "returns a new Set populated with the passed Objects" do + set = Set[1, 2, 3] + + set.instance_of?(Set).should be_true + set.size.should eql(3) + + set.should include(1) + set.should include(2) + set.should include(3) + end +end diff --git a/spec/ruby/core/set/delete_if_spec.rb b/spec/ruby/core/set/delete_if_spec.rb new file mode 100644 index 0000000000..beda73a5e5 --- /dev/null +++ b/spec/ruby/core/set/delete_if_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' + +describe "Set#delete_if" do + before :each do + @set = Set["one", "two", "three"] + end + + it "yields every element of self" do + ret = [] + @set.delete_if { |x| ret << x } + ret.sort.should == ["one", "two", "three"].sort + end + + it "deletes every element from self for which the passed block returns true" do + @set.delete_if { |x| x.size == 3 } + @set.size.should eql(1) + + @set.should_not include("one") + @set.should_not include("two") + @set.should include("three") + end + + it "returns self" do + @set.delete_if { |x| x }.should equal(@set) + end + + it "returns an Enumerator when passed no block" do + enum = @set.delete_if + enum.should be_an_instance_of(Enumerator) + + enum.each { |x| x.size == 3 } + + @set.should_not include("one") + @set.should_not include("two") + @set.should include("three") + end +end diff --git a/spec/ruby/core/set/delete_spec.rb b/spec/ruby/core/set/delete_spec.rb new file mode 100644 index 0000000000..a2543ecbee --- /dev/null +++ b/spec/ruby/core/set/delete_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' + +describe "Set#delete" do + before :each do + @set = Set["a", "b", "c"] + end + + it "deletes the passed Object from self" do + @set.delete("a") + @set.should_not include("a") + end + + it "returns self" do + @set.delete("a").should equal(@set) + @set.delete("x").should equal(@set) + end +end + +describe "Set#delete?" do + before :each do + @set = Set["a", "b", "c"] + end + + it "deletes the passed Object from self" do + @set.delete?("a") + @set.should_not include("a") + end + + it "returns self when the passed Object is in self" do + @set.delete?("a").should equal(@set) + end + + it "returns nil when the passed Object is not in self" do + @set.delete?("x").should be_nil + end +end diff --git a/spec/ruby/core/set/difference_spec.rb b/spec/ruby/core/set/difference_spec.rb new file mode 100644 index 0000000000..149f946592 --- /dev/null +++ b/spec/ruby/core/set/difference_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/difference' + +describe "Set#difference" do + it_behaves_like :set_difference, :difference +end diff --git a/spec/ruby/core/set/disjoint_spec.rb b/spec/ruby/core/set/disjoint_spec.rb new file mode 100644 index 0000000000..89a3c4b157 --- /dev/null +++ b/spec/ruby/core/set/disjoint_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_like' + +describe "Set#disjoint?" do + it "returns false when two Sets have at least one element in common" do + Set[1, 2].disjoint?(Set[2, 3]).should == false + end + + it "returns true when two Sets have no element in common" do + Set[1, 2].disjoint?(Set[3, 4]).should == true + end + + context "when comparing to a Set-like object" do + it "returns false when a Set has at least one element in common with a Set-like object" do + Set[1, 2].disjoint?(SetSpecs::SetLike.new([2, 3])).should be_false + end + + it "returns true when a Set has no element in common with a Set-like object" do + Set[1, 2].disjoint?(SetSpecs::SetLike.new([3, 4])).should be_true + end + end +end diff --git a/spec/ruby/core/set/divide_spec.rb b/spec/ruby/core/set/divide_spec.rb new file mode 100644 index 0000000000..c6c6003e99 --- /dev/null +++ b/spec/ruby/core/set/divide_spec.rb @@ -0,0 +1,68 @@ +require_relative '../../spec_helper' + +describe "Set#divide" do + it "divides self into a set of subsets based on the blocks return values" do + set = Set["one", "two", "three", "four", "five"].divide { |x| x.length } + set.map { |x| x.to_a.sort }.sort.should == [["five", "four"], ["one", "two"], ["three"]] + end + + it "yields each Object to the block" do + ret = [] + Set["one", "two", "three", "four", "five"].divide { |x| ret << x } + ret.sort.should == ["five", "four", "one", "three", "two"] + end + + it "returns an enumerator when not passed a block" do + ret = Set[1, 2, 3, 4].divide + ret.should be_kind_of(Enumerator) + ret.each(&:even?).should == Set[Set[1, 3], Set[2, 4]] + end +end + +describe "Set#divide when passed a block with an arity of 2" do + it "divides self into a set of subsets based on the blocks return values" do + set = Set[1, 3, 4, 6, 9, 10, 11].divide { |x, y| (x - y).abs == 1 } + set.map{ |x| x.to_a.sort }.sort.should == [[1], [3, 4], [6], [9, 10, 11]] + end + + ruby_version_is "4.0" do + it "yields each two Object to the block" do + ret = [] + Set[1, 2].divide { |x, y| ret << [x, y] } + ret.sort.should == [[1, 2], [2, 1]] + end + end + + ruby_version_is ""..."4.0" do + it "yields each two Object to the block" do + ret = [] + Set[1, 2].divide { |x, y| ret << [x, y] } + ret.sort.should == [[1, 1], [1, 2], [2, 1], [2, 2]] + end + end + + it "returns an enumerator when not passed a block" do + ret = Set[1, 2, 3, 4].divide + ret.should be_kind_of(Enumerator) + ret.each { |a, b| (a + b).even? }.should == Set[Set[1, 3], Set[2, 4]] + end +end + +describe "Set#divide when passed a block with an arity of > 2" do + it "only uses the first element if the arity > 2" do + set = Set["one", "two", "three", "four", "five"].divide do |x, y, z| + y.should be_nil + z.should be_nil + x.length + end + set.map { |x| x.to_a.sort }.sort.should == [["five", "four"], ["one", "two"], ["three"]] + end + + it "only uses the first element if the arity = -1" do + set = Set["one", "two", "three", "four", "five"].divide do |*xs| + xs.size.should == 1 + xs.first.length + end + set.map { |x| x.to_a.sort }.sort.should == [["five", "four"], ["one", "two"], ["three"]] + end +end diff --git a/spec/ruby/core/set/each_spec.rb b/spec/ruby/core/set/each_spec.rb new file mode 100644 index 0000000000..3d9cdc2d46 --- /dev/null +++ b/spec/ruby/core/set/each_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../spec_helper' + +describe "Set#each" do + before :each do + @set = Set[1, 2, 3] + end + + it "yields each Object in self" do + ret = [] + @set.each { |x| ret << x } + ret.sort.should == [1, 2, 3] + end + + it "returns self" do + @set.each { |x| x }.should equal(@set) + end + + it "returns an Enumerator when not passed a block" do + enum = @set.each + enum.should be_an_instance_of(Enumerator) + + ret = [] + enum.each { |x| ret << x } + ret.sort.should == [1, 2, 3] + end +end diff --git a/spec/ruby/core/set/empty_spec.rb b/spec/ruby/core/set/empty_spec.rb new file mode 100644 index 0000000000..4b55658e20 --- /dev/null +++ b/spec/ruby/core/set/empty_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +describe "Set#empty?" do + it "returns true if self is empty" do + Set[].empty?.should be_true + Set[1].empty?.should be_false + Set[1,2,3].empty?.should be_false + end +end diff --git a/spec/ruby/core/set/enumerable/to_set_spec.rb b/spec/ruby/core/set/enumerable/to_set_spec.rb new file mode 100644 index 0000000000..f139e1c025 --- /dev/null +++ b/spec/ruby/core/set/enumerable/to_set_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../../spec_helper' + +describe "Enumerable#to_set" do + it "returns a new Set created from self" do + [1, 2, 3].to_set.should == Set[1, 2, 3] + {a: 1, b: 2}.to_set.should == Set[[:b, 2], [:a, 1]] + end + + it "passes down passed blocks" do + [1, 2, 3].to_set { |x| x * x }.should == Set[1, 4, 9] + end +end diff --git a/spec/ruby/core/set/eql_spec.rb b/spec/ruby/core/set/eql_spec.rb new file mode 100644 index 0000000000..4ad5c3aa5a --- /dev/null +++ b/spec/ruby/core/set/eql_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' + +describe "Set#eql?" do + it "returns true when the passed argument is a Set and contains the same elements" do + Set[].should eql(Set[]) + Set[1, 2, 3].should eql(Set[1, 2, 3]) + Set[1, 2, 3].should eql(Set[3, 2, 1]) + Set["a", :b, ?c].should eql(Set[?c, :b, "a"]) + + Set[1, 2, 3].should_not eql(Set[1.0, 2, 3]) + Set[1, 2, 3].should_not eql(Set[2, 3]) + Set[1, 2, 3].should_not eql(Set[]) + end +end diff --git a/spec/ruby/core/set/equal_value_spec.rb b/spec/ruby/core/set/equal_value_spec.rb new file mode 100644 index 0000000000..721a79a3f1 --- /dev/null +++ b/spec/ruby/core/set/equal_value_spec.rb @@ -0,0 +1,34 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_like' + +describe "Set#==" do + it "returns true when the passed Object is a Set and self and the Object contain the same elements" do + Set[].should == Set[] + Set[1, 2, 3].should == Set[1, 2, 3] + Set["1", "2", "3"].should == Set["1", "2", "3"] + + Set[1, 2, 3].should_not == Set[1.0, 2, 3] + Set[1, 2, 3].should_not == [1, 2, 3] + end + + it "does not depend on the order of the elements" do + Set[1, 2, 3].should == Set[3, 2, 1] + Set[:a, "b", ?c].should == Set[?c, "b", :a] + end + + it "does not depend on the order of nested Sets" do + Set[Set[1], Set[2], Set[3]].should == Set[Set[3], Set[2], Set[1]] + + set1 = Set[Set["a", "b"], Set["c", "d"], Set["e", "f"]] + set2 = Set[Set["c", "d"], Set["a", "b"], Set["e", "f"]] + set1.should == set2 + end + + ruby_version_is ""..."4.0" do + context "when comparing to a Set-like object" do + it "returns true when a Set and a Set-like object contain the same elements" do + Set[1, 2, 3].should == SetSpecs::SetLike.new([1, 2, 3]) + end + end + end +end diff --git a/spec/ruby/core/set/exclusion_spec.rb b/spec/ruby/core/set/exclusion_spec.rb new file mode 100644 index 0000000000..bbc29afa95 --- /dev/null +++ b/spec/ruby/core/set/exclusion_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' + +describe "Set#^" do + before :each do + @set = Set[1, 2, 3, 4] + end + + it "returns a new Set containing elements that are not in both self and the passed Enumerable" do + (@set ^ Set[3, 4, 5]).should == Set[1, 2, 5] + (@set ^ [3, 4, 5]).should == Set[1, 2, 5] + end + + it "raises an ArgumentError when passed a non-Enumerable" do + -> { @set ^ 3 }.should raise_error(ArgumentError) + -> { @set ^ Object.new }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/set/filter_spec.rb b/spec/ruby/core/set/filter_spec.rb new file mode 100644 index 0000000000..779254ad68 --- /dev/null +++ b/spec/ruby/core/set/filter_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/select' + +describe "Set#filter!" do + it_behaves_like :set_select_bang, :filter! +end diff --git a/spec/ruby/core/set/fixtures/set_like.rb b/spec/ruby/core/set/fixtures/set_like.rb new file mode 100644 index 0000000000..86dec2ed52 --- /dev/null +++ b/spec/ruby/core/set/fixtures/set_like.rb @@ -0,0 +1,30 @@ + +module SetSpecs + # This class is used to test the interaction of "Set-like" objects with real Sets + # + # These "Set-like" objects reply to is_a?(Set) with true and thus real Set objects are able to transparently + # interoperate with them in a duck-typing manner. + class SetLike + include Enumerable + + def is_a?(klass) + super || klass == ::Set + end + + def initialize(entries) + @entries = entries + end + + def each(&block) + @entries.each(&block) + end + + def inspect + "#<#{self.class}: {#{map(&:inspect).join(", ")}}>" + end + + def size + @entries.size + end + end +end diff --git a/spec/ruby/core/set/flatten_merge_spec.rb b/spec/ruby/core/set/flatten_merge_spec.rb new file mode 100644 index 0000000000..13cedeead9 --- /dev/null +++ b/spec/ruby/core/set/flatten_merge_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' + +describe "Set#flatten_merge" do + ruby_version_is ""..."4.0" do + it "is protected" do + Set.should have_protected_instance_method("flatten_merge") + end + + it "flattens the passed Set and merges it into self" do + set1 = Set[1, 2] + set2 = Set[3, 4, Set[5, 6]] + + set1.send(:flatten_merge, set2).should == Set[1, 2, 3, 4, 5, 6] + end + + it "raises an ArgumentError when trying to flatten a recursive Set" do + set1 = Set[1, 2, 3] + set2 = Set[5, 6, 7] + set2 << set2 + + -> { set1.send(:flatten_merge, set2) }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/core/set/flatten_spec.rb b/spec/ruby/core/set/flatten_spec.rb new file mode 100644 index 0000000000..f2cb3dfa52 --- /dev/null +++ b/spec/ruby/core/set/flatten_spec.rb @@ -0,0 +1,59 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_like' +set_version = defined?(Set::VERSION) ? Set::VERSION : '1.0.0' + +describe "Set#flatten" do + it "returns a copy of self with each included Set flattened" do + set = Set[1, 2, Set[3, 4, Set[5, 6, Set[7, 8]]], 9, 10] + flattened_set = set.flatten + + flattened_set.should_not equal(set) + flattened_set.should == Set[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + end + + it "raises an ArgumentError when self is recursive" do + (set = Set[]) << set + -> { set.flatten }.should raise_error(ArgumentError) + end + + ruby_version_is ""..."4.0" do + context "when Set contains a Set-like object" do + it "returns a copy of self with each included Set-like object flattened" do + Set[SetSpecs::SetLike.new([1])].flatten.should == Set[1] + end + end + end +end + +describe "Set#flatten!" do + it "flattens self" do + set = Set[1, 2, Set[3, 4, Set[5, 6, Set[7, 8]]], 9, 10] + set.flatten! + set.should == Set[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + end + + it "returns self when self was modified" do + set = Set[1, 2, Set[3, 4]] + set.flatten!.should equal(set) + end + + it "returns nil when self was not modified" do + set = Set[1, 2, 3, 4] + set.flatten!.should be_nil + end + + it "raises an ArgumentError when self is recursive" do + (set = Set[]) << set + -> { set.flatten! }.should raise_error(ArgumentError) + end + + version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do + ruby_version_is ""..."4.0" do + context "when Set contains a Set-like object" do + it "flattens self, including Set-like objects" do + Set[SetSpecs::SetLike.new([1])].flatten!.should == Set[1] + end + end + end + end +end diff --git a/spec/ruby/core/set/hash_spec.rb b/spec/ruby/core/set/hash_spec.rb new file mode 100644 index 0000000000..63a0aa66a5 --- /dev/null +++ b/spec/ruby/core/set/hash_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' + +describe "Set#hash" do + it "is static" do + Set[].hash.should == Set[].hash + Set[1, 2, 3].hash.should == Set[1, 2, 3].hash + Set[:a, "b", ?c].hash.should == Set[?c, "b", :a].hash + + Set[].hash.should_not == Set[1, 2, 3].hash + Set[1, 2, 3].hash.should_not == Set[:a, "b", ?c].hash + end + + ruby_version_is ""..."4.0" do + # see https://github.com/jruby/jruby/issues/8393 + it "is equal to nil.hash for an uninitialized Set" do + Set.allocate.hash.should == nil.hash + end + end +end diff --git a/spec/ruby/core/set/include_spec.rb b/spec/ruby/core/set/include_spec.rb new file mode 100644 index 0000000000..dd33bbc3bd --- /dev/null +++ b/spec/ruby/core/set/include_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/include' + +describe "Set#include?" do + it_behaves_like :set_include, :include? +end diff --git a/spec/ruby/core/set/initialize_clone_spec.rb b/spec/ruby/core/set/initialize_clone_spec.rb new file mode 100644 index 0000000000..13abb7ee4e --- /dev/null +++ b/spec/ruby/core/set/initialize_clone_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' + +describe "Set#initialize_clone" do + # See https://bugs.ruby-lang.org/issues/14266 + it "does not freeze the new Set when called from clone(freeze: false)" do + set1 = Set[1, 2] + set1.freeze + set2 = set1.clone(freeze: false) + set1.frozen?.should == true + set2.frozen?.should == false + set2.add 3 + set1.should == Set[1, 2] + set2.should == Set[1, 2, 3] + end +end diff --git a/spec/ruby/core/set/initialize_spec.rb b/spec/ruby/core/set/initialize_spec.rb new file mode 100644 index 0000000000..ad9e1bd8c9 --- /dev/null +++ b/spec/ruby/core/set/initialize_spec.rb @@ -0,0 +1,72 @@ +require_relative '../../spec_helper' + +describe "Set#initialize" do + it "is private" do + Set.should have_private_instance_method(:initialize) + end + + it "adds all elements of the passed Enumerable to self" do + s = Set.new([1, 2, 3]) + s.size.should eql(3) + s.should include(1) + s.should include(2) + s.should include(3) + end + + it "uses #each_entry on the provided Enumerable" do + enumerable = MockObject.new('mock-enumerable') + enumerable.should_receive(:each_entry).and_yield(1).and_yield(2).and_yield(3) + s = Set.new(enumerable) + s.size.should eql(3) + s.should include(1) + s.should include(2) + s.should include(3) + end + + it "uses #each on the provided Enumerable if it does not respond to #each_entry" do + enumerable = MockObject.new('mock-enumerable') + enumerable.should_receive(:each).and_yield(1).and_yield(2).and_yield(3) + s = Set.new(enumerable) + s.size.should eql(3) + s.should include(1) + s.should include(2) + s.should include(3) + end + + it "raises if the provided Enumerable does not respond to #each_entry or #each" do + enumerable = MockObject.new('mock-enumerable') + -> { Set.new(enumerable) }.should raise_error(ArgumentError, "value must be enumerable") + end + + it "should initialize with empty array and set" do + s = Set.new([]) + s.size.should eql(0) + + s = Set.new({}) + s.size.should eql(0) + end + + it "preprocesses all elements by a passed block before adding to self" do + s = Set.new([1, 2, 3]) { |x| x * x } + s.size.should eql(3) + s.should include(1) + s.should include(4) + s.should include(9) + end + + it "should initialize with empty array and block" do + s = Set.new([]) { |x| x * x } + s.size.should eql(0) + end + + it "should initialize with empty set and block" do + s = Set.new(Set.new) { |x| x * x } + s.size.should eql(0) + end + + it "should initialize with just block" do + s = Set.new { |x| x * x } + s.size.should eql(0) + s.should eql(Set.new) + end +end diff --git a/spec/ruby/core/set/inspect_spec.rb b/spec/ruby/core/set/inspect_spec.rb new file mode 100644 index 0000000000..0dcce83eb6 --- /dev/null +++ b/spec/ruby/core/set/inspect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/inspect' + +describe "Set#inspect" do + it_behaves_like :set_inspect, :inspect +end diff --git a/spec/ruby/core/set/intersect_spec.rb b/spec/ruby/core/set/intersect_spec.rb new file mode 100644 index 0000000000..0736dea5fd --- /dev/null +++ b/spec/ruby/core/set/intersect_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_like' + +describe "Set#intersect?" do + it "returns true when two Sets have at least one element in common" do + Set[1, 2].intersect?(Set[2, 3]).should == true + end + + it "returns false when two Sets have no element in common" do + Set[1, 2].intersect?(Set[3, 4]).should == false + end + + context "when comparing to a Set-like object" do + it "returns true when a Set has at least one element in common with a Set-like object" do + Set[1, 2].intersect?(SetSpecs::SetLike.new([2, 3])).should be_true + end + + it "returns false when a Set has no element in common with a Set-like object" do + Set[1, 2].intersect?(SetSpecs::SetLike.new([3, 4])).should be_false + end + end +end diff --git a/spec/ruby/core/set/intersection_spec.rb b/spec/ruby/core/set/intersection_spec.rb new file mode 100644 index 0000000000..136b886775 --- /dev/null +++ b/spec/ruby/core/set/intersection_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative 'shared/intersection' + +describe "Set#intersection" do + it_behaves_like :set_intersection, :intersection +end + +describe "Set#&" do + it_behaves_like :set_intersection, :& +end diff --git a/spec/ruby/core/set/join_spec.rb b/spec/ruby/core/set/join_spec.rb new file mode 100644 index 0000000000..1c1e8a8af8 --- /dev/null +++ b/spec/ruby/core/set/join_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' + +describe "Set#join" do + it "returns an empty string if the Set is empty" do + Set[].join.should == '' + end + + it "returns a new string formed by joining elements after conversion" do + set = Set[:a, :b, :c] + set.join.should == "abc" + end + + it "does not separate elements when the passed separator is nil" do + set = Set[:a, :b, :c] + set.join(nil).should == "abc" + end + + it "returns a string formed by concatenating each element separated by the separator" do + set = Set[:a, :b, :c] + set.join(' | ').should == "a | b | c" + end + + ruby_version_is ""..."4.0" do + it "calls #to_a to convert the Set in to an Array" do + set = Set[:a, :b, :c] + set.should_receive(:to_a).and_return([:a, :b, :c]) + set.join.should == "abc" + end + end +end diff --git a/spec/ruby/core/set/keep_if_spec.rb b/spec/ruby/core/set/keep_if_spec.rb new file mode 100644 index 0000000000..d6abdd6adc --- /dev/null +++ b/spec/ruby/core/set/keep_if_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' + +describe "Set#keep_if" do + before :each do + @set = Set["one", "two", "three"] + end + + it "yields every element of self" do + ret = [] + @set.keep_if { |x| ret << x } + ret.sort.should == ["one", "two", "three"].sort + end + + it "keeps every element from self for which the passed block returns true" do + @set.keep_if { |x| x.size != 3 } + @set.size.should eql(1) + + @set.should_not include("one") + @set.should_not include("two") + @set.should include("three") + end + + it "returns self" do + @set.keep_if {}.should equal(@set) + end + + it "returns an Enumerator when passed no block" do + enum = @set.keep_if + enum.should be_an_instance_of(Enumerator) + + enum.each { |x| x.size != 3 } + + @set.should_not include("one") + @set.should_not include("two") + @set.should include("three") + end +end diff --git a/spec/ruby/core/set/length_spec.rb b/spec/ruby/core/set/length_spec.rb new file mode 100644 index 0000000000..6bb697b4ca --- /dev/null +++ b/spec/ruby/core/set/length_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/length' + +describe "Set#length" do + it_behaves_like :set_length, :length +end diff --git a/spec/ruby/core/set/map_spec.rb b/spec/ruby/core/set/map_spec.rb new file mode 100644 index 0000000000..996191b0a8 --- /dev/null +++ b/spec/ruby/core/set/map_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/collect' + +describe "Set#map!" do + it_behaves_like :set_collect_bang, :map! +end diff --git a/spec/ruby/core/set/member_spec.rb b/spec/ruby/core/set/member_spec.rb new file mode 100644 index 0000000000..5c82e8f826 --- /dev/null +++ b/spec/ruby/core/set/member_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/include' + +describe "Set#member?" do + it_behaves_like :set_include, :member? +end diff --git a/spec/ruby/core/set/merge_spec.rb b/spec/ruby/core/set/merge_spec.rb new file mode 100644 index 0000000000..0c6ed27670 --- /dev/null +++ b/spec/ruby/core/set/merge_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' + +describe "Set#merge" do + it "adds the elements of the passed Enumerable to self" do + Set[:a, :b].merge(Set[:b, :c, :d]).should == Set[:a, :b, :c, :d] + Set[1, 2].merge([3, 4]).should == Set[1, 2, 3, 4] + end + + it "returns self" do + set = Set[1, 2] + set.merge([3, 4]).should equal(set) + end + + it "raises an ArgumentError when passed a non-Enumerable" do + -> { Set[1, 2].merge(1) }.should raise_error(ArgumentError) + -> { Set[1, 2].merge(Object.new) }.should raise_error(ArgumentError) + end + + it "raises RuntimeError when called during iteration" do + set = Set[:a, :b] + set.each do |_m| + -> { set.merge([1, 2]) }.should raise_error(RuntimeError, /iteration/) + end + end + + ruby_version_is ""..."3.3" do + it "accepts only a single argument" do + -> { Set[].merge([], []) }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 1)") + end + end + + ruby_version_is "3.3" do + it "accepts multiple arguments" do + Set[:a, :b].merge(Set[:b, :c], [:d]).should == Set[:a, :b, :c, :d] + end + end +end diff --git a/spec/ruby/core/set/minus_spec.rb b/spec/ruby/core/set/minus_spec.rb new file mode 100644 index 0000000000..72f98f985e --- /dev/null +++ b/spec/ruby/core/set/minus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/difference' + +describe "Set#-" do + it_behaves_like :set_difference, :- +end diff --git a/spec/ruby/core/set/plus_spec.rb b/spec/ruby/core/set/plus_spec.rb new file mode 100644 index 0000000000..7e44ff0b7e --- /dev/null +++ b/spec/ruby/core/set/plus_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/union' + +describe "Set#+" do + it_behaves_like :set_union, :+ +end diff --git a/spec/ruby/core/set/pretty_print_cycle_spec.rb b/spec/ruby/core/set/pretty_print_cycle_spec.rb new file mode 100644 index 0000000000..7e6017c112 --- /dev/null +++ b/spec/ruby/core/set/pretty_print_cycle_spec.rb @@ -0,0 +1,14 @@ +require_relative '../../spec_helper' + +describe "Set#pretty_print_cycle" do + it "passes the 'pretty print' representation of a self-referencing Set to the pretty print writer" do + pp = mock("PrettyPrint") + ruby_version_is(""..."4.0") do + pp.should_receive(:text).with("#<Set: {...}>") + end + ruby_version_is("4.0") do + pp.should_receive(:text).with("Set[...]") + end + Set[1, 2, 3].pretty_print_cycle(pp) + end +end diff --git a/spec/ruby/core/set/proper_subset_spec.rb b/spec/ruby/core/set/proper_subset_spec.rb new file mode 100644 index 0000000000..fb7848c001 --- /dev/null +++ b/spec/ruby/core/set/proper_subset_spec.rb @@ -0,0 +1,45 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_like' +set_version = defined?(Set::VERSION) ? Set::VERSION : '1.0.0' + +describe "Set#proper_subset?" do + before :each do + @set = Set[1, 2, 3, 4] + end + + it "returns true if passed a Set that self is a proper subset of" do + Set[].proper_subset?(@set).should be_true + Set[].proper_subset?(Set[1, 2, 3]).should be_true + Set[].proper_subset?(Set["a", :b, ?c]).should be_true + + Set[1, 2, 3].proper_subset?(@set).should be_true + Set[1, 3].proper_subset?(@set).should be_true + Set[1, 2].proper_subset?(@set).should be_true + Set[1].proper_subset?(@set).should be_true + + Set[5].proper_subset?(@set).should be_false + Set[1, 5].proper_subset?(@set).should be_false + Set[nil].proper_subset?(@set).should be_false + Set["test"].proper_subset?(@set).should be_false + + @set.proper_subset?(@set).should be_false + Set[].proper_subset?(Set[]).should be_false + end + + it "raises an ArgumentError when passed a non-Set" do + -> { Set[].proper_subset?([]) }.should raise_error(ArgumentError) + -> { Set[].proper_subset?(1) }.should raise_error(ArgumentError) + -> { Set[].proper_subset?("test") }.should raise_error(ArgumentError) + -> { Set[].proper_subset?(Object.new) }.should raise_error(ArgumentError) + end + + version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do + ruby_version_is ""..."4.0" do + context "when comparing to a Set-like object" do + it "returns true if passed a Set-like object that self is a proper subset of" do + Set[1, 2, 3].proper_subset?(SetSpecs::SetLike.new([1, 2, 3, 4])).should be_true + end + end + end + end +end diff --git a/spec/ruby/core/set/proper_superset_spec.rb b/spec/ruby/core/set/proper_superset_spec.rb new file mode 100644 index 0000000000..dc1e87e230 --- /dev/null +++ b/spec/ruby/core/set/proper_superset_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_like' + +describe "Set#proper_superset?" do + before :each do + @set = Set[1, 2, 3, 4] + end + + it "returns true if passed a Set that self is a proper superset of" do + @set.proper_superset?(Set[]).should be_true + Set[1, 2, 3].proper_superset?(Set[]).should be_true + Set["a", :b, ?c].proper_superset?(Set[]).should be_true + + @set.proper_superset?(Set[1, 2, 3]).should be_true + @set.proper_superset?(Set[1, 3]).should be_true + @set.proper_superset?(Set[1, 2]).should be_true + @set.proper_superset?(Set[1]).should be_true + + @set.proper_superset?(Set[5]).should be_false + @set.proper_superset?(Set[1, 5]).should be_false + @set.proper_superset?(Set[nil]).should be_false + @set.proper_superset?(Set["test"]).should be_false + + @set.proper_superset?(@set).should be_false + Set[].proper_superset?(Set[]).should be_false + end + + it "raises an ArgumentError when passed a non-Set" do + -> { Set[].proper_superset?([]) }.should raise_error(ArgumentError) + -> { Set[].proper_superset?(1) }.should raise_error(ArgumentError) + -> { Set[].proper_superset?("test") }.should raise_error(ArgumentError) + -> { Set[].proper_superset?(Object.new) }.should raise_error(ArgumentError) + end + + ruby_version_is ""..."4.0" do + context "when comparing to a Set-like object" do + it "returns true if passed a Set-like object that self is a proper superset of" do + Set[1, 2, 3, 4].proper_superset?(SetSpecs::SetLike.new([1, 2, 3])).should be_true + end + end + end +end diff --git a/spec/ruby/core/set/reject_spec.rb b/spec/ruby/core/set/reject_spec.rb new file mode 100644 index 0000000000..91d0293415 --- /dev/null +++ b/spec/ruby/core/set/reject_spec.rb @@ -0,0 +1,41 @@ +require_relative '../../spec_helper' + +describe "Set#reject!" do + before :each do + @set = Set["one", "two", "three"] + end + + it "yields every element of self" do + ret = [] + @set.reject! { |x| ret << x } + ret.sort.should == ["one", "two", "three"].sort + end + + it "deletes every element from self for which the passed block returns true" do + @set.reject! { |x| x.size == 3 } + @set.size.should eql(1) + + @set.should_not include("one") + @set.should_not include("two") + @set.should include("three") + end + + it "returns self when self was modified" do + @set.reject! { |x| true }.should equal(@set) + end + + it "returns nil when self was not modified" do + @set.reject! { |x| false }.should be_nil + end + + it "returns an Enumerator when passed no block" do + enum = @set.reject! + enum.should be_an_instance_of(Enumerator) + + enum.each { |x| x.size == 3 } + + @set.should_not include("one") + @set.should_not include("two") + @set.should include("three") + end +end diff --git a/spec/ruby/core/set/replace_spec.rb b/spec/ruby/core/set/replace_spec.rb new file mode 100644 index 0000000000..c66a2d0ec3 --- /dev/null +++ b/spec/ruby/core/set/replace_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' + +describe "Set#replace" do + before :each do + @set = Set[:a, :b, :c] + end + + it "replaces the contents with other and returns self" do + @set.replace(Set[1, 2, 3]).should == @set + @set.should == Set[1, 2, 3] + end + + it "raises RuntimeError when called during iteration" do + set = Set[:a, :b, :c, :d, :e, :f] + set.each do |_m| + -> { set.replace(Set[1, 2, 3]) }.should raise_error(RuntimeError, /iteration/) + end + set.should == Set[:a, :b, :c, :d, :e, :f] + end + + it "accepts any enumerable as other" do + @set.replace([1, 2, 3]).should == Set[1, 2, 3] + end +end diff --git a/spec/ruby/core/set/select_spec.rb b/spec/ruby/core/set/select_spec.rb new file mode 100644 index 0000000000..b458ffacaa --- /dev/null +++ b/spec/ruby/core/set/select_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/select' + +describe "Set#select!" do + it_behaves_like :set_select_bang, :select! +end diff --git a/spec/ruby/core/set/set_spec.rb b/spec/ruby/core/set/set_spec.rb new file mode 100644 index 0000000000..fd1d2072e3 --- /dev/null +++ b/spec/ruby/core/set/set_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' + +describe 'Set' do + it 'is available without explicit requiring' do + output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1') + puts Set.new([1, 2, 3]).to_a.inspect + RUBY + output.chomp.should == "[1, 2, 3]" + end +end diff --git a/spec/ruby/core/set/shared/add.rb b/spec/ruby/core/set/shared/add.rb new file mode 100644 index 0000000000..9e797f5df9 --- /dev/null +++ b/spec/ruby/core/set/shared/add.rb @@ -0,0 +1,14 @@ +describe :set_add, shared: true do + before :each do + @set = Set.new + end + + it "adds the passed Object to self" do + @set.send(@method, "dog") + @set.should include("dog") + end + + it "returns self" do + @set.send(@method, "dog").should equal(@set) + end +end diff --git a/spec/ruby/core/set/shared/collect.rb b/spec/ruby/core/set/shared/collect.rb new file mode 100644 index 0000000000..bc58c231be --- /dev/null +++ b/spec/ruby/core/set/shared/collect.rb @@ -0,0 +1,20 @@ +describe :set_collect_bang, shared: true do + before :each do + @set = Set[1, 2, 3, 4, 5] + end + + it "yields each Object in self" do + res = [] + @set.send(@method) { |x| res << x } + res.sort.should == [1, 2, 3, 4, 5].sort + end + + it "returns self" do + @set.send(@method) { |x| x }.should equal(@set) + end + + it "replaces self with the return values of the block" do + @set.send(@method) { |x| x * 2 } + @set.should == Set[2, 4, 6, 8, 10] + end +end diff --git a/spec/ruby/core/set/shared/difference.rb b/spec/ruby/core/set/shared/difference.rb new file mode 100644 index 0000000000..f88987ed2a --- /dev/null +++ b/spec/ruby/core/set/shared/difference.rb @@ -0,0 +1,15 @@ +describe :set_difference, shared: true do + before :each do + @set = Set[:a, :b, :c] + end + + it "returns a new Set containing self's elements excluding the elements in the passed Enumerable" do + @set.send(@method, Set[:a, :b]).should == Set[:c] + @set.send(@method, [:b, :c]).should == Set[:a] + end + + it "raises an ArgumentError when passed a non-Enumerable" do + -> { @set.send(@method, 1) }.should raise_error(ArgumentError) + -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/set/shared/include.rb b/spec/ruby/core/set/shared/include.rb new file mode 100644 index 0000000000..b4d95cde24 --- /dev/null +++ b/spec/ruby/core/set/shared/include.rb @@ -0,0 +1,29 @@ +describe :set_include, shared: true do + it "returns true when self contains the passed Object" do + set = Set[:a, :b, :c] + set.send(@method, :a).should be_true + set.send(@method, :e).should be_false + end + + describe "member equality" do + it "is checked using both #hash and #eql?" do + obj = Object.new + obj_another = Object.new + + def obj.hash; 42 end + def obj_another.hash; 42 end + def obj_another.eql?(o) hash == o.hash end + + set = Set["a", "b", "c", obj] + set.send(@method, obj_another).should == true + end + + it "is not checked using #==" do + obj = Object.new + set = Set["a", "b", "c"] + + obj.should_not_receive(:==) + set.send(@method, obj) + end + end +end diff --git a/spec/ruby/core/set/shared/inspect.rb b/spec/ruby/core/set/shared/inspect.rb new file mode 100644 index 0000000000..a90af66c98 --- /dev/null +++ b/spec/ruby/core/set/shared/inspect.rb @@ -0,0 +1,45 @@ +describe :set_inspect, shared: true do + it "returns a String representation of self" do + Set[].send(@method).should be_kind_of(String) + Set[nil, false, true].send(@method).should be_kind_of(String) + Set[1, 2, 3].send(@method).should be_kind_of(String) + Set["1", "2", "3"].send(@method).should be_kind_of(String) + Set[:a, "b", Set[?c]].send(@method).should be_kind_of(String) + end + + ruby_version_is "4.0" do + it "does include the elements of the set" do + Set["1"].send(@method).should == 'Set["1"]' + end + end + + ruby_version_is ""..."4.0" do + it "does include the elements of the set" do + Set["1"].send(@method).should == '#<Set: {"1"}>' + end + end + + it "puts spaces between the elements" do + Set["1", "2"].send(@method).should include('", "') + end + + ruby_version_is "4.0" do + it "correctly handles cyclic-references" do + set1 = Set[] + set2 = Set[set1] + set1 << set2 + set1.send(@method).should be_kind_of(String) + set1.send(@method).should include("Set[...]") + end + end + + ruby_version_is ""..."4.0" do + it "correctly handles cyclic-references" do + set1 = Set[] + set2 = Set[set1] + set1 << set2 + set1.send(@method).should be_kind_of(String) + set1.send(@method).should include("#<Set: {...}>") + end + end +end diff --git a/spec/ruby/core/set/shared/intersection.rb b/spec/ruby/core/set/shared/intersection.rb new file mode 100644 index 0000000000..5ae4199c94 --- /dev/null +++ b/spec/ruby/core/set/shared/intersection.rb @@ -0,0 +1,15 @@ +describe :set_intersection, shared: true do + before :each do + @set = Set[:a, :b, :c] + end + + it "returns a new Set containing only elements shared by self and the passed Enumerable" do + @set.send(@method, Set[:b, :c, :d, :e]).should == Set[:b, :c] + @set.send(@method, [:b, :c, :d]).should == Set[:b, :c] + end + + it "raises an ArgumentError when passed a non-Enumerable" do + -> { @set.send(@method, 1) }.should raise_error(ArgumentError) + -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/set/shared/length.rb b/spec/ruby/core/set/shared/length.rb new file mode 100644 index 0000000000..a8fcee9f39 --- /dev/null +++ b/spec/ruby/core/set/shared/length.rb @@ -0,0 +1,6 @@ +describe :set_length, shared: true do + it "returns the number of elements in the set" do + set = Set[:a, :b, :c] + set.send(@method).should == 3 + end +end diff --git a/spec/ruby/core/set/shared/select.rb b/spec/ruby/core/set/shared/select.rb new file mode 100644 index 0000000000..467b236ed3 --- /dev/null +++ b/spec/ruby/core/set/shared/select.rb @@ -0,0 +1,41 @@ +require_relative '../../../spec_helper' + +describe :set_select_bang, shared: true do + before :each do + @set = Set["one", "two", "three"] + end + + it "yields every element of self" do + ret = [] + @set.send(@method) { |x| ret << x } + ret.sort.should == ["one", "two", "three"].sort + end + + it "keeps every element from self for which the passed block returns true" do + @set.send(@method) { |x| x.size != 3 } + @set.size.should eql(1) + + @set.should_not include("one") + @set.should_not include("two") + @set.should include("three") + end + + it "returns self when self was modified" do + @set.send(@method) { false }.should equal(@set) + end + + it "returns nil when self was not modified" do + @set.send(@method) { true }.should be_nil + end + + it "returns an Enumerator when passed no block" do + enum = @set.send(@method) + enum.should be_an_instance_of(Enumerator) + + enum.each { |x| x.size != 3 } + + @set.should_not include("one") + @set.should_not include("two") + @set.should include("three") + end +end diff --git a/spec/ruby/core/set/shared/union.rb b/spec/ruby/core/set/shared/union.rb new file mode 100644 index 0000000000..314f0e852d --- /dev/null +++ b/spec/ruby/core/set/shared/union.rb @@ -0,0 +1,15 @@ +describe :set_union, shared: true do + before :each do + @set = Set[:a, :b, :c] + end + + it "returns a new Set containing all elements of self and the passed Enumerable" do + @set.send(@method, Set[:b, :d, :e]).should == Set[:a, :b, :c, :d, :e] + @set.send(@method, [:b, :e]).should == Set[:a, :b, :c, :e] + end + + it "raises an ArgumentError when passed a non-Enumerable" do + -> { @set.send(@method, 1) }.should raise_error(ArgumentError) + -> { @set.send(@method, Object.new) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/set/size_spec.rb b/spec/ruby/core/set/size_spec.rb new file mode 100644 index 0000000000..4ae22c5f0a --- /dev/null +++ b/spec/ruby/core/set/size_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/length' + +describe "Set#size" do + it_behaves_like :set_length, :size +end diff --git a/spec/ruby/core/set/sortedset/sortedset_spec.rb b/spec/ruby/core/set/sortedset/sortedset_spec.rb new file mode 100644 index 0000000000..f3c1ec058d --- /dev/null +++ b/spec/ruby/core/set/sortedset/sortedset_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../../spec_helper' + +describe "SortedSet" do + ruby_version_is ""..."4.0" do + it "raises error including message that it has been extracted from the set stdlib" do + -> { + SortedSet + }.should raise_error(RuntimeError) { |e| + e.message.should.include?("The `SortedSet` class has been extracted from the `set` library") + } + end + end +end diff --git a/spec/ruby/core/set/subset_spec.rb b/spec/ruby/core/set/subset_spec.rb new file mode 100644 index 0000000000..112bd9b38a --- /dev/null +++ b/spec/ruby/core/set/subset_spec.rb @@ -0,0 +1,45 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_like' +set_version = defined?(Set::VERSION) ? Set::VERSION : '1.0.0' + +describe "Set#subset?" do + before :each do + @set = Set[1, 2, 3, 4] + end + + it "returns true if passed a Set that is equal to self or self is a subset of" do + @set.subset?(@set).should be_true + Set[].subset?(Set[]).should be_true + + Set[].subset?(@set).should be_true + Set[].subset?(Set[1, 2, 3]).should be_true + Set[].subset?(Set["a", :b, ?c]).should be_true + + Set[1, 2, 3].subset?(@set).should be_true + Set[1, 3].subset?(@set).should be_true + Set[1, 2].subset?(@set).should be_true + Set[1].subset?(@set).should be_true + + Set[5].subset?(@set).should be_false + Set[1, 5].subset?(@set).should be_false + Set[nil].subset?(@set).should be_false + Set["test"].subset?(@set).should be_false + end + + it "raises an ArgumentError when passed a non-Set" do + -> { Set[].subset?([]) }.should raise_error(ArgumentError) + -> { Set[].subset?(1) }.should raise_error(ArgumentError) + -> { Set[].subset?("test") }.should raise_error(ArgumentError) + -> { Set[].subset?(Object.new) }.should raise_error(ArgumentError) + end + + version_is(set_version, ""..."1.1.0") do #ruby_version_is ""..."3.3" do + ruby_version_is ""..."4.0" do + context "when comparing to a Set-like object" do + it "returns true if passed a Set-like object that self is a subset of" do + Set[1, 2, 3].subset?(SetSpecs::SetLike.new([1, 2, 3, 4])).should be_true + end + end + end + end +end diff --git a/spec/ruby/core/set/subtract_spec.rb b/spec/ruby/core/set/subtract_spec.rb new file mode 100644 index 0000000000..ae4bc73d41 --- /dev/null +++ b/spec/ruby/core/set/subtract_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' + +describe "Set#subtract" do + before :each do + @set = Set[:a, :b, :c] + end + + it "deletes any elements contained in other and returns self" do + @set.subtract(Set[:b, :c]).should == @set + @set.should == Set[:a] + end + + it "accepts any enumerable as other" do + @set.subtract([:c]).should == Set[:a, :b] + end +end diff --git a/spec/ruby/core/set/superset_spec.rb b/spec/ruby/core/set/superset_spec.rb new file mode 100644 index 0000000000..9b3df2d047 --- /dev/null +++ b/spec/ruby/core/set/superset_spec.rb @@ -0,0 +1,42 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_like' + +describe "Set#superset?" do + before :each do + @set = Set[1, 2, 3, 4] + end + + it "returns true if passed a Set that equals self or self is a proper superset of" do + @set.superset?(@set).should be_true + Set[].superset?(Set[]).should be_true + + @set.superset?(Set[]).should be_true + Set[1, 2, 3].superset?(Set[]).should be_true + Set["a", :b, ?c].superset?(Set[]).should be_true + + @set.superset?(Set[1, 2, 3]).should be_true + @set.superset?(Set[1, 3]).should be_true + @set.superset?(Set[1, 2]).should be_true + @set.superset?(Set[1]).should be_true + + @set.superset?(Set[5]).should be_false + @set.superset?(Set[1, 5]).should be_false + @set.superset?(Set[nil]).should be_false + @set.superset?(Set["test"]).should be_false + end + + it "raises an ArgumentError when passed a non-Set" do + -> { Set[].superset?([]) }.should raise_error(ArgumentError) + -> { Set[].superset?(1) }.should raise_error(ArgumentError) + -> { Set[].superset?("test") }.should raise_error(ArgumentError) + -> { Set[].superset?(Object.new) }.should raise_error(ArgumentError) + end + + ruby_version_is ""..."4.0" do + context "when comparing to a Set-like object" do + it "returns true if passed a Set-like object that self is a superset of" do + Set[1, 2, 3, 4].superset?(SetSpecs::SetLike.new([1, 2, 3])).should be_true + end + end + end +end diff --git a/spec/ruby/core/set/to_a_spec.rb b/spec/ruby/core/set/to_a_spec.rb new file mode 100644 index 0000000000..1e9800167a --- /dev/null +++ b/spec/ruby/core/set/to_a_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' + +describe "Set#to_a" do + it "returns an array containing elements of self" do + Set[1, 2, 3].to_a.sort.should == [1, 2, 3] + end +end diff --git a/spec/ruby/core/set/to_s_spec.rb b/spec/ruby/core/set/to_s_spec.rb new file mode 100644 index 0000000000..55b8bfd9b2 --- /dev/null +++ b/spec/ruby/core/set/to_s_spec.rb @@ -0,0 +1,11 @@ +require_relative "../../spec_helper" +require_relative 'shared/inspect' + +describe "Set#to_s" do + it_behaves_like :set_inspect, :to_s + + it "is an alias of inspect" do + set = Set.new + set.method(:to_s).should == set.method(:inspect) + end +end diff --git a/spec/ruby/core/set/union_spec.rb b/spec/ruby/core/set/union_spec.rb new file mode 100644 index 0000000000..3e77022d4b --- /dev/null +++ b/spec/ruby/core/set/union_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative 'shared/union' + +describe "Set#union" do + it_behaves_like :set_union, :union +end + +describe "Set#|" do + it_behaves_like :set_union, :| +end diff --git a/spec/ruby/core/signal/signame_spec.rb b/spec/ruby/core/signal/signame_spec.rb index b66de9fc85..adfe895d97 100644 --- a/spec/ruby/core/signal/signame_spec.rb +++ b/spec/ruby/core/signal/signame_spec.rb @@ -9,10 +9,22 @@ describe "Signal.signame" do Signal.signame(-1).should == nil end + it "calls #to_int on an object to convert to an Integer" do + obj = mock('signal') + obj.should_receive(:to_int).and_return(0) + Signal.signame(obj).should == "EXIT" + end + it "raises a TypeError when the passed argument can't be coerced to Integer" do -> { Signal.signame("hello") }.should raise_error(TypeError) end + it "raises a TypeError when the passed argument responds to #to_int but does not return an Integer" do + obj = mock('signal') + obj.should_receive(:to_int).and_return('not an int') + -> { Signal.signame(obj) }.should raise_error(TypeError) + end + platform_is_not :windows do it "the original should take precedence over alias when looked up by number" do Signal.signame(Signal.list["ABRT"]).should == "ABRT" diff --git a/spec/ruby/core/signal/trap_spec.rb b/spec/ruby/core/signal/trap_spec.rb index 10e122e072..6d654a99be 100644 --- a/spec/ruby/core/signal/trap_spec.rb +++ b/spec/ruby/core/signal/trap_spec.rb @@ -221,10 +221,29 @@ describe "Signal.trap" do Signal.trap(:HUP, @saved_trap).should equal(@proc) end + it "calls #to_str on an object to convert to a String" do + obj = mock("signal") + obj.should_receive(:to_str).exactly(2).times.and_return("HUP") + Signal.trap obj, @proc + Signal.trap(obj, @saved_trap).should equal(@proc) + end + + it "accepts Integer values" do + hup = Signal.list["HUP"] + Signal.trap hup, @proc + Signal.trap(hup, @saved_trap).should equal(@proc) + end + + it "does not call #to_int on an object to convert to an Integer" do + obj = mock("signal") + obj.should_not_receive(:to_int) + -> { Signal.trap obj, @proc }.should raise_error(ArgumentError, /bad signal type/) + end + it "raises ArgumentError when passed unknown signal" do -> { Signal.trap(300) { } }.should raise_error(ArgumentError, "invalid signal number (300)") - -> { Signal.trap("USR10") { } }.should raise_error(ArgumentError, "unsupported signal `SIGUSR10'") - -> { Signal.trap("SIGUSR10") { } }.should raise_error(ArgumentError, "unsupported signal `SIGUSR10'") + -> { Signal.trap("USR10") { } }.should raise_error(ArgumentError, /\Aunsupported signal [`']SIGUSR10'\z/) + -> { Signal.trap("SIGUSR10") { } }.should raise_error(ArgumentError, /\Aunsupported signal [`']SIGUSR10'\z/) end it "raises ArgumentError when passed signal is not Integer, String or Symbol" do @@ -245,6 +264,14 @@ describe "Signal.trap" do end end + %w[SEGV BUS ILL FPE VTALRM].each do |signal| + it "raises ArgumentError for SIG#{signal} which is reserved by Ruby" do + -> { + Signal.trap(signal, -> {}) + }.should raise_error(ArgumentError, "can't trap reserved signal: SIG#{signal}") + end + end + it "allows to register a handler for all known signals, except reserved signals for which it raises ArgumentError" do out = ruby_exe(fixture(__FILE__, "trap_all.rb"), args: "2>&1") out.should == "OK\n" diff --git a/spec/ruby/core/sizedqueue/append_spec.rb b/spec/ruby/core/sizedqueue/append_spec.rb index 6fffe2f272..c52baa3802 100644 --- a/spec/ruby/core/sizedqueue/append_spec.rb +++ b/spec/ruby/core/sizedqueue/append_spec.rb @@ -12,7 +12,5 @@ describe "SizedQueue#<<" do end describe "SizedQueue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(1); q.send(:<<, 1, timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(1); q.send(:<<, 1, timeout: v) } end diff --git a/spec/ruby/core/sizedqueue/deq_spec.rb b/spec/ruby/core/sizedqueue/deq_spec.rb index 985d654bb3..2aeb52f8a6 100644 --- a/spec/ruby/core/sizedqueue/deq_spec.rb +++ b/spec/ruby/core/sizedqueue/deq_spec.rb @@ -7,7 +7,5 @@ describe "SizedQueue#deq" do end describe "SizedQueue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(10); q.push(1); q.deq(timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(10); q.push(1); q.deq(timeout: v) } end diff --git a/spec/ruby/core/sizedqueue/enq_spec.rb b/spec/ruby/core/sizedqueue/enq_spec.rb index 619373e46b..b955909475 100644 --- a/spec/ruby/core/sizedqueue/enq_spec.rb +++ b/spec/ruby/core/sizedqueue/enq_spec.rb @@ -12,7 +12,5 @@ describe "SizedQueue#enq" do end describe "SizedQueue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(1); q.enq(1, timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(1); q.enq(1, timeout: v) } end diff --git a/spec/ruby/core/sizedqueue/freeze_spec.rb b/spec/ruby/core/sizedqueue/freeze_spec.rb new file mode 100644 index 0000000000..98f01cae2f --- /dev/null +++ b/spec/ruby/core/sizedqueue/freeze_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative '../../shared/queue/freeze' + +describe "SizedQueue#freeze" do + it_behaves_like :queue_freeze, :freeze, -> { SizedQueue.new(1) } +end diff --git a/spec/ruby/core/sizedqueue/pop_spec.rb b/spec/ruby/core/sizedqueue/pop_spec.rb index 5e7cfea8fb..6338ddbaa0 100644 --- a/spec/ruby/core/sizedqueue/pop_spec.rb +++ b/spec/ruby/core/sizedqueue/pop_spec.rb @@ -7,7 +7,5 @@ describe "SizedQueue#pop" do end describe "SizedQueue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(10); q.push(1); q.pop(timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(10); q.push(1); q.pop(timeout: v) } end diff --git a/spec/ruby/core/sizedqueue/push_spec.rb b/spec/ruby/core/sizedqueue/push_spec.rb index ce61e89b53..9eaa6beca0 100644 --- a/spec/ruby/core/sizedqueue/push_spec.rb +++ b/spec/ruby/core/sizedqueue/push_spec.rb @@ -12,7 +12,5 @@ describe "SizedQueue#push" do end describe "SizedQueue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(1); q.push(1, timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(1); q.push(1, timeout: v) } end diff --git a/spec/ruby/core/sizedqueue/shift_spec.rb b/spec/ruby/core/sizedqueue/shift_spec.rb index 3220801f3a..52974c1d99 100644 --- a/spec/ruby/core/sizedqueue/shift_spec.rb +++ b/spec/ruby/core/sizedqueue/shift_spec.rb @@ -7,7 +7,5 @@ describe "SizedQueue#shift" do end describe "SizedQueue operations with timeout" do - ruby_version_is "3.2" do - it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(10); q.push(1); q.shift(timeout: v) } - end + it_behaves_like :rb_num2dbl_fails, nil, -> v { q = SizedQueue.new(10); q.push(1); q.shift(timeout: v) } end diff --git a/spec/ruby/core/string/append_as_bytes_spec.rb b/spec/ruby/core/string/append_as_bytes_spec.rb new file mode 100644 index 0000000000..b1703e5f89 --- /dev/null +++ b/spec/ruby/core/string/append_as_bytes_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../spec_helper' + +describe "String#append_bytes" do + ruby_version_is "3.4" do + it "doesn't allow to mutate frozen strings" do + str = "hello".freeze + -> { str.append_as_bytes("\xE2\x82") }.should raise_error(FrozenError) + end + + it "allows creating broken strings" do + str = +"hello" + str.append_as_bytes("\xE2\x82") + str.valid_encoding?.should == false + + str.append_as_bytes("\xAC") + str.valid_encoding?.should == true + + str = "abc".encode(Encoding::UTF_32LE) + str.append_as_bytes("def") + str.encoding.should == Encoding::UTF_32LE + str.valid_encoding?.should == false + end + + it "never changes the receiver encoding" do + str = "".b + str.append_as_bytes("€") + str.encoding.should == Encoding::BINARY + end + + it "accepts variadic String or Integer arguments" do + str = "hello".b + str.append_as_bytes("\xE2\x82", 12, 43, "\xAC") + str.encoding.should == Encoding::BINARY + str.should == "hello\xE2\x82\f+\xAC".b + end + + it "truncates integers to the least significant byte" do + str = +"" + str.append_as_bytes(0x131, 0x232, 0x333, bignum_value, bignum_value(1)) + str.bytes.should == [0x31, 0x32, 0x33, 0, 1] + end + + it "wraps negative integers" do + str = "".b + str.append_as_bytes(-1, -bignum_value, -bignum_value(1)) + str.bytes.should == [0xFF, 0, 0xFF] + end + + it "only accepts strings or integers, and doesn't attempt to cast with #to_str or #to_int" do + to_str = mock("to_str") + to_str.should_not_receive(:to_str) + to_str.should_not_receive(:to_int) + + str = +"hello" + -> { str.append_as_bytes(to_str) }.should raise_error(TypeError, "wrong argument type MockObject (expected String or Integer)") + end + end +end diff --git a/spec/ruby/core/string/ascii_only_spec.rb b/spec/ruby/core/string/ascii_only_spec.rb index c7e02fd874..88a0559cfd 100644 --- a/spec/ruby/core/string/ascii_only_spec.rb +++ b/spec/ruby/core/string/ascii_only_spec.rb @@ -7,12 +7,12 @@ describe "String#ascii_only?" do it "returns true if the encoding is UTF-8" do [ ["hello", true], ["hello".encode('UTF-8'), true], - ["hello".force_encoding('UTF-8'), true], + ["hello".dup.force_encoding('UTF-8'), true], ].should be_computed_by(:ascii_only?) end it "returns true if the encoding is US-ASCII" do - "hello".force_encoding(Encoding::US_ASCII).ascii_only?.should be_true + "hello".dup.force_encoding(Encoding::US_ASCII).ascii_only?.should be_true "hello".encode(Encoding::US_ASCII).ascii_only?.should be_true end @@ -34,13 +34,13 @@ describe "String#ascii_only?" do [ ["\u{6666}", false], ["hello, \u{6666}", false], ["\u{6666}".encode('UTF-8'), false], - ["\u{6666}".force_encoding('UTF-8'), false], + ["\u{6666}".dup.force_encoding('UTF-8'), false], ].should be_computed_by(:ascii_only?) end it "returns false if the encoding is US-ASCII" do - [ ["\u{6666}".force_encoding(Encoding::US_ASCII), false], - ["hello, \u{6666}".force_encoding(Encoding::US_ASCII), false], + [ ["\u{6666}".dup.force_encoding(Encoding::US_ASCII), false], + ["hello, \u{6666}".dup.force_encoding(Encoding::US_ASCII), false], ].should be_computed_by(:ascii_only?) end end @@ -51,17 +51,16 @@ describe "String#ascii_only?" do end it "returns false for the empty String with a non-ASCII-compatible encoding" do - "".force_encoding('UTF-16LE').ascii_only?.should be_false + "".dup.force_encoding('UTF-16LE').ascii_only?.should be_false "".encode('UTF-16BE').ascii_only?.should be_false end it "returns false for a non-empty String with non-ASCII-compatible encoding" do - "\x78\x00".force_encoding("UTF-16LE").ascii_only?.should be_false + "\x78\x00".dup.force_encoding("UTF-16LE").ascii_only?.should be_false end it "returns false when interpolating non ascii strings" do - base = "EU currency is" - base.force_encoding(Encoding::US_ASCII) + base = "EU currency is".dup.force_encoding(Encoding::US_ASCII) euro = "\u20AC" interp = "#{base} #{euro}" euro.ascii_only?.should be_false @@ -70,14 +69,14 @@ describe "String#ascii_only?" do end it "returns false after appending non ASCII characters to an empty String" do - ("" << "λ").ascii_only?.should be_false + ("".dup << "λ").ascii_only?.should be_false end it "returns false when concatenating an ASCII and non-ASCII String" do - "".concat("λ").ascii_only?.should be_false + "".dup.concat("λ").ascii_only?.should be_false end it "returns false when replacing an ASCII String with a non-ASCII String" do - "".replace("λ").ascii_only?.should be_false + "".dup.replace("λ").ascii_only?.should be_false end end diff --git a/spec/ruby/core/string/b_spec.rb b/spec/ruby/core/string/b_spec.rb index 37c7994700..4b1fafff11 100644 --- a/spec/ruby/core/string/b_spec.rb +++ b/spec/ruby/core/string/b_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#b" do diff --git a/spec/ruby/core/string/byteindex_spec.rb b/spec/ruby/core/string/byteindex_spec.rb index 7be0c7ec1e..d420f3f683 100644 --- a/spec/ruby/core/string/byteindex_spec.rb +++ b/spec/ruby/core/string/byteindex_spec.rb @@ -4,301 +4,295 @@ require_relative 'fixtures/classes' require_relative 'shared/byte_index_common.rb' describe "String#byteindex" do - ruby_version_is "3.2" do - it "calls #to_str to convert the first argument" do - char = mock("string index char") - char.should_receive(:to_str).and_return("b") - "abc".byteindex(char).should == 1 - end - - it "calls #to_int to convert the second argument" do - offset = mock("string index offset") - offset.should_receive(:to_int).and_return(1) - "abc".byteindex("c", offset).should == 2 - end + it "calls #to_str to convert the first argument" do + char = mock("string index char") + char.should_receive(:to_str).and_return("b") + "abc".byteindex(char).should == 1 + end - it "does not raise IndexError when byte offset is correct or on string boundary" do - "ã‚".byteindex("").should == 0 - "ã‚".byteindex("", 0).should == 0 - "ã‚".byteindex("", 3).should == 3 - end + it "calls #to_int to convert the second argument" do + offset = mock("string index offset") + offset.should_receive(:to_int).and_return(1) + "abc".byteindex("c", offset).should == 2 + end - it_behaves_like :byte_index_common, :byteindex + it "does not raise IndexError when byte offset is correct or on string boundary" do + "ã‚".byteindex("").should == 0 + "ã‚".byteindex("", 0).should == 0 + "ã‚".byteindex("", 3).should == 3 end + + it_behaves_like :byte_index_common, :byteindex end describe "String#byteindex with String" do - ruby_version_is "3.2" do - it "behaves the same as String#byteindex(char) for one-character strings" do - "blablabla hello cruel world...!".split("").uniq.each do |str| - chr = str[0] - str.byteindex(str).should == str.byteindex(chr) + it "behaves the same as String#byteindex(char) for one-character strings" do + "blablabla hello cruel world...!".split("").uniq.each do |str| + chr = str[0] + str.byteindex(str).should == str.byteindex(chr) - 0.upto(str.size + 1) do |start| - str.byteindex(str, start).should == str.byteindex(chr, start) - end + 0.upto(str.size + 1) do |start| + str.byteindex(str, start).should == str.byteindex(chr, start) + end - (-str.size - 1).upto(-1) do |start| - str.byteindex(str, start).should == str.byteindex(chr, start) - end + (-str.size - 1).upto(-1) do |start| + str.byteindex(str, start).should == str.byteindex(chr, start) end end + end - it "returns the byteindex of the first occurrence of the given substring" do - "blablabla".byteindex("").should == 0 - "blablabla".byteindex("b").should == 0 - "blablabla".byteindex("bla").should == 0 - "blablabla".byteindex("blabla").should == 0 - "blablabla".byteindex("blablabla").should == 0 - - "blablabla".byteindex("l").should == 1 - "blablabla".byteindex("la").should == 1 - "blablabla".byteindex("labla").should == 1 - "blablabla".byteindex("lablabla").should == 1 - - "blablabla".byteindex("a").should == 2 - "blablabla".byteindex("abla").should == 2 - "blablabla".byteindex("ablabla").should == 2 - end + it "returns the byteindex of the first occurrence of the given substring" do + "blablabla".byteindex("").should == 0 + "blablabla".byteindex("b").should == 0 + "blablabla".byteindex("bla").should == 0 + "blablabla".byteindex("blabla").should == 0 + "blablabla".byteindex("blablabla").should == 0 + + "blablabla".byteindex("l").should == 1 + "blablabla".byteindex("la").should == 1 + "blablabla".byteindex("labla").should == 1 + "blablabla".byteindex("lablabla").should == 1 + + "blablabla".byteindex("a").should == 2 + "blablabla".byteindex("abla").should == 2 + "blablabla".byteindex("ablabla").should == 2 + end - it "treats the offset as a byteindex" do - "aaaaa".byteindex("a", 0).should == 0 - "aaaaa".byteindex("a", 2).should == 2 - "aaaaa".byteindex("a", 4).should == 4 - end + it "treats the offset as a byteindex" do + "aaaaa".byteindex("a", 0).should == 0 + "aaaaa".byteindex("a", 2).should == 2 + "aaaaa".byteindex("a", 4).should == 4 + end - it "ignores string subclasses" do - "blablabla".byteindex(StringSpecs::MyString.new("bla")).should == 0 - StringSpecs::MyString.new("blablabla").byteindex("bla").should == 0 - StringSpecs::MyString.new("blablabla").byteindex(StringSpecs::MyString.new("bla")).should == 0 - end + it "ignores string subclasses" do + "blablabla".byteindex(StringSpecs::MyString.new("bla")).should == 0 + StringSpecs::MyString.new("blablabla").byteindex("bla").should == 0 + StringSpecs::MyString.new("blablabla").byteindex(StringSpecs::MyString.new("bla")).should == 0 + end - it "starts the search at the given offset" do - "blablabla".byteindex("bl", 0).should == 0 - "blablabla".byteindex("bl", 1).should == 3 - "blablabla".byteindex("bl", 2).should == 3 - "blablabla".byteindex("bl", 3).should == 3 - - "blablabla".byteindex("bla", 0).should == 0 - "blablabla".byteindex("bla", 1).should == 3 - "blablabla".byteindex("bla", 2).should == 3 - "blablabla".byteindex("bla", 3).should == 3 - - "blablabla".byteindex("blab", 0).should == 0 - "blablabla".byteindex("blab", 1).should == 3 - "blablabla".byteindex("blab", 2).should == 3 - "blablabla".byteindex("blab", 3).should == 3 - - "blablabla".byteindex("la", 1).should == 1 - "blablabla".byteindex("la", 2).should == 4 - "blablabla".byteindex("la", 3).should == 4 - "blablabla".byteindex("la", 4).should == 4 - - "blablabla".byteindex("lab", 1).should == 1 - "blablabla".byteindex("lab", 2).should == 4 - "blablabla".byteindex("lab", 3).should == 4 - "blablabla".byteindex("lab", 4).should == 4 - - "blablabla".byteindex("ab", 2).should == 2 - "blablabla".byteindex("ab", 3).should == 5 - "blablabla".byteindex("ab", 4).should == 5 - "blablabla".byteindex("ab", 5).should == 5 - - "blablabla".byteindex("", 0).should == 0 - "blablabla".byteindex("", 1).should == 1 - "blablabla".byteindex("", 2).should == 2 - "blablabla".byteindex("", 7).should == 7 - "blablabla".byteindex("", 8).should == 8 - "blablabla".byteindex("", 9).should == 9 - end + it "starts the search at the given offset" do + "blablabla".byteindex("bl", 0).should == 0 + "blablabla".byteindex("bl", 1).should == 3 + "blablabla".byteindex("bl", 2).should == 3 + "blablabla".byteindex("bl", 3).should == 3 + + "blablabla".byteindex("bla", 0).should == 0 + "blablabla".byteindex("bla", 1).should == 3 + "blablabla".byteindex("bla", 2).should == 3 + "blablabla".byteindex("bla", 3).should == 3 + + "blablabla".byteindex("blab", 0).should == 0 + "blablabla".byteindex("blab", 1).should == 3 + "blablabla".byteindex("blab", 2).should == 3 + "blablabla".byteindex("blab", 3).should == 3 + + "blablabla".byteindex("la", 1).should == 1 + "blablabla".byteindex("la", 2).should == 4 + "blablabla".byteindex("la", 3).should == 4 + "blablabla".byteindex("la", 4).should == 4 + + "blablabla".byteindex("lab", 1).should == 1 + "blablabla".byteindex("lab", 2).should == 4 + "blablabla".byteindex("lab", 3).should == 4 + "blablabla".byteindex("lab", 4).should == 4 + + "blablabla".byteindex("ab", 2).should == 2 + "blablabla".byteindex("ab", 3).should == 5 + "blablabla".byteindex("ab", 4).should == 5 + "blablabla".byteindex("ab", 5).should == 5 + + "blablabla".byteindex("", 0).should == 0 + "blablabla".byteindex("", 1).should == 1 + "blablabla".byteindex("", 2).should == 2 + "blablabla".byteindex("", 7).should == 7 + "blablabla".byteindex("", 8).should == 8 + "blablabla".byteindex("", 9).should == 9 + end - it "starts the search at offset + self.length if offset is negative" do - str = "blablabla" + it "starts the search at offset + self.length if offset is negative" do + str = "blablabla" - ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle| - (-str.length .. -1).each do |offset| - str.byteindex(needle, offset).should == - str.byteindex(needle, offset + str.length) - end + ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle| + (-str.length .. -1).each do |offset| + str.byteindex(needle, offset).should == + str.byteindex(needle, offset + str.length) end end + end - it "returns nil if the substring isn't found" do - "blablabla".byteindex("B").should == nil - "blablabla".byteindex("z").should == nil - "blablabla".byteindex("BLA").should == nil - "blablabla".byteindex("blablablabla").should == nil - "blablabla".byteindex("", 10).should == nil + it "returns nil if the substring isn't found" do + "blablabla".byteindex("B").should == nil + "blablabla".byteindex("z").should == nil + "blablabla".byteindex("BLA").should == nil + "blablabla".byteindex("blablablabla").should == nil + "blablabla".byteindex("", 10).should == nil - "hello".byteindex("he", 1).should == nil - "hello".byteindex("he", 2).should == nil - "I’ve got a multibyte character.\n".byteindex("\n\n").should == nil - end + "hello".byteindex("he", 1).should == nil + "hello".byteindex("he", 2).should == nil + "I’ve got a multibyte character.\n".byteindex("\n\n").should == nil + end - it "returns the character byteindex of a multibyte character" do - "ã‚りãŒã¨ã†".byteindex("ãŒ").should == 6 - end + it "returns the character byteindex of a multibyte character" do + "ã‚りãŒã¨ã†".byteindex("ãŒ").should == 6 + end - it "returns the character byteindex after offset" do - "ã‚れã‚れ".byteindex("ã‚", 3).should == 6 - "ã‚りãŒã¨ã†ã‚りãŒã¨ã†".byteindex("ãŒ", 9).should == 21 - end + it "returns the character byteindex after offset" do + "ã‚れã‚れ".byteindex("ã‚", 3).should == 6 + "ã‚りãŒã¨ã†ã‚りãŒã¨ã†".byteindex("ãŒ", 9).should == 21 + end - it "returns the character byteindex after a partial first match" do - "</</h".byteindex("</h").should == 2 - end + it "returns the character byteindex after a partial first match" do + "</</h".byteindex("</h").should == 2 + end - it "raises an Encoding::CompatibilityError if the encodings are incompatible" do - char = "れ".encode Encoding::EUC_JP - -> do - "ã‚れ".byteindex(char) - end.should raise_error(Encoding::CompatibilityError) - end + it "raises an Encoding::CompatibilityError if the encodings are incompatible" do + char = "れ".encode Encoding::EUC_JP + -> do + "ã‚れ".byteindex(char) + end.should raise_error(Encoding::CompatibilityError) + end - it "handles a substring in a superset encoding" do - 'abc'.force_encoding(Encoding::US_ASCII).byteindex('é').should == nil - end + it "handles a substring in a superset encoding" do + 'abc'.dup.force_encoding(Encoding::US_ASCII).byteindex('é').should == nil + end - it "handles a substring in a subset encoding" do - 'été'.byteindex('t'.force_encoding(Encoding::US_ASCII)).should == 2 - end + it "handles a substring in a subset encoding" do + 'été'.byteindex('t'.dup.force_encoding(Encoding::US_ASCII)).should == 2 end end describe "String#byteindex with Regexp" do - ruby_version_is "3.2" do - it "behaves the same as String#byteindex(string) for escaped string regexps" do - ["blablabla", "hello cruel world...!"].each do |str| - ["", "b", "bla", "lab", "o c", "d."].each do |needle| - regexp = Regexp.new(Regexp.escape(needle)) - str.byteindex(regexp).should == str.byteindex(needle) - - 0.upto(str.size + 1) do |start| - str.byteindex(regexp, start).should == str.byteindex(needle, start) - end - - (-str.size - 1).upto(-1) do |start| - str.byteindex(regexp, start).should == str.byteindex(needle, start) - end + it "behaves the same as String#byteindex(string) for escaped string regexps" do + ["blablabla", "hello cruel world...!"].each do |str| + ["", "b", "bla", "lab", "o c", "d."].each do |needle| + regexp = Regexp.new(Regexp.escape(needle)) + str.byteindex(regexp).should == str.byteindex(needle) + + 0.upto(str.size + 1) do |start| + str.byteindex(regexp, start).should == str.byteindex(needle, start) + end + + (-str.size - 1).upto(-1) do |start| + str.byteindex(regexp, start).should == str.byteindex(needle, start) end end end + end - it "returns the byteindex of the first match of regexp" do - "blablabla".byteindex(/bla/).should == 0 - "blablabla".byteindex(/BLA/i).should == 0 + it "returns the byteindex of the first match of regexp" do + "blablabla".byteindex(/bla/).should == 0 + "blablabla".byteindex(/BLA/i).should == 0 - "blablabla".byteindex(/.{0}/).should == 0 - "blablabla".byteindex(/.{6}/).should == 0 - "blablabla".byteindex(/.{9}/).should == 0 + "blablabla".byteindex(/.{0}/).should == 0 + "blablabla".byteindex(/.{6}/).should == 0 + "blablabla".byteindex(/.{9}/).should == 0 - "blablabla".byteindex(/.*/).should == 0 - "blablabla".byteindex(/.+/).should == 0 + "blablabla".byteindex(/.*/).should == 0 + "blablabla".byteindex(/.+/).should == 0 - "blablabla".byteindex(/lab|b/).should == 0 + "blablabla".byteindex(/lab|b/).should == 0 - not_supported_on :opal do - "blablabla".byteindex(/\A/).should == 0 - "blablabla".byteindex(/\Z/).should == 9 - "blablabla".byteindex(/\z/).should == 9 - "blablabla\n".byteindex(/\Z/).should == 9 - "blablabla\n".byteindex(/\z/).should == 10 - end + not_supported_on :opal do + "blablabla".byteindex(/\A/).should == 0 + "blablabla".byteindex(/\Z/).should == 9 + "blablabla".byteindex(/\z/).should == 9 + "blablabla\n".byteindex(/\Z/).should == 9 + "blablabla\n".byteindex(/\z/).should == 10 + end - "blablabla".byteindex(/^/).should == 0 - "\nblablabla".byteindex(/^/).should == 0 - "b\nablabla".byteindex(/$/).should == 1 - "bl\nablabla".byteindex(/$/).should == 2 + "blablabla".byteindex(/^/).should == 0 + "\nblablabla".byteindex(/^/).should == 0 + "b\nablabla".byteindex(/$/).should == 1 + "bl\nablabla".byteindex(/$/).should == 2 - "blablabla".byteindex(/.l./).should == 0 - end + "blablabla".byteindex(/.l./).should == 0 + end - it "starts the search at the given offset" do - "blablabla".byteindex(/.{0}/, 5).should == 5 - "blablabla".byteindex(/.{1}/, 5).should == 5 - "blablabla".byteindex(/.{2}/, 5).should == 5 - "blablabla".byteindex(/.{3}/, 5).should == 5 - "blablabla".byteindex(/.{4}/, 5).should == 5 - - "blablabla".byteindex(/.{0}/, 3).should == 3 - "blablabla".byteindex(/.{1}/, 3).should == 3 - "blablabla".byteindex(/.{2}/, 3).should == 3 - "blablabla".byteindex(/.{5}/, 3).should == 3 - "blablabla".byteindex(/.{6}/, 3).should == 3 - - "blablabla".byteindex(/.l./, 0).should == 0 - "blablabla".byteindex(/.l./, 1).should == 3 - "blablabla".byteindex(/.l./, 2).should == 3 - "blablabla".byteindex(/.l./, 3).should == 3 - - "xblaxbla".byteindex(/x./, 0).should == 0 - "xblaxbla".byteindex(/x./, 1).should == 4 - "xblaxbla".byteindex(/x./, 2).should == 4 - - not_supported_on :opal do - "blablabla\n".byteindex(/\Z/, 9).should == 9 - end + it "starts the search at the given offset" do + "blablabla".byteindex(/.{0}/, 5).should == 5 + "blablabla".byteindex(/.{1}/, 5).should == 5 + "blablabla".byteindex(/.{2}/, 5).should == 5 + "blablabla".byteindex(/.{3}/, 5).should == 5 + "blablabla".byteindex(/.{4}/, 5).should == 5 + + "blablabla".byteindex(/.{0}/, 3).should == 3 + "blablabla".byteindex(/.{1}/, 3).should == 3 + "blablabla".byteindex(/.{2}/, 3).should == 3 + "blablabla".byteindex(/.{5}/, 3).should == 3 + "blablabla".byteindex(/.{6}/, 3).should == 3 + + "blablabla".byteindex(/.l./, 0).should == 0 + "blablabla".byteindex(/.l./, 1).should == 3 + "blablabla".byteindex(/.l./, 2).should == 3 + "blablabla".byteindex(/.l./, 3).should == 3 + + "xblaxbla".byteindex(/x./, 0).should == 0 + "xblaxbla".byteindex(/x./, 1).should == 4 + "xblaxbla".byteindex(/x./, 2).should == 4 + + not_supported_on :opal do + "blablabla\n".byteindex(/\Z/, 9).should == 9 end + end - it "starts the search at offset + self.length if offset is negative" do - str = "blablabla" + it "starts the search at offset + self.length if offset is negative" do + str = "blablabla" - ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle| - (-str.length .. -1).each do |offset| - str.byteindex(needle, offset).should == - str.byteindex(needle, offset + str.length) - end + ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle| + (-str.length .. -1).each do |offset| + str.byteindex(needle, offset).should == + str.byteindex(needle, offset + str.length) end end + end - it "returns nil if the substring isn't found" do - "blablabla".byteindex(/BLA/).should == nil + it "returns nil if the substring isn't found" do + "blablabla".byteindex(/BLA/).should == nil - "blablabla".byteindex(/.{10}/).should == nil - "blaxbla".byteindex(/.x/, 3).should == nil - "blaxbla".byteindex(/..x/, 2).should == nil - end + "blablabla".byteindex(/.{10}/).should == nil + "blaxbla".byteindex(/.x/, 3).should == nil + "blaxbla".byteindex(/..x/, 2).should == nil + end - it "returns nil if the Regexp matches the empty string and the offset is out of range" do - "ruby".byteindex(//, 12).should be_nil - end + it "returns nil if the Regexp matches the empty string and the offset is out of range" do + "ruby".byteindex(//, 12).should be_nil + end - it "supports \\G which matches at the given start offset" do - "helloYOU.".byteindex(/\GYOU/, 5).should == 5 - "helloYOU.".byteindex(/\GYOU/).should == nil + it "supports \\G which matches at the given start offset" do + "helloYOU.".byteindex(/\GYOU/, 5).should == 5 + "helloYOU.".byteindex(/\GYOU/).should == nil - re = /\G.+YOU/ - # The # marks where \G will match. - [ - ["#hi!YOUall.", 0], - ["h#i!YOUall.", 1], - ["hi#!YOUall.", 2], - ["hi!#YOUall.", nil] - ].each do |spec| + re = /\G.+YOU/ + # The # marks where \G will match. + [ + ["#hi!YOUall.", 0], + ["h#i!YOUall.", 1], + ["hi#!YOUall.", 2], + ["hi!#YOUall.", nil] + ].each do |spec| - start = spec[0].byteindex("#") - str = spec[0].delete("#") + start = spec[0].byteindex("#") + str = spec[0].delete("#") - str.byteindex(re, start).should == spec[1] - end + str.byteindex(re, start).should == spec[1] end + end - it "converts start_offset to an integer via to_int" do - obj = mock('1') - obj.should_receive(:to_int).and_return(1) - "RWOARW".byteindex(/R./, obj).should == 4 - end + it "converts start_offset to an integer via to_int" do + obj = mock('1') + obj.should_receive(:to_int).and_return(1) + "RWOARW".byteindex(/R./, obj).should == 4 + end - it "returns the character byteindex of a multibyte character" do - "ã‚りãŒã¨ã†".byteindex(/ãŒ/).should == 6 - end + it "returns the character byteindex of a multibyte character" do + "ã‚りãŒã¨ã†".byteindex(/ãŒ/).should == 6 + end - it "returns the character byteindex after offset" do - "ã‚れã‚れ".byteindex(/ã‚/, 3).should == 6 - end + it "returns the character byteindex after offset" do + "ã‚れã‚れ".byteindex(/ã‚/, 3).should == 6 + end - it "treats the offset as a byteindex" do - "ã‚れã‚ã‚れ".byteindex(/ã‚/, 6).should == 6 - end + it "treats the offset as a byteindex" do + "ã‚れã‚ã‚れ".byteindex(/ã‚/, 6).should == 6 end end diff --git a/spec/ruby/core/string/byterindex_spec.rb b/spec/ruby/core/string/byterindex_spec.rb index 717708c97d..983222e35d 100644 --- a/spec/ruby/core/string/byterindex_spec.rb +++ b/spec/ruby/core/string/byterindex_spec.rb @@ -4,356 +4,350 @@ require_relative 'fixtures/classes' require_relative 'shared/byte_index_common.rb' describe "String#byterindex with object" do - ruby_version_is "3.2" do - it "tries to convert obj to a string via to_str" do - obj = mock('lo') - def obj.to_str() "lo" end - "hello".byterindex(obj).should == "hello".byterindex("lo") - - obj = mock('o') - def obj.respond_to?(arg, *) true end - def obj.method_missing(*args) "o" end - "hello".byterindex(obj).should == "hello".byterindex("o") - end - - it "calls #to_int to convert the second argument" do - offset = mock("string index offset") - offset.should_receive(:to_int).and_return(3) - "abc".byterindex("c", offset).should == 2 - end + it "tries to convert obj to a string via to_str" do + obj = mock('lo') + def obj.to_str() "lo" end + "hello".byterindex(obj).should == "hello".byterindex("lo") + + obj = mock('o') + def obj.respond_to?(arg, *) true end + def obj.method_missing(*args) "o" end + "hello".byterindex(obj).should == "hello".byterindex("o") + end - it "does not raise IndexError when byte offset is correct or on string boundary" do - "ã‚".byterindex("", 0).should == 0 - "ã‚".byterindex("", 3).should == 3 - "ã‚".byterindex("").should == 3 - end + it "calls #to_int to convert the second argument" do + offset = mock("string index offset") + offset.should_receive(:to_int).and_return(3) + "abc".byterindex("c", offset).should == 2 + end - it_behaves_like :byte_index_common, :byterindex + it "does not raise IndexError when byte offset is correct or on string boundary" do + "ã‚".byterindex("", 0).should == 0 + "ã‚".byterindex("", 3).should == 3 + "ã‚".byterindex("").should == 3 end + + it_behaves_like :byte_index_common, :byterindex end describe "String#byterindex with String" do - ruby_version_is "3.2" do - it "behaves the same as String#byterindex(char) for one-character strings" do - "blablabla hello cruel world...!".split("").uniq.each do |str| - chr = str[0] - str.byterindex(str).should == str.byterindex(chr) + it "behaves the same as String#byterindex(char) for one-character strings" do + "blablabla hello cruel world...!".split("").uniq.each do |str| + chr = str[0] + str.byterindex(str).should == str.byterindex(chr) - 0.upto(str.size + 1) do |start| - str.byterindex(str, start).should == str.byterindex(chr, start) - end + 0.upto(str.size + 1) do |start| + str.byterindex(str, start).should == str.byterindex(chr, start) + end - (-str.size - 1).upto(-1) do |start| - str.byterindex(str, start).should == str.byterindex(chr, start) - end + (-str.size - 1).upto(-1) do |start| + str.byterindex(str, start).should == str.byterindex(chr, start) end end + end - it "behaves the same as String#byterindex(?char) for one-character strings" do - "blablabla hello cruel world...!".split("").uniq.each do |str| - chr = str[0] =~ / / ? str[0] : eval("?#{str[0]}") - str.byterindex(str).should == str.byterindex(chr) + it "behaves the same as String#byterindex(?char) for one-character strings" do + "blablabla hello cruel world...!".split("").uniq.each do |str| + chr = str[0] =~ / / ? str[0] : eval("?#{str[0]}") + str.byterindex(str).should == str.byterindex(chr) - 0.upto(str.size + 1) do |start| - str.byterindex(str, start).should == str.byterindex(chr, start) - end + 0.upto(str.size + 1) do |start| + str.byterindex(str, start).should == str.byterindex(chr, start) + end - (-str.size - 1).upto(-1) do |start| - str.byterindex(str, start).should == str.byterindex(chr, start) - end + (-str.size - 1).upto(-1) do |start| + str.byterindex(str, start).should == str.byterindex(chr, start) end end + end - it "returns the index of the last occurrence of the given substring" do - "blablabla".byterindex("").should == 9 - "blablabla".byterindex("a").should == 8 - "blablabla".byterindex("la").should == 7 - "blablabla".byterindex("bla").should == 6 - "blablabla".byterindex("abla").should == 5 - "blablabla".byterindex("labla").should == 4 - "blablabla".byterindex("blabla").should == 3 - "blablabla".byterindex("ablabla").should == 2 - "blablabla".byterindex("lablabla").should == 1 - "blablabla".byterindex("blablabla").should == 0 - - "blablabla".byterindex("l").should == 7 - "blablabla".byterindex("bl").should == 6 - "blablabla".byterindex("abl").should == 5 - "blablabla".byterindex("labl").should == 4 - "blablabla".byterindex("blabl").should == 3 - "blablabla".byterindex("ablabl").should == 2 - "blablabla".byterindex("lablabl").should == 1 - "blablabla".byterindex("blablabl").should == 0 - - "blablabla".byterindex("b").should == 6 - "blablabla".byterindex("ab").should == 5 - "blablabla".byterindex("lab").should == 4 - "blablabla".byterindex("blab").should == 3 - "blablabla".byterindex("ablab").should == 2 - "blablabla".byterindex("lablab").should == 1 - "blablabla".byterindex("blablab").should == 0 - end + it "returns the index of the last occurrence of the given substring" do + "blablabla".byterindex("").should == 9 + "blablabla".byterindex("a").should == 8 + "blablabla".byterindex("la").should == 7 + "blablabla".byterindex("bla").should == 6 + "blablabla".byterindex("abla").should == 5 + "blablabla".byterindex("labla").should == 4 + "blablabla".byterindex("blabla").should == 3 + "blablabla".byterindex("ablabla").should == 2 + "blablabla".byterindex("lablabla").should == 1 + "blablabla".byterindex("blablabla").should == 0 + + "blablabla".byterindex("l").should == 7 + "blablabla".byterindex("bl").should == 6 + "blablabla".byterindex("abl").should == 5 + "blablabla".byterindex("labl").should == 4 + "blablabla".byterindex("blabl").should == 3 + "blablabla".byterindex("ablabl").should == 2 + "blablabla".byterindex("lablabl").should == 1 + "blablabla".byterindex("blablabl").should == 0 + + "blablabla".byterindex("b").should == 6 + "blablabla".byterindex("ab").should == 5 + "blablabla".byterindex("lab").should == 4 + "blablabla".byterindex("blab").should == 3 + "blablabla".byterindex("ablab").should == 2 + "blablabla".byterindex("lablab").should == 1 + "blablabla".byterindex("blablab").should == 0 + end - it "ignores string subclasses" do - "blablabla".byterindex(StringSpecs::MyString.new("bla")).should == 6 - StringSpecs::MyString.new("blablabla").byterindex("bla").should == 6 - StringSpecs::MyString.new("blablabla").byterindex(StringSpecs::MyString.new("bla")).should == 6 - end + it "ignores string subclasses" do + "blablabla".byterindex(StringSpecs::MyString.new("bla")).should == 6 + StringSpecs::MyString.new("blablabla").byterindex("bla").should == 6 + StringSpecs::MyString.new("blablabla").byterindex(StringSpecs::MyString.new("bla")).should == 6 + end - it "starts the search at the given offset" do - "blablabla".byterindex("bl", 0).should == 0 - "blablabla".byterindex("bl", 1).should == 0 - "blablabla".byterindex("bl", 2).should == 0 - "blablabla".byterindex("bl", 3).should == 3 - - "blablabla".byterindex("bla", 0).should == 0 - "blablabla".byterindex("bla", 1).should == 0 - "blablabla".byterindex("bla", 2).should == 0 - "blablabla".byterindex("bla", 3).should == 3 - - "blablabla".byterindex("blab", 0).should == 0 - "blablabla".byterindex("blab", 1).should == 0 - "blablabla".byterindex("blab", 2).should == 0 - "blablabla".byterindex("blab", 3).should == 3 - "blablabla".byterindex("blab", 6).should == 3 - "blablablax".byterindex("blab", 6).should == 3 - - "blablabla".byterindex("la", 1).should == 1 - "blablabla".byterindex("la", 2).should == 1 - "blablabla".byterindex("la", 3).should == 1 - "blablabla".byterindex("la", 4).should == 4 - - "blablabla".byterindex("lab", 1).should == 1 - "blablabla".byterindex("lab", 2).should == 1 - "blablabla".byterindex("lab", 3).should == 1 - "blablabla".byterindex("lab", 4).should == 4 - - "blablabla".byterindex("ab", 2).should == 2 - "blablabla".byterindex("ab", 3).should == 2 - "blablabla".byterindex("ab", 4).should == 2 - "blablabla".byterindex("ab", 5).should == 5 - - "blablabla".byterindex("", 0).should == 0 - "blablabla".byterindex("", 1).should == 1 - "blablabla".byterindex("", 2).should == 2 - "blablabla".byterindex("", 7).should == 7 - "blablabla".byterindex("", 8).should == 8 - "blablabla".byterindex("", 9).should == 9 - "blablabla".byterindex("", 10).should == 9 - end + it "starts the search at the given offset" do + "blablabla".byterindex("bl", 0).should == 0 + "blablabla".byterindex("bl", 1).should == 0 + "blablabla".byterindex("bl", 2).should == 0 + "blablabla".byterindex("bl", 3).should == 3 + + "blablabla".byterindex("bla", 0).should == 0 + "blablabla".byterindex("bla", 1).should == 0 + "blablabla".byterindex("bla", 2).should == 0 + "blablabla".byterindex("bla", 3).should == 3 + + "blablabla".byterindex("blab", 0).should == 0 + "blablabla".byterindex("blab", 1).should == 0 + "blablabla".byterindex("blab", 2).should == 0 + "blablabla".byterindex("blab", 3).should == 3 + "blablabla".byterindex("blab", 6).should == 3 + "blablablax".byterindex("blab", 6).should == 3 + + "blablabla".byterindex("la", 1).should == 1 + "blablabla".byterindex("la", 2).should == 1 + "blablabla".byterindex("la", 3).should == 1 + "blablabla".byterindex("la", 4).should == 4 + + "blablabla".byterindex("lab", 1).should == 1 + "blablabla".byterindex("lab", 2).should == 1 + "blablabla".byterindex("lab", 3).should == 1 + "blablabla".byterindex("lab", 4).should == 4 + + "blablabla".byterindex("ab", 2).should == 2 + "blablabla".byterindex("ab", 3).should == 2 + "blablabla".byterindex("ab", 4).should == 2 + "blablabla".byterindex("ab", 5).should == 5 + + "blablabla".byterindex("", 0).should == 0 + "blablabla".byterindex("", 1).should == 1 + "blablabla".byterindex("", 2).should == 2 + "blablabla".byterindex("", 7).should == 7 + "blablabla".byterindex("", 8).should == 8 + "blablabla".byterindex("", 9).should == 9 + "blablabla".byterindex("", 10).should == 9 + end - it "starts the search at offset + self.length if offset is negative" do - str = "blablabla" + it "starts the search at offset + self.length if offset is negative" do + str = "blablabla" - ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle| - (-str.length .. -1).each do |offset| - str.byterindex(needle, offset).should == - str.byterindex(needle, offset + str.length) - end + ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle| + (-str.length .. -1).each do |offset| + str.byterindex(needle, offset).should == + str.byterindex(needle, offset + str.length) end end + end - it "returns nil if the substring isn't found" do - "blablabla".byterindex("B").should == nil - "blablabla".byterindex("z").should == nil - "blablabla".byterindex("BLA").should == nil - "blablabla".byterindex("blablablabla").should == nil + it "returns nil if the substring isn't found" do + "blablabla".byterindex("B").should == nil + "blablabla".byterindex("z").should == nil + "blablabla".byterindex("BLA").should == nil + "blablabla".byterindex("blablablabla").should == nil - "hello".byterindex("lo", 0).should == nil - "hello".byterindex("lo", 1).should == nil - "hello".byterindex("lo", 2).should == nil + "hello".byterindex("lo", 0).should == nil + "hello".byterindex("lo", 1).should == nil + "hello".byterindex("lo", 2).should == nil - "hello".byterindex("llo", 0).should == nil - "hello".byterindex("llo", 1).should == nil + "hello".byterindex("llo", 0).should == nil + "hello".byterindex("llo", 1).should == nil - "hello".byterindex("el", 0).should == nil - "hello".byterindex("ello", 0).should == nil + "hello".byterindex("el", 0).should == nil + "hello".byterindex("ello", 0).should == nil - "hello".byterindex("", -6).should == nil - "hello".byterindex("", -7).should == nil + "hello".byterindex("", -6).should == nil + "hello".byterindex("", -7).should == nil - "hello".byterindex("h", -6).should == nil - end + "hello".byterindex("h", -6).should == nil + end - it "tries to convert start_offset to an integer via to_int" do - obj = mock('5') - def obj.to_int() 5 end - "str".byterindex("st", obj).should == 0 + it "tries to convert start_offset to an integer via to_int" do + obj = mock('5') + def obj.to_int() 5 end + "str".byterindex("st", obj).should == 0 - obj = mock('5') - def obj.respond_to?(arg, *) true end - def obj.method_missing(*args) 5 end - "str".byterindex("st", obj).should == 0 - end + obj = mock('5') + def obj.respond_to?(arg, *) true end + def obj.method_missing(*args) 5 end + "str".byterindex("st", obj).should == 0 + end - it "raises a TypeError when given offset is nil" do - -> { "str".byterindex("st", nil) }.should raise_error(TypeError) - end + it "raises a TypeError when given offset is nil" do + -> { "str".byterindex("st", nil) }.should raise_error(TypeError) + end - it "handles a substring in a superset encoding" do - 'abc'.force_encoding(Encoding::US_ASCII).byterindex('é').should == nil - end + it "handles a substring in a superset encoding" do + 'abc'.dup.force_encoding(Encoding::US_ASCII).byterindex('é').should == nil + end - it "handles a substring in a subset encoding" do - 'été'.byterindex('t'.force_encoding(Encoding::US_ASCII)).should == 2 - end + it "handles a substring in a subset encoding" do + 'été'.byterindex('t'.dup.force_encoding(Encoding::US_ASCII)).should == 2 end end describe "String#byterindex with Regexp" do - ruby_version_is "3.2" do - it "behaves the same as String#byterindex(string) for escaped string regexps" do - ["blablabla", "hello cruel world...!"].each do |str| - ["", "b", "bla", "lab", "o c", "d."].each do |needle| - regexp = Regexp.new(Regexp.escape(needle)) - str.byterindex(regexp).should == str.byterindex(needle) - - 0.upto(str.size + 1) do |start| - str.byterindex(regexp, start).should == str.byterindex(needle, start) - end - - (-str.size - 1).upto(-1) do |start| - str.byterindex(regexp, start).should == str.byterindex(needle, start) - end + it "behaves the same as String#byterindex(string) for escaped string regexps" do + ["blablabla", "hello cruel world...!"].each do |str| + ["", "b", "bla", "lab", "o c", "d."].each do |needle| + regexp = Regexp.new(Regexp.escape(needle)) + str.byterindex(regexp).should == str.byterindex(needle) + + 0.upto(str.size + 1) do |start| + str.byterindex(regexp, start).should == str.byterindex(needle, start) + end + + (-str.size - 1).upto(-1) do |start| + str.byterindex(regexp, start).should == str.byterindex(needle, start) end end end + end - it "returns the index of the first match from the end of string of regexp" do - "blablabla".byterindex(/bla/).should == 6 - "blablabla".byterindex(/BLA/i).should == 6 - - "blablabla".byterindex(/.{0}/).should == 9 - "blablabla".byterindex(/.{1}/).should == 8 - "blablabla".byterindex(/.{2}/).should == 7 - "blablabla".byterindex(/.{6}/).should == 3 - "blablabla".byterindex(/.{9}/).should == 0 + it "returns the index of the first match from the end of string of regexp" do + "blablabla".byterindex(/bla/).should == 6 + "blablabla".byterindex(/BLA/i).should == 6 - "blablabla".byterindex(/.*/).should == 9 - "blablabla".byterindex(/.+/).should == 8 + "blablabla".byterindex(/.{0}/).should == 9 + "blablabla".byterindex(/.{1}/).should == 8 + "blablabla".byterindex(/.{2}/).should == 7 + "blablabla".byterindex(/.{6}/).should == 3 + "blablabla".byterindex(/.{9}/).should == 0 - "blablabla".byterindex(/bla|a/).should == 8 + "blablabla".byterindex(/.*/).should == 9 + "blablabla".byterindex(/.+/).should == 8 - not_supported_on :opal do - "blablabla".byterindex(/\A/).should == 0 - "blablabla".byterindex(/\Z/).should == 9 - "blablabla".byterindex(/\z/).should == 9 - "blablabla\n".byterindex(/\Z/).should == 10 - "blablabla\n".byterindex(/\z/).should == 10 - end + "blablabla".byterindex(/bla|a/).should == 8 - "blablabla".byterindex(/^/).should == 0 - not_supported_on :opal do - "\nblablabla".byterindex(/^/).should == 1 - "b\nlablabla".byterindex(/^/).should == 2 - end - "blablabla".byterindex(/$/).should == 9 - - "blablabla".byterindex(/.l./).should == 6 + not_supported_on :opal do + "blablabla".byterindex(/\A/).should == 0 + "blablabla".byterindex(/\Z/).should == 9 + "blablabla".byterindex(/\z/).should == 9 + "blablabla\n".byterindex(/\Z/).should == 10 + "blablabla\n".byterindex(/\z/).should == 10 end - it "starts the search at the given offset" do - "blablabla".byterindex(/.{0}/, 5).should == 5 - "blablabla".byterindex(/.{1}/, 5).should == 5 - "blablabla".byterindex(/.{2}/, 5).should == 5 - "blablabla".byterindex(/.{3}/, 5).should == 5 - "blablabla".byterindex(/.{4}/, 5).should == 5 - - "blablabla".byterindex(/.{0}/, 3).should == 3 - "blablabla".byterindex(/.{1}/, 3).should == 3 - "blablabla".byterindex(/.{2}/, 3).should == 3 - "blablabla".byterindex(/.{5}/, 3).should == 3 - "blablabla".byterindex(/.{6}/, 3).should == 3 - - "blablabla".byterindex(/.l./, 0).should == 0 - "blablabla".byterindex(/.l./, 1).should == 0 - "blablabla".byterindex(/.l./, 2).should == 0 - "blablabla".byterindex(/.l./, 3).should == 3 - - "blablablax".byterindex(/.x/, 10).should == 8 - "blablablax".byterindex(/.x/, 9).should == 8 - "blablablax".byterindex(/.x/, 8).should == 8 - - "blablablax".byterindex(/..x/, 10).should == 7 - "blablablax".byterindex(/..x/, 9).should == 7 - "blablablax".byterindex(/..x/, 8).should == 7 - "blablablax".byterindex(/..x/, 7).should == 7 - - not_supported_on :opal do - "blablabla\n".byterindex(/\Z/, 9).should == 9 - end + "blablabla".byterindex(/^/).should == 0 + not_supported_on :opal do + "\nblablabla".byterindex(/^/).should == 1 + "b\nlablabla".byterindex(/^/).should == 2 end + "blablabla".byterindex(/$/).should == 9 - it "starts the search at offset + self.length if offset is negative" do - str = "blablabla" + "blablabla".byterindex(/.l./).should == 6 + end - ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle| - (-str.length .. -1).each do |offset| - str.byterindex(needle, offset).should == - str.byterindex(needle, offset + str.length) - end - end + it "starts the search at the given offset" do + "blablabla".byterindex(/.{0}/, 5).should == 5 + "blablabla".byterindex(/.{1}/, 5).should == 5 + "blablabla".byterindex(/.{2}/, 5).should == 5 + "blablabla".byterindex(/.{3}/, 5).should == 5 + "blablabla".byterindex(/.{4}/, 5).should == 5 + + "blablabla".byterindex(/.{0}/, 3).should == 3 + "blablabla".byterindex(/.{1}/, 3).should == 3 + "blablabla".byterindex(/.{2}/, 3).should == 3 + "blablabla".byterindex(/.{5}/, 3).should == 3 + "blablabla".byterindex(/.{6}/, 3).should == 3 + + "blablabla".byterindex(/.l./, 0).should == 0 + "blablabla".byterindex(/.l./, 1).should == 0 + "blablabla".byterindex(/.l./, 2).should == 0 + "blablabla".byterindex(/.l./, 3).should == 3 + + "blablablax".byterindex(/.x/, 10).should == 8 + "blablablax".byterindex(/.x/, 9).should == 8 + "blablablax".byterindex(/.x/, 8).should == 8 + + "blablablax".byterindex(/..x/, 10).should == 7 + "blablablax".byterindex(/..x/, 9).should == 7 + "blablablax".byterindex(/..x/, 8).should == 7 + "blablablax".byterindex(/..x/, 7).should == 7 + + not_supported_on :opal do + "blablabla\n".byterindex(/\Z/, 9).should == 9 end + end - it "returns nil if the substring isn't found" do - "blablabla".byterindex(/BLA/).should == nil - "blablabla".byterindex(/.{10}/).should == nil - "blablablax".byterindex(/.x/, 7).should == nil - "blablablax".byterindex(/..x/, 6).should == nil + it "starts the search at offset + self.length if offset is negative" do + str = "blablabla" - not_supported_on :opal do - "blablabla".byterindex(/\Z/, 5).should == nil - "blablabla".byterindex(/\z/, 5).should == nil - "blablabla\n".byterindex(/\z/, 9).should == nil + ["bl", "bla", "blab", "la", "lab", "ab", ""].each do |needle| + (-str.length .. -1).each do |offset| + str.byterindex(needle, offset).should == + str.byterindex(needle, offset + str.length) end end + end + + it "returns nil if the substring isn't found" do + "blablabla".byterindex(/BLA/).should == nil + "blablabla".byterindex(/.{10}/).should == nil + "blablablax".byterindex(/.x/, 7).should == nil + "blablablax".byterindex(/..x/, 6).should == nil not_supported_on :opal do - it "supports \\G which matches at the given start offset" do - "helloYOU.".byterindex(/YOU\G/, 8).should == 5 - "helloYOU.".byterindex(/YOU\G/).should == nil - - idx = "helloYOUall!".index("YOU") - re = /YOU.+\G.+/ - # The # marks where \G will match. - [ - ["helloYOU#all.", nil], - ["helloYOUa#ll.", idx], - ["helloYOUal#l.", idx], - ["helloYOUall#.", idx], - ["helloYOUall.#", nil] - ].each do |i| - start = i[0].index("#") - str = i[0].delete("#") - - str.byterindex(re, start).should == i[1] - end + "blablabla".byterindex(/\Z/, 5).should == nil + "blablabla".byterindex(/\z/, 5).should == nil + "blablabla\n".byterindex(/\z/, 9).should == nil + end + end + + not_supported_on :opal do + it "supports \\G which matches at the given start offset" do + "helloYOU.".byterindex(/YOU\G/, 8).should == 5 + "helloYOU.".byterindex(/YOU\G/).should == nil + + idx = "helloYOUall!".index("YOU") + re = /YOU.+\G.+/ + # The # marks where \G will match. + [ + ["helloYOU#all.", nil], + ["helloYOUa#ll.", idx], + ["helloYOUal#l.", idx], + ["helloYOUall#.", idx], + ["helloYOUall.#", nil] + ].each do |i| + start = i[0].index("#") + str = i[0].delete("#") + + str.byterindex(re, start).should == i[1] end end + end - it "tries to convert start_offset to an integer" do - obj = mock('5') - def obj.to_int() 5 end - "str".byterindex(/../, obj).should == 1 + it "tries to convert start_offset to an integer" do + obj = mock('5') + def obj.to_int() 5 end + "str".byterindex(/../, obj).should == 1 - obj = mock('5') - def obj.respond_to?(arg, *) true end - def obj.method_missing(*args); 5; end - "str".byterindex(/../, obj).should == 1 - end + obj = mock('5') + def obj.respond_to?(arg, *) true end + def obj.method_missing(*args); 5; end + "str".byterindex(/../, obj).should == 1 + end - it "raises a TypeError when given offset is nil" do - -> { "str".byterindex(/../, nil) }.should raise_error(TypeError) - end + it "raises a TypeError when given offset is nil" do + -> { "str".byterindex(/../, nil) }.should raise_error(TypeError) + end - it "returns the reverse byte index of a multibyte character" do - "ã‚りãŒã‚ŠãŒã¨ã†".byterindex("ãŒ").should == 12 - "ã‚りãŒã‚ŠãŒã¨ã†".byterindex(/ãŒ/).should == 12 - end + it "returns the reverse byte index of a multibyte character" do + "ã‚りãŒã‚ŠãŒã¨ã†".byterindex("ãŒ").should == 12 + "ã‚りãŒã‚ŠãŒã¨ã†".byterindex(/ãŒ/).should == 12 + end - it "returns the character index before the finish" do - "ã‚りãŒã‚ŠãŒã¨ã†".byterindex("ãŒ", 9).should == 6 - "ã‚りãŒã‚ŠãŒã¨ã†".byterindex(/ãŒ/, 9).should == 6 - end + it "returns the character index before the finish" do + "ã‚りãŒã‚ŠãŒã¨ã†".byterindex("ãŒ", 9).should == 6 + "ã‚りãŒã‚ŠãŒã¨ã†".byterindex(/ãŒ/, 9).should == 6 end end diff --git a/spec/ruby/core/string/bytes_spec.rb b/spec/ruby/core/string/bytes_spec.rb index 859b346550..02151eebbc 100644 --- a/spec/ruby/core/string/bytes_spec.rb +++ b/spec/ruby/core/string/bytes_spec.rb @@ -50,6 +50,6 @@ describe "String#bytes" do end it "is unaffected by #force_encoding" do - @utf8.force_encoding('ASCII').bytes.to_a.should == @utf8.bytes.to_a + @utf8.dup.force_encoding('ASCII').bytes.to_a.should == @utf8.bytes.to_a end end diff --git a/spec/ruby/core/string/bytesize_spec.rb b/spec/ruby/core/string/bytesize_spec.rb index a31f3ae671..2bbefc0820 100644 --- a/spec/ruby/core/string/bytesize_spec.rb +++ b/spec/ruby/core/string/bytesize_spec.rb @@ -13,21 +13,21 @@ describe "String#bytesize" do end it "works with pseudo-ASCII strings containing single UTF-8 characters" do - "\u{6666}".force_encoding('ASCII').bytesize.should == 3 + "\u{6666}".dup.force_encoding('ASCII').bytesize.should == 3 end it "works with strings containing UTF-8 characters" do - "c \u{6666}".force_encoding('UTF-8').bytesize.should == 5 + "c \u{6666}".dup.force_encoding('UTF-8').bytesize.should == 5 "c \u{6666}".bytesize.should == 5 end it "works with pseudo-ASCII strings containing UTF-8 characters" do - "c \u{6666}".force_encoding('ASCII').bytesize.should == 5 + "c \u{6666}".dup.force_encoding('ASCII').bytesize.should == 5 end it "returns 0 for the empty string" do "".bytesize.should == 0 - "".force_encoding('ASCII').bytesize.should == 0 - "".force_encoding('UTF-8').bytesize.should == 0 + "".dup.force_encoding('ASCII').bytesize.should == 0 + "".dup.force_encoding('UTF-8').bytesize.should == 0 end end diff --git a/spec/ruby/core/string/byteslice_spec.rb b/spec/ruby/core/string/byteslice_spec.rb index 312229523d..4ad9e8d8f1 100644 --- a/spec/ruby/core/string/byteslice_spec.rb +++ b/spec/ruby/core/string/byteslice_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/slice' @@ -17,12 +17,12 @@ describe "String#byteslice with Range" do it_behaves_like :string_slice_range, :byteslice end -describe "String#byteslice on on non ASCII strings" do +describe "String#byteslice on non ASCII strings" do it "returns byteslice of unicode strings" do - "\u3042".byteslice(1).should == "\x81".force_encoding("UTF-8") - "\u3042".byteslice(1, 2).should == "\x81\x82".force_encoding("UTF-8") - "\u3042".byteslice(1..2).should == "\x81\x82".force_encoding("UTF-8") - "\u3042".byteslice(-1).should == "\x82".force_encoding("UTF-8") + "\u3042".byteslice(1).should == "\x81".dup.force_encoding("UTF-8") + "\u3042".byteslice(1, 2).should == "\x81\x82".dup.force_encoding("UTF-8") + "\u3042".byteslice(1..2).should == "\x81\x82".dup.force_encoding("UTF-8") + "\u3042".byteslice(-1).should == "\x82".dup.force_encoding("UTF-8") end it "returns a String in the same encoding as self" do diff --git a/spec/ruby/core/string/bytesplice_spec.rb b/spec/ruby/core/string/bytesplice_spec.rb index f13024a79b..2c770e340a 100644 --- a/spec/ruby/core/string/bytesplice_spec.rb +++ b/spec/ruby/core/string/bytesplice_spec.rb @@ -1,132 +1,293 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#bytesplice" do - ruby_version_is "3.2" do - it "raises IndexError when index is less than -bytesize" do - -> { "hello".bytesplice(-6, 0, "xxx") }.should raise_error(IndexError, "index -6 out of string") + it "raises IndexError when index is less than -bytesize" do + -> { "hello".bytesplice(-6, 0, "xxx") }.should raise_error(IndexError, "index -6 out of string") + end + + it "raises IndexError when index is greater than bytesize" do + -> { "hello".bytesplice(6, 0, "xxx") }.should raise_error(IndexError, "index 6 out of string") + end + + it "raises IndexError for negative length" do + -> { "abc".bytesplice(0, -2, "") }.should raise_error(IndexError, "negative length -2") + end + + it "replaces with integer indices" do + "hello".bytesplice(-5, 0, "xxx").should == "xxxhello" + "hello".bytesplice(0, 0, "xxx").should == "xxxhello" + "hello".bytesplice(0, 1, "xxx").should == "xxxello" + "hello".bytesplice(0, 5, "xxx").should == "xxx" + "hello".bytesplice(0, 6, "xxx").should == "xxx" + end + + it "raises RangeError when range left boundary is less than -bytesize" do + -> { "hello".bytesplice(-6...-6, "xxx") }.should raise_error(RangeError, "-6...-6 out of range") + end + + it "replaces with ranges" do + "hello".bytesplice(-5...-5, "xxx").should == "xxxhello" + "hello".bytesplice(0...0, "xxx").should == "xxxhello" + "hello".bytesplice(0..0, "xxx").should == "xxxello" + "hello".bytesplice(0...1, "xxx").should == "xxxello" + "hello".bytesplice(0..1, "xxx").should == "xxxllo" + "hello".bytesplice(0..-1, "xxx").should == "xxx" + "hello".bytesplice(0...5, "xxx").should == "xxx" + "hello".bytesplice(0...6, "xxx").should == "xxx" + end + + it "raises TypeError when integer index is provided without length argument" do + -> { "hello".bytesplice(0, "xxx") }.should raise_error(TypeError, "wrong argument type Integer (expected Range)") + end + + it "replaces on an empty string" do + "".bytesplice(0, 0, "").should == "" + "".bytesplice(0, 0, "xxx").should == "xxx" + end + + it "mutates self" do + s = "hello" + s.bytesplice(2, 1, "xxx").should.equal?(s) + end + + it "raises when string is frozen" do + s = "hello".freeze + -> { s.bytesplice(2, 1, "xxx") }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") + end + + ruby_version_is "3.3" do + it "raises IndexError when str_index is less than -bytesize" do + -> { "hello".bytesplice(2, 1, "HELLO", -6, 0) }.should raise_error(IndexError, "index -6 out of string") end - it "raises IndexError when index is greater than bytesize" do - -> { "hello".bytesplice(6, 0, "xxx") }.should raise_error(IndexError, "index 6 out of string") + it "raises IndexError when str_index is greater than bytesize" do + -> { "hello".bytesplice(2, 1, "HELLO", 6, 0) }.should raise_error(IndexError, "index 6 out of string") end - it "raises IndexError for negative length" do - -> { "abc".bytesplice(0, -2, "") }.should raise_error(IndexError, "negative length -2") + it "raises IndexError for negative str length" do + -> { "abc".bytesplice(0, 1, "", 0, -2) }.should raise_error(IndexError, "negative length -2") end - it "replaces with integer indices" do - "hello".bytesplice(-5, 0, "xxx").should == "xxxhello" - "hello".bytesplice(0, 0, "xxx").should == "xxxhello" - "hello".bytesplice(0, 1, "xxx").should == "xxxello" - "hello".bytesplice(0, 5, "xxx").should == "xxx" - "hello".bytesplice(0, 6, "xxx").should == "xxx" + it "replaces with integer str indices" do + "hello".bytesplice(1, 2, "HELLO", -5, 0).should == "hlo" + "hello".bytesplice(1, 2, "HELLO", 0, 0).should == "hlo" + "hello".bytesplice(1, 2, "HELLO", 0, 1).should == "hHlo" + "hello".bytesplice(1, 2, "HELLO", 0, 5).should == "hHELLOlo" + "hello".bytesplice(1, 2, "HELLO", 0, 6).should == "hHELLOlo" end - it "raises RangeError when range left boundary is less than -bytesize" do - -> { "hello".bytesplice(-6...-6, "xxx") }.should raise_error(RangeError, "-6...-6 out of range") + it "raises RangeError when str range left boundary is less than -bytesize" do + -> { "hello".bytesplice(0..1, "HELLO", -6...-6) }.should raise_error(RangeError, "-6...-6 out of range") end - it "replaces with ranges" do - "hello".bytesplice(-5...-5, "xxx").should == "xxxhello" - "hello".bytesplice(0...0, "xxx").should == "xxxhello" - "hello".bytesplice(0..0, "xxx").should == "xxxello" - "hello".bytesplice(0...1, "xxx").should == "xxxello" - "hello".bytesplice(0..1, "xxx").should == "xxxllo" - "hello".bytesplice(0..-1, "xxx").should == "xxx" - "hello".bytesplice(0...5, "xxx").should == "xxx" - "hello".bytesplice(0...6, "xxx").should == "xxx" + it "replaces with str ranges" do + "hello".bytesplice(1..2, "HELLO", -5...-5).should == "hlo" + "hello".bytesplice(1..2, "HELLO", 0...0).should == "hlo" + "hello".bytesplice(1..2, "HELLO", 0..0).should == "hHlo" + "hello".bytesplice(1..2, "HELLO", 0...1).should == "hHlo" + "hello".bytesplice(1..2, "HELLO", 0..1).should == "hHElo" + "hello".bytesplice(1..2, "HELLO", 0..-1).should == "hHELLOlo" + "hello".bytesplice(1..2, "HELLO", 0...5).should == "hHELLOlo" + "hello".bytesplice(1..2, "HELLO", 0...6).should == "hHELLOlo" end - it "raises TypeError when integer index is provided without length argument" do - -> { "hello".bytesplice(0, "xxx") }.should raise_error(TypeError, "wrong argument type Integer (expected Range)") + it "raises ArgumentError when integer str index is provided without str length argument" do + -> { "hello".bytesplice(0, 1, "xxx", 0) }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 2, 3, or 5)") end - it "replaces on an empty string" do - "".bytesplice(0, 0, "").should == "" - "".bytesplice(0, 0, "xxx").should == "xxx" + it "replaces on an empty string with str index/length" do + "".bytesplice(0, 0, "", 0, 0).should == "" + "".bytesplice(0, 0, "xxx", 0, 1).should == "x" end - it "mutates self" do + it "mutates self with substring and str index/length" do s = "hello" - s.bytesplice(2, 1, "xxx").should.equal?(s) + s.bytesplice(2, 1, "xxx", 1, 2).should.equal?(s) + s.should.eql?("hexxlo") end - it "raises when string is frozen" do + it "raises when string is frozen and str index/length" do s = "hello".freeze - -> { s.bytesplice(2, 1, "xxx") }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") + -> { s.bytesplice(2, 1, "xxx", 0, 1) }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") + end + + it "replaces on an empty string with str range" do + "".bytesplice(0..0, "", 0..0).should == "" + "".bytesplice(0..0, "xyz", 0..1).should == "xy" + end + + it "mutates self with substring and str range" do + s = "hello" + s.bytesplice(2..2, "xyz", 1..2).should.equal?(s) + s.should.eql?("heyzlo") + end + + it "raises when string is frozen and str range" do + s = "hello".freeze + -> { s.bytesplice(2..2, "yzx", 0..1) }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") end end end describe "String#bytesplice with multibyte characters" do - ruby_version_is "3.2" do - it "raises IndexError when index is out of byte size boundary" do - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-16, 0, "xxx") }.should raise_error(IndexError, "index -16 out of string") + it "raises IndexError when index is out of byte size boundary" do + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-16, 0, "xxx") }.should raise_error(IndexError, "index -16 out of string") + end + + it "raises IndexError when index is not on a codepoint boundary" do + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(1, 0, "xxx") }.should raise_error(IndexError, "offset 1 does not land on character boundary") + end + + it "raises IndexError when length is not matching the codepoint boundary" do + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(0, 1, "xxx") }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(0, 2, "xxx") }.should raise_error(IndexError, "offset 2 does not land on character boundary") + end + + it "replaces with integer indices" do + "ã“ã‚“ã«ã¡ã¯".bytesplice(-15, 0, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0, 0, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0, 3, "xxx").should == "xxxã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã¯ã¯").should == "ã“ã¯ã¯ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(15, 0, "xxx").should == "ã“ã‚“ã«ã¡ã¯xxx" + end + + it "replaces with range" do + "ã“ã‚“ã«ã¡ã¯".bytesplice(-15...-16, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0...0, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "xxx").should == "xxxã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0...3, "xxx").should == "xxxã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..5, "xxx").should == "xxxã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..-1, "xxx").should == "xxx" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0...15, "xxx").should == "xxx" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0...18, "xxx").should == "xxx" + end + + it "treats negative length for range as 0" do + "ã“ã‚“ã«ã¡ã¯".bytesplice(0...-100, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(3...-100, "xxx").should == "ã“xxxã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(-15...-100, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" + end + + it "raises when ranges not match codepoint boundaries" do + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(0..0, "x") }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(0..1, "x") }.should raise_error(IndexError, "offset 2 does not land on character boundary") + # Begin is incorrect + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-4..-1, "x") }.should raise_error(IndexError, "offset 11 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-5..-1, "x") }.should raise_error(IndexError, "offset 10 does not land on character boundary") + # End is incorrect + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-3..-2, "x") }.should raise_error(IndexError, "offset 14 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-3..-3, "x") }.should raise_error(IndexError, "offset 13 does not land on character boundary") + end + + it "deals with a different encoded argument" do + s = "ã“ã‚“ã«ã¡ã¯" + s.encoding.should == Encoding::UTF_8 + sub = "xxxxxx" + sub.force_encoding(Encoding::US_ASCII) + + result = s.bytesplice(0, 3, sub) + result.should == "xxxxxxã‚“ã«ã¡ã¯" + result.encoding.should == Encoding::UTF_8 + + s = "xxxxxx" + s.force_encoding(Encoding::US_ASCII) + sub = "ã“ã‚“ã«ã¡ã¯" + sub.encoding.should == Encoding::UTF_8 + + result = s.bytesplice(0, 3, sub) + result.should == "ã“ã‚“ã«ã¡ã¯xxx" + result.encoding.should == Encoding::UTF_8 + end + + ruby_version_is "3.3" do + it "raises IndexError when str_index is out of byte size boundary" do + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã“ã‚“ã«ã¡ã¯", -16, 0) }.should raise_error(IndexError, "index -16 out of string") end - it "raises IndexError when index is not on a codepoint boundary" do - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(1, 0, "xxx") }.should raise_error(IndexError, "offset 1 does not land on character boundary") + it "raises IndexError when str_index is not on a codepoint boundary" do + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã“ã‚“ã«ã¡ã¯", 1, 0) }.should raise_error(IndexError, "offset 1 does not land on character boundary") end - it "raises IndexError when length is not matching the codepoint boundary" do - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(0, 1, "xxx") }.should raise_error(IndexError, "offset 1 does not land on character boundary") - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(0, 2, "xxx") }.should raise_error(IndexError, "offset 2 does not land on character boundary") + it "raises IndexError when str_length is not matching the codepoint boundary" do + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã“ã‚“ã«ã¡ã¯", 0, 1) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã“ã‚“ã«ã¡ã¯", 0, 2) }.should raise_error(IndexError, "offset 2 does not land on character boundary") end - it "replaces with integer indices" do - "ã“ã‚“ã«ã¡ã¯".bytesplice(-15, 0, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0, 0, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0, 3, "xxx").should == "xxxã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã¯ã¯").should == "ã“ã¯ã¯ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(15, 0, "xxx").should == "ã“ã‚“ã«ã¡ã¯xxx" + it "replaces with integer str indices" do + "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã“ã‚“ã«ã¡ã¯", -15, 0).should == "ã“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã“ã‚“ã«ã¡ã¯", 0, 0).should == "ã“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã“ã‚“ã«ã¡ã¯", 0, 3).should == "ã“ã“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã¯ã¯", 3, 3).should == "ã“ã¯ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(3, 3, "ã“ã‚“ã«ã¡ã¯", 15, 0).should == "ã“ã«ã¡ã¯" end - it "replaces with range" do - "ã“ã‚“ã«ã¡ã¯".bytesplice(-15...-16, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0...0, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "xxx").should == "xxxã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0...3, "xxx").should == "xxxã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0..5, "xxx").should == "xxxã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0..-1, "xxx").should == "xxx" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0...15, "xxx").should == "xxx" - "ã“ã‚“ã«ã¡ã¯".bytesplice(0...18, "xxx").should == "xxx" + it "replaces with str range" do + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", -15...-16).should == "ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 0...0).should == "ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 3..5).should == "ã‚“ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 3...6).should == "ã‚“ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 3..8).should == "ã‚“ã«ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 0..-1).should == "ã“ã‚“ã«ã¡ã¯ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 0...15).should == "ã“ã‚“ã«ã¡ã¯ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 0...18).should == "ã“ã‚“ã«ã¡ã¯ã‚“ã«ã¡ã¯" end - it "treats negative length for range as 0" do - "ã“ã‚“ã«ã¡ã¯".bytesplice(0...-100, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(3...-100, "xxx").should == "ã“xxxã‚“ã«ã¡ã¯" - "ã“ã‚“ã«ã¡ã¯".bytesplice(-15...-100, "xxx").should == "xxxã“ã‚“ã«ã¡ã¯" + it "treats negative length for str range as 0" do + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 0...-100).should == "ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", 3...-100).should == "ã‚“ã«ã¡ã¯" + "ã“ã‚“ã«ã¡ã¯".bytesplice(0..2, "ã“ã‚“ã«ã¡ã¯", -15...-100).should == "ã‚“ã«ã¡ã¯" end - it "raises when ranges not match codepoint boundaries" do - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(0..0, "x") }.should raise_error(IndexError, "offset 1 does not land on character boundary") - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(0..1, "x") }.should raise_error(IndexError, "offset 2 does not land on character boundary") + it "raises when ranges not match codepoint boundaries in str" do + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3...3, "ã“", 0..0) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3...3, "ã“", 0..1) }.should raise_error(IndexError, "offset 2 does not land on character boundary") # Begin is incorrect - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-4..-1, "x") }.should raise_error(IndexError, "offset 11 does not land on character boundary") - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-5..-1, "x") }.should raise_error(IndexError, "offset 10 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3...3, "ã“ã‚“ã«ã¡ã¯", -4..-1) }.should raise_error(IndexError, "offset 11 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3...3, "ã“ã‚“ã«ã¡ã¯", -5..-1) }.should raise_error(IndexError, "offset 10 does not land on character boundary") # End is incorrect - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-3..-2, "x") }.should raise_error(IndexError, "offset 14 does not land on character boundary") - -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(-3..-3, "x") }.should raise_error(IndexError, "offset 13 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3...3, "ã“ã‚“ã«ã¡ã¯", -3..-2) }.should raise_error(IndexError, "offset 14 does not land on character boundary") + -> { "ã“ã‚“ã«ã¡ã¯".bytesplice(3...3, "ã“ã‚“ã«ã¡ã¯", -3..-3) }.should raise_error(IndexError, "offset 13 does not land on character boundary") end - it "deals with a different encoded argument" do + it "deals with a different encoded argument with str index/length" do s = "ã“ã‚“ã«ã¡ã¯" s.encoding.should == Encoding::UTF_8 - sub = "xxxxxx" + sub = "goodbye" sub.force_encoding(Encoding::US_ASCII) - result = s.bytesplice(0, 3, sub) - result.should == "xxxxxxã‚“ã«ã¡ã¯" + result = s.bytesplice(3, 3, sub, 0, 3) + result.should == "ã“gooã«ã¡ã¯" result.encoding.should == Encoding::UTF_8 - s = "xxxxxx" + s = "hello" + s.force_encoding(Encoding::US_ASCII) + sub = "ã“ã‚“ã«ã¡ã¯" + sub.encoding.should == Encoding::UTF_8 + + result = s.bytesplice(1, 2, sub, 3, 3) + result.should == "hã‚“lo" + result.encoding.should == Encoding::UTF_8 + end + + it "deals with a different encoded argument with str range" do + s = "ã“ã‚“ã«ã¡ã¯" + s.encoding.should == Encoding::UTF_8 + sub = "goodbye" + sub.force_encoding(Encoding::US_ASCII) + + result = s.bytesplice(3..5, sub, 0..2) + result.should == "ã“gooã«ã¡ã¯" + result.encoding.should == Encoding::UTF_8 + + s = "hello" s.force_encoding(Encoding::US_ASCII) sub = "ã“ã‚“ã«ã¡ã¯" sub.encoding.should == Encoding::UTF_8 - result = s.bytesplice(0, 3, sub) - result.should == "ã“ã‚“ã«ã¡ã¯xxx" + result = s.bytesplice(1..2, sub, 3..5) + result.should == "hã‚“lo" result.encoding.should == Encoding::UTF_8 end end diff --git a/spec/ruby/core/string/capitalize_spec.rb b/spec/ruby/core/string/capitalize_spec.rb index b79e9cfdbd..5e59b656c5 100644 --- a/spec/ruby/core/string/capitalize_spec.rb +++ b/spec/ruby/core/string/capitalize_spec.rb @@ -90,7 +90,7 @@ end describe "String#capitalize!" do it "capitalizes self in place" do - a = "hello" + a = +"hello" a.capitalize!.should equal(a) a.should == "Hello" end @@ -103,13 +103,13 @@ describe "String#capitalize!" do describe "full Unicode case mapping" do it "modifies self in place for all of Unicode with no option" do - a = "äöÜ" + a = +"äöÜ" a.capitalize! a.should == "Äöü" end it "only capitalizes the first resulting character when upcasing a character produces a multi-character sequence" do - a = "ß" + a = +"ß" a.capitalize! a.should == "Ss" end @@ -121,7 +121,7 @@ describe "String#capitalize!" do end it "updates string metadata" do - capitalized = "ßeT" + capitalized = +"ßeT" capitalized.capitalize! capitalized.should == "Sset" @@ -133,7 +133,7 @@ describe "String#capitalize!" do describe "modifies self in place for ASCII-only case mapping" do it "does not capitalize non-ASCII characters" do - a = "ßet" + a = +"ßet" a.capitalize!(:ascii) a.should == "ßet" end @@ -147,13 +147,13 @@ describe "String#capitalize!" do describe "modifies self in place for full Unicode case mapping adapted for Turkic languages" do it "capitalizes ASCII characters according to Turkic semantics" do - a = "iSa" + a = +"iSa" a.capitalize!(:turkic) a.should == "İsa" end it "allows Lithuanian as an extra option" do - a = "iSa" + a = +"iSa" a.capitalize!(:turkic, :lithuanian) a.should == "İsa" end @@ -165,13 +165,13 @@ describe "String#capitalize!" do describe "modifies self in place for full Unicode case mapping adapted for Lithuanian" do it "currently works the same as full Unicode case mapping" do - a = "iß" + a = +"iß" a.capitalize!(:lithuanian) a.should == "Iß" end it "allows Turkic as an extra option (and applies Turkic semantics)" do - a = "iß" + a = +"iß" a.capitalize!(:lithuanian, :turkic) a.should == "İß" end @@ -190,12 +190,12 @@ describe "String#capitalize!" do end it "returns nil when no changes are made" do - a = "Hello" + a = +"Hello" a.capitalize!.should == nil a.should == "Hello" - "".capitalize!.should == nil - "H".capitalize!.should == nil + (+"").capitalize!.should == nil + (+"H").capitalize!.should == nil end it "raises a FrozenError when self is frozen" do diff --git a/spec/ruby/core/string/center_spec.rb b/spec/ruby/core/string/center_spec.rb index a59dd2a91b..1667b59327 100644 --- a/spec/ruby/core/string/center_spec.rb +++ b/spec/ruby/core/string/center_spec.rb @@ -92,7 +92,7 @@ describe "String#center with length, padding" do describe "with width" do it "returns a String in the same encoding as the original" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.center 6 result.should == " abc " result.encoding.should equal(Encoding::IBM437) @@ -101,7 +101,7 @@ describe "String#center with length, padding" do describe "with width, pattern" do it "returns a String in the compatible encoding" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.center 6, "ã‚" result.should == "ã‚abcã‚ã‚" result.encoding.should equal(Encoding::UTF_8) diff --git a/spec/ruby/core/string/chilled_string_spec.rb b/spec/ruby/core/string/chilled_string_spec.rb new file mode 100644 index 0000000000..73d055cbdf --- /dev/null +++ b/spec/ruby/core/string/chilled_string_spec.rb @@ -0,0 +1,151 @@ +require_relative '../../spec_helper' + +describe "chilled String" do + guard -> { ruby_version_is "3.4" and !"test".equal?("test") } do + describe "chilled string literals" do + + describe "#frozen?" do + it "returns false" do + "chilled".frozen?.should == false + end + end + + describe "#-@" do + it "returns a different instance" do + input = "chilled" + interned = (-input) + interned.frozen?.should == true + interned.object_id.should_not == input.object_id + end + end + + describe "#+@" do + it "returns a different instance" do + input = "chilled" + duped = (+input) + duped.frozen?.should == false + duped.object_id.should_not == input.object_id + end + end + + describe "#clone" do + it "preserves chilled status" do + input = "chilled".clone + -> { + input << "-mutated" + }.should complain(/literal string will be frozen in the future/) + input.should == "chilled-mutated" + end + end + + describe "mutation" do + it "emits a warning" do + input = "chilled" + -> { + input << "-mutated" + }.should complain(/literal string will be frozen in the future/) + input.should == "chilled-mutated" + end + + it "emits a warning for concatenated strings" do + input = "still" "+chilled" + -> { + input << "-mutated" + }.should complain(/literal string will be frozen in the future/) + input.should == "still+chilled-mutated" + end + + it "emits a warning on singleton_class creation" do + -> { + "chilled".singleton_class + }.should complain(/literal string will be frozen in the future/) + end + + it "emits a warning on instance variable assignment" do + -> { + "chilled".instance_variable_set(:@ivar, 42) + }.should complain(/literal string will be frozen in the future/) + end + + it "raises FrozenError after the string was explicitly frozen" do + input = "chilled" + input.freeze + -> { + -> { + input << "mutated" + }.should raise_error(FrozenError) + }.should_not complain(/literal string will be frozen in the future/) + end + end + end + + describe "chilled strings returned by Symbol#to_s" do + + describe "#frozen?" do + it "returns false" do + :chilled.to_s.frozen?.should == false + end + end + + describe "#-@" do + it "returns a different instance" do + input = :chilled.to_s + interned = (-input) + interned.frozen?.should == true + interned.object_id.should_not == input.object_id + end + end + + describe "#+@" do + it "returns a different instance" do + input = :chilled.to_s + duped = (+input) + duped.frozen?.should == false + duped.object_id.should_not == input.object_id + end + end + + describe "#clone" do + it "preserves chilled status" do + input = :chilled.to_s.clone + -> { + input << "-mutated" + }.should complain(/string returned by :chilled\.to_s will be frozen in the future/) + input.should == "chilled-mutated" + end + end + + describe "mutation" do + it "emits a warning" do + input = :chilled.to_s + -> { + input << "-mutated" + }.should complain(/string returned by :chilled\.to_s will be frozen in the future/) + input.should == "chilled-mutated" + end + + it "emits a warning on singleton_class creation" do + -> { + :chilled.to_s.singleton_class + }.should complain(/string returned by :chilled\.to_s will be frozen in the future/) + end + + it "emits a warning on instance variable assignment" do + -> { + :chilled.to_s.instance_variable_set(:@ivar, 42) + }.should complain(/string returned by :chilled\.to_s will be frozen in the future/) + end + + it "raises FrozenError after the string was explicitly frozen" do + input = :chilled.to_s + input.freeze + -> { + -> { + input << "mutated" + }.should raise_error(FrozenError) + }.should_not complain(/string returned by :chilled\.to_s will be frozen in the future/) + end + end + end + end +end diff --git a/spec/ruby/core/string/chomp_spec.rb b/spec/ruby/core/string/chomp_spec.rb index ec0490220b..d27c84c6f6 100644 --- a/spec/ruby/core/string/chomp_spec.rb +++ b/spec/ruby/core/string/chomp_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/chop_spec.rb b/spec/ruby/core/string/chop_spec.rb index 75f25b39cd..99c2c82190 100644 --- a/spec/ruby/core/string/chop_spec.rb +++ b/spec/ruby/core/string/chop_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/clear_spec.rb b/spec/ruby/core/string/clear_spec.rb index e1d68e03bd..152986fd0f 100644 --- a/spec/ruby/core/string/clear_spec.rb +++ b/spec/ruby/core/string/clear_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#clear" do diff --git a/spec/ruby/core/string/codepoints_spec.rb b/spec/ruby/core/string/codepoints_spec.rb index 0b6cde82f7..12a5bf5892 100644 --- a/spec/ruby/core/string/codepoints_spec.rb +++ b/spec/ruby/core/string/codepoints_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'shared/codepoints' require_relative 'shared/each_codepoint_without_block' @@ -11,7 +11,7 @@ describe "String#codepoints" do end it "raises an ArgumentError when no block is given if self has an invalid encoding" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false -> { s.codepoints }.should raise_error(ArgumentError) end diff --git a/spec/ruby/core/string/comparison_spec.rb b/spec/ruby/core/string/comparison_spec.rb index 91cfdca25a..9db0cff5ee 100644 --- a/spec/ruby/core/string/comparison_spec.rb +++ b/spec/ruby/core/string/comparison_spec.rb @@ -61,12 +61,12 @@ describe "String#<=> with String" do end it "ignores encoding difference" do - ("ÄÖÛ".force_encoding("utf-8") <=> "ÄÖÜ".force_encoding("iso-8859-1")).should == -1 - ("ÄÖÜ".force_encoding("utf-8") <=> "ÄÖÛ".force_encoding("iso-8859-1")).should == 1 + ("ÄÖÛ".dup.force_encoding("utf-8") <=> "ÄÖÜ".dup.force_encoding("iso-8859-1")).should == -1 + ("ÄÖÜ".dup.force_encoding("utf-8") <=> "ÄÖÛ".dup.force_encoding("iso-8859-1")).should == 1 end it "returns 0 with identical ASCII-compatible bytes of different encodings" do - ("abc".force_encoding("utf-8") <=> "abc".force_encoding("iso-8859-1")).should == 0 + ("abc".dup.force_encoding("utf-8") <=> "abc".dup.force_encoding("iso-8859-1")).should == 0 end it "compares the indices of the encodings when the strings have identical non-ASCII-compatible bytes" do @@ -77,7 +77,7 @@ describe "String#<=> with String" do end it "returns 0 when comparing 2 empty strings but one is not ASCII-compatible" do - ("" <=> "".force_encoding('iso-2022-jp')).should == 0 + ("" <=> "".dup.force_encoding('iso-2022-jp')).should == 0 end end diff --git a/spec/ruby/core/string/concat_spec.rb b/spec/ruby/core/string/concat_spec.rb index 6f487eaa3a..cbd7df54e2 100644 --- a/spec/ruby/core/string/concat_spec.rb +++ b/spec/ruby/core/string/concat_spec.rb @@ -8,19 +8,19 @@ describe "String#concat" do it_behaves_like :string_concat_type_coercion, :concat it "takes multiple arguments" do - str = "hello " + str = +"hello " str.concat "wo", "", "rld" str.should == "hello world" end it "concatenates the initial value when given arguments contain 2 self" do - str = "hello" + str = +"hello" str.concat str, str str.should == "hellohellohello" end it "returns self when given no arguments" do - str = "hello" + str = +"hello" str.concat.should equal(str) str.should == "hello" end diff --git a/spec/ruby/core/string/count_spec.rb b/spec/ruby/core/string/count_spec.rb index 06ba5a4f0e..e614e901dd 100644 --- a/spec/ruby/core/string/count_spec.rb +++ b/spec/ruby/core/string/count_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/dedup_spec.rb b/spec/ruby/core/string/dedup_spec.rb index 57d2be2cfd..2b31d80708 100644 --- a/spec/ruby/core/string/dedup_spec.rb +++ b/spec/ruby/core/string/dedup_spec.rb @@ -2,7 +2,5 @@ require_relative '../../spec_helper' require_relative 'shared/dedup' describe 'String#dedup' do - ruby_version_is '3.2' do - it_behaves_like :string_dedup, :dedup - end + it_behaves_like :string_dedup, :dedup end diff --git a/spec/ruby/core/string/delete_prefix_spec.rb b/spec/ruby/core/string/delete_prefix_spec.rb index 4214fdecce..ee7f044905 100644 --- a/spec/ruby/core/string/delete_prefix_spec.rb +++ b/spec/ruby/core/string/delete_prefix_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/delete_spec.rb b/spec/ruby/core/string/delete_spec.rb index 3b9aa4fb75..6d359776e4 100644 --- a/spec/ruby/core/string/delete_spec.rb +++ b/spec/ruby/core/string/delete_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/delete_suffix_spec.rb b/spec/ruby/core/string/delete_suffix_spec.rb index 9381f4cee7..1842d75aa5 100644 --- a/spec/ruby/core/string/delete_suffix_spec.rb +++ b/spec/ruby/core/string/delete_suffix_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/downcase_spec.rb b/spec/ruby/core/string/downcase_spec.rb index 7ee9d6df1d..2d260f23f1 100644 --- a/spec/ruby/core/string/downcase_spec.rb +++ b/spec/ruby/core/string/downcase_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/dup_spec.rb b/spec/ruby/core/string/dup_spec.rb index 73f71b8ffc..073802d84b 100644 --- a/spec/ruby/core/string/dup_spec.rb +++ b/spec/ruby/core/string/dup_spec.rb @@ -51,7 +51,7 @@ describe "String#dup" do end it "does not modify the original setbyte-mutated string when changing dupped string" do - orig = "a" + orig = +"a" orig.setbyte 0, "b".ord copy = orig.dup orig.setbyte 0, "c".ord diff --git a/spec/ruby/core/string/each_byte_spec.rb b/spec/ruby/core/string/each_byte_spec.rb index e04dca807f..7b3db265ac 100644 --- a/spec/ruby/core/string/each_byte_spec.rb +++ b/spec/ruby/core/string/each_byte_spec.rb @@ -9,26 +9,26 @@ describe "String#each_byte" do end it "keeps iterating from the old position (to new string end) when self changes" do - r = "" - s = "hello world" + r = +"" + s = +"hello world" s.each_byte do |c| r << c s.insert(0, "<>") if r.size < 3 end r.should == "h><>hello world" - r = "" - s = "hello world" + r = +"" + s = +"hello world" s.each_byte { |c| s.slice!(-1); r << c } r.should == "hello " - r = "" - s = "hello world" + r = +"" + s = +"hello world" s.each_byte { |c| s.slice!(0); r << c } r.should == "hlowrd" - r = "" - s = "hello world" + r = +"" + s = +"hello world" s.each_byte { |c| s.slice!(0..-1); r << c } r.should == "h" end diff --git a/spec/ruby/core/string/element_set_spec.rb b/spec/ruby/core/string/element_set_spec.rb index fa041fa31d..e7599f832c 100644 --- a/spec/ruby/core/string/element_set_spec.rb +++ b/spec/ruby/core/string/element_set_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/encode_spec.rb b/spec/ruby/core/string/encode_spec.rb index 35ed27bb40..cd449498a3 100644 --- a/spec/ruby/core/string/encode_spec.rb +++ b/spec/ruby/core/string/encode_spec.rb @@ -34,8 +34,8 @@ describe "String#encode" do it "encodes an ascii substring of a binary string to UTF-8" do x82 = [0x82].pack('C') - str = "#{x82}foo".force_encoding("binary")[1..-1].encode("utf-8") - str.should == "foo".force_encoding("utf-8") + str = "#{x82}foo".dup.force_encoding("binary")[1..-1].encode("utf-8") + str.should == "foo".dup.force_encoding("utf-8") str.encoding.should equal(Encoding::UTF_8) end end @@ -49,7 +49,7 @@ describe "String#encode" do end it "round trips a String" do - str = "abc def".force_encoding Encoding::US_ASCII + str = "abc def".dup.force_encoding Encoding::US_ASCII str.encode("utf-32be").encode("ascii").should == "abc def" end end @@ -61,10 +61,22 @@ describe "String#encode" do str.encode(invalid: :replace).should_not equal(str) end - it "normalizes newlines" do - "\r\nfoo".encode(universal_newline: true).should == "\nfoo" + it "normalizes newlines with cr_newline option" do + "\r\nfoo".encode(cr_newline: true).should == "\r\rfoo" + "\rfoo".encode(cr_newline: true).should == "\rfoo" + "\nfoo".encode(cr_newline: true).should == "\rfoo" + end + + it "normalizes newlines with crlf_newline option" do + "\r\nfoo".encode(crlf_newline: true).should == "\r\r\nfoo" + "\rfoo".encode(crlf_newline: true).should == "\rfoo" + "\nfoo".encode(crlf_newline: true).should == "\r\nfoo" + end + it "normalizes newlines with universal_newline option" do + "\r\nfoo".encode(universal_newline: true).should == "\nfoo" "\rfoo".encode(universal_newline: true).should == "\nfoo" + "\nfoo".encode(universal_newline: true).should == "\nfoo" end it "replaces invalid encoding in source with default replacement" do @@ -122,8 +134,7 @@ describe "String#encode" do describe "when passed to, from" do it "returns a copy in the destination encoding when both encodings are the same" do - str = "ã‚" - str.force_encoding("binary") + str = "ã‚".dup.force_encoding("binary") encoded = str.encode("utf-8", "utf-8") encoded.should_not equal(str) @@ -155,8 +166,7 @@ describe "String#encode" do end it "returns a copy in the destination encoding when both encodings are the same" do - str = "ã‚" - str.force_encoding("binary") + str = "ã‚".dup.force_encoding("binary") encoded = str.encode("utf-8", "utf-8", invalid: :replace) encoded.should_not equal(str) @@ -191,13 +201,13 @@ describe "String#encode!" do describe "when passed no options" do it "returns self when Encoding.default_internal is nil" do Encoding.default_internal = nil - str = "ã‚" + str = +"ã‚" str.encode!.should equal(str) end it "returns self for a ASCII-only String when Encoding.default_internal is nil" do Encoding.default_internal = nil - str = "abc" + str = +"abc" str.encode!.should equal(str) end end @@ -205,14 +215,14 @@ describe "String#encode!" do describe "when passed options" do it "returns self for ASCII-only String when Encoding.default_internal is nil" do Encoding.default_internal = nil - str = "abc" + str = +"abc" str.encode!(invalid: :replace).should equal(str) end end describe "when passed to encoding" do it "returns self" do - str = "abc" + str = +"abc" result = str.encode!(Encoding::BINARY) result.encoding.should equal(Encoding::BINARY) result.should equal(str) @@ -221,7 +231,7 @@ describe "String#encode!" do describe "when passed to, from" do it "returns self" do - str = "ã‚ã‚" + str = +"ã‚ã‚" result = str.encode!("euc-jp", "utf-8") result.encoding.should equal(Encoding::EUC_JP) result.should equal(str) diff --git a/spec/ruby/core/string/encoding_spec.rb b/spec/ruby/core/string/encoding_spec.rb index 574a1e2f92..f6e8fd3470 100644 --- a/spec/ruby/core/string/encoding_spec.rb +++ b/spec/ruby/core/string/encoding_spec.rb @@ -14,11 +14,11 @@ describe "String#encoding" do end it "returns the given encoding if #force_encoding has been called" do - "a".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "a".dup.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS end it "returns the given encoding if #encode!has been called" do - "a".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "a".dup.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS end end @@ -108,13 +108,13 @@ describe "String#encoding for Strings with \\u escapes" do end it "returns the given encoding if #force_encoding has been called" do - "\u{20}".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS - "\u{2020}".force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "\u{20}".dup.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "\u{2020}".dup.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS end it "returns the given encoding if #encode!has been called" do - "\u{20}".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS - "\u{2020}".encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "\u{20}".dup.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "\u{2020}".dup.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS end end @@ -173,16 +173,12 @@ describe "String#encoding for Strings with \\x escapes" do end it "returns the given encoding if #force_encoding has been called" do - x50 = "\x50" - x50.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS - xD4 = [212].pack('C') - xD4.force_encoding(Encoding::ISO_8859_9).encoding.should == Encoding::ISO_8859_9 + "\x50".dup.force_encoding(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + [212].pack('C').force_encoding(Encoding::ISO_8859_9).encoding.should == Encoding::ISO_8859_9 end it "returns the given encoding if #encode!has been called" do - x50 = "\x50" - x50.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS - x00 = "x\00" - x00.encode!(Encoding::UTF_8).encoding.should == Encoding::UTF_8 + "\x50".dup.encode!(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS + "x\00".dup.encode!(Encoding::UTF_8).encoding.should == Encoding::UTF_8 end end diff --git a/spec/ruby/core/string/fixtures/utf-8-encoding.rb b/spec/ruby/core/string/fixtures/utf-8-encoding.rb deleted file mode 100644 index fd243ec522..0000000000 --- a/spec/ruby/core/string/fixtures/utf-8-encoding.rb +++ /dev/null @@ -1,7 +0,0 @@ -# -*- encoding: utf-8 -*- -module StringSpecs - class UTF8Encoding - def self.source_encoding; __ENCODING__; end - def self.egrave; "é"; end - end -end diff --git a/spec/ruby/core/string/force_encoding_spec.rb b/spec/ruby/core/string/force_encoding_spec.rb index f37aaf9eb4..2259dcf3cf 100644 --- a/spec/ruby/core/string/force_encoding_spec.rb +++ b/spec/ruby/core/string/force_encoding_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#force_encoding" do diff --git a/spec/ruby/core/string/freeze_spec.rb b/spec/ruby/core/string/freeze_spec.rb index 04d1e9513c..2e8e70386d 100644 --- a/spec/ruby/core/string/freeze_spec.rb +++ b/spec/ruby/core/string/freeze_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#freeze" do diff --git a/spec/ruby/core/string/gsub_spec.rb b/spec/ruby/core/string/gsub_spec.rb index 9e3b50322c..0d9f32eca2 100644 --- a/spec/ruby/core/string/gsub_spec.rb +++ b/spec/ruby/core/string/gsub_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/include_spec.rb b/spec/ruby/core/string/include_spec.rb index 23e1e134ec..9781140a55 100644 --- a/spec/ruby/core/string/include_spec.rb +++ b/spec/ruby/core/string/include_spec.rb @@ -15,16 +15,16 @@ describe "String#include? with String" do it "returns true if both strings are empty" do "".should.include?("") - "".force_encoding("EUC-JP").should.include?("") - "".should.include?("".force_encoding("EUC-JP")) - "".force_encoding("EUC-JP").should.include?("".force_encoding("EUC-JP")) + "".dup.force_encoding("EUC-JP").should.include?("") + "".should.include?("".dup.force_encoding("EUC-JP")) + "".dup.force_encoding("EUC-JP").should.include?("".dup.force_encoding("EUC-JP")) end it "returns true if the RHS is empty" do "a".should.include?("") - "a".force_encoding("EUC-JP").should.include?("") - "a".should.include?("".force_encoding("EUC-JP")) - "a".force_encoding("EUC-JP").should.include?("".force_encoding("EUC-JP")) + "a".dup.force_encoding("EUC-JP").should.include?("") + "a".should.include?("".dup.force_encoding("EUC-JP")) + "a".dup.force_encoding("EUC-JP").should.include?("".dup.force_encoding("EUC-JP")) end it "tries to convert other to string using to_str" do diff --git a/spec/ruby/core/string/index_spec.rb b/spec/ruby/core/string/index_spec.rb index b500cf6ca7..835263a2cd 100644 --- a/spec/ruby/core/string/index_spec.rb +++ b/spec/ruby/core/string/index_spec.rb @@ -161,16 +161,16 @@ describe "String#index with String" do end it "handles a substring in a superset encoding" do - 'abc'.force_encoding(Encoding::US_ASCII).index('é').should == nil + 'abc'.dup.force_encoding(Encoding::US_ASCII).index('é').should == nil end it "handles a substring in a subset encoding" do - 'été'.index('t'.force_encoding(Encoding::US_ASCII)).should == 1 + 'été'.index('t'.dup.force_encoding(Encoding::US_ASCII)).should == 1 end it "raises an Encoding::CompatibilityError if the encodings are incompatible" do - str = 'abc'.force_encoding("ISO-2022-JP") - pattern = 'b'.force_encoding("EUC-JP") + str = 'abc'.dup.force_encoding("ISO-2022-JP") + pattern = 'b'.dup.force_encoding("EUC-JP") -> { str.index(pattern) }.should raise_error(Encoding::CompatibilityError, "incompatible character encodings: ISO-2022-JP and EUC-JP") end @@ -231,6 +231,17 @@ describe "String#index with Regexp" do $~.should == nil end + ruby_bug "#20421", ""..."3.3" do + it "always clear $~" do + "a".index(/a/) + $~.should_not == nil + + string = "blablabla" + string.index(/bla/, string.length + 1) + $~.should == nil + end + end + it "starts the search at the given offset" do "blablabla".index(/.{0}/, 5).should == 5 "blablabla".index(/.{1}/, 5).should == 5 diff --git a/spec/ruby/core/string/insert_spec.rb b/spec/ruby/core/string/insert_spec.rb index 0c87df3a95..483f3c9367 100644 --- a/spec/ruby/core/string/insert_spec.rb +++ b/spec/ruby/core/string/insert_spec.rb @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- - +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/inspect_spec.rb b/spec/ruby/core/string/inspect_spec.rb index 8bf3d3161f..15db06c7f5 100644 --- a/spec/ruby/core/string/inspect_spec.rb +++ b/spec/ruby/core/string/inspect_spec.rb @@ -327,7 +327,7 @@ describe "String#inspect" do end it "works for broken US-ASCII strings" do - s = "©".force_encoding("US-ASCII") + s = "©".dup.force_encoding("US-ASCII") s.inspect.should == '"\xC2\xA9"' end diff --git a/spec/ruby/core/string/ljust_spec.rb b/spec/ruby/core/string/ljust_spec.rb index 9208ec5897..47324c59d2 100644 --- a/spec/ruby/core/string/ljust_spec.rb +++ b/spec/ruby/core/string/ljust_spec.rb @@ -75,7 +75,7 @@ describe "String#ljust with length, padding" do describe "with width" do it "returns a String in the same encoding as the original" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.ljust 5 result.should == "abc " result.encoding.should equal(Encoding::IBM437) @@ -84,7 +84,7 @@ describe "String#ljust with length, padding" do describe "with width, pattern" do it "returns a String in the compatible encoding" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.ljust 5, "ã‚" result.should == "abcã‚ã‚" result.encoding.should equal(Encoding::UTF_8) diff --git a/spec/ruby/core/string/lstrip_spec.rb b/spec/ruby/core/string/lstrip_spec.rb index 85685deb0a..c83650207e 100644 --- a/spec/ruby/core/string/lstrip_spec.rb +++ b/spec/ruby/core/string/lstrip_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/strip' @@ -6,11 +7,11 @@ describe "String#lstrip" do it_behaves_like :string_strip, :lstrip it "returns a copy of self with leading whitespace removed" do - " hello ".lstrip.should == "hello " - " hello world ".lstrip.should == "hello world " - "\n\r\t\n\v\r hello world ".lstrip.should == "hello world " - "hello".lstrip.should == "hello" - " ã“ã«ã¡ã‚".lstrip.should == "ã“ã«ã¡ã‚" + " hello ".lstrip.should == "hello " + " hello world ".lstrip.should == "hello world " + "\n\r\t\n\v\r hello world ".lstrip.should == "hello world " + "hello".lstrip.should == "hello" + " ã“ã«ã¡ã‚".lstrip.should == "ã“ã«ã¡ã‚" end it "works with lazy substrings" do @@ -21,8 +22,8 @@ describe "String#lstrip" do end it "strips leading \\0" do - "\x00hello".lstrip.should == "hello" - "\000 \000hello\000 \000".lstrip.should == "hello\000 \000" + "\x00hello".lstrip.should == "hello" + "\000 \000hello\000 \000".lstrip.should == "hello\000 \000" end end diff --git a/spec/ruby/core/string/modulo_spec.rb b/spec/ruby/core/string/modulo_spec.rb index bf96a82874..46e0aa0f36 100644 --- a/spec/ruby/core/string/modulo_spec.rb +++ b/spec/ruby/core/string/modulo_spec.rb @@ -55,33 +55,48 @@ describe "String#%" do -> { ("foo%" % [])}.should raise_error(ArgumentError) end - it "formats single % character before a newline as literal %" do - ("%\n" % []).should == "%\n" - ("foo%\n" % []).should == "foo%\n" - ("%\n.3f" % 1.2).should == "%\n.3f" - end + ruby_version_is ""..."3.4" do + it "formats single % character before a newline as literal %" do + ("%\n" % []).should == "%\n" + ("foo%\n" % []).should == "foo%\n" + ("%\n.3f" % 1.2).should == "%\n.3f" + end - it "formats single % character before a NUL as literal %" do - ("%\0" % []).should == "%\0" - ("foo%\0" % []).should == "foo%\0" - ("%\0.3f" % 1.2).should == "%\0.3f" - end + it "formats single % character before a NUL as literal %" do + ("%\0" % []).should == "%\0" + ("foo%\0" % []).should == "foo%\0" + ("%\0.3f" % 1.2).should == "%\0.3f" + end - it "raises an error if single % appears anywhere else" do - -> { (" % " % []) }.should raise_error(ArgumentError) - -> { ("foo%quux" % []) }.should raise_error(ArgumentError) - end + it "raises an error if single % appears anywhere else" do + -> { (" % " % []) }.should raise_error(ArgumentError) + -> { ("foo%quux" % []) }.should raise_error(ArgumentError) + end - it "raises an error if NULL or \\n appear anywhere else in the format string" do - begin - old_debug, $DEBUG = $DEBUG, false + it "raises an error if NULL or \\n appear anywhere else in the format string" do + begin + old_debug, $DEBUG = $DEBUG, false + + -> { "%.\n3f" % 1.2 }.should raise_error(ArgumentError) + -> { "%.3\nf" % 1.2 }.should raise_error(ArgumentError) + -> { "%.\03f" % 1.2 }.should raise_error(ArgumentError) + -> { "%.3\0f" % 1.2 }.should raise_error(ArgumentError) + ensure + $DEBUG = old_debug + end + end + end + ruby_version_is "3.4" do + it "raises an ArgumentError if % is not followed by a conversion specifier" do + -> { "%" % [] }.should raise_error(ArgumentError) + -> { "%\n" % [] }.should raise_error(ArgumentError) + -> { "%\0" % [] }.should raise_error(ArgumentError) + -> { " % " % [] }.should raise_error(ArgumentError) -> { "%.\n3f" % 1.2 }.should raise_error(ArgumentError) -> { "%.3\nf" % 1.2 }.should raise_error(ArgumentError) -> { "%.\03f" % 1.2 }.should raise_error(ArgumentError) -> { "%.3\0f" % 1.2 }.should raise_error(ArgumentError) - ensure - $DEBUG = old_debug end end @@ -125,8 +140,16 @@ describe "String#%" do end end - it "replaces trailing absolute argument specifier without type with percent sign" do - ("hello %1$" % "foo").should == "hello %" + ruby_version_is ""..."3.4" do + it "replaces trailing absolute argument specifier without type with percent sign" do + ("hello %1$" % "foo").should == "hello %" + end + end + + ruby_version_is "3.4" do + it "raises an ArgumentError if absolute argument specifier is followed by a conversion specifier" do + -> { "hello %1$" % "foo" }.should raise_error(ArgumentError) + end end it "raises an ArgumentError when given invalid argument specifiers" do @@ -368,16 +391,8 @@ describe "String#%" do ("%c" % 'A').should == "A" end - ruby_version_is ""..."3.2" do - it "raises an exception for multiple character strings as argument for %c" do - -> { "%c" % 'AA' }.should raise_error(ArgumentError) - end - end - - ruby_version_is "3.2" do - it "supports only the first character as argument for %c" do - ("%c" % 'AA').should == "A" - end + it "supports only the first character as argument for %c" do + ("%c" % 'AA').should == "A" end it "calls to_str on argument for %c formats" do @@ -547,7 +562,7 @@ describe "String#%" do ("%1$p" % [10, 5]).should == "10" ("%-22p" % 10).should == "10 " ("%*p" % [10, 10]).should == " 10" - ("%p" % {capture: 1}).should == "{:capture=>1}" + ("%p" % {capture: 1}).should == {capture: 1}.inspect ("%p" % "str").should == "\"str\"" end @@ -726,6 +741,11 @@ describe "String#%" do (format % "-10.4e-20").should == (format % -10.4e-20) (format % ".5").should == (format % 0.5) (format % "-.5").should == (format % -0.5) + + ruby_version_is "3.4" do + (format % "10.").should == (format % 10) + end + # Something's strange with this spec: # it works just fine in individual mode, but not when run as part of a group (format % "10_1_0.5_5_5").should == (format % 1010.555) @@ -735,7 +755,6 @@ describe "String#%" do -> { format % "" }.should raise_error(ArgumentError) -> { format % "x" }.should raise_error(ArgumentError) -> { format % "." }.should raise_error(ArgumentError) - -> { format % "10." }.should raise_error(ArgumentError) -> { format % "5x" }.should raise_error(ArgumentError) -> { format % "0b1" }.should raise_error(ArgumentError) -> { format % "10e10.5" }.should raise_error(ArgumentError) diff --git a/spec/ruby/core/string/ord_spec.rb b/spec/ruby/core/string/ord_spec.rb index 4cf26990fe..35af3b5458 100644 --- a/spec/ruby/core/string/ord_spec.rb +++ b/spec/ruby/core/string/ord_spec.rb @@ -27,7 +27,7 @@ describe "String#ord" do end it "raises ArgumentError if the character is broken" do - s = "©".force_encoding("US-ASCII") + s = "©".dup.force_encoding("US-ASCII") -> { s.ord }.should raise_error(ArgumentError, "invalid byte sequence in US-ASCII") end end diff --git a/spec/ruby/core/string/partition_spec.rb b/spec/ruby/core/string/partition_spec.rb index 9cb3672881..d5370dcc73 100644 --- a/spec/ruby/core/string/partition_spec.rb +++ b/spec/ruby/core/string/partition_spec.rb @@ -40,7 +40,7 @@ describe "String#partition with String" do end it "handles a pattern in a superset encoding" do - string = "hello".force_encoding(Encoding::US_ASCII) + string = "hello".dup.force_encoding(Encoding::US_ASCII) result = string.partition("é") @@ -51,7 +51,7 @@ describe "String#partition with String" do end it "handles a pattern in a subset encoding" do - pattern = "o".force_encoding(Encoding::US_ASCII) + pattern = "o".dup.force_encoding(Encoding::US_ASCII) result = "héllo world".partition(pattern) diff --git a/spec/ruby/core/string/prepend_spec.rb b/spec/ruby/core/string/prepend_spec.rb index a0393d4760..5248ea8056 100644 --- a/spec/ruby/core/string/prepend_spec.rb +++ b/spec/ruby/core/string/prepend_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/reverse_spec.rb b/spec/ruby/core/string/reverse_spec.rb index e67122c05c..aa6abe6036 100644 --- a/spec/ruby/core/string/reverse_spec.rb +++ b/spec/ruby/core/string/reverse_spec.rb @@ -1,4 +1,5 @@ # encoding: utf-8 +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/rindex_spec.rb b/spec/ruby/core/string/rindex_spec.rb index 45ff13a006..0863a9c3be 100644 --- a/spec/ruby/core/string/rindex_spec.rb +++ b/spec/ruby/core/string/rindex_spec.rb @@ -1,7 +1,6 @@ # -*- encoding: utf-8 -*- require_relative '../../spec_helper' require_relative 'fixtures/classes' -require_relative 'fixtures/utf-8-encoding' describe "String#rindex with object" do it "raises a TypeError if obj isn't a String or Regexp" do @@ -198,16 +197,16 @@ describe "String#rindex with String" do end it "handles a substring in a superset encoding" do - 'abc'.force_encoding(Encoding::US_ASCII).rindex('é').should == nil + 'abc'.dup.force_encoding(Encoding::US_ASCII).rindex('é').should == nil end it "handles a substring in a subset encoding" do - 'été'.rindex('t'.force_encoding(Encoding::US_ASCII)).should == 1 + 'été'.rindex('t'.dup.force_encoding(Encoding::US_ASCII)).should == 1 end it "raises an Encoding::CompatibilityError if the encodings are incompatible" do - str = 'abc'.force_encoding("ISO-2022-JP") - pattern = 'b'.force_encoding("EUC-JP") + str = 'abc'.dup.force_encoding("ISO-2022-JP") + pattern = 'b'.dup.force_encoding("EUC-JP") -> { str.rindex(pattern) }.should raise_error(Encoding::CompatibilityError, "incompatible character encodings: ISO-2022-JP and EUC-JP") end @@ -372,8 +371,8 @@ describe "String#rindex with Regexp" do end it "returns the character index before the finish" do - "ã‚りãŒã‚ŠãŒã¨ã†".rindex("ãŒ", 3).should == 2 - "ã‚りãŒã‚ŠãŒã¨ã†".rindex(/ãŒ/, 3).should == 2 + "ã‚りãŒã‚ŠãŒã¨ã†".rindex("ãŒ", 3).should == 2 + "ã‚りãŒã‚ŠãŒã¨ã†".rindex(/ãŒ/, 3).should == 2 end it "raises an Encoding::CompatibilityError if the encodings are incompatible" do diff --git a/spec/ruby/core/string/rjust_spec.rb b/spec/ruby/core/string/rjust_spec.rb index fcbaf3b938..4ad3e54aea 100644 --- a/spec/ruby/core/string/rjust_spec.rb +++ b/spec/ruby/core/string/rjust_spec.rb @@ -75,7 +75,7 @@ describe "String#rjust with length, padding" do describe "with width" do it "returns a String in the same encoding as the original" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.rjust 5 result.should == " abc" result.encoding.should equal(Encoding::IBM437) @@ -84,7 +84,7 @@ describe "String#rjust with length, padding" do describe "with width, pattern" do it "returns a String in the compatible encoding" do - str = "abc".force_encoding Encoding::IBM437 + str = "abc".dup.force_encoding Encoding::IBM437 result = str.rjust 5, "ã‚" result.should == "ã‚ã‚abc" result.encoding.should equal(Encoding::UTF_8) diff --git a/spec/ruby/core/string/rpartition_spec.rb b/spec/ruby/core/string/rpartition_spec.rb index 21e87f530a..cef0384c73 100644 --- a/spec/ruby/core/string/rpartition_spec.rb +++ b/spec/ruby/core/string/rpartition_spec.rb @@ -48,7 +48,7 @@ describe "String#rpartition with String" do end it "handles a pattern in a superset encoding" do - string = "hello".force_encoding(Encoding::US_ASCII) + string = "hello".dup.force_encoding(Encoding::US_ASCII) result = string.rpartition("é") @@ -59,7 +59,7 @@ describe "String#rpartition with String" do end it "handles a pattern in a subset encoding" do - pattern = "o".force_encoding(Encoding::US_ASCII) + pattern = "o".dup.force_encoding(Encoding::US_ASCII) result = "héllo world".rpartition(pattern) diff --git a/spec/ruby/core/string/rstrip_spec.rb b/spec/ruby/core/string/rstrip_spec.rb index e4cf93315e..55773f5238 100644 --- a/spec/ruby/core/string/rstrip_spec.rb +++ b/spec/ruby/core/string/rstrip_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/strip' @@ -67,27 +68,13 @@ describe "String#rstrip!" do -> { "".freeze.rstrip! }.should raise_error(FrozenError) end - ruby_version_is "3.2" do - it "raises an Encoding::CompatibilityError if the last non-space codepoint is invalid" do - s = "abc\xDF".force_encoding(Encoding::UTF_8) - s.valid_encoding?.should be_false - -> { s.rstrip! }.should raise_error(Encoding::CompatibilityError) + it "raises an Encoding::CompatibilityError if the last non-space codepoint is invalid" do + s = "abc\xDF".force_encoding(Encoding::UTF_8) + s.valid_encoding?.should be_false + -> { s.rstrip! }.should raise_error(Encoding::CompatibilityError) - s = "abc\xDF ".force_encoding(Encoding::UTF_8) - s.valid_encoding?.should be_false - -> { s.rstrip! }.should raise_error(Encoding::CompatibilityError) - end - end - - ruby_version_is ""..."3.2" do - it "raises an ArgumentError if the last non-space codepoint is invalid" do - s = "abc\xDF".force_encoding(Encoding::UTF_8) - s.valid_encoding?.should be_false - -> { s.rstrip! }.should raise_error(ArgumentError) - - s = "abc\xDF ".force_encoding(Encoding::UTF_8) - s.valid_encoding?.should be_false - -> { s.rstrip! }.should raise_error(ArgumentError) - end + s = "abc\xDF ".force_encoding(Encoding::UTF_8) + s.valid_encoding?.should be_false + -> { s.rstrip! }.should raise_error(Encoding::CompatibilityError) end end diff --git a/spec/ruby/core/string/scan_spec.rb b/spec/ruby/core/string/scan_spec.rb index 70c3b7fb7b..bbe843b591 100644 --- a/spec/ruby/core/string/scan_spec.rb +++ b/spec/ruby/core/string/scan_spec.rb @@ -103,11 +103,11 @@ describe "String#scan with pattern and block" do offsets = [] str.scan(/([aeiou])/) do - md = $~ - md.string.should == str - matches << md.to_a - offsets << md.offset(0) - str + md = $~ + md.string.should == str + matches << md.to_a + offsets << md.offset(0) + str end matches.should == [["e", "e"], ["o", "o"]] @@ -117,11 +117,11 @@ describe "String#scan with pattern and block" do offsets = [] str.scan("l") do - md = $~ - md.string.should == str - matches << md.to_a - offsets << md.offset(0) - str + md = $~ + md.string.should == str + matches << md.to_a + offsets << md.offset(0) + str end matches.should == [["l"], ["l"]] diff --git a/spec/ruby/core/string/scrub_spec.rb b/spec/ruby/core/string/scrub_spec.rb index bcee4db463..b9ef0f1a16 100644 --- a/spec/ruby/core/string/scrub_spec.rb +++ b/spec/ruby/core/string/scrub_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/setbyte_spec.rb b/spec/ruby/core/string/setbyte_spec.rb index 77bff64038..85403ca62c 100644 --- a/spec/ruby/core/string/setbyte_spec.rb +++ b/spec/ruby/core/string/setbyte_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#setbyte" do diff --git a/spec/ruby/core/string/shared/chars.rb b/spec/ruby/core/string/shared/chars.rb index e9fdf89fd6..c730643cf4 100644 --- a/spec/ruby/core/string/shared/chars.rb +++ b/spec/ruby/core/string/shared/chars.rb @@ -21,12 +21,12 @@ describe :string_chars, shared: true do end it "returns characters in the same encoding as self" do - "&%".force_encoding('Shift_JIS').send(@method).to_a.all? {|c| c.encoding.name.should == 'Shift_JIS'} + "&%".dup.force_encoding('Shift_JIS').send(@method).to_a.all? {|c| c.encoding.name.should == 'Shift_JIS'} "&%".encode('BINARY').send(@method).to_a.all? {|c| c.encoding.should == Encoding::BINARY } end it "works with multibyte characters" do - s = "\u{8987}".force_encoding("UTF-8") + s = "\u{8987}".dup.force_encoding("UTF-8") s.bytesize.should == 3 s.send(@method).to_a.should == [s] end @@ -39,14 +39,14 @@ describe :string_chars, shared: true do end it "returns a different character if the String is transcoded" do - s = "\u{20AC}".force_encoding('UTF-8') - s.encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".force_encoding('UTF-8')] + s = "\u{20AC}".dup.force_encoding('UTF-8') + s.encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".dup.force_encoding('UTF-8')] s.encode('iso-8859-15').send(@method).to_a.should == [[0xA4].pack('C').force_encoding('iso-8859-15')] - s.encode('iso-8859-15').encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".force_encoding('UTF-8')] + s.encode('iso-8859-15').encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".dup.force_encoding('UTF-8')] end it "uses the String's encoding to determine what characters it contains" do - s = "\u{24B62}" + s = +"\u{24B62}" s.force_encoding('UTF-8').send(@method).to_a.should == [ s.force_encoding('UTF-8') diff --git a/spec/ruby/core/string/shared/codepoints.rb b/spec/ruby/core/string/shared/codepoints.rb index 0b2e078e0a..1c28ba3d5e 100644 --- a/spec/ruby/core/string/shared/codepoints.rb +++ b/spec/ruby/core/string/shared/codepoints.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_codepoints, shared: true do it "returns self" do s = "foo" @@ -7,7 +7,7 @@ describe :string_codepoints, shared: true do end it "raises an ArgumentError when self has an invalid encoding and a method is called on the returned Enumerator" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false -> { s.send(@method).to_a }.should raise_error(ArgumentError) end @@ -21,7 +21,7 @@ describe :string_codepoints, shared: true do end it "raises an ArgumentError if self's encoding is invalid and a block is given" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false -> { s.send(@method) { } }.should raise_error(ArgumentError) end @@ -49,7 +49,7 @@ describe :string_codepoints, shared: true do it "round-trips to the original String using Integer#chr" do s = "\u{13}\u{7711}\u{1010}" - s2 = "" + s2 = +"" s.send(@method) {|n| s2 << n.chr(Encoding::UTF_8)} s.should == s2 end diff --git a/spec/ruby/core/string/shared/concat.rb b/spec/ruby/core/string/shared/concat.rb index ee5ef2a98f..dded9a69e7 100644 --- a/spec/ruby/core/string/shared/concat.rb +++ b/spec/ruby/core/string/shared/concat.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false describe :string_concat, shared: true do it "concatenates the given argument to self and returns self" do str = 'hello ' diff --git a/spec/ruby/core/string/shared/dedup.rb b/spec/ruby/core/string/shared/dedup.rb index 893fd1e360..1ffd6aa0fd 100644 --- a/spec/ruby/core/string/shared/dedup.rb +++ b/spec/ruby/core/string/shared/dedup.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false describe :string_dedup, shared: true do it 'returns self if the String is frozen' do input = 'foo'.freeze @@ -47,9 +48,4 @@ describe :string_dedup, shared: true do dynamic.send(@method).should_not equal("this string is frozen".send(@method).freeze) dynamic.send(@method).should equal(dynamic) end - - it "interns the provided string if it is frozen" do - dynamic = "this string is unique and frozen #{rand}".freeze - dynamic.send(@method).should equal(dynamic) - end end diff --git a/spec/ruby/core/string/shared/each_codepoint_without_block.rb b/spec/ruby/core/string/shared/each_codepoint_without_block.rb index 92b7f76032..c88e5c54c7 100644 --- a/spec/ruby/core/string/shared/each_codepoint_without_block.rb +++ b/spec/ruby/core/string/shared/each_codepoint_without_block.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_each_codepoint_without_block, shared: true do describe "when no block is given" do it "returns an Enumerator" do @@ -6,7 +6,7 @@ describe :string_each_codepoint_without_block, shared: true do end it "returns an Enumerator even when self has an invalid encoding" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false s.send(@method).should be_an_instance_of(Enumerator) end @@ -23,7 +23,7 @@ describe :string_each_codepoint_without_block, shared: true do end it "should return the size of the string even when the string has an invalid encoding" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false s.send(@method).size.should == 1 end diff --git a/spec/ruby/core/string/shared/each_line.rb b/spec/ruby/core/string/shared/each_line.rb index a14b4d7779..231a6d9d4f 100644 --- a/spec/ruby/core/string/shared/each_line.rb +++ b/spec/ruby/core/string/shared/each_line.rb @@ -106,7 +106,7 @@ describe :string_each_line, shared: true do end it "does not care if the string is modified while substituting" do - str = "hello\nworld." + str = +"hello\nworld." out = [] str.send(@method){|x| out << x; str[-1] = '!' }.should == "hello\nworld!" out.should == ["hello\n", "world."] diff --git a/spec/ruby/core/string/shared/encode.rb b/spec/ruby/core/string/shared/encode.rb index a73de5b943..9466308886 100644 --- a/spec/ruby/core/string/shared/encode.rb +++ b/spec/ruby/core/string/shared/encode.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false describe :string_encode, shared: true do describe "when passed no options" do it "transcodes to Encoding.default_internal when set" do @@ -193,6 +194,190 @@ describe :string_encode, shared: true do end end + describe "given the fallback option" do + context "given a hash" do + it "looks up the replacement value from the hash" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => "bar" }) + encoded.should == "Bbar" + end + + it "calls to_str on the returned value" do + obj = Object.new + obj.should_receive(:to_str).and_return("bar") + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => obj }) + encoded.should == "Bbar" + end + + it "does not call to_s on the returned value" do + obj = Object.new + obj.should_not_receive(:to_s) + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => obj }) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises an error if the key is not present in the hash" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: { "foo" => "bar" }) + }.should raise_error(Encoding::UndefinedConversionError, "U+FFFD from UTF-8 to US-ASCII") + end + + it "raises an error if the value is itself invalid" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => "\uffee" }) + }.should raise_error(ArgumentError, "too big fallback string") + end + + it "uses the hash's default value if set" do + hash = {} + hash.default = "bar" + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: hash) + encoded.should == "Bbar" + end + + it "uses the result of calling default_proc if set" do + hash = {} + hash.default_proc = -> _, _ { "bar" } + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: hash) + encoded.should == "Bbar" + end + end + + context "given an object inheriting from Hash" do + before do + klass = Class.new(Hash) + @hash_like = klass.new + @hash_like["\ufffd"] = "bar" + end + + it "looks up the replacement value from the object" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: @hash_like) + encoded.should == "Bbar" + end + end + + context "given an object responding to []" do + before do + klass = Class.new do + def [](c) = c.bytes.inspect + end + @hash_like = klass.new + end + + it "calls [] on the object, passing the invalid character" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: @hash_like) + encoded.should == "B[239, 191, 189]" + end + end + + context "given an object not responding to []" do + before do + @non_hash_like = Object.new + end + + it "raises an error" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: @non_hash_like) + }.should raise_error(Encoding::UndefinedConversionError, "U+FFFD from UTF-8 to US-ASCII") + end + end + + context "given a proc" do + it "calls the proc to get the replacement value, passing in the invalid character" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| c.bytes.inspect }) + encoded.should == "B[239, 191, 189]" + end + + it "calls to_str on the returned value" do + obj = Object.new + obj.should_receive(:to_str).and_return("bar") + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| obj }) + encoded.should == "Bbar" + end + + it "does not call to_s on the returned value" do + obj = Object.new + obj.should_not_receive(:to_s) + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| obj }) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises an error if the returned value is itself invalid" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { "\uffee" }) + }.should raise_error(ArgumentError, "too big fallback string") + end + end + + context "given a lambda" do + it "calls the lambda to get the replacement value, passing in the invalid character" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { c.bytes.inspect }) + encoded.should == "B[239, 191, 189]" + end + + it "calls to_str on the returned value" do + obj = Object.new + obj.should_receive(:to_str).and_return("bar") + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { obj }) + encoded.should == "Bbar" + end + + it "does not call to_s on the returned value" do + obj = Object.new + obj.should_not_receive(:to_s) + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { obj }) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises an error if the returned value is itself invalid" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { "\uffee" }) + }.should raise_error(ArgumentError, "too big fallback string") + end + end + + context "given a method" do + def replace(c) = c.bytes.inspect + def replace_bad(c) = "\uffee" + + def replace_to_str(c) + obj = Object.new + obj.should_receive(:to_str).and_return("bar") + obj + end + + def replace_to_s(c) + obj = Object.new + obj.should_not_receive(:to_s) + obj + end + + it "calls the method to get the replacement value, passing in the invalid character" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace)) + encoded.should == "B[239, 191, 189]" + end + + it "calls to_str on the returned value" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_to_str)) + encoded.should == "Bbar" + end + + it "does not call to_s on the returned value" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_to_s)) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises an error if the returned value is itself invalid" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_bad)) + }.should raise_error(ArgumentError, "too big fallback string") + end + end + end + describe "given the xml: :text option" do it "replaces all instances of '&' with '&'" do '& and &'.send(@method, "UTF-8", xml: :text).should == '& and &' diff --git a/spec/ruby/core/string/shared/eql.rb b/spec/ruby/core/string/shared/eql.rb index 6f268c929c..d5af337d53 100644 --- a/spec/ruby/core/string/shared/eql.rb +++ b/spec/ruby/core/string/shared/eql.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' @@ -13,15 +13,15 @@ describe :string_eql_value, shared: true do end it "ignores encoding difference of compatible string" do - "hello".force_encoding("utf-8").send(@method, "hello".force_encoding("iso-8859-1")).should be_true + "hello".dup.force_encoding("utf-8").send(@method, "hello".dup.force_encoding("iso-8859-1")).should be_true end it "considers encoding difference of incompatible string" do - "\xff".force_encoding("utf-8").send(@method, "\xff".force_encoding("iso-8859-1")).should be_false + "\xff".dup.force_encoding("utf-8").send(@method, "\xff".dup.force_encoding("iso-8859-1")).should be_false end it "considers encoding compatibility" do - "abcd".force_encoding("utf-8").send(@method, "abcd".force_encoding("utf-32le")).should be_false + "abcd".dup.force_encoding("utf-8").send(@method, "abcd".dup.force_encoding("utf-32le")).should be_false end it "ignores subclass differences" do @@ -33,6 +33,6 @@ describe :string_eql_value, shared: true do end it "returns true when comparing 2 empty strings but one is not ASCII-compatible" do - "".send(@method, "".force_encoding('iso-2022-jp')).should == true + "".send(@method, "".dup.force_encoding('iso-2022-jp')).should == true end end diff --git a/spec/ruby/core/string/shared/length.rb b/spec/ruby/core/string/shared/length.rb index 94e5ec135b..ae572ba755 100644 --- a/spec/ruby/core/string/shared/length.rb +++ b/spec/ruby/core/string/shared/length.rb @@ -18,7 +18,7 @@ describe :string_length, shared: true do end it "returns the length of the new self after encoding is changed" do - str = 'ã“ã«ã¡ã‚' + str = +'ã“ã«ã¡ã‚' str.send(@method) str.force_encoding('BINARY').send(@method).should == 12 @@ -44,12 +44,12 @@ describe :string_length, shared: true do end it "adds 1 (and not 2) for a incomplete surrogate in UTF-16" do - "\x00\xd8".force_encoding("UTF-16LE").send(@method).should == 1 - "\xd8\x00".force_encoding("UTF-16BE").send(@method).should == 1 + "\x00\xd8".dup.force_encoding("UTF-16LE").send(@method).should == 1 + "\xd8\x00".dup.force_encoding("UTF-16BE").send(@method).should == 1 end it "adds 1 for a broken sequence in UTF-32" do - "\x04\x03\x02\x01".force_encoding("UTF-32LE").send(@method).should == 1 - "\x01\x02\x03\x04".force_encoding("UTF-32BE").send(@method).should == 1 + "\x04\x03\x02\x01".dup.force_encoding("UTF-32LE").send(@method).should == 1 + "\x01\x02\x03\x04".dup.force_encoding("UTF-32BE").send(@method).should == 1 end end diff --git a/spec/ruby/core/string/shared/replace.rb b/spec/ruby/core/string/shared/replace.rb index a5108d9e7c..24dac0eb27 100644 --- a/spec/ruby/core/string/shared/replace.rb +++ b/spec/ruby/core/string/shared/replace.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false describe :string_replace, shared: true do it "returns self" do a = "a" diff --git a/spec/ruby/core/string/shared/slice.rb b/spec/ruby/core/string/shared/slice.rb index 3ef4bc50d7..7b9b9f6a14 100644 --- a/spec/ruby/core/string/shared/slice.rb +++ b/spec/ruby/core/string/shared/slice.rb @@ -84,8 +84,8 @@ describe :string_slice_index_length, shared: true do s = "hello there" s.send(@method, 1, 9).encoding.should == s.encoding - a = "hello".force_encoding("binary") - b = " there".force_encoding("ISO-8859-1") + a = "hello".dup.force_encoding("binary") + b = " there".dup.force_encoding("ISO-8859-1") c = (a + b).force_encoding(Encoding::US_ASCII) c.send(@method, 0, 5).encoding.should == Encoding::US_ASCII @@ -119,6 +119,18 @@ describe :string_slice_index_length, shared: true do "hello there".send(@method, -4,-3).should == nil end + platform_is pointer_size: 64 do + it "returns nil if the length is negative big value" do + "hello there".send(@method, 4, -(1 << 31)).should == nil + + # by some reason length < -(1 << 31) on CI on Windows leads to + # 'RangeError: bignum too big to convert into `long'' error + platform_is_not :windows do + "hello there".send(@method, 4, -(1 << 63)).should == nil + end + end + end + it "calls to_int on the given index and the given length" do "hello".send(@method, 0.5, 1).should == "h" "hello".send(@method, 0.5, 2.5).should == "he" @@ -152,6 +164,11 @@ describe :string_slice_index_length, shared: true do -> { "hello".send(@method, 0, bignum_value) }.should raise_error(RangeError) end + it "raises a RangeError if the index or length is too small" do + -> { "hello".send(@method, -bignum_value, 1) }.should raise_error(RangeError) + -> { "hello".send(@method, 0, -bignum_value) }.should raise_error(RangeError) + end + it "returns String instances" do s = StringSpecs::MyString.new("hello") s.send(@method, 0,0).should be_an_instance_of(String) diff --git a/spec/ruby/core/string/shared/succ.rb b/spec/ruby/core/string/shared/succ.rb index 24a729ce26..7c68345f10 100644 --- a/spec/ruby/core/string/shared/succ.rb +++ b/spec/ruby/core/string/shared/succ.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_succ, shared: true do it "returns an empty string for empty strings" do "".send(@method).should == "" @@ -73,6 +73,7 @@ end describe :string_succ_bang, shared: true do it "is equivalent to succ, but modifies self in place (still returns self)" do ["", "abcd", "THX1138"].each do |s| + s = +s r = s.dup.send(@method) s.send(@method).should equal(s) s.should == r diff --git a/spec/ruby/core/string/shared/to_sym.rb b/spec/ruby/core/string/shared/to_sym.rb index 52d8314211..833eae100e 100644 --- a/spec/ruby/core/string/shared/to_sym.rb +++ b/spec/ruby/core/string/shared/to_sym.rb @@ -56,9 +56,9 @@ describe :string_to_sym, shared: true do it "ignores existing symbols with different encoding" do source = "fée" - iso_symbol = source.force_encoding(Encoding::ISO_8859_1).send(@method) + iso_symbol = source.dup.force_encoding(Encoding::ISO_8859_1).send(@method) iso_symbol.encoding.should == Encoding::ISO_8859_1 - binary_symbol = source.force_encoding(Encoding::BINARY).send(@method) + binary_symbol = source.dup.force_encoding(Encoding::BINARY).send(@method) binary_symbol.encoding.should == Encoding::BINARY end diff --git a/spec/ruby/core/string/slice_spec.rb b/spec/ruby/core/string/slice_spec.rb index 87c5a7ac37..5aba2d3be0 100644 --- a/spec/ruby/core/string/slice_spec.rb +++ b/spec/ruby/core/string/slice_spec.rb @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- - +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/slice' diff --git a/spec/ruby/core/string/split_spec.rb b/spec/ruby/core/string/split_spec.rb index c5cca651c2..3c6d1864d1 100644 --- a/spec/ruby/core/string/split_spec.rb +++ b/spec/ruby/core/string/split_spec.rb @@ -4,7 +4,7 @@ require_relative 'fixtures/classes' describe "String#split with String" do it "throws an ArgumentError if the string is not a valid" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) -> { s.split }.should raise_error(ArgumentError) -> { s.split(':') }.should raise_error(ArgumentError) @@ -12,7 +12,7 @@ describe "String#split with String" do it "throws an ArgumentError if the pattern is not a valid string" do str = 'проверка' - broken_str = "\xDF".force_encoding(Encoding::UTF_8) + broken_str = "\xDF".dup.force_encoding(Encoding::UTF_8) -> { str.split(broken_str) }.should raise_error(ArgumentError) end @@ -229,7 +229,7 @@ end describe "String#split with Regexp" do it "throws an ArgumentError if the string is not a valid" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) -> { s.split(/./) }.should raise_error(ArgumentError) end @@ -409,7 +409,7 @@ describe "String#split with Regexp" do end it "returns an ArgumentError if an invalid UTF-8 string is supplied" do - broken_str = 'проверка' # in russian, means "test" + broken_str = +'проверка' # in russian, means "test" broken_str.force_encoding('binary') broken_str.chop! broken_str.force_encoding('utf-8') diff --git a/spec/ruby/core/string/squeeze_spec.rb b/spec/ruby/core/string/squeeze_spec.rb index 4796a170f2..981d480684 100644 --- a/spec/ruby/core/string/squeeze_spec.rb +++ b/spec/ruby/core/string/squeeze_spec.rb @@ -1,4 +1,5 @@ -# -*- encoding: binary -*- +# encoding: binary +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/start_with_spec.rb b/spec/ruby/core/string/start_with_spec.rb index 3833289f96..35e33b46a6 100644 --- a/spec/ruby/core/string/start_with_spec.rb +++ b/spec/ruby/core/string/start_with_spec.rb @@ -7,12 +7,21 @@ describe "String#start_with?" do it_behaves_like :start_with, :to_s # Here and not in the shared examples because this is invalid as a Symbol - it "does not check that we are not starting to match at the head of a character" do + it "matches part of a character with the same part" do "\xA9".should.start_with?("\xA9") # A9 is not a character head for UTF-8 end - it "does not check we are matching only part of a character" do - "\xe3\x81\x82".size.should == 1 - "\xe3\x81\x82".should.start_with?("\xe3") + ruby_version_is ""..."3.3" do + it "does not check we are matching only part of a character" do + "\xe3\x81\x82".size.should == 1 + "\xe3\x81\x82".should.start_with?("\xe3") + end + end + + ruby_version_is "3.3" do # #19784 + it "checks we are matching only part of a character" do + "\xe3\x81\x82".size.should == 1 + "\xe3\x81\x82".should_not.start_with?("\xe3") + end end end diff --git a/spec/ruby/core/string/strip_spec.rb b/spec/ruby/core/string/strip_spec.rb index 5e90fe35d0..edb6ea3b44 100644 --- a/spec/ruby/core/string/strip_spec.rb +++ b/spec/ruby/core/string/strip_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative 'shared/strip' diff --git a/spec/ruby/core/string/sub_spec.rb b/spec/ruby/core/string/sub_spec.rb index 51920486f5..6ff28ec851 100644 --- a/spec/ruby/core/string/sub_spec.rb +++ b/spec/ruby/core/string/sub_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' @@ -231,10 +232,10 @@ describe "String#sub with pattern and block" do offsets = [] str.sub(/([aeiou])/) do - md = $~ - md.string.should == str - offsets << md.offset(0) - str + md = $~ + md.string.should == str + offsets << md.offset(0) + str end.should == "hhellollo" offsets.should == [[1, 2]] @@ -338,10 +339,10 @@ describe "String#sub! with pattern and block" do offsets = [] str.dup.sub!(/([aeiou])/) do - md = $~ - md.string.should == str - offsets << md.offset(0) - str + md = $~ + md.string.should == str + offsets << md.offset(0) + str end.should == "hhellollo" offsets.should == [[1, 2]] diff --git a/spec/ruby/core/string/swapcase_spec.rb b/spec/ruby/core/string/swapcase_spec.rb index d740fb86c6..011a213501 100644 --- a/spec/ruby/core/string/swapcase_spec.rb +++ b/spec/ruby/core/string/swapcase_spec.rb @@ -1,12 +1,13 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "String#swapcase" do it "returns a new string with all uppercase chars from self converted to lowercase and vice versa" do - "Hello".swapcase.should == "hELLO" - "cYbEr_PuNk11".swapcase.should == "CyBeR_pUnK11" - "+++---111222???".swapcase.should == "+++---111222???" + "Hello".swapcase.should == "hELLO" + "cYbEr_PuNk11".swapcase.should == "CyBeR_pUnK11" + "+++---111222???".swapcase.should == "+++---111222???" end it "returns a String in the same encoding as self" do diff --git a/spec/ruby/core/string/to_c_spec.rb b/spec/ruby/core/string/to_c_spec.rb index 9d24f1f56c..1813890e72 100644 --- a/spec/ruby/core/string/to_c_spec.rb +++ b/spec/ruby/core/string/to_c_spec.rb @@ -13,18 +13,20 @@ describe "String#to_c" do it "ignores trailing garbage" do '79+4iruby'.to_c.should == Complex(79, 4) - ruby_bug "[Bug #19087]", ""..."3.2" do - '7__9+4__0i'.to_c.should == Complex(7, 0) - end + '7__9+4__0i'.to_c.should == Complex(7, 0) end - it "understands Float::INFINITY" do - 'Infinity'.to_c.should == Complex(0, 1) - '-Infinity'.to_c.should == Complex(0, -1) - end + context "it treats special float value strings as characters" do + it "parses any string that starts with 'I' as 1i" do + 'Infinity'.to_c.should == Complex(0, 1) + '-Infinity'.to_c.should == Complex(0, -1) + 'Insecure'.to_c.should == Complex(0, 1) + '-Insecure'.to_c.should == Complex(0, -1) + end - it "understands Float::NAN" do - 'NaN'.to_c.should == Complex(0, 0) + it "does not parse any numeric information in 'NaN'" do + 'NaN'.to_c.should == Complex(0, 0) + end end it "allows null-byte" do @@ -39,15 +41,13 @@ describe "String#to_c" do }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") end - ruby_version_is "3.2" do - it "treats a sequence of underscores as an end of Complex string" do - "5+3_1i".to_c.should == Complex(5, 31) - "5+3__1i".to_c.should == Complex(5) - "5+3___1i".to_c.should == Complex(5) + it "treats a sequence of underscores as an end of Complex string" do + "5+3_1i".to_c.should == Complex(5, 31) + "5+3__1i".to_c.should == Complex(5) + "5+3___1i".to_c.should == Complex(5) - "12_3".to_c.should == Complex(123) - "12__3".to_c.should == Complex(12) - "12___3".to_c.should == Complex(12) - end + "12_3".to_c.should == Complex(123) + "12__3".to_c.should == Complex(12) + "12___3".to_c.should == Complex(12) end end diff --git a/spec/ruby/core/string/to_f_spec.rb b/spec/ruby/core/string/to_f_spec.rb index cf64ecfc5d..abfd2517b6 100644 --- a/spec/ruby/core/string/to_f_spec.rb +++ b/spec/ruby/core/string/to_f_spec.rb @@ -5,16 +5,15 @@ require_relative 'fixtures/classes' describe "String#to_f" do it "treats leading characters of self as a floating point number" do - "123.45e1".to_f.should == 1234.5 - "45.67 degrees".to_f.should == 45.67 - "0".to_f.should == 0.0 - "123.45e1".to_f.should == 1234.5 + "123.45e1".to_f.should == 1234.5 + "45.67 degrees".to_f.should == 45.67 + "0".to_f.should == 0.0 - ".5".to_f.should == 0.5 - ".5e1".to_f.should == 5.0 - "5.".to_f.should == 5.0 - "5e".to_f.should == 5.0 - "5E".to_f.should == 5.0 + ".5".to_f.should == 0.5 + ".5e1".to_f.should == 5.0 + "5.".to_f.should == 5.0 + "5e".to_f.should == 5.0 + "5E".to_f.should == 5.0 end it "treats special float value strings as characters" do @@ -43,18 +42,39 @@ describe "String#to_f" do "1_234_567.890_1".to_f.should == 1_234_567.890_1 end - it "returns 0 for strings with any non-digit in them" do - "blah".to_f.should == 0 - "0b5".to_f.should == 0 - "0d5".to_f.should == 0 - "0o5".to_f.should == 0 - "0xx5".to_f.should == 0 - end - it "returns 0 for strings with leading underscores" do "_9".to_f.should == 0 end + it "stops if the underscore is not followed or preceded by a number" do + "1__2".to_f.should == 1.0 + "1_.2".to_f.should == 1.0 + "1._2".to_f.should == 1.0 + "1.2_e2".to_f.should == 1.2 + "1.2e_2".to_f.should == 1.2 + "1_x2".to_f.should == 1.0 + "1x_2".to_f.should == 1.0 + "+_1".to_f.should == 0.0 + "-_1".to_f.should == 0.0 + end + + it "does not allow prefixes to autodetect the base" do + "0b10".to_f.should == 0 + "010".to_f.should == 10 + "0o10".to_f.should == 0 + "0d10".to_f.should == 0 + "0x10".to_f.should == 0 + end + + it "treats any non-numeric character other than '.', 'e' and '_' as terminals" do + "blah".to_f.should == 0 + "1b5".to_f.should == 1 + "1d5".to_f.should == 1 + "1o5".to_f.should == 1 + "1xx5".to_f.should == 1 + "x5".to_f.should == 0 + end + it "takes an optional sign" do "-45.67 degrees".to_f.should == -45.67 "+45.67 degrees".to_f.should == 45.67 @@ -63,8 +83,60 @@ describe "String#to_f" do (1.0 / "-0".to_f).to_s.should == "-Infinity" end + it "treats a second 'e' as terminal" do + "1.234e1e2".to_f.should == 1.234e1 + end + + it "treats a second '.' as terminal" do + "1.2.3".to_f.should == 1.2 + end + + it "treats a '.' after an 'e' as terminal" do + "1.234e1.9".to_f.should == 1.234e1 + end + it "returns 0.0 if the conversion fails" do "bad".to_f.should == 0.0 "thx1138".to_f.should == 0.0 end + + it "ignores leading and trailing whitespace" do + " 1.2".to_f.should == 1.2 + "1.2 ".to_f.should == 1.2 + " 1.2 ".to_f.should == 1.2 + "\t1.2".to_f.should == 1.2 + "\n1.2".to_f.should == 1.2 + "\v1.2".to_f.should == 1.2 + "\f1.2".to_f.should == 1.2 + "\r1.2".to_f.should == 1.2 + end + + it "treats non-printable ASCII characters as terminals" do + "\0001.2".to_f.should == 0 + "\0011.2".to_f.should == 0 + "\0371.2".to_f.should == 0 + "\1771.2".to_f.should == 0 + "\2001.2".b.to_f.should == 0 + "\3771.2".b.to_f.should == 0 + end + + ruby_version_is "3.2.3" do + it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do + -> { + '1.2'.encode("UTF-16").to_f + }.should raise_error(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16") + end + end + + it "allows String representation without a fractional part" do + "1.".to_f.should == 1.0 + "+1.".to_f.should == 1.0 + "-1.".to_f.should == -1.0 + "1.e+0".to_f.should == 1.0 + "1.e+0".to_f.should == 1.0 + + ruby_bug "#20705", ""..."3.4" do + "1.e-2".to_f.should be_close(0.01, TOLERANCE) + end + end end diff --git a/spec/ruby/core/string/to_i_spec.rb b/spec/ruby/core/string/to_i_spec.rb index e4fa89aab3..39f69acda3 100644 --- a/spec/ruby/core/string/to_i_spec.rb +++ b/spec/ruby/core/string/to_i_spec.rb @@ -10,6 +10,18 @@ describe "String#to_i" do "1_2_3asdf".to_i.should == 123 end + it "ignores multiple non-consecutive underscores when the first digit is 0" do + (2..16).each do |base| + "0_0_010".to_i(base).should == base; + end + end + + it "bails out at the first double underscore if the first digit is 0" do + (2..16).each do |base| + "010__1".to_i(base).should == base; + end + end + it "ignores leading whitespaces" do [ " 123", " 123", "\r\n\r\n123", "\t\t123", "\r\n\t\n123", " \t\n\r\t 123"].each do |str| diff --git a/spec/ruby/core/string/to_r_spec.rb b/spec/ruby/core/string/to_r_spec.rb index 7e1d635d3b..4ffbb10d98 100644 --- a/spec/ruby/core/string/to_r_spec.rb +++ b/spec/ruby/core/string/to_r_spec.rb @@ -33,6 +33,10 @@ describe "String#to_r" do "-20".to_r.should == Rational(-20, 1) end + it "accepts leading plus signs" do + "+20".to_r.should == Rational(20, 1) + end + it "does not treat a leading period without a numeric prefix as a decimal point" do ".9".to_r.should_not == Rational(8106479329266893, 9007199254740992) end diff --git a/spec/ruby/core/string/tr_s_spec.rb b/spec/ruby/core/string/tr_s_spec.rb index 3c31473044..dd72da440c 100644 --- a/spec/ruby/core/string/tr_s_spec.rb +++ b/spec/ruby/core/string/tr_s_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/tr_spec.rb b/spec/ruby/core/string/tr_spec.rb index d60480dc7e..75841a974f 100644 --- a/spec/ruby/core/string/tr_spec.rb +++ b/spec/ruby/core/string/tr_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/unicode_normalize_spec.rb b/spec/ruby/core/string/unicode_normalize_spec.rb index 6de7533fc7..2e7d22394a 100644 --- a/spec/ruby/core/string/unicode_normalize_spec.rb +++ b/spec/ruby/core/string/unicode_normalize_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' # Examples taken from http://www.unicode.org/reports/tr15/#Norm_Forms diff --git a/spec/ruby/core/string/unicode_normalized_spec.rb b/spec/ruby/core/string/unicode_normalized_spec.rb index 87f3740459..91cf2086b2 100644 --- a/spec/ruby/core/string/unicode_normalized_spec.rb +++ b/spec/ruby/core/string/unicode_normalized_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' describe "String#unicode_normalized?" do diff --git a/spec/ruby/core/string/unpack/a_spec.rb b/spec/ruby/core/string/unpack/a_spec.rb index 2d83b4c824..a68e842e15 100644 --- a/spec/ruby/core/string/unpack/a_spec.rb +++ b/spec/ruby/core/string/unpack/a_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' @@ -31,7 +31,7 @@ describe "String#unpack with format 'A'" do end it "decodes into raw (ascii) string values" do - str = "str".force_encoding('UTF-8').unpack("A*")[0] + str = "str".dup.force_encoding('UTF-8').unpack("A*")[0] str.encoding.should == Encoding::BINARY end diff --git a/spec/ruby/core/string/unpack/at_spec.rb b/spec/ruby/core/string/unpack/at_spec.rb index 70b2389d69..d4133c23ee 100644 --- a/spec/ruby/core/string/unpack/at_spec.rb +++ b/spec/ruby/core/string/unpack/at_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb index 5c53eff721..b088f901fc 100644 --- a/spec/ruby/core/string/unpack/b_spec.rb +++ b/spec/ruby/core/string/unpack/b_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' @@ -107,7 +107,7 @@ describe "String#unpack with format 'B'" do end it "decodes into US-ASCII string values" do - str = "s".force_encoding('UTF-8').unpack("B*")[0] + str = "s".dup.force_encoding('UTF-8').unpack("B*")[0] str.encoding.name.should == 'US-ASCII' end end @@ -215,7 +215,7 @@ describe "String#unpack with format 'b'" do end it "decodes into US-ASCII string values" do - str = "s".force_encoding('UTF-8').unpack("b*")[0] + str = "s".dup.force_encoding('UTF-8').unpack("b*")[0] str.encoding.name.should == 'US-ASCII' end end diff --git a/spec/ruby/core/string/unpack/c_spec.rb b/spec/ruby/core/string/unpack/c_spec.rb index c2bf813954..1e9548fb82 100644 --- a/spec/ruby/core/string/unpack/c_spec.rb +++ b/spec/ruby/core/string/unpack/c_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/comment_spec.rb b/spec/ruby/core/string/unpack/comment_spec.rb index e18a53df3c..050d2b7fc0 100644 --- a/spec/ruby/core/string/unpack/comment_spec.rb +++ b/spec/ruby/core/string/unpack/comment_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' diff --git a/spec/ruby/core/string/unpack/h_spec.rb b/spec/ruby/core/string/unpack/h_spec.rb index 19c4d63664..535836087d 100644 --- a/spec/ruby/core/string/unpack/h_spec.rb +++ b/spec/ruby/core/string/unpack/h_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/l_spec.rb b/spec/ruby/core/string/unpack/l_spec.rb index 18bb68b8d0..0adb567eca 100644 --- a/spec/ruby/core/string/unpack/l_spec.rb +++ b/spec/ruby/core/string/unpack/l_spec.rb @@ -14,7 +14,7 @@ describe "String#unpack with format 'L'" do it_behaves_like :string_unpack_32bit_be_unsigned, 'L>' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "with modifier '<' and '_'" do it_behaves_like :string_unpack_32bit_le, 'L<_' it_behaves_like :string_unpack_32bit_le, 'L_<' @@ -44,7 +44,7 @@ describe "String#unpack with format 'L'" do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "with modifier '<' and '_'" do it_behaves_like :string_unpack_64bit_le, 'L<_' it_behaves_like :string_unpack_64bit_le, 'L_<' @@ -86,7 +86,7 @@ describe "String#unpack with format 'l'" do it_behaves_like :string_unpack_32bit_be_signed, 'l>' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "with modifier '<' and '_'" do it_behaves_like :string_unpack_32bit_le, 'l<_' it_behaves_like :string_unpack_32bit_le, 'l_<' @@ -116,7 +116,7 @@ describe "String#unpack with format 'l'" do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "with modifier '<' and '_'" do it_behaves_like :string_unpack_64bit_le, 'l<_' it_behaves_like :string_unpack_64bit_le, 'l_<' @@ -160,7 +160,7 @@ little_endian do it_behaves_like :string_unpack_32bit_le_signed, 'l' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "String#unpack with format 'L' with modifier '_'" do it_behaves_like :string_unpack_32bit_le, 'L_' it_behaves_like :string_unpack_32bit_le_unsigned, 'L_' @@ -182,7 +182,7 @@ little_endian do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "String#unpack with format 'L' with modifier '_'" do it_behaves_like :string_unpack_64bit_le, 'L_' it_behaves_like :string_unpack_64bit_le_unsigned, 'L_' @@ -218,7 +218,7 @@ big_endian do it_behaves_like :string_unpack_32bit_be_signed, 'l' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "String#unpack with format 'L' with modifier '_'" do it_behaves_like :string_unpack_32bit_be, 'L_' it_behaves_like :string_unpack_32bit_be_unsigned, 'L_' @@ -240,7 +240,7 @@ big_endian do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "String#unpack with format 'L' with modifier '_'" do it_behaves_like :string_unpack_64bit_be, 'L_' it_behaves_like :string_unpack_64bit_be_unsigned, 'L_' diff --git a/spec/ruby/core/string/unpack/m_spec.rb b/spec/ruby/core/string/unpack/m_spec.rb index c551c755d1..357987a053 100644 --- a/spec/ruby/core/string/unpack/m_spec.rb +++ b/spec/ruby/core/string/unpack/m_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/shared/basic.rb b/spec/ruby/core/string/unpack/shared/basic.rb index bb5302edc5..734630bda0 100644 --- a/spec/ruby/core/string/unpack/shared/basic.rb +++ b/spec/ruby/core/string/unpack/shared/basic.rb @@ -8,6 +8,22 @@ describe :string_unpack_basic, shared: true do d.should_receive(:to_str).and_return("a"+unpack_format) "abc".unpack(d).should be_an_instance_of(Array) end + + ruby_version_is ""..."3.3" do + it "warns about using an unknown directive" do + -> { "abcdefgh".unpack("a R" + unpack_format) }.should complain(/unknown unpack directive 'R' in 'a R#{unpack_format}'/) + -> { "abcdefgh".unpack("a 0" + unpack_format) }.should complain(/unknown unpack directive '0' in 'a 0#{unpack_format}'/) + -> { "abcdefgh".unpack("a :" + unpack_format) }.should complain(/unknown unpack directive ':' in 'a :#{unpack_format}'/) + end + end + + ruby_version_is "3.3" do + it "raises ArgumentError when a directive is unknown" do + -> { "abcdefgh".unpack("a K" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive 'K' in 'a K#{unpack_format}'") + -> { "abcdefgh".unpack("a 0" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive '0' in 'a 0#{unpack_format}'") + -> { "abcdefgh".unpack("a :" + unpack_format) }.should raise_error(ArgumentError, "unknown unpack directive ':' in 'a :#{unpack_format}'") + end + end end describe :string_unpack_no_platform, shared: true do diff --git a/spec/ruby/core/string/unpack/shared/float.rb b/spec/ruby/core/string/unpack/shared/float.rb index 93282bf4c9..b31c2c8bdc 100644 --- a/spec/ruby/core/string/unpack/shared/float.rb +++ b/spec/ruby/core/string/unpack/shared/float.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_unpack_float_le, shared: true do it "decodes one float for a single format character" do diff --git a/spec/ruby/core/string/unpack/shared/integer.rb b/spec/ruby/core/string/unpack/shared/integer.rb index d71a2cf00d..d3934753ba 100644 --- a/spec/ruby/core/string/unpack/shared/integer.rb +++ b/spec/ruby/core/string/unpack/shared/integer.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_unpack_16bit_le, shared: true do it "decodes one short for a single format character" do diff --git a/spec/ruby/core/string/unpack/u_spec.rb b/spec/ruby/core/string/unpack/u_spec.rb index 7845e6d5f2..68c8f6f11c 100644 --- a/spec/ruby/core/string/unpack/u_spec.rb +++ b/spec/ruby/core/string/unpack/u_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' @@ -33,7 +33,7 @@ describe "String#unpack with format 'u'" do str = "".unpack("u")[0] str.encoding.should == Encoding::BINARY - str = "1".force_encoding('UTF-8').unpack("u")[0] + str = "1".dup.force_encoding('UTF-8').unpack("u")[0] str.encoding.should == Encoding::BINARY end diff --git a/spec/ruby/core/string/unpack/w_spec.rb b/spec/ruby/core/string/unpack/w_spec.rb index 6a1cff1965..7d3533ccae 100644 --- a/spec/ruby/core/string/unpack/w_spec.rb +++ b/spec/ruby/core/string/unpack/w_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/x_spec.rb b/spec/ruby/core/string/unpack/x_spec.rb index 5e248de77e..2926ebbe0f 100644 --- a/spec/ruby/core/string/unpack/x_spec.rb +++ b/spec/ruby/core/string/unpack/x_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack/z_spec.rb b/spec/ruby/core/string/unpack/z_spec.rb index ce8da4b29e..1030390550 100644 --- a/spec/ruby/core/string/unpack/z_spec.rb +++ b/spec/ruby/core/string/unpack/z_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' diff --git a/spec/ruby/core/string/unpack1_spec.rb b/spec/ruby/core/string/unpack1_spec.rb index df830916a3..cfb47fe695 100644 --- a/spec/ruby/core/string/unpack1_spec.rb +++ b/spec/ruby/core/string/unpack1_spec.rb @@ -8,29 +8,40 @@ describe "String#unpack1" do "A".unpack1("B*").should == "01000001" end - ruby_version_is "3.1" do - it "starts unpacking from the given offset" do - "ZZABCD".unpack1('x3C', offset: 2).should == "ABCD".unpack('x3C')[0] - "ZZZZaG9nZWZ1Z2E=".unpack1("m", offset: 4).should == "hogefuga" - "ZA".unpack1("B*", offset: 1).should == "01000001" - end + it "starts unpacking from the given offset" do + "ZZABCD".unpack1('x3C', offset: 2).should == "ABCD".unpack('x3C')[0] + "ZZZZaG9nZWZ1Z2E=".unpack1("m", offset: 4).should == "hogefuga" + "ZA".unpack1("B*", offset: 1).should == "01000001" + end - it "traits offset as a bytes offset" do - "؈".unpack("CC").should == [216, 136] - "؈".unpack1("C").should == 216 - "؈".unpack1("C", offset: 1).should == 136 - end + it "traits offset as a bytes offset" do + "؈".unpack("CC").should == [216, 136] + "؈".unpack1("C").should == 216 + "؈".unpack1("C", offset: 1).should == 136 + end - it "raises an ArgumentError when the offset is negative" do - -> { "a".unpack1("C", offset: -1) }.should raise_error(ArgumentError, "offset can't be negative") - end + it "raises an ArgumentError when the offset is negative" do + -> { "a".unpack1("C", offset: -1) }.should raise_error(ArgumentError, "offset can't be negative") + end + + it "returns nil if the offset is at the end of the string" do + "a".unpack1("C", offset: 1).should == nil + end + + it "raises an ArgumentError when the offset is larger than the string bytesize" do + -> { "a".unpack1("C", offset: 2) }.should raise_error(ArgumentError, "offset outside of string") + end + + context "with format 'm0'" do + # unpack1("m0") takes a special code path that calls Pack.unpackBase46Strict instead of Pack.unpack_m, + # which is why we repeat the tests for unpack("m0") here. - it "returns nil if the offset is at the end of the string" do - "a".unpack1("C", offset: 1).should == nil + it "decodes base64" do + "dGVzdA==".unpack1("m0").should == "test" end - it "raises an ArgumentError when the offset is larger than the string bytesize" do - -> { "a".unpack1("C", offset: 2) }.should raise_error(ArgumentError, "offset outside of string") + it "raises an ArgumentError for an invalid base64 character" do + -> { "dGV%zdA==".unpack1("m0") }.should raise_error(ArgumentError) end end end diff --git a/spec/ruby/core/string/unpack_spec.rb b/spec/ruby/core/string/unpack_spec.rb index 52b4af3a95..a0abf8fa99 100644 --- a/spec/ruby/core/string/unpack_spec.rb +++ b/spec/ruby/core/string/unpack_spec.rb @@ -9,26 +9,24 @@ describe "String#unpack" do -> { "abc".unpack(1) }.should raise_error(TypeError) end - ruby_version_is "3.1" do - it "starts unpacking from the given offset" do - "abc".unpack("CC", offset: 1).should == [98, 99] - end + it "starts unpacking from the given offset" do + "abc".unpack("CC", offset: 1).should == [98, 99] + end - it "traits offset as a bytes offset" do - "؈".unpack("CC").should == [216, 136] - "؈".unpack("CC", offset: 1).should == [136, nil] - end + it "traits offset as a bytes offset" do + "؈".unpack("CC").should == [216, 136] + "؈".unpack("CC", offset: 1).should == [136, nil] + end - it "raises an ArgumentError when the offset is negative" do - -> { "a".unpack("C", offset: -1) }.should raise_error(ArgumentError, "offset can't be negative") - end + it "raises an ArgumentError when the offset is negative" do + -> { "a".unpack("C", offset: -1) }.should raise_error(ArgumentError, "offset can't be negative") + end - it "returns nil if the offset is at the end of the string" do - "a".unpack("C", offset: 1).should == [nil] - end + it "returns nil if the offset is at the end of the string" do + "a".unpack("C", offset: 1).should == [nil] + end - it "raises an ArgumentError when the offset is larget than the string" do - -> { "a".unpack("C", offset: 2) }.should raise_error(ArgumentError, "offset outside of string") - end + it "raises an ArgumentError when the offset is larger than the string" do + -> { "a".unpack("C", offset: 2) }.should raise_error(ArgumentError, "offset outside of string") end end diff --git a/spec/ruby/core/string/upcase_spec.rb b/spec/ruby/core/string/upcase_spec.rb index a2e34f5f40..652de5c2ef 100644 --- a/spec/ruby/core/string/upcase_spec.rb +++ b/spec/ruby/core/string/upcase_spec.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/uplus_spec.rb b/spec/ruby/core/string/uplus_spec.rb index 038b283c90..20767bcc01 100644 --- a/spec/ruby/core/string/uplus_spec.rb +++ b/spec/ruby/core/string/uplus_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require_relative '../../spec_helper' describe 'String#+@' do @@ -7,16 +8,53 @@ describe 'String#+@' do output.should_not.frozen? output.should == 'foo' + + output << 'bar' + output.should == 'foobar' end - it 'returns self if the String is not frozen' do - input = 'foo' + it 'returns a mutable String itself' do + input = String.new("foo") output = +input - output.equal?(input).should == true + output.should.equal?(input) + + input << "bar" + output.should == "foobar" + end + + context 'if file has "frozen_string_literal: true" magic comment' do + it 'returns mutable copy of a literal' do + ruby_exe(fixture(__FILE__, "freeze_magic_comment.rb")).should == 'mutable' + end end - it 'returns mutable copy despite freeze-magic-comment in file' do - ruby_exe(fixture(__FILE__, "freeze_magic_comment.rb")).should == 'mutable' + context 'if file has "frozen_string_literal: false" magic comment' do + it 'returns literal string itself' do + input = 'foo' + output = +input + + output.equal?(input).should == true + end + end + + context 'if file has no frozen_string_literal magic comment' do + ruby_version_is ''...'3.4' do + it 'returns literal string itself' do + eval(<<~RUBY).should == true + s = "foo" + s.equal?(+s) + RUBY + end + end + + ruby_version_is '3.4' do + it 'returns mutable copy of a literal' do + eval(<<~RUBY).should == false + s = "foo" + s.equal?(+s) + RUBY + end + end end end diff --git a/spec/ruby/core/string/upto_spec.rb b/spec/ruby/core/string/upto_spec.rb index 3799e338e0..8bc847d5ac 100644 --- a/spec/ruby/core/string/upto_spec.rb +++ b/spec/ruby/core/string/upto_spec.rb @@ -81,8 +81,8 @@ describe "String#upto" do end it "raises Encoding::CompatibilityError when incompatible characters are given" do - char1 = 'a'.force_encoding("EUC-JP") - char2 = 'b'.force_encoding("ISO-2022-JP") + char1 = 'a'.dup.force_encoding("EUC-JP") + char2 = 'b'.dup.force_encoding("ISO-2022-JP") -> { char1.upto(char2) {} }.should raise_error(Encoding::CompatibilityError, "incompatible character encodings: EUC-JP and ISO-2022-JP") end diff --git a/spec/ruby/core/string/valid_encoding_spec.rb b/spec/ruby/core/string/valid_encoding_spec.rb index bb26062c0f..375035cd94 100644 --- a/spec/ruby/core/string/valid_encoding_spec.rb +++ b/spec/ruby/core/string/valid_encoding_spec.rb @@ -7,13 +7,13 @@ describe "String#valid_encoding?" do end it "returns true if self is valid in the current encoding and other encodings" do - str = "\x77" + str = +"\x77" str.force_encoding('utf-8').valid_encoding?.should be_true str.force_encoding('binary').valid_encoding?.should be_true end it "returns true for all encodings self is valid in" do - str = "\xE6\x9D\x94" + str = +"\xE6\x9D\x94" str.force_encoding('BINARY').valid_encoding?.should be_true str.force_encoding('UTF-8').valid_encoding?.should be_true str.force_encoding('US-ASCII').valid_encoding?.should be_false @@ -43,10 +43,10 @@ describe "String#valid_encoding?" do str.force_encoding('KOI8-R').valid_encoding?.should be_true str.force_encoding('KOI8-U').valid_encoding?.should be_true str.force_encoding('Shift_JIS').valid_encoding?.should be_false - "\xD8\x00".force_encoding('UTF-16BE').valid_encoding?.should be_false - "\x00\xD8".force_encoding('UTF-16LE').valid_encoding?.should be_false - "\x04\x03\x02\x01".force_encoding('UTF-32BE').valid_encoding?.should be_false - "\x01\x02\x03\x04".force_encoding('UTF-32LE').valid_encoding?.should be_false + "\xD8\x00".dup.force_encoding('UTF-16BE').valid_encoding?.should be_false + "\x00\xD8".dup.force_encoding('UTF-16LE').valid_encoding?.should be_false + "\x04\x03\x02\x01".dup.force_encoding('UTF-32BE').valid_encoding?.should be_false + "\x01\x02\x03\x04".dup.force_encoding('UTF-32LE').valid_encoding?.should be_false str.force_encoding('Windows-1251').valid_encoding?.should be_true str.force_encoding('IBM437').valid_encoding?.should be_true str.force_encoding('IBM737').valid_encoding?.should be_true @@ -101,24 +101,24 @@ describe "String#valid_encoding?" do end it "returns true for IBM720 encoding self is valid in" do - str = "\xE6\x9D\x94" + str = +"\xE6\x9D\x94" str.force_encoding('IBM720').valid_encoding?.should be_true str.force_encoding('CP720').valid_encoding?.should be_true end it "returns false if self is valid in one encoding, but invalid in the one it's tagged with" do - str = "\u{8765}" + str = +"\u{8765}" str.valid_encoding?.should be_true - str = str.force_encoding('ascii') + str.force_encoding('ascii') str.valid_encoding?.should be_false end it "returns false if self contains a character invalid in the associated encoding" do - "abc#{[0x80].pack('C')}".force_encoding('ascii').valid_encoding?.should be_false + "abc#{[0x80].pack('C')}".dup.force_encoding('ascii').valid_encoding?.should be_false end it "returns false if a valid String had an invalid character appended to it" do - str = "a" + str = +"a" str.valid_encoding?.should be_true str << [0xDD].pack('C').force_encoding('utf-8') str.valid_encoding?.should be_false diff --git a/spec/ruby/core/struct/constants_spec.rb b/spec/ruby/core/struct/constants_spec.rb new file mode 100644 index 0000000000..7e8af1a211 --- /dev/null +++ b/spec/ruby/core/struct/constants_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' + +describe "Struct::Group" do + it "is no longer defined" do + Struct.should_not.const_defined?(:Group) + end +end + +describe "Struct::Passwd" do + it "is no longer defined" do + Struct.should_not.const_defined?(:Passwd) + end +end diff --git a/spec/ruby/core/struct/deconstruct_keys_spec.rb b/spec/ruby/core/struct/deconstruct_keys_spec.rb index b4c84c49df..e16b50f930 100644 --- a/spec/ruby/core/struct/deconstruct_keys_spec.rb +++ b/spec/ruby/core/struct/deconstruct_keys_spec.rb @@ -40,6 +40,21 @@ describe "Struct#deconstruct_keys" do s.deconstruct_keys([0, 1, 2]).should == {0 => 10, 1 => 20, 2 => 30} s.deconstruct_keys([0, 1] ).should == {0 => 10, 1 => 20} s.deconstruct_keys([0] ).should == {0 => 10} + s.deconstruct_keys([-1] ).should == {-1 => 30} + end + + it "ignores incorrect position numbers" do + struct = Struct.new(:x, :y, :z) + s = struct.new(10, 20, 30) + + s.deconstruct_keys([0, 3]).should == {0 => 10} + end + + it "support mixing attribute names and argument position numbers" do + struct = Struct.new(:x, :y) + s = struct.new(1, 2) + + s.deconstruct_keys([0, :x]).should == {0 => 1, :x => 1} end it "returns an empty hash when there are more keys than attributes" do @@ -57,6 +72,14 @@ describe "Struct#deconstruct_keys" do s.deconstruct_keys([:x, :a]).should == {x: 1} end + it "returns at first not existing argument position number" do + struct = Struct.new(:x, :y) + s = struct.new(1, 2) + + s.deconstruct_keys([3, 0]).should == {} + s.deconstruct_keys([0, 3]).should == {0 => 1} + end + it "accepts nil argument and return all the attributes" do struct = Struct.new(:x, :y) obj = struct.new(1, 2) @@ -64,6 +87,37 @@ describe "Struct#deconstruct_keys" do obj.deconstruct_keys(nil).should == {x: 1, y: 2} end + it "tries to convert a key with #to_int if index is not a String nor a Symbol, but responds to #to_int" do + struct = Struct.new(:x, :y) + s = struct.new(1, 2) + + key = mock("to_int") + key.should_receive(:to_int).and_return(1) + + s.deconstruct_keys([key]).should == { key => 2 } + end + + it "raises a TypeError if the conversion with #to_int does not return an Integer" do + struct = Struct.new(:x, :y) + s = struct.new(1, 2) + + key = mock("to_int") + key.should_receive(:to_int).and_return("not an Integer") + + -> { + s.deconstruct_keys([key]) + }.should raise_error(TypeError, /can't convert MockObject to Integer/) + end + + it "raises TypeError if index is not a String, a Symbol and not convertible to Integer" do + struct = Struct.new(:x, :y) + s = struct.new(1, 2) + + -> { + s.deconstruct_keys([0, []]) + }.should raise_error(TypeError, "no implicit conversion of Array into Integer") + end + it "raise TypeError if passed anything except nil or array" do struct = Struct.new(:x, :y) s = struct.new(1, 2) diff --git a/spec/ruby/core/struct/element_set_spec.rb b/spec/ruby/core/struct/element_set_spec.rb index 6ba7b081a9..0a0e34a5ee 100644 --- a/spec/ruby/core/struct/element_set_spec.rb +++ b/spec/ruby/core/struct/element_set_spec.rb @@ -26,4 +26,11 @@ describe "Struct#[]=" do -> { car[-4] = true }.should raise_error(IndexError) -> { car[Object.new] = true }.should raise_error(TypeError) end + + it "raises a FrozenError on a frozen struct" do + car = StructClasses::Car.new('Ford', 'Ranger') + car.freeze + + -> { car[:model] = 'Escape' }.should raise_error(FrozenError) + end end diff --git a/spec/ruby/core/struct/fixtures/classes.rb b/spec/ruby/core/struct/fixtures/classes.rb index bf838d05df..7b80b814ef 100644 --- a/spec/ruby/core/struct/fixtures/classes.rb +++ b/spec/ruby/core/struct/fixtures/classes.rb @@ -29,4 +29,6 @@ module StructClasses super end end + + class StructSubclass < Struct; end end diff --git a/spec/ruby/core/struct/initialize_spec.rb b/spec/ruby/core/struct/initialize_spec.rb index a5ebe9551c..06055594d5 100644 --- a/spec/ruby/core/struct/initialize_spec.rb +++ b/spec/ruby/core/struct/initialize_spec.rb @@ -41,21 +41,11 @@ describe "Struct#initialize" do StructClasses::SubclassX.new(:y).new.key.should == :value end - ruby_version_is "3.1"..."3.2" do - it "warns about passing only keyword arguments" do - -> { - StructClasses::Ruby.new(version: "3.1", platform: "OS") - }.should complain(/warning: Passing only keyword arguments/) - end - end + it "can be initialized with keyword arguments" do + positional_args = StructClasses::Ruby.new("3.2", "OS") + keyword_args = StructClasses::Ruby.new(version: "3.2", platform: "OS") - ruby_version_is "3.2" do - it "can be initialized with keyword arguments" do - positional_args = StructClasses::Ruby.new("3.2", "OS") - keyword_args = StructClasses::Ruby.new(version: "3.2", platform: "OS") - - positional_args.version.should == keyword_args.version - positional_args.platform.should == keyword_args.platform - end + positional_args.version.should == keyword_args.version + positional_args.platform.should == keyword_args.platform end end diff --git a/spec/ruby/core/struct/keyword_init_spec.rb b/spec/ruby/core/struct/keyword_init_spec.rb index 8de4c14351..536b82041a 100644 --- a/spec/ruby/core/struct/keyword_init_spec.rb +++ b/spec/ruby/core/struct/keyword_init_spec.rb @@ -1,40 +1,45 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' -ruby_version_is "3.1" do - # See https://bugs.ruby-lang.org/issues/18008 - describe "StructClass#keyword_init?" do - it "returns true for a struct that accepts keyword arguments to initialize" do - struct = Struct.new(:arg, keyword_init: true) - struct.keyword_init?.should be_true - end +# See https://bugs.ruby-lang.org/issues/18008 +describe "StructClass#keyword_init?" do + it "returns true for a struct that accepts keyword arguments to initialize" do + struct = Struct.new(:arg, keyword_init: true) + struct.keyword_init?.should be_true + end - it "returns false for a struct that does not accept keyword arguments to initialize" do - struct = Struct.new(:arg, keyword_init: false) - struct.keyword_init?.should be_false - end + it "returns false for a struct that does not accept keyword arguments to initialize" do + struct = Struct.new(:arg, keyword_init: false) + struct.keyword_init?.should be_false + end - it "returns nil for a struct that did not explicitly specify keyword_init" do - struct = Struct.new(:arg) - struct.keyword_init?.should be_nil - end + it "returns nil for a struct that did not explicitly specify keyword_init" do + struct = Struct.new(:arg) + struct.keyword_init?.should be_nil + end - it "returns nil for a struct that does specify keyword_init to be nil" do - struct = Struct.new(:arg, keyword_init: nil) - struct.keyword_init?.should be_nil - end + it "returns nil for a struct that does specify keyword_init to be nil" do + struct = Struct.new(:arg, keyword_init: nil) + struct.keyword_init?.should be_nil + end - it "returns true for any truthy value, not just for true" do - struct = Struct.new(:arg, keyword_init: 1) - struct.keyword_init?.should be_true + it "returns true for any truthy value, not just for true" do + struct = Struct.new(:arg, keyword_init: 1) + struct.keyword_init?.should be_true - struct = Struct.new(:arg, keyword_init: "") - struct.keyword_init?.should be_true + struct = Struct.new(:arg, keyword_init: "") + struct.keyword_init?.should be_true - struct = Struct.new(:arg, keyword_init: []) - struct.keyword_init?.should be_true + struct = Struct.new(:arg, keyword_init: []) + struct.keyword_init?.should be_true + + struct = Struct.new(:arg, keyword_init: {}) + struct.keyword_init?.should be_true + end - struct = Struct.new(:arg, keyword_init: {}) - struct.keyword_init?.should be_true + context "class inheriting Struct" do + it "isn't available in a subclass" do + StructClasses::StructSubclass.should_not.respond_to?(:keyword_init?) end end end diff --git a/spec/ruby/core/struct/members_spec.rb b/spec/ruby/core/struct/members_spec.rb index 1f2ff950d9..1ff7b9387a 100644 --- a/spec/ruby/core/struct/members_spec.rb +++ b/spec/ruby/core/struct/members_spec.rb @@ -11,3 +11,15 @@ describe "Struct#members" do it_behaves_like :struct_accessor, :members end + +describe "StructClass#members" do + it "returns an array of attribute names" do + StructClasses::Car.members.should == [:make, :model, :year] + end + + context "class inheriting Struct" do + it "isn't available in a subclass" do + StructClasses::StructSubclass.should_not.respond_to?(:members) + end + end +end diff --git a/spec/ruby/core/struct/new_spec.rb b/spec/ruby/core/struct/new_spec.rb index 8758051a81..1d35de7b87 100644 --- a/spec/ruby/core/struct/new_spec.rb +++ b/spec/ruby/core/struct/new_spec.rb @@ -6,6 +6,8 @@ describe "Struct.new" do struct = Struct.new('Animal', :name, :legs, :eyeballs) struct.should == Struct::Animal struct.name.should == "Struct::Animal" + ensure + Struct.send(:remove_const, :Animal) end it "overwrites previously defined constants with string as first argument" do @@ -19,6 +21,8 @@ describe "Struct.new" do second.should == Struct::Person first.members.should_not == second.members + ensure + Struct.send(:remove_const, :Person) end it "calls to_str on its first argument (constant name)" do @@ -27,6 +31,8 @@ describe "Struct.new" do struct = Struct.new(obj) struct.should == Struct::Foo struct.name.should == "Struct::Foo" + ensure + Struct.send(:remove_const, :Foo) end it "creates a new anonymous class with nil first argument" do @@ -48,7 +54,7 @@ describe "Struct.new" do end it "allows non-ASCII member name" do - name = "r\xe9sum\xe9".force_encoding(Encoding::ISO_8859_1).to_sym + name = "r\xe9sum\xe9".dup.force_encoding(Encoding::ISO_8859_1).to_sym struct = Struct.new(name) struct.new("foo").send(name).should == "foo" end @@ -67,20 +73,8 @@ describe "Struct.new" do -> { Struct.new(:animal, ['chris', 'evan']) }.should raise_error(TypeError) end - ruby_version_is ""..."3.2" do - it "raises a TypeError or ArgumentError if passed a Hash with an unknown key" do - # CRuby < 3.2 raises ArgumentError: unknown keyword: :name, but that seems a bug: - # https://bugs.ruby-lang.org/issues/18632 - -> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(StandardError) { |e| - [ArgumentError, TypeError].should.include?(e.class) - } - end - end - - ruby_version_is "3.2" do - it "raises a TypeError if passed a Hash with an unknown key" do - -> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError) - end + it "raises a TypeError if passed a Hash with an unknown key" do + -> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError) end ruby_version_is ""..."3.3" do @@ -138,6 +132,8 @@ describe "Struct.new" do it "creates a constant in subclass' namespace" do struct = StructClasses::Apple.new('Computer', :size) struct.should == StructClasses::Apple::Computer + ensure + StructClasses::Apple.send(:remove_const, :Computer) end it "creates an instance" do @@ -158,29 +154,43 @@ describe "Struct.new" do -> { StructClasses::Ruby.new('2.0', 'i686', true) }.should raise_error(ArgumentError) end - ruby_version_is ''...'3.1' do - it "passes a hash as a normal argument" do - type = Struct.new(:args) + it "accepts keyword arguments to initialize" do + type = Struct.new(:args) - obj = suppress_warning {type.new(keyword: :arg)} - obj2 = type.new(*[{keyword: :arg}]) + obj = type.new(args: 42) + obj2 = type.new(42) - obj.should == obj2 - obj.args.should == {keyword: :arg} - obj2.args.should == {keyword: :arg} - end + obj.should == obj2 + obj.args.should == 42 + obj2.args.should == 42 end - ruby_version_is '3.2' do - it "accepts keyword arguments to initialize" do - type = Struct.new(:args) + context "given positional and keyword arguments" do + it "treats keyword arguments as a positional parameter" do + type = Struct.new(:a, :b) + s = type.new("a", b: "b") + s.a.should == "a" + s.b.should == {b: "b"} + + type = Struct.new(:a, :b, :c) + s = type.new("a", b: "b", c: "c") + s.a.should == "a" + s.b.should == {b: "b", c: "c"} + s.c.should == nil + end + + it "ignores empty keyword arguments" do + type = Struct.new(:a, :b) + h = {} + s = type.new("a", **h) - obj = type.new(args: 42) - obj2 = type.new(42) + s.a.should == "a" + s.b.should == nil + end - obj.should == obj2 - obj.args.should == 42 - obj2.args.should == 42 + it "raises ArgumentError when all struct attribute values are specified" do + type = Struct.new(:a, :b) + -> { type.new("a", "b", c: "c") }.should raise_error(ArgumentError, "struct size differs") end end end diff --git a/spec/ruby/core/struct/struct_spec.rb b/spec/ruby/core/struct/struct_spec.rb index 8817dc1a58..1b6a4488ce 100644 --- a/spec/ruby/core/struct/struct_spec.rb +++ b/spec/ruby/core/struct/struct_spec.rb @@ -33,6 +33,13 @@ describe "Struct anonymous class instance methods" do car['model'].should == 'F150' car[1].should == 'F150' end + + it "writer methods raise a FrozenError on a frozen struct" do + car = StructClasses::Car.new('Ford', 'Ranger') + car.freeze + + -> { car.model = 'Escape' }.should raise_error(FrozenError) + end end describe "Struct subclasses" do diff --git a/spec/ruby/core/struct/to_h_spec.rb b/spec/ruby/core/struct/to_h_spec.rb index bfb0af07ba..861ce3f49d 100644 --- a/spec/ruby/core/struct/to_h_spec.rb +++ b/spec/ruby/core/struct/to_h_spec.rb @@ -21,6 +21,18 @@ describe "Struct#to_h" do h.should == { "make" => "ford", "model" => "ranger", "year" => "" } end + it "passes to a block each pair's key and value as separate arguments" do + s = StructClasses::Ruby.new('3.2.4', 'macos') + + ScratchPad.record [] + s.to_h { |k, v| ScratchPad << [k, v]; [k, v] } + ScratchPad.recorded.sort.should == [[:platform, 'macos'], [:version, '3.2.4']] + + ScratchPad.record [] + s.to_h { |*args| ScratchPad << args; [args[0], args[1]] } + ScratchPad.recorded.sort.should == [[:platform, 'macos'], [:version, '3.2.4']] + end + it "raises ArgumentError if block returns longer or shorter array" do -> do StructClasses::Car.new.to_h { |k, v| [k.to_s, "#{v}".downcase, 1] } diff --git a/spec/ruby/core/symbol/inspect_spec.rb b/spec/ruby/core/symbol/inspect_spec.rb index 6dbb36c2ad..df4566c48e 100644 --- a/spec/ruby/core/symbol/inspect_spec.rb +++ b/spec/ruby/core/symbol/inspect_spec.rb @@ -6,7 +6,7 @@ describe "Symbol#inspect" do :fred? => ":fred?", :fred! => ":fred!", :BAD! => ":BAD!", - :_BAD! => ":_BAD!", + :_BAD! => ":_BAD!", :$ruby => ":$ruby", :@ruby => ":@ruby", :@@ruby => ":@@ruby", @@ -66,9 +66,9 @@ describe "Symbol#inspect" do :~ => ":~", :| => ":|", - :"!" => [":\"!\"", ":!" ], - :"!=" => [":\"!=\"", ":!="], - :"!~" => [":\"!~\"", ":!~"], + :"!" => ":!", + :"!=" => ":!=", + :"!~" => ":!~", :"\$" => ":\"$\"", # for justice! :"&&" => ":\"&&\"", :"'" => ":\"\'\"", @@ -96,10 +96,15 @@ describe "Symbol#inspect" do :"foo " => ":\"foo \"", :" foo" => ":\" foo\"", :" " => ":\" \"", + + :"ê" => [":ê", ":\"\\u00EA\""], + :"测" => [":测", ":\"\\u6D4B\""], + :"🦊" => [":🦊", ":\"\\u{1F98A}\""], } + expected_by_encoding = Encoding::default_external == Encoding::UTF_8 ? 0 : 1 symbols.each do |input, expected| - expected = expected[1] if expected.is_a?(Array) + expected = expected[expected_by_encoding] if expected.is_a?(Array) it "returns self as a symbol literal for #{expected}" do input.inspect.should == expected end diff --git a/spec/ruby/core/symbol/shared/id2name.rb b/spec/ruby/core/symbol/shared/id2name.rb index d012b7634e..00a9c7d7dc 100644 --- a/spec/ruby/core/symbol/shared/id2name.rb +++ b/spec/ruby/core/symbol/shared/id2name.rb @@ -13,4 +13,18 @@ describe :symbol_id2name, shared: true do symbol.send(@method).encoding.should == Encoding::US_ASCII end + + ruby_version_is "3.4" do + it "warns about mutating returned string" do + -> { :bad!.send(@method).upcase! }.should complain(/warning: string returned by :bad!.to_s will be frozen in the future/) + end + + it "does not warn about mutation when Warning[:deprecated] is false" do + deprecated = Warning[:deprecated] + Warning[:deprecated] = false + -> { :bad!.send(@method).upcase! }.should_not complain + ensure + Warning[:deprecated] = deprecated + end + end end diff --git a/spec/ruby/core/symbol/shared/slice.rb b/spec/ruby/core/symbol/shared/slice.rb index 0df87e183d..d3d4aad617 100644 --- a/spec/ruby/core/symbol/shared/slice.rb +++ b/spec/ruby/core/symbol/shared/slice.rb @@ -7,7 +7,7 @@ describe :symbol_slice, shared: true do end it "returns nil if the index starts from the end and is greater than the length" do - :symbol.send(@method, -10).should be_nil + :symbol.send(@method, -10).should be_nil end it "returns nil if the index is greater than the length" do diff --git a/spec/ruby/core/symbol/to_proc_spec.rb b/spec/ruby/core/symbol/to_proc_spec.rb index 2cd013696a..def5d6d344 100644 --- a/spec/ruby/core/symbol/to_proc_spec.rb +++ b/spec/ruby/core/symbol/to_proc_spec.rb @@ -27,31 +27,29 @@ describe "Symbol#to_proc" do pr.parameters.should == [[:req], [:rest]] end - ruby_version_is "3.2" do - it "only calls public methods" do - body = proc do - public def pub; @a << :pub end - protected def pro; @a << :pro end - private def pri; @a << :pri end - attr_reader :a - end + it "only calls public methods" do + body = proc do + public def pub; @a << :pub end + protected def pro; @a << :pro end + private def pri; @a << :pri end + attr_reader :a + end - @a = [] - singleton_class.class_eval(&body) - tap(&:pub) - proc{tap(&:pro)}.should raise_error(NoMethodError, /protected method `pro' called/) - proc{tap(&:pri)}.should raise_error(NoMethodError, /private method `pri' called/) - @a.should == [:pub] + @a = [] + singleton_class.class_eval(&body) + tap(&:pub) + proc{tap(&:pro)}.should raise_error(NoMethodError, /protected method [`']pro' called/) + proc{tap(&:pri)}.should raise_error(NoMethodError, /private method [`']pri' called/) + @a.should == [:pub] - @a = [] - c = Class.new(&body) - o = c.new - o.instance_variable_set(:@a, []) - o.tap(&:pub) - proc{tap(&:pro)}.should raise_error(NoMethodError, /protected method `pro' called/) - proc{o.tap(&:pri)}.should raise_error(NoMethodError, /private method `pri' called/) - o.a.should == [:pub] - end + @a = [] + c = Class.new(&body) + o = c.new + o.instance_variable_set(:@a, []) + o.tap(&:pub) + proc{tap(&:pro)}.should raise_error(NoMethodError, /protected method [`']pro' called/) + proc{o.tap(&:pri)}.should raise_error(NoMethodError, /private method [`']pri' called/) + o.a.should == [:pub] end it "raises an ArgumentError when calling #call on the Proc without receiver" do diff --git a/spec/ruby/core/thread/abort_on_exception_spec.rb b/spec/ruby/core/thread/abort_on_exception_spec.rb index 34b648ca0f..49be84ea9f 100644 --- a/spec/ruby/core/thread/abort_on_exception_spec.rb +++ b/spec/ruby/core/thread/abort_on_exception_spec.rb @@ -72,7 +72,7 @@ describe "Thread.abort_on_exception" do end after do - Thread.abort_on_exception = @abort_on_exception + Thread.abort_on_exception = @abort_on_exception end it "is false by default" do diff --git a/spec/ruby/core/thread/backtrace/limit_spec.rb b/spec/ruby/core/thread/backtrace/limit_spec.rb index 26a87a806c..b55ca67ea0 100644 --- a/spec/ruby/core/thread/backtrace/limit_spec.rb +++ b/spec/ruby/core/thread/backtrace/limit_spec.rb @@ -1,15 +1,13 @@ require_relative '../../../spec_helper' -ruby_version_is "3.1" do - describe "Thread::Backtrace.limit" do - it "returns maximum backtrace length set by --backtrace-limit command-line option" do - out = ruby_exe("print Thread::Backtrace.limit", options: "--backtrace-limit=2") - out.should == "2" - end +describe "Thread::Backtrace.limit" do + it "returns maximum backtrace length set by --backtrace-limit command-line option" do + out = ruby_exe("print Thread::Backtrace.limit", options: "--backtrace-limit=2") + out.should == "2" + end - it "returns -1 when --backtrace-limit command-line option is not set" do - out = ruby_exe("print Thread::Backtrace.limit") - out.should == "-1" - end + it "returns -1 when --backtrace-limit command-line option is not set" do + out = ruby_exe("print Thread::Backtrace.limit") + out.should == "-1" end end diff --git a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb index e35e1fc0b4..68a69049d9 100644 --- a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb @@ -27,20 +27,11 @@ describe 'Thread::Backtrace::Location#absolute_path' do end context "when used in eval with a given filename" do - code = "caller_locations(0)[0].absolute_path" + it "returns nil with absolute_path" do + code = "caller_locations(0)[0].absolute_path" - ruby_version_is ""..."3.1" do - it "returns filename with absolute_path" do - eval(code, nil, "foo.rb").should == "foo.rb" - eval(code, nil, "foo/bar.rb").should == "foo/bar.rb" - end - end - - ruby_version_is "3.1" do - it "returns nil with absolute_path" do - eval(code, nil, "foo.rb").should == nil - eval(code, nil, "foo/bar.rb").should == nil - end + eval(code, nil, "foo.rb").should == nil + eval(code, nil, "foo/bar.rb").should == nil end end @@ -59,7 +50,7 @@ describe 'Thread::Backtrace::Location#absolute_path' do it "returns nil" do location = nil tap { location = caller_locations(1, 1)[0] } - location.label.should == "tap" + location.label.should =~ /\A(?:Kernel#)?tap\z/ if location.path.start_with?("<internal:") location.absolute_path.should == nil else diff --git a/spec/ruby/core/thread/backtrace/location/label_spec.rb b/spec/ruby/core/thread/backtrace/location/label_spec.rb index 7312d017e5..85ddccc8e3 100644 --- a/spec/ruby/core/thread/backtrace/location/label_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/label_spec.rb @@ -7,11 +7,11 @@ describe 'Thread::Backtrace::Location#label' do end it 'returns the method name for a method location' do - ThreadBacktraceLocationSpecs.method_location[0].label.should == "method_location" + ThreadBacktraceLocationSpecs.method_location[0].label.should =~ /\A(?:ThreadBacktraceLocationSpecs\.)?method_location\z/ end it 'returns the block name for a block location' do - ThreadBacktraceLocationSpecs.block_location[0].label.should == "block in block_location" + ThreadBacktraceLocationSpecs.block_location[0].label.should =~ /\Ablock in (?:ThreadBacktraceLocationSpecs\.)?block_location\z/ end it 'returns the module name for a module location' do @@ -22,9 +22,9 @@ describe 'Thread::Backtrace::Location#label' do first_level_location, second_level_location, third_level_location = ThreadBacktraceLocationSpecs.locations_inside_nested_blocks - first_level_location.label.should == 'block in locations_inside_nested_blocks' - second_level_location.label.should == 'block (2 levels) in locations_inside_nested_blocks' - third_level_location.label.should == 'block (3 levels) in locations_inside_nested_blocks' + first_level_location.label.should =~ /\Ablock in (?:ThreadBacktraceLocationSpecs\.)?locations_inside_nested_blocks\z/ + second_level_location.label.should =~ /\Ablock \(2 levels\) in (?:ThreadBacktraceLocationSpecs\.)?locations_inside_nested_blocks\z/ + third_level_location.label.should =~ /\Ablock \(3 levels\) in (?:ThreadBacktraceLocationSpecs\.)?locations_inside_nested_blocks\z/ end it 'sets the location label for a top-level block differently depending on it being in the main file or a required file' do diff --git a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb index d14cf17514..10457f80f0 100644 --- a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb @@ -7,7 +7,7 @@ describe 'Thread::Backtrace::Location#lineno' do @line = __LINE__ - 1 end - it 'returns the absolute path of the call frame' do + it 'returns the line number of the call frame' do @frame.lineno.should == @line end diff --git a/spec/ruby/core/thread/backtrace/location/path_spec.rb b/spec/ruby/core/thread/backtrace/location/path_spec.rb index 7863c055d3..75f76833a9 100644 --- a/spec/ruby/core/thread/backtrace/location/path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/path_spec.rb @@ -41,7 +41,7 @@ describe 'Thread::Backtrace::Location#path' do context 'when using a relative script path' do it 'returns a path relative to the working directory' do path = 'fixtures/main.rb' - directory = File.dirname(__FILE__) + directory = __dir__ Dir.chdir(directory) { ruby_exe(path) }.should == path diff --git a/spec/ruby/core/thread/backtrace_locations_spec.rb b/spec/ruby/core/thread/backtrace_locations_spec.rb index c970ae023b..09fe622e0d 100644 --- a/spec/ruby/core/thread/backtrace_locations_spec.rb +++ b/spec/ruby/core/thread/backtrace_locations_spec.rb @@ -70,7 +70,7 @@ describe "Thread#backtrace_locations" do end it "the first location reports the call to #backtrace_locations" do - Thread.current.backtrace_locations(0..0)[0].to_s.should == "#{__FILE__ }:#{__LINE__ }:in `backtrace_locations'" + Thread.current.backtrace_locations(0..0)[0].to_s.should =~ /\A#{__FILE__ }:#{__LINE__ }:in [`'](?:Thread#)?backtrace_locations'\z/ end it "[1..-1] is the same as #caller_locations(0..-1) for Thread.current" do diff --git a/spec/ruby/core/thread/backtrace_spec.rb b/spec/ruby/core/thread/backtrace_spec.rb index 9001b1b7eb..15bb29a349 100644 --- a/spec/ruby/core/thread/backtrace_spec.rb +++ b/spec/ruby/core/thread/backtrace_spec.rb @@ -13,7 +13,7 @@ describe "Thread#backtrace" do backtrace = t.backtrace backtrace.should be_kind_of(Array) - backtrace.first.should =~ /`sleep'/ + backtrace.first.should =~ /[`'](?:Kernel#)?sleep'/ t.raise 'finish the thread' t.join diff --git a/spec/ruby/core/thread/each_caller_location_spec.rb b/spec/ruby/core/thread/each_caller_location_spec.rb index dbece06cd8..aa7423675b 100644 --- a/spec/ruby/core/thread/each_caller_location_spec.rb +++ b/spec/ruby/core/thread/each_caller_location_spec.rb @@ -1,49 +1,47 @@ require_relative '../../spec_helper' describe "Thread.each_caller_location" do - ruby_version_is "3.2" do - it "iterates through the current execution stack and matches caller_locations content and type" do - ScratchPad.record [] - Thread.each_caller_location { |l| ScratchPad << l; } + it "iterates through the current execution stack and matches caller_locations content and type" do + ScratchPad.record [] + Thread.each_caller_location { |l| ScratchPad << l; } - ScratchPad.recorded.map(&:to_s).should == caller_locations.map(&:to_s) - ScratchPad.recorded[0].should be_kind_of(Thread::Backtrace::Location) - end + ScratchPad.recorded.map(&:to_s).should == caller_locations.map(&:to_s) + ScratchPad.recorded[0].should be_kind_of(Thread::Backtrace::Location) + end - it "returns subset of 'Thread.to_enum(:each_caller_location)' locations" do - ar = [] - ecl = Thread.each_caller_location { |x| ar << x } + it "returns subset of 'Thread.to_enum(:each_caller_location)' locations" do + ar = [] + ecl = Thread.each_caller_location { |x| ar << x } - (ar.map(&:to_s) - Thread.to_enum(:each_caller_location).to_a.map(&:to_s)).should.empty? - end + (ar.map(&:to_s) - Thread.to_enum(:each_caller_location).to_a.map(&:to_s)).should.empty? + end - it "stops the backtrace iteration if 'break' occurs" do - i = 0 - ar = [] - ecl = Thread.each_caller_location do |x| - ar << x - i += 1 - break x if i == 2 - end - - ar.map(&:to_s).should == caller_locations(1, 2).map(&:to_s) - ecl.should be_kind_of(Thread::Backtrace::Location) + it "stops the backtrace iteration if 'break' occurs" do + i = 0 + ar = [] + ecl = Thread.each_caller_location do |x| + ar << x + i += 1 + break x if i == 2 end - it "returns nil" do - Thread.each_caller_location {}.should == nil - end + ar.map(&:to_s).should == caller_locations(1, 2).map(&:to_s) + ecl.should be_kind_of(Thread::Backtrace::Location) + end - it "raises LocalJumpError when called without a block" do - -> { - Thread.each_caller_location - }.should raise_error(LocalJumpError, "no block given") - end + it "returns nil" do + Thread.each_caller_location {}.should == nil + end - it "doesn't accept positional and keyword arguments" do - -> { - Thread.each_caller_location(12, foo: 10) {} - }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 0)") - end + it "raises LocalJumpError when called without a block" do + -> { + Thread.each_caller_location + }.should raise_error(LocalJumpError, "no block given") + end + + it "doesn't accept keyword arguments" do + -> { + Thread.each_caller_location(12, foo: 10) {} + }.should raise_error(ArgumentError); end end diff --git a/spec/ruby/core/thread/element_reference_spec.rb b/spec/ruby/core/thread/element_reference_spec.rb index 85280cb287..fde9d1f440 100644 --- a/spec/ruby/core/thread/element_reference_spec.rb +++ b/spec/ruby/core/thread/element_reference_spec.rb @@ -37,6 +37,17 @@ describe "Thread#[]" do t2["value"].should == 2 end + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('value') + key.should_receive(:to_str).and_return('value') + + th = Thread.new do + Thread.current[:value] = 1 + end.join + + th[key].should == 1 + end + it "raises exceptions on the wrong type of keys" do -> { Thread.current[nil] }.should raise_error(TypeError) -> { Thread.current[5] }.should raise_error(TypeError) diff --git a/spec/ruby/core/thread/element_set_spec.rb b/spec/ruby/core/thread/element_set_spec.rb index c7498f7ac9..f205177304 100644 --- a/spec/ruby/core/thread/element_set_spec.rb +++ b/spec/ruby/core/thread/element_set_spec.rb @@ -12,10 +12,33 @@ describe "Thread#[]=" do th.freeze -> { th[:foo] = "bar" - }.should raise_error(FrozenError, /frozen/) + }.should raise_error(FrozenError, "can't modify frozen thread locals") end.join end + it "accepts Strings and Symbols" do + t1 = Thread.new do + Thread.current[:value] = 1 + end.join + t2 = Thread.new do + Thread.current["value"] = 2 + end.join + + t1[:value].should == 1 + t2[:value].should == 2 + end + + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('value') + key.should_receive(:to_str).and_return('value') + + th = Thread.new do + Thread.current[key] = 1 + end.join + + th[:value].should == 1 + end + it "raises exceptions on the wrong type of keys" do -> { Thread.current[nil] = true }.should raise_error(TypeError) -> { Thread.current[5] = true }.should raise_error(TypeError) diff --git a/spec/ruby/core/thread/fetch_spec.rb b/spec/ruby/core/thread/fetch_spec.rb index 6b37d4cfc5..85ffb71874 100644 --- a/spec/ruby/core/thread/fetch_spec.rb +++ b/spec/ruby/core/thread/fetch_spec.rb @@ -29,6 +29,36 @@ describe 'Thread#fetch' do end end + describe 'with a block' do + it 'returns the value of the fiber-local variable if value has been assigned' do + th = Thread.new { Thread.current[:cat] = 'meow' } + th.join + th.fetch(:cat) { true }.should == 'meow' + end + + it "returns the block value if fiber-local variable hasn't been assigned" do + th = Thread.new {} + th.join + th.fetch(:cat) { true }.should == true + end + + it "does not call the block if value has been assigned" do + th = Thread.new { Thread.current[:cat] = 'meow' } + th.join + var = :not_updated + th.fetch(:cat) { var = :updated }.should == 'meow' + var.should == :not_updated + end + + it "uses the block if a default is given and warns about it" do + th = Thread.new {} + th.join + -> { + th.fetch(:cat, false) { true }.should == true + }.should complain(/warning: block supersedes default value argument/) + end + end + it 'raises an ArgumentError when not passed one or two arguments' do -> { Thread.current.fetch() }.should raise_error(ArgumentError) -> { Thread.current.fetch(1, 2, 3) }.should raise_error(ArgumentError) diff --git a/spec/ruby/core/thread/fixtures/classes.rb b/spec/ruby/core/thread/fixtures/classes.rb index 23a090feb0..7c485660a8 100644 --- a/spec/ruby/core/thread/fixtures/classes.rb +++ b/spec/ruby/core/thread/fixtures/classes.rb @@ -6,6 +6,31 @@ module ThreadSpecs end end + class NewThreadToRaise + def self.raise(*args, **kwargs, &block) + thread = Thread.new do + Thread.current.report_on_exception = false + + if block_given? + block.call do + sleep + end + else + sleep + end + end + + Thread.pass until thread.stop? + + thread.raise(*args, **kwargs) + + thread.join + ensure + thread.kill if thread.alive? + Thread.pass while thread.alive? # Thread#kill may not terminate a thread immediately so it may be detected as a leaked one + end + end + class Status attr_reader :thread, :inspect, :status, :to_s def initialize(thread) diff --git a/spec/ruby/core/thread/group_spec.rb b/spec/ruby/core/thread/group_spec.rb index 59f5ac37c8..d0d4704b66 100644 --- a/spec/ruby/core/thread/group_spec.rb +++ b/spec/ruby/core/thread/group_spec.rb @@ -1,5 +1,16 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' + describe "Thread#group" do - it "needs to be reviewed for spec completeness" + it "returns the default thread group for the main thread" do + Thread.main.group.should == ThreadGroup::Default + end + + it "returns the thread group explicitly set for this thread" do + thread = Thread.new { nil } + thread_group = ThreadGroup.new + thread_group.add(thread) + thread.group.should == thread_group + ensure + thread.join if thread + end end diff --git a/spec/ruby/core/thread/key_spec.rb b/spec/ruby/core/thread/key_spec.rb index 6940cf5f28..339fa98f53 100644 --- a/spec/ruby/core/thread/key_spec.rb +++ b/spec/ruby/core/thread/key_spec.rb @@ -16,6 +16,13 @@ describe "Thread#key?" do @th.key?(:stanley.to_s).should == false end + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('key') + key.should_receive(:to_str).and_return('oliver') + + @th.key?(key).should == true + end + it "raises exceptions on the wrong type of keys" do -> { Thread.current.key? nil }.should raise_error(TypeError) -> { Thread.current.key? 5 }.should raise_error(TypeError) diff --git a/spec/ruby/core/thread/native_thread_id_spec.rb b/spec/ruby/core/thread/native_thread_id_spec.rb index 8460a1db8c..374cc59279 100644 --- a/spec/ruby/core/thread/native_thread_id_spec.rb +++ b/spec/ruby/core/thread/native_thread_id_spec.rb @@ -1,30 +1,35 @@ require_relative '../../spec_helper' -ruby_version_is "3.1" do - platform_is :linux, :darwin, :windows, :freebsd do - describe "Thread#native_thread_id" do - it "returns an integer when the thread is alive" do - Thread.current.native_thread_id.should be_kind_of(Integer) - end +platform_is :linux, :darwin, :windows, :freebsd do + describe "Thread#native_thread_id" do + it "returns an integer when the thread is alive" do + Thread.current.native_thread_id.should be_kind_of(Integer) + end - it "returns nil when the thread is not running" do - t = Thread.new {} - t.join - t.native_thread_id.should == nil - end + it "returns nil when the thread is not running" do + t = Thread.new {} + t.join + t.native_thread_id.should == nil + end - it "each thread has different native thread id" do - t = Thread.new { sleep } - Thread.pass until t.stop? - main_thread_id = Thread.current.native_thread_id - t_thread_id = t.native_thread_id + it "each thread has different native thread id" do + t = Thread.new { sleep } + Thread.pass until t.stop? + main_thread_id = Thread.current.native_thread_id + t_thread_id = t.native_thread_id + if ruby_version_is "3.3" + # native_thread_id can be nil on a M:N scheduler + t_thread_id.should be_kind_of(Integer) if t_thread_id != nil + else t_thread_id.should be_kind_of(Integer) - main_thread_id.should_not == t_thread_id - t.run - t.join - t.native_thread_id.should == nil end + + main_thread_id.should_not == t_thread_id + + t.run + t.join + t.native_thread_id.should == nil end end end diff --git a/spec/ruby/core/thread/raise_spec.rb b/spec/ruby/core/thread/raise_spec.rb index 49323cf270..b473eabd42 100644 --- a/spec/ruby/core/thread/raise_spec.rb +++ b/spec/ruby/core/thread/raise_spec.rb @@ -3,6 +3,9 @@ require_relative 'fixtures/classes' require_relative '../../shared/kernel/raise' describe "Thread#raise" do + it_behaves_like :kernel_raise, :raise, ThreadSpecs::NewThreadToRaise + it_behaves_like :kernel_raise_across_contexts, :raise, ThreadSpecs::NewThreadToRaise + it "ignores dead threads and returns nil" do t = Thread.new { :dead } Thread.pass while t.alive? diff --git a/spec/ruby/core/thread/report_on_exception_spec.rb b/spec/ruby/core/thread/report_on_exception_spec.rb index ab0af0972e..d9daa041cd 100644 --- a/spec/ruby/core/thread/report_on_exception_spec.rb +++ b/spec/ruby/core/thread/report_on_exception_spec.rb @@ -78,11 +78,11 @@ describe "Thread#report_on_exception=" do -> { go = true Thread.pass while t.alive? - }.should output("", <<ERR) -#{t.inspect} terminated with exception (report_on_exception is true): -#{__FILE__}:#{line_raise}:in `foo': Thread#report_on_exception specs backtrace order (RuntimeError) -\tfrom #{__FILE__}:#{line_call_foo}:in `block (4 levels) in <top (required)>' -ERR + }.should output("", /\A +#{Regexp.quote(t.inspect)}\sterminated\swith\sexception\s\(report_on_exception\sis\strue\):\n +#{Regexp.quote(__FILE__)}:#{line_raise}:in\s[`']foo':\sThread\#report_on_exception\sspecs\sbacktrace\sorder\s\(RuntimeError\)\n +\tfrom\s#{Regexp.quote(__FILE__)}:#{line_call_foo}:in\s[`']block\s\(4\slevels\)\sin\s<top\s\(required\)>'\n +\z/x) -> { t.join diff --git a/spec/ruby/core/thread/thread_variable_get_spec.rb b/spec/ruby/core/thread/thread_variable_get_spec.rb index 38f90d5830..1ea34cf2b3 100644 --- a/spec/ruby/core/thread/thread_variable_get_spec.rb +++ b/spec/ruby/core/thread/thread_variable_get_spec.rb @@ -13,13 +13,48 @@ describe "Thread#thread_variable_get" do @t.thread_variable_get(:a).should be_nil end - it "returns the value previously set by #[]=" do - @t.thread_variable_set :a, 49 + it "returns the value previously set by #thread_variable_set" do + @t.thread_variable_set(:a, 49) @t.thread_variable_get(:a).should == 49 end it "returns a value private to self" do - @t.thread_variable_set :thread_variable_get_spec, 82 + @t.thread_variable_set(:thread_variable_get_spec, 82) Thread.current.thread_variable_get(:thread_variable_get_spec).should be_nil end + + it "accepts String and Symbol keys interchangeably" do + @t.thread_variable_set("a", 49) + @t.thread_variable_get("a").should == 49 + @t.thread_variable_get(:a).should == 49 + end + + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('key') + key.should_receive(:to_str).and_return('a') + @t.thread_variable_set(:a, 49) + @t.thread_variable_get(key).should == 49 + end + + it "does not raise FrozenError if the thread is frozen" do + @t.freeze + @t.thread_variable_get(:a).should be_nil + end + + it "raises a TypeError if the key is neither Symbol nor String when thread variables are already set" do + @t.thread_variable_set(:a, 49) + -> { @t.thread_variable_get(123) }.should raise_error(TypeError, /123 is not a symbol/) + end + + ruby_version_is '3.4' do + it "raises a TypeError if the key is neither Symbol nor String when no thread variables are set" do + -> { @t.thread_variable_get(123) }.should raise_error(TypeError, /123 is not a symbol/) + end + + it "raises a TypeError if the key is neither Symbol nor String without calling #to_sym" do + key = mock('key') + key.should_not_receive(:to_sym) + -> { @t.thread_variable_get(key) }.should raise_error(TypeError, /#{Regexp.escape(key.inspect)} is not a symbol/) + end + end end diff --git a/spec/ruby/core/thread/thread_variable_set_spec.rb b/spec/ruby/core/thread/thread_variable_set_spec.rb index 1338c306c7..eadee76afb 100644 --- a/spec/ruby/core/thread/thread_variable_set_spec.rb +++ b/spec/ruby/core/thread/thread_variable_set_spec.rb @@ -10,17 +10,53 @@ describe "Thread#thread_variable_set" do end it "returns the value set" do - (@t.thread_variable_set :a, 2).should == 2 + @t.thread_variable_set(:a, 2).should == 2 end it "sets a value that will be returned by #thread_variable_get" do - @t.thread_variable_set :a, 49 + @t.thread_variable_set(:a, 49) @t.thread_variable_get(:a).should == 49 end it "sets a value private to self" do - @t.thread_variable_set :thread_variable_get_spec, 82 + @t.thread_variable_set(:thread_variable_get_spec, 82) @t.thread_variable_get(:thread_variable_get_spec).should == 82 Thread.current.thread_variable_get(:thread_variable_get_spec).should be_nil end + + it "accepts String and Symbol keys interchangeably" do + @t.thread_variable_set('a', 49) + @t.thread_variable_get('a').should == 49 + + @t.thread_variable_set(:a, 50) + @t.thread_variable_get('a').should == 50 + end + + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('key') + key.should_receive(:to_str).and_return('a') + @t.thread_variable_set(key, 49) + @t.thread_variable_get(:a).should == 49 + end + + it "removes a key if the value is nil" do + @t.thread_variable_set(:a, 52) + @t.thread_variable_set(:a, nil) + @t.thread_variable?(:a).should be_false + end + + it "raises a FrozenError if the thread is frozen" do + @t.freeze + -> { @t.thread_variable_set(:a, 1) }.should raise_error(FrozenError, "can't modify frozen thread locals") + end + + it "raises a TypeError if the key is neither Symbol nor String, nor responds to #to_str" do + -> { @t.thread_variable_set(123, 1) }.should raise_error(TypeError, /123 is not a symbol/) + end + + it "does not try to convert the key with #to_sym" do + key = mock('key') + key.should_not_receive(:to_sym) + -> { @t.thread_variable_set(key, 42) }.should raise_error(TypeError, /#{Regexp.quote(key.inspect)} is not a symbol/) + end end diff --git a/spec/ruby/core/thread/thread_variable_spec.rb b/spec/ruby/core/thread/thread_variable_spec.rb index 6bd1950c04..1b021e9404 100644 --- a/spec/ruby/core/thread/thread_variable_spec.rb +++ b/spec/ruby/core/thread/thread_variable_spec.rb @@ -10,12 +10,51 @@ describe "Thread#thread_variable?" do end it "returns false if the thread variables do not contain 'key'" do - @t.thread_variable_set :a, 2 + @t.thread_variable_set(:a, 2) @t.thread_variable?(:b).should be_false end it "returns true if the thread variables contain 'key'" do - @t.thread_variable_set :a, 2 + @t.thread_variable_set(:a, 2) @t.thread_variable?(:a).should be_true end + + it "accepts String and Symbol keys interchangeably" do + @t.thread_variable?('a').should be_false + @t.thread_variable?(:a).should be_false + + @t.thread_variable_set(:a, 49) + + @t.thread_variable?('a').should be_true + @t.thread_variable?(:a).should be_true + end + + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('key') + key.should_receive(:to_str).and_return('a') + @t.thread_variable_set(:a, 49) + @t.thread_variable?(key).should be_true + end + + it "does not raise FrozenError if the thread is frozen" do + @t.freeze + @t.thread_variable?(:a).should be_false + end + + it "raises a TypeError if the key is neither Symbol nor String when thread variables are already set" do + @t.thread_variable_set(:a, 49) + -> { @t.thread_variable?(123) }.should raise_error(TypeError, /123 is not a symbol/) + end + + ruby_version_is '3.4' do + it "raises a TypeError if the key is neither Symbol nor String when no thread variables are set" do + -> { @t.thread_variable?(123) }.should raise_error(TypeError, /123 is not a symbol/) + end + + it "raises a TypeError if the key is neither Symbol nor String without calling #to_sym" do + key = mock('key') + key.should_not_receive(:to_sym) + -> { @t.thread_variable?(key) }.should raise_error(TypeError, /#{Regexp.escape(key.inspect)} is not a symbol/) + end + end end diff --git a/spec/ruby/core/thread/thread_variables_spec.rb b/spec/ruby/core/thread/thread_variables_spec.rb index 1bd68b17f1..51ceef3376 100644 --- a/spec/ruby/core/thread/thread_variables_spec.rb +++ b/spec/ruby/core/thread/thread_variables_spec.rb @@ -10,15 +10,15 @@ describe "Thread#thread_variables" do end it "returns the keys of all the values set" do - @t.thread_variable_set :a, 2 - @t.thread_variable_set :b, 4 - @t.thread_variable_set :c, 6 + @t.thread_variable_set(:a, 2) + @t.thread_variable_set(:b, 4) + @t.thread_variable_set(:c, 6) @t.thread_variables.sort.should == [:a, :b, :c] end - it "sets a value private to self" do - @t.thread_variable_set :a, 82 - @t.thread_variable_set :b, 82 + it "returns the keys private to self" do + @t.thread_variable_set(:a, 82) + @t.thread_variable_set(:b, 82) Thread.current.thread_variables.should_not include(:a, :b) end @@ -26,4 +26,14 @@ describe "Thread#thread_variables" do Thread.current.thread_variables.should == [] @t.thread_variables.should == [] end + + it "returns keys as Symbols" do + key = mock('key') + key.should_receive(:to_str).and_return('a') + + @t.thread_variable_set(key, 49) + @t.thread_variable_set('b', 50) + @t.thread_variable_set(:c, 51) + @t.thread_variables.sort.should == [:a, :b, :c] + end end diff --git a/spec/ruby/core/time/_dump_spec.rb b/spec/ruby/core/time/_dump_spec.rb index 4dc1c43cd2..852f9a07ab 100644 --- a/spec/ruby/core/time/_dump_spec.rb +++ b/spec/ruby/core/time/_dump_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' describe "Time#_dump" do diff --git a/spec/ruby/core/time/_load_spec.rb b/spec/ruby/core/time/_load_spec.rb index 152934370f..30899de262 100644 --- a/spec/ruby/core/time/_load_spec.rb +++ b/spec/ruby/core/time/_load_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../spec_helper' describe "Time._load" do @@ -44,8 +44,7 @@ describe "Time._load" do end it "treats the data as binary data" do - data = "\x04\bu:\tTime\r\fM\x1C\xC0\x00\x00\xD0\xBE" - data.force_encoding Encoding::UTF_8 + data = "\x04\bu:\tTime\r\fM\x1C\xC0\x00\x00\xD0\xBE".dup.force_encoding Encoding::UTF_8 t = Marshal.load(data) t.to_s.should == "2013-04-08 12:47:45 UTC" end diff --git a/spec/ruby/core/time/at_spec.rb b/spec/ruby/core/time/at_spec.rb index 0459589f01..97906b8c8c 100644 --- a/spec/ruby/core/time/at_spec.rb +++ b/spec/ruby/core/time/at_spec.rb @@ -32,13 +32,6 @@ describe "Time.at" do t2.nsec.should == t.nsec end - describe "passed BigDecimal" do - it "doesn't round input value" do - require 'bigdecimal' - Time.at(BigDecimal('1.1')).to_f.should == 1.1 - end - end - describe "passed Rational" do it "returns Time with correct microseconds" do t = Time.at(Rational(1_486_570_508_539_759, 1_000_000)) @@ -109,8 +102,8 @@ describe "Time.at" do it "needs for the argument to respond to #to_int too" do o = mock('rational-but-no-to_int') - o.should_receive(:to_r).and_return(Rational(5, 2)) - -> { Time.at(o) }.should raise_error(TypeError) + def o.to_r; Rational(5, 2) end + -> { Time.at(o) }.should raise_error(TypeError, "can't convert MockObject into an exact number") end end end @@ -203,7 +196,7 @@ describe "Time.at" do end it "does not try to convert format to Symbol with #to_sym" do - format = "usec" + format = +"usec" format.should_not_receive(:to_sym) -> { Time.at(0, 123456, format) }.should raise_error(ArgumentError) end @@ -235,6 +228,12 @@ describe "Time.at" do time.utc_offset.should == -9*60*60 time.zone.should == nil time.to_i.should == @epoch_time + + time = Time.at(@epoch_time, in: "-09:00:01") + + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil + time.to_i.should == @epoch_time end it "could be UTC offset as a number of seconds" do @@ -287,5 +286,31 @@ describe "Time.at" do -> { Time.at(@epoch_time, in: "+09:99") }.should raise_error(ArgumentError) -> { Time.at(@epoch_time, in: "ABC") }.should raise_error(ArgumentError) end + + it "raises ArgumentError if hours greater than 23" do # TODO + -> { Time.at(@epoch_time, in: "+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.at(@epoch_time, in: "+2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.at(@epoch_time, in: "+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.at(@epoch_time, in: "+9900") }.should raise_error(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if minutes greater than 59" do # TODO + -> { Time.at(@epoch_time, in: "+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.at(@epoch_time, in: "+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.at(@epoch_time, in: "+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.at(@epoch_time, in: "+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if seconds greater than 59" do + -> { Time.at(@epoch_time, in: "+00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.at(@epoch_time, in: "+000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.at(@epoch_time, in: "+00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.at(@epoch_time, in: "+000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end end end diff --git a/spec/ruby/core/time/comparison_spec.rb b/spec/ruby/core/time/comparison_spec.rb index 5b53c9fb50..866fbea72e 100644 --- a/spec/ruby/core/time/comparison_spec.rb +++ b/spec/ruby/core/time/comparison_spec.rb @@ -55,6 +55,32 @@ describe "Time#<=>" do }.should_not complain end + context "given different timezones" do + it "returns 0 if time is the same as other" do + # three timezones, all at the same timestamp + time_utc = Time.new(2000, 1, 1, 0, 0, 0, 0) + time_cet = Time.new(2000, 1, 1, 1, 0, 0, '+01:00') + time_brt = Time.new(1999, 12, 31, 21, 0, 0, '-03:00') + (time_utc <=> time_cet).should == 0 + (time_utc <=> time_brt).should == 0 + (time_cet <=> time_brt).should == 0 + end + + it "returns -1 if the first argument is before the second argument" do + # time_brt is later, even though the date is earlier + time_utc = Time.new(2000, 1, 1, 0, 0, 0, 0) + time_brt = Time.new(1999, 12, 31, 23, 0, 0, '-03:00') + (time_utc <=> time_brt).should == -1 + end + + it "returns 1 if the first argument is before the second argument" do + # time_brt is later, even though the date is earlier + time_utc = Time.new(2000, 1, 1, 0, 0, 0, 0) + time_brt = Time.new(1999, 12, 31, 23, 0, 0, '-03:00') + (time_brt <=> time_utc).should == 1 + end + end + describe "given a non-Time argument" do it "returns nil if argument <=> self returns nil" do t = Time.now diff --git a/spec/ruby/core/time/deconstruct_keys_spec.rb b/spec/ruby/core/time/deconstruct_keys_spec.rb index fbb0ec2164..b5cfdaa93f 100644 --- a/spec/ruby/core/time/deconstruct_keys_spec.rb +++ b/spec/ruby/core/time/deconstruct_keys_spec.rb @@ -1,44 +1,43 @@ require_relative '../../spec_helper' -ruby_version_is "3.2" do - describe "Time#deconstruct_keys" do - it "returns whole hash for nil as an argument" do - d = Time.utc(2022, 10, 5, 13, 30) - res = { year: 2022, month: 10, day: 5, yday: 278, wday: 3, hour: 13, - min: 30, sec: 0, subsec: 0, dst: false, zone: "UTC" } - d.deconstruct_keys(nil).should == res - end - - it "returns only specified keys" do - d = Time.utc(2022, 10, 5, 13, 39) - d.deconstruct_keys([:zone, :subsec]).should == { zone: "UTC", subsec: 0 } - end - - it "requires one argument" do - -> { - Time.new(2022, 10, 5, 13, 30).deconstruct_keys - }.should raise_error(ArgumentError) - end - - it "it raises error when argument is neither nil nor array" do - d = Time.new(2022, 10, 5, 13, 30) - - -> { d.deconstruct_keys(1) }.should raise_error(TypeError, "wrong argument type Integer (expected Array or nil)") - -> { d.deconstruct_keys("asd") }.should raise_error(TypeError, "wrong argument type String (expected Array or nil)") - -> { d.deconstruct_keys(:x) }.should raise_error(TypeError, "wrong argument type Symbol (expected Array or nil)") - -> { d.deconstruct_keys({}) }.should raise_error(TypeError, "wrong argument type Hash (expected Array or nil)") - end - - it "returns {} when passed []" do - Time.new(2022, 10, 5, 13, 30).deconstruct_keys([]).should == {} - end - - it "ignores non-Symbol keys" do - Time.new(2022, 10, 5, 13, 30).deconstruct_keys(['year', []]).should == {} - end - - it "ignores not existing Symbol keys" do - Time.new(2022, 10, 5, 13, 30).deconstruct_keys([:year, :a]).should == { year: 2022 } - end +describe "Time#deconstruct_keys" do + it "returns whole hash for nil as an argument" do + d = Time.utc(2022, 10, 5, 13, 30) + res = { year: 2022, month: 10, day: 5, yday: 278, wday: 3, hour: 13, + min: 30, sec: 0, subsec: 0, dst: false, zone: "UTC" } + d.deconstruct_keys(nil).should == res + end + + it "returns only specified keys" do + d = Time.utc(2022, 10, 5, 13, 39) + d.deconstruct_keys([:zone, :subsec]).should == { zone: "UTC", subsec: 0 } + end + + it "requires one argument" do + -> { + Time.new(2022, 10, 5, 13, 30).deconstruct_keys + }.should raise_error(ArgumentError) + end + + it "it raises error when argument is neither nil nor array" do + d = Time.new(2022, 10, 5, 13, 30) + + -> { d.deconstruct_keys(1) }.should raise_error(TypeError, "wrong argument type Integer (expected Array or nil)") + -> { d.deconstruct_keys("asd") }.should raise_error(TypeError, "wrong argument type String (expected Array or nil)") + -> { d.deconstruct_keys(:x) }.should raise_error(TypeError, "wrong argument type Symbol (expected Array or nil)") + -> { d.deconstruct_keys({}) }.should raise_error(TypeError, "wrong argument type Hash (expected Array or nil)") + end + + it "returns {} when passed []" do + Time.new(2022, 10, 5, 13, 30).deconstruct_keys([]).should == {} + end + + it "ignores non-Symbol keys" do + Time.new(2022, 10, 5, 13, 30).deconstruct_keys(['year', []]).should == {} + end + + it "ignores not existing Symbol keys and processes keys after the first non-existing one" do + d = Time.utc(2022, 10, 5, 13, 30) + d.deconstruct_keys([:year, :a, :month, :b, :day]).should == { year: 2022, month: 10, day: 5 } end end diff --git a/spec/ruby/core/time/fixtures/classes.rb b/spec/ruby/core/time/fixtures/classes.rb index 1a9511b261..21c4e1effb 100644 --- a/spec/ruby/core/time/fixtures/classes.rb +++ b/spec/ruby/core/time/fixtures/classes.rb @@ -59,7 +59,6 @@ module TimeSpecs Zone = Struct.new(:std, :dst, :dst_range) Zones = { "Asia/Colombo" => Zone[Z[5*3600+30*60, "MMT"], nil, nil], - "Europe/Kiev" => Zone[Z[2*3600, "EET"], Z[3*3600, "EEST"], 4..10], "PST" => Zone[Z[(-9*60*60), "PST"], nil, nil], } diff --git a/spec/ruby/core/time/getlocal_spec.rb b/spec/ruby/core/time/getlocal_spec.rb index 926a6dbf45..398596f400 100644 --- a/spec/ruby/core/time/getlocal_spec.rb +++ b/spec/ruby/core/time/getlocal_spec.rb @@ -14,6 +14,7 @@ describe "Time#getlocal" do t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(3630) t.should == Time.new(2007, 1, 9, 13, 0, 30, 3630) t.utc_offset.should == 3630 + t.zone.should be_nil end platform_is_not :windows do @@ -59,12 +60,24 @@ describe "Time#getlocal" do t.utc_offset.should == 3600 end + it "returns a Time with a UTC offset specified as +HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("+01:00:01") + t.should == Time.new(2007, 1, 9, 13, 0, 1, 3601) + t.utc_offset.should == 3601 + end + it "returns a Time with a UTC offset specified as -HH:MM" do t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("-01:00") t.should == Time.new(2007, 1, 9, 11, 0, 0, -3600) t.utc_offset.should == -3600 end + it "returns a Time with a UTC offset specified as -HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("-01:00:01") + t.should == Time.new(2007, 1, 9, 10, 59, 59, -3601) + t.utc_offset.should == -3601 + end + describe "with an argument that responds to #to_str" do it "coerces using #to_str" do o = mock('string') @@ -97,6 +110,32 @@ describe "Time#getlocal" do -> { t.getlocal(86400) }.should raise_error(ArgumentError) end + it "raises ArgumentError if String argument and hours greater than 23" do + -> { Time.now.getlocal("+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.getlocal("+2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.now.getlocal("+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.getlocal("+9900") }.should raise_error(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + -> { Time.now.getlocal("+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now.getlocal("+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now.getlocal("+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now.getlocal("+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now.getlocal("+00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now.getlocal("+000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now.getlocal("+00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now.getlocal("+000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + describe "with a timezone argument" do it "returns a Time in the timezone" do zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60)) diff --git a/spec/ruby/core/time/iso8601_spec.rb b/spec/ruby/core/time/iso8601_spec.rb new file mode 100644 index 0000000000..ad60c3bb32 --- /dev/null +++ b/spec/ruby/core/time/iso8601_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' + +describe "Time#iso8601" do + it_behaves_like :time_xmlschema, :iso8601 +end diff --git a/spec/ruby/core/time/localtime_spec.rb b/spec/ruby/core/time/localtime_spec.rb index 609b6532a1..71c0dfebde 100644 --- a/spec/ruby/core/time/localtime_spec.rb +++ b/spec/ruby/core/time/localtime_spec.rb @@ -72,6 +72,13 @@ describe "Time#localtime" do t.utc_offset.should == 3600 end + it "returns a Time with a UTC offset specified as +HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0) + t.localtime("+01:00:01") + t.should == Time.new(2007, 1, 9, 13, 0, 1, 3601) + t.utc_offset.should == 3601 + end + it "returns a Time with a UTC offset specified as -HH:MM" do t = Time.gm(2007, 1, 9, 12, 0, 0) t.localtime("-01:00") @@ -79,6 +86,13 @@ describe "Time#localtime" do t.utc_offset.should == -3600 end + it "returns a Time with a UTC offset specified as -HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0) + t.localtime("-01:00:01") + t.should == Time.new(2007, 1, 9, 10, 59, 59, -3601) + t.utc_offset.should == -3601 + end + it "returns a Time with a UTC offset specified as UTC" do t = Time.new(2007, 1, 9, 12, 0, 0, 3600) t.localtime("UTC") @@ -91,6 +105,32 @@ describe "Time#localtime" do t.utc_offset.should == 3600 * 2 end + it "raises ArgumentError if String argument and hours greater than 23" do + -> { Time.now.localtime("+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.localtime("+2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.now.localtime("+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now.localtime("+9900") }.should raise_error(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + -> { Time.now.localtime("+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now.localtime("+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now.localtime("+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now.localtime("+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now.localtime("+00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now.localtime("+000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now.localtime("+00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now.localtime("+000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + platform_is_not :windows do it "changes the timezone according to the set one" do t = Time.new(2005, 2, 27, 22, 50, 0, -3600) @@ -128,6 +168,17 @@ describe "Time#localtime" do end end + describe "with an argument that responds to #utc_to_local" do + it "coerces using #utc_to_local" do + o = mock('string') + o.should_receive(:utc_to_local).and_return(Time.new(2007, 1, 9, 13, 0, 0, 3600)) + t = Time.gm(2007, 1, 9, 12, 0, 0) + t.localtime(o) + t.should == Time.new(2007, 1, 9, 13, 0, 0, 3600) + t.utc_offset.should == 3600 + end + end + it "raises ArgumentError if the String argument is not of the form (+|-)HH:MM" do t = Time.now -> { t.localtime("3600") }.should raise_error(ArgumentError) diff --git a/spec/ruby/core/time/minus_spec.rb b/spec/ruby/core/time/minus_spec.rb index 8449778465..9182d99652 100644 --- a/spec/ruby/core/time/minus_spec.rb +++ b/spec/ruby/core/time/minus_spec.rb @@ -109,7 +109,7 @@ describe "Time#-" do it "does not return a subclass instance" do c = Class.new(Time) - x = c.now + 1 + x = c.now - 1 x.should be_an_instance_of(Time) end diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index 18d7396269..dc3ccbdc00 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -58,30 +58,28 @@ describe "Time.new with a utc_offset argument" do Time.new(2000, 1, 1, 0, 0, 0, "-04:10:43").utc_offset.should == -15043 end - ruby_bug '#13669', ''...'3.1' do - it "returns a Time with a UTC offset specified as +HH" do - Time.new(2000, 1, 1, 0, 0, 0, "+05").utc_offset.should == 3600 * 5 - end + it "returns a Time with a UTC offset specified as +HH" do + Time.new(2000, 1, 1, 0, 0, 0, "+05").utc_offset.should == 3600 * 5 + end - it "returns a Time with a UTC offset specified as -HH" do - Time.new(2000, 1, 1, 0, 0, 0, "-05").utc_offset.should == -3600 * 5 - end + it "returns a Time with a UTC offset specified as -HH" do + Time.new(2000, 1, 1, 0, 0, 0, "-05").utc_offset.should == -3600 * 5 + end - it "returns a Time with a UTC offset specified as +HHMM" do - Time.new(2000, 1, 1, 0, 0, 0, "+0530").utc_offset.should == 19800 - end + it "returns a Time with a UTC offset specified as +HHMM" do + Time.new(2000, 1, 1, 0, 0, 0, "+0530").utc_offset.should == 19800 + end - it "returns a Time with a UTC offset specified as -HHMM" do - Time.new(2000, 1, 1, 0, 0, 0, "-0530").utc_offset.should == -19800 - end + it "returns a Time with a UTC offset specified as -HHMM" do + Time.new(2000, 1, 1, 0, 0, 0, "-0530").utc_offset.should == -19800 + end - it "returns a Time with a UTC offset specified as +HHMMSS" do - Time.new(2000, 1, 1, 0, 0, 0, "+053037").utc_offset.should == 19837 - end + it "returns a Time with a UTC offset specified as +HHMMSS" do + Time.new(2000, 1, 1, 0, 0, 0, "+053037").utc_offset.should == 19837 + end - it "returns a Time with a UTC offset specified as -HHMMSS" do - Time.new(2000, 1, 1, 0, 0, 0, "-053037").utc_offset.should == -19837 - end + it "returns a Time with a UTC offset specified as -HHMMSS" do + Time.new(2000, 1, 1, 0, 0, 0, "-053037").utc_offset.should == -19837 end describe "with an argument that responds to #to_str" do @@ -129,18 +127,9 @@ describe "Time.new with a utc_offset argument" do end end - ruby_version_is ""..."3.1" do - it "raises ArgumentError if the string argument is J" do - message = '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset' - -> { Time.new(2000, 1, 1, 0, 0, 0, "J") }.should raise_error(ArgumentError, message) - end - end - - ruby_version_is "3.1" do - it "raises ArgumentError if the string argument is J" do - message = '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: J' - -> { Time.new(2000, 1, 1, 0, 0, 0, "J") }.should raise_error(ArgumentError, message) - end + it "raises ArgumentError if the string argument is J" do + message = '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: J' + -> { Time.new(2000, 1, 1, 0, 0, 0, "J") }.should raise_error(ArgumentError, message) end it "returns a local Time if the argument is nil" do @@ -193,6 +182,7 @@ describe "Time.new with a utc_offset argument" do end end +# The method #local_to_utc is tested only here because Time.new is the only method that calls #local_to_utc. describe "Time.new with a timezone argument" do it "returns a Time in the timezone" do zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60)) @@ -213,9 +203,7 @@ describe "Time.new with a timezone argument" do time end - -> { - Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) - }.should_not raise_error + Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) end it "raises TypeError if timezone does not implement #local_to_utc method" do @@ -226,7 +214,7 @@ describe "Time.new with a timezone argument" do -> { Time.new(2000, 1, 1, 12, 0, 0, zone) - }.should raise_error(TypeError, /can't convert \w+ into an exact number/) + }.should raise_error(TypeError, /can't convert Object into an exact number/) end it "does not raise exception if timezone does not implement #utc_to_local method" do @@ -235,51 +223,48 @@ describe "Time.new with a timezone argument" do time end - -> { - Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) - }.should_not raise_error + Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) end # The result also should be a Time or Time-like object (not necessary to be the same class) - # The zone of the result is just ignored + # or respond to #to_int method. The zone of the result is just ignored. describe "returned value by #utc_to_local and #local_to_utc methods" do it "could be Time instance" do zone = Object.new def zone.local_to_utc(t) - Time.utc(t.year, t.mon, t.day, t.hour - 1, t.min, t.sec) + time = Time.utc(t.year, t.mon, t.day, t.hour, t.min, t.sec) + time - 60 * 60 # - 1 hour end - -> { - Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) - Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 - }.should_not raise_error + Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 end it "could be Time subclass instance" do zone = Object.new def zone.local_to_utc(t) - Class.new(Time).utc(t.year, t.mon, t.day, t.hour - 1, t.min, t.sec) + time = Time.utc(t.year, t.mon, t.day, t.hour, t.min, t.sec) + time -= 60 * 60 # - 1 hour + Class.new(Time).utc(time.year, time.mon, time.day, time.hour, t.min, t.sec) end - -> { - Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) - Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 - }.should_not raise_error + Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 end it "could be any object with #to_i method" do zone = Object.new def zone.local_to_utc(time) - Struct.new(:to_i).new(time.to_i - 60*60) + obj = Object.new + obj.singleton_class.define_method(:to_i) { time.to_i - 60*60 } + obj end - -> { - Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) - Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 - }.should_not raise_error + Time.new(2000, 1, 1, 12, 0, 0, zone).should be_kind_of(Time) + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 end - it "could have any #zone and #utc_offset because they are ignored" do + it "could have any #zone and #utc_offset because they are ignored if it isn't an instance of Time" do zone = Object.new def zone.local_to_utc(time) Struct.new(:to_i, :zone, :utc_offset).new(time.to_i, 'America/New_York', -5*60*60) @@ -293,7 +278,15 @@ describe "Time.new with a timezone argument" do Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 0 end - it "leads to raising Argument error if difference between argument and result is too large" do + it "cannot have arbitrary #utc_offset if it is an instance of Time" do + zone = Object.new + def zone.local_to_utc(t) + Time.new(t.year, t.mon, t.mday, t.hour, t.min, t.sec, 9*60*60) + end + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 9*60*60 + end + + it "raises ArgumentError if difference between argument and result is too large" do zone = Object.new def zone.local_to_utc(t) Time.utc(t.year, t.mon, t.day + 1, t.hour, t.min, t.sec) @@ -318,12 +311,9 @@ describe "Time.new with a timezone argument" do end it "implements subset of Time methods" do + # List only methods that are explicitly documented. [ - :year, :mon, :month, :mday, :hour, :min, :sec, - :tv_sec, :tv_usec, :usec, :tv_nsec, :nsec, :subsec, - :to_i, :to_f, :to_r, :+, :-, - :isdst, :dst?, :zone, :gmtoff, :gmt_offset, :utc_offset, :utc?, :gmt?, - :to_s, :inspect, :to_a, :to_time, + :year, :mon, :mday, :hour, :min, :sec, :to_i, :isdst ].each do |name| @obj.respond_to?(name).should == true end @@ -358,7 +348,7 @@ describe "Time.new with a timezone argument" do -> { Marshal.dump(time) - }.should raise_error(NoMethodError, /undefined method `name' for/) + }.should raise_error(NoMethodError, /undefined method [`']name' for/) end end @@ -403,243 +393,360 @@ describe "Time.new with a timezone argument" do end end - ruby_version_is '3.1' do # https://bugs.ruby-lang.org/issues/17485 - describe ":in keyword argument" do - it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do - time = Time.new(2000, 1, 1, 12, 0, 0, in: "+05:00") + describe ":in keyword argument" do + it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do + time = Time.new(2000, 1, 1, 12, 0, 0, in: "+05:00") - time.utc_offset.should == 5*60*60 - time.zone.should == nil + time.utc_offset.should == 5*60*60 + time.zone.should == nil - time = Time.new(2000, 1, 1, 12, 0, 0, in: "-09:00") + time = Time.new(2000, 1, 1, 12, 0, 0, in: "-09:00") - time.utc_offset.should == -9*60*60 - time.zone.should == nil - end + time.utc_offset.should == -9*60*60 + time.zone.should == nil - it "could be UTC offset as a number of seconds" do - time = Time.new(2000, 1, 1, 12, 0, 0, in: 5*60*60) + time = Time.new(2000, 1, 1, 12, 0, 0, in: "-09:00:01") - time.utc_offset.should == 5*60*60 - time.zone.should == nil + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil + end - time = Time.new(2000, 1, 1, 12, 0, 0, in: -9*60*60) + it "could be UTC offset as a number of seconds" do + time = Time.new(2000, 1, 1, 12, 0, 0, in: 5*60*60) - time.utc_offset.should == -9*60*60 - time.zone.should == nil - end + time.utc_offset.should == 5*60*60 + time.zone.should == nil - it "returns a Time with UTC offset specified as a single letter military timezone" do - Time.new(2000, 1, 1, 0, 0, 0, in: "W").utc_offset.should == 3600 * -10 - end + time = Time.new(2000, 1, 1, 12, 0, 0, in: -9*60*60) - it "could be a timezone object" do - zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") - time = Time.new(2000, 1, 1, 12, 0, 0, in: zone) + time.utc_offset.should == -9*60*60 + time.zone.should == nil + end - time.utc_offset.should == 5*3600+30*60 - time.zone.should == zone + it "returns a Time with UTC offset specified as a single letter military timezone" do + Time.new(2000, 1, 1, 0, 0, 0, in: "W").utc_offset.should == 3600 * -10 + end - zone = TimeSpecs::TimezoneWithName.new(name: "PST") - time = Time.new(2000, 1, 1, 12, 0, 0, in: zone) + it "could be a timezone object" do + zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") + time = Time.new(2000, 1, 1, 12, 0, 0, in: zone) - time.utc_offset.should == -9*60*60 - time.zone.should == zone - end + time.utc_offset.should == 5*3600+30*60 + time.zone.should == zone - it "allows omitting minor arguments" do - Time.new(2000, 1, 1, 12, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 1, "+05:00") - Time.new(2000, 1, 1, 12, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 0, "+05:00") - Time.new(2000, 1, 1, 12, in: "+05:00").should == Time.new(2000, 1, 1, 12, 0, 0, "+05:00") - Time.new(2000, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") - Time.new(2000, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") - Time.new(2000, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") - Time.new(in: "+05:00").should be_close(Time.now.getlocal("+05:00"), TIME_TOLERANCE) - end + zone = TimeSpecs::TimezoneWithName.new(name: "PST") + time = Time.new(2000, 1, 1, 12, 0, 0, in: zone) - it "converts to a provided timezone if all the positional arguments are omitted" do - Time.new(in: "+05:00").utc_offset.should == 5*3600 - end + time.utc_offset.should == -9*60*60 + time.zone.should == zone + end - it "raises ArgumentError if format is invalid" do - -> { Time.new(2000, 1, 1, 12, 0, 0, in: "+09:99") }.should raise_error(ArgumentError) - -> { Time.new(2000, 1, 1, 12, 0, 0, in: "ABC") }.should raise_error(ArgumentError) - end + it "allows omitting minor arguments" do + Time.new(2000, 1, 1, 12, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 1, "+05:00") + Time.new(2000, 1, 1, 12, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 0, "+05:00") + Time.new(2000, 1, 1, 12, in: "+05:00").should == Time.new(2000, 1, 1, 12, 0, 0, "+05:00") + Time.new(2000, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(2000, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(2000, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(in: "+05:00").should be_close(Time.now.getlocal("+05:00"), TIME_TOLERANCE) + end - it "raises ArgumentError if two offset arguments are given" do - -> { - Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00") - }.should raise_error(ArgumentError, "timezone argument given as positional and keyword arguments") - end + it "converts to a provided timezone if all the positional arguments are omitted" do + Time.new(in: "+05:00").utc_offset.should == 5*3600 + end + + it "raises ArgumentError if format is invalid" do + -> { Time.new(2000, 1, 1, 12, 0, 0, in: "+09:99") }.should raise_error(ArgumentError) + -> { Time.new(2000, 1, 1, 12, 0, 0, in: "ABC") }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if two offset arguments are given" do + -> { + Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00") + }.should raise_error(ArgumentError, "timezone argument given as positional and keyword arguments") end end - ruby_version_is "3.2" do - describe "Time.new with a String argument" do - it "parses an ISO-8601 like format" do - t = Time.utc(2020, 12, 24, 15, 56, 17) + describe "Time.new with a String argument" do + it "parses an ISO-8601 like format" do + t = Time.utc(2020, 12, 24, 15, 56, 17) - Time.new("2020-12-24T15:56:17Z").should == t - Time.new("2020-12-25 00:56:17 +09:00").should == t - Time.new("2020-12-25 00:57:47 +09:01:30").should == t - Time.new("2020-12-25 00:56:17 +0900").should == t - Time.new("2020-12-25 00:57:47 +090130").should == t - Time.new("2020-12-25T00:56:17+09:00").should == t - end + Time.new("2020-12-24T15:56:17Z").should == t + Time.new("2020-12-25 00:56:17 +09:00").should == t + Time.new("2020-12-25 00:57:47 +09:01:30").should == t + Time.new("2020-12-25 00:56:17 +0900").should == t + Time.new("2020-12-25 00:57:47 +090130").should == t + Time.new("2020-12-25T00:56:17+09:00").should == t - it "accepts precision keyword argument and truncates specified digits of sub-second part" do - Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec.should == 0.123456789r - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec.should == 0.123456789876r - Time.new("2021-12-25 00:00:00 +09:00", precision: 0).subsec.should == 0 - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: -1).subsec.should == 0.123456789876r - end + Time.new("2020-12-25T00:56:17.123456+09:00").should == Time.utc(2020, 12, 24, 15, 56, 17, 123456) + end - it "returns Time in local timezone if not provided in the String argument" do - Time.new("2021-12-25 00:00:00").zone.should == Time.new(2021, 12, 25).zone - Time.new("2021-12-25 00:00:00").utc_offset.should == Time.new(2021, 12, 25).utc_offset - end + it "accepts precision keyword argument and truncates specified digits of sub-second part" do + Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec.should == 0.123456789r + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec.should == 0.123456789876r + Time.new("2021-12-25 00:00:00 +09:00", precision: 0).subsec.should == 0 + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: -1).subsec.should == 0.123456789876r + end - it "returns Time in timezone specified in the String argument" do - Time.new("2021-12-25 00:00:00 +05:00").to_s.should == "2021-12-25 00:00:00 +0500" - end + it "returns Time in local timezone if not provided in the String argument" do + Time.new("2021-12-25 00:00:00").zone.should == Time.new(2021, 12, 25).zone + Time.new("2021-12-25 00:00:00").utc_offset.should == Time.new(2021, 12, 25).utc_offset + end - it "returns Time in timezone specified in the String argument even if the in keyword argument provided" do - Time.new("2021-12-25 00:00:00 +09:00", in: "-01:00").to_s.should == "2021-12-25 00:00:00 +0900" - end + it "returns Time in timezone specified in the String argument" do + Time.new("2021-12-25 00:00:00 +05:00").to_s.should == "2021-12-25 00:00:00 +0500" + end - it "returns Time in timezone specified with in keyword argument if timezone isn't provided in the String argument" do - Time.new("2021-12-25 00:00:00", in: "-01:00").to_s.should == "2021-12-25 00:00:00 -0100" - end + it "returns Time in timezone specified in the String argument even if the in keyword argument provided" do + Time.new("2021-12-25 00:00:00 +09:00", in: "-01:00").to_s.should == "2021-12-25 00:00:00 +0900" + end - it "converts precision keyword argument into Integer if is not nil" do - obj = Object.new - def obj.to_int; 3; end + it "returns Time in timezone specified with in keyword argument if timezone isn't provided in the String argument" do + Time.new("2021-12-25 00:00:00", in: "-01:00").to_s.should == "2021-12-25 00:00:00 -0100" + end - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 1.2).subsec.should == 0.1r - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: obj).subsec.should == 0.123r - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3r).subsec.should == 0.123r - end + it "returns Time of Jan 1 for string with just year" do + Time.new("2021").should == Time.new(2021, 1, 1) + Time.new("2021").zone.should == Time.new(2021, 1, 1).zone + Time.new("2021").utc_offset.should == Time.new(2021, 1, 1).utc_offset + end - ruby_version_is ""..."3.3" do - it "raise TypeError is can't convert precision keyword argument into Integer" do - -> { - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: "") - }.should raise_error(TypeError, "no implicit conversion from string") - end - end + it "returns Time of Jan 1 for string with just year in timezone specified with in keyword argument" do + Time.new("2021", in: "+17:00").to_s.should == "2021-01-01 00:00:00 +1700" + end - ruby_version_is "3.3" do - it "raise TypeError is can't convert precision keyword argument into Integer" do - -> { - Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: "") - }.should raise_error(TypeError, "no implicit conversion of String into Integer") - end - end + it "converts precision keyword argument into Integer if is not nil" do + obj = Object.new + def obj.to_int; 3; end - it "raises ArgumentError if part of time string is missing" do - -> { - Time.new("2020-12-25 00:56 +09:00") - }.should raise_error(ArgumentError, "missing sec part: 00:56 ") + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 1.2).subsec.should == 0.1r + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: obj).subsec.should == 0.123r + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3r).subsec.should == 0.123r + end + + it "returns Time with correct subseconds when given seconds fraction is shorted than 6 digits" do + Time.new("2020-12-25T00:56:17.123 +09:00").nsec.should == 123000000 + Time.new("2020-12-25T00:56:17.123 +09:00").usec.should == 123000 + Time.new("2020-12-25T00:56:17.123 +09:00").subsec.should == 0.123 + end + + it "returns Time with correct subseconds when given seconds fraction is milliseconds" do + Time.new("2020-12-25T00:56:17.123456 +09:00").nsec.should == 123456000 + Time.new("2020-12-25T00:56:17.123456 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456 +09:00").subsec.should == 0.123456 + end + + it "returns Time with correct subseconds when given seconds fraction is longer that 6 digits but shorted than 9 digits" do + Time.new("2020-12-25T00:56:17.12345678 +09:00").nsec.should == 123456780 + Time.new("2020-12-25T00:56:17.12345678 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.12345678 +09:00").subsec.should == 0.12345678 + end + it "returns Time with correct subseconds when given seconds fraction is nanoseconds" do + Time.new("2020-12-25T00:56:17.123456789 +09:00").nsec.should == 123456789 + Time.new("2020-12-25T00:56:17.123456789 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456789 +09:00").subsec.should == 0.123456789 + end + + it "returns Time with correct subseconds when given seconds fraction is longer than 9 digits" do + Time.new("2020-12-25T00:56:17.123456789876 +09:00").nsec.should == 123456789 + Time.new("2020-12-25T00:56:17.123456789876 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456789876 +09:00").subsec.should == 0.123456789 + end + + ruby_version_is ""..."3.3" do + it "raise TypeError is can't convert precision keyword argument into Integer" do -> { - Time.new("2020-12-25 00 +09:00") - }.should raise_error(ArgumentError, "missing min part: 00 ") + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: "") + }.should raise_error(TypeError, "no implicit conversion from string") end + end - it "raises ArgumentError if subsecond is missing after dot" do + ruby_version_is "3.3" do + it "raise TypeError is can't convert precision keyword argument into Integer" do -> { - Time.new("2020-12-25 00:56:17. +0900") - }.should raise_error(ArgumentError, "subsecond expected after dot: 00:56:17. ") + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: "") + }.should raise_error(TypeError, "no implicit conversion of String into Integer") end + end - it "raises ArgumentError if String argument is not in the supported format" do - -> { - Time.new("021-12-25 00:00:00.123456 +09:00") - }.should raise_error(ArgumentError, "year must be 4 or more digits: 021") + it "raises ArgumentError if part of time string is missing" do + -> { + Time.new("2020-12-25 00:56 +09:00") + }.should raise_error(ArgumentError, /missing sec part: 00:56 |can't parse:/) - -> { - Time.new("2020-012-25 00:56:17 +0900") - }.should raise_error(ArgumentError, "two digits mon is expected after `-': -012-25 00:") + -> { + Time.new("2020-12-25 00 +09:00") + }.should raise_error(ArgumentError, /missing min part: 00 |can't parse:/) + end + ruby_version_is "3.2.3" do + it "raises ArgumentError if the time part is missing" do -> { - Time.new("2020-2-25 00:56:17 +0900") - }.should raise_error(ArgumentError, "two digits mon is expected after `-': -2-25 00:56") + Time.new("2020-12-25") + }.should raise_error(ArgumentError, /no time information|can't parse:/) + end + it "raises ArgumentError if day is missing" do -> { - Time.new("2020-12-215 00:56:17 +0900") - }.should raise_error(ArgumentError, "two digits mday is expected after `-': -215 00:56:") + Time.new("2020-12") + }.should raise_error(ArgumentError, /no time information|can't parse:/) + end + end - -> { - Time.new("2020-12-25 000:56:17 +0900") - }.should raise_error(ArgumentError, "two digits hour is expected: 000:56:17 ") + it "raises ArgumentError if subsecond is missing after dot" do + -> { + Time.new("2020-12-25 00:56:17. +0900") + }.should raise_error(ArgumentError, /subsecond expected after dot: 00:56:17. |can't parse:/) + end - -> { - Time.new("2020-12-25 0:56:17 +0900") - }.should raise_error(ArgumentError, "two digits hour is expected: 0:56:17 +0") + it "raises ArgumentError if String argument is not in the supported format" do + -> { + Time.new("021-12-25 00:00:00.123456 +09:00") + }.should raise_error(ArgumentError, /year must be 4 or more digits: 021|can't parse:/) - -> { - Time.new("2020-12-25 00:516:17 +0900") - }.should raise_error(ArgumentError, "two digits min is expected after `:': :516:17 +09") + -> { + Time.new("2020-012-25 00:56:17 +0900") + }.should raise_error(ArgumentError, /\Atwo digits mon is expected after [`']-': -012-25 00:\z|can't parse:/) - -> { - Time.new("2020-12-25 00:6:17 +0900") - }.should raise_error(ArgumentError, "two digits min is expected after `:': :6:17 +0900") + -> { + Time.new("2020-2-25 00:56:17 +0900") + }.should raise_error(ArgumentError, /\Atwo digits mon is expected after [`']-': -2-25 00:56\z|can't parse:/) - -> { - Time.new("2020-12-25 00:56:137 +0900") - }.should raise_error(ArgumentError, "two digits sec is expected after `:': :137 +0900") + -> { + Time.new("2020-12-215 00:56:17 +0900") + }.should raise_error(ArgumentError, /\Atwo digits mday is expected after [`']-': -215 00:56:\z|can't parse:/) - -> { - Time.new("2020-12-25 00:56:7 +0900") - }.should raise_error(ArgumentError, "two digits sec is expected after `:': :7 +0900") + -> { + Time.new("2020-12-25 000:56:17 +0900") + }.should raise_error(ArgumentError, /two digits hour is expected: 000:56:17 |can't parse:/) - -> { - Time.new("2020-12-25 00:56. +0900") - }.should raise_error(ArgumentError, "fraction min is not supported: 00:56.") + -> { + Time.new("2020-12-25 0:56:17 +0900") + }.should raise_error(ArgumentError, /two digits hour is expected: 0:56:17 \+0|can't parse:/) + + -> { + Time.new("2020-12-25 00:516:17 +0900") + }.should raise_error(ArgumentError, /\Atwo digits min is expected after [`']:': :516:17 \+09\z|can't parse:/) + + -> { + Time.new("2020-12-25 00:6:17 +0900") + }.should raise_error(ArgumentError, /\Atwo digits min is expected after [`']:': :6:17 \+0900\z|can't parse:/) + + -> { + Time.new("2020-12-25 00:56:137 +0900") + }.should raise_error(ArgumentError, /\Atwo digits sec is expected after [`']:': :137 \+0900\z|can't parse:/) + + -> { + Time.new("2020-12-25 00:56:7 +0900") + }.should raise_error(ArgumentError, /\Atwo digits sec is expected after [`']:': :7 \+0900\z|can't parse:/) + + -> { + Time.new("2020-12-25 00:56. +0900") + }.should raise_error(ArgumentError, /fraction min is not supported: 00:56\.|can't parse:/) + + -> { + Time.new("2020-12-25 00. +0900") + }.should raise_error(ArgumentError, /fraction hour is not supported: 00\.|can't parse:/) + end + + it "raises ArgumentError if date/time parts values are not valid" do + -> { + Time.new("2020-13-25 00:56:17 +09:00") + }.should raise_error(ArgumentError, /(mon|argument) out of range/) + + -> { + Time.new("2020-12-32 00:56:17 +09:00") + }.should raise_error(ArgumentError, /(mday|argument) out of range/) + + -> { + Time.new("2020-12-25 25:56:17 +09:00") + }.should raise_error(ArgumentError, /(hour|argument) out of range/) + + -> { + Time.new("2020-12-25 00:61:17 +09:00") + }.should raise_error(ArgumentError, /(min|argument) out of range/) + + -> { + Time.new("2020-12-25 00:56:61 +09:00") + }.should raise_error(ArgumentError, /(sec|argument) out of range/) + + -> { + Time.new("2020-12-25 00:56:17 +23:59:60") + }.should raise_error(ArgumentError, /utc_offset|argument out of range/) + + -> { + Time.new("2020-12-25 00:56:17 +24:00") + }.should raise_error(ArgumentError, /(utc_offset|argument) out of range/) + + -> { + Time.new("2020-12-25 00:56:17 +23:61") + }.should raise_error(ArgumentError, /utc_offset/) + ruby_bug '#20797', ''...'3.4' do -> { - Time.new("2020-12-25 00. +0900") - }.should raise_error(ArgumentError, "fraction hour is not supported: 00.") + Time.new("2020-12-25 00:56:17 +00:23:61") + }.should raise_error(ArgumentError, /utc_offset/) end + end - it "raises ArgumentError if date/time parts values are not valid" do - -> { - Time.new("2020-13-25 00:56:17 +09:00") - }.should raise_error(ArgumentError, "mon out of range") + it "raises ArgumentError if utc offset parts are not valid" do + -> { Time.new("2020-12-25 00:56:17 +24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.new("2020-12-25 00:56:17 +2400") }.should raise_error(ArgumentError, "utc_offset out of range") - -> { - Time.new("2020-12-32 00:56:17 +09:00") - }.should raise_error(ArgumentError, "mday out of range") + -> { Time.new("2020-12-25 00:56:17 +99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.new("2020-12-25 00:56:17 +9900") }.should raise_error(ArgumentError, "utc_offset out of range") - -> { - Time.new("2020-12-25 25:56:17 +09:00") - }.should raise_error(ArgumentError, "hour out of range") + -> { Time.new("2020-12-25 00:56:17 +00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.new("2020-12-25 00:56:17 +0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') - -> { - Time.new("2020-12-25 00:61:17 +09:00") - }.should raise_error(ArgumentError, "min out of range") + -> { Time.new("2020-12-25 00:56:17 +00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.new("2020-12-25 00:56:17 +0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') - -> { - Time.new("2020-12-25 00:56:61 +09:00") - }.should raise_error(ArgumentError, "sec out of range") + ruby_bug '#20797', ''...'3.4' do + -> { Time.new("2020-12-25 00:56:17 +00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.new("2020-12-25 00:56:17 +000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') - -> { - Time.new("2020-12-25 00:56:17 +23:59:60") - }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.new("2020-12-25 00:56:17 +00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.new("2020-12-25 00:56:17 +000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end - -> { - Time.new("2020-12-25 00:56:17 +24:00") - }.should raise_error(ArgumentError, "utc_offset out of range") + it "raises ArgumentError if string has not ascii-compatible encoding" do + -> { + Time.new("2021-11-31 00:00:60 +09:00".encode("utf-32le")) + }.should raise_error(ArgumentError, "time string should have ASCII compatible encoding") + end - -> { - Time.new("2020-12-25 00:56:17 +23:61") - }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +23:61') + it "raises ArgumentError if string doesn't start with year" do + -> { + Time.new("a\nb") + }.should raise_error(ArgumentError, "can't parse: \"a\\nb\"") + end + + it "raises ArgumentError if string has extra characters after offset" do + -> { + Time.new("2021-11-31 00:00:59 +09:00 abc") + }.should raise_error(ArgumentError, /can't parse.+ abc/) + end + + ruby_version_is "3.2.3" do + it "raises ArgumentError when there are leading space characters" do + -> { Time.new(" 2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\t2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\n2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\v2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\f2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("\r2020-12-02 00:00:00") }.should raise_error(ArgumentError, /can't parse/) end - it "raises ArgumentError if string has not ascii-compatible encoding" do - -> { - Time.new("2021-11-31 00:00:60 +09:00".encode("utf-32le")) - }.should raise_error(ArgumentError, "time string should have ASCII compatible encoding") + it "raises ArgumentError when there are trailing whitespaces" do + -> { Time.new("2020-12-02 00:00:00 ") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\t") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\n") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\v") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\f") }.should raise_error(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\r") }.should raise_error(ArgumentError, /can't parse/) end end end diff --git a/spec/ruby/core/time/now_spec.rb b/spec/ruby/core/time/now_spec.rb index d47f00723e..e3fe6edad6 100644 --- a/spec/ruby/core/time/now_spec.rb +++ b/spec/ruby/core/time/now_spec.rb @@ -4,53 +4,177 @@ require_relative 'shared/now' describe "Time.now" do it_behaves_like :time_now, :now - ruby_version_is '3.1' do # https://bugs.ruby-lang.org/issues/17485 - describe ":in keyword argument" do - it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do - time = Time.now(in: "+05:00") + describe ":in keyword argument" do + it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do + time = Time.now(in: "+05:00") - time.utc_offset.should == 5*60*60 - time.zone.should == nil + time.utc_offset.should == 5*60*60 + time.zone.should == nil - time = Time.now(in: "-09:00") + time = Time.now(in: "-09:00") - time.utc_offset.should == -9*60*60 - time.zone.should == nil + time.utc_offset.should == -9*60*60 + time.zone.should == nil + + time = Time.now(in: "-09:00:01") + + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil + end + + it "could be UTC offset as a number of seconds" do + time = Time.now(in: 5*60*60) + + time.utc_offset.should == 5*60*60 + time.zone.should == nil + + time = Time.now(in: -9*60*60) + + time.utc_offset.should == -9*60*60 + time.zone.should == nil + end + + it "returns a Time with UTC offset specified as a single letter military timezone" do + Time.now(in: "W").utc_offset.should == 3600 * -10 + end + + it "could be a timezone object" do + zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") + time = Time.now(in: zone) + + time.utc_offset.should == 5*3600+30*60 + time.zone.should == zone + + zone = TimeSpecs::TimezoneWithName.new(name: "PST") + time = Time.now(in: zone) + + time.utc_offset.should == -9*60*60 + time.zone.should == zone + end + + it "raises ArgumentError if format is invalid" do + -> { Time.now(in: "+09:99") }.should raise_error(ArgumentError) + -> { Time.now(in: "ABC") }.should raise_error(ArgumentError) + end + + it "raises ArgumentError if String argument and hours greater than 23" do + -> { Time.now(in: "+24:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now(in: "+2400") }.should raise_error(ArgumentError, "utc_offset out of range") + + -> { Time.now(in: "+99:00") }.should raise_error(ArgumentError, "utc_offset out of range") + -> { Time.now(in: "+9900") }.should raise_error(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + -> { Time.now(in: "+00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now(in: "+0060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now(in: "+00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now(in: "+0099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now(in: "+00:00:60") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now(in: "+000060") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now(in: "+00:00:99") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now(in: "+000099") }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') end + end + end - it "could be UTC offset as a number of seconds" do - time = Time.now(in: 5*60*60) + describe "Timezone object" do # https://bugs.ruby-lang.org/issues/17485 + it "raises TypeError if timezone does not implement #utc_to_local method" do + zone = Object.new + def zone.local_to_utc(time) + time + end - time.utc_offset.should == 5*60*60 - time.zone.should == nil + -> { + Time.now(in: zone) + }.should raise_error(TypeError, /can't convert Object into an exact number/) + end - time = Time.now(in: -9*60*60) + it "does not raise exception if timezone does not implement #local_to_utc method" do + zone = Object.new + def zone.utc_to_local(time) + time + end + + Time.now(in: zone).should be_kind_of(Time) + end - time.utc_offset.should == -9*60*60 - time.zone.should == nil + # The result also should be a Time or Time-like object (not necessary to be the same class) + # or Integer. The zone of the result is just ignored. + describe "returned value by #utc_to_local and #local_to_utc methods" do + it "could be Time instance" do + zone = Object.new + def zone.utc_to_local(t) + time = Time.new(t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset) + time + 60 * 60 # + 1 hour + end + + Time.now(in: zone).should be_kind_of(Time) + Time.now(in: zone).utc_offset.should == 3600 end - it "returns a Time with UTC offset specified as a single letter military timezone" do - Time.now(in: "W").utc_offset.should == 3600 * -10 + it "could be Time subclass instance" do + zone = Object.new + def zone.utc_to_local(t) + time = Time.new(t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset) + time += 60 * 60 # + 1 hour + + Class.new(Time).new(time.year, time.mon, time.day, time.hour, time.min, time.sec, time.utc_offset) + end + + Time.now(in: zone).should be_kind_of(Time) + Time.now(in: zone).utc_offset.should == 3600 end - it "could be a timezone object" do - zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") - time = Time.now(in: zone) + it "could be Integer" do + zone = Object.new + def zone.utc_to_local(time) + time.to_i + 60*60 + end + + Time.now(in: zone).should be_kind_of(Time) + Time.now(in: zone).utc_offset.should == 60*60 + end - time.utc_offset.should == 5*3600+30*60 - time.zone.should == zone + it "could have any #zone and #utc_offset because they are ignored" do + zone = Object.new + def zone.utc_to_local(t) + Struct.new(:year, :mon, :mday, :hour, :min, :sec, :isdst, :to_i, :zone, :utc_offset) # rubocop:disable Lint/StructNewOverride + .new(t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.isdst, t.to_i, 'America/New_York', -5*60*60) + end + Time.now(in: zone).utc_offset.should == 0 - zone = TimeSpecs::TimezoneWithName.new(name: "PST") - time = Time.now(in: zone) + zone = Object.new + def zone.utc_to_local(t) + Struct.new(:year, :mon, :mday, :hour, :min, :sec, :isdst, :to_i, :zone, :utc_offset) # rubocop:disable Lint/StructNewOverride + .new(t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.isdst, t.to_i, 'Asia/Tokyo', 9*60*60) + end + Time.now(in: zone).utc_offset.should == 0 - time.utc_offset.should == -9*60*60 - time.zone.should == zone + zone = Object.new + def zone.utc_to_local(t) + Time.new(t.year, t.mon, t.mday, t.hour, t.min, t.sec, 9*60*60) + end + Time.now(in: zone).utc_offset.should == 0 end - it "raises ArgumentError if format is invalid" do - -> { Time.now(in: "+09:99") }.should raise_error(ArgumentError) - -> { Time.now(in: "ABC") }.should raise_error(ArgumentError) + it "raises ArgumentError if difference between argument and result is too large" do + zone = Object.new + def zone.utc_to_local(t) + time = Time.utc(t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset) + time -= 24 * 60 * 60 # - 1 day + Time.utc(time.year, time.mon, time.day, time.hour, time.min, time.sec, time.utc_offset) + end + + -> { + Time.now(in: zone) + }.should raise_error(ArgumentError, "utc_offset out of range") end end end diff --git a/spec/ruby/core/time/shared/gmtime.rb b/spec/ruby/core/time/shared/gmtime.rb index bae19da462..7b4f65f0b7 100644 --- a/spec/ruby/core/time/shared/gmtime.rb +++ b/spec/ruby/core/time/shared/gmtime.rb @@ -4,7 +4,14 @@ describe :time_gmtime, shared: true do with_timezone("CST", -6) do t = Time.local(2007, 1, 9, 6, 0, 0) t.send(@method) - t.should == Time.gm(2007, 1, 9, 12, 0, 0) + # Time#== compensates for time zones, so check all parts separately + t.year.should == 2007 + t.month.should == 1 + t.mday.should == 9 + t.hour.should == 12 + t.min.should == 0 + t.sec.should == 0 + t.zone.should == "UTC" end end diff --git a/spec/ruby/core/time/shared/time_params.rb b/spec/ruby/core/time/shared/time_params.rb index b6a6c88c8e..9832fd17fe 100644 --- a/spec/ruby/core/time/shared/time_params.rb +++ b/spec/ruby/core/time/shared/time_params.rb @@ -179,6 +179,10 @@ describe :time_params, shared: true do }.should raise_error(ArgumentError, "argument out of range") end + it "raises ArgumentError when given 8 arguments" do + -> { Time.send(@method, *[0]*8) }.should raise_error(ArgumentError) + end + it "raises ArgumentError when given 9 arguments" do -> { Time.send(@method, *[0]*9) }.should raise_error(ArgumentError) end diff --git a/spec/ruby/core/time/shared/xmlschema.rb b/spec/ruby/core/time/shared/xmlschema.rb new file mode 100644 index 0000000000..d68c18df36 --- /dev/null +++ b/spec/ruby/core/time/shared/xmlschema.rb @@ -0,0 +1,31 @@ +describe :time_xmlschema, shared: true do + ruby_version_is "3.4" do + it "generates ISO-8601 strings in Z for UTC times" do + t = Time.utc(1985, 4, 12, 23, 20, 50, 521245) + t.send(@method).should == "1985-04-12T23:20:50Z" + t.send(@method, 2).should == "1985-04-12T23:20:50.52Z" + t.send(@method, 9).should == "1985-04-12T23:20:50.521245000Z" + end + + it "generates ISO-8601 string with timeone offset for non-UTC times" do + t = Time.new(1985, 4, 12, 23, 20, 50, "+02:00") + t.send(@method).should == "1985-04-12T23:20:50+02:00" + t.send(@method, 2).should == "1985-04-12T23:20:50.00+02:00" + end + + it "year is always at least 4 digits" do + t = Time.utc(12, 4, 12) + t.send(@method).should == "0012-04-12T00:00:00Z" + end + + it "year can be more than 4 digits" do + t = Time.utc(40_000, 4, 12) + t.send(@method).should == "40000-04-12T00:00:00Z" + end + + it "year can be negative" do + t = Time.utc(-2000, 4, 12) + t.send(@method).should == "-2000-04-12T00:00:00Z" + end + end +end diff --git a/spec/ruby/core/time/strftime_spec.rb b/spec/ruby/core/time/strftime_spec.rb index 4cb300c916..fd233f3577 100644 --- a/spec/ruby/core/time/strftime_spec.rb +++ b/spec/ruby/core/time/strftime_spec.rb @@ -50,44 +50,42 @@ describe "Time#strftime" do time.strftime("%::z").should == "+01:01:05" end - ruby_version_is "3.1" do - it "supports RFC 3339 UTC for unknown offset local time, -0000, as %-z" do - time = Time.gm(2022) - - time.strftime("%z").should == "+0000" - time.strftime("%-z").should == "-0000" - time.strftime("%-:z").should == "-00:00" - time.strftime("%-::z").should == "-00:00:00" - end + it "supports RFC 3339 UTC for unknown offset local time, -0000, as %-z" do + time = Time.gm(2022) - it "applies '-' flag to UTC time" do - time = Time.utc(2022) - time.strftime("%-z").should == "-0000" + time.strftime("%z").should == "+0000" + time.strftime("%-z").should == "-0000" + time.strftime("%-:z").should == "-00:00" + time.strftime("%-::z").should == "-00:00:00" + end - time = Time.gm(2022) - time.strftime("%-z").should == "-0000" + it "applies '-' flag to UTC time" do + time = Time.utc(2022) + time.strftime("%-z").should == "-0000" - time = Time.new(2022, 1, 1, 0, 0, 0, "Z") - time.strftime("%-z").should == "-0000" + time = Time.gm(2022) + time.strftime("%-z").should == "-0000" - time = Time.new(2022, 1, 1, 0, 0, 0, "-00:00") - time.strftime("%-z").should == "-0000" + time = Time.new(2022, 1, 1, 0, 0, 0, "Z") + time.strftime("%-z").should == "-0000" - time = Time.new(2022, 1, 1, 0, 0, 0, "+03:00").utc - time.strftime("%-z").should == "-0000" - end + time = Time.new(2022, 1, 1, 0, 0, 0, "-00:00") + time.strftime("%-z").should == "-0000" - it "ignores '-' flag for non-UTC time" do - time = Time.new(2022, 1, 1, 0, 0, 0, "+03:00") - time.strftime("%-z").should == "+0300" - end + time = Time.new(2022, 1, 1, 0, 0, 0, "+03:00").utc + time.strftime("%-z").should == "-0000" + end - it "works correctly with width, _ and 0 flags, and :" do - Time.now.utc.strftime("%-_10z").should == " -000" - Time.now.utc.strftime("%-10z").should == "-000000000" - Time.now.utc.strftime("%-010:z").should == "-000000:00" - Time.now.utc.strftime("%-_10:z").should == " -0:00" - Time.now.utc.strftime("%-_10::z").should == " -0:00:00" - end + it "ignores '-' flag for non-UTC time" do + time = Time.new(2022, 1, 1, 0, 0, 0, "+03:00") + time.strftime("%-z").should == "+0300" + end + + it "works correctly with width, _ and 0 flags, and :" do + Time.now.utc.strftime("%-_10z").should == " -000" + Time.now.utc.strftime("%-10z").should == "-000000000" + Time.now.utc.strftime("%-010:z").should == "-000000:00" + Time.now.utc.strftime("%-_10:z").should == " -0:00" + Time.now.utc.strftime("%-_10::z").should == " -0:00:00" end end diff --git a/spec/ruby/core/time/utc_spec.rb b/spec/ruby/core/time/utc_spec.rb index 566509fd33..ab3c0df657 100644 --- a/spec/ruby/core/time/utc_spec.rb +++ b/spec/ruby/core/time/utc_spec.rb @@ -22,10 +22,8 @@ describe "Time#utc?" do Time.now.localtime("UTC").utc?.should == true Time.at(Time.now, in: 'UTC').utc?.should == true - ruby_version_is "3.1" do - Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").utc?.should == true - Time.now(in: "UTC").utc?.should == true - end + Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").utc?.should == true + Time.now(in: "UTC").utc?.should == true end it "does treat time with Z offset as UTC" do @@ -33,26 +31,26 @@ describe "Time#utc?" do Time.now.localtime("Z").utc?.should == true Time.at(Time.now, in: 'Z').utc?.should == true - ruby_version_is "3.1" do - Time.new(2022, 1, 1, 0, 0, 0, in: "Z").utc?.should == true - Time.now(in: "Z").utc?.should == true - end + Time.new(2022, 1, 1, 0, 0, 0, in: "Z").utc?.should == true + Time.now(in: "Z").utc?.should == true end - ruby_version_is "3.1" do - it "does treat time with -00:00 offset as UTC" do - Time.new(2022, 1, 1, 0, 0, 0, "-00:00").utc?.should == true - Time.now.localtime("-00:00").utc?.should == true - Time.at(Time.now, in: '-00:00').utc?.should == true - end + it "does treat time with -00:00 offset as UTC" do + Time.new(2022, 1, 1, 0, 0, 0, "-00:00").utc?.should == true + Time.now.localtime("-00:00").utc?.should == true + Time.at(Time.now, in: '-00:00').utc?.should == true end it "does not treat time with +00:00 offset as UTC" do Time.new(2022, 1, 1, 0, 0, 0, "+00:00").utc?.should == false + Time.now.localtime("+00:00").utc?.should == false + Time.at(Time.now, in: "+00:00").utc?.should == false end it "does not treat time with 0 offset as UTC" do Time.new(2022, 1, 1, 0, 0, 0, 0).utc?.should == false + Time.now.localtime(0).utc?.should == false + Time.at(Time.now, in: 0).utc?.should == false end end diff --git a/spec/ruby/core/time/xmlschema_spec.rb b/spec/ruby/core/time/xmlschema_spec.rb new file mode 100644 index 0000000000..bdf1dc7923 --- /dev/null +++ b/spec/ruby/core/time/xmlschema_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' + +describe "Time#xmlschema" do + it_behaves_like :time_xmlschema, :xmlschema +end diff --git a/spec/ruby/core/time/yday_spec.rb b/spec/ruby/core/time/yday_spec.rb index 6ea5ff8f1b..e920c2e28d 100644 --- a/spec/ruby/core/time/yday_spec.rb +++ b/spec/ruby/core/time/yday_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative '../../shared/time/yday' describe "Time#yday" do it "returns an integer representing the day of the year, 1..366" do @@ -7,15 +8,5 @@ describe "Time#yday" do end end - it 'returns the correct value for each day of each month' do - mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - - yday = 1 - mdays.each_with_index do |days, month| - days.times do |day| - Time.new(2014, month+1, day+1).yday.should == yday - yday += 1 - end - end - end + it_behaves_like :time_yday, -> year, month, day { Time.new(year, month, day).yday } end diff --git a/spec/ruby/core/time/zone_spec.rb b/spec/ruby/core/time/zone_spec.rb index 63c92602d1..9a15bd569b 100644 --- a/spec/ruby/core/time/zone_spec.rb +++ b/spec/ruby/core/time/zone_spec.rb @@ -69,22 +69,18 @@ describe "Time#zone" do Time.at(Time.now, in: 'UTC').zone.should == "UTC" Time.at(Time.now, in: 'Z').zone.should == "UTC" - ruby_version_is "3.1" do - Time.new(2022, 1, 1, 0, 0, 0, "-00:00").zone.should == "UTC" - Time.now.localtime("-00:00").zone.should == "UTC" - Time.at(Time.now, in: '-00:00').zone.should == "UTC" - end + Time.new(2022, 1, 1, 0, 0, 0, "-00:00").zone.should == "UTC" + Time.now.localtime("-00:00").zone.should == "UTC" + Time.at(Time.now, in: '-00:00').zone.should == "UTC" - ruby_version_is "3.1" do - Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").zone.should == "UTC" - Time.new(2022, 1, 1, 0, 0, 0, in: "Z").zone.should == "UTC" + Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").zone.should == "UTC" + Time.new(2022, 1, 1, 0, 0, 0, in: "Z").zone.should == "UTC" - Time.now(in: 'UTC').zone.should == "UTC" - Time.now(in: 'Z').zone.should == "UTC" + Time.now(in: 'UTC').zone.should == "UTC" + Time.now(in: 'Z').zone.should == "UTC" - Time.at(Time.now, in: 'UTC').zone.should == "UTC" - Time.at(Time.now, in: 'Z').zone.should == "UTC" - end + Time.at(Time.now, in: 'UTC').zone.should == "UTC" + Time.at(Time.now, in: 'Z').zone.should == "UTC" end platform_is_not :aix, :windows do diff --git a/spec/ruby/core/tracepoint/allow_reentry_spec.rb b/spec/ruby/core/tracepoint/allow_reentry_spec.rb index 6bff1bed76..75e9e859a9 100644 --- a/spec/ruby/core/tracepoint/allow_reentry_spec.rb +++ b/spec/ruby/core/tracepoint/allow_reentry_spec.rb @@ -1,32 +1,30 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is "3.1" do - describe 'TracePoint.allow_reentry' do - it 'allows the reentrance in a given block' do - event_lines = [] - l1 = l2 = l3 = l4 = nil - TracePoint.new(:line) do |tp| - next unless TracePointSpec.target_thread? +describe 'TracePoint.allow_reentry' do + it 'allows the reentrance in a given block' do + event_lines = [] + l1 = l2 = l3 = l4 = nil + TracePoint.new(:line) do |tp| + next unless TracePointSpec.target_thread? - event_lines << tp.lineno - next if (__LINE__ + 2 .. __LINE__ + 4).cover?(tp.lineno) - TracePoint.allow_reentry do - a = 1; l3 = __LINE__ - b = 2; l4 = __LINE__ - end - end.enable do - c = 3; l1 = __LINE__ - d = 4; l2 = __LINE__ + event_lines << tp.lineno + next if (__LINE__ + 2 .. __LINE__ + 4).cover?(tp.lineno) + TracePoint.allow_reentry do + a = 1; l3 = __LINE__ + b = 2; l4 = __LINE__ end - - event_lines.should == [l1, l3, l4, l2, l3, l4] + end.enable do + c = 3; l1 = __LINE__ + d = 4; l2 = __LINE__ end - it 'raises RuntimeError when not called inside a TracePoint' do - -> { - TracePoint.allow_reentry{} - }.should raise_error(RuntimeError) - end + event_lines.should == [l1, l3, l4, l2, l3, l4] + end + + it 'raises RuntimeError when not called inside a TracePoint' do + -> { + TracePoint.allow_reentry{} + }.should raise_error(RuntimeError) end end diff --git a/spec/ruby/core/tracepoint/enable_spec.rb b/spec/ruby/core/tracepoint/enable_spec.rb index 6cc8bb3897..93a6b281e3 100644 --- a/spec/ruby/core/tracepoint/enable_spec.rb +++ b/spec/ruby/core/tracepoint/enable_spec.rb @@ -57,50 +57,25 @@ describe 'TracePoint#enable' do end.enable { event_name.should equal(:line) } end - ruby_version_is '3.2' do - it 'enables the trace object only for the current thread' do - threads = [] - trace = TracePoint.new(:line) do |tp| - # Runs on purpose on any Thread - threads << Thread.current - end - - thread = nil - trace.enable do - line_event = true - thread = Thread.new do - event_in_other_thread = true - end - thread.join - end - - threads = threads.uniq - threads.should.include?(Thread.current) - threads.should_not.include?(thread) + it 'enables the trace object only for the current thread' do + threads = [] + trace = TracePoint.new(:line) do |tp| + # Runs on purpose on any Thread + threads << Thread.current end - end - ruby_version_is ''...'3.2' do - it 'enables the trace object for any thread' do - threads = [] - trace = TracePoint.new(:line) do |tp| - # Runs on purpose on any Thread - threads << Thread.current - end - - thread = nil - trace.enable do - line_event = true - thread = Thread.new do - event_in_other_thread = true - end - thread.join + thread = nil + trace.enable do + line_event = true + thread = Thread.new do + event_in_other_thread = true end - - threads = threads.uniq - threads.should.include?(Thread.current) - threads.should.include?(thread) + thread.join end + + threads = threads.uniq + threads.should.include?(Thread.current) + threads.should_not.include?(thread) end it 'can accept arguments within a block but it should not yield arguments' do diff --git a/spec/ruby/core/tracepoint/inspect_spec.rb b/spec/ruby/core/tracepoint/inspect_spec.rb index 21d62e9e26..6cc2ebe243 100644 --- a/spec/ruby/core/tracepoint/inspect_spec.rb +++ b/spec/ruby/core/tracepoint/inspect_spec.rb @@ -24,6 +24,8 @@ describe 'TracePoint#inspect' do line = nil TracePoint.new(:line) { |tp| next unless TracePointSpec.target_thread? + next unless tp.path == __FILE__ + inspect ||= tp.inspect }.enable do line = __LINE__ @@ -37,6 +39,8 @@ describe 'TracePoint#inspect' do line = nil TracePoint.new(:call) { |tp| next unless TracePointSpec.target_thread? + next unless tp.path == __FILE__ + inspect ||= tp.inspect }.enable do line = __LINE__ + 1 @@ -44,7 +48,7 @@ describe 'TracePoint#inspect' do trace_point_spec_test_call end - inspect.should == "#<TracePoint:call `trace_point_spec_test_call'#{@path_prefix}#{__FILE__}:#{line}>" + inspect.should =~ /\A#<TracePoint:call [`']trace_point_spec_test_call'#{@path_prefix}#{__FILE__}:#{line}>\z/ end it 'returns a String showing the event, method, path and line for a :return event' do @@ -52,6 +56,8 @@ describe 'TracePoint#inspect' do line = nil TracePoint.new(:return) { |tp| next unless TracePointSpec.target_thread? + next unless tp.path == __FILE__ + inspect ||= tp.inspect }.enable do line = __LINE__ + 4 @@ -61,14 +67,17 @@ describe 'TracePoint#inspect' do end trace_point_spec_test_return end + ruby_version_is("3.4") { line -= 1 } - inspect.should == "#<TracePoint:return `trace_point_spec_test_return'#{@path_prefix}#{__FILE__}:#{line}>" + inspect.should =~ /\A#<TracePoint:return [`']trace_point_spec_test_return'#{@path_prefix}#{__FILE__}:#{line}>\z/ end it 'returns a String showing the event, method, path and line for a :c_call event' do inspect = nil tracepoint = TracePoint.new(:c_call) { |tp| next unless TracePointSpec.target_thread? + next unless tp.path == __FILE__ + inspect ||= tp.inspect } line = __LINE__ + 2 @@ -76,7 +85,7 @@ describe 'TracePoint#inspect' do [0, 1].max end - inspect.should == "#<TracePoint:c_call `max'#{@path_prefix}#{__FILE__}:#{line}>" + inspect.should =~ /\A#<TracePoint:c_call [`']max'#{@path_prefix}#{__FILE__}:#{line}>\z/ end it 'returns a String showing the event, path and line for a :class event' do @@ -84,6 +93,8 @@ describe 'TracePoint#inspect' do line = nil TracePoint.new(:class) { |tp| next unless TracePointSpec.target_thread? + next unless tp.path == __FILE__ + inspect ||= tp.inspect }.enable do line = __LINE__ + 1 @@ -100,6 +111,7 @@ describe 'TracePoint#inspect' do thread_inspection = nil TracePoint.new(:thread_begin) { |tp| next unless Thread.current == thread + inspect ||= tp.inspect }.enable(target_thread: nil) do thread = Thread.new {} @@ -116,6 +128,7 @@ describe 'TracePoint#inspect' do thread_inspection = nil TracePoint.new(:thread_end) { |tp| next unless Thread.current == thread + inspect ||= tp.inspect }.enable(target_thread: nil) do thread = Thread.new {} diff --git a/spec/ruby/core/tracepoint/raised_exception_spec.rb b/spec/ruby/core/tracepoint/raised_exception_spec.rb index ca2f50abb3..5ac8531840 100644 --- a/spec/ruby/core/tracepoint/raised_exception_spec.rb +++ b/spec/ruby/core/tracepoint/raised_exception_spec.rb @@ -17,4 +17,22 @@ describe 'TracePoint#raised_exception' do raised_exception.should equal(error_result) end end + + ruby_version_is "3.3" do + it 'returns value from exception rescued on the :rescue event' do + raised_exception, error_result = nil + trace = TracePoint.new(:rescue) { |tp| + next unless TracePointSpec.target_thread? + raised_exception = tp.raised_exception + } + trace.enable do + begin + raise StandardError + rescue => e + error_result = e + end + raised_exception.should equal(error_result) + end + end + end end diff --git a/spec/ruby/core/true/singleton_method_spec.rb b/spec/ruby/core/true/singleton_method_spec.rb index f765054c5f..c06793850f 100644 --- a/spec/ruby/core/true/singleton_method_spec.rb +++ b/spec/ruby/core/true/singleton_method_spec.rb @@ -3,10 +3,10 @@ require_relative '../../spec_helper' describe "TrueClass#singleton_method" do ruby_version_is '3.3' do it "raises regardless of whether TrueClass defines the method" do - proc{true.singleton_method(:foo)}.should raise_error(NameError) + -> { true.singleton_method(:foo) }.should raise_error(NameError) begin - def true.foo; end - proc{true.singleton_method(:foo)}.should raise_error(NameError) + def (true).foo; end + -> { true.singleton_method(:foo) }.should raise_error(NameError) ensure TrueClass.send(:remove_method, :foo) end diff --git a/spec/ruby/core/unboundmethod/clone_spec.rb b/spec/ruby/core/unboundmethod/clone_spec.rb index 098ee61476..1e7fb18744 100644 --- a/spec/ruby/core/unboundmethod/clone_spec.rb +++ b/spec/ruby/core/unboundmethod/clone_spec.rb @@ -1,12 +1,13 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' +require_relative 'shared/dup' describe "UnboundMethod#clone" do - it "returns a copy of the UnboundMethod" do - um1 = UnboundMethodSpecs::Methods.instance_method(:foo) - um2 = um1.clone + it_behaves_like :unboundmethod_dup, :clone - (um1 == um2).should == true - um1.bind(UnboundMethodSpecs::Methods.new).call.should == um2.bind(UnboundMethodSpecs::Methods.new).call + it "preserves frozen status" do + method = Class.instance_method(:instance_method) + method.freeze + method.frozen?.should == true + method.clone.frozen?.should == true end end diff --git a/spec/ruby/core/unboundmethod/dup_spec.rb b/spec/ruby/core/unboundmethod/dup_spec.rb new file mode 100644 index 0000000000..5a78dd8e36 --- /dev/null +++ b/spec/ruby/core/unboundmethod/dup_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'shared/dup' + +describe "UnboundMethod#dup" do + ruby_version_is "3.4" do + it_behaves_like :unboundmethod_dup, :dup + + it "resets frozen status" do + method = Class.instance_method(:instance_method) + method.freeze + method.frozen?.should == true + method.dup.frozen?.should == false + end + end +end diff --git a/spec/ruby/core/unboundmethod/equal_value_spec.rb b/spec/ruby/core/unboundmethod/equal_value_spec.rb index 036c6b7f8c..b2d78c50af 100644 --- a/spec/ruby/core/unboundmethod/equal_value_spec.rb +++ b/spec/ruby/core/unboundmethod/equal_value_spec.rb @@ -76,38 +76,19 @@ describe "UnboundMethod#==" do (@identical_body == @original_body).should == false end - ruby_version_is ""..."3.2" do - it "returns false if same method but one extracted from a subclass" do - (@parent == @child1).should == false - (@child1 == @parent).should == false - end - - it "returns false if same method but extracted from two different subclasses" do - (@child2 == @child1).should == false - (@child1 == @child2).should == false - end - - it "returns false if methods are the same but added from an included Module" do - (@includee == @includer).should == false - (@includer == @includee).should == false - end + it "returns true if same method but one extracted from a subclass" do + (@parent == @child1).should == true + (@child1 == @parent).should == true end - ruby_version_is "3.2" do - it "returns true if same method but one extracted from a subclass" do - (@parent == @child1).should == true - (@child1 == @parent).should == true - end - - it "returns false if same method but extracted from two different subclasses" do - (@child2 == @child1).should == true - (@child1 == @child2).should == true - end + it "returns true if same method but extracted from two different subclasses" do + (@child2 == @child1).should == true + (@child1 == @child2).should == true + end - it "returns true if methods are the same but added from an included Module" do - (@includee == @includer).should == true - (@includer == @includee).should == true - end + it "returns true if methods are the same but added from an included Module" do + (@includee == @includer).should == true + (@includer == @includee).should == true end it "returns false if both have same Module, same name, identical body but not the same" do diff --git a/spec/ruby/core/unboundmethod/owner_spec.rb b/spec/ruby/core/unboundmethod/owner_spec.rb index e8a734dac4..b099c56de1 100644 --- a/spec/ruby/core/unboundmethod/owner_spec.rb +++ b/spec/ruby/core/unboundmethod/owner_spec.rb @@ -25,9 +25,7 @@ describe "UnboundMethod#owner" do child_singleton_class.instance_method(:another_class_method).owner.should == child_singleton_class end - ruby_version_is "3.2" do - it "returns the class on which public was called for a private method in ancestor" do - MethodSpecs::InheritedMethods::C.instance_method(:derp).owner.should == MethodSpecs::InheritedMethods::C - end + it "returns the class on which public was called for a private method in ancestor" do + MethodSpecs::InheritedMethods::C.instance_method(:derp).owner.should == MethodSpecs::InheritedMethods::C end end diff --git a/spec/ruby/core/unboundmethod/private_spec.rb b/spec/ruby/core/unboundmethod/private_spec.rb index 8ea50bb5d4..5a563939d1 100644 --- a/spec/ruby/core/unboundmethod/private_spec.rb +++ b/spec/ruby/core/unboundmethod/private_spec.rb @@ -2,27 +2,8 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "UnboundMethod#private?" do - ruby_version_is "3.1"..."3.2" do - it "returns false when the method is public" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_public_method).unbind.private?.should == false - end - - it "returns false when the method is protected" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_protected_method).unbind.private?.should == false - end - - it "returns true when the method is private" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_private_method).unbind.private?.should == true - end - end - - ruby_version_is "3.2" do - it "has been removed" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_private_method).unbind.should_not.respond_to?(:private?) - end + it "has been removed" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:my_private_method).unbind.should_not.respond_to?(:private?) end end diff --git a/spec/ruby/core/unboundmethod/protected_spec.rb b/spec/ruby/core/unboundmethod/protected_spec.rb index 0c215d8638..70622d658d 100644 --- a/spec/ruby/core/unboundmethod/protected_spec.rb +++ b/spec/ruby/core/unboundmethod/protected_spec.rb @@ -2,27 +2,8 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "UnboundMethod#protected?" do - ruby_version_is "3.1"..."3.2" do - it "returns false when the method is public" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_public_method).unbind.protected?.should == false - end - - it "returns true when the method is protected" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_protected_method).unbind.protected?.should == true - end - - it "returns false when the method is private" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_private_method).unbind.protected?.should == false - end - end - - ruby_version_is "3.2" do - it "has been removed" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_protected_method).unbind.should_not.respond_to?(:protected?) - end + it "has been removed" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:my_protected_method).unbind.should_not.respond_to?(:protected?) end end diff --git a/spec/ruby/core/unboundmethod/public_spec.rb b/spec/ruby/core/unboundmethod/public_spec.rb index 552bbf6eab..ae75e2601c 100644 --- a/spec/ruby/core/unboundmethod/public_spec.rb +++ b/spec/ruby/core/unboundmethod/public_spec.rb @@ -2,27 +2,8 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "UnboundMethod#public?" do - ruby_version_is "3.1"..."3.2" do - it "returns true when the method is public" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_public_method).unbind.public?.should == true - end - - it "returns false when the method is protected" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_protected_method).unbind.public?.should == false - end - - it "returns false when the method is private" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_private_method).unbind.public?.should == false - end - end - - ruby_version_is "3.2" do - it "has been removed" do - obj = UnboundMethodSpecs::Methods.new - obj.method(:my_public_method).unbind.should_not.respond_to?(:public?) - end + it "has been removed" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:my_public_method).unbind.should_not.respond_to?(:public?) end end diff --git a/spec/ruby/core/unboundmethod/shared/dup.rb b/spec/ruby/core/unboundmethod/shared/dup.rb new file mode 100644 index 0000000000..194e2cc1a1 --- /dev/null +++ b/spec/ruby/core/unboundmethod/shared/dup.rb @@ -0,0 +1,32 @@ +describe :unboundmethod_dup, shared: true do + it "returns a copy of self" do + a = Class.instance_method(:instance_method) + b = a.send(@method) + + a.should == b + a.should_not equal(b) + end + + ruby_version_is "3.4" do + it "copies instance variables" do + method = Class.instance_method(:instance_method) + method.instance_variable_set(:@ivar, 1) + cl = method.send(@method) + cl.instance_variables.should == [:@ivar] + end + + it "copies the finalizer" do + code = <<-'RUBY' + obj = Class.instance_method(:instance_method) + + ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) + + obj.clone + + exit 0 + RUBY + + ruby_exe(code).lines.sort.should == ["finalized\n", "finalized\n"] + end + end +end diff --git a/spec/ruby/core/unboundmethod/shared/to_s.rb b/spec/ruby/core/unboundmethod/shared/to_s.rb index b92bb0b207..6b2c9c3e79 100644 --- a/spec/ruby/core/unboundmethod/shared/to_s.rb +++ b/spec/ruby/core/unboundmethod/shared/to_s.rb @@ -20,22 +20,11 @@ describe :unboundmethod_to_s, shared: true do it "the String shows the method name, Module defined in and Module extracted from" do @from_module.send(@method).should =~ /\bfrom_mod\b/ @from_module.send(@method).should =~ /\bUnboundMethodSpecs::Mod\b/ - - ruby_version_is ""..."3.2" do - @from_method.send(@method).should =~ /\bUnboundMethodSpecs::Methods\b/ - end end it "returns a String including all details" do - ruby_version_is ""..."3.2" do - @from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Methods(UnboundMethodSpecs::Mod)#from_mod" - @from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Methods(UnboundMethodSpecs::Mod)#from_mod" - end - - ruby_version_is "3.2" do - @from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod" - @from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod" - end + @from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod" + @from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod" end it "does not show the defining module if it is the same as the origin" do diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb index c6823aa84b..9cc2198017 100644 --- a/spec/ruby/core/unboundmethod/source_location_spec.rb +++ b/spec/ruby/core/unboundmethod/source_location_spec.rb @@ -7,23 +7,23 @@ describe "UnboundMethod#source_location" do end it "sets the first value to the path of the file in which the method was defined" do - file = @method.source_location.first + file = @method.source_location[0] file.should be_an_instance_of(String) - file.should == File.realpath('../fixtures/classes.rb', __FILE__) + file.should == File.realpath('fixtures/classes.rb', __dir__) end - it "sets the last value to an Integer representing the line on which the method was defined" do - line = @method.source_location.last + it "sets the second value to an Integer representing the line on which the method was defined" do + line = @method.source_location[1] line.should be_an_instance_of(Integer) line.should == 5 end it "returns the last place the method was defined" do - UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location.last.should == 13 + UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location[1].should == 13 end it "returns the location of the original method even if it was aliased" do - UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location.last.should == 17 + UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location[1].should == 17 end it "works for define_method methods" do @@ -54,6 +54,12 @@ describe "UnboundMethod#source_location" do c = Class.new do eval('def m; end', nil, "foo", 100) end - c.instance_method(:m).source_location.should == ["foo", 100] + location = c.instance_method(:m).source_location + ruby_version_is(""..."4.1") do + location.should == ["foo", 100] + end + ruby_version_is("4.1") do + location.should == ["foo", 100, 0, 100, 10] + end end end diff --git a/spec/ruby/core/unboundmethod/super_method_spec.rb b/spec/ruby/core/unboundmethod/super_method_spec.rb index 101c83b8b3..aa7c129377 100644 --- a/spec/ruby/core/unboundmethod/super_method_spec.rb +++ b/spec/ruby/core/unboundmethod/super_method_spec.rb @@ -40,12 +40,10 @@ describe "UnboundMethod#super_method" do end end - ruby_version_is "2.7.3" do - context "after aliasing an inherited method" do - it "returns the expected super_method" do - method = MethodSpecs::InheritedMethods::C.instance_method(:meow) - method.super_method.owner.should == MethodSpecs::InheritedMethods::A - end + context "after aliasing an inherited method" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.instance_method(:meow) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A end end end diff --git a/spec/ruby/core/warning/categories_spec.rb b/spec/ruby/core/warning/categories_spec.rb new file mode 100644 index 0000000000..1e310ef38b --- /dev/null +++ b/spec/ruby/core/warning/categories_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.4" do + describe "Warning.categories" do + # There might be more, but these are standard across Ruby implementations + it "returns the list of possible warning categories" do + Warning.categories.should.include? :deprecated + Warning.categories.should.include? :experimental + Warning.categories.should.include? :performance + end + end +end diff --git a/spec/ruby/core/warning/element_reference_spec.rb b/spec/ruby/core/warning/element_reference_spec.rb index 41129e533a..c0ed37ef13 100644 --- a/spec/ruby/core/warning/element_reference_spec.rb +++ b/spec/ruby/core/warning/element_reference_spec.rb @@ -1,11 +1,13 @@ require_relative '../../spec_helper' describe "Warning.[]" do - ruby_version_is '2.7.2' do - it "returns default values for categories :deprecated and :experimental" do - ruby_exe('p Warning[:deprecated]').chomp.should == "false" - ruby_exe('p Warning[:experimental]').chomp.should == "true" - end + it "returns default values for categories :deprecated and :experimental" do + # If any warning options were set on the Ruby that will be executed, then + # it's possible this test will fail. In this case we will skip this test. + skip if ruby_exe.any? { |opt| opt.start_with?("-W") } + + ruby_exe('p [Warning[:deprecated], Warning[:experimental]]').chomp.should == "[false, true]" + ruby_exe('p [Warning[:deprecated], Warning[:experimental]]', options: "-w").chomp.should == "[true, true]" end ruby_version_is '3.3' do diff --git a/spec/ruby/core/warning/element_set_spec.rb b/spec/ruby/core/warning/element_set_spec.rb index f439ffde0a..d59a7d4c9e 100644 --- a/spec/ruby/core/warning/element_set_spec.rb +++ b/spec/ruby/core/warning/element_set_spec.rb @@ -17,6 +17,18 @@ describe "Warning.[]=" do end end + ruby_version_is '3.3' do + it "enables or disables performance warnings" do + original = Warning[:performance] + begin + Warning[:performance] = !original + Warning[:performance].should == !original + ensure + Warning[:performance] = original + end + end + end + it "raises for unknown category" do -> { Warning[:noop] = false }.should raise_error(ArgumentError, /unknown category: noop/) end diff --git a/spec/ruby/core/warning/performance_warning_spec.rb b/spec/ruby/core/warning/performance_warning_spec.rb new file mode 100644 index 0000000000..ab0badcd3d --- /dev/null +++ b/spec/ruby/core/warning/performance_warning_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../spec_helper' + + +describe "Performance warnings" do + guard -> { ruby_version_is("3.4") || RUBY_ENGINE == "truffleruby" } do + # Optimising Integer, Float or Symbol methods is kind of implementation detail + # but multiple implementations do so. So it seems reasonable to have a test case + # for at least one such common method. + # See https://bugs.ruby-lang.org/issues/20429 + context "when redefined optimised methods" do + it "emits performance warning for redefining Integer#+" do + code = <<~CODE + Warning[:performance] = true + + class Integer + ORIG_METHOD = instance_method(:+) + + def +(...) + ORIG_METHOD.bind(self).call(...) + end + end + CODE + + ruby_exe(code, args: "2>&1").should.include?("warning: Redefining 'Integer#+' disables interpreter and JIT optimizations") + end + end + end +end diff --git a/spec/ruby/core/warning/warn_spec.rb b/spec/ruby/core/warning/warn_spec.rb index 8f96fe9287..2e4a822e02 100644 --- a/spec/ruby/core/warning/warn_spec.rb +++ b/spec/ruby/core/warning/warn_spec.rb @@ -97,6 +97,20 @@ describe "Warning.warn" do end end + ruby_version_is "3.4" do + it "warns when category is :strict_unused_block but Warning[:strict_unused_block] is false" do + warn_strict_unused_block = Warning[:strict_unused_block] + Warning[:strict_unused_block] = true + begin + -> { + Warning.warn("foo", category: :strict_unused_block) + }.should complain("foo") + ensure + Warning[:strict_unused_block] = warn_strict_unused_block + end + end + end + it "doesn't print message when category is :deprecated but Warning[:deprecated] is false" do warn_deprecated = Warning[:deprecated] Warning[:deprecated] = false @@ -121,6 +135,44 @@ describe "Warning.warn" do end end + ruby_version_is "3.4" do + it "doesn't print message when category is :strict_unused_block but Warning[:strict_unused_block] is false" do + warn_strict_unused_block = Warning[:strict_unused_block] + Warning[:strict_unused_block] = false + begin + -> { + Warning.warn("foo", category: :strict_unused_block) + }.should_not complain + ensure + Warning[:strict_unused_block] = warn_strict_unused_block + end + end + end + + ruby_bug '#20573', ''...'3.4' do + it "isn't called by Kernel.warn when category is :deprecated but Warning[:deprecated] is false" do + warn_deprecated = Warning[:deprecated] + begin + Warning[:deprecated] = false + Warning.should_not_receive(:warn) + Kernel.warn("foo", category: :deprecated) + ensure + Warning[:deprecated] = warn_deprecated + end + end + + it "isn't called by Kernel.warn when category is :experimental but Warning[:experimental] is false" do + warn_experimental = Warning[:experimental] + begin + Warning[:experimental] = false + Warning.should_not_receive(:warn) + Kernel.warn("foo", category: :experimental) + ensure + Warning[:experimental] = warn_experimental + end + end + end + it "prints the message when VERBOSE is false" do -> { Warning.warn("foo") }.should complain("foo") end |
