diff options
Diffstat (limited to 'spec/ruby/core')
636 files changed, 17004 insertions, 7300 deletions
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/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/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 f1206efb3e..b77b2d1efa 100644 --- a/spec/ruby/core/array/pack/buffer_spec.rb +++ b/spec/ruby/core/array/pack/buffer_spec.rb @@ -28,6 +28,16 @@ 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 ] 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 2f70dc3951..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" 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/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/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 fc65a091aa..bd21458ea7 100644 --- a/spec/ruby/core/basicobject/singleton_method_added_spec.rb +++ b/spec/ruby/core/basicobject/singleton_method_added_spec.rb @@ -96,6 +96,8 @@ describe "BasicObject#singleton_method_added" do end }.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 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/irb_spec.rb b/spec/ruby/core/binding/irb_spec.rb deleted file mode 100644 index 2607c7ef33..0000000000 --- a/spec/ruby/core/binding/irb_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require_relative '../../spec_helper' -require 'tmpdir' - -describe "Binding#irb" do - it "creates an IRB session with the binding in scope" do - irb_fixture = fixture __FILE__, "irb.rb" - envs = %w[IRBRC HOME XDG_CONFIG_HOME].to_h {|e| [e, nil]} - - out = Dir.mktmpdir do |dir| - IO.popen([envs, *ruby_exe, irb_fixture, chdir: dir], "r+") do |pipe| - pipe.puts "a ** 2" - pipe.puts "exit" - pipe.readlines.map(&:chomp).reject(&:empty?) - end - end - - out.last(3).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 1224b8ec7d..2d854fce96 100644 --- a/spec/ruby/core/binding/shared/clone.rb +++ b/spec/ruby/core/binding/shared/clone.rb @@ -40,7 +40,7 @@ describe :binding_clone, shared: true do end it "copies the finalizer" do - code = <<-RUBY + code = <<-'RUBY' obj = binding ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) 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 f1c0f63a44..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, /is not a singleton class/) - 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, /[`']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 + 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 c09ed71b31..1ff9abf7b4 100644 --- a/spec/ruby/core/class/dup_spec.rb +++ b/spec/ruby/core/class/dup_spec.rb @@ -59,6 +59,8 @@ 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 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 50eb5358d9..f692152787 100644 --- a/spec/ruby/core/class/subclasses_spec.rb +++ b/spec/ruby/core/class/subclasses_spec.rb @@ -1,87 +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 from the parent" 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 - - it "does not return included modules from the child" 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 included modules from the child" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + parent.include(mod) - 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 - assert_subclasses(parent, [child]) - end + it "does not return prepended modules from the parent" do + parent = Class.new + child = Class.new(parent) + mod = Module.new + parent.prepend(mod) - 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 - 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) - it "does not return singleton classes" do - a = Class.new + assert_subclasses(parent, [child]) + end - a_obj = a.new - def a_obj.force_singleton_class - 42 - end + it "does not return singleton classes" do + a = Class.new - a.subclasses.should_not include(a_obj.singleton_class) + a_obj = a.new + def a_obj.force_singleton_class + 42 end - it "has 1 entry per module or class" do - ModuleSpecs::Parent.subclasses.should == ModuleSpecs::Parent.subclasses.uniq - 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 + 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/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/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 46a6b48bb2..650c0b2a62 100644 --- a/spec/ruby/core/data/fixtures/classes.rb +++ b/spec/ruby/core/data/fixtures/classes.rb @@ -1,5 +1,34 @@ module DataSpecs - guard -> { ruby_version_is "3.2" and Data.respond_to?(:define) } 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 2c36bd3ac4..010c73b91b 100644 --- a/spec/ruby/core/data/initialize_spec.rb +++ b/spec/ruby/core/data/initialize_spec.rb @@ -1,65 +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") + it "accepts keyword arguments" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") - data.amount.should == 42 - data.unit.should == "km" - end + data.amount.should == 42 + data.unit.should == "km" + end + + it "accepts alternative keyword arguments" do + data = DataSpecs::Measure[amount: 42, unit: "km"] - 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 - data.amount.should == 42 - data.unit.should == "km" + 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 "can be overridden" do + ScratchPad.record [] + + measure_class = Data.define(:amount, :unit) do + def initialize(*, **) + super + ScratchPad << :initialize + end end - it "accepts String keyword arguments" do - data = DataSpecs::Measure.new("amount" => 42, "unit" => "km") + measure_class.new(42, "m") + ScratchPad.recorded.should == [:initialize] + end + + 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 - data.amount.should == 42 - data.unit.should == "km" + 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 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 alternative positional arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize[42, "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 keyword arguments" do + ScratchPad.clear + DataSpecs::DataWithOverriddenInitialize[amount: 42, unit: "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") - } + # 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 index 97e34c951f..fd0a99d1fa 100644 --- a/spec/ruby/core/data/with_spec.rb +++ b/spec/ruby/core/data/with_spec.rb @@ -1,35 +1,57 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is "3.2" do - 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 +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") + 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 + 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 "accepts String keyword arguments" do - data = DataSpecs::Measure.new(amount: 42, unit: "km") - data = data.with("amount" => 4, "unit" => "m") + it "raises ArgumentError if no keyword arguments are given" do + data = DataSpecs::Measure.new(amount: 42, unit: "km") - data.amount.should == 4 - data.unit.should == "m" + -> { + 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 - it "raises ArgumentError if no keyword arguments are given" do - data = DataSpecs::Measure.new(amount: 42, unit: "km") + 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) - -> { - data.with(4, "m") - }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 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/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/exist_spec.rb b/spec/ruby/core/dir/exist_spec.rb index 9023de533f..0b8e521894 100644 --- a/spec/ruby/core/dir/exist_spec.rb +++ b/spec/ruby/core/dir/exist_spec.rb @@ -14,10 +14,8 @@ describe "Dir.exist?" do it_behaves_like :dir_exist, :exist? end -ruby_version_is "3.2" do - describe "Dir.exists?" do - it "has been removed" do - Dir.should_not.respond_to?(:exists?) - 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 32f515c81d..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 3cf745ab46..966ac38af3 100644 --- a/spec/ruby/core/dir/home_spec.rb +++ b/spec/ruby/core/dir/home_spec.rb @@ -33,40 +33,32 @@ 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 - old_dirs = [ENV.delete('HOME'), ENV.delete('USERPROFILE'), ENV.delete('HOMEDRIVE'), ENV.delete('HOMEPATH')] + it "retrieves the directory from HOME, USERPROFILE, HOMEDRIVE/HOMEPATH and the WinAPI in that order" do + old_dirs = [ENV.delete('HOME'), ENV.delete('USERPROFILE'), ENV.delete('HOMEDRIVE'), ENV.delete('HOMEPATH')] - Dir.home.should == old_dirs[1].gsub("\\", "/") - ENV['HOMEDRIVE'] = "C:" - ENV['HOMEPATH'] = "\\rubyspec\\home1" - Dir.home.should == "C:/rubyspec/home1" - ENV['USERPROFILE'] = "C:\\rubyspec\\home2" - Dir.home.should == "C:/rubyspec/home2" - ENV['HOME'] = "C:\\rubyspec\\home3" - Dir.home.should == "C:/rubyspec/home3" - ensure - ENV['HOME'], ENV['USERPROFILE'], ENV['HOMEDRIVE'], ENV['HOMEPATH'] = *old_dirs - end + Dir.home.should == old_dirs[1].gsub("\\", "/") + ENV['HOMEDRIVE'] = "C:" + ENV['HOMEPATH'] = "\\rubyspec\\home1" + Dir.home.should == "C:/rubyspec/home1" + ENV['USERPROFILE'] = "C:\\rubyspec\\home2" + Dir.home.should == "C:/rubyspec/home2" + ENV['HOME'] = "C:\\rubyspec\\home3" + Dir.home.should == "C:/rubyspec/home3" + ensure + ENV['HOME'], ENV['USERPROFILE'], ENV['HOMEDRIVE'], ENV['HOMEPATH'] = *old_dirs end end 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/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 2ea4f88a80..3097f57715 100644 --- a/spec/ruby/core/dir/shared/exist.rb +++ b/spec/ruby/core/dir/shared/exist.rb @@ -34,6 +34,7 @@ describe :dir_exist, shared: true do 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 diff --git a/spec/ruby/core/dir/shared/glob.rb b/spec/ruby/core/dir/shared/glob.rb index 745f02d46b..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*".dup.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 f18d8680a9..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' @@ -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".dup.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".dup.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 @@ -161,6 +161,379 @@ describe "Encoding.compatible? String, String" 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 @@ -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 7f249d90a3..8533af4565 100644 --- a/spec/ruby/core/encoding/converter/convert_spec.rb +++ b/spec/ruby/core/encoding/converter/convert_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary # frozen_string_literal: true require_relative '../../../spec_helper' diff --git a/spec/ruby/core/encoding/converter/finish_spec.rb b/spec/ruby/core/encoding/converter/finish_spec.rb index 239243430b..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".dup.force_encoding('iso-2022-jp') - @ec.finish.should == "\e(B".dup.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 78779be70b..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 diff --git a/spec/ruby/core/encoding/converter/new_spec.rb b/spec/ruby/core/encoding/converter/new_spec.rb index db9c3364d7..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 diff --git a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb index 63f25eddef..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,4 @@ -# -*- encoding: binary -*- +# encoding: binary # frozen_string_literal: false require_relative '../../../spec_helper' diff --git a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb index 668eb9a924..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,4 @@ -# -*- encoding: binary -*- +# encoding: binary # frozen_string_literal: false require_relative '../../../spec_helper' diff --git a/spec/ruby/core/encoding/converter/putback_spec.rb b/spec/ruby/core/encoding/converter/putback_spec.rb index e19fe6c314..04bb565655 100644 --- a/spec/ruby/core/encoding/converter/putback_spec.rb +++ b/spec/ruby/core/encoding/converter/putback_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative '../../../spec_helper' describe "Encoding::Converter#putback" do 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/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 8a3f3de69a..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 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 a5e2824984..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' diff --git a/spec/ruby/core/encoding/replicate_spec.rb b/spec/ruby/core/encoding/replicate_spec.rb index e22673db7d..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 @@ -67,7 +67,7 @@ describe "Encoding#replicate" do 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') 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 2701c6999c..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 @@ -343,9 +345,6 @@ module EnumerableSpecs end end - # Set is a core class since Ruby 3.2 - ruby_version_is '3.2' do - class SetSubclass < Set - 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 index c21a2772c4..c02ead11fa 100644 --- a/spec/ruby/core/enumerable/to_set_spec.rb +++ b/spec/ruby/core/enumerable/to_set_spec.rb @@ -1,29 +1,30 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -ruby_version_is "3.2" do - 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 +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 + 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 = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass) + 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 - it "does not need explicit `require 'set'`" do - output = ruby_exe(<<~RUBY, options: '--disable-gems', args: '2>&1') - puts [1, 2, 3].to_set - RUBY - - output.chomp.should == "#<Set: {1, 2, 3}>" + 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/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/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/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/product/each_spec.rb b/spec/ruby/core/enumerator/product/each_spec.rb index cabeb9d93a..88f115a712 100644 --- a/spec/ruby/core/enumerator/product/each_spec.rb +++ b/spec/ruby/core/enumerator/product/each_spec.rb @@ -1,73 +1,71 @@ require_relative '../../../spec_helper' require_relative '../../enumerable/shared/enumeratorized' -ruby_version_is "3.2" do - describe "Enumerator::Product#each" do - it_behaves_like :enumeratorized_with_origin_size, :each, Enumerator::Product.new([1, 2], [:a, :b]) +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 + 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 - enum = Enumerator::Product.new(object1, object2) - acc = [] - enum.each { |e| acc << e } - acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + it "calls #each_entry method on enumerators" do + object1 = Object.new + def object1.each_entry + yield 1 + yield 2 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/) + object2 = Object.new + def object2.each_entry + yield :a + yield :b 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(object1, object2) + acc = [] + enum.each { |e| acc << e } + acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end - enum = Enumerator::Product.new([1, 2], [:a, :b]) - enum.each.to_a.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 self if given a block" do - enum = Enumerator::Product.new([1, 2], [:a, :b]) - enum.each {}.should.equal?(enum) - end + it "returns enumerator if not given a block" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each.should.kind_of?(Enumerator) - it "doesn't accept arguments" do - Enumerator::Product.instance_method(:each).arity.should == 0 - end + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]] + end - it "yields each element to a block that takes multiple arguments" do - enum = Enumerator::Product.new([1, 2], [:a, :b]) + it "returns self if given a block" do + enum = Enumerator::Product.new([1, 2], [:a, :b]) + enum.each {}.should.equal?(enum) + end - acc = [] - enum.each { |x, y| acc << x } - acc.should == [1, 1, 2, 2] + it "doesn't accept arguments" do + Enumerator::Product.instance_method(:each).arity.should == 0 + end - acc = [] - enum.each { |x, y| acc << y } - acc.should == [:a, :b, :a, :b] + 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, z| acc << z } - acc.should == [nil, nil, nil, nil] - end + 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 index 46e8421322..b1b9f3ca9b 100644 --- a/spec/ruby/core/enumerator/product/initialize_copy_spec.rb +++ b/spec/ruby/core/enumerator/product/initialize_copy_spec.rb @@ -1,54 +1,52 @@ require_relative '../../../spec_helper' -ruby_version_is "3.2" do - 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]) +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 + 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]) + 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 + 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 "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) + 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 + 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) + 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 + -> { 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) + 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 + -> { 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 + 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 + -> { 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 index 4b60564240..ed2a8a2a13 100644 --- a/spec/ruby/core/enumerator/product/initialize_spec.rb +++ b/spec/ruby/core/enumerator/product/initialize_spec.rb @@ -1,33 +1,31 @@ require_relative '../../../spec_helper' -ruby_version_is "3.2" do - describe "Enumerator::Product#initialize" do - before :each do - @uninitialized = Enumerator::Product.allocate - end +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 "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 "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 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 + 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 + 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 index 1ea8e9c49b..e0d7441f26 100644 --- a/spec/ruby/core/enumerator/product/inspect_spec.rb +++ b/spec/ruby/core/enumerator/product/inspect_spec.rb @@ -1,22 +1,20 @@ require_relative '../../../spec_helper' -ruby_version_is "3.2" do - 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 +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) + 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 + 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 + 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 index e8ee730239..2beffaf5c1 100644 --- a/spec/ruby/core/enumerator/product/rewind_spec.rb +++ b/spec/ruby/core/enumerator/product/rewind_spec.rb @@ -1,64 +1,62 @@ require_relative '../../../spec_helper' -ruby_version_is "3.2" do - describe "Enumerator::Product#rewind" do - before :each do - @enum = Enumerator::Product.new([1, 2].each.to_enum, [:a, :b].each.to_enum) - end +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 "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 "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 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 "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) + 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 + 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) + 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 + enum.rewind.should == enum + end - it "calls a rewind method on each enumerable in direct order" do - ScratchPad.record [] + it "calls a rewind method on each enumerable in direct order" do + ScratchPad.record [] - object1 = Object.new - def object1.rewind; ScratchPad << :object1; end + object1 = Object.new + def object1.rewind; ScratchPad << :object1; end - object2 = Object.new - def object2.rewind; ScratchPad << :object2; end + object2 = Object.new + def object2.rewind; ScratchPad << :object2; end - object3 = Object.new - def object3.rewind; ScratchPad << :object3; end + object3 = Object.new + def object3.rewind; ScratchPad << :object3; end - enum = Enumerator::Product.new(object1, object2, object3) - enum.rewind + enum = Enumerator::Product.new(object1, object2, object3) + enum.rewind - ScratchPad.recorded.should == [:object1, :object2, :object3] - end + 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 index 46958b1a22..96632d6eee 100644 --- a/spec/ruby/core/enumerator/product/size_spec.rb +++ b/spec/ruby/core/enumerator/product/size_spec.rb @@ -1,56 +1,54 @@ require_relative '../../../spec_helper' -ruby_version_is "3.2" do - 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 +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 0acca6690e..83c7cb8e0e 100644 --- a/spec/ruby/core/enumerator/product_spec.rb +++ b/spec/ruby/core/enumerator/product_spec.rb @@ -1,93 +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 - - it "accepts a list of enumerators of any length" do - enum = Enumerator.product(1..2) - enum.to_a.should == [[1], [2]] +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 - enum = Enumerator.product(1..2, ["A"]) - enum.to_a.should == [[1, "A"], [2, "A"]] + 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"], ["B"]) - enum.to_a.should == [[1, "A", "B"], [2, "A", "B"]] + enum = Enumerator.product(1..2, ["A"]) + enum.to_a.should == [[1, "A"], [2, "A"]] - enum = Enumerator.product(2..3, ["A"], ["B"], ["C"]) - enum.to_a.should == [[2, "A", "B", "C"], [3, "A", "B", "C"]] - end + enum = Enumerator.product(1..2, ["A"], ["B"]) + enum.to_a.should == [[1, "A", "B"], [2, "A", "B"]] - it "returns an enumerator with an empty array when no arguments passed" do - enum = Enumerator.product - enum.to_a.should == [[]] - 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 instance of Enumerator::Product" do - enum = Enumerator.product - enum.class.should == Enumerator::Product - end + it "returns an enumerator with an empty array when no arguments passed" do + enum = Enumerator.product + enum.to_a.should == [[]] + 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 "returns an instance of Enumerator::Product" do + enum = Enumerator.product + enum.class.should == Enumerator::Product + end - it "accepts a block" do - elems = [] - enum = Enumerator.product(1..2, ["X", "Y"]) { elems << _1 } + 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 - elems.should == [[1, "X"], [1, "Y"], [2, "X"], [2, "Y"]] - end + it "accepts a block" do + elems = [] + enum = Enumerator.product(1..2, ["X", "Y"]) { elems << _1 } - it "returns nil when a block passed" do - Enumerator.product(1..2) {}.should == nil - end + elems.should == [[1, "X"], [1, "Y"], [2, "X"], [2, "Y"]] + 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 + 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/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/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/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/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/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/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/detailed_message_spec.rb b/spec/ruby/core/exception/detailed_message_spec.rb index fbe4443daa..9df164a1cf 100644 --- a/spec/ruby/core/exception/detailed_message_spec.rb +++ b/spec/ruby/core/exception/detailed_message_spec.rb @@ -2,42 +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 "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>" + 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 "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 "returns just a message if exception class is anonymous" do + Class.new(RuntimeError).new("message").detailed_message.should == "message" + end - it "allows and ignores other keyword arguments" do - RuntimeError.new("new error").detailed_message(foo: true).should == "new error (RuntimeError)" - 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 a message if exception class is anonymous" do - Class.new(RuntimeError).new("message").detailed_message.should == "message" - 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 'unhandled exception' for an instance of RuntimeError with empty message" do - RuntimeError.new("").detailed_message.should == "unhandled exception" - 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 just class name for an instance of RuntimeError subclass with empty message" do - DetailedMessageSpec::C.new("").detailed_message.should == "DetailedMessageSpec::C" - 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 "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 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/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/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb index 979ec2ff98..af2e925661 100644 --- a/spec/ruby/core/exception/frozen_error_spec.rb +++ b/spec/ruby/core/exception/frozen_error_spec.rb @@ -21,6 +21,22 @@ describe "FrozenError#receiver" do 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 diff --git a/spec/ruby/core/exception/full_message_spec.rb b/spec/ruby/core/exception/full_message_spec.rb index 5154354555..0761d2b40c 100644 --- a/spec/ruby/core/exception/full_message_spec.rb +++ b/spec/ruby/core/exception/full_message_spec.rb @@ -79,6 +79,24 @@ describe "Exception#full_message" do 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 @@ -147,52 +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) { |**| "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/ + e.full_message.lines.first.should =~ /DETAILED MESSAGE/ + end + + 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 (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 + e.full_message(foo: "bar") + options_passed.should == { foo: "bar", highlight: Exception.to_tty? } + 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 - 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 = RuntimeError.new("new error") - e.define_singleton_method(:detailed_message) { |**| message } + e.full_message.lines.first.should =~ /DETAILED MESSAGE/ + end - 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 } - 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(highlight: false).lines.first.should =~ /RuntimeError/ - e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/ + 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 - 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 - e.full_message(highlight: false).lines.first.should =~ /RuntimeError/ - e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/ + 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/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb index 26df3338e9..772c569f67 100644 --- a/spec/ruby/core/exception/no_method_error_spec.rb +++ b/spec/ruby/core/exception/no_method_error_spec.rb @@ -67,7 +67,7 @@ describe "NoMethodError#message" do 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 12c1da919c..2cd93326ec 100644 --- a/spec/ruby/core/exception/set_backtrace_spec.rb +++ b/spec/ruby/core/exception/set_backtrace_spec.rb @@ -1,13 +1,8 @@ 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 @@ -20,65 +15,9 @@ describe "Exception#set_backtrace" do err.backtrace_locations.should == nil 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 = RuntimeError.new - err.backtrace.should == nil - err.backtrace_locations.should == nil - - err.set_backtrace 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 = RuntimeError.new - err.set_backtrace [] - err.backtrace.should == [] - end - - it "accepts a String" do + it_behaves_like :exception_set_backtrace, -> backtrace { 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) - end - - it "raises a TypeError when the Array contains a Symbol" do - 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 index 6cc8522de3..4c713a3507 100644 --- a/spec/ruby/core/exception/syntax_error_spec.rb +++ b/spec/ruby/core/exception/syntax_error_spec.rb @@ -1,27 +1,25 @@ require_relative '../../spec_helper' -ruby_version_is "3.2" do - describe "SyntaxError#path" do - it "returns the file path provided to eval" do - filename = "speccing.rb" +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 + -> { + 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") + 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 + -> { + 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 + 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/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 b3e021e636..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| @@ -130,7 +131,6 @@ 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 5c87ed5d41..015caaf3bb 100644 --- a/spec/ruby/core/fiber/storage_spec.rb +++ b/spec/ruby/core/fiber/storage_spec.rb @@ -1,158 +1,181 @@ require_relative '../../spec_helper' -ruby_version_is "3.2" do - describe "Fiber.new(storage:)" 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 +describe "Fiber.new(storage:)" 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 lazily initialized storage" do - Fiber.new(storage: nil) { Fiber[:x] = 10; Fiber.current.storage }.resume.should == {x: 10} - end + 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} + 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 "cannot create a fiber with non-hash storage" do - -> { Fiber.new(storage: 42) {} }.should raise_error(TypeError) - end + 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 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 + 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 +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} +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 "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} + 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 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 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 + 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.[]" 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 +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 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 - it "returns nil if the current fiber has no storage" do - Fiber.new { Fiber[:life] }.resume.should be_nil + 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 - 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 + it "can't use invalid keys" do + invalid_keys = [Object.new, 12] + invalid_keys.each do |key| + -> { Fiber[key] }.should raise_error(TypeError) end + end + end - it "can't use invalid keys" do - invalid_keys = [Object.new, "Foo", 12] - invalid_keys.each do |key| - -> { Fiber[key] }.should raise_error(TypeError) - end - end + 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 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 + 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 access the storage of the fiber with non-symbol keys" do - -> { Fiber[Object.new] }.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 - describe "Fiber.[]=" 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 "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 "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 + 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 "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 "can't access the storage of the fiber with non-symbol keys" do + -> { Fiber[Object.new] }.should raise_error(TypeError) + end +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 +describe "Fiber.[]=" 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[: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 + end - it "can't access the storage of the fiber with non-symbol keys" do - -> { Fiber[Object.new] = 44 }.should raise_error(TypeError) + 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 - 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 == {} - 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 == {} end end +end - describe "Thread.new" 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} +describe "Thread.new" 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} 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/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/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 2633376880..b5600e5b07 100644 --- a/spec/ruby/core/file/exist_spec.rb +++ b/spec/ruby/core/file/exist_spec.rb @@ -5,10 +5,8 @@ describe "File.exist?" do it_behaves_like :file_exist, :exist?, File end -ruby_version_is "3.2" do - describe "File.exists?" do - it "has been removed" do - File.should_not.respond_to?(:exists?) - 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/flock_spec.rb b/spec/ruby/core/file/flock_spec.rb index 070d830bc4..23ddf89ed8 100644 --- a/spec/ruby/core/file/flock_spec.rb +++ b/spec/ruby/core/file/flock_spec.rb @@ -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/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/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/path.rb b/spec/ruby/core/file/shared/path.rb index aa2a64cf25..5a9fe1b0c5 100644 --- a/spec/ruby/core/file/shared/path.rb +++ b/spec/ruby/core/file/shared/path.rb @@ -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 index 9c063a8e93..3fe7266a00 100644 --- a/spec/ruby/core/file/shared/update_time.rb +++ b/spec/ruby/core/file/shared/update_time.rb @@ -84,7 +84,7 @@ describe :update_time, shared: true do end platform_is :linux do - platform_is wordsize: 64 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." 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/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 a95d3f91a1..612ffa9fcb 100644 --- a/spec/ruby/core/filetest/exist_spec.rb +++ b/spec/ruby/core/filetest/exist_spec.rb @@ -5,10 +5,8 @@ describe "FileTest.exist?" do it_behaves_like :file_exist, :exist?, FileTest end -ruby_version_is "3.2" do - describe "FileTest.exists?" do - it "has been removed" do - FileTest.should_not.respond_to?(:exists?) - 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/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/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/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb index 94e8237839..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 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 19eb806dc4..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 omitting 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/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 dd1bb52bac..72a462a42f 100644 --- a/spec/ruby/core/hash/shared/store.rb +++ b/spec/ruby/core/hash/shared/store.rb @@ -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 7864d7cd4c..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.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"}' + 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/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/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 418e89213b..9e36b84da9 100644 --- a/spec/ruby/core/io/binread_spec.rb +++ b/spec/ruby/core/io/binread_spec.rb @@ -45,7 +45,7 @@ describe "IO.binread" do -> { IO.binread @fname, 0, -1 }.should raise_error(Errno::EINVAL) end - ruby_version_is "3.3" do + 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" 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/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/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 c361d27879..6abe8901ba 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -14,33 +14,35 @@ 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" - end + 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 } + 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 - 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 = $$ - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach("|-") { |l| ScratchPad << l } - end + 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 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/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 6d93b432c2..dc7bcedf3e 100644 --- a/spec/ruby/core/io/pread_spec.rb +++ b/spec/ruby/core/io/pread_spec.rb @@ -59,6 +59,12 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } 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 diff --git a/spec/ruby/core/io/puts_spec.rb b/spec/ruby/core/io/puts_spec.rb index 9ed343c94c..a186ddaa5d 100644 --- a/spec/ruby/core/io/puts_spec.rb +++ b/spec/ruby/core/io/puts_spec.rb @@ -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/read_spec.rb b/spec/ruby/core/io/read_spec.rb index 8741d9f017..988ec2ce30 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -168,76 +168,78 @@ 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" - end - - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read(cmd).should == "hello\n" - end - end +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 - 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("|-") + IO.read(cmd).should == "hello\n" end + end - 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" - 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" + end - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read(cmd, 1).should == "h" + suppress_warning do # https://bugs.ruby-lang.org/issues/19630 + IO.read(cmd, 1).should == "h" + end end - end - 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) + 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 - -> { - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|cmd.exe /C echo hello", 1, 1) + 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 - }.should raise_error(Errno::EINVAL) + 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.read(cmd) - }.should complain(/IO process creation with a leading '\|'/) + 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 diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb index 3a6ff3d0f3..b4770775d1 100644 --- a/spec/ruby/core/io/readlines_spec.rb +++ b/spec/ruby/core/io/readlines_spec.rb @@ -174,45 +174,47 @@ 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 = nil - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - lines = IO.readlines(cmd) - end - 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 = nil suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - lines = IO.readlines("|-") + 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 - 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 '\|'/) + 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 diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb index 0852f20b2d..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' @@ -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 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 aca622834f..0747f31b8a 100644 --- a/spec/ruby/core/io/shared/each.rb +++ b/spec/ruby/core/io/shared/each.rb @@ -202,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 cba5f33ebf..e84133493c 100644 --- a/spec/ruby/core/io/shared/new.rb +++ b/spec/ruby/core/io/shared/new.rb @@ -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/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb index 8851214283..d56a27b3af 100644 --- a/spec/ruby/core/io/sysread_spec.rb +++ b/spec/ruby/core/io/sysread_spec.rb @@ -131,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 4a26f8dbaf..e58100f846 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -220,7 +220,7 @@ describe "IO.write" do end end - ruby_version_is "3.3" do + 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 -> { diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index 0f83cb5824..9c436b05f7 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -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/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/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 aaacd9a910..a917dba504 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -83,7 +83,7 @@ describe 'Kernel#caller_locations' do end end - ruby_version_is "3.4" do + 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:') @@ -94,5 +94,17 @@ describe 'Kernel#caller_locations' do 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 c3d63ccb00..7cd703de5a 100644 --- a/spec/ruby/core/kernel/caller_spec.rb +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -43,26 +43,67 @@ describe 'Kernel#caller' do 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:') + 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<internal:.*in [`'](?:Kernel#)?tap'\z/ + 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/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb index 454bc4a58e..e027294347 100644 --- a/spec/ruby/core/kernel/eval_spec.rb +++ b/spec/ruby/core/kernel/eval_spec.rb @@ -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__})" @@ -274,6 +343,26 @@ describe "Kernel#eval" 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 @@ -293,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 @@ -306,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 @@ -319,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 @@ -333,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 @@ -347,6 +444,8 @@ 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 @@ -365,6 +464,8 @@ CODE EvalSpecs::VÏ€string.should == "frozen" EvalSpecs::VÏ€string.encoding.should == Encoding::UTF_8 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 @@ -380,6 +481,8 @@ 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 @@ -400,6 +503,8 @@ CODE value.should == "frozen" value.encoding.should == Encoding::BINARY 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 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/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/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 f8dd82fad8..082e56fed7 100644 --- a/spec/ruby/core/kernel/not_match_spec.rb +++ b/spec/ruby/core/kernel/not_match_spec.rb @@ -14,18 +14,8 @@ describe "Kernel#!~" do (obj !~ :foo).should == false end - ruby_version_is ""..."3.2" do - it "returns true if self does not respond to #=~" do - suppress_warning do - (Object.new !~ :foo).should == true - end - end - end - - ruby_version_is "3.2" do - it "raises NoMethodError if self does not respond to #=~" do - -> { Object.new !~ :foo }.should raise_error(NoMethodError) - 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 diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index bb42c31f31..b967d5044b 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -27,64 +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 - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @io = open("|date") + 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 - 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") { |f| f.read } + 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 - @output.should_not == '' - end - 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(".") + 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 - suppress_warning do # https://bugs.ruby-lang.org/issues/19630 - @io = open("|date /t") + 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 - 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 } + 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 - @output.should_not == '' 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" - -> { - open(cmd) { |f| f.read } - }.should complain(/Kernel#open with a leading '\|'/) + 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 @@ -106,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/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_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 4029e68725..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 @@ -31,9 +32,14 @@ describe "Kernel#require" do 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 0fe2d5ce16..62c5c7be9b 100644 --- a/spec/ruby/core/kernel/shared/load.rb +++ b/spec/ruby/core/kernel/shared/load.rb @@ -165,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 250813191b..52f86f73e5 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -223,7 +223,7 @@ describe :kernel_require, shared: true 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(Exception, /file too short|not a mach-o file/) + -> { @object.send(@method, path) }.should raise_error(LoadError) end end @@ -231,7 +231,7 @@ describe :kernel_require, shared: true 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(Exception, /load_fixture\.bundle.+(file too short|not a mach-o file)/) + -> { @object.send(@method, path) }.should raise_error(LoadError) end end @@ -296,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 @@ -815,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 13dc6e97f0..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 @@ -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/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 0570629723..e9c600aac4 100644 --- a/spec/ruby/core/kernel/sleep_spec.rb +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative '../fiber/fixtures/scheduler' describe "Kernel#sleep" do it "is a private method" do @@ -21,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 @@ -51,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) @@ -73,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 9ef7f86f16..5a4a90ff7a 100644 --- a/spec/ruby/core/kernel/sprintf_spec.rb +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -13,28 +13,52 @@ 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 { - sprintf(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 { - Kernel.sprintf(format, *args) + r = nil + -> { + r = Kernel.sprintf(format, *args) + }.should_not complain(verbose: true) + r } end 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/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 0f77279a4f..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 @@ -93,6 +98,11 @@ describe "Marshal.dump" do 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 @@ -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".dup.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".dup.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) @@ -275,12 +349,47 @@ describe "Marshal.dump" 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 @@ -338,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 @@ -405,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 @@ -440,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 @@ -459,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 @@ -497,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 @@ -531,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 @@ -623,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 @@ -684,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".dup.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 @@ -743,6 +962,21 @@ describe "Marshal.dump" do 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 anonymous_class = Class.new(Exception) obj = anonymous_class.new diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb index a508b6bea1..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) @@ -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.dup.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 @@ -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 f599042529..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,250 +23,164 @@ 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.instance_variable_set(:@foo, 5) @@ -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 @@ -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 @@ -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 @@ -1049,7 +1065,7 @@ describe :marshal_load, shared: true do 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/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 b27267fd0e..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 the 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 bounds" 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 806db2d7b5..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| 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/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/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 8495aef4d2..f1c2523cf0 100644 --- a/spec/ruby/core/method/parameters_spec.rb +++ b/spec/ruby/core/method/parameters_spec.rb @@ -233,54 +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 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 + it "adds rest arg with name * for \"star\" argument" do + m = MethodSpecs::Methods.new + m.method(:one_unnamed_splat).parameters.should == [[:rest, :*]] 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]] - end - - it "adds nameless keyrest arg for \"double star\" argument" do - m = MethodSpecs::Methods.new - m.method(:one_unnamed_keyrest).parameters.should == [[:keyrest]] - 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.1' do - it "adds block arg with name & for anonymous block argument" do - object = Object.new - - eval(<<~RUBY).should == [[:block, :&]] - def object.foo(&) - end - object.method(:foo).parameters - RUBY + it "adds block arg with name & for anonymous block argument" do + object = Object.new + def object.foo(&) end - end - ruby_version_is ""..."3.1" do - it "returns [:rest, :*], [:block, :&] for forward parameters operator" do - m = MethodSpecs::Methods.new - m.method(:forward_parameters).parameters.should == [[:rest, :*], [:block, :&]] - end + object.method(:foo).parameters.should == [[:block, :&]] end - ruby_version_is "3.1" do - 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 [: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 index 1a10b90689..c74847083f 100644 --- a/spec/ruby/core/method/shared/dup.rb +++ b/spec/ruby/core/method/shared/dup.rb @@ -16,7 +16,7 @@ describe :method_dup, shared: true do end it "copies the finalizer" do - code = <<-RUBY + code = <<-'RUBY' obj = Object.new.method(:method) ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb index c5b296f6e2..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', __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/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/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb index 45d18b8608..625d945686 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -486,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 @@ -624,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 @@ -766,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 f9edda3a07..90cd36551a 100644 --- a/spec/ruby/core/module/const_added_spec.rb +++ b/spec/ruby/core/module/const_added_spec.rb @@ -1,160 +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 "returns nil in the default implementation" do - Module.new do - const_added(:TEST).should == nil + 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 "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 + parent = Class.new do + def self.inherited(_) + ScratchPad << :inherited end + end - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - TEST = 1 - RUBY + class mod::C < parent; end - ScratchPad.recorded.should == [:TEST] - end + ScratchPad.recorded.should == [:const_added, :inherited] + end - it "is called when a new constant is assigned on self through const_set" do - ScratchPad.record [] + it "the superclass of a class assigned to a constant is set before const_added is called" do + ScratchPad.record [] - mod = Module.new do - def self.const_added(name) - ScratchPad << name - end + parent = Class.new do + def self.const_added(name) + ScratchPad << name + ScratchPad << const_get(name).superclass end + end + + class parent::C < parent; end - mod.const_set(:TEST, 1) + ScratchPad.recorded.should == [:C, parent] + end + + it "is called when a new constant is assigned on 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) + 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.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - module SubModule - end + mod.const_set(:TEST, 1) - module SubModule - end - RUBY + ScratchPad.recorded.should == [:TEST] + end - ScratchPad.recorded.should == [:SubModule] + it "is called when a new module 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) + module SubModule + end + + module SubModule + end + RUBY + + 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 - mod = Module.new do + module self::A def self.const_added(name) ScratchPad << name end - end - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - class SubClass + module self::B end + end + end - class SubClass - end - RUBY + ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModule + end + + it "is called when a new class is defined under self" 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.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + class SubClass + end - mod = Module.new do - def self.const_added(name) - ScratchPad << name - end + class SubClass end + RUBY - mod.autoload :Autoload, "foo" - ScratchPad.recorded.should == [:Autoload] - end + 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 [] - it "is called with a precise caller location with the line of definition" 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) - location = caller_locations(1, 1)[0] - ScratchPad << location.lineno + ScratchPad << name + end + + class self::B end end + end - line = __LINE__ - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - TEST = 1 + ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModuleB + end - module SubModule - end + it "is called when an autoload is defined" do + ScratchPad.record [] - class SubClass - end - RUBY + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end - mod.const_set(:CONST_SET, 1) + mod.autoload :Autoload, "foo" + ScratchPad.recorded.should == [:Autoload] + end - ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11] + it "is called with a precise caller location with the line of definition" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + location = caller_locations(1, 1)[0] + ScratchPad << location.lineno + end end - it "is called when the constant is already assigned a value" do - ScratchPad.record [] + line = __LINE__ + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 1 - mod = Module.new do - def self.const_added(name) - ScratchPad.record const_get(name) - end + module SubModule end - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - TEST = 123 - RUBY + class SubClass + end + RUBY - ScratchPad.recorded.should == 123 - end + mod.const_set(:CONST_SET, 1) - it "records re-definition of existing constants" do - ScratchPad.record [] + ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11] + end - mod = Module.new do - def self.const_added(name) - ScratchPad << const_get(name) - end + it "is called when the constant is already assigned a value" do + ScratchPad.record [] + + 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 - -> { - mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) - TEST = 123 - TEST = 456 - RUBY - }.should complain(/warning: already initialized constant .+::TEST/) + ScratchPad.recorded.should == 123 + end + + it "records re-definition of existing constants" do + ScratchPad.record [] - ScratchPad.recorded.should == [123, 456] + 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 c194c9113f..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 @@ -243,6 +273,8 @@ describe "Module#const_source_location" do 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 diff --git a/spec/ruby/core/module/define_method_spec.rb b/spec/ruby/core/module/define_method_spec.rb index e04bb87ceb..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 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/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 78f6b41031..210918b2e7 100644 --- a/spec/ruby/core/module/include_spec.rb +++ b/spec/ruby/core/module/include_spec.rb @@ -44,35 +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) - end - - ruby_version_is ""..."3.2" do - it "raises ArgumentError 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(ArgumentError, "refinement module is not allowed") + 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 - ruby_version_is "3.2" do - it "raises a TypeError when the argument is a refinement" do - refinement = nil + it "raises a TypeError when the argument is a refinement" do + refinement = nil - Module.new do - refine String do - refinement = self - end + Module.new do + refine String do + refinement = self end - - -> { ModuleSpecs::Basic.include(refinement) }.should raise_error(TypeError, "Cannot include refinement") end + + -> { ModuleSpecs::Basic.include(refinement) }.should raise_error(TypeError, "Cannot include refinement") end it "imports constants to modules and classes" do @@ -427,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 @@ -453,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 @@ -479,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 @@ -507,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 @@ -531,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 @@ -559,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 @@ -581,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 b40d12f0de..71e82c513e 100644 --- a/spec/ruby/core/module/prepend_spec.rb +++ b/spec/ruby/core/module/prepend_spec.rb @@ -261,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 @@ -281,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 @@ -302,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 @@ -325,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 @@ -348,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 @@ -372,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 @@ -399,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 @@ -425,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 @@ -432,35 +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) - end - - ruby_version_is ""..."3.2" do - it "raises ArgumentError 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(ArgumentError, "refinement module is not allowed") + 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 - ruby_version_is "3.2" do - it "raises a TypeError when the argument is a refinement" do - refinement = nil + it "raises a TypeError when the argument is a refinement" do + refinement = nil - Module.new do - refine String do - refinement = self - end + Module.new do + refine String do + refinement = self end - - -> { ModuleSpecs::Basic.prepend(refinement) }.should raise_error(TypeError, "Cannot prepend refinement") end + + -> { ModuleSpecs::Basic.prepend(refinement) }.should raise_error(TypeError, "Cannot prepend refinement") end it "imports constants" do @@ -787,34 +791,17 @@ describe "Module#prepend" do # https://bugs.ruby-lang.org/issues/17423 describe "when module already exists in ancestor chain" do - ruby_version_is ""..."3.1" do - it "does not modify 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(4).should == [b, m, a, Object] - end - end + it "modifies the ancestor chain" do + m = Module.new do; end + a = Module.new do; end + b = Class.new do; end - ruby_version_is "3.1" 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.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 + b.prepend(m) + b.ancestors.take(5).should == [m, b, m, a, Object] end end 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 aca419f522..652f9f7083 100644 --- a/spec/ruby/core/module/ruby2_keywords_spec.rb +++ b/spec/ruby/core/module/ruby2_keywords_spec.rb @@ -76,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 @@ -275,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 @@ -286,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 @@ -294,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/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/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/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 effecc41d0..0f4b54c345 100644 --- a/spec/ruby/core/objectspace/define_finalizer_spec.rb +++ b/spec/ruby/core/objectspace/define_finalizer_spec.rb @@ -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 c427e01ca5..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) @@ -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/clone_spec.rb b/spec/ruby/core/proc/clone_spec.rb index 7eca9c561e..730dc421a8 100644 --- a/spec/ruby/core/proc/clone_spec.rb +++ b/spec/ruby/core/proc/clone_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/common' require_relative 'shared/dup' describe "Proc#clone" do @@ -12,4 +13,18 @@ describe "Proc#clone" do 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 dd19b3c1e9..716357d1f0 100644 --- a/spec/ruby/core/proc/dup_spec.rb +++ b/spec/ruby/core/proc/dup_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/common' require_relative 'shared/dup' describe "Proc#dup" do @@ -10,4 +11,18 @@ describe "Proc#dup" do 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/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index 972596d2ea..cf8a8f5b12 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -20,29 +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 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 "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 "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 "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 @@ -64,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 @@ -110,30 +108,16 @@ describe "Proc#parameters" do -> x { }.parameters.should == [[:req, :x]] end - ruby_version_is '3.2' do - 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 rest arg with name * for \"star\" argument" do + -> * {}.parameters.should == [[:rest, :*]] end - ruby_version_is ''...'3.2' do - it "adds nameless rest arg for \"star\" argument" do - -> * {}.parameters.should == [[:rest]] - end - - it "adds nameless keyrest arg for \"double star\" argument" do - -> ** {}.parameters.should == [[:keyrest]] - end + it "adds keyrest arg with ** as a name for \"double star\" argument" do + -> ** {}.parameters.should == [[:keyrest, :**]] end - ruby_version_is '3.1' do - it "adds block arg with name & for anonymous block argument" do - eval('-> & {}.parameters').should == [[:block, :&]] - 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 @@ -174,4 +158,18 @@ describe "Proc#parameters" do 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 c419a4078a..1266337f94 100644 --- a/spec/ruby/core/proc/shared/dup.rb +++ b/spec/ruby/core/proc/shared/dup.rb @@ -8,12 +8,10 @@ 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 - end + cl.new{}.send(@method).class.should == cl end ruby_version_is "3.4" do @@ -25,7 +23,7 @@ describe :proc_dup, shared: true do end it "copies the finalizer" do - code = <<-RUBY + code = <<-'RUBY' obj = Proc.new { } ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) diff --git a/spec/ruby/core/proc/source_location_spec.rb b/spec/ruby/core/proc/source_location_spec.rb index a8b99287d5..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', __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', __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', __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', __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/constants_spec.rb b/spec/ruby/core/process/constants_spec.rb index 616c54b8e1..57cacadef2 100644 --- a/spec/ruby/core/process/constants_spec.rb +++ b/spec/ruby/core/process/constants_spec.rb @@ -2,69 +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 "has the correct constant values on NetBSD and FreeBSD" 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 "has the correct constant values on FreeBSD" do - Process::RLIMIT_NPTS.should == 11 + 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/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/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/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/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/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 b42dc61234..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' 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/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 eb7cddc967..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' 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 277de205d1..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' 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 f36a2cef8b..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") 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 a1fe3ce17d..1a3ddd197e 100644 --- a/spec/ruby/core/range/size_spec.rb +++ b/spec/ruby/core/range/size_spec.rb @@ -22,16 +22,7 @@ describe "Range#size" do eval("('z'..)").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"..."3.4" 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 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/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 6c2a0af4f3..4da8b359cc 100644 --- a/spec/ruby/core/refinement/extend_object_spec.rb +++ b/spec/ruby/core/refinement/extend_object_spec.rb @@ -1,22 +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 } - -> { - 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 614c54dff8..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 7c3fabf612..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 @@ -123,345 +123,100 @@ describe :regexp_new_string, shared: true do (r.options & Regexp::EXTENDED).should_not == 0 end - ruby_version_is ""..."3.2" do - it "does not try to convert the second argument to Integer with #to_int method call" do - ScratchPad.clear - obj = Object.new - def obj.to_int() ScratchPad.record(:called) end + it "does not try to convert the second argument to Integer with #to_int method call" do + ScratchPad.clear + obj = Object.new + def obj.to_int() ScratchPad.record(:called) end + -> { Regexp.send(@method, "Hi", obj) + }.should complain(/expected true or false as ignorecase/, {verbose: true}) - ScratchPad.recorded.should == nil - end - end - - ruby_version_is "3.2" do - it "does not try to convert the second argument to Integer with #to_int method call" do - ScratchPad.clear - obj = Object.new - def obj.to_int() ScratchPad.record(:called) end - - -> { - Regexp.send(@method, "Hi", obj) - }.should complain(/expected true or false as ignorecase/, {verbose: true}) - - ScratchPad.recorded.should == nil - end + 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, "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/) + }.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 @@ -502,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 @@ -619,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 b5ecc35f04..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 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/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/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/byteindex_spec.rb b/spec/ruby/core/string/byteindex_spec.rb index 47c7be1029..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'.dup.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'.dup.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 150f709b90..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'.dup.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'.dup.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/byteslice_spec.rb b/spec/ruby/core/string/byteslice_spec.rb index 5b1027f4a5..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,7 +17,7 @@ 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".dup.force_encoding("UTF-8") "\u3042".byteslice(1, 2).should == "\x81\x82".dup.force_encoding("UTF-8") diff --git a/spec/ruby/core/string/bytesplice_spec.rb b/spec/ruby/core/string/bytesplice_spec.rb index 967edcba29..2c770e340a 100644 --- a/spec/ruby/core/string/bytesplice_spec.rb +++ b/spec/ruby/core/string/bytesplice_spec.rb @@ -3,131 +3,291 @@ 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/chilled_string_spec.rb b/spec/ruby/core/string/chilled_string_spec.rb index 9f81b1af6d..73d055cbdf 100644 --- a/spec/ruby/core/string/chilled_string_spec.rb +++ b/spec/ruby/core/string/chilled_string_spec.rb @@ -2,69 +2,149 @@ require_relative '../../spec_helper' describe "chilled String" do guard -> { ruby_version_is "3.4" and !"test".equal?("test") } do - describe "#frozen?" do - it "returns false" do - "chilled".frozen?.should == false + describe "chilled string literals" do + + describe "#frozen?" do + it "returns false" do + "chilled".frozen?.should == false + end 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 + 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 - 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 + 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 - 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" + 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 "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" + describe "chilled strings returned by Symbol#to_s" do + + describe "#frozen?" do + it "returns false" do + :chilled.to_s.frozen?.should == false + end end - it "emits a warning on singleton_class creation" do - -> { - "chilled".singleton_class - }.should complain(/literal string will be frozen in the future/) + 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 - 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/) + 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 - it "raises FrozenError after the string was explicitly frozen" do - input = "chilled" - input.freeze - -> { + 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(/literal string will be frozen in the future/) + -> { + 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 diff --git a/spec/ruby/core/string/codepoints_spec.rb b/spec/ruby/core/string/codepoints_spec.rb index b276d0baa8..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' 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/encode_spec.rb b/spec/ruby/core/string/encode_spec.rb index 97dd753b62..cd449498a3 100644 --- a/spec/ruby/core/string/encode_spec.rb +++ b/spec/ruby/core/string/encode_spec.rb @@ -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 diff --git a/spec/ruby/core/string/lstrip_spec.rb b/spec/ruby/core/string/lstrip_spec.rb index 99bab6f349..c83650207e 100644 --- a/spec/ruby/core/string/lstrip_spec.rb +++ b/spec/ruby/core/string/lstrip_spec.rb @@ -7,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 @@ -22,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 9045afa263..46e0aa0f36 100644 --- a/spec/ruby/core/string/modulo_spec.rb +++ b/spec/ruby/core/string/modulo_spec.rb @@ -391,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 @@ -570,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 @@ -749,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) @@ -758,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/rindex_spec.rb b/spec/ruby/core/string/rindex_spec.rb index 88ce733583..0863a9c3be 100644 --- a/spec/ruby/core/string/rindex_spec.rb +++ b/spec/ruby/core/string/rindex_spec.rb @@ -371,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/rstrip_spec.rb b/spec/ruby/core/string/rstrip_spec.rb index 6d46eb590e..55773f5238 100644 --- a/spec/ruby/core/string/rstrip_spec.rb +++ b/spec/ruby/core/string/rstrip_spec.rb @@ -68,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/shared/codepoints.rb b/spec/ruby/core/string/shared/codepoints.rb index f71263054a..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" diff --git a/spec/ruby/core/string/shared/dedup.rb b/spec/ruby/core/string/shared/dedup.rb index 97b5df6ed1..1ffd6aa0fd 100644 --- a/spec/ruby/core/string/shared/dedup.rb +++ b/spec/ruby/core/string/shared/dedup.rb @@ -48,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 31b4c02c9c..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 diff --git a/spec/ruby/core/string/shared/encode.rb b/spec/ruby/core/string/shared/encode.rb index 3776e0d709..9466308886 100644 --- a/spec/ruby/core/string/shared/encode.rb +++ b/spec/ruby/core/string/shared/encode.rb @@ -194,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 845b0a3e15..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' diff --git a/spec/ruby/core/string/shared/slice.rb b/spec/ruby/core/string/shared/slice.rb index 2f69b9ddce..7b9b9f6a14 100644 --- a/spec/ruby/core/string/shared/slice.rb +++ b/spec/ruby/core/string/shared/slice.rb @@ -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 b69a394875..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 == "" diff --git a/spec/ruby/core/string/squeeze_spec.rb b/spec/ruby/core/string/squeeze_spec.rb index 4ea238e6b5..981d480684 100644 --- a/spec/ruby/core/string/squeeze_spec.rb +++ b/spec/ruby/core/string/squeeze_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary # frozen_string_literal: false require_relative '../../spec_helper' require_relative 'fixtures/classes' diff --git a/spec/ruby/core/string/sub_spec.rb b/spec/ruby/core/string/sub_spec.rb index 4f9f87a433..6ff28ec851 100644 --- a/spec/ruby/core/string/sub_spec.rb +++ b/spec/ruby/core/string/sub_spec.rb @@ -232,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]] @@ -339,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 7f4c68366d..011a213501 100644 --- a/spec/ruby/core/string/swapcase_spec.rb +++ b/spec/ruby/core/string/swapcase_spec.rb @@ -5,9 +5,9 @@ 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 9931502baa..39f69acda3 100644 --- a/spec/ruby/core/string/to_i_spec.rb +++ b/spec/ruby/core/string/to_i_spec.rb @@ -10,7 +10,7 @@ describe "String#to_i" do "1_2_3asdf".to_i.should == 123 end - it "ignores multiple non-consecutive underscoes when the first digit is 0" do + 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 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/unpack/a_spec.rb b/spec/ruby/core/string/unpack/a_spec.rb index 4002ece697..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' 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 23d93a8aea..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' 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 456abee784..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' 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/uplus_spec.rb b/spec/ruby/core/string/uplus_spec.rb index c0b0c49ede..20767bcc01 100644 --- a/spec/ruby/core/string/uplus_spec.rb +++ b/spec/ruby/core/string/uplus_spec.rb @@ -13,14 +13,48 @@ describe 'String#+@' do 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/struct/constants_spec.rb b/spec/ruby/core/struct/constants_spec.rb index fa61a4b912..7e8af1a211 100644 --- a/spec/ruby/core/struct/constants_spec.rb +++ b/spec/ruby/core/struct/constants_spec.rb @@ -1,15 +1,13 @@ require_relative '../../spec_helper' -ruby_version_is "3.2" do - describe "Struct::Group" do - it "is no longer defined" do - Struct.should_not.const_defined?(:Group) - end +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 +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 a94eb852e1..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 @@ -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 54eccdba11..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 6e381e4868..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 diff --git a/spec/ruby/core/thread/each_caller_location_spec.rb b/spec/ruby/core/thread/each_caller_location_spec.rb index 29c271789b..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 keyword arguments" do - -> { - Thread.each_caller_location(12, foo: 10) {} - }.should raise_error(ArgumentError); - 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/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 17a08c8a15..374cc59279 100644 --- a/spec/ruby/core/thread/native_thread_id_spec.rb +++ b/spec/ruby/core/thread/native_thread_id_spec.rb @@ -1,37 +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) - end + 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) + end - main_thread_id.should_not == t_thread_id + main_thread_id.should_not == t_thread_id - t.run - t.join - t.native_thread_id.should == nil - end + 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/thread_variable_get_spec.rb b/spec/ruby/core/thread/thread_variable_get_spec.rb index 0ad19bfd88..1ea34cf2b3 100644 --- a/spec/ruby/core/thread/thread_variable_get_spec.rb +++ b/spec/ruby/core/thread/thread_variable_get_spec.rb @@ -14,12 +14,47 @@ describe "Thread#thread_variable_get" do end it "returns the value previously set by #thread_variable_set" do - @t.thread_variable_set :a, 49 + @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 bb0d705bbc..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 diff --git a/spec/ruby/core/time/at_spec.rb b/spec/ruby/core/time/at_spec.rb index 48fb3c6f52..97906b8c8c 100644 --- a/spec/ruby/core/time/at_spec.rb +++ b/spec/ruby/core/time/at_spec.rb @@ -102,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 @@ -228,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 @@ -280,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 ee17e7dbd4..b5cfdaa93f 100644 --- a/spec/ruby/core/time/deconstruct_keys_spec.rb +++ b/spec/ruby/core/time/deconstruct_keys_spec.rb @@ -1,45 +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 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 +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/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 d686355270..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 @@ -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, /\Atwo digits mon is expected after [`']-': -012-25 00:\z/) + -> { + 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, /\Atwo digits mon is expected after [`']-': -2-25 00:56\z/) + 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, /\Atwo digits mday is expected after [`']-': -215 00:56:\z/) + 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, /\Atwo digits min is expected after [`']:': :516:17 \+09\z/) + -> { + 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, /\Atwo digits min is expected after [`']:': :6:17 \+0900\z/) + -> { + 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, /\Atwo digits sec is expected after [`']:': :137 \+0900\z/) + -> { + 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, /\Atwo digits sec is expected after [`']:': :7 \+0900\z/) + -> { + 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 cc6bf0f842..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 @@ -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,6 +67,7 @@ describe 'TracePoint#inspect' do end trace_point_spec_test_return end + ruby_version_is("3.4") { line -= 1 } inspect.should =~ /\A#<TracePoint:return [`']trace_point_spec_test_return'#{@path_prefix}#{__FILE__}:#{line}>\z/ end @@ -69,6 +76,8 @@ describe 'TracePoint#inspect' 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 @@ -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/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 index 943a7faaa3..194e2cc1a1 100644 --- a/spec/ruby/core/unboundmethod/shared/dup.rb +++ b/spec/ruby/core/unboundmethod/shared/dup.rb @@ -16,7 +16,7 @@ describe :unboundmethod_dup, shared: true do end it "copies the finalizer" do - code = <<-RUBY + code = <<-'RUBY' obj = Class.instance_method(:instance_method) ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) 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 5c2f14362c..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', __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/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/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 |
