summaryrefslogtreecommitdiff
path: root/spec/ruby/library/uri/generic/userinfo_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/library/uri/generic/userinfo_spec.rb')
0 files changed, 0 insertions, 0 deletions
'width: 85.9%;'/> -rw-r--r--spec/ruby/.rubocop.yml204
-rw-r--r--spec/ruby/.rubocop_todo.yml138
-rw-r--r--spec/ruby/CONTRIBUTING.md297
-rw-r--r--spec/ruby/LICENSE22
-rw-r--r--spec/ruby/README.md167
-rw-r--r--spec/ruby/TODO8
-rwxr-xr-xspec/ruby/bin/rubocop12
-rw-r--r--spec/ruby/command_line/backtrace_limit_spec.rb93
-rwxr-xr-xspec/ruby/command_line/dash_0_spec.rb13
-rw-r--r--spec/ruby/command_line/dash_a_spec.rb19
-rw-r--r--spec/ruby/command_line/dash_c_spec.rb13
-rw-r--r--spec/ruby/command_line/dash_d_spec.rb22
-rw-r--r--spec/ruby/command_line/dash_e_spec.rb41
-rw-r--r--spec/ruby/command_line/dash_encoding_spec.rb36
-rw-r--r--spec/ruby/command_line/dash_external_encoding_spec.rb15
-rw-r--r--spec/ruby/command_line/dash_internal_encoding_spec.rb15
-rw-r--r--spec/ruby/command_line/dash_l_spec.rb31
-rw-r--r--spec/ruby/command_line/dash_n_spec.rb36
-rw-r--r--spec/ruby/command_line/dash_p_spec.rb19
-rw-r--r--spec/ruby/command_line/dash_r_spec.rb28
-rw-r--r--spec/ruby/command_line/dash_s_spec.rb52
-rw-r--r--spec/ruby/command_line/dash_upper_c_spec.rb6
-rw-r--r--spec/ruby/command_line/dash_upper_e_spec.rb37
-rw-r--r--spec/ruby/command_line/dash_upper_f_spec.rb13
-rw-r--r--spec/ruby/command_line/dash_upper_i_spec.rb51
-rw-r--r--spec/ruby/command_line/dash_upper_k_spec.rb65
-rw-r--r--spec/ruby/command_line/dash_upper_s_spec.rb67
-rw-r--r--spec/ruby/command_line/dash_upper_u_spec.rb52
-rw-r--r--spec/ruby/command_line/dash_upper_w_spec.rb44
-rw-r--r--spec/ruby/command_line/dash_upper_x_spec.rb6
-rw-r--r--spec/ruby/command_line/dash_v_spec.rb15
-rw-r--r--spec/ruby/command_line/dash_w_spec.rb10
-rw-r--r--spec/ruby/command_line/dash_x_spec.rb21
-rw-r--r--spec/ruby/command_line/error_message_spec.rb16
-rw-r--r--spec/ruby/command_line/feature_spec.rb71
-rw-r--r--spec/ruby/command_line/fixtures/backtrace.rb35
-rw-r--r--spec/ruby/command_line/fixtures/bad_syntax.rb1
-rw-r--r--spec/ruby/command_line/fixtures/bin/bad_embedded_ruby.txt3
-rw-r--r--spec/ruby/command_line/fixtures/bin/dash_s_fail1
-rw-r--r--spec/ruby/command_line/fixtures/bin/embedded_ruby.txt3
-rw-r--r--spec/ruby/command_line/fixtures/bin/hybrid_launcher.sh4
-rwxr-xr-xspec/ruby/command_line/fixtures/bin/launcher.rb2
-rw-r--r--spec/ruby/command_line/fixtures/change_directory_script.rb1
-rw-r--r--spec/ruby/command_line/fixtures/conditional_range.txt5
-rw-r--r--spec/ruby/command_line/fixtures/dash_s_script.rb12
-rw-r--r--spec/ruby/command_line/fixtures/debug.rb10
-rw-r--r--spec/ruby/command_line/fixtures/debug_info.rb10
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_across_files.rb3
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_across_files_diff_enc.rb3
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb2
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_required.rb1
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb3
-rw-r--r--spec/ruby/command_line/fixtures/freeze_flag_two_literals.rb1
-rw-r--r--spec/ruby/command_line/fixtures/full_names.txt3
-rw-r--r--spec/ruby/command_line/fixtures/loadpath.rb1
-rw-r--r--spec/ruby/command_line/fixtures/names.txt3
-rw-r--r--spec/ruby/command_line/fixtures/passwd_file.txt3
-rw-r--r--spec/ruby/command_line/fixtures/require.rb1
-rw-r--r--spec/ruby/command_line/fixtures/rubyopt.rb1
-rw-r--r--spec/ruby/command_line/fixtures/string_literal_frozen_comment.rb4
-rw-r--r--spec/ruby/command_line/fixtures/string_literal_mutable_comment.rb4
-rw-r--r--spec/ruby/command_line/fixtures/string_literal_raw.rb3
-rw-r--r--spec/ruby/command_line/fixtures/test_file.rb1
-rw-r--r--spec/ruby/command_line/fixtures/verbose.rb1
-rw-r--r--spec/ruby/command_line/frozen_strings_spec.rb94
-rw-r--r--spec/ruby/command_line/rubylib_spec.rb69
-rw-r--r--spec/ruby/command_line/rubyopt_spec.rb185
-rw-r--r--spec/ruby/command_line/shared/change_directory.rb21
-rw-r--r--spec/ruby/command_line/shared/verbose.rb9
-rw-r--r--spec/ruby/command_line/syntax_error_spec.rb13
-rw-r--r--spec/ruby/core/argf/argf_spec.rb11
-rw-r--r--spec/ruby/core/argf/argv_spec.rb19
-rw-r--r--spec/ruby/core/argf/binmode_spec.rb43
-rw-r--r--spec/ruby/core/argf/close_spec.rb35
-rw-r--r--spec/ruby/core/argf/closed_spec.rb18
-rw-r--r--spec/ruby/core/argf/each_byte_spec.rb60
-rw-r--r--spec/ruby/core/argf/each_char_spec.rb60
-rw-r--r--spec/ruby/core/argf/each_codepoint_spec.rb60
-rw-r--r--spec/ruby/core/argf/each_line_spec.rb64
-rw-r--r--spec/ruby/core/argf/each_spec.rb7
-rw-r--r--spec/ruby/core/argf/eof_spec.rb32
-rw-r--r--spec/ruby/core/argf/file_spec.rb21
-rw-r--r--spec/ruby/core/argf/filename_spec.rb30
-rw-r--r--spec/ruby/core/argf/fileno_spec.rb26
-rw-r--r--spec/ruby/core/argf/fixtures/bin_file.txt2
-rw-r--r--spec/ruby/core/argf/fixtures/file1.txt2
-rw-r--r--spec/ruby/core/argf/fixtures/file2.txt2
-rw-r--r--spec/ruby/core/argf/fixtures/filename.rb3
-rw-r--r--spec/ruby/core/argf/fixtures/lineno.rb5
-rw-r--r--spec/ruby/core/argf/fixtures/rewind.rb5
-rw-r--r--spec/ruby/core/argf/fixtures/stdin.txt2
-rw-r--r--spec/ruby/core/argf/getc_spec.rb20
-rw-r--r--spec/ruby/core/argf/gets_spec.rb49
-rw-r--r--spec/ruby/core/argf/inspect_spec.rb7
-rw-r--r--spec/ruby/core/argf/lineno_spec.rb30
-rw-r--r--spec/ruby/core/argf/path_spec.rb7
-rw-r--r--spec/ruby/core/argf/pos_spec.rb65
-rw-r--r--spec/ruby/core/argf/read_nonblock_spec.rb80
-rw-r--r--spec/ruby/core/argf/read_spec.rb85
-rw-r--r--spec/ruby/core/argf/readchar_spec.rb19
-rw-r--r--spec/ruby/core/argf/readline_spec.rb23
-rw-r--r--spec/ruby/core/argf/readlines_spec.rb24
-rw-r--r--spec/ruby/core/argf/readpartial_spec.rb75
-rw-r--r--spec/ruby/core/argf/rewind_spec.rb39
-rw-r--r--spec/ruby/core/argf/seek_spec.rb63
-rw-r--r--spec/ruby/core/argf/set_encoding_spec.rb41
-rw-r--r--spec/ruby/core/argf/shared/getc.rb17
-rw-r--r--spec/ruby/core/argf/shared/gets.rb99
-rw-r--r--spec/ruby/core/argf/shared/read.rb58
-rw-r--r--spec/ruby/core/argf/skip_spec.rb42
-rw-r--r--spec/ruby/core/argf/tell_spec.rb7
-rw-r--r--spec/ruby/core/argf/to_a_spec.rb7
-rw-r--r--spec/ruby/core/argf/to_i_spec.rb7
-rw-r--r--spec/ruby/core/argf/to_io_spec.rb23
-rw-r--r--spec/ruby/core/argf/to_s_spec.rb14
-rw-r--r--spec/ruby/core/array/all_spec.rb13
-rw-r--r--spec/ruby/core/array/allocate_spec.rb19
-rw-r--r--spec/ruby/core/array/any_spec.rb49
-rw-r--r--spec/ruby/core/array/append_spec.rb41
-rw-r--r--spec/ruby/core/array/array_spec.rb7
-rw-r--r--spec/ruby/core/array/assoc_spec.rb52
-rw-r--r--spec/ruby/core/array/at_spec.rb56
-rw-r--r--spec/ruby/core/array/bsearch_index_spec.rb81
-rw-r--r--spec/ruby/core/array/bsearch_spec.rb84
-rw-r--r--spec/ruby/core/array/clear_spec.rb32
-rw-r--r--spec/ruby/core/array/clone_spec.rb31
-rw-r--r--spec/ruby/core/array/collect_spec.rb143
-rw-r--r--spec/ruby/core/array/combination_spec.rb74
-rw-r--r--spec/ruby/core/array/compact_spec.rb51
-rw-r--r--spec/ruby/core/array/comparison_spec.rb97
-rw-r--r--spec/ruby/core/array/concat_spec.rb74
-rw-r--r--spec/ruby/core/array/constructor_spec.rb24
-rw-r--r--spec/ruby/core/array/count_spec.rb26
-rw-r--r--spec/ruby/core/array/cycle_spec.rb101
-rw-r--r--spec/ruby/core/array/deconstruct_spec.rb9
-rw-r--r--spec/ruby/core/array/delete_at_spec.rb41
-rw-r--r--spec/ruby/core/array/delete_if_spec.rb82
-rw-r--r--spec/ruby/core/array/delete_spec.rb46
-rw-r--r--spec/ruby/core/array/difference_spec.rb22
-rw-r--r--spec/ruby/core/array/dig_spec.rb52
-rw-r--r--spec/ruby/core/array/drop_spec.rb56
-rw-r--r--spec/ruby/core/array/drop_while_spec.rb24
-rw-r--r--spec/ruby/core/array/dup_spec.rb31
-rw-r--r--spec/ruby/core/array/each_index_spec.rb58
-rw-r--r--spec/ruby/core/array/each_spec.rb82
-rw-r--r--spec/ruby/core/array/element_reference_spec.rb903
-rw-r--r--spec/ruby/core/array/element_set_spec.rb537
-rw-r--r--spec/ruby/core/array/empty_spec.rb10
-rw-r--r--spec/ruby/core/array/eql_spec.rb19
-rw-r--r--spec/ruby/core/array/equal_value_spec.rb51
-rw-r--r--spec/ruby/core/array/fetch_spec.rb55
-rw-r--r--spec/ruby/core/array/fetch_values_spec.rb55
-rw-r--r--spec/ruby/core/array/fill_spec.rb374
-rw-r--r--spec/ruby/core/array/filter_spec.rb13
-rw-r--r--spec/ruby/core/array/find_index_spec.rb42
-rw-r--r--spec/ruby/core/array/first_spec.rb93
-rw-r--r--spec/ruby/core/array/fixtures/classes.rb592
-rw-r--r--spec/ruby/core/array/fixtures/encoded_strings.rb69
-rw-r--r--spec/ruby/core/array/flatten_spec.rb266
-rw-r--r--spec/ruby/core/array/frozen_spec.rb16
-rw-r--r--spec/ruby/core/array/hash_spec.rb83
-rw-r--r--spec/ruby/core/array/include_spec.rb33
-rw-r--r--spec/ruby/core/array/index_spec.rb7
-rw-r--r--spec/ruby/core/array/initialize_spec.rb158
-rw-r--r--spec/ruby/core/array/insert_spec.rb78
-rw-r--r--spec/ruby/core/array/inspect_spec.rb108
-rw-r--r--spec/ruby/core/array/intersect_spec.rb64
-rw-r--r--spec/ruby/core/array/intersection_spec.rb19
-rw-r--r--spec/ruby/core/array/join_spec.rb146
-rw-r--r--spec/ruby/core/array/keep_if_spec.rb11
-rw-r--r--spec/ruby/core/array/last_spec.rb87
-rw-r--r--spec/ruby/core/array/length_spec.rb14
-rw-r--r--spec/ruby/core/array/map_spec.rb13
-rw-r--r--spec/ruby/core/array/max_spec.rb116
-rw-r--r--spec/ruby/core/array/min_spec.rb121
-rw-r--r--spec/ruby/core/array/minmax_spec.rb14
-rw-r--r--spec/ruby/core/array/minus_spec.rb7
-rw-r--r--spec/ruby/core/array/multiply_spec.rb94
-rw-r--r--spec/ruby/core/array/new_spec.rb124
-rw-r--r--spec/ruby/core/array/none_spec.rb13
-rw-r--r--spec/ruby/core/array/one_spec.rb13
-rw-r--r--spec/ruby/core/array/pack/a_spec.rb73
-rw-r--r--spec/ruby/core/array/pack/at_spec.rb30
-rw-r--r--spec/ruby/core/array/pack/b_spec.rb113
-rw-r--r--spec/ruby/core/array/pack/buffer_spec.rb60
-rw-r--r--spec/ruby/core/array/pack/c_spec.rb77
-rw-r--r--spec/ruby/core/array/pack/comment_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/d_spec.rb39
-rw-r--r--spec/ruby/core/array/pack/e_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/empty_spec.rb11
-rw-r--r--spec/ruby/core/array/pack/f_spec.rb39
-rw-r--r--spec/ruby/core/array/pack/g_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/h_spec.rb205
-rw-r--r--spec/ruby/core/array/pack/i_spec.rb133
-rw-r--r--spec/ruby/core/array/pack/j_spec.rb217
-rw-r--r--spec/ruby/core/array/pack/l_spec.rb221
-rw-r--r--spec/ruby/core/array/pack/m_spec.rb317
-rw-r--r--spec/ruby/core/array/pack/n_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/p_spec.rb38
-rw-r--r--spec/ruby/core/array/pack/percent_spec.rb7
-rw-r--r--spec/ruby/core/array/pack/q_spec.rb61
-rw-r--r--spec/ruby/core/array/pack/r_spec.rb89
-rw-r--r--spec/ruby/core/array/pack/s_spec.rb133
-rw-r--r--spec/ruby/core/array/pack/shared/basic.rb73
-rw-r--r--spec/ruby/core/array/pack/shared/encodings.rb16
-rw-r--r--spec/ruby/core/array/pack/shared/float.rb255
-rw-r--r--spec/ruby/core/array/pack/shared/integer.rb387
-rw-r--r--spec/ruby/core/array/pack/shared/numeric_basic.rb50
-rw-r--r--spec/ruby/core/array/pack/shared/string.rb48
-rw-r--r--spec/ruby/core/array/pack/shared/taint.rb2
-rw-r--r--spec/ruby/core/array/pack/shared/unicode.rb96
-rw-r--r--spec/ruby/core/array/pack/u_spec.rb140
-rw-r--r--spec/ruby/core/array/pack/v_spec.rb25
-rw-r--r--spec/ruby/core/array/pack/w_spec.rb44
-rw-r--r--spec/ruby/core/array/pack/x_spec.rb65
-rw-r--r--spec/ruby/core/array/pack/z_spec.rb44
-rw-r--r--spec/ruby/core/array/partition_spec.rb43
-rw-r--r--spec/ruby/core/array/permutation_spec.rb138
-rw-r--r--spec/ruby/core/array/plus_spec.rb56
-rw-r--r--spec/ruby/core/array/pop_spec.rb124
-rw-r--r--spec/ruby/core/array/prepend_spec.rb7
-rw-r--r--spec/ruby/core/array/product_spec.rb73
-rw-r--r--spec/ruby/core/array/push_spec.rb36
-rw-r--r--spec/ruby/core/array/rassoc_spec.rb50
-rw-r--r--spec/ruby/core/array/reject_spec.rb158
-rw-r--r--spec/ruby/core/array/repeated_combination_spec.rb84
-rw-r--r--spec/ruby/core/array/repeated_permutation_spec.rb94
-rw-r--r--spec/ruby/core/array/replace_spec.rb63
-rw-r--r--spec/ruby/core/array/reverse_each_spec.rb57
-rw-r--r--spec/ruby/core/array/reverse_spec.rb42
-rw-r--r--spec/ruby/core/array/rindex_spec.rb95
-rw-r--r--spec/ruby/core/array/rotate_spec.rb129
-rw-r--r--spec/ruby/core/array/sample_spec.rb155
-rw-r--r--spec/ruby/core/array/select_spec.rb43
-rw-r--r--spec/ruby/core/array/shared/clone.rb20
-rw-r--r--spec/ruby/core/array/shared/delete_if.rb13
-rw-r--r--spec/ruby/core/array/shared/difference.rb78
-rw-r--r--spec/ruby/core/array/shared/enumeratorize.rb5
-rw-r--r--spec/ruby/core/array/shared/eql.rb92
-rw-r--r--spec/ruby/core/array/shared/intersection.rb85
-rw-r--r--spec/ruby/core/array/shared/iterable_and_tolerating_size_increasing.rb25
-rw-r--r--spec/ruby/core/array/shared/join.rb15
-rw-r--r--spec/ruby/core/array/shared/keep_if.rb95
-rw-r--r--spec/ruby/core/array/shared/union.rb79
-rw-r--r--spec/ruby/core/array/shift_spec.rb120
-rw-r--r--spec/ruby/core/array/shuffle_spec.rb119
-rw-r--r--spec/ruby/core/array/size_spec.rb7
-rw-r--r--spec/ruby/core/array/slice_spec.rb219
-rw-r--r--spec/ruby/core/array/sort_by_spec.rb85
-rw-r--r--spec/ruby/core/array/sort_spec.rb252
-rw-r--r--spec/ruby/core/array/sum_spec.rb88
-rw-r--r--spec/ruby/core/array/take_spec.rb32
-rw-r--r--spec/ruby/core/array/take_while_spec.rb26
-rw-r--r--spec/ruby/core/array/to_a_spec.rb24
-rw-r--r--spec/ruby/core/array/to_ary_spec.rb20
-rw-r--r--spec/ruby/core/array/to_h_spec.rb91
-rw-r--r--spec/ruby/core/array/to_s_spec.rb7
-rw-r--r--spec/ruby/core/array/transpose_spec.rb53
-rw-r--r--spec/ruby/core/array/try_convert_spec.rb50
-rw-r--r--spec/ruby/core/array/union_spec.rb25
-rw-r--r--spec/ruby/core/array/uniq_spec.rb243
-rw-r--r--spec/ruby/core/array/unshift_spec.rb67
-rw-r--r--spec/ruby/core/array/values_at_spec.rb74
-rw-r--r--spec/ruby/core/array/zip_spec.rb71
-rw-r--r--spec/ruby/core/basicobject/__id__spec.rb6
-rw-r--r--spec/ruby/core/basicobject/__send___spec.rb10
-rw-r--r--spec/ruby/core/basicobject/basicobject_spec.rb91
-rw-r--r--spec/ruby/core/basicobject/equal_spec.rb54
-rw-r--r--spec/ruby/core/basicobject/equal_value_spec.rb10
-rw-r--r--spec/ruby/core/basicobject/fixtures/classes.rb255
-rw-r--r--spec/ruby/core/basicobject/fixtures/common.rb9
-rw-r--r--spec/ruby/core/basicobject/fixtures/remove_method_missing.rb9
-rw-r--r--spec/ruby/core/basicobject/fixtures/singleton_method.rb10
-rw-r--r--spec/ruby/core/basicobject/initialize_spec.rb13
-rw-r--r--spec/ruby/core/basicobject/instance_eval_spec.rb327
-rw-r--r--spec/ruby/core/basicobject/instance_exec_spec.rb113
-rw-r--r--spec/ruby/core/basicobject/method_missing_spec.rb40
-rw-r--r--spec/ruby/core/basicobject/not_equal_spec.rb53
-rw-r--r--spec/ruby/core/basicobject/not_spec.rb11
-rw-r--r--spec/ruby/core/basicobject/singleton_method_added_spec.rb147
-rw-r--r--spec/ruby/core/basicobject/singleton_method_removed_spec.rb24
-rw-r--r--spec/ruby/core/basicobject/singleton_method_undefined_spec.rb24
-rw-r--r--spec/ruby/core/binding/clone_spec.rb13
-rw-r--r--spec/ruby/core/binding/dup_spec.rb30
-rw-r--r--spec/ruby/core/binding/eval_spec.rb105
-rw-r--r--spec/ruby/core/binding/fixtures/classes.rb66
-rw-r--r--spec/ruby/core/binding/fixtures/location.rb6
-rw-r--r--spec/ruby/core/binding/local_variable_defined_spec.rb46
-rw-r--r--spec/ruby/core/binding/local_variable_get_spec.rb56
-rw-r--r--spec/ruby/core/binding/local_variable_set_spec.rb71
-rw-r--r--spec/ruby/core/binding/local_variables_spec.rb35
-rw-r--r--spec/ruby/core/binding/receiver_spec.rb11
-rw-r--r--spec/ruby/core/binding/shared/clone.rb56
-rw-r--r--spec/ruby/core/binding/source_location_spec.rb14
-rw-r--r--spec/ruby/core/builtin_constants/builtin_constants_spec.rb149
-rw-r--r--spec/ruby/core/class/allocate_spec.rb41
-rw-r--r--spec/ruby/core/class/attached_object_spec.rb29
-rw-r--r--spec/ruby/core/class/dup_spec.rb69
-rw-r--r--spec/ruby/core/class/fixtures/classes.rb47
-rw-r--r--spec/ruby/core/class/inherited_spec.rb118
-rw-r--r--spec/ruby/core/class/initialize_spec.rb34
-rw-r--r--spec/ruby/core/class/new_spec.rb157
-rw-r--r--spec/ruby/core/class/subclasses_spec.rb85
-rw-r--r--spec/ruby/core/class/superclass_spec.rb27
-rw-r--r--spec/ruby/core/comparable/between_spec.rb25
-rw-r--r--spec/ruby/core/comparable/clamp_spec.rb223
-rw-r--r--spec/ruby/core/comparable/equal_value_spec.rb114
-rw-r--r--spec/ruby/core/comparable/fixtures/classes.rb37
-rw-r--r--spec/ruby/core/comparable/gt_spec.rb43
-rw-r--r--spec/ruby/core/comparable/gte_spec.rb47
-rw-r--r--spec/ruby/core/comparable/lt_spec.rb49
-rw-r--r--spec/ruby/core/comparable/lte_spec.rb46
-rw-r--r--spec/ruby/core/complex/abs2_spec.rb9
-rw-r--r--spec/ruby/core/complex/abs_spec.rb12
-rw-r--r--spec/ruby/core/complex/angle_spec.rb7
-rw-r--r--spec/ruby/core/complex/arg_spec.rb11
-rw-r--r--spec/ruby/core/complex/coerce_spec.rb70
-rw-r--r--spec/ruby/core/complex/comparison_spec.rb25
-rw-r--r--spec/ruby/core/complex/conj_spec.rb7
-rw-r--r--spec/ruby/core/complex/conjugate_spec.rb10
-rw-r--r--spec/ruby/core/complex/constants_spec.rb7
-rw-r--r--spec/ruby/core/complex/denominator_spec.rb13
-rw-r--r--spec/ruby/core/complex/divide_spec.rb84
-rw-r--r--spec/ruby/core/complex/eql_spec.rb31
-rw-r--r--spec/ruby/core/complex/equal_value_spec.rb93
-rw-r--r--spec/ruby/core/complex/exponent_spec.rb61
-rw-r--r--spec/ruby/core/complex/fdiv_spec.rb129
-rw-r--r--spec/ruby/core/complex/finite_spec.rb32
-rw-r--r--spec/ruby/core/complex/hash_spec.rb16
-rw-r--r--spec/ruby/core/complex/imag_spec.rb7
-rw-r--r--spec/ruby/core/complex/imaginary_spec.rb10
-rw-r--r--spec/ruby/core/complex/infinite_spec.rb32
-rw-r--r--spec/ruby/core/complex/inspect_spec.rb37
-rw-r--r--spec/ruby/core/complex/integer_spec.rb11
-rw-r--r--spec/ruby/core/complex/magnitude_spec.rb7
-rw-r--r--spec/ruby/core/complex/marshal_dump_spec.rb11
-rw-r--r--spec/ruby/core/complex/minus_spec.rb45
-rw-r--r--spec/ruby/core/complex/multiply_spec.rb49
-rw-r--r--spec/ruby/core/complex/negative_spec.rb13
-rw-r--r--spec/ruby/core/complex/numerator_spec.rb19
-rw-r--r--spec/ruby/core/complex/phase_spec.rb7
-rw-r--r--spec/ruby/core/complex/plus_spec.rb45
-rw-r--r--spec/ruby/core/complex/polar_spec.rb41
-rw-r--r--spec/ruby/core/complex/positive_spec.rb13
-rw-r--r--spec/ruby/core/complex/quo_spec.rb7
-rw-r--r--spec/ruby/core/complex/rationalize_spec.rb31
-rw-r--r--spec/ruby/core/complex/real_spec.rb28
-rw-r--r--spec/ruby/core/complex/rect_spec.rb13
-rw-r--r--spec/ruby/core/complex/rectangular_spec.rb114
-rw-r--r--spec/ruby/core/complex/to_c_spec.rb12
-rw-r--r--spec/ruby/core/complex/to_f_spec.rb41
-rw-r--r--spec/ruby/core/complex/to_i_spec.rb41
-rw-r--r--spec/ruby/core/complex/to_r_spec.rb49
-rw-r--r--spec/ruby/core/complex/to_s_spec.rb55
-rw-r--r--spec/ruby/core/complex/uminus_spec.rb11
-rw-r--r--spec/ruby/core/conditionvariable/broadcast_spec.rb39
-rw-r--r--spec/ruby/core/conditionvariable/marshal_dump_spec.rb8
-rw-r--r--spec/ruby/core/conditionvariable/signal_spec.rb76
-rw-r--r--spec/ruby/core/conditionvariable/wait_spec.rb174
-rw-r--r--spec/ruby/core/data/constants_spec.rb11
-rw-r--r--spec/ruby/core/data/deconstruct_keys_spec.rb110
-rw-r--r--spec/ruby/core/data/deconstruct_spec.rb8
-rw-r--r--spec/ruby/core/data/define_spec.rb34
-rw-r--r--spec/ruby/core/data/eql_spec.rb63
-rw-r--r--spec/ruby/core/data/equal_value_spec.rb63
-rw-r--r--spec/ruby/core/data/fixtures/classes.rb41
-rw-r--r--spec/ruby/core/data/hash_spec.rb25
-rw-r--r--spec/ruby/core/data/initialize_spec.rb204
-rw-r--r--spec/ruby/core/data/inspect_spec.rb63
-rw-r--r--spec/ruby/core/data/members_spec.rb21
-rw-r--r--spec/ruby/core/data/to_h_spec.rb63
-rw-r--r--spec/ruby/core/data/to_s_spec.rb9
-rw-r--r--spec/ruby/core/data/with_spec.rb55
-rw-r--r--spec/ruby/core/dir/chdir_spec.rb218
-rw-r--r--spec/ruby/core/dir/children_spec.rb147
-rw-r--r--spec/ruby/core/dir/chroot_spec.rb47
-rw-r--r--spec/ruby/core/dir/close_spec.rb53
-rw-r--r--spec/ruby/core/dir/delete_spec.rb15
-rw-r--r--spec/ruby/core/dir/dir_spec.rb7
-rw-r--r--spec/ruby/core/dir/each_child_spec.rb119
-rw-r--r--spec/ruby/core/dir/each_spec.rb75
-rw-r--r--spec/ruby/core/dir/element_reference_spec.rb33
-rw-r--r--spec/ruby/core/dir/empty_spec.rb31
-rw-r--r--spec/ruby/core/dir/entries_spec.rb70
-rw-r--r--spec/ruby/core/dir/exist_spec.rb74
-rw-r--r--spec/ruby/core/dir/fchdir_spec.rb71
-rw-r--r--spec/ruby/core/dir/fileno_spec.rb37
-rw-r--r--spec/ruby/core/dir/fixtures/common.rb203
-rw-r--r--spec/ruby/core/dir/for_fd_spec.rb77
-rw-r--r--spec/ruby/core/dir/foreach_spec.rb68
-rw-r--r--spec/ruby/core/dir/getwd_spec.rb7
-rw-r--r--spec/ruby/core/dir/glob_spec.rb362
-rw-r--r--spec/ruby/core/dir/home_spec.rb85
-rw-r--r--spec/ruby/core/dir/initialize_spec.rb23
-rw-r--r--spec/ruby/core/dir/inspect_spec.rb24
-rw-r--r--spec/ruby/core/dir/mkdir_spec.rb107
-rw-r--r--spec/ruby/core/dir/open_spec.rb84
-rw-r--r--spec/ruby/core/dir/path_spec.rb37
-rw-r--r--spec/ruby/core/dir/pos_spec.rb21
-rw-r--r--spec/ruby/core/dir/pwd_spec.rb80
-rw-r--r--spec/ruby/core/dir/read_spec.rb76
-rw-r--r--spec/ruby/core/dir/rewind_spec.rb36
-rw-r--r--spec/ruby/core/dir/rmdir_spec.rb15
-rw-r--r--spec/ruby/core/dir/scan_spec.rb224
-rw-r--r--spec/ruby/core/dir/seek_spec.rb19
-rw-r--r--spec/ruby/core/dir/shared/chroot.rb44
-rw-r--r--spec/ruby/core/dir/shared/closed.rb9
-rw-r--r--spec/ruby/core/dir/shared/delete.rb53
-rw-r--r--spec/ruby/core/dir/shared/glob.rb441
-rw-r--r--spec/ruby/core/dir/shared/pos.rb24
-rw-r--r--spec/ruby/core/dir/tell_spec.rb41
-rw-r--r--spec/ruby/core/dir/to_path_spec.rb7
-rw-r--r--spec/ruby/core/dir/unlink_spec.rb15
-rw-r--r--spec/ruby/core/encoding/_dump_spec.rb5
-rw-r--r--spec/ruby/core/encoding/_load_spec.rb5
-rw-r--r--spec/ruby/core/encoding/aliases_spec.rb43
-rw-r--r--spec/ruby/core/encoding/ascii_compatible_spec.rb22
-rw-r--r--spec/ruby/core/encoding/compatible_spec.rb772
-rw-r--r--spec/ruby/core/encoding/converter/asciicompat_encoding_spec.rb37
-rw-r--r--spec/ruby/core/encoding/converter/constants_spec.rb131
-rw-r--r--spec/ruby/core/encoding/converter/convert_spec.rb45
-rw-r--r--spec/ruby/core/encoding/converter/convpath_spec.rb24
-rw-r--r--spec/ruby/core/encoding/converter/destination_encoding_spec.rb11
-rw-r--r--spec/ruby/core/encoding/converter/finish_spec.rb36
-rw-r--r--spec/ruby/core/encoding/converter/insert_output_spec.rb5
-rw-r--r--spec/ruby/core/encoding/converter/inspect_spec.rb13
-rw-r--r--spec/ruby/core/encoding/converter/last_error_spec.rb91
-rw-r--r--spec/ruby/core/encoding/converter/new_spec.rb119
-rw-r--r--spec/ruby/core/encoding/converter/primitive_convert_spec.rb216
-rw-r--r--spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb69
-rw-r--r--spec/ruby/core/encoding/converter/putback_spec.rb56
-rw-r--r--spec/ruby/core/encoding/converter/replacement_spec.rb70
-rw-r--r--spec/ruby/core/encoding/converter/search_convpath_spec.rb30
-rw-r--r--spec/ruby/core/encoding/converter/source_encoding_spec.rb11
-rw-r--r--spec/ruby/core/encoding/default_external_spec.rb69
-rw-r--r--spec/ruby/core/encoding/default_internal_spec.rb74
-rw-r--r--spec/ruby/core/encoding/dummy_spec.rb25
-rw-r--r--spec/ruby/core/encoding/find_spec.rb82
-rw-r--r--spec/ruby/core/encoding/fixtures/classes.rb49
-rw-r--r--spec/ruby/core/encoding/inspect_spec.rb33
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_name_spec.rb19
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_spec.rb19
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb31
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb28
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb31
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_name_spec.rb29
-rw-r--r--spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_spec.rb34
-rw-r--r--spec/ruby/core/encoding/list_spec.rb49
-rw-r--r--spec/ruby/core/encoding/locale_charmap_spec.rb56
-rw-r--r--spec/ruby/core/encoding/name_list_spec.rb23
-rw-r--r--spec/ruby/core/encoding/name_spec.rb15
-rw-r--r--spec/ruby/core/encoding/names_spec.rb35
-rw-r--r--spec/ruby/core/encoding/replicate_spec.rb8
-rw-r--r--spec/ruby/core/encoding/to_s_spec.rb7
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_name_spec.rb16
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_spec.rb16
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/error_char_spec.rb28
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/source_encoding_name_spec.rb29
-rw-r--r--spec/ruby/core/encoding/undefined_conversion_error/source_encoding_spec.rb30
-rw-r--r--spec/ruby/core/enumerable/all_spec.rb187
-rw-r--r--spec/ruby/core/enumerable/any_spec.rb200
-rw-r--r--spec/ruby/core/enumerable/chain_spec.rb23
-rw-r--r--spec/ruby/core/enumerable/chunk_spec.rb77
-rw-r--r--spec/ruby/core/enumerable/chunk_while_spec.rb42
-rw-r--r--spec/ruby/core/enumerable/collect_concat_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/collect_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/compact_spec.rb9
-rw-r--r--spec/ruby/core/enumerable/count_spec.rb59
-rw-r--r--spec/ruby/core/enumerable/cycle_spec.rb104
-rw-r--r--spec/ruby/core/enumerable/detect_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/drop_spec.rb43
-rw-r--r--spec/ruby/core/enumerable/drop_while_spec.rb50
-rw-r--r--spec/ruby/core/enumerable/each_cons_spec.rb103
-rw-r--r--spec/ruby/core/enumerable/each_entry_spec.rb41
-rw-r--r--spec/ruby/core/enumerable/each_slice_spec.rb105
-rw-r--r--spec/ruby/core/enumerable/each_with_index_spec.rb53
-rw-r--r--spec/ruby/core/enumerable/each_with_object_spec.rb41
-rw-r--r--spec/ruby/core/enumerable/entries_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/filter_map_spec.rb24
-rw-r--r--spec/ruby/core/enumerable/filter_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/find_all_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/find_index_spec.rb89
-rw-r--r--spec/ruby/core/enumerable/find_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/first_spec.rb28
-rw-r--r--spec/ruby/core/enumerable/fixtures/classes.rb350
-rw-r--r--spec/ruby/core/enumerable/flat_map_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/grep_spec.rb87
-rw-r--r--spec/ruby/core/enumerable/grep_v_spec.rb76
-rw-r--r--spec/ruby/core/enumerable/group_by_spec.rb37
-rw-r--r--spec/ruby/core/enumerable/include_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/inject_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/lazy_spec.rb10
-rw-r--r--spec/ruby/core/enumerable/map_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/max_by_spec.rb81
-rw-r--r--spec/ruby/core/enumerable/max_spec.rb119
-rw-r--r--spec/ruby/core/enumerable/member_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/min_by_spec.rb81
-rw-r--r--spec/ruby/core/enumerable/min_spec.rb123
-rw-r--r--spec/ruby/core/enumerable/minmax_by_spec.rb44
-rw-r--r--spec/ruby/core/enumerable/minmax_spec.rb20
-rw-r--r--spec/ruby/core/enumerable/none_spec.rb153
-rw-r--r--spec/ruby/core/enumerable/one_spec.rb154
-rw-r--r--spec/ruby/core/enumerable/partition_spec.rb20
-rw-r--r--spec/ruby/core/enumerable/reduce_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/reject_spec.rb25
-rw-r--r--spec/ruby/core/enumerable/reverse_each_spec.rb26
-rw-r--r--spec/ruby/core/enumerable/select_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/shared/collect.rb107
-rw-r--r--spec/ruby/core/enumerable/shared/collect_concat.rb54
-rw-r--r--spec/ruby/core/enumerable/shared/entries.rb16
-rw-r--r--spec/ruby/core/enumerable/shared/enumerable_enumeratorized.rb33
-rw-r--r--spec/ruby/core/enumerable/shared/enumeratorized.rb42
-rw-r--r--spec/ruby/core/enumerable/shared/find.rb77
-rw-r--r--spec/ruby/core/enumerable/shared/find_all.rb31
-rw-r--r--spec/ruby/core/enumerable/shared/include.rb34
-rw-r--r--spec/ruby/core/enumerable/shared/inject.rb142
-rw-r--r--spec/ruby/core/enumerable/shared/take.rb63
-rw-r--r--spec/ruby/core/enumerable/shared/value_packing.rb26
-rw-r--r--spec/ruby/core/enumerable/slice_after_spec.rb61
-rw-r--r--spec/ruby/core/enumerable/slice_before_spec.rb64
-rw-r--r--spec/ruby/core/enumerable/slice_when_spec.rb54
-rw-r--r--spec/ruby/core/enumerable/sort_by_spec.rb43
-rw-r--r--spec/ruby/core/enumerable/sort_spec.rb54
-rw-r--r--spec/ruby/core/enumerable/sum_spec.rb50
-rw-r--r--spec/ruby/core/enumerable/take_spec.rb21
-rw-r--r--spec/ruby/core/enumerable/take_while_spec.rb51
-rw-r--r--spec/ruby/core/enumerable/tally_spec.rb91
-rw-r--r--spec/ruby/core/enumerable/to_a_spec.rb7
-rw-r--r--spec/ruby/core/enumerable/to_h_spec.rb96
-rw-r--r--spec/ruby/core/enumerable/to_set_spec.rb30
-rw-r--r--spec/ruby/core/enumerable/uniq_spec.rb78
-rw-r--r--spec/ruby/core/enumerable/zip_spec.rb46
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb16
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb16
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb18
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb9
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb20
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb20
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb9
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb17
-rw-r--r--spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb11
-rw-r--r--spec/ruby/core/enumerator/chain/each_spec.rb15
-rw-r--r--spec/ruby/core/enumerator/chain/initialize_spec.rb31
-rw-r--r--spec/ruby/core/enumerator/chain/inspect_spec.rb18
-rw-r--r--spec/ruby/core/enumerator/chain/rewind_spec.rb51
-rw-r--r--spec/ruby/core/enumerator/chain/size_spec.rb22
-rw-r--r--spec/ruby/core/enumerator/each_spec.rb104
-rw-r--r--spec/ruby/core/enumerator/each_with_index_spec.rb36
-rw-r--r--spec/ruby/core/enumerator/each_with_object_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/enum_for_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/enumerator_spec.rb7
-rw-r--r--spec/ruby/core/enumerator/feed_spec.rb52
-rw-r--r--spec/ruby/core/enumerator/first_spec.rb7
-rw-r--r--spec/ruby/core/enumerator/fixtures/classes.rb15
-rw-r--r--spec/ruby/core/enumerator/fixtures/common.rb9
-rw-r--r--spec/ruby/core/enumerator/initialize_spec.rb57
-rw-r--r--spec/ruby/core/enumerator/inspect_spec.rb22
-rw-r--r--spec/ruby/core/enumerator/lazy/chunk_spec.rb67
-rw-r--r--spec/ruby/core/enumerator/lazy/chunk_while_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/collect_concat_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/collect_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/compact_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/drop_spec.rb58
-rw-r--r--spec/ruby/core/enumerator/lazy/drop_while_spec.rb66
-rw-r--r--spec/ruby/core/enumerator/lazy/eager_spec.rb27
-rw-r--r--spec/ruby/core/enumerator/lazy/enum_for_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/filter_map_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/filter_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/lazy/find_all_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/fixtures/classes.rb54
-rw-r--r--spec/ruby/core/enumerator/lazy/flat_map_spec.rb16
-rw-r--r--spec/ruby/core/enumerator/lazy/force_spec.rb36
-rw-r--r--spec/ruby/core/enumerator/lazy/grep_spec.rb121
-rw-r--r--spec/ruby/core/enumerator/lazy/grep_v_spec.rb123
-rw-r--r--spec/ruby/core/enumerator/lazy/initialize_spec.rb63
-rw-r--r--spec/ruby/core/enumerator/lazy/lazy_spec.rb27
-rw-r--r--spec/ruby/core/enumerator/lazy/map_spec.rb12
-rw-r--r--spec/ruby/core/enumerator/lazy/reject_spec.rb78
-rw-r--r--spec/ruby/core/enumerator/lazy/select_spec.rb47
-rw-r--r--spec/ruby/core/enumerator/lazy/shared/collect.rb62
-rw-r--r--spec/ruby/core/enumerator/lazy/shared/collect_concat.rb78
-rw-r--r--spec/ruby/core/enumerator/lazy/shared/select.rb66
-rw-r--r--spec/ruby/core/enumerator/lazy/shared/to_enum.rb55
-rw-r--r--spec/ruby/core/enumerator/lazy/slice_after_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/slice_before_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/slice_when_spec.rb14
-rw-r--r--spec/ruby/core/enumerator/lazy/take_spec.rb74
-rw-r--r--spec/ruby/core/enumerator/lazy/take_while_spec.rb60
-rw-r--r--spec/ruby/core/enumerator/lazy/to_enum_spec.rb8
-rw-r--r--spec/ruby/core/enumerator/lazy/uniq_spec.rb74
-rw-r--r--spec/ruby/core/enumerator/lazy/with_index_spec.rb36
-rw-r--r--spec/ruby/core/enumerator/lazy/zip_spec.rb86
-rw-r--r--spec/ruby/core/enumerator/new_spec.rb115
-rw-r--r--spec/ruby/core/enumerator/next_spec.rb38
-rw-r--r--spec/ruby/core/enumerator/next_values_spec.rb61
-rw-r--r--spec/ruby/core/enumerator/peek_spec.rb36
-rw-r--r--spec/ruby/core/enumerator/peek_values_spec.rb63
-rw-r--r--spec/ruby/core/enumerator/plus_spec.rb33
-rw-r--r--spec/ruby/core/enumerator/produce_spec.rb78
-rw-r--r--spec/ruby/core/enumerator/product/each_spec.rb85
-rw-r--r--spec/ruby/core/enumerator/product/initialize_copy_spec.rb52
-rw-r--r--spec/ruby/core/enumerator/product/initialize_spec.rb31
-rw-r--r--spec/ruby/core/enumerator/product/inspect_spec.rb20
-rw-r--r--spec/ruby/core/enumerator/product/rewind_spec.rb62
-rw-r--r--spec/ruby/core/enumerator/product/size_spec.rb64
-rw-r--r--spec/ruby/core/enumerator/product_spec.rb91
-rw-r--r--spec/ruby/core/enumerator/rewind_spec.rb70
-rw-r--r--spec/ruby/core/enumerator/shared/enum_for.rb57
-rw-r--r--spec/ruby/core/enumerator/shared/with_index.rb33
-rw-r--r--spec/ruby/core/enumerator/shared/with_object.rb42
-rw-r--r--spec/ruby/core/enumerator/size_spec.rb26
-rw-r--r--spec/ruby/core/enumerator/to_enum_spec.rb6
-rw-r--r--spec/ruby/core/enumerator/with_index_spec.rb89
-rw-r--r--spec/ruby/core/enumerator/with_object_spec.rb6
-rw-r--r--spec/ruby/core/env/assoc_spec.rb31
-rw-r--r--spec/ruby/core/env/clear_spec.rb20
-rw-r--r--spec/ruby/core/env/clone_spec.rb21
-rw-r--r--spec/ruby/core/env/delete_if_spec.rb54
-rw-r--r--spec/ruby/core/env/delete_spec.rb55
-rw-r--r--spec/ruby/core/env/dup_spec.rb9
-rw-r--r--spec/ruby/core/env/each_key_spec.rb34
-rw-r--r--spec/ruby/core/env/each_pair_spec.rb6
-rw-r--r--spec/ruby/core/env/each_spec.rb6
-rw-r--r--spec/ruby/core/env/each_value_spec.rb34
-rw-r--r--spec/ruby/core/env/element_reference_spec.rb76
-rw-r--r--spec/ruby/core/env/element_set_spec.rb6
-rw-r--r--spec/ruby/core/env/empty_spec.rb23
-rw-r--r--spec/ruby/core/env/except_spec.rb34
-rw-r--r--spec/ruby/core/env/fetch_spec.rb63
-rw-r--r--spec/ruby/core/env/fetch_values_spec.rb51
-rw-r--r--spec/ruby/core/env/filter_spec.rb13
-rw-r--r--spec/ruby/core/env/fixtures/common.rb9
-rw-r--r--spec/ruby/core/env/has_key_spec.rb6
-rw-r--r--spec/ruby/core/env/has_value_spec.rb6
-rw-r--r--spec/ruby/core/env/include_spec.rb6
-rw-r--r--spec/ruby/core/env/inspect_spec.rb11
-rw-r--r--spec/ruby/core/env/invert_spec.rb16
-rw-r--r--spec/ruby/core/env/keep_if_spec.rb54
-rw-r--r--spec/ruby/core/env/key_spec.rb39
-rw-r--r--spec/ruby/core/env/keys_spec.rb14
-rw-r--r--spec/ruby/core/env/length_spec.rb6
-rw-r--r--spec/ruby/core/env/member_spec.rb6
-rw-r--r--spec/ruby/core/env/merge_spec.rb6
-rw-r--r--spec/ruby/core/env/rassoc_spec.rb42
-rw-r--r--spec/ruby/core/env/rehash_spec.rb7
-rw-r--r--spec/ruby/core/env/reject_spec.rb101
-rw-r--r--spec/ruby/core/env/replace_spec.rb51
-rw-r--r--spec/ruby/core/env/select_spec.rb13
-rw-r--r--spec/ruby/core/env/shared/each.rb65
-rw-r--r--spec/ruby/core/env/shared/include.rb30
-rw-r--r--spec/ruby/core/env/shared/length.rb13
-rw-r--r--spec/ruby/core/env/shared/select.rb61
-rw-r--r--spec/ruby/core/env/shared/store.rb60
-rw-r--r--spec/ruby/core/env/shared/to_hash.rb33
-rw-r--r--spec/ruby/core/env/shared/update.rb104
-rw-r--r--spec/ruby/core/env/shared/value.rb29
-rw-r--r--spec/ruby/core/env/shift_spec.rb47
-rw-r--r--spec/ruby/core/env/size_spec.rb6
-rw-r--r--spec/ruby/core/env/slice_spec.rb37
-rw-r--r--spec/ruby/core/env/spec_helper.rb26
-rw-r--r--spec/ruby/core/env/store_spec.rb6
-rw-r--r--spec/ruby/core/env/to_a_spec.rb21
-rw-r--r--spec/ruby/core/env/to_h_spec.rb70
-rw-r--r--spec/ruby/core/env/to_hash_spec.rb6
-rw-r--r--spec/ruby/core/env/to_s_spec.rb7
-rw-r--r--spec/ruby/core/env/update_spec.rb6
-rw-r--r--spec/ruby/core/env/value_spec.rb6
-rw-r--r--spec/ruby/core/env/values_at_spec.rb38
-rw-r--r--spec/ruby/core/env/values_spec.rb14
-rw-r--r--spec/ruby/core/exception/backtrace_locations_spec.rb39
-rw-r--r--spec/ruby/core/exception/backtrace_spec.rb106
-rw-r--r--spec/ruby/core/exception/case_compare_spec.rb37
-rw-r--r--spec/ruby/core/exception/cause_spec.rb38
-rw-r--r--spec/ruby/core/exception/detailed_message_spec.rb50
-rw-r--r--spec/ruby/core/exception/dup_spec.rb74
-rw-r--r--spec/ruby/core/exception/equal_value_spec.rb68
-rw-r--r--spec/ruby/core/exception/errno_spec.rb69
-rw-r--r--spec/ruby/core/exception/exception_spec.rb69
-rw-r--r--spec/ruby/core/exception/exit_value_spec.rb13
-rw-r--r--spec/ruby/core/exception/fixtures/common.rb102
-rw-r--r--spec/ruby/core/exception/fixtures/syntax_error.rb3
-rw-r--r--spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb22
-rw-r--r--spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb25
-rw-r--r--spec/ruby/core/exception/frozen_error_spec.rb54
-rw-r--r--spec/ruby/core/exception/full_message_spec.rb226
-rw-r--r--spec/ruby/core/exception/hierarchy_spec.rb62
-rw-r--r--spec/ruby/core/exception/inspect_spec.rb24
-rw-r--r--spec/ruby/core/exception/interrupt_spec.rb60
-rw-r--r--spec/ruby/core/exception/io_error_spec.rb45
-rw-r--r--spec/ruby/core/exception/key_error_spec.rb19
-rw-r--r--spec/ruby/core/exception/load_error_spec.rb21
-rw-r--r--spec/ruby/core/exception/message_spec.rb27
-rw-r--r--spec/ruby/core/exception/name_error_spec.rb28
-rw-r--r--spec/ruby/core/exception/name_spec.rb43
-rw-r--r--spec/ruby/core/exception/new_spec.rb7
-rw-r--r--spec/ruby/core/exception/no_method_error_spec.rb224
-rw-r--r--spec/ruby/core/exception/reason_spec.rb13
-rw-r--r--spec/ruby/core/exception/receiver_spec.rb58
-rw-r--r--spec/ruby/core/exception/result_spec.rb21
-rw-r--r--spec/ruby/core/exception/set_backtrace_spec.rb23
-rw-r--r--spec/ruby/core/exception/shared/new.rb18
-rw-r--r--spec/ruby/core/exception/shared/set_backtrace.rb64
-rw-r--r--spec/ruby/core/exception/signal_exception_spec.rb123
-rw-r--r--spec/ruby/core/exception/signm_spec.rb9
-rw-r--r--spec/ruby/core/exception/signo_spec.rb9
-rw-r--r--spec/ruby/core/exception/standard_error_spec.rb23
-rw-r--r--spec/ruby/core/exception/status_spec.rb9
-rw-r--r--spec/ruby/core/exception/success_spec.rb15
-rw-r--r--spec/ruby/core/exception/syntax_error_spec.rb25
-rw-r--r--spec/ruby/core/exception/system_call_error_spec.rb163
-rw-r--r--spec/ruby/core/exception/system_exit_spec.rb59
-rw-r--r--spec/ruby/core/exception/to_s_spec.rb37
-rw-r--r--spec/ruby/core/exception/top_level_spec.rb65
-rw-r--r--spec/ruby/core/exception/uncaught_throw_error_spec.rb12
-rw-r--r--spec/ruby/core/false/and_spec.rb11
-rw-r--r--spec/ruby/core/false/case_compare_spec.rb14
-rw-r--r--spec/ruby/core/false/dup_spec.rb7
-rw-r--r--spec/ruby/core/false/falseclass_spec.rb15
-rw-r--r--spec/ruby/core/false/inspect_spec.rb7
-rw-r--r--spec/ruby/core/false/or_spec.rb11
-rw-r--r--spec/ruby/core/false/singleton_method_spec.rb13
-rw-r--r--spec/ruby/core/false/to_s_spec.rb15
-rw-r--r--spec/ruby/core/false/xor_spec.rb11
-rw-r--r--spec/ruby/core/fiber/alive_spec.rb44
-rw-r--r--spec/ruby/core/fiber/blocking_spec.rb73
-rw-r--r--spec/ruby/core/fiber/current_spec.rb50
-rw-r--r--spec/ruby/core/fiber/fixtures/classes.rb22
-rw-r--r--spec/ruby/core/fiber/fixtures/scheduler.rb35
-rw-r--r--spec/ruby/core/fiber/inspect_spec.rb35
-rw-r--r--spec/ruby/core/fiber/kill_spec.rb88
-rw-r--r--spec/ruby/core/fiber/new_spec.rb39
-rw-r--r--spec/ruby/core/fiber/raise_spec.rb141
-rw-r--r--spec/ruby/core/fiber/resume_spec.rb83
-rw-r--r--spec/ruby/core/fiber/scheduler_spec.rb8
-rw-r--r--spec/ruby/core/fiber/set_scheduler_spec.rb8
-rw-r--r--spec/ruby/core/fiber/shared/blocking.rb41
-rw-r--r--spec/ruby/core/fiber/shared/resume.rb58
-rw-r--r--spec/ruby/core/fiber/shared/scheduler.rb51
-rw-r--r--spec/ruby/core/fiber/storage_spec.rb177
-rw-r--r--spec/ruby/core/fiber/transfer_spec.rb84
-rw-r--r--spec/ruby/core/fiber/yield_spec.rb49
-rw-r--r--spec/ruby/core/file/absolute_path_spec.rb94
-rw-r--r--spec/ruby/core/file/atime_spec.rb60
-rw-r--r--spec/ruby/core/file/basename_spec.rb205
-rw-r--r--spec/ruby/core/file/birthtime_spec.rb56
-rw-r--r--spec/ruby/core/file/blockdev_spec.rb6
-rw-r--r--spec/ruby/core/file/chardev_spec.rb6
-rw-r--r--spec/ruby/core/file/chmod_spec.rb185
-rw-r--r--spec/ruby/core/file/chown_spec.rb144
-rw-r--r--spec/ruby/core/file/constants/constants_spec.rb31
-rw-r--r--spec/ruby/core/file/constants_spec.rb141
-rw-r--r--spec/ruby/core/file/ctime_spec.rb54
-rw-r--r--spec/ruby/core/file/delete_spec.rb6
-rw-r--r--spec/ruby/core/file/directory_spec.rb10
-rw-r--r--spec/ruby/core/file/dirname_spec.rb170
-rw-r--r--spec/ruby/core/file/empty_spec.rb7
-rw-r--r--spec/ruby/core/file/executable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/executable_spec.rb7
-rw-r--r--spec/ruby/core/file/exist_spec.rb12
-rw-r--r--spec/ruby/core/file/expand_path_spec.rb265
-rw-r--r--spec/ruby/core/file/extname_spec.rb76
-rw-r--r--spec/ruby/core/file/file_spec.rb16
-rw-r--r--spec/ruby/core/file/fixtures/common.rb22
-rw-r--r--spec/ruby/core/file/fixtures/do_not_remove1
-rw-r--r--spec/ruby/core/file/fixtures/file_types.rb66
-rw-r--r--spec/ruby/core/file/flock_spec.rb74
-rw-r--r--spec/ruby/core/file/fnmatch_spec.rb10
-rw-r--r--spec/ruby/core/file/ftype_spec.rb82
-rw-r--r--spec/ruby/core/file/grpowned_spec.rb10
-rw-r--r--spec/ruby/core/file/identical_spec.rb6
-rw-r--r--spec/ruby/core/file/initialize_spec.rb19
-rw-r--r--spec/ruby/core/file/inspect_spec.rb17
-rw-r--r--spec/ruby/core/file/join_spec.rb148
-rw-r--r--spec/ruby/core/file/lchmod_spec.rb32
-rw-r--r--spec/ruby/core/file/lchown_spec.rb59
-rw-r--r--spec/ruby/core/file/link_spec.rb39
-rw-r--r--spec/ruby/core/file/lstat_spec.rb33
-rw-r--r--spec/ruby/core/file/lutime_spec.rb43
-rw-r--r--spec/ruby/core/file/mkfifo_spec.rb51
-rw-r--r--spec/ruby/core/file/mtime_spec.rb56
-rw-r--r--spec/ruby/core/file/new_spec.rb223
-rw-r--r--spec/ruby/core/file/null_spec.rb15
-rw-r--r--spec/ruby/core/file/open_spec.rb713
-rw-r--r--spec/ruby/core/file/owned_spec.rb35
-rw-r--r--spec/ruby/core/file/path_spec.rb81
-rw-r--r--spec/ruby/core/file/pipe_spec.rb32
-rw-r--r--spec/ruby/core/file/printf_spec.rb18
-rw-r--r--spec/ruby/core/file/read_spec.rb6
-rw-r--r--spec/ruby/core/file/readable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/readable_spec.rb7
-rw-r--r--spec/ruby/core/file/readlink_spec.rb86
-rw-r--r--spec/ruby/core/file/realdirpath_spec.rb104
-rw-r--r--spec/ruby/core/file/realpath_spec.rb98
-rw-r--r--spec/ruby/core/file/rename_spec.rb37
-rw-r--r--spec/ruby/core/file/reopen_spec.rb32
-rw-r--r--spec/ruby/core/file/setgid_spec.rb36
-rw-r--r--spec/ruby/core/file/setuid_spec.rb34
-rw-r--r--spec/ruby/core/file/shared/fnmatch.rb294
-rw-r--r--spec/ruby/core/file/shared/open.rb12
-rw-r--r--spec/ruby/core/file/shared/path.rb82
-rw-r--r--spec/ruby/core/file/shared/read.rb15
-rw-r--r--spec/ruby/core/file/shared/stat.rb32
-rw-r--r--spec/ruby/core/file/shared/unlink.rb61
-rw-r--r--spec/ruby/core/file/shared/update_time.rb105
-rw-r--r--spec/ruby/core/file/size_spec.rb119
-rw-r--r--spec/ruby/core/file/socket_spec.rb10
-rw-r--r--spec/ruby/core/file/split_spec.rb64
-rw-r--r--spec/ruby/core/file/stat/atime_spec.rb18
-rw-r--r--spec/ruby/core/file/stat/birthtime_spec.rb29
-rw-r--r--spec/ruby/core/file/stat/blksize_spec.rb27
-rw-r--r--spec/ruby/core/file/stat/blockdev_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/blocks_spec.rb27
-rw-r--r--spec/ruby/core/file/stat/chardev_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/comparison_spec.rb66
-rw-r--r--spec/ruby/core/file/stat/ctime_spec.rb18
-rw-r--r--spec/ruby/core/file/stat/dev_major_spec.rb23
-rw-r--r--spec/ruby/core/file/stat/dev_minor_spec.rb23
-rw-r--r--spec/ruby/core/file/stat/dev_spec.rb15
-rw-r--r--spec/ruby/core/file/stat/directory_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/executable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/executable_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/file_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/fixtures/classes.rb5
-rw-r--r--spec/ruby/core/file/stat/ftype_spec.rb64
-rw-r--r--spec/ruby/core/file/stat/gid_spec.rb19
-rw-r--r--spec/ruby/core/file/stat/grpowned_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/ino_spec.rb28
-rw-r--r--spec/ruby/core/file/stat/inspect_spec.rb26
-rw-r--r--spec/ruby/core/file/stat/mode_spec.rb19
-rw-r--r--spec/ruby/core/file/stat/mtime_spec.rb18
-rw-r--r--spec/ruby/core/file/stat/new_spec.rb32
-rw-r--r--spec/ruby/core/file/stat/nlink_spec.rb21
-rw-r--r--spec/ruby/core/file/stat/owned_spec.rb33
-rw-r--r--spec/ruby/core/file/stat/pipe_spec.rb32
-rw-r--r--spec/ruby/core/file/stat/rdev_major_spec.rb24
-rw-r--r--spec/ruby/core/file/stat/rdev_minor_spec.rb24
-rw-r--r--spec/ruby/core/file/stat/rdev_spec.rb15
-rw-r--r--spec/ruby/core/file/stat/readable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/readable_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/setgid_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/setuid_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/size_spec.rb21
-rw-r--r--spec/ruby/core/file/stat/socket_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/sticky_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/symlink_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/uid_spec.rb18
-rw-r--r--spec/ruby/core/file/stat/world_readable_spec.rb11
-rw-r--r--spec/ruby/core/file/stat/world_writable_spec.rb11
-rw-r--r--spec/ruby/core/file/stat/writable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/writable_spec.rb7
-rw-r--r--spec/ruby/core/file/stat/zero_spec.rb7
-rw-r--r--spec/ruby/core/file/stat_spec.rb55
-rw-r--r--spec/ruby/core/file/sticky_spec.rb50
-rw-r--r--spec/ruby/core/file/symlink_spec.rb57
-rw-r--r--spec/ruby/core/file/to_path_spec.rb6
-rw-r--r--spec/ruby/core/file/truncate_spec.rb177
-rw-r--r--spec/ruby/core/file/umask_spec.rb57
-rw-r--r--spec/ruby/core/file/unlink_spec.rb6
-rw-r--r--spec/ruby/core/file/utime_spec.rb6
-rw-r--r--spec/ruby/core/file/world_readable_spec.rb12
-rw-r--r--spec/ruby/core/file/world_writable_spec.rb12
-rw-r--r--spec/ruby/core/file/writable_real_spec.rb7
-rw-r--r--spec/ruby/core/file/writable_spec.rb7
-rw-r--r--spec/ruby/core/file/zero_spec.rb7
-rw-r--r--spec/ruby/core/filetest/blockdev_spec.rb6
-rw-r--r--spec/ruby/core/filetest/chardev_spec.rb6
-rw-r--r--spec/ruby/core/filetest/directory_spec.rb10
-rw-r--r--spec/ruby/core/filetest/executable_real_spec.rb7
-rw-r--r--spec/ruby/core/filetest/executable_spec.rb7
-rw-r--r--spec/ruby/core/filetest/exist_spec.rb12
-rw-r--r--spec/ruby/core/filetest/file_spec.rb10
-rw-r--r--spec/ruby/core/filetest/grpowned_spec.rb10
-rw-r--r--spec/ruby/core/filetest/identical_spec.rb6
-rw-r--r--spec/ruby/core/filetest/owned_spec.rb6
-rw-r--r--spec/ruby/core/filetest/pipe_spec.rb6
-rw-r--r--spec/ruby/core/filetest/readable_real_spec.rb7
-rw-r--r--spec/ruby/core/filetest/readable_spec.rb7
-rw-r--r--spec/ruby/core/filetest/setgid_spec.rb6
-rw-r--r--spec/ruby/core/filetest/setuid_spec.rb6
-rw-r--r--spec/ruby/core/filetest/size_spec.rb34
-rw-r--r--spec/ruby/core/filetest/socket_spec.rb10
-rw-r--r--spec/ruby/core/filetest/sticky_spec.rb7
-rw-r--r--spec/ruby/core/filetest/symlink_spec.rb10
-rw-r--r--spec/ruby/core/filetest/world_readable_spec.rb5
-rw-r--r--spec/ruby/core/filetest/world_writable_spec.rb5
-rw-r--r--spec/ruby/core/filetest/writable_real_spec.rb7
-rw-r--r--spec/ruby/core/filetest/writable_spec.rb7
-rw-r--r--spec/ruby/core/filetest/zero_spec.rb7
-rw-r--r--spec/ruby/core/float/abs_spec.rb6
-rw-r--r--spec/ruby/core/float/angle_spec.rb6
-rw-r--r--spec/ruby/core/float/arg_spec.rb6
-rw-r--r--spec/ruby/core/float/case_compare_spec.rb6
-rw-r--r--spec/ruby/core/float/ceil_spec.rb28
-rw-r--r--spec/ruby/core/float/coerce_spec.rb18
-rw-r--r--spec/ruby/core/float/comparison_spec.rb113
-rw-r--r--spec/ruby/core/float/constants_spec.rb55
-rw-r--r--spec/ruby/core/float/denominator_spec.rb29
-rw-r--r--spec/ruby/core/float/divide_spec.rb43
-rw-r--r--spec/ruby/core/float/divmod_spec.rb43
-rw-r--r--spec/ruby/core/float/dup_spec.rb8
-rw-r--r--spec/ruby/core/float/eql_spec.rb16
-rw-r--r--spec/ruby/core/float/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/float/exponent_spec.rb15
-rw-r--r--spec/ruby/core/float/fdiv_spec.rb6
-rw-r--r--spec/ruby/core/float/finite_spec.rb19
-rw-r--r--spec/ruby/core/float/fixtures/classes.rb4
-rw-r--r--spec/ruby/core/float/fixtures/coerce.rb15
-rw-r--r--spec/ruby/core/float/float_spec.rb19
-rw-r--r--spec/ruby/core/float/floor_spec.rb28
-rw-r--r--spec/ruby/core/float/gt_spec.rb38
-rw-r--r--spec/ruby/core/float/gte_spec.rb38
-rw-r--r--spec/ruby/core/float/hash_spec.rb11
-rw-r--r--spec/ruby/core/float/infinite_spec.rb19
-rw-r--r--spec/ruby/core/float/inspect_spec.rb6
-rw-r--r--spec/ruby/core/float/lt_spec.rb38
-rw-r--r--spec/ruby/core/float/lte_spec.rb39
-rw-r--r--spec/ruby/core/float/magnitude_spec.rb6
-rw-r--r--spec/ruby/core/float/minus_spec.rb12
-rw-r--r--spec/ruby/core/float/modulo_spec.rb10
-rw-r--r--spec/ruby/core/float/multiply_spec.rb17
-rw-r--r--spec/ruby/core/float/nan_spec.rb9
-rw-r--r--spec/ruby/core/float/negative_spec.rb33
-rw-r--r--spec/ruby/core/float/next_float_spec.rb49
-rw-r--r--spec/ruby/core/float/numerator_spec.rb39
-rw-r--r--spec/ruby/core/float/phase_spec.rb6
-rw-r--r--spec/ruby/core/float/plus_spec.rb12
-rw-r--r--spec/ruby/core/float/positive_spec.rb33
-rw-r--r--spec/ruby/core/float/prev_float_spec.rb49
-rw-r--r--spec/ruby/core/float/quo_spec.rb6
-rw-r--r--spec/ruby/core/float/rationalize_spec.rb43
-rw-r--r--spec/ruby/core/float/round_spec.rb208
-rw-r--r--spec/ruby/core/float/shared/abs.rb21
-rw-r--r--spec/ruby/core/float/shared/arg.rb36
-rw-r--r--spec/ruby/core/float/shared/arithmetic_exception_in_coerce.rb11
-rw-r--r--spec/ruby/core/float/shared/comparison_exception_in_coerce.rb11
-rw-r--r--spec/ruby/core/float/shared/equal.rb38
-rw-r--r--spec/ruby/core/float/shared/modulo.rb48
-rw-r--r--spec/ruby/core/float/shared/quo.rb59
-rw-r--r--spec/ruby/core/float/shared/to_i.rb14
-rw-r--r--spec/ruby/core/float/shared/to_s.rb308
-rw-r--r--spec/ruby/core/float/to_f_spec.rb9
-rw-r--r--spec/ruby/core/float/to_i_spec.rb6
-rw-r--r--spec/ruby/core/float/to_int_spec.rb6
-rw-r--r--spec/ruby/core/float/to_r_spec.rb5
-rw-r--r--spec/ruby/core/float/to_s_spec.rb6
-rw-r--r--spec/ruby/core/float/truncate_spec.rb14
-rw-r--r--spec/ruby/core/float/uminus_spec.rb28
-rw-r--r--spec/ruby/core/float/uplus_spec.rb9
-rw-r--r--spec/ruby/core/float/zero_spec.rb9
-rw-r--r--spec/ruby/core/gc/auto_compact_spec.rb24
-rw-r--r--spec/ruby/core/gc/config_spec.rb97
-rw-r--r--spec/ruby/core/gc/count_spec.rb17
-rw-r--r--spec/ruby/core/gc/disable_spec.rb18
-rw-r--r--spec/ruby/core/gc/enable_spec.rb13
-rw-r--r--spec/ruby/core/gc/garbage_collect_spec.rb15
-rw-r--r--spec/ruby/core/gc/measure_total_time_spec.rb17
-rw-r--r--spec/ruby/core/gc/profiler/clear_spec.rb5
-rw-r--r--spec/ruby/core/gc/profiler/disable_spec.rb16
-rw-r--r--spec/ruby/core/gc/profiler/enable_spec.rb17
-rw-r--r--spec/ruby/core/gc/profiler/enabled_spec.rb21
-rw-r--r--spec/ruby/core/gc/profiler/report_spec.rb5
-rw-r--r--spec/ruby/core/gc/profiler/result_spec.rb7
-rw-r--r--spec/ruby/core/gc/profiler/total_time_spec.rb7
-rw-r--r--spec/ruby/core/gc/start_spec.rb12
-rw-r--r--spec/ruby/core/gc/stat_spec.rb62
-rw-r--r--spec/ruby/core/gc/stress_spec.rb27
-rw-r--r--spec/ruby/core/gc/total_time_spec.rb13
-rw-r--r--spec/ruby/core/hash/allocate_spec.rb15
-rw-r--r--spec/ruby/core/hash/any_spec.rb30
-rw-r--r--spec/ruby/core/hash/assoc_spec.rb50
-rw-r--r--spec/ruby/core/hash/clear_spec.rb32
-rw-r--r--spec/ruby/core/hash/clone_spec.rb12
-rw-r--r--spec/ruby/core/hash/compact_spec.rb81
-rw-r--r--spec/ruby/core/hash/compare_by_identity_spec.rb147
-rw-r--r--spec/ruby/core/hash/constructor_spec.rb127
-rw-r--r--spec/ruby/core/hash/deconstruct_keys_spec.rb23
-rw-r--r--spec/ruby/core/hash/default_proc_spec.rb80
-rw-r--r--spec/ruby/core/hash/default_spec.rb46
-rw-r--r--spec/ruby/core/hash/delete_if_spec.rb44
-rw-r--r--spec/ruby/core/hash/delete_spec.rb58
-rw-r--r--spec/ruby/core/hash/dig_spec.rb66
-rw-r--r--spec/ruby/core/hash/each_key_spec.rb23
-rw-r--r--spec/ruby/core/hash/each_pair_spec.rb111
-rw-r--r--spec/ruby/core/hash/each_spec.rb7
-rw-r--r--spec/ruby/core/hash/each_value_spec.rb23
-rw-r--r--spec/ruby/core/hash/element_reference_spec.rb134
-rw-r--r--spec/ruby/core/hash/element_set_spec.rb121
-rw-r--r--spec/ruby/core/hash/empty_spec.rb15
-rw-r--r--spec/ruby/core/hash/eql_spec.rb9
-rw-r--r--spec/ruby/core/hash/equal_value_spec.rb18
-rw-r--r--spec/ruby/core/hash/except_spec.rb42
-rw-r--r--spec/ruby/core/hash/fetch_spec.rb44
-rw-r--r--spec/ruby/core/hash/fetch_values_spec.rb35
-rw-r--r--spec/ruby/core/hash/filter_spec.rb13
-rw-r--r--spec/ruby/core/hash/fixtures/classes.rb75
-rw-r--r--spec/ruby/core/hash/fixtures/name.rb30
-rw-r--r--spec/ruby/core/hash/flatten_spec.rb62
-rw-r--r--spec/ruby/core/hash/gt_spec.rb42
-rw-r--r--spec/ruby/core/hash/gte_spec.rb42
-rw-r--r--spec/ruby/core/hash/has_key_spec.rb7
-rw-r--r--spec/ruby/core/hash/has_value_spec.rb16
-rw-r--r--spec/ruby/core/hash/hash_spec.rb51
-rw-r--r--spec/ruby/core/hash/include_spec.rb40
-rw-r--r--spec/ruby/core/hash/initialize_spec.rb61
-rw-r--r--spec/ruby/core/hash/inspect_spec.rb123
-rw-r--r--spec/ruby/core/hash/invert_spec.rb48
-rw-r--r--spec/ruby/core/hash/keep_if_spec.rb37
-rw-r--r--spec/ruby/core/hash/key_spec.rb32
-rw-r--r--spec/ruby/core/hash/keys_spec.rb23
-rw-r--r--spec/ruby/core/hash/length_spec.rb7
-rw-r--r--spec/ruby/core/hash/lt_spec.rb42
-rw-r--r--spec/ruby/core/hash/lte_spec.rb42
-rw-r--r--spec/ruby/core/hash/member_spec.rb7
-rw-r--r--spec/ruby/core/hash/merge_spec.rb123
-rw-r--r--spec/ruby/core/hash/new_spec.rb68
-rw-r--r--spec/ruby/core/hash/rassoc_spec.rb42
-rw-r--r--spec/ruby/core/hash/rehash_spec.rb114
-rw-r--r--spec/ruby/core/hash/reject_spec.rb116
-rw-r--r--spec/ruby/core/hash/replace_spec.rb79
-rw-r--r--spec/ruby/core/hash/ruby2_keywords_hash_spec.rb81
-rw-r--r--spec/ruby/core/hash/select_spec.rb112
-rw-r--r--spec/ruby/core/hash/shared/comparison.rb15
-rw-r--r--spec/ruby/core/hash/shared/eql.rb204
-rw-r--r--spec/ruby/core/hash/shared/greater_than.rb23
-rw-r--r--spec/ruby/core/hash/shared/iteration.rb19
-rw-r--r--spec/ruby/core/hash/shared/less_than.rb23
-rw-r--r--spec/ruby/core/hash/shift_spec.rb78
-rw-r--r--spec/ruby/core/hash/size_spec.rb14
-rw-r--r--spec/ruby/core/hash/slice_spec.rb74
-rw-r--r--spec/ruby/core/hash/sort_spec.rb17
-rw-r--r--spec/ruby/core/hash/store_spec.rb7
-rw-r--r--spec/ruby/core/hash/to_a_spec.rb29
-rw-r--r--spec/ruby/core/hash/to_h_spec.rb106
-rw-r--r--spec/ruby/core/hash/to_hash_spec.rb14
-rw-r--r--spec/ruby/core/hash/to_proc_spec.rb91
-rw-r--r--spec/ruby/core/hash/to_s_spec.rb7
-rw-r--r--spec/ruby/core/hash/transform_keys_spec.rb150
-rw-r--r--spec/ruby/core/hash/transform_values_spec.rb118
-rw-r--r--spec/ruby/core/hash/try_convert_spec.rb50
-rw-r--r--spec/ruby/core/hash/update_spec.rb79
-rw-r--r--spec/ruby/core/hash/value_spec.rb7
-rw-r--r--spec/ruby/core/hash/values_at_spec.rb11
-rw-r--r--spec/ruby/core/hash/values_spec.rb10
-rw-r--r--spec/ruby/core/integer/abs_spec.rb6
-rw-r--r--spec/ruby/core/integer/allbits_spec.rb37
-rw-r--r--spec/ruby/core/integer/anybits_spec.rb36
-rw-r--r--spec/ruby/core/integer/bit_and_spec.rb97
-rw-r--r--spec/ruby/core/integer/bit_length_spec.rb76
-rw-r--r--spec/ruby/core/integer/bit_or_spec.rb89
-rw-r--r--spec/ruby/core/integer/bit_xor_spec.rb93
-rw-r--r--spec/ruby/core/integer/case_compare_spec.rb6
-rw-r--r--spec/ruby/core/integer/ceil_spec.rb13
-rw-r--r--spec/ruby/core/integer/ceildiv_spec.rb20
-rw-r--r--spec/ruby/core/integer/chr_spec.rb257
-rw-r--r--spec/ruby/core/integer/coerce_spec.rb91
-rw-r--r--spec/ruby/core/integer/comparison_spec.rb185
-rw-r--r--spec/ruby/core/integer/complement_spec.rb20
-rw-r--r--spec/ruby/core/integer/constants_spec.rb13
-rw-r--r--spec/ruby/core/integer/denominator_spec.rb20
-rw-r--r--spec/ruby/core/integer/digits_spec.rb41
-rw-r--r--spec/ruby/core/integer/div_spec.rb154
-rw-r--r--spec/ruby/core/integer/divide_spec.rb126
-rw-r--r--spec/ruby/core/integer/divmod_spec.rb117
-rw-r--r--spec/ruby/core/integer/downto_spec.rb69
-rw-r--r--spec/ruby/core/integer/dup_spec.rb13
-rw-r--r--spec/ruby/core/integer/element_reference_spec.rb188
-rw-r--r--spec/ruby/core/integer/eql_spec.rb25
-rw-r--r--spec/ruby/core/integer/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/integer/even_spec.rb40
-rw-r--r--spec/ruby/core/integer/exponent_spec.rb7
-rw-r--r--spec/ruby/core/integer/fdiv_spec.rb100
-rw-r--r--spec/ruby/core/integer/fixtures/classes.rb14
-rw-r--r--spec/ruby/core/integer/floor_spec.rb13
-rw-r--r--spec/ruby/core/integer/gcd_spec.rb69
-rw-r--r--spec/ruby/core/integer/gcdlcm_spec.rb53
-rw-r--r--spec/ruby/core/integer/gt_spec.rb48
-rw-r--r--spec/ruby/core/integer/gte_spec.rb48
-rw-r--r--spec/ruby/core/integer/integer_spec.rb20
-rw-r--r--spec/ruby/core/integer/lcm_spec.rb58
-rw-r--r--spec/ruby/core/integer/left_shift_spec.rb211
-rw-r--r--spec/ruby/core/integer/lt_spec.rb50
-rw-r--r--spec/ruby/core/integer/lte_spec.rb58
-rw-r--r--spec/ruby/core/integer/magnitude_spec.rb6
-rw-r--r--spec/ruby/core/integer/minus_spec.rb60
-rw-r--r--spec/ruby/core/integer/modulo_spec.rb10
-rw-r--r--spec/ruby/core/integer/multiply_spec.rb45
-rw-r--r--spec/ruby/core/integer/next_spec.rb6
-rw-r--r--spec/ruby/core/integer/nobits_spec.rb36
-rw-r--r--spec/ruby/core/integer/numerator_spec.rb18
-rw-r--r--spec/ruby/core/integer/odd_spec.rb38
-rw-r--r--spec/ruby/core/integer/ord_spec.rb17
-rw-r--r--spec/ruby/core/integer/plus_spec.rb75
-rw-r--r--spec/ruby/core/integer/pow_spec.rb51
-rw-r--r--spec/ruby/core/integer/pred_spec.rb11
-rw-r--r--spec/ruby/core/integer/rationalize_spec.rb39
-rw-r--r--spec/ruby/core/integer/remainder_spec.rb51
-rw-r--r--spec/ruby/core/integer/right_shift_spec.rb233
-rw-r--r--spec/ruby/core/integer/round_spec.rb81
-rw-r--r--spec/ruby/core/integer/shared/abs.rb18
-rw-r--r--spec/ruby/core/integer/shared/arithmetic_coerce.rb11
-rw-r--r--spec/ruby/core/integer/shared/comparison_coerce.rb11
-rw-r--r--spec/ruby/core/integer/shared/equal.rb63
-rw-r--r--spec/ruby/core/integer/shared/exponent.rb162
-rw-r--r--spec/ruby/core/integer/shared/integer_ceil_precision.rb54
-rw-r--r--spec/ruby/core/integer/shared/integer_floor_precision.rb42
-rw-r--r--spec/ruby/core/integer/shared/integer_rounding.rb19
-rw-r--r--spec/ruby/core/integer/shared/modulo.rb114
-rw-r--r--spec/ruby/core/integer/shared/next.rb25
-rw-r--r--spec/ruby/core/integer/shared/to_i.rb8
-rw-r--r--spec/ruby/core/integer/size_spec.rb34
-rw-r--r--spec/ruby/core/integer/sqrt_spec.rb31
-rw-r--r--spec/ruby/core/integer/succ_spec.rb6
-rw-r--r--spec/ruby/core/integer/times_spec.rb79
-rw-r--r--spec/ruby/core/integer/to_f_spec.rb23
-rw-r--r--spec/ruby/core/integer/to_i_spec.rb6
-rw-r--r--spec/ruby/core/integer/to_int_spec.rb6
-rw-r--r--spec/ruby/core/integer/to_r_spec.rb26
-rw-r--r--spec/ruby/core/integer/to_s_spec.rb95
-rw-r--r--spec/ruby/core/integer/truncate_spec.rb19
-rw-r--r--spec/ruby/core/integer/try_convert_spec.rb48
-rw-r--r--spec/ruby/core/integer/uminus_spec.rb30
-rw-r--r--spec/ruby/core/integer/upto_spec.rb69
-rw-r--r--spec/ruby/core/integer/zero_spec.rb13
-rw-r--r--spec/ruby/core/io/advise_spec.rb86
-rw-r--r--spec/ruby/core/io/autoclose_spec.rb77
-rw-r--r--spec/ruby/core/io/binmode_spec.rb64
-rw-r--r--spec/ruby/core/io/binread_spec.rb57
-rw-r--r--spec/ruby/core/io/binwrite_spec.rb6
-rw-r--r--spec/ruby/core/io/buffer/and_spec.rb62
-rw-r--r--spec/ruby/core/io/buffer/bit_count_spec.rb64
-rw-r--r--spec/ruby/core/io/buffer/empty_spec.rb27
-rw-r--r--spec/ruby/core/io/buffer/external_spec.rb23
-rw-r--r--spec/ruby/core/io/buffer/for_spec.rb95
-rw-r--r--spec/ruby/core/io/buffer/free_spec.rb102
-rw-r--r--spec/ruby/core/io/buffer/initialize_spec.rb119
-rw-r--r--spec/ruby/core/io/buffer/internal_spec.rb23
-rw-r--r--spec/ruby/core/io/buffer/locked_spec.rb75
-rw-r--r--spec/ruby/core/io/buffer/map_spec.rb347
-rw-r--r--spec/ruby/core/io/buffer/mapped_spec.rb23
-rw-r--r--spec/ruby/core/io/buffer/not_spec.rb37
-rw-r--r--spec/ruby/core/io/buffer/null_spec.rb27
-rw-r--r--spec/ruby/core/io/buffer/or_spec.rb62
-rw-r--r--spec/ruby/core/io/buffer/private_spec.rb23
-rw-r--r--spec/ruby/core/io/buffer/readonly_spec.rb28
-rw-r--r--spec/ruby/core/io/buffer/resize_spec.rb151
-rw-r--r--spec/ruby/core/io/buffer/shared/null_and_empty.rb57
-rw-r--r--spec/ruby/core/io/buffer/shared_spec.rb33
-rw-r--r--spec/ruby/core/io/buffer/string_spec.rb62
-rw-r--r--spec/ruby/core/io/buffer/transfer_spec.rb117
-rw-r--r--spec/ruby/core/io/buffer/valid_spec.rb99
-rw-r--r--spec/ruby/core/io/buffer/xor_spec.rb62
-rw-r--r--spec/ruby/core/io/close_on_exec_spec.rb76
-rw-r--r--spec/ruby/core/io/close_read_spec.rb61
-rw-r--r--spec/ruby/core/io/close_spec.rb118
-rw-r--r--spec/ruby/core/io/close_write_spec.rb68
-rw-r--r--spec/ruby/core/io/closed_spec.rb20
-rw-r--r--spec/ruby/core/io/constants_spec.rb19
-rw-r--r--spec/ruby/core/io/copy_stream_spec.rb351
-rw-r--r--spec/ruby/core/io/dup_spec.rb106
-rw-r--r--spec/ruby/core/io/each_byte_spec.rb57
-rw-r--r--spec/ruby/core/io/each_char_spec.rb12
-rw-r--r--spec/ruby/core/io/each_codepoint_spec.rb43
-rw-r--r--spec/ruby/core/io/each_line_spec.rb11
-rw-r--r--spec/ruby/core/io/each_spec.rb11
-rw-r--r--spec/ruby/core/io/eof_spec.rb107
-rw-r--r--spec/ruby/core/io/external_encoding_spec.rb223
-rw-r--r--spec/ruby/core/io/fcntl_spec.rb8
-rw-r--r--spec/ruby/core/io/fdatasync_spec.rb5
-rw-r--r--spec/ruby/core/io/fileno_spec.rb12
-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-16BE.txtbin0 -> 20 bytes-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-16LE.txtbin0 -> 20 bytes-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-32BE.txtbin0 -> 40 bytes-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-32LE.txtbin0 -> 40 bytes-rw-r--r--spec/ruby/core/io/fixtures/bom_UTF-8.txt1
-rw-r--r--spec/ruby/core/io/fixtures/classes.rb218
-rw-r--r--spec/ruby/core/io/fixtures/copy_in_out.rb2
-rw-r--r--spec/ruby/core/io/fixtures/copy_stream.txt6
-rw-r--r--spec/ruby/core/io/fixtures/empty.txt0
-rw-r--r--spec/ruby/core/io/fixtures/incomplete.txt1
-rw-r--r--spec/ruby/core/io/fixtures/lines.txt9
-rw-r--r--spec/ruby/core/io/fixtures/no_bom_UTF-8.txt1
-rw-r--r--spec/ruby/core/io/fixtures/numbered_lines.txt5
-rw-r--r--spec/ruby/core/io/fixtures/one_byte.txt1
-rw-r--r--spec/ruby/core/io/fixtures/read_binary.txt1
-rw-r--r--spec/ruby/core/io/fixtures/read_euc_jp.txt1
-rw-r--r--spec/ruby/core/io/fixtures/read_text.txt1
-rw-r--r--spec/ruby/core/io/fixtures/reopen_stdout.rb3
-rw-r--r--spec/ruby/core/io/flush_spec.rb37
-rw-r--r--spec/ruby/core/io/for_fd_spec.rb10
-rw-r--r--spec/ruby/core/io/foreach_spec.rb96
-rw-r--r--spec/ruby/core/io/fsync_spec.rb24
-rw-r--r--spec/ruby/core/io/getbyte_spec.rb58
-rw-r--r--spec/ruby/core/io/getc_spec.rb42
-rw-r--r--spec/ruby/core/io/gets_spec.rb348
-rw-r--r--spec/ruby/core/io/initialize_spec.rb60
-rw-r--r--spec/ruby/core/io/inspect_spec.rb23
-rw-r--r--spec/ruby/core/io/internal_encoding_spec.rb145
-rw-r--r--spec/ruby/core/io/io_spec.rb11
-rw-r--r--spec/ruby/core/io/ioctl_spec.rb32
-rw-r--r--spec/ruby/core/io/isatty_spec.rb6
-rw-r--r--spec/ruby/core/io/lineno_spec.rb138
-rw-r--r--spec/ruby/core/io/new_spec.rb18
-rw-r--r--spec/ruby/core/io/nonblock_spec.rb48
-rw-r--r--spec/ruby/core/io/open_spec.rb99
-rw-r--r--spec/ruby/core/io/output_spec.rb27
-rw-r--r--spec/ruby/core/io/path_spec.rb12
-rw-r--r--spec/ruby/core/io/pid_spec.rb35
-rw-r--r--spec/ruby/core/io/pipe_spec.rb225
-rw-r--r--spec/ruby/core/io/popen_spec.rb287
-rw-r--r--spec/ruby/core/io/pos_spec.rb11
-rw-r--r--spec/ruby/core/io/pread_spec.rb138
-rw-r--r--spec/ruby/core/io/print_spec.rb66
-rw-r--r--spec/ruby/core/io/printf_spec.rb32
-rw-r--r--spec/ruby/core/io/putc_spec.rb11
-rw-r--r--spec/ruby/core/io/puts_spec.rb139
-rw-r--r--spec/ruby/core/io/pwrite_spec.rb67
-rw-r--r--spec/ruby/core/io/read_nonblock_spec.rb148
-rw-r--r--spec/ruby/core/io/read_spec.rb735
-rw-r--r--spec/ruby/core/io/readbyte_spec.rb24
-rw-r--r--spec/ruby/core/io/readchar_spec.rb110
-rw-r--r--spec/ruby/core/io/readline_spec.rb84
-rw-r--r--spec/ruby/core/io/readlines_spec.rb257
-rw-r--r--spec/ruby/core/io/readpartial_spec.rb115
-rw-r--r--spec/ruby/core/io/reopen_spec.rb313
-rw-r--r--spec/ruby/core/io/rewind_spec.rb53
-rw-r--r--spec/ruby/core/io/seek_spec.rb79
-rw-r--r--spec/ruby/core/io/select_spec.rb190
-rw-r--r--spec/ruby/core/io/set_encoding_by_bom_spec.rb262
-rw-r--r--spec/ruby/core/io/set_encoding_spec.rb238
-rw-r--r--spec/ruby/core/io/shared/binwrite.rb91
-rw-r--r--spec/ruby/core/io/shared/chars.rb73
-rw-r--r--spec/ruby/core/io/shared/codepoints.rb54
-rw-r--r--spec/ruby/core/io/shared/each.rb251
-rw-r--r--spec/ruby/core/io/shared/gets_ascii.rb19
-rw-r--r--spec/ruby/core/io/shared/new.rb413
-rw-r--r--spec/ruby/core/io/shared/pos.rb78
-rw-r--r--spec/ruby/core/io/shared/readlines.rb257
-rw-r--r--spec/ruby/core/io/shared/tty.rb24
-rw-r--r--spec/ruby/core/io/shared/write.rb154
-rw-r--r--spec/ruby/core/io/stat_spec.rb25
-rw-r--r--spec/ruby/core/io/sync_spec.rb64
-rw-r--r--spec/ruby/core/io/sysopen_spec.rb50
-rw-r--r--spec/ruby/core/io/sysread_spec.rb137
-rw-r--r--spec/ruby/core/io/sysseek_spec.rb49
-rw-r--r--spec/ruby/core/io/syswrite_spec.rb82
-rw-r--r--spec/ruby/core/io/tell_spec.rb7
-rw-r--r--spec/ruby/core/io/to_i_spec.rb12
-rw-r--r--spec/ruby/core/io/to_io_spec.rb21
-rw-r--r--spec/ruby/core/io/try_convert_spec.rb49
-rw-r--r--spec/ruby/core/io/tty_spec.rb6
-rw-r--r--spec/ruby/core/io/ungetbyte_spec.rb54
-rw-r--r--spec/ruby/core/io/ungetc_spec.rb138
-rw-r--r--spec/ruby/core/io/write_nonblock_spec.rb96
-rw-r--r--spec/ruby/core/io/write_spec.rb304
-rw-r--r--spec/ruby/core/kernel/Array_spec.rb97
-rw-r--r--spec/ruby/core/kernel/Complex_spec.rb276
-rw-r--r--spec/ruby/core/kernel/Float_spec.rb413
-rw-r--r--spec/ruby/core/kernel/Hash_spec.rb63
-rw-r--r--spec/ruby/core/kernel/Integer_spec.rb845
-rw-r--r--spec/ruby/core/kernel/Rational_spec.rb236
-rw-r--r--spec/ruby/core/kernel/String_spec.rb106
-rw-r--r--spec/ruby/core/kernel/__callee___spec.rb48
-rw-r--r--spec/ruby/core/kernel/__dir___spec.rb27
-rw-r--r--spec/ruby/core/kernel/__method___spec.rb40
-rw-r--r--spec/ruby/core/kernel/abort_spec.rb15
-rw-r--r--spec/ruby/core/kernel/at_exit_spec.rb19
-rw-r--r--spec/ruby/core/kernel/autoload_relative_spec.rb114
-rw-r--r--spec/ruby/core/kernel/autoload_spec.rb178
-rw-r--r--spec/ruby/core/kernel/backtick_spec.rb84
-rw-r--r--spec/ruby/core/kernel/binding_spec.rb51
-rw-r--r--spec/ruby/core/kernel/block_given_spec.rb43
-rw-r--r--spec/ruby/core/kernel/caller_locations_spec.rb113
-rw-r--r--spec/ruby/core/kernel/caller_spec.rb121
-rw-r--r--spec/ruby/core/kernel/case_compare_spec.rb135
-rw-r--r--spec/ruby/core/kernel/catch_spec.rb127
-rw-r--r--spec/ruby/core/kernel/chomp_spec.rb65
-rw-r--r--spec/ruby/core/kernel/chop_spec.rb53
-rw-r--r--spec/ruby/core/kernel/class_spec.rb26
-rw-r--r--spec/ruby/core/kernel/clone_spec.rb177
-rw-r--r--spec/ruby/core/kernel/comparison_spec.rb31
-rw-r--r--spec/ruby/core/kernel/define_singleton_method_spec.rb120
-rw-r--r--spec/ruby/core/kernel/display_spec.rb6
-rw-r--r--spec/ruby/core/kernel/dup_spec.rb67
-rw-r--r--spec/ruby/core/kernel/enum_for_spec.rb5
-rw-r--r--spec/ruby/core/kernel/eql_spec.rb10
-rw-r--r--spec/ruby/core/kernel/equal_value_spec.rb15
-rw-r--r--spec/ruby/core/kernel/eval_spec.rb552
-rw-r--r--spec/ruby/core/kernel/exec_spec.rb18
-rw-r--r--spec/ruby/core/kernel/exit_spec.rb27
-rw-r--r--spec/ruby/core/kernel/extend_spec.rb91
-rw-r--r--spec/ruby/core/kernel/fail_spec.rb42
-rw-r--r--spec/ruby/core/kernel/fixtures/Complex.rb5
-rw-r--r--spec/ruby/core/kernel/fixtures/__callee__.rb34
-rw-r--r--spec/ruby/core/kernel/fixtures/__dir__.rb2
-rw-r--r--spec/ruby/core/kernel/fixtures/__method__.rb34
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_b.rb5
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_d.rb5
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_from_included_module.rb9
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_from_included_module2.rb9
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_frozen.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_relative_b.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/autoload_relative_d.rb5
-rw-r--r--spec/ruby/core/kernel/fixtures/caller.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/caller_at_exit.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/caller_locations.rb7
-rw-r--r--spec/ruby/core/kernel/fixtures/chomp.rb4
-rw-r--r--spec/ruby/core/kernel/fixtures/chomp_f.rb4
-rw-r--r--spec/ruby/core/kernel/fixtures/chop.rb4
-rw-r--r--spec/ruby/core/kernel/fixtures/chop_f.rb4
-rw-r--r--spec/ruby/core/kernel/fixtures/classes.rb577
-rw-r--r--spec/ruby/core/kernel/fixtures/eval_locals.rb6
-rw-r--r--spec/ruby/core/kernel/fixtures/eval_return_with_lambda.rb12
-rw-r--r--spec/ruby/core/kernel/fixtures/eval_return_without_lambda.rb14
-rw-r--r--spec/ruby/core/kernel/fixtures/singleton_methods.rb13
-rw-r--r--spec/ruby/core/kernel/fixtures/test.rb362
-rw-r--r--spec/ruby/core/kernel/fixtures/warn_core_method.rb14
-rw-r--r--spec/ruby/core/kernel/fixtures/warn_require.rb1
-rw-r--r--spec/ruby/core/kernel/fixtures/warn_require_caller.rb2
-rw-r--r--spec/ruby/core/kernel/fork_spec.rb15
-rw-r--r--spec/ruby/core/kernel/format_spec.rb47
-rw-r--r--spec/ruby/core/kernel/freeze_spec.rb91
-rw-r--r--spec/ruby/core/kernel/frozen_spec.rb76
-rw-r--r--spec/ruby/core/kernel/gets_spec.rb17
-rw-r--r--spec/ruby/core/kernel/global_variables_spec.rb26
-rw-r--r--spec/ruby/core/kernel/gsub_spec.rb96
-rw-r--r--spec/ruby/core/kernel/initialize_clone_spec.rb26
-rw-r--r--spec/ruby/core/kernel/initialize_copy_spec.rb36
-rw-r--r--spec/ruby/core/kernel/initialize_dup_spec.rb20
-rw-r--r--spec/ruby/core/kernel/inspect_spec.rb103
-rw-r--r--spec/ruby/core/kernel/instance_of_spec.rb40
-rw-r--r--spec/ruby/core/kernel/instance_variable_defined_spec.rb41
-rw-r--r--spec/ruby/core/kernel/instance_variable_get_spec.rb111
-rw-r--r--spec/ruby/core/kernel/instance_variable_set_spec.rb105
-rw-r--r--spec/ruby/core/kernel/instance_variables_spec.rb40
-rw-r--r--spec/ruby/core/kernel/is_a_spec.rb6
-rw-r--r--spec/ruby/core/kernel/itself_spec.rb9
-rw-r--r--spec/ruby/core/kernel/kind_of_spec.rb6
-rw-r--r--spec/ruby/core/kernel/lambda_spec.rb110
-rw-r--r--spec/ruby/core/kernel/load_spec.rb40
-rw-r--r--spec/ruby/core/kernel/local_variables_spec.rb45
-rw-r--r--spec/ruby/core/kernel/loop_spec.rb79
-rw-r--r--spec/ruby/core/kernel/match_spec.rb7
-rw-r--r--spec/ruby/core/kernel/method_spec.rb88
-rw-r--r--spec/ruby/core/kernel/methods_spec.rb101
-rw-r--r--spec/ruby/core/kernel/nil_spec.rb12
-rw-r--r--spec/ruby/core/kernel/not_match_spec.rb25
-rw-r--r--spec/ruby/core/kernel/object_id_spec.rb6
-rw-r--r--spec/ruby/core/kernel/open_spec.rb192
-rw-r--r--spec/ruby/core/kernel/p_spec.rb85
-rw-r--r--spec/ruby/core/kernel/pp_spec.rb9
-rw-r--r--spec/ruby/core/kernel/print_spec.rb24
-rw-r--r--spec/ruby/core/kernel/printf_spec.rb70
-rw-r--r--spec/ruby/core/kernel/private_methods_spec.rb69
-rw-r--r--spec/ruby/core/kernel/proc_spec.rb48
-rw-r--r--spec/ruby/core/kernel/protected_methods_spec.rb69
-rw-r--r--spec/ruby/core/kernel/public_method_spec.rb32
-rw-r--r--spec/ruby/core/kernel/public_methods_spec.rb75
-rw-r--r--spec/ruby/core/kernel/public_send_spec.rb116
-rw-r--r--spec/ruby/core/kernel/putc_spec.rb39
-rw-r--r--spec/ruby/core/kernel/puts_spec.rb29
-rw-r--r--spec/ruby/core/kernel/raise_spec.rb222
-rw-r--r--spec/ruby/core/kernel/rand_spec.rb197
-rw-r--r--spec/ruby/core/kernel/readline_spec.rb12
-rw-r--r--spec/ruby/core/kernel/readlines_spec.rb12
-rw-r--r--spec/ruby/core/kernel/remove_instance_variable_spec.rb72
-rw-r--r--spec/ruby/core/kernel/require_relative_spec.rb437
-rw-r--r--spec/ruby/core/kernel/require_spec.rb67
-rw-r--r--spec/ruby/core/kernel/respond_to_missing_spec.rb100
-rw-r--r--spec/ruby/core/kernel/respond_to_spec.rb99
-rw-r--r--spec/ruby/core/kernel/select_spec.rb18
-rw-r--r--spec/ruby/core/kernel/send_spec.rb68
-rw-r--r--spec/ruby/core/kernel/set_trace_func_spec.rb12
-rw-r--r--spec/ruby/core/kernel/shared/dup_clone.rb91
-rw-r--r--spec/ruby/core/kernel/shared/kind_of.rb55
-rw-r--r--spec/ruby/core/kernel/shared/lambda.rb11
-rw-r--r--spec/ruby/core/kernel/shared/load.rb215
-rw-r--r--spec/ruby/core/kernel/shared/method.rb56
-rw-r--r--spec/ruby/core/kernel/shared/require.rb846
-rw-r--r--spec/ruby/core/kernel/shared/sprintf.rb1034
-rw-r--r--spec/ruby/core/kernel/shared/sprintf_encoding.rb67
-rw-r--r--spec/ruby/core/kernel/shared/then.rb20
-rw-r--r--spec/ruby/core/kernel/singleton_class_spec.rb74
-rw-r--r--spec/ruby/core/kernel/singleton_method_spec.rb85
-rw-r--r--spec/ruby/core/kernel/singleton_methods_spec.rb199
-rw-r--r--spec/ruby/core/kernel/sleep_spec.rb118
-rw-r--r--spec/ruby/core/kernel/spawn_spec.rb25
-rw-r--r--spec/ruby/core/kernel/sprintf_spec.rb64
-rw-r--r--spec/ruby/core/kernel/srand_spec.rb73
-rw-r--r--spec/ruby/core/kernel/sub_spec.rb26
-rw-r--r--spec/ruby/core/kernel/syscall_spec.rb12
-rw-r--r--spec/ruby/core/kernel/system_spec.rb132
-rw-r--r--spec/ruby/core/kernel/taint_spec.rb8
-rw-r--r--spec/ruby/core/kernel/tainted_spec.rb8
-rw-r--r--spec/ruby/core/kernel/tap_spec.rb13
-rw-r--r--spec/ruby/core/kernel/test_spec.rb109
-rw-r--r--spec/ruby/core/kernel/then_spec.rb6
-rw-r--r--spec/ruby/core/kernel/throw_spec.rb80
-rw-r--r--spec/ruby/core/kernel/to_enum_spec.rb5
-rw-r--r--spec/ruby/core/kernel/to_s_spec.rb8
-rw-r--r--spec/ruby/core/kernel/trace_var_spec.rb54
-rw-r--r--spec/ruby/core/kernel/trap_spec.rb9
-rw-r--r--spec/ruby/core/kernel/trust_spec.rb8
-rw-r--r--spec/ruby/core/kernel/untaint_spec.rb8
-rw-r--r--spec/ruby/core/kernel/untrace_var_spec.rb12
-rw-r--r--spec/ruby/core/kernel/untrust_spec.rb8
-rw-r--r--spec/ruby/core/kernel/untrusted_spec.rb8
-rw-r--r--spec/ruby/core/kernel/warn_spec.rb298
-rw-r--r--spec/ruby/core/kernel/yield_self_spec.rb6
-rw-r--r--spec/ruby/core/main/define_method_spec.rb28
-rw-r--r--spec/ruby/core/main/fixtures/classes.rb26
-rw-r--r--spec/ruby/core/main/fixtures/string_refinement.rb7
-rw-r--r--spec/ruby/core/main/fixtures/string_refinement_user.rb11
-rw-r--r--spec/ruby/core/main/fixtures/using.rb1
-rw-r--r--spec/ruby/core/main/fixtures/using_in_main.rb5
-rw-r--r--spec/ruby/core/main/fixtures/using_in_method.rb5
-rw-r--r--spec/ruby/core/main/fixtures/wrapped_include.rb1
-rw-r--r--spec/ruby/core/main/include_spec.rb16
-rw-r--r--spec/ruby/core/main/private_spec.rb42
-rw-r--r--spec/ruby/core/main/public_spec.rb43
-rw-r--r--spec/ruby/core/main/ruby2_keywords_spec.rb9
-rw-r--r--spec/ruby/core/main/to_s_spec.rb7
-rw-r--r--spec/ruby/core/main/using_spec.rb150
-rw-r--r--spec/ruby/core/marshal/dump_spec.rb1082
-rw-r--r--spec/ruby/core/marshal/fixtures/classes.rb4
-rw-r--r--spec/ruby/core/marshal/fixtures/marshal_data.rb567
-rw-r--r--spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb12
-rw-r--r--spec/ruby/core/marshal/fixtures/random.dumpbin0 -> 2520 bytes-rw-r--r--spec/ruby/core/marshal/float_spec.rb77
-rw-r--r--spec/ruby/core/marshal/load_spec.rb6
-rw-r--r--spec/ruby/core/marshal/major_version_spec.rb7
-rw-r--r--spec/ruby/core/marshal/minor_version_spec.rb7
-rw-r--r--spec/ruby/core/marshal/restore_spec.rb6
-rw-r--r--spec/ruby/core/marshal/shared/load.rb1291
-rw-r--r--spec/ruby/core/matchdata/allocate_spec.rb8
-rw-r--r--spec/ruby/core/matchdata/begin_spec.rb132
-rw-r--r--spec/ruby/core/matchdata/bytebegin_spec.rb132
-rw-r--r--spec/ruby/core/matchdata/byteend_spec.rb104
-rw-r--r--spec/ruby/core/matchdata/byteoffset_spec.rb93
-rw-r--r--spec/ruby/core/matchdata/captures_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/deconstruct_keys_spec.rb78
-rw-r--r--spec/ruby/core/matchdata/deconstruct_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/dup_spec.rb14
-rw-r--r--spec/ruby/core/matchdata/element_reference_spec.rb124
-rw-r--r--spec/ruby/core/matchdata/end_spec.rb104
-rw-r--r--spec/ruby/core/matchdata/eql_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/fixtures/classes.rb3
-rw-r--r--spec/ruby/core/matchdata/hash_spec.rb5
-rw-r--r--spec/ruby/core/matchdata/inspect_spec.rb23
-rw-r--r--spec/ruby/core/matchdata/integer_at_spec.rb38
-rw-r--r--spec/ruby/core/matchdata/length_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/match_length_spec.rb32
-rw-r--r--spec/ruby/core/matchdata/match_spec.rb32
-rw-r--r--spec/ruby/core/matchdata/named_captures_spec.rb25
-rw-r--r--spec/ruby/core/matchdata/names_spec.rb33
-rw-r--r--spec/ruby/core/matchdata/offset_spec.rb102
-rw-r--r--spec/ruby/core/matchdata/post_match_spec.rb24
-rw-r--r--spec/ruby/core/matchdata/pre_match_spec.rb24
-rw-r--r--spec/ruby/core/matchdata/regexp_spec.rb24
-rw-r--r--spec/ruby/core/matchdata/shared/captures.rb13
-rw-r--r--spec/ruby/core/matchdata/shared/eql.rb26
-rw-r--r--spec/ruby/core/matchdata/shared/length.rb5
-rw-r--r--spec/ruby/core/matchdata/size_spec.rb6
-rw-r--r--spec/ruby/core/matchdata/string_spec.rb26
-rw-r--r--spec/ruby/core/matchdata/to_a_spec.rb13
-rw-r--r--spec/ruby/core/matchdata/to_s_spec.rb13
-rw-r--r--spec/ruby/core/matchdata/values_at_spec.rb76
-rw-r--r--spec/ruby/core/math/acos_spec.rb56
-rw-r--r--spec/ruby/core/math/acosh_spec.rb41
-rw-r--r--spec/ruby/core/math/asin_spec.rb48
-rw-r--r--spec/ruby/core/math/asinh_spec.rb42
-rw-r--r--spec/ruby/core/math/atan2_spec.rb54
-rw-r--r--spec/ruby/core/math/atan_spec.rb40
-rw-r--r--spec/ruby/core/math/atanh_spec.rb14
-rw-r--r--spec/ruby/core/math/cbrt_spec.rb27
-rw-r--r--spec/ruby/core/math/constants_spec.rb22
-rw-r--r--spec/ruby/core/math/cos_spec.rb50
-rw-r--r--spec/ruby/core/math/cosh_spec.rb37
-rw-r--r--spec/ruby/core/math/erf_spec.rb44
-rw-r--r--spec/ruby/core/math/erfc_spec.rb43
-rw-r--r--spec/ruby/core/math/exp_spec.rb37
-rw-r--r--spec/ruby/core/math/expm1_spec.rb37
-rw-r--r--spec/ruby/core/math/fixtures/classes.rb28
-rw-r--r--spec/ruby/core/math/fixtures/common.rb3
-rw-r--r--spec/ruby/core/math/frexp_spec.rb37
-rw-r--r--spec/ruby/core/math/gamma_spec.rb69
-rw-r--r--spec/ruby/core/math/hypot_spec.rb41
-rw-r--r--spec/ruby/core/math/ldexp_spec.rb60
-rw-r--r--spec/ruby/core/math/lgamma_spec.rb51
-rw-r--r--spec/ruby/core/math/log10_spec.rb47
-rw-r--r--spec/ruby/core/math/log1p_spec.rb49
-rw-r--r--spec/ruby/core/math/log2_spec.rb41
-rw-r--r--spec/ruby/core/math/log_spec.rb57
-rw-r--r--spec/ruby/core/math/shared/atanh.rb44
-rw-r--r--spec/ruby/core/math/sin_spec.rb39
-rw-r--r--spec/ruby/core/math/sinh_spec.rb37
-rw-r--r--spec/ruby/core/math/sqrt_spec.rb40
-rw-r--r--spec/ruby/core/math/tan_spec.rb42
-rw-r--r--spec/ruby/core/math/tanh_spec.rb39
-rw-r--r--spec/ruby/core/method/arity_spec.rb222
-rw-r--r--spec/ruby/core/method/call_spec.rb7
-rw-r--r--spec/ruby/core/method/case_compare_spec.rb7
-rw-r--r--spec/ruby/core/method/clone_spec.rb13
-rw-r--r--spec/ruby/core/method/compose_spec.rb99
-rw-r--r--spec/ruby/core/method/curry_spec.rb36
-rw-r--r--spec/ruby/core/method/dup_spec.rb15
-rw-r--r--spec/ruby/core/method/element_reference_spec.rb7
-rw-r--r--spec/ruby/core/method/eql_spec.rb6
-rw-r--r--spec/ruby/core/method/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/method/fixtures/classes.rb247
-rw-r--r--spec/ruby/core/method/hash_spec.rb15
-rw-r--r--spec/ruby/core/method/inspect_spec.rb8
-rw-r--r--spec/ruby/core/method/name_spec.rb22
-rw-r--r--spec/ruby/core/method/original_name_spec.rb59
-rw-r--r--spec/ruby/core/method/owner_spec.rb30
-rw-r--r--spec/ruby/core/method/parameters_spec.rb317
-rw-r--r--spec/ruby/core/method/private_spec.rb9
-rw-r--r--spec/ruby/core/method/protected_spec.rb9
-rw-r--r--spec/ruby/core/method/public_spec.rb9
-rw-r--r--spec/ruby/core/method/receiver_spec.rb22
-rw-r--r--spec/ruby/core/method/shared/aliased_inspect.rb31
-rw-r--r--spec/ruby/core/method/shared/call.rb51
-rw-r--r--spec/ruby/core/method/shared/dup.rb32
-rw-r--r--spec/ruby/core/method/shared/eql.rb94
-rw-r--r--spec/ruby/core/method/shared/to_s.rb81
-rw-r--r--spec/ruby/core/method/source_location_spec.rb120
-rw-r--r--spec/ruby/core/method/super_method_spec.rb64
-rw-r--r--spec/ruby/core/method/to_proc_spec.rb104
-rw-r--r--spec/ruby/core/method/to_s_spec.rb8
-rw-r--r--spec/ruby/core/method/unbind_spec.rb46
-rw-r--r--spec/ruby/core/module/alias_method_spec.rb171
-rw-r--r--spec/ruby/core/module/ancestors_spec.rb88
-rw-r--r--spec/ruby/core/module/append_features_spec.rb61
-rw-r--r--spec/ruby/core/module/attr_accessor_spec.rb112
-rw-r--r--spec/ruby/core/module/attr_reader_spec.rb73
-rw-r--r--spec/ruby/core/module/attr_spec.rb159
-rw-r--r--spec/ruby/core/module/attr_writer_spec.rb83
-rw-r--r--spec/ruby/core/module/autoload_relative_spec.rb128
-rw-r--r--spec/ruby/core/module/autoload_spec.rb1028
-rw-r--r--spec/ruby/core/module/case_compare_spec.rb31
-rw-r--r--spec/ruby/core/module/class_eval_spec.rb7
-rw-r--r--spec/ruby/core/module/class_exec_spec.rb7
-rw-r--r--spec/ruby/core/module/class_variable_defined_spec.rb72
-rw-r--r--spec/ruby/core/module/class_variable_get_spec.rb76
-rw-r--r--spec/ruby/core/module/class_variable_set_spec.rb62
-rw-r--r--spec/ruby/core/module/class_variables_spec.rb34
-rw-r--r--spec/ruby/core/module/comparison_spec.rb36
-rw-r--r--spec/ruby/core/module/const_added_spec.rb238
-rw-r--r--spec/ruby/core/module/const_defined_spec.rb169
-rw-r--r--spec/ruby/core/module/const_get_spec.rb273
-rw-r--r--spec/ruby/core/module/const_missing_spec.rb36
-rw-r--r--spec/ruby/core/module/const_set_spec.rb145
-rw-r--r--spec/ruby/core/module/const_source_location_spec.rb281
-rw-r--r--spec/ruby/core/module/constants_spec.rb98
-rw-r--r--spec/ruby/core/module/define_method_spec.rb846
-rw-r--r--spec/ruby/core/module/define_singleton_method_spec.rb15
-rw-r--r--spec/ruby/core/module/deprecate_constant_spec.rb70
-rw-r--r--spec/ruby/core/module/eql_spec.rb7
-rw-r--r--spec/ruby/core/module/equal_spec.rb7
-rw-r--r--spec/ruby/core/module/equal_value_spec.rb7
-rw-r--r--spec/ruby/core/module/extend_object_spec.rb56
-rw-r--r--spec/ruby/core/module/extended_spec.rb44
-rw-r--r--spec/ruby/core/module/fixtures/autoload.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_abc.rb11
-rw-r--r--spec/ruby/core/module/fixtures/autoload_c.rb11
-rw-r--r--spec/ruby/core/module/fixtures/autoload_callback.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_concur.rb9
-rw-r--r--spec/ruby/core/module/fixtures/autoload_const_source_location.rb6
-rw-r--r--spec/ruby/core/module/fixtures/autoload_d.rb11
-rw-r--r--spec/ruby/core/module/fixtures/autoload_during_autoload.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_during_autoload_after_define.rb6
-rw-r--r--spec/ruby/core/module/fixtures/autoload_during_require.rb4
-rw-r--r--spec/ruby/core/module/fixtures/autoload_during_require_current_file.rb5
-rw-r--r--spec/ruby/core/module/fixtures/autoload_e.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_empty.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_ex1.rb16
-rw-r--r--spec/ruby/core/module/fixtures/autoload_exception.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_f.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_g.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_h.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_i.rb5
-rw-r--r--spec/ruby/core/module/fixtures/autoload_j.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_k.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_lm.rb4
-rw-r--r--spec/ruby/core/module/fixtures/autoload_location.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_nested.rb8
-rw-r--r--spec/ruby/core/module/fixtures/autoload_never_set.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_o.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_overridden.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_r.rb4
-rw-r--r--spec/ruby/core/module/fixtures/autoload_raise.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_relative_a.rb9
-rw-r--r--spec/ruby/core/module/fixtures/autoload_required_directly.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_required_directly_nested.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_required_directly_no_constant.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_s.rb5
-rw-r--r--spec/ruby/core/module/fixtures/autoload_self_during_require.rb5
-rw-r--r--spec/ruby/core/module/fixtures/autoload_subclass.rb11
-rw-r--r--spec/ruby/core/module/fixtures/autoload_t.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_v.rb7
-rw-r--r--spec/ruby/core/module/fixtures/autoload_w.rb2
-rw-r--r--spec/ruby/core/module/fixtures/autoload_w2.rb1
-rw-r--r--spec/ruby/core/module/fixtures/autoload_x.rb3
-rw-r--r--spec/ruby/core/module/fixtures/autoload_z.rb5
-rw-r--r--spec/ruby/core/module/fixtures/classes.rb653
-rw-r--r--spec/ruby/core/module/fixtures/const_added.rb4
-rw-r--r--spec/ruby/core/module/fixtures/constant_unicode.rb5
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload.rb6
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload_a.rb2
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload_b.rb2
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload_c.rb3
-rw-r--r--spec/ruby/core/module/fixtures/constants_autoload_d.rb4
-rw-r--r--spec/ruby/core/module/fixtures/module.rb8
-rw-r--r--spec/ruby/core/module/fixtures/multi/foo.rb6
-rw-r--r--spec/ruby/core/module/fixtures/multi/foo/bar_baz.rb11
-rw-r--r--spec/ruby/core/module/fixtures/name.rb13
-rw-r--r--spec/ruby/core/module/fixtures/path1/load_path.rb9
-rw-r--r--spec/ruby/core/module/fixtures/path2/load_path.rb1
-rw-r--r--spec/ruby/core/module/fixtures/refine.rb25
-rw-r--r--spec/ruby/core/module/fixtures/repeated_concurrent_autoload.rb8
-rw-r--r--spec/ruby/core/module/fixtures/set_temporary_name.rb4
-rw-r--r--spec/ruby/core/module/freeze_spec.rb6
-rw-r--r--spec/ruby/core/module/gt_spec.rb36
-rw-r--r--spec/ruby/core/module/gte_spec.rb33
-rw-r--r--spec/ruby/core/module/include_spec.rb628
-rw-r--r--spec/ruby/core/module/included_modules_spec.rb14
-rw-r--r--spec/ruby/core/module/included_spec.rb44
-rw-r--r--spec/ruby/core/module/initialize_copy_spec.rb18
-rw-r--r--spec/ruby/core/module/initialize_spec.rb18
-rw-r--r--spec/ruby/core/module/instance_method_spec.rb106
-rw-r--r--spec/ruby/core/module/instance_methods_spec.rb63
-rw-r--r--spec/ruby/core/module/lt_spec.rb36
-rw-r--r--spec/ruby/core/module/lte_spec.rb33
-rw-r--r--spec/ruby/core/module/method_added_spec.rb146
-rw-r--r--spec/ruby/core/module/method_defined_spec.rb98
-rw-r--r--spec/ruby/core/module/method_removed_spec.rb33
-rw-r--r--spec/ruby/core/module/method_undefined_spec.rb33
-rw-r--r--spec/ruby/core/module/module_eval_spec.rb7
-rw-r--r--spec/ruby/core/module/module_exec_spec.rb7
-rw-r--r--spec/ruby/core/module/module_function_spec.rb358
-rw-r--r--spec/ruby/core/module/name_spec.rb205
-rw-r--r--spec/ruby/core/module/nesting_spec.rb31
-rw-r--r--spec/ruby/core/module/new_spec.rb35
-rw-r--r--spec/ruby/core/module/prepend_features_spec.rb64
-rw-r--r--spec/ruby/core/module/prepend_spec.rb823
-rw-r--r--spec/ruby/core/module/prepended_spec.rb25
-rw-r--r--spec/ruby/core/module/private_class_method_spec.rb91
-rw-r--r--spec/ruby/core/module/private_constant_spec.rb32
-rw-r--r--spec/ruby/core/module/private_instance_methods_spec.rb54
-rw-r--r--spec/ruby/core/module/private_method_defined_spec.rb120
-rw-r--r--spec/ruby/core/module/private_spec.rb95
-rw-r--r--spec/ruby/core/module/protected_instance_methods_spec.rb57
-rw-r--r--spec/ruby/core/module/protected_method_defined_spec.rb120
-rw-r--r--spec/ruby/core/module/protected_spec.rb57
-rw-r--r--spec/ruby/core/module/public_class_method_spec.rb94
-rw-r--r--spec/ruby/core/module/public_constant_spec.rb38
-rw-r--r--spec/ruby/core/module/public_instance_method_spec.rb65
-rw-r--r--spec/ruby/core/module/public_instance_methods_spec.rb61
-rw-r--r--spec/ruby/core/module/public_method_defined_spec.rb72
-rw-r--r--spec/ruby/core/module/public_spec.rb45
-rw-r--r--spec/ruby/core/module/refine_spec.rb713
-rw-r--r--spec/ruby/core/module/refinements_spec.rb43
-rw-r--r--spec/ruby/core/module/remove_class_variable_spec.rb44
-rw-r--r--spec/ruby/core/module/remove_const_spec.rb107
-rw-r--r--spec/ruby/core/module/remove_method_spec.rb131
-rw-r--r--spec/ruby/core/module/ruby2_keywords_spec.rb248
-rw-r--r--spec/ruby/core/module/set_temporary_name_spec.rb145
-rw-r--r--spec/ruby/core/module/shared/attr_added.rb34
-rw-r--r--spec/ruby/core/module/shared/class_eval.rb172
-rw-r--r--spec/ruby/core/module/shared/class_exec.rb35
-rw-r--r--spec/ruby/core/module/shared/equal_value.rb14
-rw-r--r--spec/ruby/core/module/shared/set_visibility.rb184
-rw-r--r--spec/ruby/core/module/singleton_class_spec.rb27
-rw-r--r--spec/ruby/core/module/to_s_spec.rb70
-rw-r--r--spec/ruby/core/module/undef_method_spec.rb181
-rw-r--r--spec/ruby/core/module/undefined_instance_methods_spec.rb25
-rw-r--r--spec/ruby/core/module/used_refinements_spec.rb85
-rw-r--r--spec/ruby/core/module/using_spec.rb377
-rw-r--r--spec/ruby/core/mutex/lock_spec.rb92
-rw-r--r--spec/ruby/core/mutex/locked_spec.rb36
-rw-r--r--spec/ruby/core/mutex/owned_spec.rb53
-rw-r--r--spec/ruby/core/mutex/sleep_spec.rb111
-rw-r--r--spec/ruby/core/mutex/synchronize_spec.rb66
-rw-r--r--spec/ruby/core/mutex/try_lock_spec.rb32
-rw-r--r--spec/ruby/core/mutex/unlock_spec.rb38
-rw-r--r--spec/ruby/core/nil/and_spec.rb11
-rw-r--r--spec/ruby/core/nil/case_compare_spec.rb13
-rw-r--r--spec/ruby/core/nil/dup_spec.rb7
-rw-r--r--spec/ruby/core/nil/inspect_spec.rb7
-rw-r--r--spec/ruby/core/nil/match_spec.rb21
-rw-r--r--spec/ruby/core/nil/nil_spec.rb7
-rw-r--r--spec/ruby/core/nil/nilclass_spec.rb15
-rw-r--r--spec/ruby/core/nil/or_spec.rb11
-rw-r--r--spec/ruby/core/nil/rationalize_spec.rb16
-rw-r--r--spec/ruby/core/nil/singleton_method_spec.rb13
-rw-r--r--spec/ruby/core/nil/to_a_spec.rb7
-rw-r--r--spec/ruby/core/nil/to_c_spec.rb7
-rw-r--r--spec/ruby/core/nil/to_f_spec.rb11
-rw-r--r--spec/ruby/core/nil/to_h_spec.rb8
-rw-r--r--spec/ruby/core/nil/to_i_spec.rb11
-rw-r--r--spec/ruby/core/nil/to_r_spec.rb7
-rw-r--r--spec/ruby/core/nil/to_s_spec.rb15
-rw-r--r--spec/ruby/core/nil/xor_spec.rb11
-rw-r--r--spec/ruby/core/numeric/abs2_spec.rb34
-rw-r--r--spec/ruby/core/numeric/abs_spec.rb6
-rw-r--r--spec/ruby/core/numeric/angle_spec.rb6
-rw-r--r--spec/ruby/core/numeric/arg_spec.rb6
-rw-r--r--spec/ruby/core/numeric/ceil_spec.rb15
-rw-r--r--spec/ruby/core/numeric/clone_spec.rb30
-rw-r--r--spec/ruby/core/numeric/coerce_spec.rb59
-rw-r--r--spec/ruby/core/numeric/comparison_spec.rb48
-rw-r--r--spec/ruby/core/numeric/conj_spec.rb6
-rw-r--r--spec/ruby/core/numeric/conjugate_spec.rb6
-rw-r--r--spec/ruby/core/numeric/denominator_spec.rb24
-rw-r--r--spec/ruby/core/numeric/div_spec.rb22
-rw-r--r--spec/ruby/core/numeric/divmod_spec.rb15
-rw-r--r--spec/ruby/core/numeric/dup_spec.rb16
-rw-r--r--spec/ruby/core/numeric/eql_spec.rb22
-rw-r--r--spec/ruby/core/numeric/fdiv_spec.rb31
-rw-r--r--spec/ruby/core/numeric/finite_spec.rb8
-rw-r--r--spec/ruby/core/numeric/fixtures/classes.rb17
-rw-r--r--spec/ruby/core/numeric/floor_spec.rb14
-rw-r--r--spec/ruby/core/numeric/i_spec.rb15
-rw-r--r--spec/ruby/core/numeric/imag_spec.rb6
-rw-r--r--spec/ruby/core/numeric/imaginary_spec.rb6
-rw-r--r--spec/ruby/core/numeric/infinite_spec.rb8
-rw-r--r--spec/ruby/core/numeric/integer_spec.rb8
-rw-r--r--spec/ruby/core/numeric/magnitude_spec.rb6
-rw-r--r--spec/ruby/core/numeric/modulo_spec.rb24
-rw-r--r--spec/ruby/core/numeric/negative_spec.rb41
-rw-r--r--spec/ruby/core/numeric/nonzero_spec.rb18
-rw-r--r--spec/ruby/core/numeric/numerator_spec.rb33
-rw-r--r--spec/ruby/core/numeric/numeric_spec.rb7
-rw-r--r--spec/ruby/core/numeric/phase_spec.rb6
-rw-r--r--spec/ruby/core/numeric/polar_spec.rb50
-rw-r--r--spec/ruby/core/numeric/positive_spec.rb41
-rw-r--r--spec/ruby/core/numeric/quo_spec.rb63
-rw-r--r--spec/ruby/core/numeric/real_spec.rb37
-rw-r--r--spec/ruby/core/numeric/rect_spec.rb6
-rw-r--r--spec/ruby/core/numeric/rectangular_spec.rb6
-rw-r--r--spec/ruby/core/numeric/remainder_spec.rb68
-rw-r--r--spec/ruby/core/numeric/round_spec.rb14
-rw-r--r--spec/ruby/core/numeric/shared/abs.rb19
-rw-r--r--spec/ruby/core/numeric/shared/arg.rb38
-rw-r--r--spec/ruby/core/numeric/shared/conj.rb20
-rw-r--r--spec/ruby/core/numeric/shared/imag.rb26
-rw-r--r--spec/ruby/core/numeric/shared/rect.rb48
-rw-r--r--spec/ruby/core/numeric/shared/step.rb410
-rw-r--r--spec/ruby/core/numeric/singleton_method_added_spec.rb41
-rw-r--r--spec/ruby/core/numeric/step_spec.rb121
-rw-r--r--spec/ruby/core/numeric/to_c_spec.rb45
-rw-r--r--spec/ruby/core/numeric/to_int_spec.rb10
-rw-r--r--spec/ruby/core/numeric/truncate_spec.rb14
-rw-r--r--spec/ruby/core/numeric/uminus_spec.rb31
-rw-r--r--spec/ruby/core/numeric/uplus_spec.rb9
-rw-r--r--spec/ruby/core/numeric/zero_spec.rb18
-rw-r--r--spec/ruby/core/objectspace/_id2ref_spec.rb65
-rw-r--r--spec/ruby/core/objectspace/count_objects_spec.rb5
-rw-r--r--spec/ruby/core/objectspace/define_finalizer_spec.rb217
-rw-r--r--spec/ruby/core/objectspace/each_object_spec.rb213
-rw-r--r--spec/ruby/core/objectspace/fixtures/classes.rb64
-rw-r--r--spec/ruby/core/objectspace/garbage_collect_spec.rb22
-rw-r--r--spec/ruby/core/objectspace/undefine_finalizer_spec.rb33
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/clear_spec.rb25
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/delete_spec.rb49
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/element_reference_spec.rb105
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/element_set_spec.rb80
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/fixtures/classes.rb5
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/getkey_spec.rb26
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/inspect_spec.rb19
-rw-r--r--spec/ruby/core/objectspace/weakkeymap/key_spec.rb42
-rw-r--r--spec/ruby/core/objectspace/weakmap/delete_spec.rb28
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_key_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_pair_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/each_value_spec.rb11
-rw-r--r--spec/ruby/core/objectspace/weakmap/element_reference_spec.rb24
-rw-r--r--spec/ruby/core/objectspace/weakmap/element_set_spec.rb38
-rw-r--r--spec/ruby/core/objectspace/weakmap/include_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/inspect_spec.rb25
-rw-r--r--spec/ruby/core/objectspace/weakmap/key_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/keys_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/length_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/member_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/each.rb10
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/include.rb30
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/members.rb14
-rw-r--r--spec/ruby/core/objectspace/weakmap/shared/size.rb14
-rw-r--r--spec/ruby/core/objectspace/weakmap/size_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap/values_spec.rb6
-rw-r--r--spec/ruby/core/objectspace/weakmap_spec.rb12
-rw-r--r--spec/ruby/core/proc/allocate_spec.rb9
-rw-r--r--spec/ruby/core/proc/arity_spec.rb656
-rw-r--r--spec/ruby/core/proc/binding_spec.rb21
-rw-r--r--spec/ruby/core/proc/block_pass_spec.rb21
-rw-r--r--spec/ruby/core/proc/call_spec.rb16
-rw-r--r--spec/ruby/core/proc/case_compare_spec.rb16
-rw-r--r--spec/ruby/core/proc/clone_spec.rb28
-rw-r--r--spec/ruby/core/proc/compose_spec.rb142
-rw-r--r--spec/ruby/core/proc/curry_spec.rb179
-rw-r--r--spec/ruby/core/proc/dup_spec.rb26
-rw-r--r--spec/ruby/core/proc/element_reference_spec.rb27
-rw-r--r--spec/ruby/core/proc/eql_spec.rb6
-rw-r--r--spec/ruby/core/proc/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/proc/fixtures/common.rb72
-rw-r--r--spec/ruby/core/proc/fixtures/proc_aref.rb10
-rw-r--r--spec/ruby/core/proc/fixtures/proc_aref_frozen.rb10
-rw-r--r--spec/ruby/core/proc/fixtures/source_location.rb55
-rw-r--r--spec/ruby/core/proc/hash_spec.rb17
-rw-r--r--spec/ruby/core/proc/inspect_spec.rb6
-rw-r--r--spec/ruby/core/proc/lambda_spec.rb55
-rw-r--r--spec/ruby/core/proc/new_spec.rb178
-rw-r--r--spec/ruby/core/proc/parameters_spec.rb183
-rw-r--r--spec/ruby/core/proc/ruby2_keywords_spec.rb66
-rw-r--r--spec/ruby/core/proc/shared/call.rb99
-rw-r--r--spec/ruby/core/proc/shared/call_arguments.rb29
-rw-r--r--spec/ruby/core/proc/shared/compose.rb22
-rw-r--r--spec/ruby/core/proc/shared/dup.rb39
-rw-r--r--spec/ruby/core/proc/shared/equal.rb83
-rw-r--r--spec/ruby/core/proc/shared/to_s.rb60
-rw-r--r--spec/ruby/core/proc/source_location_spec.rb91
-rw-r--r--spec/ruby/core/proc/to_proc_spec.rb9
-rw-r--r--spec/ruby/core/proc/to_s_spec.rb6
-rw-r--r--spec/ruby/core/proc/yield_spec.rb16
-rw-r--r--spec/ruby/core/process/_fork_spec.rb24
-rw-r--r--spec/ruby/core/process/abort_spec.rb6
-rw-r--r--spec/ruby/core/process/argv0_spec.rb23
-rw-r--r--spec/ruby/core/process/clock_getres_spec.rb33
-rw-r--r--spec/ruby/core/process/clock_gettime_spec.rb152
-rw-r--r--spec/ruby/core/process/constants_spec.rb126
-rw-r--r--spec/ruby/core/process/daemon_spec.rb118
-rw-r--r--spec/ruby/core/process/detach_spec.rb81
-rw-r--r--spec/ruby/core/process/egid_spec.rb58
-rw-r--r--spec/ruby/core/process/euid_spec.rb56
-rw-r--r--spec/ruby/core/process/exec_spec.rb241
-rw-r--r--spec/ruby/core/process/exit_spec.rb10
-rw-r--r--spec/ruby/core/process/fixtures/argv0.rb6
-rw-r--r--spec/ruby/core/process/fixtures/clocks.rb18
-rw-r--r--spec/ruby/core/process/fixtures/common.rb88
-rw-r--r--spec/ruby/core/process/fixtures/daemon.rb111
-rw-r--r--spec/ruby/core/process/fixtures/in.txt1
-rw-r--r--spec/ruby/core/process/fixtures/kill.rb43
-rw-r--r--spec/ruby/core/process/fixtures/map_fd.rb9
-rw-r--r--spec/ruby/core/process/fixtures/setpriority.rb12
-rw-r--r--spec/ruby/core/process/fork_spec.rb6
-rw-r--r--spec/ruby/core/process/getpgid_spec.rb17
-rw-r--r--spec/ruby/core/process/getpgrp_spec.rb7
-rw-r--r--spec/ruby/core/process/getpriority_spec.rb23
-rw-r--r--spec/ruby/core/process/getrlimit_spec.rb100
-rw-r--r--spec/ruby/core/process/gid/change_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/eid_spec.rb9
-rw-r--r--spec/ruby/core/process/gid/grant_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/re_exchange_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/re_exchangeable_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/rid_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/sid_available_spec.rb5
-rw-r--r--spec/ruby/core/process/gid/switch_spec.rb5
-rw-r--r--spec/ruby/core/process/gid_spec.rb22
-rw-r--r--spec/ruby/core/process/groups_spec.rb67
-rw-r--r--spec/ruby/core/process/initgroups_spec.rb22
-rw-r--r--spec/ruby/core/process/kill_spec.rb132
-rw-r--r--spec/ruby/core/process/last_status_spec.rb18
-rw-r--r--spec/ruby/core/process/maxgroups_spec.rb19
-rw-r--r--spec/ruby/core/process/pid_spec.rb9
-rw-r--r--spec/ruby/core/process/ppid_spec.rb9
-rw-r--r--spec/ruby/core/process/set_proctitle_spec.rb23
-rw-r--r--spec/ruby/core/process/setpgid_spec.rb29
-rw-r--r--spec/ruby/core/process/setpgrp_spec.rb37
-rw-r--r--spec/ruby/core/process/setpriority_spec.rb60
-rw-r--r--spec/ruby/core/process/setrlimit_spec.rb237
-rw-r--r--spec/ruby/core/process/setsid_spec.rb16
-rw-r--r--spec/ruby/core/process/spawn_spec.rb796
-rw-r--r--spec/ruby/core/process/status/bit_and_spec.rb38
-rw-r--r--spec/ruby/core/process/status/coredump_spec.rb5
-rw-r--r--spec/ruby/core/process/status/equal_value_spec.rb15
-rw-r--r--spec/ruby/core/process/status/exited_spec.rb32
-rw-r--r--spec/ruby/core/process/status/exitstatus_spec.rb25
-rw-r--r--spec/ruby/core/process/status/inspect_spec.rb5
-rw-r--r--spec/ruby/core/process/status/pid_spec.rb15
-rw-r--r--spec/ruby/core/process/status/right_shift_spec.rb37
-rw-r--r--spec/ruby/core/process/status/signaled_spec.rb31
-rw-r--r--spec/ruby/core/process/status/stopped_spec.rb5
-rw-r--r--spec/ruby/core/process/status/stopsig_spec.rb5
-rw-r--r--spec/ruby/core/process/status/success_spec.rb41
-rw-r--r--spec/ruby/core/process/status/termsig_spec.rb43
-rw-r--r--spec/ruby/core/process/status/to_i_spec.rb13
-rw-r--r--spec/ruby/core/process/status/to_int_spec.rb5
-rw-r--r--spec/ruby/core/process/status/to_s_spec.rb5
-rw-r--r--spec/ruby/core/process/status/wait_spec.rb100
-rw-r--r--spec/ruby/core/process/sys/getegid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/geteuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/getgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/getuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/issetugid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setegid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/seteuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setregid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setresgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setresuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setreuid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setrgid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setruid_spec.rb5
-rw-r--r--spec/ruby/core/process/sys/setuid_spec.rb5
-rw-r--r--spec/ruby/core/process/times_spec.rb19
-rw-r--r--spec/ruby/core/process/tms/cstime_spec.rb17
-rw-r--r--spec/ruby/core/process/tms/cutime_spec.rb17
-rw-r--r--spec/ruby/core/process/tms/stime_spec.rb17
-rw-r--r--spec/ruby/core/process/tms/utime_spec.rb17
-rw-r--r--spec/ruby/core/process/uid/change_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/eid_spec.rb9
-rw-r--r--spec/ruby/core/process/uid/grant_privilege_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/re_exchange_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/re_exchangeable_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/rid_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/sid_available_spec.rb5
-rw-r--r--spec/ruby/core/process/uid/switch_spec.rb5
-rw-r--r--spec/ruby/core/process/uid_spec.rb57
-rw-r--r--spec/ruby/core/process/wait2_spec.rb45
-rw-r--r--spec/ruby/core/process/wait_spec.rb91
-rw-r--r--spec/ruby/core/process/waitall_spec.rb48
-rw-r--r--spec/ruby/core/process/waitpid2_spec.rb5
-rw-r--r--spec/ruby/core/process/waitpid_spec.rb14
-rw-r--r--spec/ruby/core/process/warmup_spec.rb9
-rw-r--r--spec/ruby/core/queue/append_spec.rb6
-rw-r--r--spec/ruby/core/queue/clear_spec.rb6
-rw-r--r--spec/ruby/core/queue/close_spec.rb6
-rw-r--r--spec/ruby/core/queue/closed_spec.rb6
-rw-r--r--spec/ruby/core/queue/deq_spec.rb11
-rw-r--r--spec/ruby/core/queue/empty_spec.rb6
-rw-r--r--spec/ruby/core/queue/enq_spec.rb6
-rw-r--r--spec/ruby/core/queue/freeze_spec.rb6
-rw-r--r--spec/ruby/core/queue/initialize_spec.rb60
-rw-r--r--spec/ruby/core/queue/length_spec.rb6
-rw-r--r--spec/ruby/core/queue/num_waiting_spec.rb6
-rw-r--r--spec/ruby/core/queue/pop_spec.rb11
-rw-r--r--spec/ruby/core/queue/push_spec.rb6
-rw-r--r--spec/ruby/core/queue/shift_spec.rb11
-rw-r--r--spec/ruby/core/queue/size_spec.rb6
-rw-r--r--spec/ruby/core/random/bytes_spec.rb29
-rw-r--r--spec/ruby/core/random/default_spec.rb7
-rw-r--r--spec/ruby/core/random/equal_value_spec.rb37
-rw-r--r--spec/ruby/core/random/fixtures/classes.rb15
-rw-r--r--spec/ruby/core/random/new_seed_spec.rb24
-rw-r--r--spec/ruby/core/random/new_spec.rb38
-rw-r--r--spec/ruby/core/random/rand_spec.rb224
-rw-r--r--spec/ruby/core/random/random_number_spec.rb8
-rw-r--r--spec/ruby/core/random/seed_spec.rb29
-rw-r--r--spec/ruby/core/random/shared/bytes.rb17
-rw-r--r--spec/ruby/core/random/shared/rand.rb9
-rw-r--r--spec/ruby/core/random/srand_spec.rb39
-rw-r--r--spec/ruby/core/random/urandom_spec.rb25
-rw-r--r--spec/ruby/core/range/begin_spec.rb6
-rw-r--r--spec/ruby/core/range/bsearch_spec.rb466
-rw-r--r--spec/ruby/core/range/case_compare_spec.rb17
-rw-r--r--spec/ruby/core/range/clone_spec.rb26
-rw-r--r--spec/ruby/core/range/count_spec.rb12
-rw-r--r--spec/ruby/core/range/cover_spec.rb14
-rw-r--r--spec/ruby/core/range/dup_spec.rb23
-rw-r--r--spec/ruby/core/range/each_spec.rb101
-rw-r--r--spec/ruby/core/range/end_spec.rb6
-rw-r--r--spec/ruby/core/range/eql_spec.rb10
-rw-r--r--spec/ruby/core/range/equal_value_spec.rb18
-rw-r--r--spec/ruby/core/range/exclude_end_spec.rb19
-rw-r--r--spec/ruby/core/range/first_spec.rb53
-rw-r--r--spec/ruby/core/range/fixtures/classes.rb90
-rw-r--r--spec/ruby/core/range/frozen_spec.rb25
-rw-r--r--spec/ruby/core/range/hash_spec.rb24
-rw-r--r--spec/ruby/core/range/include_spec.rb14
-rw-r--r--spec/ruby/core/range/initialize_spec.rb41
-rw-r--r--spec/ruby/core/range/inspect_spec.rb29
-rw-r--r--spec/ruby/core/range/last_spec.rb57
-rw-r--r--spec/ruby/core/range/max_spec.rb115
-rw-r--r--spec/ruby/core/range/member_spec.rb10
-rw-r--r--spec/ruby/core/range/min_spec.rb88
-rw-r--r--spec/ruby/core/range/minmax_spec.rb130
-rw-r--r--spec/ruby/core/range/new_spec.rb77
-rw-r--r--spec/ruby/core/range/overlap_spec.rb87
-rw-r--r--spec/ruby/core/range/percent_spec.rb16
-rw-r--r--spec/ruby/core/range/range_spec.rb7
-rw-r--r--spec/ruby/core/range/reverse_each_spec.rb125
-rw-r--r--spec/ruby/core/range/shared/begin.rb10
-rw-r--r--spec/ruby/core/range/shared/cover.rb193
-rw-r--r--spec/ruby/core/range/shared/cover_and_include.rb86
-rw-r--r--spec/ruby/core/range/shared/end.rb10
-rw-r--r--spec/ruby/core/range/shared/equal_value.rb51
-rw-r--r--spec/ruby/core/range/shared/include.rb91
-rw-r--r--spec/ruby/core/range/size_spec.rb92
-rw-r--r--spec/ruby/core/range/step_spec.rb725
-rw-r--r--spec/ruby/core/range/to_a_spec.rb39
-rw-r--r--spec/ruby/core/range/to_s_spec.rb23
-rw-r--r--spec/ruby/core/range/to_set_spec.rb54
-rw-r--r--spec/ruby/core/rational/abs_spec.rb6
-rw-r--r--spec/ruby/core/rational/ceil_spec.rb48
-rw-r--r--spec/ruby/core/rational/comparison_spec.rb93
-rw-r--r--spec/ruby/core/rational/denominator_spec.rb14
-rw-r--r--spec/ruby/core/rational/div_spec.rb54
-rw-r--r--spec/ruby/core/rational/divide_spec.rb74
-rw-r--r--spec/ruby/core/rational/divmod_spec.rb42
-rw-r--r--spec/ruby/core/rational/equal_value_spec.rb39
-rw-r--r--spec/ruby/core/rational/exponent_spec.rb236
-rw-r--r--spec/ruby/core/rational/fdiv_spec.rb5
-rw-r--r--spec/ruby/core/rational/fixtures/rational.rb14
-rw-r--r--spec/ruby/core/rational/floor_spec.rb49
-rw-r--r--spec/ruby/core/rational/hash_spec.rb9
-rw-r--r--spec/ruby/core/rational/inspect_spec.rb14
-rw-r--r--spec/ruby/core/rational/integer_spec.rb13
-rw-r--r--spec/ruby/core/rational/magnitude_spec.rb6
-rw-r--r--spec/ruby/core/rational/marshal_dump_spec.rb11
-rw-r--r--spec/ruby/core/rational/minus_spec.rb51
-rw-r--r--spec/ruby/core/rational/modulo_spec.rb43
-rw-r--r--spec/ruby/core/rational/multiply_spec.rb65
-rw-r--r--spec/ruby/core/rational/numerator_spec.rb10
-rw-r--r--spec/ruby/core/rational/plus_spec.rb50
-rw-r--r--spec/ruby/core/rational/quo_spec.rb25
-rw-r--r--spec/ruby/core/rational/rational_spec.rb11
-rw-r--r--spec/ruby/core/rational/rationalize_spec.rb36
-rw-r--r--spec/ruby/core/rational/remainder_spec.rb5
-rw-r--r--spec/ruby/core/rational/round_spec.rb106
-rw-r--r--spec/ruby/core/rational/shared/abs.rb11
-rw-r--r--spec/ruby/core/rational/shared/arithmetic_exception_in_coerce.rb11
-rw-r--r--spec/ruby/core/rational/to_f_spec.rb16
-rw-r--r--spec/ruby/core/rational/to_i_spec.rb12
-rw-r--r--spec/ruby/core/rational/to_r_spec.rb26
-rw-r--r--spec/ruby/core/rational/to_s_spec.rb14
-rw-r--r--spec/ruby/core/rational/truncate_spec.rb71
-rw-r--r--spec/ruby/core/rational/zero_spec.rb14
-rw-r--r--spec/ruby/core/refinement/append_features_spec.rb19
-rw-r--r--spec/ruby/core/refinement/extend_object_spec.rb21
-rw-r--r--spec/ruby/core/refinement/fixtures/classes.rb10
-rw-r--r--spec/ruby/core/refinement/import_methods_spec.rb287
-rw-r--r--spec/ruby/core/refinement/include_spec.rb13
-rw-r--r--spec/ruby/core/refinement/prepend_features_spec.rb19
-rw-r--r--spec/ruby/core/refinement/prepend_spec.rb13
-rw-r--r--spec/ruby/core/refinement/refined_class_spec.rb34
-rw-r--r--spec/ruby/core/refinement/shared/target.rb13
-rw-r--r--spec/ruby/core/refinement/target_spec.rb6
-rw-r--r--spec/ruby/core/regexp/case_compare_spec.rb35
-rw-r--r--spec/ruby/core/regexp/casefold_spec.rb8
-rw-r--r--spec/ruby/core/regexp/compile_spec.rb19
-rw-r--r--spec/ruby/core/regexp/encoding_spec.rb62
-rw-r--r--spec/ruby/core/regexp/eql_spec.rb6
-rw-r--r--spec/ruby/core/regexp/equal_value_spec.rb6
-rw-r--r--spec/ruby/core/regexp/escape_spec.rb6
-rw-r--r--spec/ruby/core/regexp/fixed_encoding_spec.rb36
-rw-r--r--spec/ruby/core/regexp/hash_spec.rb20
-rw-r--r--spec/ruby/core/regexp/initialize_spec.rb29
-rw-r--r--spec/ruby/core/regexp/inspect_spec.rb44
-rw-r--r--spec/ruby/core/regexp/last_match_spec.rb56
-rw-r--r--spec/ruby/core/regexp/linear_time_spec.rb80
-rw-r--r--spec/ruby/core/regexp/match_spec.rb146
-rw-r--r--spec/ruby/core/regexp/named_captures_spec.rb35
-rw-r--r--spec/ruby/core/regexp/names_spec.rb29
-rw-r--r--spec/ruby/core/regexp/new_spec.rb19
-rw-r--r--spec/ruby/core/regexp/options_spec.rb54
-rw-r--r--spec/ruby/core/regexp/quote_spec.rb6
-rw-r--r--spec/ruby/core/regexp/shared/equal_value.rb31
-rw-r--r--spec/ruby/core/regexp/shared/new.rb321
-rw-r--r--spec/ruby/core/regexp/shared/quote.rb41
-rw-r--r--spec/ruby/core/regexp/source_spec.rb47
-rw-r--r--spec/ruby/core/regexp/timeout_spec.rb33
-rw-r--r--spec/ruby/core/regexp/to_s_spec.rb62
-rw-r--r--spec/ruby/core/regexp/try_convert_spec.rb27
-rw-r--r--spec/ruby/core/regexp/union_spec.rb182
-rw-r--r--spec/ruby/core/set/add_spec.rb34
-rw-r--r--spec/ruby/core/set/append_spec.rb6
-rw-r--r--spec/ruby/core/set/case_compare_spec.rb11
-rw-r--r--spec/ruby/core/set/case_equality_spec.rb6
-rw-r--r--spec/ruby/core/set/classify_spec.rb26
-rw-r--r--spec/ruby/core/set/clear_spec.rb16
-rw-r--r--spec/ruby/core/set/collect_spec.rb6
-rw-r--r--spec/ruby/core/set/compare_by_identity_spec.rb153
-rw-r--r--spec/ruby/core/set/comparison_spec.rb26
-rw-r--r--spec/ruby/core/set/constructor_spec.rb14
-rw-r--r--spec/ruby/core/set/delete_if_spec.rb37
-rw-r--r--spec/ruby/core/set/delete_spec.rb36
-rw-r--r--spec/ruby/core/set/difference_spec.rb6
-rw-r--r--spec/ruby/core/set/disjoint_spec.rb22
-rw-r--r--spec/ruby/core/set/divide_spec.rb68
-rw-r--r--spec/ruby/core/set/each_spec.rb26
-rw-r--r--spec/ruby/core/set/empty_spec.rb9
-rw-r--r--spec/ruby/core/set/enumerable/to_set_spec.rb12
-rw-r--r--spec/ruby/core/set/eql_spec.rb14
-rw-r--r--spec/ruby/core/set/equal_value_spec.rb34
-rw-r--r--spec/ruby/core/set/exclusion_spec.rb17
-rw-r--r--spec/ruby/core/set/filter_spec.rb6
-rw-r--r--spec/ruby/core/set/fixtures/set_like.rb30
-rw-r--r--spec/ruby/core/set/flatten_merge_spec.rb24
-rw-r--r--spec/ruby/core/set/flatten_spec.rb49
-rw-r--r--spec/ruby/core/set/hash_spec.rb19
-rw-r--r--spec/ruby/core/set/include_spec.rb6
-rw-r--r--spec/ruby/core/set/initialize_clone_spec.rb15
-rw-r--r--spec/ruby/core/set/initialize_spec.rb88
-rw-r--r--spec/ruby/core/set/inspect_spec.rb6
-rw-r--r--spec/ruby/core/set/intersect_spec.rb22
-rw-r--r--spec/ruby/core/set/intersection_spec.rb10
-rw-r--r--spec/ruby/core/set/join_spec.rb30
-rw-r--r--spec/ruby/core/set/keep_if_spec.rb37
-rw-r--r--spec/ruby/core/set/length_spec.rb6
-rw-r--r--spec/ruby/core/set/map_spec.rb6
-rw-r--r--spec/ruby/core/set/member_spec.rb6
-rw-r--r--spec/ruby/core/set/merge_spec.rb29
-rw-r--r--spec/ruby/core/set/minus_spec.rb6
-rw-r--r--spec/ruby/core/set/plus_spec.rb6
-rw-r--r--spec/ruby/core/set/pretty_print_cycle_spec.rb14
-rw-r--r--spec/ruby/core/set/proper_subset_spec.rb35
-rw-r--r--spec/ruby/core/set/proper_superset_spec.rb42
-rw-r--r--spec/ruby/core/set/reject_spec.rb41
-rw-r--r--spec/ruby/core/set/replace_spec.rb24
-rw-r--r--spec/ruby/core/set/select_spec.rb6
-rw-r--r--spec/ruby/core/set/set_spec.rb10
-rw-r--r--spec/ruby/core/set/shared/add.rb14
-rw-r--r--spec/ruby/core/set/shared/collect.rb20
-rw-r--r--spec/ruby/core/set/shared/difference.rb15
-rw-r--r--spec/ruby/core/set/shared/include.rb29
-rw-r--r--spec/ruby/core/set/shared/inspect.rb45
-rw-r--r--spec/ruby/core/set/shared/intersection.rb15
-rw-r--r--spec/ruby/core/set/shared/length.rb6
-rw-r--r--spec/ruby/core/set/shared/select.rb41
-rw-r--r--spec/ruby/core/set/shared/union.rb15
-rw-r--r--spec/ruby/core/set/size_spec.rb6
-rw-r--r--spec/ruby/core/set/sortedset/sortedset_spec.rb13
-rw-r--r--spec/ruby/core/set/subset_spec.rb35
-rw-r--r--spec/ruby/core/set/subtract_spec.rb16
-rw-r--r--spec/ruby/core/set/superset_spec.rb42
-rw-r--r--spec/ruby/core/set/to_a_spec.rb7
-rw-r--r--spec/ruby/core/set/to_s_spec.rb11
-rw-r--r--spec/ruby/core/set/union_spec.rb10
-rw-r--r--spec/ruby/core/signal/fixtures/trap_all.rb15
-rw-r--r--spec/ruby/core/signal/list_spec.rb68
-rw-r--r--spec/ruby/core/signal/signame_spec.rb34
-rw-r--r--spec/ruby/core/signal/trap_spec.rb320
-rw-r--r--spec/ruby/core/sizedqueue/append_spec.rb16
-rw-r--r--spec/ruby/core/sizedqueue/clear_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/close_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/closed_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/deq_spec.rb11
-rw-r--r--spec/ruby/core/sizedqueue/empty_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/enq_spec.rb16
-rw-r--r--spec/ruby/core/sizedqueue/freeze_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/length_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/max_spec.rb10
-rw-r--r--spec/ruby/core/sizedqueue/new_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/num_waiting_spec.rb6
-rw-r--r--spec/ruby/core/sizedqueue/pop_spec.rb11
-rw-r--r--spec/ruby/core/sizedqueue/push_spec.rb16
-rw-r--r--spec/ruby/core/sizedqueue/shift_spec.rb11
-rw-r--r--spec/ruby/core/sizedqueue/size_spec.rb6
-rw-r--r--spec/ruby/core/string/allocate_spec.rb19
-rw-r--r--spec/ruby/core/string/append_as_bytes_spec.rb60
-rw-r--r--spec/ruby/core/string/append_spec.rb14
-rw-r--r--spec/ruby/core/string/ascii_only_spec.rb82
-rw-r--r--spec/ruby/core/string/b_spec.rb16
-rw-r--r--spec/ruby/core/string/byteindex_spec.rb298
-rw-r--r--spec/ruby/core/string/byterindex_spec.rb353
-rw-r--r--spec/ruby/core/string/bytes_spec.rb55
-rw-r--r--spec/ruby/core/string/bytesize_spec.rb33
-rw-r--r--spec/ruby/core/string/byteslice_spec.rb33
-rw-r--r--spec/ruby/core/string/bytesplice_spec.rb290
-rw-r--r--spec/ruby/core/string/capitalize_spec.rb207
-rw-r--r--spec/ruby/core/string/case_compare_spec.rb8
-rw-r--r--spec/ruby/core/string/casecmp_spec.rb204
-rw-r--r--spec/ruby/core/string/center_spec.rb117
-rw-r--r--spec/ruby/core/string/chars_spec.rb16
-rw-r--r--spec/ruby/core/string/chilled_string_spec.rb151
-rw-r--r--spec/ruby/core/string/chomp_spec.rb366
-rw-r--r--spec/ruby/core/string/chop_spec.rb119
-rw-r--r--spec/ruby/core/string/chr_spec.rb42
-rw-r--r--spec/ruby/core/string/clear_spec.rb38
-rw-r--r--spec/ruby/core/string/clone_spec.rb61
-rw-r--r--spec/ruby/core/string/codepoints_spec.rb18
-rw-r--r--spec/ruby/core/string/comparison_spec.rb112
-rw-r--r--spec/ruby/core/string/concat_spec.rb27
-rw-r--r--spec/ruby/core/string/count_spec.rb105
-rw-r--r--spec/ruby/core/string/crypt_spec.rb92
-rw-r--r--spec/ruby/core/string/dedup_spec.rb6
-rw-r--r--spec/ruby/core/string/delete_prefix_spec.rb83
-rw-r--r--spec/ruby/core/string/delete_spec.rb117
-rw-r--r--spec/ruby/core/string/delete_suffix_spec.rb83
-rw-r--r--spec/ruby/core/string/downcase_spec.rb195
-rw-r--r--spec/ruby/core/string/dump_spec.rb396
-rw-r--r--spec/ruby/core/string/dup_spec.rb65
-rw-r--r--spec/ruby/core/string/each_byte_spec.rb61
-rw-r--r--spec/ruby/core/string/each_char_spec.rb8
-rw-r--r--spec/ruby/core/string/each_codepoint_spec.rb8
-rw-r--r--spec/ruby/core/string/each_grapheme_cluster_spec.rb16
-rw-r--r--spec/ruby/core/string/each_line_spec.rb9
-rw-r--r--spec/ruby/core/string/element_reference_spec.rb35
-rw-r--r--spec/ruby/core/string/element_set_spec.rb589
-rw-r--r--spec/ruby/core/string/empty_spec.rb12
-rw-r--r--spec/ruby/core/string/encode_spec.rb240
-rw-r--r--spec/ruby/core/string/encoding_spec.rb184
-rw-r--r--spec/ruby/core/string/end_with_spec.rb8
-rw-r--r--spec/ruby/core/string/eql_spec.rb21
-rw-r--r--spec/ruby/core/string/equal_value_spec.rb8
-rw-r--r--spec/ruby/core/string/fixtures/classes.rb60
-rw-r--r--spec/ruby/core/string/fixtures/freeze_magic_comment.rb3
-rw-r--r--spec/ruby/core/string/fixtures/iso-8859-9-encoding.rb9
-rw-r--r--spec/ruby/core/string/fixtures/to_c.rb5
-rw-r--r--spec/ruby/core/string/force_encoding_spec.rb72
-rw-r--r--spec/ruby/core/string/freeze_spec.rb18
-rw-r--r--spec/ruby/core/string/getbyte_spec.rb69
-rw-r--r--spec/ruby/core/string/grapheme_clusters_spec.rb14
-rw-r--r--spec/ruby/core/string/gsub_spec.rb615
-rw-r--r--spec/ruby/core/string/hash_spec.rb9
-rw-r--r--spec/ruby/core/string/hex_spec.rb49
-rw-r--r--spec/ruby/core/string/include_spec.rb49
-rw-r--r--spec/ruby/core/string/index_spec.rb337
-rw-r--r--spec/ruby/core/string/initialize_spec.rb26
-rw-r--r--spec/ruby/core/string/insert_spec.rb81
-rw-r--r--spec/ruby/core/string/inspect_spec.rb520
-rw-r--r--spec/ruby/core/string/intern_spec.rb7
-rw-r--r--spec/ruby/core/string/length_spec.rb7
-rw-r--r--spec/ruby/core/string/lines_spec.rb19
-rw-r--r--spec/ruby/core/string/ljust_spec.rb100
-rw-r--r--spec/ruby/core/string/lstrip_spec.rb74
-rw-r--r--spec/ruby/core/string/match_spec.rb175
-rw-r--r--spec/ruby/core/string/modulo_spec.rb797
-rw-r--r--spec/ruby/core/string/multiply_spec.rb7
-rw-r--r--spec/ruby/core/string/new_spec.rb61
-rw-r--r--spec/ruby/core/string/next_spec.rb11
-rw-r--r--spec/ruby/core/string/oct_spec.rb88
-rw-r--r--spec/ruby/core/string/ord_spec.rb33
-rw-r--r--spec/ruby/core/string/partition_spec.rb63
-rw-r--r--spec/ruby/core/string/plus_spec.rb37
-rw-r--r--spec/ruby/core/string/prepend_spec.rb55
-rw-r--r--spec/ruby/core/string/replace_spec.rb7
-rw-r--r--spec/ruby/core/string/reverse_spec.rb70
-rw-r--r--spec/ruby/core/string/rindex_spec.rb384
-rw-r--r--spec/ruby/core/string/rjust_spec.rb100
-rw-r--r--spec/ruby/core/string/rpartition_spec.rb71
-rw-r--r--spec/ruby/core/string/rstrip_spec.rb80
-rw-r--r--spec/ruby/core/string/scan_spec.rb173
-rw-r--r--spec/ruby/core/string/scrub_spec.rb164
-rw-r--r--spec/ruby/core/string/setbyte_spec.rb112
-rw-r--r--spec/ruby/core/string/shared/byte_index_common.rb63
-rw-r--r--spec/ruby/core/string/shared/chars.rb86
-rw-r--r--spec/ruby/core/string/shared/codepoints.rb67
-rw-r--r--spec/ruby/core/string/shared/concat.rb159
-rw-r--r--spec/ruby/core/string/shared/dedup.rb51
-rw-r--r--spec/ruby/core/string/shared/each_char_without_block.rb26
-rw-r--r--spec/ruby/core/string/shared/each_codepoint_without_block.rb33
-rw-r--r--spec/ruby/core/string/shared/each_line.rb198
-rw-r--r--spec/ruby/core/string/shared/each_line_without_block.rb17
-rw-r--r--spec/ruby/core/string/shared/encode.rb448
-rw-r--r--spec/ruby/core/string/shared/eql.rb38
-rw-r--r--spec/ruby/core/string/shared/equal_value.rb29
-rw-r--r--spec/ruby/core/string/shared/grapheme_clusters.rb25
-rw-r--r--spec/ruby/core/string/shared/length.rb55
-rw-r--r--spec/ruby/core/string/shared/partition.rb33
-rw-r--r--spec/ruby/core/string/shared/replace.rb48
-rw-r--r--spec/ruby/core/string/shared/slice.rb517
-rw-r--r--spec/ruby/core/string/shared/strip.rb14
-rw-r--r--spec/ruby/core/string/shared/succ.rb87
-rw-r--r--spec/ruby/core/string/shared/to_s.rb13
-rw-r--r--spec/ruby/core/string/shared/to_sym.rb72
-rw-r--r--spec/ruby/core/string/size_spec.rb7
-rw-r--r--spec/ruby/core/string/slice_spec.rb390
-rw-r--r--spec/ruby/core/string/split_spec.rb546
-rw-r--r--spec/ruby/core/string/squeeze_spec.rb111
-rw-r--r--spec/ruby/core/string/start_with_spec.rb18
-rw-r--r--spec/ruby/core/string/string_spec.rb7
-rw-r--r--spec/ruby/core/string/strip_spec.rb58
-rw-r--r--spec/ruby/core/string/sub_spec.rb512
-rw-r--r--spec/ruby/core/string/succ_spec.rb11
-rw-r--r--spec/ruby/core/string/sum_spec.rb22
-rw-r--r--spec/ruby/core/string/swapcase_spec.rb193
-rw-r--r--spec/ruby/core/string/to_c_spec.rb53
-rw-r--r--spec/ruby/core/string/to_f_spec.rb140
-rw-r--r--spec/ruby/core/string/to_i_spec.rb349
-rw-r--r--spec/ruby/core/string/to_r_spec.rb62
-rw-r--r--spec/ruby/core/string/to_s_spec.rb7
-rw-r--r--spec/ruby/core/string/to_str_spec.rb7
-rw-r--r--spec/ruby/core/string/to_sym_spec.rb7
-rw-r--r--spec/ruby/core/string/tr_s_spec.rb131
-rw-r--r--spec/ruby/core/string/tr_spec.rb126
-rw-r--r--spec/ruby/core/string/try_convert_spec.rb50
-rw-r--r--spec/ruby/core/string/uminus_spec.rb6
-rw-r--r--spec/ruby/core/string/undump_spec.rb441
-rw-r--r--spec/ruby/core/string/unicode_normalize_spec.rb116
-rw-r--r--spec/ruby/core/string/unicode_normalized_spec.rb75
-rw-r--r--spec/ruby/core/string/unpack/a_spec.rb66
-rw-r--r--spec/ruby/core/string/unpack/at_spec.rb29
-rw-r--r--spec/ruby/core/string/unpack/b_spec.rb201
-rw-r--r--spec/ruby/core/string/unpack/c_spec.rb65
-rw-r--r--spec/ruby/core/string/unpack/carret_spec.rb43
-rw-r--r--spec/ruby/core/string/unpack/comment_spec.rb25
-rw-r--r--spec/ruby/core/string/unpack/d_spec.rb28
-rw-r--r--spec/ruby/core/string/unpack/e_spec.rb14
-rw-r--r--spec/ruby/core/string/unpack/f_spec.rb28
-rw-r--r--spec/ruby/core/string/unpack/g_spec.rb14
-rw-r--r--spec/ruby/core/string/unpack/h_spec.rb139
-rw-r--r--spec/ruby/core/string/unpack/i_spec.rb152
-rw-r--r--spec/ruby/core/string/unpack/j_spec.rb272
-rw-r--r--spec/ruby/core/string/unpack/l_spec.rb265
-rw-r--r--spec/ruby/core/string/unpack/m_spec.rb192
-rw-r--r--spec/ruby/core/string/unpack/n_spec.rb18
-rw-r--r--spec/ruby/core/string/unpack/p_spec.rb44
-rw-r--r--spec/ruby/core/string/unpack/percent_spec.rb7
-rw-r--r--spec/ruby/core/string/unpack/q_spec.rb64
-rw-r--r--spec/ruby/core/string/unpack/r_spec.rb85
-rw-r--r--spec/ruby/core/string/unpack/s_spec.rb152
-rw-r--r--spec/ruby/core/string/unpack/shared/basic.rb27
-rw-r--r--spec/ruby/core/string/unpack/shared/float.rb277
-rw-r--r--spec/ruby/core/string/unpack/shared/integer.rb349
-rw-r--r--spec/ruby/core/string/unpack/shared/string.rb51
-rw-r--r--spec/ruby/core/string/unpack/shared/taint.rb2
-rw-r--r--spec/ruby/core/string/unpack/shared/unicode.rb62
-rw-r--r--spec/ruby/core/string/unpack/u_spec.rb97
-rw-r--r--spec/ruby/core/string/unpack/v_spec.rb18
-rw-r--r--spec/ruby/core/string/unpack/w_spec.rb37
-rw-r--r--spec/ruby/core/string/unpack/x_spec.rb62
-rw-r--r--spec/ruby/core/string/unpack/z_spec.rb28
-rw-r--r--spec/ruby/core/string/unpack1_spec.rb61
-rw-r--r--spec/ruby/core/string/unpack_spec.rb46
-rw-r--r--spec/ruby/core/string/upcase_spec.rb187
-rw-r--r--spec/ruby/core/string/uplus_spec.rb60
-rw-r--r--spec/ruby/core/string/upto_spec.rb110
-rw-r--r--spec/ruby/core/string/valid_encoding/utf_8_spec.rb214
-rw-r--r--spec/ruby/core/string/valid_encoding_spec.rb133
-rw-r--r--spec/ruby/core/struct/clone_spec.rb7
-rw-r--r--spec/ruby/core/struct/constants_spec.rb13
-rw-r--r--spec/ruby/core/struct/deconstruct_keys_spec.rb130
-rw-r--r--spec/ruby/core/struct/deconstruct_spec.rb10
-rw-r--r--spec/ruby/core/struct/dig_spec.rb52
-rw-r--r--spec/ruby/core/struct/dup_spec.rb23
-rw-r--r--spec/ruby/core/struct/each_pair_spec.rb33
-rw-r--r--spec/ruby/core/struct/each_spec.rb27
-rw-r--r--spec/ruby/core/struct/element_reference_spec.rb52
-rw-r--r--spec/ruby/core/struct/element_set_spec.rb36
-rw-r--r--spec/ruby/core/struct/eql_spec.rb13
-rw-r--r--spec/ruby/core/struct/equal_value_spec.rb7
-rw-r--r--spec/ruby/core/struct/filter_spec.rb10
-rw-r--r--spec/ruby/core/struct/fixtures/classes.rb35
-rw-r--r--spec/ruby/core/struct/hash_spec.rb64
-rw-r--r--spec/ruby/core/struct/initialize_spec.rb74
-rw-r--r--spec/ruby/core/struct/inspect_spec.rb7
-rw-r--r--spec/ruby/core/struct/instance_variable_get_spec.rb16
-rw-r--r--spec/ruby/core/struct/instance_variables_spec.rb16
-rw-r--r--spec/ruby/core/struct/keyword_init_spec.rb45
-rw-r--r--spec/ruby/core/struct/length_spec.rb12
-rw-r--r--spec/ruby/core/struct/members_spec.rb25
-rw-r--r--spec/ruby/core/struct/new_spec.rb255
-rw-r--r--spec/ruby/core/struct/select_spec.rb10
-rw-r--r--spec/ruby/core/struct/shared/accessor.rb7
-rw-r--r--spec/ruby/core/struct/shared/dup.rb9
-rw-r--r--spec/ruby/core/struct/shared/equal_value.rb37
-rw-r--r--spec/ruby/core/struct/shared/inspect.rb40
-rw-r--r--spec/ruby/core/struct/shared/select.rb26
-rw-r--r--spec/ruby/core/struct/size_spec.rb11
-rw-r--r--spec/ruby/core/struct/struct_spec.rb50
-rw-r--r--spec/ruby/core/struct/to_a_spec.rb12
-rw-r--r--spec/ruby/core/struct/to_h_spec.rb68
-rw-r--r--spec/ruby/core/struct/to_s_spec.rb12
-rw-r--r--spec/ruby/core/struct/values_at_spec.rb59
-rw-r--r--spec/ruby/core/struct/values_spec.rb11
-rw-r--r--spec/ruby/core/symbol/all_symbols_spec.rb19
-rw-r--r--spec/ruby/core/symbol/capitalize_spec.rb41
-rw-r--r--spec/ruby/core/symbol/case_compare_spec.rb11
-rw-r--r--spec/ruby/core/symbol/casecmp_spec.rb152
-rw-r--r--spec/ruby/core/symbol/comparison_spec.rb51
-rw-r--r--spec/ruby/core/symbol/downcase_spec.rb25
-rw-r--r--spec/ruby/core/symbol/dup_spec.rb7
-rw-r--r--spec/ruby/core/symbol/element_reference_spec.rb6
-rw-r--r--spec/ruby/core/symbol/empty_spec.rb11
-rw-r--r--spec/ruby/core/symbol/encoding_spec.rb23
-rw-r--r--spec/ruby/core/symbol/end_with_spec.rb8
-rw-r--r--spec/ruby/core/symbol/equal_value_spec.rb14
-rw-r--r--spec/ruby/core/symbol/fixtures/classes.rb3
-rw-r--r--spec/ruby/core/symbol/id2name_spec.rb6
-rw-r--r--spec/ruby/core/symbol/inspect_spec.rb131
-rw-r--r--spec/ruby/core/symbol/intern_spec.rb11
-rw-r--r--spec/ruby/core/symbol/length_spec.rb6
-rw-r--r--spec/ruby/core/symbol/match_spec.rb77
-rw-r--r--spec/ruby/core/symbol/name_spec.rb17
-rw-r--r--spec/ruby/core/symbol/next_spec.rb6
-rw-r--r--spec/ruby/core/symbol/shared/id2name.rb30
-rw-r--r--spec/ruby/core/symbol/shared/length.rb23
-rw-r--r--spec/ruby/core/symbol/shared/slice.rb262
-rw-r--r--spec/ruby/core/symbol/shared/succ.rb18
-rw-r--r--spec/ruby/core/symbol/size_spec.rb6
-rw-r--r--spec/ruby/core/symbol/slice_spec.rb6
-rw-r--r--spec/ruby/core/symbol/start_with_spec.rb8
-rw-r--r--spec/ruby/core/symbol/succ_spec.rb6
-rw-r--r--spec/ruby/core/symbol/swapcase_spec.rb29
-rw-r--r--spec/ruby/core/symbol/symbol_spec.rb19
-rw-r--r--spec/ruby/core/symbol/to_proc_spec.rb78
-rw-r--r--spec/ruby/core/symbol/to_s_spec.rb6
-rw-r--r--spec/ruby/core/symbol/to_sym_spec.rb9
-rw-r--r--spec/ruby/core/symbol/upcase_spec.rb21
-rw-r--r--spec/ruby/core/systemexit/initialize_spec.rb26
-rw-r--r--spec/ruby/core/systemexit/success_spec.rb13
-rw-r--r--spec/ruby/core/thread/abort_on_exception_spec.rb106
-rw-r--r--spec/ruby/core/thread/add_trace_func_spec.rb5
-rw-r--r--spec/ruby/core/thread/alive_spec.rb58
-rw-r--r--spec/ruby/core/thread/allocate_spec.rb9
-rw-r--r--spec/ruby/core/thread/backtrace/limit_spec.rb13
-rw-r--r--spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb93
-rw-r--r--spec/ruby/core/thread/backtrace/location/base_label_spec.rb49
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb4
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb2
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb10
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/classes.rb139
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb5
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb3
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/main.rb5
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/path.rb2
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb11
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb1
-rw-r--r--spec/ruby/core/thread/backtrace/location/inspect_spec.rb13
-rw-r--r--spec/ruby/core/thread/backtrace/location/label_spec.rb227
-rw-r--r--spec/ruby/core/thread/backtrace/location/lineno_spec.rb23
-rw-r--r--spec/ruby/core/thread/backtrace/location/path_spec.rb124
-rw-r--r--spec/ruby/core/thread/backtrace/location/to_s_spec.rb13
-rw-r--r--spec/ruby/core/thread/backtrace_locations_spec.rb79
-rw-r--r--spec/ruby/core/thread/backtrace_spec.rb69
-rw-r--r--spec/ruby/core/thread/current_spec.rb31
-rw-r--r--spec/ruby/core/thread/each_caller_location_spec.rb47
-rw-r--r--spec/ruby/core/thread/element_reference_spec.rb55
-rw-r--r--spec/ruby/core/thread/element_set_spec.rb74
-rw-r--r--spec/ruby/core/thread/exit_spec.rb15
-rw-r--r--spec/ruby/core/thread/fetch_spec.rb66
-rw-r--r--spec/ruby/core/thread/fixtures/classes.rb322
-rw-r--r--spec/ruby/core/thread/fork_spec.rb9
-rw-r--r--spec/ruby/core/thread/group_spec.rb16
-rw-r--r--spec/ruby/core/thread/handle_interrupt_spec.rb125
-rw-r--r--spec/ruby/core/thread/ignore_deadlock_spec.rb19
-rw-r--r--spec/ruby/core/thread/initialize_spec.rb27
-rw-r--r--spec/ruby/core/thread/inspect_spec.rb6
-rw-r--r--spec/ruby/core/thread/join_spec.rb70
-rw-r--r--spec/ruby/core/thread/key_spec.rb60
-rw-r--r--spec/ruby/core/thread/keys_spec.rb44
-rw-r--r--spec/ruby/core/thread/kill_spec.rb21
-rw-r--r--spec/ruby/core/thread/list_spec.rb55
-rw-r--r--spec/ruby/core/thread/main_spec.rb10
-rw-r--r--spec/ruby/core/thread/name_spec.rb54
-rw-r--r--spec/ruby/core/thread/native_thread_id_spec.rb31
-rw-r--r--spec/ruby/core/thread/new_spec.rb83
-rw-r--r--spec/ruby/core/thread/pass_spec.rb8
-rw-r--r--spec/ruby/core/thread/pending_interrupt_spec.rb32
-rw-r--r--spec/ruby/core/thread/priority_spec.rb72
-rw-r--r--spec/ruby/core/thread/raise_spec.rb267
-rw-r--r--spec/ruby/core/thread/report_on_exception_spec.rb155
-rw-r--r--spec/ruby/core/thread/run_spec.rb8
-rw-r--r--spec/ruby/core/thread/set_trace_func_spec.rb5
-rw-r--r--spec/ruby/core/thread/shared/exit.rb219
-rw-r--r--spec/ruby/core/thread/shared/start.rb41
-rw-r--r--spec/ruby/core/thread/shared/to_s.rb53
-rw-r--r--spec/ruby/core/thread/shared/wakeup.rb62
-rw-r--r--spec/ruby/core/thread/start_spec.rb9
-rw-r--r--spec/ruby/core/thread/status_spec.rb60
-rw-r--r--spec/ruby/core/thread/stop_spec.rb54
-rw-r--r--spec/ruby/core/thread/terminate_spec.rb7
-rw-r--r--spec/ruby/core/thread/thread_variable_get_spec.rb60
-rw-r--r--spec/ruby/core/thread/thread_variable_set_spec.rb62
-rw-r--r--spec/ruby/core/thread/thread_variable_spec.rb60
-rw-r--r--spec/ruby/core/thread/thread_variables_spec.rb40
-rw-r--r--spec/ruby/core/thread/to_s_spec.rb6
-rw-r--r--spec/ruby/core/thread/value_spec.rb31
-rw-r--r--spec/ruby/core/thread/wakeup_spec.rb7
-rw-r--r--spec/ruby/core/threadgroup/add_spec.rb39
-rw-r--r--spec/ruby/core/threadgroup/default_spec.rb11
-rw-r--r--spec/ruby/core/threadgroup/enclose_spec.rb24
-rw-r--r--spec/ruby/core/threadgroup/enclosed_spec.rb14
-rw-r--r--spec/ruby/core/threadgroup/list_spec.rb23
-rw-r--r--spec/ruby/core/time/_dump_spec.rb55
-rw-r--r--spec/ruby/core/time/_load_spec.rb51
-rw-r--r--spec/ruby/core/time/asctime_spec.rb6
-rw-r--r--spec/ruby/core/time/at_spec.rb316
-rw-r--r--spec/ruby/core/time/ceil_spec.rb44
-rw-r--r--spec/ruby/core/time/comparison_spec.rb130
-rw-r--r--spec/ruby/core/time/ctime_spec.rb6
-rw-r--r--spec/ruby/core/time/day_spec.rb6
-rw-r--r--spec/ruby/core/time/deconstruct_keys_spec.rb43
-rw-r--r--spec/ruby/core/time/dst_spec.rb6
-rw-r--r--spec/ruby/core/time/dup_spec.rb46
-rw-r--r--spec/ruby/core/time/eql_spec.rb29
-rw-r--r--spec/ruby/core/time/fixtures/classes.rb105
-rw-r--r--spec/ruby/core/time/floor_spec.rb36
-rw-r--r--spec/ruby/core/time/friday_spec.rb11
-rw-r--r--spec/ruby/core/time/getgm_spec.rb6
-rw-r--r--spec/ruby/core/time/getlocal_spec.rb206
-rw-r--r--spec/ruby/core/time/getutc_spec.rb6
-rw-r--r--spec/ruby/core/time/gm_spec.rb10
-rw-r--r--spec/ruby/core/time/gmt_offset_spec.rb6
-rw-r--r--spec/ruby/core/time/gmt_spec.rb8
-rw-r--r--spec/ruby/core/time/gmtime_spec.rb6
-rw-r--r--spec/ruby/core/time/gmtoff_spec.rb6
-rw-r--r--spec/ruby/core/time/hash_spec.rb11
-rw-r--r--spec/ruby/core/time/hour_spec.rb17
-rw-r--r--spec/ruby/core/time/inspect_spec.rb33
-rw-r--r--spec/ruby/core/time/isdst_spec.rb6
-rw-r--r--spec/ruby/core/time/iso8601_spec.rb6
-rw-r--r--spec/ruby/core/time/local_spec.rb11
-rw-r--r--spec/ruby/core/time/localtime_spec.rb203
-rw-r--r--spec/ruby/core/time/mday_spec.rb6
-rw-r--r--spec/ruby/core/time/min_spec.rb17
-rw-r--r--spec/ruby/core/time/minus_spec.rb121
-rw-r--r--spec/ruby/core/time/mktime_spec.rb11
-rw-r--r--spec/ruby/core/time/mon_spec.rb6
-rw-r--r--spec/ruby/core/time/monday_spec.rb11
-rw-r--r--spec/ruby/core/time/month_spec.rb6
-rw-r--r--spec/ruby/core/time/new_spec.rb739
-rw-r--r--spec/ruby/core/time/now_spec.rb181
-rw-r--r--spec/ruby/core/time/nsec_spec.rb31
-rw-r--r--spec/ruby/core/time/plus_spec.rb118
-rw-r--r--spec/ruby/core/time/round_spec.rb35
-rw-r--r--spec/ruby/core/time/saturday_spec.rb11
-rw-r--r--spec/ruby/core/time/sec_spec.rb7
-rw-r--r--spec/ruby/core/time/shared/asctime.rb6
-rw-r--r--spec/ruby/core/time/shared/day.rb15
-rw-r--r--spec/ruby/core/time/shared/getgm.rb9
-rw-r--r--spec/ruby/core/time/shared/gm.rb70
-rw-r--r--spec/ruby/core/time/shared/gmt_offset.rb59
-rw-r--r--spec/ruby/core/time/shared/gmtime.rb40
-rw-r--r--spec/ruby/core/time/shared/inspect.rb21
-rw-r--r--spec/ruby/core/time/shared/isdst.rb8
-rw-r--r--spec/ruby/core/time/shared/local.rb42
-rw-r--r--spec/ruby/core/time/shared/month.rb15
-rw-r--r--spec/ruby/core/time/shared/now.rb33
-rw-r--r--spec/ruby/core/time/shared/time_params.rb271
-rw-r--r--spec/ruby/core/time/shared/to_i.rb16
-rw-r--r--spec/ruby/core/time/shared/xmlschema.rb31
-rw-r--r--spec/ruby/core/time/strftime_spec.rb91
-rw-r--r--spec/ruby/core/time/subsec_spec.rb27
-rw-r--r--spec/ruby/core/time/sunday_spec.rb11
-rw-r--r--spec/ruby/core/time/thursday_spec.rb11
-rw-r--r--spec/ruby/core/time/time_spec.rb7
-rw-r--r--spec/ruby/core/time/to_a_spec.rb12
-rw-r--r--spec/ruby/core/time/to_f_spec.rb7
-rw-r--r--spec/ruby/core/time/to_i_spec.rb6
-rw-r--r--spec/ruby/core/time/to_r_spec.rb11
-rw-r--r--spec/ruby/core/time/to_s_spec.rb6
-rw-r--r--spec/ruby/core/time/tuesday_spec.rb11
-rw-r--r--spec/ruby/core/time/tv_nsec_spec.rb5
-rw-r--r--spec/ruby/core/time/tv_sec_spec.rb6
-rw-r--r--spec/ruby/core/time/tv_usec_spec.rb5
-rw-r--r--spec/ruby/core/time/usec_spec.rb43
-rw-r--r--spec/ruby/core/time/utc_offset_spec.rb6
-rw-r--r--spec/ruby/core/time/utc_spec.rb66
-rw-r--r--spec/ruby/core/time/wday_spec.rb9
-rw-r--r--spec/ruby/core/time/wednesday_spec.rb11
-rw-r--r--spec/ruby/core/time/xmlschema_spec.rb6
-rw-r--r--spec/ruby/core/time/yday_spec.rb12
-rw-r--r--spec/ruby/core/time/year_spec.rb17
-rw-r--r--spec/ruby/core/time/zone_spec.rb111
-rw-r--r--spec/ruby/core/tracepoint/allow_reentry_spec.rb30
-rw-r--r--spec/ruby/core/tracepoint/binding_spec.rb21
-rw-r--r--spec/ruby/core/tracepoint/callee_id_spec.rb18
-rw-r--r--spec/ruby/core/tracepoint/defined_class_spec.rb27
-rw-r--r--spec/ruby/core/tracepoint/disable_spec.rb76
-rw-r--r--spec/ruby/core/tracepoint/enable_spec.rb543
-rw-r--r--spec/ruby/core/tracepoint/enabled_spec.rb15
-rw-r--r--spec/ruby/core/tracepoint/eval_script_spec.rb23
-rw-r--r--spec/ruby/core/tracepoint/event_spec.rb22
-rw-r--r--spec/ruby/core/tracepoint/fixtures/classes.rb40
-rw-r--r--spec/ruby/core/tracepoint/inspect_spec.rb141
-rw-r--r--spec/ruby/core/tracepoint/lineno_spec.rb20
-rw-r--r--spec/ruby/core/tracepoint/method_id_spec.rb15
-rw-r--r--spec/ruby/core/tracepoint/new_spec.rb72
-rw-r--r--spec/ruby/core/tracepoint/parameters_spec.rb28
-rw-r--r--spec/ruby/core/tracepoint/path_spec.rb26
-rw-r--r--spec/ruby/core/tracepoint/raised_exception_spec.rb36
-rw-r--r--spec/ruby/core/tracepoint/return_value_spec.rb17
-rw-r--r--spec/ruby/core/tracepoint/self_spec.rb26
-rw-r--r--spec/ruby/core/tracepoint/trace_spec.rb10
-rw-r--r--spec/ruby/core/true/and_spec.rb11
-rw-r--r--spec/ruby/core/true/case_compare_spec.rb13
-rw-r--r--spec/ruby/core/true/dup_spec.rb7
-rw-r--r--spec/ruby/core/true/inspect_spec.rb7
-rw-r--r--spec/ruby/core/true/or_spec.rb11
-rw-r--r--spec/ruby/core/true/singleton_method_spec.rb13
-rw-r--r--spec/ruby/core/true/to_s_spec.rb15
-rw-r--r--spec/ruby/core/true/trueclass_spec.rb15
-rw-r--r--spec/ruby/core/true/xor_spec.rb11
-rw-r--r--spec/ruby/core/unboundmethod/arity_spec.rb207
-rw-r--r--spec/ruby/core/unboundmethod/bind_call_spec.rb58
-rw-r--r--spec/ruby/core/unboundmethod/bind_spec.rb69
-rw-r--r--spec/ruby/core/unboundmethod/clone_spec.rb13
-rw-r--r--spec/ruby/core/unboundmethod/dup_spec.rb15
-rw-r--r--spec/ruby/core/unboundmethod/eql_spec.rb5
-rw-r--r--spec/ruby/core/unboundmethod/equal_value_spec.rb165
-rw-r--r--spec/ruby/core/unboundmethod/fixtures/classes.rb124
-rw-r--r--spec/ruby/core/unboundmethod/hash_spec.rb22
-rw-r--r--spec/ruby/core/unboundmethod/inspect_spec.rb9
-rw-r--r--spec/ruby/core/unboundmethod/name_spec.rb15
-rw-r--r--spec/ruby/core/unboundmethod/original_name_spec.rb59
-rw-r--r--spec/ruby/core/unboundmethod/owner_spec.rb31
-rw-r--r--spec/ruby/core/unboundmethod/parameters_spec.rb5
-rw-r--r--spec/ruby/core/unboundmethod/private_spec.rb9
-rw-r--r--spec/ruby/core/unboundmethod/protected_spec.rb9
-rw-r--r--spec/ruby/core/unboundmethod/public_spec.rb9
-rw-r--r--spec/ruby/core/unboundmethod/shared/dup.rb32
-rw-r--r--spec/ruby/core/unboundmethod/shared/to_s.rb33
-rw-r--r--spec/ruby/core/unboundmethod/source_location_spec.rb59
-rw-r--r--spec/ruby/core/unboundmethod/super_method_spec.rb49
-rw-r--r--spec/ruby/core/unboundmethod/to_s_spec.rb9
-rw-r--r--spec/ruby/core/warning/categories_spec.rb12
-rw-r--r--spec/ruby/core/warning/element_reference_spec.rb27
-rw-r--r--spec/ruby/core/warning/element_set_spec.rb39
-rw-r--r--spec/ruby/core/warning/performance_warning_spec.rb28
-rw-r--r--spec/ruby/core/warning/warn_spec.rb187
-rw-r--r--spec/ruby/default.mspec54
-rw-r--r--spec/ruby/fixtures/basicobject/method_missing.rb55
-rw-r--r--spec/ruby/fixtures/class.rb142
-rw-r--r--spec/ruby/fixtures/class_variables.rb58
-rw-r--r--spec/ruby/fixtures/code/a/load_fixture.bundle1
-rw-r--r--spec/ruby/fixtures/code/a/load_fixture.dll1
-rw-r--r--spec/ruby/fixtures/code/a/load_fixture.dylib1
-rw-r--r--spec/ruby/fixtures/code/a/load_fixture.so1
-rw-r--r--spec/ruby/fixtures/code/b/load_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/c/load_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/concurrent.rb12
-rw-r--r--spec/ruby/fixtures/code/concurrent2.rb8
-rw-r--r--spec/ruby/fixtures/code/concurrent3.rb2
-rw-r--r--spec/ruby/fixtures/code/concurrent_require_fixture.rb4
-rw-r--r--spec/ruby/fixtures/code/d/load_fixture.rb.rb1
-rw-r--r--spec/ruby/fixtures/code/file_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/gem/load_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/line_fixture.rb5
-rw-r--r--spec/ruby/fixtures/code/load_ext_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/load_fixture1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.bundle1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.dll1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.dylib1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.bundle1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.dll1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.dylib1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.rb1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.ext.so1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/load_fixture.so1
-rw-r--r--spec/ruby/fixtures/code/load_fixture_and__FILE__.rb1
-rw-r--r--spec/ruby/fixtures/code/load_wrap_fixture.rb12
-rw-r--r--spec/ruby/fixtures/code/load_wrap_method_fixture.rb9
-rw-r--r--spec/ruby/fixtures/code/methods_fixture.rb364
-rw-r--r--spec/ruby/fixtures/code/raise_fixture.rb1
-rw-r--r--spec/ruby/fixtures/code/recursive_load_fixture.rb5
-rw-r--r--spec/ruby/fixtures/code/recursive_require_fixture.rb3
-rw-r--r--spec/ruby/fixtures/code/symlink/symlink1.rb1
-rw-r--r--spec/ruby/fixtures/code/symlink/symlink2/symlink2.rb1
-rw-r--r--spec/ruby/fixtures/code_loading.rb41
-rw-r--r--spec/ruby/fixtures/constants.rb324
-rw-r--r--spec/ruby/fixtures/io.rb12
-rw-r--r--spec/ruby/fixtures/reflection.rb352
-rw-r--r--spec/ruby/language/BEGIN_spec.rb41
-rw-r--r--spec/ruby/language/END_spec.rb33
-rw-r--r--spec/ruby/language/README30
-rw-r--r--spec/ruby/language/alias_spec.rb294
-rw-r--r--spec/ruby/language/and_spec.rb80
-rw-r--r--spec/ruby/language/array_spec.rb166
-rw-r--r--spec/ruby/language/assignments_spec.rb582
-rw-r--r--spec/ruby/language/block_spec.rb1148
-rw-r--r--spec/ruby/language/break_spec.rb402
-rw-r--r--spec/ruby/language/case_spec.rb544
-rw-r--r--spec/ruby/language/class_spec.rb396
-rw-r--r--spec/ruby/language/class_variable_spec.rb114
-rw-r--r--spec/ruby/language/comment_spec.rb13
-rw-r--r--spec/ruby/language/constants_spec.rb809
-rw-r--r--spec/ruby/language/def_spec.rb815
-rw-r--r--spec/ruby/language/defined_spec.rb1328
-rw-r--r--spec/ruby/language/delegation_spec.rb162
-rw-r--r--spec/ruby/language/encoding_spec.rb36
-rw-r--r--spec/ruby/language/ensure_spec.rb346
-rw-r--r--spec/ruby/language/execution_spec.rb93
-rw-r--r--spec/ruby/language/file_spec.rb21
-rw-r--r--spec/ruby/language/fixtures/argv_encoding.rb1
-rw-r--r--spec/ruby/language/fixtures/array.rb32
-rw-r--r--spec/ruby/language/fixtures/begin_file.rb3
-rw-r--r--spec/ruby/language/fixtures/binary_symbol.rb4
-rw-r--r--spec/ruby/language/fixtures/block.rb61
-rw-r--r--spec/ruby/language/fixtures/break.rb291
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel.rb9
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_block.rb23
-rw-r--r--spec/ruby/language/fixtures/break_lambda_toplevel_method.rb17
-rw-r--r--spec/ruby/language/fixtures/bytes_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/case_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/class_with_class_variable.rb9
-rw-r--r--spec/ruby/language/fixtures/classes.rb31
-rw-r--r--spec/ruby/language/fixtures/coding_us_ascii.rb11
-rw-r--r--spec/ruby/language/fixtures/coding_utf_8.rb11
-rw-r--r--spec/ruby/language/fixtures/constant_visibility.rb114
-rw-r--r--spec/ruby/language/fixtures/constants_sclass.rb54
-rw-r--r--spec/ruby/language/fixtures/def.rb14
-rw-r--r--spec/ruby/language/fixtures/defined.rb339
-rw-r--r--spec/ruby/language/fixtures/delegation.rb11
-rw-r--r--spec/ruby/language/fixtures/dollar_zero.rb6
-rw-r--r--spec/ruby/language/fixtures/emacs_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/ensure.rb121
-rw-r--r--spec/ruby/language/fixtures/file.rb1
-rw-r--r--spec/ruby/language/fixtures/for_scope.rb15
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files.rb6
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files_diff_enc.rb6
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_across_files_no_comment.rb6
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_one_literal.rb4
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required.rb3
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb4
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_required_no_comment.rb1
-rw-r--r--spec/ruby/language/fixtures/freeze_magic_comment_two_literals.rb3
-rw-r--r--spec/ruby/language/fixtures/hash_strings_binary.rb7
-rw-r--r--spec/ruby/language/fixtures/hash_strings_usascii.rb7
-rw-r--r--spec/ruby/language/fixtures/hash_strings_utf8.rb7
-rw-r--r--spec/ruby/language/fixtures/magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/match_operators.rb9
-rw-r--r--spec/ruby/language/fixtures/metaclass.rb33
-rw-r--r--spec/ruby/language/fixtures/module.rb15
-rw-r--r--spec/ruby/language/fixtures/next.rb134
-rw-r--r--spec/ruby/language/fixtures/no_magic_comment.rb1
-rw-r--r--spec/ruby/language/fixtures/precedence.rb16
-rw-r--r--spec/ruby/language/fixtures/print_magic_comment_result_at_exit.rb3
-rw-r--r--spec/ruby/language/fixtures/private.rb59
-rw-r--r--spec/ruby/language/fixtures/rescue.rb67
-rw-r--r--spec/ruby/language/fixtures/rescue/top_level.rb7
-rw-r--r--spec/ruby/language/fixtures/rescue_captures.rb107
-rw-r--r--spec/ruby/language/fixtures/return.rb135
-rw-r--r--spec/ruby/language/fixtures/second_line_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/second_token_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/send.rb151
-rwxr-xr-xspec/ruby/language/fixtures/shebang_magic_comment.rb3
-rw-r--r--spec/ruby/language/fixtures/squiggly_heredoc.rb71
-rw-r--r--spec/ruby/language/fixtures/super.rb804
-rw-r--r--spec/ruby/language/fixtures/utf16-be-nobom.rbbin0 -> 68 bytes-rw-r--r--spec/ruby/language/fixtures/utf16-le-nobom.rbbin0 -> 69 bytes-rw-r--r--spec/ruby/language/fixtures/utf8-bom.rb2
-rw-r--r--spec/ruby/language/fixtures/utf8-nobom.rb2
-rw-r--r--spec/ruby/language/fixtures/variables.rb157
-rw-r--r--spec/ruby/language/fixtures/vim_magic_comment.rb2
-rw-r--r--spec/ruby/language/fixtures/yield.rb41
-rw-r--r--spec/ruby/language/for_spec.rb380
-rw-r--r--spec/ruby/language/hash_spec.rb333
-rw-r--r--spec/ruby/language/heredoc_spec.rb119
-rw-r--r--spec/ruby/language/if_spec.rb424
-rw-r--r--spec/ruby/language/it_parameter_spec.rb108
-rw-r--r--spec/ruby/language/keyword_arguments_spec.rb398
-rw-r--r--spec/ruby/language/lambda_spec.rb629
-rw-r--r--spec/ruby/language/line_spec.rb45
-rw-r--r--spec/ruby/language/loop_spec.rb67
-rw-r--r--spec/ruby/language/magic_comment_spec.rb93
-rw-r--r--spec/ruby/language/match_spec.rb89
-rw-r--r--spec/ruby/language/metaclass_spec.rb143
-rw-r--r--spec/ruby/language/method_spec.rb1669
-rw-r--r--spec/ruby/language/module_spec.rb123
-rw-r--r--spec/ruby/language/next_spec.rb410
-rw-r--r--spec/ruby/language/not_spec.rb51
-rw-r--r--spec/ruby/language/numbered_parameters_spec.rb113
-rw-r--r--spec/ruby/language/numbers_spec.rb105
-rw-r--r--spec/ruby/language/optional_assignments_spec.rb742
-rw-r--r--spec/ruby/language/or_spec.rb90
-rw-r--r--spec/ruby/language/order_spec.rb75
-rw-r--r--spec/ruby/language/pattern_matching_spec.rb1310
-rw-r--r--spec/ruby/language/precedence_spec.rb445
-rw-r--r--spec/ruby/language/predefined/data_spec.rb48
-rw-r--r--spec/ruby/language/predefined/fixtures/data1.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/data2.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/data3.rb6
-rw-r--r--spec/ruby/language/predefined/fixtures/data4.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/data5.rb5
-rw-r--r--spec/ruby/language/predefined/fixtures/data_offset.rb12
-rw-r--r--spec/ruby/language/predefined/fixtures/data_only.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/empty_data.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/print_data.rb3
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_dynamic_required.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_id.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_id_required.rb1
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_required_before.rb2
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_values.rb9
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_variables.rb4
-rw-r--r--spec/ruby/language/predefined/fixtures/toplevel_binding_variables_required.rb2
-rw-r--r--spec/ruby/language/predefined/toplevel_binding_spec.rb34
-rw-r--r--spec/ruby/language/predefined_spec.rb1575
-rw-r--r--spec/ruby/language/private_spec.rb67
-rw-r--r--spec/ruby/language/proc_spec.rb267
-rw-r--r--spec/ruby/language/range_spec.rb30
-rw-r--r--spec/ruby/language/redo_spec.rb66
-rw-r--r--spec/ruby/language/regexp/anchors_spec.rb179
-rw-r--r--spec/ruby/language/regexp/back-references_spec.rb149
-rw-r--r--spec/ruby/language/regexp/character_classes_spec.rb647
-rw-r--r--spec/ruby/language/regexp/empty_checks_spec.rb135
-rw-r--r--spec/ruby/language/regexp/encoding_spec.rb152
-rw-r--r--spec/ruby/language/regexp/escapes_spec.rb169
-rw-r--r--spec/ruby/language/regexp/grouping_spec.rb63
-rw-r--r--spec/ruby/language/regexp/interpolation_spec.rb58
-rw-r--r--spec/ruby/language/regexp/modifiers_spec.rb115
-rw-r--r--spec/ruby/language/regexp/repetition_spec.rb138
-rw-r--r--spec/ruby/language/regexp/subexpression_call_spec.rb50
-rw-r--r--spec/ruby/language/regexp_spec.rb167
-rw-r--r--spec/ruby/language/rescue_spec.rb616
-rw-r--r--spec/ruby/language/reserved_keywords.rb149
-rw-r--r--spec/ruby/language/retry_spec.rb55
-rw-r--r--spec/ruby/language/return_spec.rb490
-rw-r--r--spec/ruby/language/safe_navigator_spec.rb147
-rw-r--r--spec/ruby/language/safe_spec.rb11
-rw-r--r--spec/ruby/language/send_spec.rb591
-rw-r--r--spec/ruby/language/shared/__FILE__.rb23
-rw-r--r--spec/ruby/language/shared/__LINE__.rb15
-rw-r--r--spec/ruby/language/singleton_class_spec.rb317
-rw-r--r--spec/ruby/language/source_encoding_spec.rb61
-rw-r--r--spec/ruby/language/string_spec.rb307
-rw-r--r--spec/ruby/language/super_spec.rb478
-rw-r--r--spec/ruby/language/symbol_spec.rb108
-rw-r--r--spec/ruby/language/throw_spec.rb81
-rw-r--r--spec/ruby/language/undef_spec.rb79
-rw-r--r--spec/ruby/language/unless_spec.rb43
-rw-r--r--spec/ruby/language/until_spec.rb234
-rw-r--r--spec/ruby/language/variables_spec.rb930
-rw-r--r--spec/ruby/language/while_spec.rb344
-rw-r--r--spec/ruby/language/yield_spec.rb226
-rw-r--r--spec/ruby/library/English/English_spec.rb161
-rw-r--r--spec/ruby/library/English/alias_spec.rb14
-rw-r--r--spec/ruby/library/abbrev/abbrev_spec.rb31
-rw-r--r--spec/ruby/library/base64/decode64_spec.rb29
-rw-r--r--spec/ruby/library/base64/encode64_spec.rb23
-rw-r--r--spec/ruby/library/base64/strict_decode64_spec.rb41
-rw-r--r--spec/ruby/library/base64/strict_encode64_spec.rb19
-rw-r--r--spec/ruby/library/base64/urlsafe_decode64_spec.rb19
-rw-r--r--spec/ruby/library/base64/urlsafe_encode64_spec.rb20
-rw-r--r--spec/ruby/library/bigdecimal/BigDecimal_spec.rb239
-rw-r--r--spec/ruby/library/bigdecimal/abs_spec.rb50
-rw-r--r--spec/ruby/library/bigdecimal/add_spec.rb185
-rw-r--r--spec/ruby/library/bigdecimal/case_compare_spec.rb7
-rw-r--r--spec/ruby/library/bigdecimal/ceil_spec.rb104
-rw-r--r--spec/ruby/library/bigdecimal/clone_spec.rb6
-rw-r--r--spec/ruby/library/bigdecimal/coerce_spec.rb26
-rw-r--r--spec/ruby/library/bigdecimal/comparison_spec.rb81
-rw-r--r--spec/ruby/library/bigdecimal/constants_spec.rb70
-rw-r--r--spec/ruby/library/bigdecimal/core_spec.rb62
-rw-r--r--spec/ruby/library/bigdecimal/div_spec.rb110
-rw-r--r--spec/ruby/library/bigdecimal/divide_spec.rb17
-rw-r--r--spec/ruby/library/bigdecimal/divmod_spec.rb212
-rw-r--r--spec/ruby/library/bigdecimal/double_fig_spec.rb9
-rw-r--r--spec/ruby/library/bigdecimal/dup_spec.rb6
-rw-r--r--spec/ruby/library/bigdecimal/eql_spec.rb6
-rw-r--r--spec/ruby/library/bigdecimal/equal_value_spec.rb7
-rw-r--r--spec/ruby/library/bigdecimal/exponent_spec.rb27
-rw-r--r--spec/ruby/library/bigdecimal/finite_spec.rb34
-rw-r--r--spec/ruby/library/bigdecimal/fix_spec.rb57
-rw-r--r--spec/ruby/library/bigdecimal/fixtures/classes.rb17
-rw-r--r--spec/ruby/library/bigdecimal/floor_spec.rb100
-rw-r--r--spec/ruby/library/bigdecimal/frac_spec.rb48
-rw-r--r--spec/ruby/library/bigdecimal/gt_spec.rb96
-rw-r--r--spec/ruby/library/bigdecimal/gte_spec.rb100
-rw-r--r--spec/ruby/library/bigdecimal/hash_spec.rb30
-rw-r--r--spec/ruby/library/bigdecimal/infinite_spec.rb32
-rw-r--r--spec/ruby/library/bigdecimal/inspect_spec.rb30
-rw-r--r--spec/ruby/library/bigdecimal/limit_spec.rb55
-rw-r--r--spec/ruby/library/bigdecimal/lt_spec.rb94
-rw-r--r--spec/ruby/library/bigdecimal/lte_spec.rb100
-rw-r--r--spec/ruby/library/bigdecimal/minus_spec.rb66
-rw-r--r--spec/ruby/library/bigdecimal/mode_spec.rb36
-rw-r--r--spec/ruby/library/bigdecimal/modulo_spec.rb12
-rw-r--r--spec/ruby/library/bigdecimal/mult_spec.rb24
-rw-r--r--spec/ruby/library/bigdecimal/multiply_spec.rb41
-rw-r--r--spec/ruby/library/bigdecimal/nan_spec.rb23
-rw-r--r--spec/ruby/library/bigdecimal/nonzero_spec.rb29
-rw-r--r--spec/ruby/library/bigdecimal/plus_spec.rb54
-rw-r--r--spec/ruby/library/bigdecimal/power_spec.rb6
-rw-r--r--spec/ruby/library/bigdecimal/quo_spec.rb12
-rw-r--r--spec/ruby/library/bigdecimal/remainder_spec.rb77
-rw-r--r--spec/ruby/library/bigdecimal/round_spec.rb234
-rw-r--r--spec/ruby/library/bigdecimal/shared/clone.rb13
-rw-r--r--spec/ruby/library/bigdecimal/shared/eql.rb61
-rw-r--r--spec/ruby/library/bigdecimal/shared/modulo.rb131
-rw-r--r--spec/ruby/library/bigdecimal/shared/mult.rb97
-rw-r--r--spec/ruby/library/bigdecimal/shared/power.rb72
-rw-r--r--spec/ruby/library/bigdecimal/shared/quo.rb68
-rw-r--r--spec/ruby/library/bigdecimal/shared/to_int.rb16
-rw-r--r--spec/ruby/library/bigdecimal/sign_spec.rb46
-rw-r--r--spec/ruby/library/bigdecimal/split_spec.rb86
-rw-r--r--spec/ruby/library/bigdecimal/sqrt_spec.rb114
-rw-r--r--spec/ruby/library/bigdecimal/sub_spec.rb62
-rw-r--r--spec/ruby/library/bigdecimal/to_d_spec.rb10
-rw-r--r--spec/ruby/library/bigdecimal/to_f_spec.rb54
-rw-r--r--spec/ruby/library/bigdecimal/to_i_spec.rb7
-rw-r--r--spec/ruby/library/bigdecimal/to_int_spec.rb8
-rw-r--r--spec/ruby/library/bigdecimal/to_r_spec.rb28
-rw-r--r--spec/ruby/library/bigdecimal/to_s_spec.rb98
-rw-r--r--spec/ruby/library/bigdecimal/truncate_spec.rb81
-rw-r--r--spec/ruby/library/bigdecimal/uminus_spec.rb58
-rw-r--r--spec/ruby/library/bigdecimal/uplus_spec.rb17
-rw-r--r--spec/ruby/library/bigdecimal/util_spec.rb40
-rw-r--r--spec/ruby/library/bigdecimal/zero_spec.rb27
-rw-r--r--spec/ruby/library/cgi/cookie/domain_spec.rb26
-rw-r--r--spec/ruby/library/cgi/cookie/expires_spec.rb26
-rw-r--r--spec/ruby/library/cgi/cookie/initialize_spec.rb150
-rw-r--r--spec/ruby/library/cgi/cookie/name_spec.rb26
-rw-r--r--spec/ruby/library/cgi/cookie/parse_spec.rb29
-rw-r--r--spec/ruby/library/cgi/cookie/path_spec.rb26
-rw-r--r--spec/ruby/library/cgi/cookie/secure_spec.rb73
-rw-r--r--spec/ruby/library/cgi/cookie/to_s_spec.rb36
-rw-r--r--spec/ruby/library/cgi/cookie/value_spec.rb79
-rw-r--r--spec/ruby/library/cgi/escapeElement_spec.rb26
-rw-r--r--spec/ruby/library/cgi/escapeHTML_spec.rb21
-rw-r--r--spec/ruby/library/cgi/escapeURIComponent_spec.rb78
-rw-r--r--spec/ruby/library/cgi/escape_spec.rb22
-rw-r--r--spec/ruby/library/cgi/htmlextension/a_spec.rb52
-rw-r--r--spec/ruby/library/cgi/htmlextension/base_spec.rb36
-rw-r--r--spec/ruby/library/cgi/htmlextension/blockquote_spec.rb36
-rw-r--r--spec/ruby/library/cgi/htmlextension/br_spec.rb25
-rw-r--r--spec/ruby/library/cgi/htmlextension/caption_spec.rb36
-rw-r--r--spec/ruby/library/cgi/htmlextension/checkbox_group_spec.rb79
-rw-r--r--spec/ruby/library/cgi/htmlextension/checkbox_spec.rb80
-rw-r--r--spec/ruby/library/cgi/htmlextension/doctype_spec.rb30
-rw-r--r--spec/ruby/library/cgi/htmlextension/file_field_spec.rb75
-rw-r--r--spec/ruby/library/cgi/htmlextension/fixtures/common.rb15
-rw-r--r--spec/ruby/library/cgi/htmlextension/form_spec.rb61
-rw-r--r--spec/ruby/library/cgi/htmlextension/frame_spec.rb17
-rw-r--r--spec/ruby/library/cgi/htmlextension/frameset_spec.rb17
-rw-r--r--spec/ruby/library/cgi/htmlextension/hidden_spec.rb62
-rw-r--r--spec/ruby/library/cgi/htmlextension/html_spec.rb69
-rw-r--r--spec/ruby/library/cgi/htmlextension/image_button_spec.rb72
-rw-r--r--spec/ruby/library/cgi/htmlextension/img_spec.rb86
-rw-r--r--spec/ruby/library/cgi/htmlextension/multipart_form_spec.rb67
-rw-r--r--spec/ruby/library/cgi/htmlextension/password_field_spec.rb87
-rw-r--r--spec/ruby/library/cgi/htmlextension/popup_menu_spec.rb11
-rw-r--r--spec/ruby/library/cgi/htmlextension/radio_button_spec.rb80
-rw-r--r--spec/ruby/library/cgi/htmlextension/radio_group_spec.rb80
-rw-r--r--spec/ruby/library/cgi/htmlextension/reset_spec.rb60
-rw-r--r--spec/ruby/library/cgi/htmlextension/scrolling_list_spec.rb11
-rw-r--r--spec/ruby/library/cgi/htmlextension/shared/popup_menu.rb94
-rw-r--r--spec/ruby/library/cgi/htmlextension/submit_spec.rb60
-rw-r--r--spec/ruby/library/cgi/htmlextension/text_field_spec.rb87
-rw-r--r--spec/ruby/library/cgi/htmlextension/textarea_spec.rb76
-rw-r--r--spec/ruby/library/cgi/http_header_spec.rb11
-rw-r--r--spec/ruby/library/cgi/initialize_spec.rb136
-rw-r--r--spec/ruby/library/cgi/out_spec.rb54
-rw-r--r--spec/ruby/library/cgi/parse_spec.rb27
-rw-r--r--spec/ruby/library/cgi/pretty_spec.rb27
-rw-r--r--spec/ruby/library/cgi/print_spec.rb29
-rw-r--r--spec/ruby/library/cgi/queryextension/accept_charset_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/accept_encoding_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/accept_language_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/accept_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/auth_type_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/cache_control_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/content_length_spec.rb29
-rw-r--r--spec/ruby/library/cgi/queryextension/content_type_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/cookies_spec.rb13
-rw-r--r--spec/ruby/library/cgi/queryextension/element_reference_spec.rb30
-rw-r--r--spec/ruby/library/cgi/queryextension/from_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/gateway_interface_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/has_key_spec.rb10
-rw-r--r--spec/ruby/library/cgi/queryextension/host_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/include_spec.rb10
-rw-r--r--spec/ruby/library/cgi/queryextension/key_spec.rb10
-rw-r--r--spec/ruby/library/cgi/queryextension/keys_spec.rb23
-rw-r--r--spec/ruby/library/cgi/queryextension/multipart_spec.rb43
-rw-r--r--spec/ruby/library/cgi/queryextension/negotiate_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/params_spec.rb40
-rw-r--r--spec/ruby/library/cgi/queryextension/path_info_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/path_translated_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/pragma_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/query_string_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/raw_cookie2_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/raw_cookie_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/referer_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/remote_addr_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/remote_host_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/remote_ident_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/remote_user_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/request_method_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/script_name_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/server_name_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/server_port_spec.rb29
-rw-r--r--spec/ruby/library/cgi/queryextension/server_protocol_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/server_software_spec.rb25
-rw-r--r--spec/ruby/library/cgi/queryextension/shared/has_key.rb19
-rw-r--r--spec/ruby/library/cgi/queryextension/user_agent_spec.rb25
-rw-r--r--spec/ruby/library/cgi/rfc1123_date_spec.rb13
-rw-r--r--spec/ruby/library/cgi/shared/http_header.rb112
-rw-r--r--spec/ruby/library/cgi/unescapeElement_spec.rb26
-rw-r--r--spec/ruby/library/cgi/unescapeHTML_spec.rb48
-rw-r--r--spec/ruby/library/cgi/unescapeURIComponent_spec.rb128
-rw-r--r--spec/ruby/library/cgi/unescape_spec.rb21
-rw-r--r--spec/ruby/library/coverage/fixtures/code_with_begin.rb3
-rw-r--r--spec/ruby/library/coverage/fixtures/eval_code.rb11
-rw-r--r--spec/ruby/library/coverage/fixtures/second_class.rb5
-rw-r--r--spec/ruby/library/coverage/fixtures/some_class.rb16
-rw-r--r--spec/ruby/library/coverage/fixtures/start_coverage.rb3
-rw-r--r--spec/ruby/library/coverage/peek_result_spec.rb64
-rw-r--r--spec/ruby/library/coverage/result_spec.rb343
-rw-r--r--spec/ruby/library/coverage/running_spec.rb20
-rw-r--r--spec/ruby/library/coverage/start_spec.rb87
-rw-r--r--spec/ruby/library/coverage/supported_spec.rb30
-rw-r--r--spec/ruby/library/csv/basicwriter/close_on_terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/basicwriter/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/basicwriter/terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/cell/data_spec.rb6
-rw-r--r--spec/ruby/library/csv/cell/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/fixtures/one_line.csv1
-rw-r--r--spec/ruby/library/csv/foreach_spec.rb6
-rw-r--r--spec/ruby/library/csv/generate_line_spec.rb30
-rw-r--r--spec/ruby/library/csv/generate_row_spec.rb6
-rw-r--r--spec/ruby/library/csv/generate_spec.rb32
-rw-r--r--spec/ruby/library/csv/iobuf/close_spec.rb6
-rw-r--r--spec/ruby/library/csv/iobuf/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/iobuf/read_spec.rb6
-rw-r--r--spec/ruby/library/csv/iobuf/terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/ioreader/close_on_terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/ioreader/get_row_spec.rb6
-rw-r--r--spec/ruby/library/csv/ioreader/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/ioreader/terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/liberal_parsing_spec.rb19
-rw-r--r--spec/ruby/library/csv/open_spec.rb6
-rw-r--r--spec/ruby/library/csv/parse_spec.rb93
-rw-r--r--spec/ruby/library/csv/read_spec.rb6
-rw-r--r--spec/ruby/library/csv/readlines_spec.rb35
-rw-r--r--spec/ruby/library/csv/streambuf/add_buf_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/buf_size_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/drop_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/element_reference_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/get_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/idx_is_eos_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/is_eos_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/read_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/rel_buf_spec.rb6
-rw-r--r--spec/ruby/library/csv/streambuf/terminate_spec.rb6
-rw-r--r--spec/ruby/library/csv/stringreader/get_row_spec.rb6
-rw-r--r--spec/ruby/library/csv/stringreader/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/add_row_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/append_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/close_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/create_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/generate_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/initialize_spec.rb6
-rw-r--r--spec/ruby/library/csv/writer/terminate_spec.rb6
-rw-r--r--spec/ruby/library/date/accessor_spec.rb91
-rw-r--r--spec/ruby/library/date/add_month_spec.rb38
-rw-r--r--spec/ruby/library/date/add_spec.rb30
-rw-r--r--spec/ruby/library/date/ajd_spec.rb6
-rw-r--r--spec/ruby/library/date/ajd_to_amjd_spec.rb6
-rw-r--r--spec/ruby/library/date/ajd_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/amjd_spec.rb6
-rw-r--r--spec/ruby/library/date/amjd_to_ajd_spec.rb6
-rw-r--r--spec/ruby/library/date/append_spec.rb6
-rw-r--r--spec/ruby/library/date/asctime_spec.rb6
-rw-r--r--spec/ruby/library/date/boat_spec.rb24
-rw-r--r--spec/ruby/library/date/case_compare_spec.rb6
-rw-r--r--spec/ruby/library/date/civil_spec.rb7
-rw-r--r--spec/ruby/library/date/commercial_spec.rb17
-rw-r--r--spec/ruby/library/date/commercial_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/comparison_spec.rb6
-rw-r--r--spec/ruby/library/date/constants_spec.rb48
-rw-r--r--spec/ruby/library/date/conversions_spec.rb43
-rw-r--r--spec/ruby/library/date/ctime_spec.rb6
-rw-r--r--spec/ruby/library/date/cwday_spec.rb6
-rw-r--r--spec/ruby/library/date/cweek_spec.rb6
-rw-r--r--spec/ruby/library/date/cwyear_spec.rb6
-rw-r--r--spec/ruby/library/date/day_fraction_spec.rb6
-rw-r--r--spec/ruby/library/date/day_fraction_to_time_spec.rb6
-rw-r--r--spec/ruby/library/date/day_spec.rb9
-rw-r--r--spec/ruby/library/date/deconstruct_keys_spec.rb42
-rw-r--r--spec/ruby/library/date/downto_spec.rb18
-rw-r--r--spec/ruby/library/date/england_spec.rb6
-rw-r--r--spec/ruby/library/date/eql_spec.rb12
-rw-r--r--spec/ruby/library/date/format/bag/method_missing_spec.rb6
-rw-r--r--spec/ruby/library/date/format/bag/to_hash_spec.rb6
-rw-r--r--spec/ruby/library/date/friday_spec.rb12
-rw-r--r--spec/ruby/library/date/gregorian_leap_spec.rb15
-rw-r--r--spec/ruby/library/date/gregorian_spec.rb16
-rw-r--r--spec/ruby/library/date/hash_spec.rb8
-rw-r--r--spec/ruby/library/date/infinity/abs_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/coerce_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/comparison_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/d_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/finite_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/infinite_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/nan_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/uminus_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/uplus_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity/zero_spec.rb6
-rw-r--r--spec/ruby/library/date/infinity_spec.rb67
-rw-r--r--spec/ruby/library/date/inspect_spec.rb6
-rw-r--r--spec/ruby/library/date/iso8601_spec.rb56
-rw-r--r--spec/ruby/library/date/italy_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_spec.rb15
-rw-r--r--spec/ruby/library/date/jd_to_ajd_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_civil_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_commercial_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_ld_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_mjd_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_ordinal_spec.rb6
-rw-r--r--spec/ruby/library/date/jd_to_wday_spec.rb6
-rw-r--r--spec/ruby/library/date/julian_leap_spec.rb15
-rw-r--r--spec/ruby/library/date/julian_spec.rb16
-rw-r--r--spec/ruby/library/date/ld_spec.rb6
-rw-r--r--spec/ruby/library/date/ld_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/leap_spec.rb10
-rw-r--r--spec/ruby/library/date/mday_spec.rb6
-rw-r--r--spec/ruby/library/date/minus_month_spec.rb23
-rw-r--r--spec/ruby/library/date/minus_spec.rb30
-rw-r--r--spec/ruby/library/date/mjd_spec.rb6
-rw-r--r--spec/ruby/library/date/mjd_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/mon_spec.rb7
-rw-r--r--spec/ruby/library/date/monday_spec.rb8
-rw-r--r--spec/ruby/library/date/month_spec.rb7
-rw-r--r--spec/ruby/library/date/new_spec.rb7
-rw-r--r--spec/ruby/library/date/new_start_spec.rb6
-rw-r--r--spec/ruby/library/date/next_day_spec.rb14
-rw-r--r--spec/ruby/library/date/next_month_spec.rb29
-rw-r--r--spec/ruby/library/date/next_spec.rb6
-rw-r--r--spec/ruby/library/date/next_year_spec.rb12
-rw-r--r--spec/ruby/library/date/ordinal_spec.rb7
-rw-r--r--spec/ruby/library/date/ordinal_to_jd_spec.rb6
-rw-r--r--spec/ruby/library/date/parse_spec.rb159
-rw-r--r--spec/ruby/library/date/plus_spec.rb20
-rw-r--r--spec/ruby/library/date/prev_day_spec.rb14
-rw-r--r--spec/ruby/library/date/prev_month_spec.rb29
-rw-r--r--spec/ruby/library/date/prev_year_spec.rb12
-rw-r--r--spec/ruby/library/date/relationship_spec.rb20
-rw-r--r--spec/ruby/library/date/rfc3339_spec.rb13
-rw-r--r--spec/ruby/library/date/right_shift_spec.rb6
-rw-r--r--spec/ruby/library/date/saturday_spec.rb8
-rw-r--r--spec/ruby/library/date/shared/civil.rb57
-rw-r--r--spec/ruby/library/date/shared/commercial.rb39
-rw-r--r--spec/ruby/library/date/shared/jd.rb14
-rw-r--r--spec/ruby/library/date/shared/month.rb6
-rw-r--r--spec/ruby/library/date/shared/ordinal.rb22
-rw-r--r--spec/ruby/library/date/shared/parse.rb54
-rw-r--r--spec/ruby/library/date/shared/parse_eu.rb37
-rw-r--r--spec/ruby/library/date/shared/parse_us.rb36
-rw-r--r--spec/ruby/library/date/shared/valid_civil.rb36
-rw-r--r--spec/ruby/library/date/shared/valid_commercial.rb34
-rw-r--r--spec/ruby/library/date/shared/valid_jd.rb20
-rw-r--r--spec/ruby/library/date/shared/valid_ordinal.rb26
-rw-r--r--spec/ruby/library/date/start_spec.rb6
-rw-r--r--spec/ruby/library/date/step_spec.rb56
-rw-r--r--spec/ruby/library/date/strftime_spec.rb41
-rw-r--r--spec/ruby/library/date/strptime_spec.rb149
-rw-r--r--spec/ruby/library/date/succ_spec.rb6
-rw-r--r--spec/ruby/library/date/sunday_spec.rb8
-rw-r--r--spec/ruby/library/date/thursday_spec.rb8
-rw-r--r--spec/ruby/library/date/time/to_date_spec.rb42
-rw-r--r--spec/ruby/library/date/time_to_day_fraction_spec.rb6
-rw-r--r--spec/ruby/library/date/to_s_spec.rb6
-rw-r--r--spec/ruby/library/date/today_spec.rb14
-rw-r--r--spec/ruby/library/date/tuesday_spec.rb8
-rw-r--r--spec/ruby/library/date/upto_spec.rb16
-rw-r--r--spec/ruby/library/date/valid_civil_spec.rb9
-rw-r--r--spec/ruby/library/date/valid_commercial_spec.rb8
-rw-r--r--spec/ruby/library/date/valid_date_spec.rb7
-rw-r--r--spec/ruby/library/date/valid_jd_spec.rb9
-rw-r--r--spec/ruby/library/date/valid_ordinal_spec.rb9
-rw-r--r--spec/ruby/library/date/valid_time_spec.rb6
-rw-r--r--spec/ruby/library/date/wday_spec.rb9
-rw-r--r--spec/ruby/library/date/wednesday_spec.rb8
-rw-r--r--spec/ruby/library/date/yday_spec.rb7
-rw-r--r--spec/ruby/library/date/year_spec.rb9
-rw-r--r--spec/ruby/library/date/zone_to_diff_spec.rb6
-rw-r--r--spec/ruby/library/datetime/_strptime_spec.rb6
-rw-r--r--spec/ruby/library/datetime/add_spec.rb9
-rw-r--r--spec/ruby/library/datetime/civil_spec.rb6
-rw-r--r--spec/ruby/library/datetime/commercial_spec.rb6
-rw-r--r--spec/ruby/library/datetime/deconstruct_keys_spec.rb44
-rw-r--r--spec/ruby/library/datetime/hour_spec.rb47
-rw-r--r--spec/ruby/library/datetime/httpdate_spec.rb6
-rw-r--r--spec/ruby/library/datetime/iso8601_spec.rb10
-rw-r--r--spec/ruby/library/datetime/jd_spec.rb6
-rw-r--r--spec/ruby/library/datetime/jisx0301_spec.rb10
-rw-r--r--spec/ruby/library/datetime/min_spec.rb6
-rw-r--r--spec/ruby/library/datetime/minute_spec.rb6
-rw-r--r--spec/ruby/library/datetime/new_offset_spec.rb6
-rw-r--r--spec/ruby/library/datetime/new_spec.rb52
-rw-r--r--spec/ruby/library/datetime/now_spec.rb25
-rw-r--r--spec/ruby/library/datetime/offset_spec.rb6
-rw-r--r--spec/ruby/library/datetime/ordinal_spec.rb6
-rw-r--r--spec/ruby/library/datetime/parse_spec.rb127
-rw-r--r--spec/ruby/library/datetime/rfc2822_spec.rb10
-rw-r--r--spec/ruby/library/datetime/rfc3339_spec.rb10
-rw-r--r--spec/ruby/library/datetime/rfc822_spec.rb6
-rw-r--r--spec/ruby/library/datetime/sec_fraction_spec.rb6
-rw-r--r--spec/ruby/library/datetime/sec_spec.rb6
-rw-r--r--spec/ruby/library/datetime/second_fraction_spec.rb6
-rw-r--r--spec/ruby/library/datetime/second_spec.rb6
-rw-r--r--spec/ruby/library/datetime/shared/min.rb40
-rw-r--r--spec/ruby/library/datetime/shared/sec.rb45
-rw-r--r--spec/ruby/library/datetime/strftime_spec.rb52
-rw-r--r--spec/ruby/library/datetime/strptime_spec.rb6
-rw-r--r--spec/ruby/library/datetime/subtract_spec.rb19
-rw-r--r--spec/ruby/library/datetime/time/to_datetime_spec.rb40
-rw-r--r--spec/ruby/library/datetime/to_date_spec.rb37
-rw-r--r--spec/ruby/library/datetime/to_datetime_spec.rb9
-rw-r--r--spec/ruby/library/datetime/to_s_spec.rb17
-rw-r--r--spec/ruby/library/datetime/to_time_spec.rb48
-rw-r--r--spec/ruby/library/datetime/xmlschema_spec.rb10
-rw-r--r--spec/ruby/library/datetime/yday_spec.rb7
-rw-r--r--spec/ruby/library/datetime/zone_spec.rb6
-rw-r--r--spec/ruby/library/delegate/delegate_class/instance_method_spec.rb52
-rw-r--r--spec/ruby/library/delegate/delegate_class/instance_methods_spec.rb26
-rw-r--r--spec/ruby/library/delegate/delegate_class/private_instance_methods_spec.rb23
-rw-r--r--spec/ruby/library/delegate/delegate_class/protected_instance_methods_spec.rb29
-rw-r--r--spec/ruby/library/delegate/delegate_class/public_instance_methods_spec.rb25
-rw-r--r--spec/ruby/library/delegate/delegate_class/respond_to_missing_spec.rb24
-rw-r--r--spec/ruby/library/delegate/delegator/case_compare_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/compare_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/complement_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/eql_spec.rb35
-rw-r--r--spec/ruby/library/delegate/delegator/equal_spec.rb13
-rw-r--r--spec/ruby/library/delegate/delegator/equal_value_spec.rb24
-rw-r--r--spec/ruby/library/delegate/delegator/frozen_spec.rb39
-rw-r--r--spec/ruby/library/delegate/delegator/hash_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/marshal_spec.rb21
-rw-r--r--spec/ruby/library/delegate/delegator/method_spec.rb69
-rw-r--r--spec/ruby/library/delegate/delegator/methods_spec.rb37
-rw-r--r--spec/ruby/library/delegate/delegator/not_equal_spec.rb24
-rw-r--r--spec/ruby/library/delegate/delegator/not_spec.rb11
-rw-r--r--spec/ruby/library/delegate/delegator/private_methods_spec.rb20
-rw-r--r--spec/ruby/library/delegate/delegator/protected_methods_spec.rb18
-rw-r--r--spec/ruby/library/delegate/delegator/public_methods_spec.rb18
-rw-r--r--spec/ruby/library/delegate/delegator/send_spec.rb26
-rw-r--r--spec/ruby/library/delegate/delegator/taint_spec.rb8
-rw-r--r--spec/ruby/library/delegate/delegator/tap_spec.rb16
-rw-r--r--spec/ruby/library/delegate/delegator/trust_spec.rb8
-rw-r--r--spec/ruby/library/delegate/delegator/untaint_spec.rb8
-rw-r--r--spec/ruby/library/delegate/delegator/untrust_spec.rb8
-rw-r--r--spec/ruby/library/delegate/fixtures/classes.rb60
-rw-r--r--spec/ruby/library/digest/bubblebabble_spec.rb29
-rw-r--r--spec/ruby/library/digest/hexencode_spec.rb31
-rw-r--r--spec/ruby/library/digest/instance/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/instance/new_spec.rb19
-rw-r--r--spec/ruby/library/digest/instance/shared/update.rb8
-rw-r--r--spec/ruby/library/digest/instance/update_spec.rb7
-rw-r--r--spec/ruby/library/digest/md5/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/md5/block_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/md5/digest_bang_spec.rb13
-rw-r--r--spec/ruby/library/digest/md5/digest_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/md5/digest_spec.rb32
-rw-r--r--spec/ruby/library/digest/md5/equal_spec.rb37
-rw-r--r--spec/ruby/library/digest/md5/file_spec.rb43
-rw-r--r--spec/ruby/library/digest/md5/hexdigest_bang_spec.rb14
-rw-r--r--spec/ruby/library/digest/md5/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/md5/inspect_spec.rb11
-rw-r--r--spec/ruby/library/digest/md5/length_spec.rb7
-rw-r--r--spec/ruby/library/digest/md5/reset_spec.rb14
-rw-r--r--spec/ruby/library/digest/md5/shared/constants.rb17
-rw-r--r--spec/ruby/library/digest/md5/shared/length.rb8
-rw-r--r--spec/ruby/library/digest/md5/shared/update.rb7
-rw-r--r--spec/ruby/library/digest/md5/size_spec.rb7
-rw-r--r--spec/ruby/library/digest/md5/to_s_spec.rb24
-rw-r--r--spec/ruby/library/digest/md5/update_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha1/digest_spec.rb20
-rw-r--r--spec/ruby/library/digest/sha1/file_spec.rb43
-rw-r--r--spec/ruby/library/digest/sha1/shared/constants.rb18
-rw-r--r--spec/ruby/library/digest/sha2/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha256/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha256/block_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha256/digest_bang_spec.rb13
-rw-r--r--spec/ruby/library/digest/sha256/digest_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha256/digest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha256/equal_spec.rb36
-rw-r--r--spec/ruby/library/digest/sha256/file_spec.rb47
-rw-r--r--spec/ruby/library/digest/sha256/hexdigest_bang_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha256/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha256/inspect_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha256/length_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha256/reset_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha256/shared/constants.rb18
-rw-r--r--spec/ruby/library/digest/sha256/shared/length.rb8
-rw-r--r--spec/ruby/library/digest/sha256/shared/update.rb7
-rw-r--r--spec/ruby/library/digest/sha256/size_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha256/to_s_spec.rb21
-rw-r--r--spec/ruby/library/digest/sha256/update_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha384/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha384/block_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha384/digest_bang_spec.rb13
-rw-r--r--spec/ruby/library/digest/sha384/digest_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha384/digest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha384/equal_spec.rb36
-rw-r--r--spec/ruby/library/digest/sha384/file_spec.rb43
-rw-r--r--spec/ruby/library/digest/sha384/hexdigest_bang_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha384/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha384/inspect_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha384/length_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha384/reset_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha384/shared/constants.rb19
-rw-r--r--spec/ruby/library/digest/sha384/shared/length.rb8
-rw-r--r--spec/ruby/library/digest/sha384/shared/update.rb7
-rw-r--r--spec/ruby/library/digest/sha384/size_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha384/to_s_spec.rb21
-rw-r--r--spec/ruby/library/digest/sha384/update_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha512/append_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha512/block_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha512/digest_bang_spec.rb13
-rw-r--r--spec/ruby/library/digest/sha512/digest_length_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha512/digest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha512/equal_spec.rb36
-rw-r--r--spec/ruby/library/digest/sha512/file_spec.rb43
-rw-r--r--spec/ruby/library/digest/sha512/hexdigest_bang_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha512/hexdigest_spec.rb32
-rw-r--r--spec/ruby/library/digest/sha512/inspect_spec.rb11
-rw-r--r--spec/ruby/library/digest/sha512/length_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha512/reset_spec.rb14
-rw-r--r--spec/ruby/library/digest/sha512/shared/constants.rb18
-rw-r--r--spec/ruby/library/digest/sha512/shared/length.rb8
-rw-r--r--spec/ruby/library/digest/sha512/shared/update.rb7
-rw-r--r--spec/ruby/library/digest/sha512/size_spec.rb7
-rw-r--r--spec/ruby/library/digest/sha512/to_s_spec.rb21
-rw-r--r--spec/ruby/library/digest/sha512/update_spec.rb7
-rw-r--r--spec/ruby/library/drb/fixtures/test_server.rb8
-rw-r--r--spec/ruby/library/drb/start_service_spec.rb33
-rw-r--r--spec/ruby/library/erb/def_class_spec.rb31
-rw-r--r--spec/ruby/library/erb/def_method_spec.rb26
-rw-r--r--spec/ruby/library/erb/def_module_spec.rb30
-rw-r--r--spec/ruby/library/erb/defmethod/def_erb_method_spec.rb66
-rw-r--r--spec/ruby/library/erb/filename_spec.rb40
-rw-r--r--spec/ruby/library/erb/fixtures/classes.rb5
-rw-r--r--spec/ruby/library/erb/new_spec.rb157
-rw-r--r--spec/ruby/library/erb/result_spec.rb86
-rw-r--r--spec/ruby/library/erb/run_spec.rb96
-rw-r--r--spec/ruby/library/erb/src_spec.rb33
-rw-r--r--spec/ruby/library/erb/util/h_spec.rb7
-rw-r--r--spec/ruby/library/erb/util/html_escape_spec.rb7
-rw-r--r--spec/ruby/library/erb/util/shared/html_escape.rb42
-rw-r--r--spec/ruby/library/erb/util/shared/url_encode.rb42
-rw-r--r--spec/ruby/library/erb/util/u_spec.rb7
-rw-r--r--spec/ruby/library/erb/util/url_encode_spec.rb7
-rw-r--r--spec/ruby/library/etc/confstr_spec.rb14
-rw-r--r--spec/ruby/library/etc/endgrent_spec.rb7
-rw-r--r--spec/ruby/library/etc/endpwent_spec.rb7
-rw-r--r--spec/ruby/library/etc/getgrent_spec.rb7
-rw-r--r--spec/ruby/library/etc/getgrgid_spec.rb69
-rw-r--r--spec/ruby/library/etc/getgrnam_spec.rb30
-rw-r--r--spec/ruby/library/etc/getlogin_spec.rb43
-rw-r--r--spec/ruby/library/etc/getpwent_spec.rb7
-rw-r--r--spec/ruby/library/etc/getpwnam_spec.rb28
-rw-r--r--spec/ruby/library/etc/getpwuid_spec.rb36
-rw-r--r--spec/ruby/library/etc/group_spec.rb27
-rw-r--r--spec/ruby/library/etc/nprocessors_spec.rb9
-rw-r--r--spec/ruby/library/etc/passwd_spec.rb15
-rw-r--r--spec/ruby/library/etc/shared/windows.rb7
-rw-r--r--spec/ruby/library/etc/struct_group_spec.rb35
-rw-r--r--spec/ruby/library/etc/struct_passwd_spec.rb43
-rw-r--r--spec/ruby/library/etc/sysconf_spec.rb22
-rw-r--r--spec/ruby/library/etc/sysconfdir_spec.rb8
-rw-r--r--spec/ruby/library/etc/systmpdir_spec.rb8
-rw-r--r--spec/ruby/library/etc/uname_spec.rb14
-rw-r--r--spec/ruby/library/expect/expect_spec.rb63
-rw-r--r--spec/ruby/library/fiddle/handle/initialize_spec.rb10
-rw-r--r--spec/ruby/library/find/find_spec.rb30
-rw-r--r--spec/ruby/library/find/fixtures/common.rb178
-rw-r--r--spec/ruby/library/find/prune_spec.rb12
-rw-r--r--spec/ruby/library/getoptlong/each_option_spec.rb7
-rw-r--r--spec/ruby/library/getoptlong/each_spec.rb7
-rw-r--r--spec/ruby/library/getoptlong/error_message_spec.rb23
-rw-r--r--spec/ruby/library/getoptlong/get_option_spec.rb7
-rw-r--r--spec/ruby/library/getoptlong/get_spec.rb7
-rw-r--r--spec/ruby/library/getoptlong/initialize_spec.rb28
-rw-r--r--spec/ruby/library/getoptlong/ordering_spec.rb38
-rw-r--r--spec/ruby/library/getoptlong/set_options_spec.rb98
-rw-r--r--spec/ruby/library/getoptlong/shared/each.rb18
-rw-r--r--spec/ruby/library/getoptlong/shared/get.rb62
-rw-r--r--spec/ruby/library/getoptlong/terminate_spec.rb30
-rw-r--r--spec/ruby/library/getoptlong/terminated_spec.rb17
-rw-r--r--spec/ruby/library/io-wait/wait_readable_spec.rb42
-rw-r--r--spec/ruby/library/io-wait/wait_spec.rb162
-rw-r--r--spec/ruby/library/io-wait/wait_writable_spec.rb37
-rw-r--r--spec/ruby/library/ipaddr/hton_spec.rb30
-rw-r--r--spec/ruby/library/ipaddr/ipv4_conversion_spec.rb44
-rw-r--r--spec/ruby/library/ipaddr/new_spec.rb92
-rw-r--r--spec/ruby/library/ipaddr/operator_spec.rb82
-rw-r--r--spec/ruby/library/ipaddr/reverse_spec.rb27
-rw-r--r--spec/ruby/library/ipaddr/to_s_spec.rb20
-rw-r--r--spec/ruby/library/irb/fixtures/irb.rb3
-rw-r--r--spec/ruby/library/irb/irb_spec.rb19
-rw-r--r--spec/ruby/library/logger/device/close_spec.rb22
-rw-r--r--spec/ruby/library/logger/device/new_spec.rb47
-rw-r--r--spec/ruby/library/logger/device/write_spec.rb42
-rw-r--r--spec/ruby/library/logger/fixtures/common.rb9
-rw-r--r--spec/ruby/library/logger/logger/add_spec.rb81
-rw-r--r--spec/ruby/library/logger/logger/close_spec.rb20
-rw-r--r--spec/ruby/library/logger/logger/datetime_format_spec.rb60
-rw-r--r--spec/ruby/library/logger/logger/debug_spec.rb52
-rw-r--r--spec/ruby/library/logger/logger/error_spec.rb53
-rw-r--r--spec/ruby/library/logger/logger/fatal_spec.rb53
-rw-r--r--spec/ruby/library/logger/logger/info_spec.rb53
-rw-r--r--spec/ruby/library/logger/logger/new_spec.rb118
-rw-r--r--spec/ruby/library/logger/logger/unknown_spec.rb36
-rw-r--r--spec/ruby/library/logger/logger/warn_spec.rb53
-rw-r--r--spec/ruby/library/logger/severity_spec.rb13
-rw-r--r--spec/ruby/library/matrix/I_spec.rb6
-rw-r--r--spec/ruby/library/matrix/antisymmetric_spec.rb36
-rw-r--r--spec/ruby/library/matrix/build_spec.rb73
-rw-r--r--spec/ruby/library/matrix/clone_spec.rb25
-rw-r--r--spec/ruby/library/matrix/coerce_spec.rb8
-rw-r--r--spec/ruby/library/matrix/collect_spec.rb6
-rw-r--r--spec/ruby/library/matrix/column_size_spec.rb13
-rw-r--r--spec/ruby/library/matrix/column_spec.rb35
-rw-r--r--spec/ruby/library/matrix/column_vector_spec.rb25
-rw-r--r--spec/ruby/library/matrix/column_vectors_spec.rb26
-rw-r--r--spec/ruby/library/matrix/columns_spec.rb42
-rw-r--r--spec/ruby/library/matrix/conj_spec.rb6
-rw-r--r--spec/ruby/library/matrix/conjugate_spec.rb6
-rw-r--r--spec/ruby/library/matrix/constructor_spec.rb63
-rw-r--r--spec/ruby/library/matrix/det_spec.rb7
-rw-r--r--spec/ruby/library/matrix/determinant_spec.rb7
-rw-r--r--spec/ruby/library/matrix/diagonal_spec.rb72
-rw-r--r--spec/ruby/library/matrix/divide_spec.rb54
-rw-r--r--spec/ruby/library/matrix/each_spec.rb74
-rw-r--r--spec/ruby/library/matrix/each_with_index_spec.rb81
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalue_matrix_spec.rb9
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/eigenvalues_spec.rb22
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/eigenvector_matrix_spec.rb20
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/eigenvectors_spec.rb22
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/initialize_spec.rb24
-rw-r--r--spec/ruby/library/matrix/eigenvalue_decomposition/to_a_spec.rb18
-rw-r--r--spec/ruby/library/matrix/element_reference_spec.rb23
-rw-r--r--spec/ruby/library/matrix/empty_spec.rb68
-rw-r--r--spec/ruby/library/matrix/eql_spec.rb11
-rw-r--r--spec/ruby/library/matrix/equal_value_spec.rb11
-rw-r--r--spec/ruby/library/matrix/exponent_spec.rb62
-rw-r--r--spec/ruby/library/matrix/find_index_spec.rb146
-rw-r--r--spec/ruby/library/matrix/fixtures/classes.rb7
-rw-r--r--spec/ruby/library/matrix/hash_spec.rb15
-rw-r--r--spec/ruby/library/matrix/hermitian_spec.rb34
-rw-r--r--spec/ruby/library/matrix/identity_spec.rb6
-rw-r--r--spec/ruby/library/matrix/imag_spec.rb6
-rw-r--r--spec/ruby/library/matrix/imaginary_spec.rb6
-rw-r--r--spec/ruby/library/matrix/inspect_spec.rb27
-rw-r--r--spec/ruby/library/matrix/inv_spec.rb7
-rw-r--r--spec/ruby/library/matrix/inverse_from_spec.rb6
-rw-r--r--spec/ruby/library/matrix/inverse_spec.rb7
-rw-r--r--spec/ruby/library/matrix/lower_triangular_spec.rb24
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/determinant_spec.rb21
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/initialize_spec.rb13
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/l_spec.rb18
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/p_spec.rb18
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/solve_spec.rb53
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/to_a_spec.rb33
-rw-r--r--spec/ruby/library/matrix/lup_decomposition/u_spec.rb18
-rw-r--r--spec/ruby/library/matrix/map_spec.rb6
-rw-r--r--spec/ruby/library/matrix/minor_spec.rb85
-rw-r--r--spec/ruby/library/matrix/minus_spec.rb42
-rw-r--r--spec/ruby/library/matrix/multiply_spec.rb69
-rw-r--r--spec/ruby/library/matrix/new_spec.rb8
-rw-r--r--spec/ruby/library/matrix/normal_spec.rb26
-rw-r--r--spec/ruby/library/matrix/orthogonal_spec.rb26
-rw-r--r--spec/ruby/library/matrix/permutation_spec.rb32
-rw-r--r--spec/ruby/library/matrix/plus_spec.rb42
-rw-r--r--spec/ruby/library/matrix/rank_spec.rb19
-rw-r--r--spec/ruby/library/matrix/real_spec.rb43
-rw-r--r--spec/ruby/library/matrix/rect_spec.rb6
-rw-r--r--spec/ruby/library/matrix/rectangular_spec.rb6
-rw-r--r--spec/ruby/library/matrix/regular_spec.rb31
-rw-r--r--spec/ruby/library/matrix/round_spec.rb21
-rw-r--r--spec/ruby/library/matrix/row_size_spec.rb13
-rw-r--r--spec/ruby/library/matrix/row_spec.rb36
-rw-r--r--spec/ruby/library/matrix/row_vector_spec.rb24
-rw-r--r--spec/ruby/library/matrix/row_vectors_spec.rb26
-rw-r--r--spec/ruby/library/matrix/rows_spec.rb41
-rw-r--r--spec/ruby/library/matrix/scalar/Fail_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar/Raise_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar/divide_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar/exponent_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar/included_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar/initialize_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar/minus_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar/multiply_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar/plus_spec.rb6
-rw-r--r--spec/ruby/library/matrix/scalar_spec.rb67
-rw-r--r--spec/ruby/library/matrix/shared/collect.rb26
-rw-r--r--spec/ruby/library/matrix/shared/conjugate.rb20
-rw-r--r--spec/ruby/library/matrix/shared/determinant.rb38
-rw-r--r--spec/ruby/library/matrix/shared/equal_value.rb33
-rw-r--r--spec/ruby/library/matrix/shared/identity.rb19
-rw-r--r--spec/ruby/library/matrix/shared/imaginary.rb20
-rw-r--r--spec/ruby/library/matrix/shared/inverse.rb38
-rw-r--r--spec/ruby/library/matrix/shared/rectangular.rb18
-rw-r--r--spec/ruby/library/matrix/shared/trace.rb12
-rw-r--r--spec/ruby/library/matrix/shared/transpose.rb19
-rw-r--r--spec/ruby/library/matrix/singular_spec.rb31
-rw-r--r--spec/ruby/library/matrix/spec_helper.rb35
-rw-r--r--spec/ruby/library/matrix/square_spec.rb28
-rw-r--r--spec/ruby/library/matrix/symmetric_spec.rb29
-rw-r--r--spec/ruby/library/matrix/t_spec.rb6
-rw-r--r--spec/ruby/library/matrix/to_a_spec.rb11
-rw-r--r--spec/ruby/library/matrix/to_s_spec.rb6
-rw-r--r--spec/ruby/library/matrix/tr_spec.rb7
-rw-r--r--spec/ruby/library/matrix/trace_spec.rb7
-rw-r--r--spec/ruby/library/matrix/transpose_spec.rb6
-rw-r--r--spec/ruby/library/matrix/unit_spec.rb6
-rw-r--r--spec/ruby/library/matrix/unitary_spec.rb32
-rw-r--r--spec/ruby/library/matrix/upper_triangular_spec.rb24
-rw-r--r--spec/ruby/library/matrix/vector/cross_product_spec.rb14
-rw-r--r--spec/ruby/library/matrix/vector/each2_spec.rb49
-rw-r--r--spec/ruby/library/matrix/vector/eql_spec.rb16
-rw-r--r--spec/ruby/library/matrix/vector/inner_product_spec.rb22
-rw-r--r--spec/ruby/library/matrix/vector/normalize_spec.rb18
-rw-r--r--spec/ruby/library/matrix/zero_spec.rb52
-rw-r--r--spec/ruby/library/mkmf/mkmf_spec.rb7
-rw-r--r--spec/ruby/library/monitor/enter_spec.rb28
-rw-r--r--spec/ruby/library/monitor/exit_spec.rb10
-rw-r--r--spec/ruby/library/monitor/mon_initialize_spec.rb31
-rw-r--r--spec/ruby/library/monitor/new_cond_spec.rb88
-rw-r--r--spec/ruby/library/monitor/synchronize_spec.rb41
-rw-r--r--spec/ruby/library/monitor/try_enter_spec.rb39
-rw-r--r--spec/ruby/library/net-ftp/FTPError_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/FTPPermError_spec.rb15
-rw-r--r--spec/ruby/library/net-ftp/FTPProtoError_spec.rb15
-rw-r--r--spec/ruby/library/net-ftp/FTPReplyError_spec.rb15
-rw-r--r--spec/ruby/library/net-ftp/FTPTempError_spec.rb15
-rw-r--r--spec/ruby/library/net-ftp/abort_spec.rb65
-rw-r--r--spec/ruby/library/net-ftp/acct_spec.rb61
-rw-r--r--spec/ruby/library/net-ftp/binary_spec.rb27
-rw-r--r--spec/ruby/library/net-ftp/chdir_spec.rb102
-rw-r--r--spec/ruby/library/net-ftp/close_spec.rb33
-rw-r--r--spec/ruby/library/net-ftp/closed_spec.rb24
-rw-r--r--spec/ruby/library/net-ftp/connect_spec.rb46
-rw-r--r--spec/ruby/library/net-ftp/debug_mode_spec.rb26
-rw-r--r--spec/ruby/library/net-ftp/default_passive_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/delete_spec.rb62
-rw-r--r--spec/ruby/library/net-ftp/dir_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/fixtures/default_passive.rb3
-rw-r--r--spec/ruby/library/net-ftp/fixtures/passive.rb2
-rw-r--r--spec/ruby/library/net-ftp/fixtures/putbinaryfile3
-rw-r--r--spec/ruby/library/net-ftp/fixtures/puttextfile3
-rw-r--r--spec/ruby/library/net-ftp/fixtures/server.rb279
-rw-r--r--spec/ruby/library/net-ftp/get_spec.rb24
-rw-r--r--spec/ruby/library/net-ftp/getbinaryfile_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/getdir_spec.rb10
-rw-r--r--spec/ruby/library/net-ftp/gettextfile_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/help_spec.rb69
-rw-r--r--spec/ruby/library/net-ftp/initialize_spec.rb408
-rw-r--r--spec/ruby/library/net-ftp/last_response_code_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/last_response_spec.rb28
-rw-r--r--spec/ruby/library/net-ftp/lastresp_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/list_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/login_spec.rb198
-rw-r--r--spec/ruby/library/net-ftp/ls_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/mdtm_spec.rb41
-rw-r--r--spec/ruby/library/net-ftp/mkdir_spec.rb64
-rw-r--r--spec/ruby/library/net-ftp/mtime_spec.rb53
-rw-r--r--spec/ruby/library/net-ftp/nlst_spec.rb95
-rw-r--r--spec/ruby/library/net-ftp/noop_spec.rb41
-rw-r--r--spec/ruby/library/net-ftp/open_spec.rb58
-rw-r--r--spec/ruby/library/net-ftp/passive_spec.rb31
-rw-r--r--spec/ruby/library/net-ftp/put_spec.rb24
-rw-r--r--spec/ruby/library/net-ftp/putbinaryfile_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/puttextfile_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/pwd_spec.rb56
-rw-r--r--spec/ruby/library/net-ftp/quit_spec.rb36
-rw-r--r--spec/ruby/library/net-ftp/rename_spec.rb97
-rw-r--r--spec/ruby/library/net-ftp/resume_spec.rb26
-rw-r--r--spec/ruby/library/net-ftp/retrbinary_spec.rb33
-rw-r--r--spec/ruby/library/net-ftp/retrlines_spec.rb37
-rw-r--r--spec/ruby/library/net-ftp/return_code_spec.rb27
-rw-r--r--spec/ruby/library/net-ftp/rmdir_spec.rb61
-rw-r--r--spec/ruby/library/net-ftp/sendcmd_spec.rb57
-rw-r--r--spec/ruby/library/net-ftp/set_socket_spec.rb11
-rw-r--r--spec/ruby/library/net-ftp/shared/getbinaryfile.rb152
-rw-r--r--spec/ruby/library/net-ftp/shared/gettextfile.rb102
-rw-r--r--spec/ruby/library/net-ftp/shared/last_response_code.rb27
-rw-r--r--spec/ruby/library/net-ftp/shared/list.rb106
-rw-r--r--spec/ruby/library/net-ftp/shared/putbinaryfile.rb169
-rw-r--r--spec/ruby/library/net-ftp/shared/puttextfile.rb130
-rw-r--r--spec/ruby/library/net-ftp/shared/pwd.rb5
-rw-r--r--spec/ruby/library/net-ftp/site_spec.rb56
-rw-r--r--spec/ruby/library/net-ftp/size_spec.rb51
-rw-r--r--spec/ruby/library/net-ftp/spec_helper.rb7
-rw-r--r--spec/ruby/library/net-ftp/status_spec.rb70
-rw-r--r--spec/ruby/library/net-ftp/storbinary_spec.rb52
-rw-r--r--spec/ruby/library/net-ftp/storlines_spec.rb47
-rw-r--r--spec/ruby/library/net-ftp/system_spec.rb51
-rw-r--r--spec/ruby/library/net-ftp/voidcmd_spec.rb57
-rw-r--r--spec/ruby/library/net-ftp/welcome_spec.rb28
-rw-r--r--spec/ruby/library/net-http/HTTPBadResponse_spec.rb8
-rw-r--r--spec/ruby/library/net-http/HTTPClientExcepton_spec.rb12
-rw-r--r--spec/ruby/library/net-http/HTTPError_spec.rb12
-rw-r--r--spec/ruby/library/net-http/HTTPFatalError_spec.rb12
-rw-r--r--spec/ruby/library/net-http/HTTPHeaderSyntaxError_spec.rb8
-rw-r--r--spec/ruby/library/net-http/HTTPRetriableError_spec.rb12
-rw-r--r--spec/ruby/library/net-http/HTTPServerException_spec.rb12
-rw-r--r--spec/ruby/library/net-http/http/Proxy_spec.rb35
-rw-r--r--spec/ruby/library/net-http/http/active_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/address_spec.rb9
-rw-r--r--spec/ruby/library/net-http/http/close_on_empty_response_spec.rb10
-rw-r--r--spec/ruby/library/net-http/http/copy_spec.rb21
-rw-r--r--spec/ruby/library/net-http/http/default_port_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/delete_spec.rb21
-rw-r--r--spec/ruby/library/net-http/http/finish_spec.rb29
-rw-r--r--spec/ruby/library/net-http/http/fixtures/http_server.rb123
-rw-r--r--spec/ruby/library/net-http/http/get2_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/get_print_spec.rb30
-rw-r--r--spec/ruby/library/net-http/http/get_response_spec.rb30
-rw-r--r--spec/ruby/library/net-http/http/get_spec.rb94
-rw-r--r--spec/ruby/library/net-http/http/head2_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/head_spec.rb25
-rw-r--r--spec/ruby/library/net-http/http/http_default_port_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/https_default_port_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/initialize_spec.rb46
-rw-r--r--spec/ruby/library/net-http/http/inspect_spec.rb24
-rw-r--r--spec/ruby/library/net-http/http/is_version_1_1_spec.rb7
-rw-r--r--spec/ruby/library/net-http/http/is_version_1_2_spec.rb7
-rw-r--r--spec/ruby/library/net-http/http/lock_spec.rb21
-rw-r--r--spec/ruby/library/net-http/http/mkcol_spec.rb21
-rw-r--r--spec/ruby/library/net-http/http/move_spec.rb25
-rw-r--r--spec/ruby/library/net-http/http/new_spec.rb86
-rw-r--r--spec/ruby/library/net-http/http/newobj_spec.rb48
-rw-r--r--spec/ruby/library/net-http/http/open_timeout_spec.rb24
-rw-r--r--spec/ruby/library/net-http/http/options_spec.rb25
-rw-r--r--spec/ruby/library/net-http/http/port_spec.rb9
-rw-r--r--spec/ruby/library/net-http/http/post2_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/post_form_spec.rb22
-rw-r--r--spec/ruby/library/net-http/http/post_spec.rb76
-rw-r--r--spec/ruby/library/net-http/http/propfind_spec.rb24
-rw-r--r--spec/ruby/library/net-http/http/proppatch_spec.rb24
-rw-r--r--spec/ruby/library/net-http/http/proxy_address_spec.rb31
-rw-r--r--spec/ruby/library/net-http/http/proxy_class_spec.rb9
-rw-r--r--spec/ruby/library/net-http/http/proxy_pass_spec.rb39
-rw-r--r--spec/ruby/library/net-http/http/proxy_port_spec.rb39
-rw-r--r--spec/ruby/library/net-http/http/proxy_user_spec.rb39
-rw-r--r--spec/ruby/library/net-http/http/put2_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/put_spec.rb24
-rw-r--r--spec/ruby/library/net-http/http/read_timeout_spec.rb24
-rw-r--r--spec/ruby/library/net-http/http/request_get_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/request_head_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/request_post_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/request_put_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/request_spec.rb109
-rw-r--r--spec/ruby/library/net-http/http/request_types_spec.rb254
-rw-r--r--spec/ruby/library/net-http/http/send_request_spec.rb61
-rw-r--r--spec/ruby/library/net-http/http/set_debug_output_spec.rb33
-rw-r--r--spec/ruby/library/net-http/http/shared/request_get.rb41
-rw-r--r--spec/ruby/library/net-http/http/shared/request_head.rb41
-rw-r--r--spec/ruby/library/net-http/http/shared/request_post.rb41
-rw-r--r--spec/ruby/library/net-http/http/shared/request_put.rb41
-rw-r--r--spec/ruby/library/net-http/http/shared/started.rb26
-rw-r--r--spec/ruby/library/net-http/http/shared/version_1_1.rb6
-rw-r--r--spec/ruby/library/net-http/http/shared/version_1_2.rb6
-rw-r--r--spec/ruby/library/net-http/http/socket_type_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/start_spec.rb111
-rw-r--r--spec/ruby/library/net-http/http/started_spec.rb8
-rw-r--r--spec/ruby/library/net-http/http/trace_spec.rb24
-rw-r--r--spec/ruby/library/net-http/http/unlock_spec.rb24
-rw-r--r--spec/ruby/library/net-http/http/use_ssl_spec.rb9
-rw-r--r--spec/ruby/library/net-http/http/version_1_1_spec.rb7
-rw-r--r--spec/ruby/library/net-http/http/version_1_2_spec.rb20
-rw-r--r--spec/ruby/library/net-http/httpexceptions/fixtures/classes.rb5
-rw-r--r--spec/ruby/library/net-http/httpexceptions/initialize_spec.rb17
-rw-r--r--spec/ruby/library/net-http/httpexceptions/response_spec.rb10
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/body_exist_spec.rb21
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/body_spec.rb30
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/body_stream_spec.rb32
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb135
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/inspect_spec.rb25
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/method_spec.rb15
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/path_spec.rb12
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/request_body_permitted_spec.rb12
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/response_body_permitted_spec.rb12
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/set_body_internal_spec.rb21
-rw-r--r--spec/ruby/library/net-http/httpheader/add_field_spec.rb31
-rw-r--r--spec/ruby/library/net-http/httpheader/basic_auth_spec.rb14
-rw-r--r--spec/ruby/library/net-http/httpheader/canonical_each_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/chunked_spec.rb22
-rw-r--r--spec/ruby/library/net-http/httpheader/content_length_spec.rb54
-rw-r--r--spec/ruby/library/net-http/httpheader/content_range_spec.rb32
-rw-r--r--spec/ruby/library/net-http/httpheader/content_type_spec.rb26
-rw-r--r--spec/ruby/library/net-http/httpheader/delete_spec.rb30
-rw-r--r--spec/ruby/library/net-http/httpheader/each_capitalized_name_spec.rb35
-rw-r--r--spec/ruby/library/net-http/httpheader/each_capitalized_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/each_header_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/each_key_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/each_name_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/each_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/each_value_spec.rb35
-rw-r--r--spec/ruby/library/net-http/httpheader/element_reference_spec.rb39
-rw-r--r--spec/ruby/library/net-http/httpheader/element_set_spec.rb41
-rw-r--r--spec/ruby/library/net-http/httpheader/fetch_spec.rb68
-rw-r--r--spec/ruby/library/net-http/httpheader/fixtures/classes.rb11
-rw-r--r--spec/ruby/library/net-http/httpheader/form_data_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/get_fields_spec.rb39
-rw-r--r--spec/ruby/library/net-http/httpheader/initialize_http_header_spec.rb21
-rw-r--r--spec/ruby/library/net-http/httpheader/key_spec.rb21
-rw-r--r--spec/ruby/library/net-http/httpheader/length_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/main_type_spec.rb24
-rw-r--r--spec/ruby/library/net-http/httpheader/proxy_basic_auth_spec.rb14
-rw-r--r--spec/ruby/library/net-http/httpheader/range_length_spec.rb32
-rw-r--r--spec/ruby/library/net-http/httpheader/range_spec.rb48
-rw-r--r--spec/ruby/library/net-http/httpheader/set_content_type_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/set_form_data_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/set_range_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/shared/each_capitalized.rb31
-rw-r--r--spec/ruby/library/net-http/httpheader/shared/each_header.rb31
-rw-r--r--spec/ruby/library/net-http/httpheader/shared/each_name.rb31
-rw-r--r--spec/ruby/library/net-http/httpheader/shared/set_content_type.rb18
-rw-r--r--spec/ruby/library/net-http/httpheader/shared/set_form_data.rb27
-rw-r--r--spec/ruby/library/net-http/httpheader/shared/set_range.rb89
-rw-r--r--spec/ruby/library/net-http/httpheader/shared/size.rb18
-rw-r--r--spec/ruby/library/net-http/httpheader/size_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpheader/sub_type_spec.rb32
-rw-r--r--spec/ruby/library/net-http/httpheader/to_hash_spec.rb25
-rw-r--r--spec/ruby/library/net-http/httpheader/type_params_spec.rb24
-rw-r--r--spec/ruby/library/net-http/httprequest/initialize_spec.rb45
-rw-r--r--spec/ruby/library/net-http/httpresponse/body_permitted_spec.rb13
-rw-r--r--spec/ruby/library/net-http/httpresponse/body_spec.rb7
-rw-r--r--spec/ruby/library/net-http/httpresponse/code_spec.rb24
-rw-r--r--spec/ruby/library/net-http/httpresponse/code_type_spec.rb24
-rw-r--r--spec/ruby/library/net-http/httpresponse/entity_spec.rb7
-rw-r--r--spec/ruby/library/net-http/httpresponse/error_spec.rb24
-rw-r--r--spec/ruby/library/net-http/httpresponse/error_type_spec.rb24
-rw-r--r--spec/ruby/library/net-http/httpresponse/exception_type_spec.rb13
-rw-r--r--spec/ruby/library/net-http/httpresponse/header_spec.rb9
-rw-r--r--spec/ruby/library/net-http/httpresponse/http_version_spec.rb12
-rw-r--r--spec/ruby/library/net-http/httpresponse/initialize_spec.rb11
-rw-r--r--spec/ruby/library/net-http/httpresponse/inspect_spec.rb15
-rw-r--r--spec/ruby/library/net-http/httpresponse/message_spec.rb9
-rw-r--r--spec/ruby/library/net-http/httpresponse/msg_spec.rb9
-rw-r--r--spec/ruby/library/net-http/httpresponse/read_body_spec.rb86
-rw-r--r--spec/ruby/library/net-http/httpresponse/read_header_spec.rb9
-rw-r--r--spec/ruby/library/net-http/httpresponse/read_new_spec.rb23
-rw-r--r--spec/ruby/library/net-http/httpresponse/reading_body_spec.rb58
-rw-r--r--spec/ruby/library/net-http/httpresponse/response_spec.rb9
-rw-r--r--spec/ruby/library/net-http/httpresponse/shared/body.rb20
-rw-r--r--spec/ruby/library/net-http/httpresponse/value_spec.rb24
-rw-r--r--spec/ruby/library/objectspace/dump_all_spec.rb112
-rw-r--r--spec/ruby/library/objectspace/dump_spec.rb70
-rw-r--r--spec/ruby/library/objectspace/fixtures/trace.rb6
-rw-r--r--spec/ruby/library/objectspace/memsize_of_all_spec.rb22
-rw-r--r--spec/ruby/library/objectspace/memsize_of_spec.rb34
-rw-r--r--spec/ruby/library/objectspace/reachable_objects_from_spec.rb59
-rw-r--r--spec/ruby/library/objectspace/trace_object_allocations_spec.rb163
-rw-r--r--spec/ruby/library/objectspace/trace_spec.rb13
-rw-r--r--spec/ruby/library/observer/add_observer_spec.rb23
-rw-r--r--spec/ruby/library/observer/count_observers_spec.rb23
-rw-r--r--spec/ruby/library/observer/delete_observer_spec.rb19
-rw-r--r--spec/ruby/library/observer/delete_observers_spec.rb19
-rw-r--r--spec/ruby/library/observer/fixtures/classes.rb17
-rw-r--r--spec/ruby/library/observer/notify_observers_spec.rb31
-rw-r--r--spec/ruby/library/open3/capture2_spec.rb6
-rw-r--r--spec/ruby/library/open3/capture2e_spec.rb6
-rw-r--r--spec/ruby/library/open3/capture3_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_r_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_rw_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_start_spec.rb6
-rw-r--r--spec/ruby/library/open3/pipeline_w_spec.rb6
-rw-r--r--spec/ruby/library/open3/popen2_spec.rb6
-rw-r--r--spec/ruby/library/open3/popen2e_spec.rb6
-rw-r--r--spec/ruby/library/open3/popen3_spec.rb41
-rw-r--r--spec/ruby/library/openssl/cipher_spec.rb9
-rw-r--r--spec/ruby/library/openssl/digest/append_spec.rb6
-rw-r--r--spec/ruby/library/openssl/digest/block_length_spec.rb44
-rw-r--r--spec/ruby/library/openssl/digest/digest_length_spec.rb44
-rw-r--r--spec/ruby/library/openssl/digest/digest_spec.rb62
-rw-r--r--spec/ruby/library/openssl/digest/initialize_spec.rb137
-rw-r--r--spec/ruby/library/openssl/digest/name_spec.rb16
-rw-r--r--spec/ruby/library/openssl/digest/reset_spec.rb36
-rw-r--r--spec/ruby/library/openssl/digest/shared/update.rb123
-rw-r--r--spec/ruby/library/openssl/digest/update_spec.rb6
-rw-r--r--spec/ruby/library/openssl/fixed_length_secure_compare_spec.rb42
-rw-r--r--spec/ruby/library/openssl/hmac/digest_spec.rb16
-rw-r--r--spec/ruby/library/openssl/hmac/hexdigest_spec.rb16
-rw-r--r--spec/ruby/library/openssl/kdf/pbkdf2_hmac_spec.rb162
-rw-r--r--spec/ruby/library/openssl/kdf/scrypt_spec.rb210
-rw-r--r--spec/ruby/library/openssl/random/pseudo_bytes_spec.rb8
-rw-r--r--spec/ruby/library/openssl/random/random_bytes_spec.rb6
-rw-r--r--spec/ruby/library/openssl/random/shared/random_bytes.rb29
-rw-r--r--spec/ruby/library/openssl/secure_compare_spec.rb38
-rw-r--r--spec/ruby/library/openssl/shared/constants.rb11
-rw-r--r--spec/ruby/library/openssl/x509/name/parse_spec.rb48
-rw-r--r--spec/ruby/library/openssl/x509/store/verify_spec.rb78
-rw-r--r--spec/ruby/library/openstruct/delete_field_spec.rb19
-rw-r--r--spec/ruby/library/openstruct/element_reference_spec.rb13
-rw-r--r--spec/ruby/library/openstruct/element_set_spec.rb13
-rw-r--r--spec/ruby/library/openstruct/equal_value_spec.rb28
-rw-r--r--spec/ruby/library/openstruct/fixtures/classes.rb4
-rw-r--r--spec/ruby/library/openstruct/frozen_spec.rb40
-rw-r--r--spec/ruby/library/openstruct/initialize_spec.rb8
-rw-r--r--spec/ruby/library/openstruct/inspect_spec.rb8
-rw-r--r--spec/ruby/library/openstruct/marshal_dump_spec.rb9
-rw-r--r--spec/ruby/library/openstruct/marshal_load_spec.rb12
-rw-r--r--spec/ruby/library/openstruct/method_missing_spec.rb24
-rw-r--r--spec/ruby/library/openstruct/new_spec.rb20
-rw-r--r--spec/ruby/library/openstruct/shared/inspect.rb20
-rw-r--r--spec/ruby/library/openstruct/to_h_spec.rb68
-rw-r--r--spec/ruby/library/openstruct/to_s_spec.rb8
-rw-r--r--spec/ruby/library/optionparser/order_spec.rb28
-rw-r--r--spec/ruby/library/optionparser/parse_spec.rb28
-rw-r--r--spec/ruby/library/pathname/absolute_spec.rb22
-rw-r--r--spec/ruby/library/pathname/birthtime_spec.rb16
-rw-r--r--spec/ruby/library/pathname/divide_spec.rb6
-rw-r--r--spec/ruby/library/pathname/empty_spec.rb32
-rw-r--r--spec/ruby/library/pathname/equal_value_spec.rb14
-rw-r--r--spec/ruby/library/pathname/glob_spec.rb92
-rw-r--r--spec/ruby/library/pathname/hash_spec.rb14
-rw-r--r--spec/ruby/library/pathname/inspect_spec.rb10
-rw-r--r--spec/ruby/library/pathname/join_spec.rb40
-rw-r--r--spec/ruby/library/pathname/new_spec.rb23
-rw-r--r--spec/ruby/library/pathname/parent_spec.rb18
-rw-r--r--spec/ruby/library/pathname/pathname_spec.rb19
-rw-r--r--spec/ruby/library/pathname/plus_spec.rb6
-rw-r--r--spec/ruby/library/pathname/realdirpath_spec.rb10
-rw-r--r--spec/ruby/library/pathname/realpath_spec.rb10
-rw-r--r--spec/ruby/library/pathname/relative_path_from_spec.rb55
-rw-r--r--spec/ruby/library/pathname/relative_spec.rb22
-rw-r--r--spec/ruby/library/pathname/root_spec.rb26
-rw-r--r--spec/ruby/library/pathname/shared/plus.rb8
-rw-r--r--spec/ruby/library/pathname/sub_spec.rb15
-rw-r--r--spec/ruby/library/pp/pp_spec.rb30
-rw-r--r--spec/ruby/library/prime/each_spec.rb167
-rw-r--r--spec/ruby/library/prime/instance_spec.rb21
-rw-r--r--spec/ruby/library/prime/int_from_prime_division_spec.rb13
-rw-r--r--spec/ruby/library/prime/integer/each_prime_spec.rb13
-rw-r--r--spec/ruby/library/prime/integer/from_prime_division_spec.rb13
-rw-r--r--spec/ruby/library/prime/integer/prime_division_spec.rb19
-rw-r--r--spec/ruby/library/prime/integer/prime_spec.rb17
-rw-r--r--spec/ruby/library/prime/next_spec.rb7
-rw-r--r--spec/ruby/library/prime/prime_division_spec.rb25
-rw-r--r--spec/ruby/library/prime/prime_spec.rb17
-rw-r--r--spec/ruby/library/prime/shared/next.rb8
-rw-r--r--spec/ruby/library/prime/succ_spec.rb7
-rw-r--r--spec/ruby/library/random/formatter/alphanumeric_spec.rb54
-rw-r--r--spec/ruby/library/rbconfig/rbconfig_spec.rb163
-rw-r--r--spec/ruby/library/rbconfig/sizeof/limits_spec.rb40
-rw-r--r--spec/ruby/library/rbconfig/sizeof/sizeof_spec.rb30
-rw-r--r--spec/ruby/library/rbconfig/unicode_emoji_version_spec.rb17
-rw-r--r--spec/ruby/library/rbconfig/unicode_version_spec.rb17
-rw-r--r--spec/ruby/library/readline/basic_quote_characters_spec.rb18
-rw-r--r--spec/ruby/library/readline/basic_word_break_characters_spec.rb16
-rw-r--r--spec/ruby/library/readline/completer_quote_characters_spec.rb16
-rw-r--r--spec/ruby/library/readline/completer_word_break_characters_spec.rb16
-rw-r--r--spec/ruby/library/readline/completion_append_character_spec.rb16
-rw-r--r--spec/ruby/library/readline/completion_case_fold_spec.rb18
-rw-r--r--spec/ruby/library/readline/completion_proc_spec.rb22
-rw-r--r--spec/ruby/library/readline/constants_spec.rb18
-rw-r--r--spec/ruby/library/readline/emacs_editing_mode_spec.rb11
-rw-r--r--spec/ruby/library/readline/filename_quote_characters_spec.rb18
-rw-r--r--spec/ruby/library/readline/history/append_spec.rb28
-rw-r--r--spec/ruby/library/readline/history/delete_at_spec.rb38
-rw-r--r--spec/ruby/library/readline/history/each_spec.rb23
-rw-r--r--spec/ruby/library/readline/history/element_reference_spec.rb35
-rw-r--r--spec/ruby/library/readline/history/element_set_spec.rb35
-rw-r--r--spec/ruby/library/readline/history/empty_spec.rb13
-rw-r--r--spec/ruby/library/readline/history/history_spec.rb9
-rw-r--r--spec/ruby/library/readline/history/length_spec.rb9
-rw-r--r--spec/ruby/library/readline/history/pop_spec.rb23
-rw-r--r--spec/ruby/library/readline/history/push_spec.rb26
-rw-r--r--spec/ruby/library/readline/history/shared/size.rb14
-rw-r--r--spec/ruby/library/readline/history/shift_spec.rb23
-rw-r--r--spec/ruby/library/readline/history/size_spec.rb9
-rw-r--r--spec/ruby/library/readline/history/to_s_spec.rb9
-rw-r--r--spec/ruby/library/readline/readline_spec.rb26
-rw-r--r--spec/ruby/library/readline/spec_helper.rb11
-rw-r--r--spec/ruby/library/readline/vi_editing_mode_spec.rb11
-rw-r--r--spec/ruby/library/resolv/fixtures/hosts1
-rw-r--r--spec/ruby/library/resolv/get_address_spec.rb19
-rw-r--r--spec/ruby/library/resolv/get_addresses_spec.rb12
-rw-r--r--spec/ruby/library/resolv/get_name_spec.rb18
-rw-r--r--spec/ruby/library/resolv/get_names_spec.rb11
-rw-r--r--spec/ruby/library/ripper/lex_spec.rb23
-rw-r--r--spec/ruby/library/ripper/sexp_spec.rb13
-rw-r--r--spec/ruby/library/rubygems/gem/bin_path_spec.rb35
-rw-r--r--spec/ruby/library/rubygems/gem/load_path_insert_index_spec.rb10
-rw-r--r--spec/ruby/library/securerandom/base64_spec.rb55
-rw-r--r--spec/ruby/library/securerandom/bytes_spec.rb8
-rw-r--r--spec/ruby/library/securerandom/hex_spec.rb54
-rw-r--r--spec/ruby/library/securerandom/random_bytes_spec.rb53
-rw-r--r--spec/ruby/library/securerandom/random_number_spec.rb97
-rw-r--r--spec/ruby/library/shellwords/shellwords_spec.rb33
-rw-r--r--spec/ruby/library/singleton/allocate_spec.rb8
-rw-r--r--spec/ruby/library/singleton/clone_spec.rb8
-rw-r--r--spec/ruby/library/singleton/dump_spec.rb14
-rw-r--r--spec/ruby/library/singleton/dup_spec.rb8
-rw-r--r--spec/ruby/library/singleton/fixtures/classes.rb18
-rw-r--r--spec/ruby/library/singleton/instance_spec.rb30
-rw-r--r--spec/ruby/library/singleton/load_spec.rb20
-rw-r--r--spec/ruby/library/singleton/new_spec.rb8
-rw-r--r--spec/ruby/library/socket/addrinfo/afamily_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/bind_spec.rb28
-rw-r--r--spec/ruby/library/socket/addrinfo/canonname_spec.rb27
-rw-r--r--spec/ruby/library/socket/addrinfo/connect_from_spec.rb75
-rw-r--r--spec/ruby/library/socket/addrinfo/connect_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/connect_to_spec.rb75
-rw-r--r--spec/ruby/library/socket/addrinfo/family_addrinfo_spec.rb113
-rw-r--r--spec/ruby/library/socket/addrinfo/foreach_spec.rb9
-rw-r--r--spec/ruby/library/socket/addrinfo/getaddrinfo_spec.rb87
-rw-r--r--spec/ruby/library/socket/addrinfo/getnameinfo_spec.rb40
-rw-r--r--spec/ruby/library/socket/addrinfo/initialize_spec.rb589
-rw-r--r--spec/ruby/library/socket/addrinfo/inspect_sockaddr_spec.rb48
-rw-r--r--spec/ruby/library/socket/addrinfo/inspect_spec.rb63
-rw-r--r--spec/ruby/library/socket/addrinfo/ip_address_spec.rb64
-rw-r--r--spec/ruby/library/socket/addrinfo/ip_port_spec.rb33
-rw-r--r--spec/ruby/library/socket/addrinfo/ip_spec.rb62
-rw-r--r--spec/ruby/library/socket/addrinfo/ip_unpack_spec.rb33
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv4_loopback_spec.rb41
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv4_multicast_spec.rb27
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv4_private_spec.rb45
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv4_spec.rb33
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_linklocal_spec.rb23
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_loopback_spec.rb43
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_global_spec.rb20
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_linklocal_spec.rb19
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_nodelocal_spec.rb18
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_orglocal_spec.rb18
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_mc_sitelocal_spec.rb18
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_multicast_spec.rb46
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_sitelocal_spec.rb23
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_spec.rb33
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_to_ipv4_spec.rb71
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_unique_local_spec.rb18
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_unspecified_spec.rb15
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_v4compat_spec.rb20
-rw-r--r--spec/ruby/library/socket/addrinfo/ipv6_v4mapped_spec.rb20
-rw-r--r--spec/ruby/library/socket/addrinfo/listen_spec.rb34
-rw-r--r--spec/ruby/library/socket/addrinfo/marshal_dump_spec.rb80
-rw-r--r--spec/ruby/library/socket/addrinfo/marshal_load_spec.rb33
-rw-r--r--spec/ruby/library/socket/addrinfo/pfamily_spec.rb41
-rw-r--r--spec/ruby/library/socket/addrinfo/protocol_spec.rb22
-rw-r--r--spec/ruby/library/socket/addrinfo/shared/to_sockaddr.rb47
-rw-r--r--spec/ruby/library/socket/addrinfo/socktype_spec.rb21
-rw-r--r--spec/ruby/library/socket/addrinfo/tcp_spec.rb34
-rw-r--r--spec/ruby/library/socket/addrinfo/to_s_spec.rb6
-rw-r--r--spec/ruby/library/socket/addrinfo/to_sockaddr_spec.rb6
-rw-r--r--spec/ruby/library/socket/addrinfo/udp_spec.rb34
-rw-r--r--spec/ruby/library/socket/addrinfo/unix_path_spec.rb35
-rw-r--r--spec/ruby/library/socket/addrinfo/unix_spec.rb69
-rw-r--r--spec/ruby/library/socket/ancillarydata/cmsg_is_spec.rb33
-rw-r--r--spec/ruby/library/socket/ancillarydata/data_spec.rb9
-rw-r--r--spec/ruby/library/socket/ancillarydata/family_spec.rb9
-rw-r--r--spec/ruby/library/socket/ancillarydata/initialize_spec.rb284
-rw-r--r--spec/ruby/library/socket/ancillarydata/int_spec.rb43
-rw-r--r--spec/ruby/library/socket/ancillarydata/ip_pktinfo_spec.rb145
-rw-r--r--spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_addr_spec.rb11
-rw-r--r--spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_ifindex_spec.rb11
-rw-r--r--spec/ruby/library/socket/ancillarydata/ipv6_pktinfo_spec.rb89
-rw-r--r--spec/ruby/library/socket/ancillarydata/level_spec.rb9
-rw-r--r--spec/ruby/library/socket/ancillarydata/type_spec.rb9
-rw-r--r--spec/ruby/library/socket/ancillarydata/unix_rights_spec.rb61
-rw-r--r--spec/ruby/library/socket/basicsocket/close_read_spec.rb43
-rw-r--r--spec/ruby/library/socket/basicsocket/close_write_spec.rb48
-rw-r--r--spec/ruby/library/socket/basicsocket/connect_address_spec.rb152
-rw-r--r--spec/ruby/library/socket/basicsocket/do_not_reverse_lookup_spec.rb103
-rw-r--r--spec/ruby/library/socket/basicsocket/for_fd_spec.rb38
-rw-r--r--spec/ruby/library/socket/basicsocket/getpeereid_spec.rb36
-rw-r--r--spec/ruby/library/socket/basicsocket/getpeername_spec.rb25
-rw-r--r--spec/ruby/library/socket/basicsocket/getsockname_spec.rb28
-rw-r--r--spec/ruby/library/socket/basicsocket/getsockopt_spec.rb188
-rw-r--r--spec/ruby/library/socket/basicsocket/ioctl_spec.rb42
-rw-r--r--spec/ruby/library/socket/basicsocket/local_address_spec.rb10
-rw-r--r--spec/ruby/library/socket/basicsocket/read_nonblock_spec.rb74
-rw-r--r--spec/ruby/library/socket/basicsocket/read_spec.rb47
-rw-r--r--spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb142
-rw-r--r--spec/ruby/library/socket/basicsocket/recv_spec.rb229
-rw-r--r--spec/ruby/library/socket/basicsocket/recvmsg_nonblock_spec.rb267
-rw-r--r--spec/ruby/library/socket/basicsocket/recvmsg_spec.rb257
-rw-r--r--spec/ruby/library/socket/basicsocket/remote_address_spec.rb10
-rw-r--r--spec/ruby/library/socket/basicsocket/send_spec.rb220
-rw-r--r--spec/ruby/library/socket/basicsocket/sendmsg_nonblock_spec.rb118
-rw-r--r--spec/ruby/library/socket/basicsocket/sendmsg_spec.rb111
-rw-r--r--spec/ruby/library/socket/basicsocket/setsockopt_spec.rb334
-rw-r--r--spec/ruby/library/socket/basicsocket/shutdown_spec.rb155
-rw-r--r--spec/ruby/library/socket/basicsocket/write_nonblock_spec.rb43
-rw-r--r--spec/ruby/library/socket/constants/constants_spec.rb108
-rw-r--r--spec/ruby/library/socket/fixtures/classes.rb166
-rw-r--r--spec/ruby/library/socket/fixtures/send_io.txt1
-rw-r--r--spec/ruby/library/socket/ipsocket/addr_spec.rb105
-rw-r--r--spec/ruby/library/socket/ipsocket/getaddress_spec.rb28
-rw-r--r--spec/ruby/library/socket/ipsocket/inspect_spec.rb24
-rw-r--r--spec/ruby/library/socket/ipsocket/peeraddr_spec.rb117
-rw-r--r--spec/ruby/library/socket/ipsocket/recvfrom_spec.rb183
-rw-r--r--spec/ruby/library/socket/option/bool_spec.rb27
-rw-r--r--spec/ruby/library/socket/option/initialize_spec.rb83
-rw-r--r--spec/ruby/library/socket/option/inspect_spec.rb19
-rw-r--r--spec/ruby/library/socket/option/int_spec.rb43
-rw-r--r--spec/ruby/library/socket/option/linger_spec.rb76
-rw-r--r--spec/ruby/library/socket/option/new_spec.rb35
-rw-r--r--spec/ruby/library/socket/shared/address.rb259
-rw-r--r--spec/ruby/library/socket/shared/pack_sockaddr.rb92
-rw-r--r--spec/ruby/library/socket/shared/partially_closable_sockets.rb13
-rw-r--r--spec/ruby/library/socket/shared/socketpair.rb138
-rw-r--r--spec/ruby/library/socket/socket/accept_loop_spec.rb84
-rw-r--r--spec/ruby/library/socket/socket/accept_nonblock_spec.rb141
-rw-r--r--spec/ruby/library/socket/socket/accept_spec.rb121
-rw-r--r--spec/ruby/library/socket/socket/bind_spec.rb150
-rw-r--r--spec/ruby/library/socket/socket/connect_nonblock_spec.rb147
-rw-r--r--spec/ruby/library/socket/socket/connect_spec.rb78
-rw-r--r--spec/ruby/library/socket/socket/for_fd_spec.rb30
-rw-r--r--spec/ruby/library/socket/socket/getaddrinfo_spec.rb381
-rw-r--r--spec/ruby/library/socket/socket/gethostbyaddr_spec.rb121
-rw-r--r--spec/ruby/library/socket/socket/gethostbyname_spec.rb135
-rw-r--r--spec/ruby/library/socket/socket/gethostname_spec.rb18
-rw-r--r--spec/ruby/library/socket/socket/getifaddrs_spec.rb117
-rw-r--r--spec/ruby/library/socket/socket/getnameinfo_spec.rb155
-rw-r--r--spec/ruby/library/socket/socket/getservbyname_spec.rb32
-rw-r--r--spec/ruby/library/socket/socket/getservbyport_spec.rb23
-rw-r--r--spec/ruby/library/socket/socket/initialize_spec.rb87
-rw-r--r--spec/ruby/library/socket/socket/ip_address_list_spec.rb50
-rw-r--r--spec/ruby/library/socket/socket/ipv6only_bang_spec.rb20
-rw-r--r--spec/ruby/library/socket/socket/listen_spec.rb66
-rw-r--r--spec/ruby/library/socket/socket/local_address_spec.rb43
-rw-r--r--spec/ruby/library/socket/socket/pack_sockaddr_in_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/pack_sockaddr_un_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/pair_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb188
-rw-r--r--spec/ruby/library/socket/socket/recvfrom_spec.rb157
-rw-r--r--spec/ruby/library/socket/socket/remote_address_spec.rb54
-rw-r--r--spec/ruby/library/socket/socket/sockaddr_in_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/sockaddr_un_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/socket_spec.rb38
-rw-r--r--spec/ruby/library/socket/socket/socketpair_spec.rb7
-rw-r--r--spec/ruby/library/socket/socket/sysaccept_spec.rb91
-rw-r--r--spec/ruby/library/socket/socket/tcp_server_loop_spec.rb54
-rw-r--r--spec/ruby/library/socket/socket/tcp_server_sockets_spec.rb39
-rw-r--r--spec/ruby/library/socket/socket/tcp_spec.rb88
-rw-r--r--spec/ruby/library/socket/socket/udp_server_loop_on_spec.rb47
-rw-r--r--spec/ruby/library/socket/socket/udp_server_loop_spec.rb59
-rw-r--r--spec/ruby/library/socket/socket/udp_server_recv_spec.rb35
-rw-r--r--spec/ruby/library/socket/socket/udp_server_sockets_spec.rb39
-rw-r--r--spec/ruby/library/socket/socket/unix_server_loop_spec.rb56
-rw-r--r--spec/ruby/library/socket/socket/unix_server_socket_spec.rb46
-rw-r--r--spec/ruby/library/socket/socket/unix_spec.rb43
-rw-r--r--spec/ruby/library/socket/socket/unpack_sockaddr_in_spec.rb44
-rw-r--r--spec/ruby/library/socket/socket/unpack_sockaddr_un_spec.rb24
-rw-r--r--spec/ruby/library/socket/spec_helper.rb14
-rw-r--r--spec/ruby/library/socket/tcpserver/accept_nonblock_spec.rb85
-rw-r--r--spec/ruby/library/socket/tcpserver/accept_spec.rb132
-rw-r--r--spec/ruby/library/socket/tcpserver/gets_spec.rb16
-rw-r--r--spec/ruby/library/socket/tcpserver/initialize_spec.rb101
-rw-r--r--spec/ruby/library/socket/tcpserver/listen_spec.rb22
-rw-r--r--spec/ruby/library/socket/tcpserver/new_spec.rb137
-rw-r--r--spec/ruby/library/socket/tcpserver/sysaccept_spec.rb66
-rw-r--r--spec/ruby/library/socket/tcpsocket/gethostbyname_spec.rb119
-rw-r--r--spec/ruby/library/socket/tcpsocket/initialize_spec.rb100
-rw-r--r--spec/ruby/library/socket/tcpsocket/local_address_spec.rb73
-rw-r--r--spec/ruby/library/socket/tcpsocket/open_spec.rb6
-rw-r--r--spec/ruby/library/socket/tcpsocket/partially_closable_spec.rb21
-rw-r--r--spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb48
-rw-r--r--spec/ruby/library/socket/tcpsocket/recv_spec.rb28
-rw-r--r--spec/ruby/library/socket/tcpsocket/remote_address_spec.rb72
-rw-r--r--spec/ruby/library/socket/tcpsocket/setsockopt_spec.rb45
-rw-r--r--spec/ruby/library/socket/tcpsocket/shared/new.rb118
-rw-r--r--spec/ruby/library/socket/udpsocket/bind_spec.rb83
-rw-r--r--spec/ruby/library/socket/udpsocket/connect_spec.rb35
-rw-r--r--spec/ruby/library/socket/udpsocket/initialize_spec.rb53
-rw-r--r--spec/ruby/library/socket/udpsocket/local_address_spec.rb80
-rw-r--r--spec/ruby/library/socket/udpsocket/new_spec.rb40
-rw-r--r--spec/ruby/library/socket/udpsocket/open_spec.rb13
-rw-r--r--spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb111
-rw-r--r--spec/ruby/library/socket/udpsocket/remote_address_spec.rb79
-rw-r--r--spec/ruby/library/socket/udpsocket/send_spec.rb154
-rw-r--r--spec/ruby/library/socket/udpsocket/write_spec.rb21
-rw-r--r--spec/ruby/library/socket/unixserver/accept_nonblock_spec.rb87
-rw-r--r--spec/ruby/library/socket/unixserver/accept_spec.rb126
-rw-r--r--spec/ruby/library/socket/unixserver/for_fd_spec.rb21
-rw-r--r--spec/ruby/library/socket/unixserver/initialize_spec.rb26
-rw-r--r--spec/ruby/library/socket/unixserver/listen_spec.rb19
-rw-r--r--spec/ruby/library/socket/unixserver/new_spec.rb12
-rw-r--r--spec/ruby/library/socket/unixserver/open_spec.rb24
-rw-r--r--spec/ruby/library/socket/unixserver/shared/new.rb20
-rw-r--r--spec/ruby/library/socket/unixserver/sysaccept_spec.rb50
-rw-r--r--spec/ruby/library/socket/unixsocket/addr_spec.rb33
-rw-r--r--spec/ruby/library/socket/unixsocket/initialize_spec.rb56
-rw-r--r--spec/ruby/library/socket/unixsocket/inspect_spec.rb15
-rw-r--r--spec/ruby/library/socket/unixsocket/local_address_spec.rb92
-rw-r--r--spec/ruby/library/socket/unixsocket/new_spec.rb12
-rw-r--r--spec/ruby/library/socket/unixsocket/open_spec.rb26
-rw-r--r--spec/ruby/library/socket/unixsocket/pair_spec.rb18
-rw-r--r--spec/ruby/library/socket/unixsocket/partially_closable_spec.rb21
-rw-r--r--spec/ruby/library/socket/unixsocket/path_spec.rb24
-rw-r--r--spec/ruby/library/socket/unixsocket/peeraddr_spec.rb26
-rw-r--r--spec/ruby/library/socket/unixsocket/recv_io_spec.rb84
-rw-r--r--spec/ruby/library/socket/unixsocket/recvfrom_spec.rb174
-rw-r--r--spec/ruby/library/socket/unixsocket/remote_address_spec.rb43
-rw-r--r--spec/ruby/library/socket/unixsocket/send_io_spec.rb55
-rw-r--r--spec/ruby/library/socket/unixsocket/shared/new.rb22
-rw-r--r--spec/ruby/library/socket/unixsocket/shared/pair.rb47
-rw-r--r--spec/ruby/library/socket/unixsocket/socketpair_spec.rb18
-rw-r--r--spec/ruby/library/stringio/append_spec.rb74
-rw-r--r--spec/ruby/library/stringio/binmode_spec.rb23
-rw-r--r--spec/ruby/library/stringio/close_read_spec.rb31
-rw-r--r--spec/ruby/library/stringio/close_spec.rb23
-rw-r--r--spec/ruby/library/stringio/close_write_spec.rb31
-rw-r--r--spec/ruby/library/stringio/closed_read_spec.rb12
-rw-r--r--spec/ruby/library/stringio/closed_spec.rb16
-rw-r--r--spec/ruby/library/stringio/closed_write_spec.rb12
-rw-r--r--spec/ruby/library/stringio/each_byte_spec.rb11
-rw-r--r--spec/ruby/library/stringio/each_char_spec.rb11
-rw-r--r--spec/ruby/library/stringio/each_codepoint_spec.rb9
-rw-r--r--spec/ruby/library/stringio/each_line_spec.rb27
-rw-r--r--spec/ruby/library/stringio/each_spec.rb31
-rw-r--r--spec/ruby/library/stringio/eof_spec.rb11
-rw-r--r--spec/ruby/library/stringio/external_encoding_spec.rb25
-rw-r--r--spec/ruby/library/stringio/fcntl_spec.rb8
-rw-r--r--spec/ruby/library/stringio/fileno_spec.rb8
-rw-r--r--spec/ruby/library/stringio/fixtures/classes.rb15
-rw-r--r--spec/ruby/library/stringio/flush_spec.rb9
-rw-r--r--spec/ruby/library/stringio/fsync_spec.rb9
-rw-r--r--spec/ruby/library/stringio/getbyte_spec.rb19
-rw-r--r--spec/ruby/library/stringio/getc_spec.rb19
-rw-r--r--spec/ruby/library/stringio/getch_spec.rb44
-rw-r--r--spec/ruby/library/stringio/getpass_spec.rb11
-rw-r--r--spec/ruby/library/stringio/gets_spec.rb61
-rw-r--r--spec/ruby/library/stringio/initialize_spec.rb328
-rw-r--r--spec/ruby/library/stringio/inspect_spec.rb19
-rw-r--r--spec/ruby/library/stringio/internal_encoding_spec.rb10
-rw-r--r--spec/ruby/library/stringio/isatty_spec.rb7
-rw-r--r--spec/ruby/library/stringio/length_spec.rb7
-rw-r--r--spec/ruby/library/stringio/lineno_spec.rb30
-rw-r--r--spec/ruby/library/stringio/new_spec.rb10
-rw-r--r--spec/ruby/library/stringio/open_spec.rb215
-rw-r--r--spec/ruby/library/stringio/path_spec.rb8
-rw-r--r--spec/ruby/library/stringio/pid_spec.rb8
-rw-r--r--spec/ruby/library/stringio/pos_spec.rb28
-rw-r--r--spec/ruby/library/stringio/print_spec.rb102
-rw-r--r--spec/ruby/library/stringio/printf_spec.rb91
-rw-r--r--spec/ruby/library/stringio/putc_spec.rb103
-rw-r--r--spec/ruby/library/stringio/puts_spec.rb184
-rw-r--r--spec/ruby/library/stringio/read_nonblock_spec.rb53
-rw-r--r--spec/ruby/library/stringio/read_spec.rb62
-rw-r--r--spec/ruby/library/stringio/readbyte_spec.rb20
-rw-r--r--spec/ruby/library/stringio/readchar_spec.rb20
-rw-r--r--spec/ruby/library/stringio/readline_spec.rb58
-rw-r--r--spec/ruby/library/stringio/readlines_spec.rb118
-rw-r--r--spec/ruby/library/stringio/readpartial_spec.rb102
-rw-r--r--spec/ruby/library/stringio/reopen_spec.rb251
-rw-r--r--spec/ruby/library/stringio/rewind_spec.rb24
-rw-r--r--spec/ruby/library/stringio/seek_spec.rb67
-rw-r--r--spec/ruby/library/stringio/set_encoding_by_bom_spec.rb237
-rw-r--r--spec/ruby/library/stringio/set_encoding_spec.rb28
-rw-r--r--spec/ruby/library/stringio/shared/codepoints.rb45
-rw-r--r--spec/ruby/library/stringio/shared/each.rb209
-rw-r--r--spec/ruby/library/stringio/shared/each_byte.rb48
-rw-r--r--spec/ruby/library/stringio/shared/each_char.rb36
-rw-r--r--spec/ruby/library/stringio/shared/eof.rb24
-rw-r--r--spec/ruby/library/stringio/shared/getc.rb43
-rw-r--r--spec/ruby/library/stringio/shared/gets.rb249
-rw-r--r--spec/ruby/library/stringio/shared/isatty.rb5
-rw-r--r--spec/ruby/library/stringio/shared/length.rb5
-rw-r--r--spec/ruby/library/stringio/shared/read.rb145
-rw-r--r--spec/ruby/library/stringio/shared/readchar.rb29
-rw-r--r--spec/ruby/library/stringio/shared/sysread.rb15
-rw-r--r--spec/ruby/library/stringio/shared/tell.rb12
-rw-r--r--spec/ruby/library/stringio/shared/write.rb135
-rw-r--r--spec/ruby/library/stringio/size_spec.rb7
-rw-r--r--spec/ruby/library/stringio/string_spec.rb50
-rw-r--r--spec/ruby/library/stringio/stringio_spec.rb8
-rw-r--r--spec/ruby/library/stringio/sync_spec.rb19
-rw-r--r--spec/ruby/library/stringio/sysread_spec.rb53
-rw-r--r--spec/ruby/library/stringio/syswrite_spec.rb19
-rw-r--r--spec/ruby/library/stringio/tell_spec.rb7
-rw-r--r--spec/ruby/library/stringio/truncate_spec.rb62
-rw-r--r--spec/ruby/library/stringio/tty_spec.rb7
-rw-r--r--spec/ruby/library/stringio/ungetbyte_spec.rb42
-rw-r--r--spec/ruby/library/stringio/ungetc_spec.rb72
-rw-r--r--spec/ruby/library/stringio/write_nonblock_spec.rb25
-rw-r--r--spec/ruby/library/stringio/write_spec.rb19
-rw-r--r--spec/ruby/library/stringscanner/append_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/beginning_of_line_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/bol_spec.rb7
-rw-r--r--spec/ruby/library/stringscanner/captures_spec.rb36
-rw-r--r--spec/ruby/library/stringscanner/charpos_spec.rb18
-rw-r--r--spec/ruby/library/stringscanner/check_spec.rb93
-rw-r--r--spec/ruby/library/stringscanner/check_until_spec.rb129
-rw-r--r--spec/ruby/library/stringscanner/concat_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/dup_spec.rb39
-rw-r--r--spec/ruby/library/stringscanner/element_reference_spec.rb67
-rw-r--r--spec/ruby/library/stringscanner/eos_spec.rb20
-rw-r--r--spec/ruby/library/stringscanner/exist_spec.rb119
-rw-r--r--spec/ruby/library/stringscanner/fixed_anchor_spec.rb17
-rw-r--r--spec/ruby/library/stringscanner/get_byte_spec.rb84
-rw-r--r--spec/ruby/library/stringscanner/getch_spec.rb96
-rw-r--r--spec/ruby/library/stringscanner/initialize_spec.rb32
-rw-r--r--spec/ruby/library/stringscanner/inspect_spec.rb20
-rw-r--r--spec/ruby/library/stringscanner/match_spec.rb51
-rw-r--r--spec/ruby/library/stringscanner/matched_size_spec.rb24
-rw-r--r--spec/ruby/library/stringscanner/matched_spec.rb41
-rw-r--r--spec/ruby/library/stringscanner/must_C_version_spec.rb8
-rw-r--r--spec/ruby/library/stringscanner/named_captures_spec.rb28
-rw-r--r--spec/ruby/library/stringscanner/peek_byte_spec.rb35
-rw-r--r--spec/ruby/library/stringscanner/peek_spec.rb42
-rw-r--r--spec/ruby/library/stringscanner/pointer_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/pos_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/post_match_spec.rb28
-rw-r--r--spec/ruby/library/stringscanner/pre_match_spec.rb41
-rw-r--r--spec/ruby/library/stringscanner/reset_spec.rb15
-rw-r--r--spec/ruby/library/stringscanner/rest_size_spec.rb30
-rw-r--r--spec/ruby/library/stringscanner/rest_spec.rb48
-rw-r--r--spec/ruby/library/stringscanner/scan_byte_spec.rb98
-rw-r--r--spec/ruby/library/stringscanner/scan_full_spec.rb44
-rw-r--r--spec/ruby/library/stringscanner/scan_integer_spec.rb157
-rw-r--r--spec/ruby/library/stringscanner/scan_spec.rb101
-rw-r--r--spec/ruby/library/stringscanner/scan_until_spec.rb135
-rw-r--r--spec/ruby/library/stringscanner/search_full_spec.rb133
-rw-r--r--spec/ruby/library/stringscanner/shared/bol.rb25
-rw-r--r--spec/ruby/library/stringscanner/shared/concat.rb30
-rw-r--r--spec/ruby/library/stringscanner/shared/extract_range.rb11
-rw-r--r--spec/ruby/library/stringscanner/shared/extract_range_matched.rb13
-rw-r--r--spec/ruby/library/stringscanner/shared/pos.rb59
-rw-r--r--spec/ruby/library/stringscanner/size_spec.rb17
-rw-r--r--spec/ruby/library/stringscanner/skip_spec.rb32
-rw-r--r--spec/ruby/library/stringscanner/skip_until_spec.rb132
-rw-r--r--spec/ruby/library/stringscanner/string_spec.rb40
-rw-r--r--spec/ruby/library/stringscanner/terminate_spec.rb11
-rw-r--r--spec/ruby/library/stringscanner/unscan_spec.rb28
-rw-r--r--spec/ruby/library/stringscanner/values_at_spec.rb68
-rw-r--r--spec/ruby/library/syslog/alert_spec.rb10
-rw-r--r--spec/ruby/library/syslog/close_spec.rb58
-rw-r--r--spec/ruby/library/syslog/constants_spec.rb41
-rw-r--r--spec/ruby/library/syslog/crit_spec.rb10
-rw-r--r--spec/ruby/library/syslog/debug_spec.rb10
-rw-r--r--spec/ruby/library/syslog/emerg_spec.rb16
-rw-r--r--spec/ruby/library/syslog/err_spec.rb10
-rw-r--r--spec/ruby/library/syslog/facility_spec.rb48
-rw-r--r--spec/ruby/library/syslog/ident_spec.rb35
-rw-r--r--spec/ruby/library/syslog/info_spec.rb10
-rw-r--r--spec/ruby/library/syslog/inspect_spec.rb39
-rw-r--r--spec/ruby/library/syslog/instance_spec.rb13
-rw-r--r--spec/ruby/library/syslog/log_spec.rb56
-rw-r--r--spec/ruby/library/syslog/mask_spec.rb113
-rw-r--r--spec/ruby/library/syslog/notice_spec.rb10
-rw-r--r--spec/ruby/library/syslog/open_spec.rb92
-rw-r--r--spec/ruby/library/syslog/opened_spec.rb39
-rw-r--r--spec/ruby/library/syslog/options_spec.rb48
-rw-r--r--spec/ruby/library/syslog/reopen_spec.rb10
-rw-r--r--spec/ruby/library/syslog/shared/log.rb39
-rw-r--r--spec/ruby/library/syslog/shared/reopen.rb40
-rw-r--r--spec/ruby/library/syslog/warning_spec.rb10
-rw-r--r--spec/ruby/library/tempfile/_close_spec.rb21
-rw-r--r--spec/ruby/library/tempfile/close_spec.rb57
-rw-r--r--spec/ruby/library/tempfile/create_spec.rb176
-rw-r--r--spec/ruby/library/tempfile/delete_spec.rb7
-rw-r--r--spec/ruby/library/tempfile/initialize_spec.rb46
-rw-r--r--spec/ruby/library/tempfile/length_spec.rb7
-rw-r--r--spec/ruby/library/tempfile/open_spec.rb97
-rw-r--r--spec/ruby/library/tempfile/path_spec.rb26
-rw-r--r--spec/ruby/library/tempfile/shared/length.rb21
-rw-r--r--spec/ruby/library/tempfile/shared/unlink.rb12
-rw-r--r--spec/ruby/library/tempfile/size_spec.rb7
-rw-r--r--spec/ruby/library/tempfile/unlink_spec.rb7
-rw-r--r--spec/ruby/library/thread/queue_spec.rb8
-rw-r--r--spec/ruby/library/thread/sizedqueue_spec.rb8
-rw-r--r--spec/ruby/library/time/httpdate_spec.rb21
-rw-r--r--spec/ruby/library/time/iso8601_spec.rb7
-rw-r--r--spec/ruby/library/time/rfc2822_spec.rb7
-rw-r--r--spec/ruby/library/time/rfc822_spec.rb7
-rw-r--r--spec/ruby/library/time/shared/rfc2822.rb65
-rw-r--r--spec/ruby/library/time/shared/xmlschema.rb53
-rw-r--r--spec/ruby/library/time/to_time_spec.rb15
-rw-r--r--spec/ruby/library/time/xmlschema_spec.rb7
-rw-r--r--spec/ruby/library/timeout/error_spec.rb8
-rw-r--r--spec/ruby/library/timeout/timeout_spec.rb50
-rw-r--r--spec/ruby/library/tmpdir/dir/mktmpdir_spec.rb117
-rw-r--r--spec/ruby/library/tmpdir/dir/tmpdir_spec.rb10
-rw-r--r--spec/ruby/library/uri/decode_www_form_component_spec.rb6
-rw-r--r--spec/ruby/library/uri/decode_www_form_spec.rb6
-rw-r--r--spec/ruby/library/uri/encode_www_form_component_spec.rb6
-rw-r--r--spec/ruby/library/uri/encode_www_form_spec.rb6
-rw-r--r--spec/ruby/library/uri/eql_spec.rb10
-rw-r--r--spec/ruby/library/uri/equality_spec.rb46
-rw-r--r--spec/ruby/library/uri/escape/decode_spec.rb6
-rw-r--r--spec/ruby/library/uri/escape/encode_spec.rb6
-rw-r--r--spec/ruby/library/uri/escape/escape_spec.rb6
-rw-r--r--spec/ruby/library/uri/escape/unescape_spec.rb6
-rw-r--r--spec/ruby/library/uri/extract_spec.rb86
-rw-r--r--spec/ruby/library/uri/fixtures/classes.rb11
-rw-r--r--spec/ruby/library/uri/fixtures/normalization.rb54
-rw-r--r--spec/ruby/library/uri/ftp/build_spec.rb6
-rw-r--r--spec/ruby/library/uri/ftp/merge_spec.rb6
-rw-r--r--spec/ruby/library/uri/ftp/new2_spec.rb6
-rw-r--r--spec/ruby/library/uri/ftp/path_spec.rb26
-rw-r--r--spec/ruby/library/uri/ftp/set_typecode_spec.rb6
-rw-r--r--spec/ruby/library/uri/ftp/to_s_spec.rb15
-rw-r--r--spec/ruby/library/uri/ftp/typecode_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/absolute_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/build2_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/build_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/coerce_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/component_ary_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/component_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/default_port_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/eql_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/equal_value_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/fragment_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/hash_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/hierarchical_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/host_spec.rb13
-rw-r--r--spec/ruby/library/uri/generic/inspect_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/merge_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/minus_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/normalize_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/opaque_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/password_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/path_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/plus_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/port_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/query_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/registry_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/relative_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/route_from_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/route_to_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/scheme_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/select_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_fragment_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_host_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_opaque_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_password_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_path_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_port_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_query_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_registry_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_scheme_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_user_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/set_userinfo_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/to_s_spec.rb9
-rw-r--r--spec/ruby/library/uri/generic/use_registry_spec.rb6
-rw-r--r--spec/ruby/library/uri/generic/user_spec.rb10
-rw-r--r--spec/ruby/library/uri/generic/userinfo_spec.rb10
-rw-r--r--spec/ruby/library/uri/http/build_spec.rb6
-rw-r--r--spec/ruby/library/uri/http/request_uri_spec.rb16
-rw-r--r--spec/ruby/library/uri/join_spec.rb59
-rw-r--r--spec/ruby/library/uri/ldap/attributes_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/build_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/dn_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/extensions_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/filter_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/hierarchical_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/scope_spec.rb10
-rw-r--r--spec/ruby/library/uri/ldap/set_attributes_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/set_dn_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/set_extensions_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/set_filter_spec.rb6
-rw-r--r--spec/ruby/library/uri/ldap/set_scope_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/build_spec.rb92
-rw-r--r--spec/ruby/library/uri/mailto/headers_spec.rb10
-rw-r--r--spec/ruby/library/uri/mailto/set_headers_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/set_to_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/to_mailtext_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/to_rfc822text_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/to_s_spec.rb6
-rw-r--r--spec/ruby/library/uri/mailto/to_spec.rb10
-rw-r--r--spec/ruby/library/uri/merge_spec.rb20
-rw-r--r--spec/ruby/library/uri/normalize_spec.rb35
-rw-r--r--spec/ruby/library/uri/parse_spec.rb203
-rw-r--r--spec/ruby/library/uri/parser/escape_spec.rb6
-rw-r--r--spec/ruby/library/uri/parser/extract_spec.rb7
-rw-r--r--spec/ruby/library/uri/parser/inspect_spec.rb6
-rw-r--r--spec/ruby/library/uri/parser/join_spec.rb7
-rw-r--r--spec/ruby/library/uri/parser/make_regexp_spec.rb6
-rw-r--r--spec/ruby/library/uri/parser/parse_spec.rb7
-rw-r--r--spec/ruby/library/uri/parser/split_spec.rb6
-rw-r--r--spec/ruby/library/uri/parser/unescape_spec.rb6
-rw-r--r--spec/ruby/library/uri/plus_spec.rb459
-rw-r--r--spec/ruby/library/uri/regexp_spec.rb18
-rw-r--r--spec/ruby/library/uri/route_from_spec.rb23
-rw-r--r--spec/ruby/library/uri/route_to_spec.rb26
-rw-r--r--spec/ruby/library/uri/select_spec.rb27
-rw-r--r--spec/ruby/library/uri/set_component_spec.rb47
-rw-r--r--spec/ruby/library/uri/shared/eql.rb17
-rw-r--r--spec/ruby/library/uri/shared/extract.rb83
-rw-r--r--spec/ruby/library/uri/shared/join.rb56
-rw-r--r--spec/ruby/library/uri/shared/parse.rb206
-rw-r--r--spec/ruby/library/uri/split_spec.rb6
-rw-r--r--spec/ruby/library/uri/uri_spec.rb29
-rw-r--r--spec/ruby/library/uri/util/make_components_hash_spec.rb6
-rw-r--r--spec/ruby/library/weakref/__getobj___spec.rb17
-rw-r--r--spec/ruby/library/weakref/allocate_spec.rb8
-rw-r--r--spec/ruby/library/weakref/fixtures/classes.rb26
-rw-r--r--spec/ruby/library/weakref/new_spec.rb13
-rw-r--r--spec/ruby/library/weakref/send_spec.rb37
-rw-r--r--spec/ruby/library/weakref/weakref_alive_spec.rb15
-rw-r--r--spec/ruby/library/win32ole/fixtures/classes.rb33
-rw-r--r--spec/ruby/library/win32ole/fixtures/event.xml4
-rw-r--r--spec/ruby/library/win32ole/win32ole/_getproperty_spec.rb15
-rw-r--r--spec/ruby/library/win32ole/win32ole/_invoke_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole/codepage_spec.rb14
-rw-r--r--spec/ruby/library/win32ole/win32ole/connect_spec.rb16
-rw-r--r--spec/ruby/library/win32ole/win32ole/const_load_spec.rb33
-rw-r--r--spec/ruby/library/win32ole/win32ole/constants_spec.rb43
-rw-r--r--spec/ruby/library/win32ole/win32ole/create_guid_spec.rb10
-rw-r--r--spec/ruby/library/win32ole/win32ole/invoke_spec.rb15
-rw-r--r--spec/ruby/library/win32ole/win32ole/locale_spec.rb30
-rw-r--r--spec/ruby/library/win32ole/win32ole/new_spec.rb26
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_func_methods_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_get_methods_spec.rb17
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_method_help_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_method_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_methods_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_obj_help_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole/ole_put_methods_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole/setproperty_spec.rb11
-rw-r--r--spec/ruby/library/win32ole/win32ole/shared/ole_method.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole/shared/setproperty.rb23
-rw-r--r--spec/ruby/library/win32ole/win32ole_event/new_spec.rb34
-rw-r--r--spec/ruby/library/win32ole/win32ole_event/on_event_spec.rb71
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/dispid_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/event_interface_spec.rb29
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/event_spec.rb23
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/helpcontext_spec.rb27
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/helpfile_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/helpstring_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/invkind_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/invoke_kind_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/name_spec.rb12
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/new_spec.rb34
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/offset_vtbl_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/params_spec.rb29
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/return_type_detail_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/return_type_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/return_vtype_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/shared/name.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/size_opt_params_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/size_params_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/to_s_spec.rb12
-rw-r--r--spec/ruby/library/win32ole/win32ole_method/visible_spec.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/default_spec.rb32
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/input_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/name_spec.rb12
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/ole_type_detail_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/ole_type_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/optional_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/retval_spec.rb22
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/shared/name.rb21
-rw-r--r--spec/ruby/library/win32ole/win32ole_param/to_s_spec.rb12
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/guid_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/helpcontext_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/helpfile_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/helpstring_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/major_version_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/minor_version_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/name_spec.rb12
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/new_spec.rb41
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/ole_classes_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/ole_methods_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/ole_type_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/progid_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/progids_spec.rb15
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/shared/name.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/src_type_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/to_s_spec.rb12
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/typekind_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/typelibs_spec.rb23
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/variables_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_type/visible_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/name_spec.rb12
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/ole_type_detail_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/ole_type_spec.rb19
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/shared/name.rb18
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/to_s_spec.rb12
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/value_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/variable_kind_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/varkind_spec.rb20
-rw-r--r--spec/ruby/library/win32ole/win32ole_variable/visible_spec.rb19
-rw-r--r--spec/ruby/library/yaml/dump_spec.rb64
-rw-r--r--spec/ruby/library/yaml/dump_stream_spec.rb9
-rw-r--r--spec/ruby/library/yaml/fixtures/example_class.rb7
-rw-r--r--spec/ruby/library/yaml/fixtures/strings.rb26
-rw-r--r--spec/ruby/library/yaml/fixtures/test_yaml.yml2
-rw-r--r--spec/ruby/library/yaml/load_file_spec.rb18
-rw-r--r--spec/ruby/library/yaml/load_spec.rb10
-rw-r--r--spec/ruby/library/yaml/load_stream_spec.rb9
-rw-r--r--spec/ruby/library/yaml/parse_file_spec.rb10
-rw-r--r--spec/ruby/library/yaml/parse_spec.rb23
-rw-r--r--spec/ruby/library/yaml/shared/each_document.rb19
-rw-r--r--spec/ruby/library/yaml/shared/load.rb142
-rw-r--r--spec/ruby/library/yaml/to_yaml_spec.rb114
-rw-r--r--spec/ruby/library/yaml/unsafe_load_spec.rb9
-rw-r--r--spec/ruby/library/zlib/adler32_spec.rb46
-rw-r--r--spec/ruby/library/zlib/crc32_spec.rb54
-rw-r--r--spec/ruby/library/zlib/crc_table_spec.rb80
-rw-r--r--spec/ruby/library/zlib/deflate/deflate_spec.rb133
-rw-r--r--spec/ruby/library/zlib/deflate/params_spec.rb17
-rw-r--r--spec/ruby/library/zlib/deflate/set_dictionary_spec.rb14
-rw-r--r--spec/ruby/library/zlib/deflate_spec.rb8
-rw-r--r--spec/ruby/library/zlib/gunzip_spec.rb14
-rw-r--r--spec/ruby/library/zlib/gzip_spec.rb15
-rw-r--r--spec/ruby/library/zlib/gzipfile/close_spec.rb19
-rw-r--r--spec/ruby/library/zlib/gzipfile/closed_spec.rb16
-rw-r--r--spec/ruby/library/zlib/gzipfile/comment_spec.rb25
-rw-r--r--spec/ruby/library/zlib/gzipfile/orig_name_spec.rb25
-rw-r--r--spec/ruby/library/zlib/gzipreader/each_byte_spec.rb51
-rw-r--r--spec/ruby/library/zlib/gzipreader/each_char_spec.rb51
-rw-r--r--spec/ruby/library/zlib/gzipreader/each_line_spec.rb6
-rw-r--r--spec/ruby/library/zlib/gzipreader/each_spec.rb6
-rw-r--r--spec/ruby/library/zlib/gzipreader/eof_spec.rb54
-rw-r--r--spec/ruby/library/zlib/gzipreader/getc_spec.rb39
-rw-r--r--spec/ruby/library/zlib/gzipreader/gets_spec.rb22
-rw-r--r--spec/ruby/library/zlib/gzipreader/mtime_spec.rb11
-rw-r--r--spec/ruby/library/zlib/gzipreader/pos_spec.rb24
-rw-r--r--spec/ruby/library/zlib/gzipreader/read_spec.rb66
-rw-r--r--spec/ruby/library/zlib/gzipreader/readpartial_spec.rb17
-rw-r--r--spec/ruby/library/zlib/gzipreader/rewind_spec.rb47
-rw-r--r--spec/ruby/library/zlib/gzipreader/shared/each.rb49
-rw-r--r--spec/ruby/library/zlib/gzipreader/ungetbyte_spec.rb120
-rw-r--r--spec/ruby/library/zlib/gzipreader/ungetc_spec.rb284
-rw-r--r--spec/ruby/library/zlib/gzipwriter/append_spec.rb15
-rw-r--r--spec/ruby/library/zlib/gzipwriter/mtime_spec.rb37
-rw-r--r--spec/ruby/library/zlib/gzipwriter/write_spec.rb36
-rw-r--r--spec/ruby/library/zlib/inflate/append_spec.rb60
-rw-r--r--spec/ruby/library/zlib/inflate/finish_spec.rb29
-rw-r--r--spec/ruby/library/zlib/inflate/inflate_spec.rb159
-rw-r--r--spec/ruby/library/zlib/inflate/set_dictionary_spec.rb20
-rw-r--r--spec/ruby/library/zlib/inflate_spec.rb8
-rw-r--r--spec/ruby/library/zlib/zlib_version_spec.rb8
-rw-r--r--spec/ruby/library/zlib/zstream/adler_spec.rb11
-rw-r--r--spec/ruby/library/zlib/zstream/avail_in_spec.rb9
-rw-r--r--spec/ruby/library/zlib/zstream/avail_out_spec.rb9
-rw-r--r--spec/ruby/library/zlib/zstream/data_type_spec.rb9
-rw-r--r--spec/ruby/library/zlib/zstream/flush_next_out_spec.rb14
-rw-r--r--spec/ruby/optional/capi/README13
-rw-r--r--spec/ruby/optional/capi/array_spec.rb533
-rw-r--r--spec/ruby/optional/capi/basic_object_spec.rb24
-rw-r--r--spec/ruby/optional/capi/bignum_spec.rb226
-rw-r--r--spec/ruby/optional/capi/binding_spec.rb16
-rw-r--r--spec/ruby/optional/capi/boolean_spec.rb33
-rw-r--r--spec/ruby/optional/capi/class_spec.rb506
-rw-r--r--spec/ruby/optional/capi/complex_spec.rb45
-rw-r--r--spec/ruby/optional/capi/constants_spec.rb325
-rw-r--r--spec/ruby/optional/capi/data_spec.rb53
-rw-r--r--spec/ruby/optional/capi/debug_spec.rb74
-rw-r--r--spec/ruby/optional/capi/digest_spec.rb103
-rw-r--r--spec/ruby/optional/capi/encoding_spec.rb817
-rw-r--r--spec/ruby/optional/capi/enumerator_spec.rb66
-rw-r--r--spec/ruby/optional/capi/exception_spec.rb201
-rw-r--r--spec/ruby/optional/capi/ext/.gitignore9
-rw-r--r--spec/ruby/optional/capi/ext/array_spec.c333
-rw-r--r--spec/ruby/optional/capi/ext/basic_object_spec.c19
-rw-r--r--spec/ruby/optional/capi/ext/bignum_spec.c106
-rw-r--r--spec/ruby/optional/capi/ext/binding_spec.c19
-rw-r--r--spec/ruby/optional/capi/ext/boolean_spec.c33
-rw-r--r--spec/ruby/optional/capi/ext/class_id_under_autoload_spec.c13
-rw-r--r--spec/ruby/optional/capi/ext/class_spec.c180
-rw-r--r--spec/ruby/optional/capi/ext/class_under_autoload_spec.c13
-rw-r--r--spec/ruby/optional/capi/ext/complex_spec.c45
-rw-r--r--spec/ruby/optional/capi/ext/constants_spec.c172
-rw-r--r--spec/ruby/optional/capi/ext/data_spec.c93
-rw-r--r--spec/ruby/optional/capi/ext/debug_spec.c93
-rw-r--r--spec/ruby/optional/capi/ext/digest_spec.c168
-rw-r--r--spec/ruby/optional/capi/ext/encoding_spec.c402
-rw-r--r--spec/ruby/optional/capi/ext/enumerator_spec.c32
-rw-r--r--spec/ruby/optional/capi/ext/exception_spec.c84
-rw-r--r--spec/ruby/optional/capi/ext/fiber_spec.c64
-rw-r--r--spec/ruby/optional/capi/ext/file_spec.c29
-rw-r--r--spec/ruby/optional/capi/ext/finalizer_spec.c25
-rw-r--r--spec/ruby/optional/capi/ext/fixnum_spec.c26
-rw-r--r--spec/ruby/optional/capi/ext/float_spec.c47
-rw-r--r--spec/ruby/optional/capi/ext/gc_spec.c178
-rw-r--r--spec/ruby/optional/capi/ext/globals_spec.c161
-rw-r--r--spec/ruby/optional/capi/ext/hash_spec.c180
-rw-r--r--spec/ruby/optional/capi/ext/integer_spec.c40
-rw-r--r--spec/ruby/optional/capi/ext/io_spec.c416
-rw-r--r--spec/ruby/optional/capi/ext/kernel_spec.c463
-rw-r--r--spec/ruby/optional/capi/ext/language_spec.c42
-rw-r--r--spec/ruby/optional/capi/ext/marshal_spec.c24
-rw-r--r--spec/ruby/optional/capi/ext/module_spec.c176
-rw-r--r--spec/ruby/optional/capi/ext/module_under_autoload_spec.c15
-rw-r--r--spec/ruby/optional/capi/ext/mutex_spec.c76
-rw-r--r--spec/ruby/optional/capi/ext/numeric_spec.c130
-rw-r--r--spec/ruby/optional/capi/ext/object_spec.c475
-rw-r--r--spec/ruby/optional/capi/ext/proc_spec.c147
-rw-r--r--spec/ruby/optional/capi/ext/range_spec.c90
-rw-r--r--spec/ruby/optional/capi/ext/rational_spec.c54
-rw-r--r--spec/ruby/optional/capi/ext/rbasic_spec.c104
-rw-r--r--spec/ruby/optional/capi/ext/regexp_spec.c74
-rw-r--r--spec/ruby/optional/capi/ext/rubyspec.h50
-rw-r--r--spec/ruby/optional/capi/ext/set_spec.c65
-rw-r--r--spec/ruby/optional/capi/ext/st_spec.c83
-rw-r--r--spec/ruby/optional/capi/ext/string_spec.c737
-rw-r--r--spec/ruby/optional/capi/ext/struct_spec.c101
-rw-r--r--spec/ruby/optional/capi/ext/symbol_spec.c116
-rw-r--r--spec/ruby/optional/capi/ext/thread_spec.c195
-rw-r--r--spec/ruby/optional/capi/ext/time_spec.c81
-rw-r--r--spec/ruby/optional/capi/ext/tracepoint_spec.c49
-rw-r--r--spec/ruby/optional/capi/ext/typed_data_spec.c210
-rw-r--r--spec/ruby/optional/capi/ext/util_spec.c117
-rw-r--r--spec/ruby/optional/capi/fiber_spec.rb86
-rw-r--r--spec/ruby/optional/capi/file_spec.rb89
-rw-r--r--spec/ruby/optional/capi/finalizer_spec.rb40
-rw-r--r--spec/ruby/optional/capi/fixnum_spec.rb101
-rw-r--r--spec/ruby/optional/capi/fixtures/class.rb104
-rw-r--r--spec/ruby/optional/capi/fixtures/const_get.rb5
-rw-r--r--spec/ruby/optional/capi/fixtures/const_get_at.rb5
-rw-r--r--spec/ruby/optional/capi/fixtures/const_get_from.rb5
-rw-r--r--spec/ruby/optional/capi/fixtures/const_get_object.rb3
-rw-r--r--spec/ruby/optional/capi/fixtures/encoding.rb3
-rw-r--r--spec/ruby/optional/capi/fixtures/foo.rb1
-rw-r--r--spec/ruby/optional/capi/fixtures/kernel.rb19
-rw-r--r--spec/ruby/optional/capi/fixtures/module.rb39
-rw-r--r--spec/ruby/optional/capi/fixtures/module_autoload.rb4
-rw-r--r--spec/ruby/optional/capi/fixtures/object.rb29
-rw-r--r--spec/ruby/optional/capi/fixtures/path_to_class.rb6
-rw-r--r--spec/ruby/optional/capi/fixtures/proc.rb20
-rw-r--r--spec/ruby/optional/capi/fixtures/read.txt1
-rw-r--r--spec/ruby/optional/capi/float_spec.rb43
-rw-r--r--spec/ruby/optional/capi/gc_spec.rb154
-rw-r--r--spec/ruby/optional/capi/globals_spec.rb298
-rw-r--r--spec/ruby/optional/capi/hash_spec.rb343
-rw-r--r--spec/ruby/optional/capi/integer_spec.rb307
-rw-r--r--spec/ruby/optional/capi/io_spec.rb788
-rw-r--r--spec/ruby/optional/capi/kernel_spec.rb929
-rw-r--r--spec/ruby/optional/capi/language_spec.rb37
-rw-r--r--spec/ruby/optional/capi/marshal_spec.rb46
-rw-r--r--spec/ruby/optional/capi/module_spec.rb435
-rw-r--r--spec/ruby/optional/capi/mutex_spec.rb102
-rw-r--r--spec/ruby/optional/capi/numeric_spec.rb495
-rw-r--r--spec/ruby/optional/capi/object_spec.rb1021
-rw-r--r--spec/ruby/optional/capi/proc_spec.rb188
-rw-r--r--spec/ruby/optional/capi/rake_helper.rb22
-rw-r--r--spec/ruby/optional/capi/range_spec.rb231
-rw-r--r--spec/ruby/optional/capi/rational_spec.rb57
-rw-r--r--spec/ruby/optional/capi/rbasic_spec.rb48
-rw-r--r--spec/ruby/optional/capi/regexp_spec.rb128
-rw-r--r--spec/ruby/optional/capi/set_spec.rb96
-rw-r--r--spec/ruby/optional/capi/shared/rbasic.rb27
-rw-r--r--spec/ruby/optional/capi/spec_helper.rb172
-rw-r--r--spec/ruby/optional/capi/st_spec.rb41
-rw-r--r--spec/ruby/optional/capi/string_spec.rb1558
-rw-r--r--spec/ruby/optional/capi/struct_spec.rb314
-rw-r--r--spec/ruby/optional/capi/symbol_spec.rb180
-rw-r--r--spec/ruby/optional/capi/thread_spec.rb195
-rw-r--r--spec/ruby/optional/capi/time_spec.rb301
-rw-r--r--spec/ruby/optional/capi/tracepoint_spec.rb56
-rw-r--r--spec/ruby/optional/capi/typed_data_spec.rb102
-rw-r--r--spec/ruby/optional/capi/util_spec.rb302
-rw-r--r--spec/ruby/optional/thread_safety/fixtures/classes.rb39
-rw-r--r--spec/ruby/optional/thread_safety/hash_spec.rb210
-rw-r--r--spec/ruby/security/cve_2010_1330_spec.rb19
-rw-r--r--spec/ruby/security/cve_2011_4815_spec.rb47
-rw-r--r--spec/ruby/security/cve_2013_4164_spec.rb15
-rw-r--r--spec/ruby/security/cve_2018_16396_spec.rb7
-rw-r--r--spec/ruby/security/cve_2018_6914_spec.rb55
-rw-r--r--spec/ruby/security/cve_2018_8778_spec.rb10
-rw-r--r--spec/ruby/security/cve_2018_8779_spec.rb30
-rw-r--r--spec/ruby/security/cve_2018_8780_spec.rb43
-rw-r--r--spec/ruby/security/cve_2019_8321_spec.rb20
-rw-r--r--spec/ruby/security/cve_2019_8322_spec.rb24
-rw-r--r--spec/ruby/security/cve_2019_8323_spec.rb46
-rw-r--r--spec/ruby/security/cve_2019_8325_spec.rb46
-rw-r--r--spec/ruby/security/cve_2020_10663_spec.rb49
-rw-r--r--spec/ruby/security/cve_2024_49761_spec.rb7
-rw-r--r--spec/ruby/shared/basicobject/method_missing.rb124
-rw-r--r--spec/ruby/shared/basicobject/send.rb128
-rw-r--r--spec/ruby/shared/enumerable/minmax.rb24
-rw-r--r--spec/ruby/shared/file/blockdev.rb9
-rw-r--r--spec/ruby/shared/file/chardev.rb9
-rw-r--r--spec/ruby/shared/file/directory.rb66
-rw-r--r--spec/ruby/shared/file/executable.rb83
-rw-r--r--spec/ruby/shared/file/executable_real.rb81
-rw-r--r--spec/ruby/shared/file/exist.rb19
-rw-r--r--spec/ruby/shared/file/file.rb45
-rw-r--r--spec/ruby/shared/file/grpowned.rb39
-rw-r--r--spec/ruby/shared/file/identical.rb51
-rw-r--r--spec/ruby/shared/file/owned.rb3
-rw-r--r--spec/ruby/shared/file/pipe.rb3
-rw-r--r--spec/ruby/shared/file/readable.rb49
-rw-r--r--spec/ruby/shared/file/readable_real.rb39
-rw-r--r--spec/ruby/shared/file/setgid.rb2
-rw-r--r--spec/ruby/shared/file/setuid.rb2
-rw-r--r--spec/ruby/shared/file/size.rb124
-rw-r--r--spec/ruby/shared/file/socket.rb33
-rw-r--r--spec/ruby/shared/file/sticky.rb29
-rw-r--r--spec/ruby/shared/file/symlink.rb46
-rw-r--r--spec/ruby/shared/file/world_readable.rb49
-rw-r--r--spec/ruby/shared/file/world_writable.rb49
-rw-r--r--spec/ruby/shared/file/writable.rb44
-rw-r--r--spec/ruby/shared/file/writable_real.rb49
-rw-r--r--spec/ruby/shared/file/zero.rb68
-rw-r--r--spec/ruby/shared/hash/key_error.rb23
-rw-r--r--spec/ruby/shared/io/putc.rb57
-rw-r--r--spec/ruby/shared/kernel/at_exit.rb73
-rw-r--r--spec/ruby/shared/kernel/complex.rb133
-rw-r--r--spec/ruby/shared/kernel/equal.rb54
-rw-r--r--spec/ruby/shared/kernel/fixtures/END.rb3
-rw-r--r--spec/ruby/shared/kernel/fixtures/at_exit.rb3
-rw-r--r--spec/ruby/shared/kernel/object_id.rb100
-rw-r--r--spec/ruby/shared/kernel/raise.rb392
-rw-r--r--spec/ruby/shared/process/abort.rb36
-rw-r--r--spec/ruby/shared/process/exit.rb126
-rw-r--r--spec/ruby/shared/process/fork.rb93
-rw-r--r--spec/ruby/shared/queue/clear.rb12
-rw-r--r--spec/ruby/shared/queue/close.rb14
-rw-r--r--spec/ruby/shared/queue/closed.rb12
-rw-r--r--spec/ruby/shared/queue/deque.rb164
-rw-r--r--spec/ruby/shared/queue/empty.rb12
-rw-r--r--spec/ruby/shared/queue/enque.rb18
-rw-r--r--spec/ruby/shared/queue/freeze.rb8
-rw-r--r--spec/ruby/shared/queue/length.rb9
-rw-r--r--spec/ruby/shared/queue/num_waiting.rb16
-rw-r--r--spec/ruby/shared/sizedqueue/enque.rb129
-rw-r--r--spec/ruby/shared/sizedqueue/max.rb47
-rw-r--r--spec/ruby/shared/sizedqueue/new.rb23
-rw-r--r--spec/ruby/shared/sizedqueue/num_waiting.rb12
-rw-r--r--spec/ruby/shared/string/end_with.rb61
-rw-r--r--spec/ruby/shared/string/start_with.rb76
-rw-r--r--spec/ruby/shared/string/times.rb58
-rw-r--r--spec/ruby/shared/time/strftime_for_date.rb273
-rw-r--r--spec/ruby/shared/time/strftime_for_time.rb181
-rw-r--r--spec/ruby/shared/time/yday.rb18
-rw-r--r--spec/ruby/shared/types/rb_num2dbl_fails.rb17
-rw-r--r--spec/ruby/spec_helper.rb38
4695 files changed, 264715 insertions, 0 deletions
diff --git a/spec/ruby/.gitignore b/spec/ruby/.gitignore
new file mode 100644
index 0000000000..3f1206a16e
--- /dev/null
+++ b/spec/ruby/.gitignore
@@ -0,0 +1,5 @@
+/Gemfile.lock
+/rubyspec_temp
+/ext
+/.ruby-version
+/.ruby-gemset
diff --git a/spec/ruby/.mspec.constants b/spec/ruby/.mspec.constants
new file mode 100644
index 0000000000..4da3633715
--- /dev/null
+++ b/spec/ruby/.mspec.constants
@@ -0,0 +1,236 @@
+Abbrev
+Addrinfo
+AliasObject
+AliasObject2
+AnonWithConstant
+ArbitraryException
+ArraySub
+ArraySubPush
+AryChild
+Base64
+BaseClass
+BasicSocket
+BeCloseToMatrixMatcher
+BigDecimal
+BigMath
+BitwiseAndTest
+BreakTest
+BreakTest2
+CAPI_SIZEOF_LONG
+CApiModuleSpecsAutoload
+CApiModuleSpecsModuleA
+CGI
+CMath
+CODE_LOADING_DIR
+CSAutoloadA
+CSAutoloadB
+CSAutoloadC
+CSAutoloadD
+CSV
+ChainedNextTest
+ChildClass
+ClassIdUnderAutoload
+ClassSpecDefineClass
+ClassSpecsKeywordWithSemicolon
+ClassSpecsKeywordWithoutSemicolon
+ClassSpecsNumber
+ClassUnderAutoload
+CodingUS_ASCII
+CodingUTF_8
+ComparisonTest
+ConstantSpecsIncludedModule
+ConstantSpecsTwo
+ConstantSpecsThree
+ConstantVisibility
+Coverage
+CoverageSpecs
+CustomArgumentError
+DRb
+DRbIdConv
+DRbObject
+DRbUndumped
+Date
+DateTime
+DefSpecNested
+DefSpecNestedB
+DefSpecSingleton
+DefSpecsLambdaVisibility
+DefineMethodByProcClass
+DefineMethodSpecClass
+DefineSingletonMethodSpecClass
+Delegator
+DescArray
+DescObjectTest
+Digest
+DumpableDir
+ERB
+EnsureInClassExample
+EnumerableSpecGrep
+EnumerableSpecGrep2
+EnumerableSpecIncludeP
+EnumerableSpecIncludeP11
+Etc
+EvalBindingA
+EvalBindingProcA
+Exception2MessageMapper
+ExceptionForMatrix
+Fcntl
+Fiddle
+FileStat
+FileUtils
+Find
+Forwardable
+GetoptLong
+HMACConstants
+HashStringsBinary
+HashStringsUSASCII
+HashStringsUTF8
+IPAddr
+IPSocket
+Importer
+IncludeSpecsClass
+IncludeSpecsMiddle
+IncludeSpecsTop
+IncludesMath
+JSON
+KSAutoloadA
+KSAutoloadB
+KSAutoloadBB
+KSAutoloadCallsRequire
+KSAutoloadD
+Logger
+MD5Constants
+MY_INPUT4_FOR_ERB
+Matrix
+MatrixSub
+MethodArity
+Meths
+MethsMore
+Mixin
+ModuleSpecsKeywordWithoutSemicolon
+ModuleSpecsToplevel
+ModuleSpecs_CS1
+ModuleSpecs_CS2
+ModuleSpecs_CS3
+MyClass
+MyClass0ForErb
+MyClass1ForErb
+MyClass1ForErb_
+MyClass2ForErb
+MyClass4ForErb
+MyFiber
+MyModule2ForErb
+MyString
+NamespaceTest
+Net
+OBJDIR
+OBJECT_SPACE_TOP_LEVEL_CONSTANT
+OFor
+ObjectSpaceFixtures
+ObjectSpecDup
+ObjectSpecDupInitCopy
+ObjectTest
+Observable
+Open3
+OpenSSL
+OpenStruct
+OperatorImplementor
+OptParse
+OptionParser
+OrAndXorTest
+OtherCustomException
+ParentClass
+Pathname
+Person
+Prime
+Private
+ProcFromMethod
+Psych
+RactorLocalSingleton
+REXML
+RUBY_SIGNALS
+RbReadline
+Readline
+ReceiverClass
+RegexpSpecsSubclass
+RegexpSpecsSubclassTwo
+Reline
+RescueInClassExample
+Resolv
+Ripper
+SHA1Constants
+SHA256Constants
+SHA384Constants
+SHA512Constants
+SameName
+ScanError
+Scanf
+SecondClass
+SecureRandom
+Set
+Shellwords
+SimpleDelegator
+SingleForwardable
+Singleton
+Socket
+SocketError
+SomeClass
+SortedSet
+SpecificExampleException
+Specs
+StrChild
+StrangeEach
+StringIO
+StringRefinement
+StringScanner
+StringSubclass
+StructClasses
+Syck
+Syslog
+TCPServer
+TCPSocket
+TSort
+Tempfile
+TestServer
+Timeout
+TimeoutError
+UDPSocket
+UNIXServer
+UNIXSocket
+URI
+UnaryMinusTest
+UnicodeNormalize
+UnloadableDumpableDir
+UserArray
+UserCustomConstructorString
+UserDefined
+UserDefinedImmediate
+UserDefinedString
+UserDefinedWithIvar
+UserHash
+UserHashInitParams
+UserMarshal
+UserMarshalWithClassName
+UserMarshalWithIvar
+UserObject
+UserPreviouslyDefinedWithInitializedIvar
+UserRegexp
+UserString
+Vector
+WEBrick
+WIN32OLE
+WIN32OLEQueryInterfaceError
+WIN32OLERuntimeError
+WIN32OLE_EVENT
+WIN32OLE_METHOD
+WIN32OLE_PARAM
+WIN32OLE_RECORD
+WIN32OLE_RUBYSPEC
+WIN32OLE_TYPE
+WIN32OLE_TYPELIB
+WIN32OLE_VARIABLE
+WIN32OLE_VARIANT
+WeakRef
+Win32
+YAML
+Zlib
diff --git a/spec/ruby/.rubocop.yml b/spec/ruby/.rubocop.yml
new file mode 100644
index 0000000000..0b5dcb80a2
--- /dev/null
+++ b/spec/ruby/.rubocop.yml
@@ -0,0 +1,204 @@
+inherit_from: .rubocop_todo.yml
+
+AllCops:
+ TargetRubyVersion: 3.2
+ DisplayCopNames: true
+ Exclude:
+ - command_line/fixtures/bad_syntax.rb
+ - core/exception/fixtures/syntax_error.rb
+ DisabledByDefault: true
+ NewCops: disable
+
+Layout/IndentationConsistency:
+ Enabled: true
+
+Layout/TrailingWhitespace:
+ Enabled: true
+
+Layout/TrailingEmptyLines:
+ Enabled: true
+ Exclude:
+ - library/coverage/fixtures/some_class.rb
+
+Layout/SpaceInLambdaLiteral:
+ Enabled: true
+ EnforcedStyle: require_space
+
+Lint:
+ Enabled: true
+
+# {...} has higher precedence than do ... end, on purpose
+Lint/AmbiguousBlockAssociation:
+ Enabled: false
+
+Lint/AssignmentInCondition:
+ Enabled: false
+
+Lint/BooleanSymbol:
+ Enabled: false
+
+Lint/DeprecatedOpenSSLConstant:
+ Exclude:
+ - library/openssl/digest/**/*.rb
+
+Lint/InterpolationCheck:
+ Enabled: false
+
+Lint/LiteralAsCondition:
+ Enabled: false
+
+# Required to support Ruby 3.0
+Lint/RedundantRequireStatement:
+ Exclude:
+ - core/fiber/**/*.rb
+ - library/fiber/**/*.rb
+ - optional/capi/fiber_spec.rb
+
+Lint/RedundantSplatExpansion:
+ Enabled: false
+
+Lint/RescueException:
+ Enabled: false
+
+Lint/UnifiedInteger:
+ Enabled: false
+
+Lint/UnusedBlockArgument:
+ Enabled: false
+
+Lint/UnusedMethodArgument:
+ Enabled: false
+
+Lint/UselessAssignment:
+ Enabled: false
+
+Lint/BinaryOperatorWithIdenticalOperands:
+ Enabled: false
+
+Lint/EmptyConditionalBody:
+ Enabled: false # buggy
+
+Lint/Void:
+ Enabled: false
+
+Lint/ConstantDefinitionInBlock:
+ Enabled: false
+
+Lint/RaiseException:
+ Enabled: false
+
+Lint/FloatComparison:
+ Enabled: false
+
+Lint/DeprecatedClassMethods:
+ Enabled: false
+
+Lint/UnreachableLoop:
+ Enabled: false
+
+Lint/MissingSuper:
+ Enabled: false
+
+Lint/UselessMethodDefinition:
+ Enabled: false
+
+Lint/UselessTimes:
+ Enabled: false
+
+Lint/MixedRegexpCaptureTypes:
+ Enabled: false
+
+Lint/DuplicateElsifCondition:
+ Enabled: false
+
+Lint/OutOfRangeRegexpRef:
+ Enabled: false
+
+Lint/InheritException:
+ Enabled: false
+
+Lint/SafeNavigationChain:
+ Enabled: false
+
+Lint/ElseLayout:
+ Exclude:
+ - 'language/if_spec.rb'
+
+Lint/EmptyExpression:
+ Exclude:
+ - 'language/**/*.rb'
+
+Lint/EmptyWhen:
+ Exclude:
+ - language/case_spec.rb
+ - optional/capi/spec_helper.rb
+
+Lint/ErbNewArguments:
+ Exclude:
+ - 'library/erb/new_spec.rb'
+
+Lint/FormatParameterMismatch:
+ Exclude:
+ - 'core/kernel/shared/sprintf.rb'
+ - 'core/string/modulo_spec.rb'
+
+Lint/NestedMethodDefinition:
+ Exclude:
+ - language/def_spec.rb
+ - language/fixtures/def.rb
+
+Lint/ShadowingOuterLocalVariable:
+ Exclude:
+ - 'core/binding/local_variables_spec.rb'
+ - 'core/kernel/local_variables_spec.rb'
+ - 'language/block_spec.rb'
+ - 'language/proc_spec.rb'
+
+Lint/UnreachableCode:
+ Exclude:
+ - 'core/enumerator/lazy/fixtures/classes.rb'
+ - 'core/kernel/catch_spec.rb'
+ - 'core/kernel/raise_spec.rb'
+ - 'core/kernel/throw_spec.rb'
+ - 'language/break_spec.rb'
+ - 'language/optional_assignments_spec.rb'
+ - 'language/fixtures/break.rb'
+ - 'language/fixtures/break_lambda_toplevel.rb'
+ - 'language/fixtures/break_lambda_toplevel_block.rb'
+ - 'language/fixtures/break_lambda_toplevel_method.rb'
+ - 'language/fixtures/return.rb'
+ - 'language/next_spec.rb'
+ - 'language/return_spec.rb'
+ - 'optional/capi/kernel_spec.rb'
+ - 'shared/kernel/raise.rb'
+
+Lint/UriRegexp:
+ Exclude:
+ - 'library/uri/regexp_spec.rb'
+
+Lint/Debugger:
+ Exclude:
+ - 'core/binding/fixtures/irb.rb'
+
+Lint/Loop:
+ Enabled: false
+
+Style/BlockComments:
+ Enabled: true
+
+Style/Lambda:
+ Enabled: true
+ EnforcedStyle: literal
+ Exclude:
+ - 'language/lambda_spec.rb'
+ - 'language/proc_spec.rb'
+ - 'language/numbered_parameters_spec.rb'
+ - 'language/it_parameter_spec.rb'
+ - 'core/kernel/lambda_spec.rb'
+
+Style/EmptyLambdaParameter:
+ Enabled: true
+
+Style/StabbyLambdaParentheses:
+ Enabled: true
+ EnforcedStyle: require_no_parentheses
diff --git a/spec/ruby/.rubocop_todo.yml b/spec/ruby/.rubocop_todo.yml
new file mode 100644
index 0000000000..f998002c6d
--- /dev/null
+++ b/spec/ruby/.rubocop_todo.yml
@@ -0,0 +1,138 @@
+# This configuration was generated by
+# `rubocop --auto-gen-config`
+# on 2026-05-29 08:10:07 UTC using RuboCop version 1.86.2.
+# The point is for the user to remove these configuration records
+# one by one as the offenses are removed from the code base.
+# Note that changes in the inspected code, or installation of new
+# versions of RuboCop, may require this file to be generated again.
+
+# Offense count: 2
+Lint/DuplicateCaseCondition:
+ Exclude:
+ - 'language/case_spec.rb'
+
+# Offense count: 20
+Lint/DuplicateMethods:
+ Exclude:
+ - 'core/array/fixtures/encoded_strings.rb'
+ - 'core/method/fixtures/classes.rb'
+ - 'core/module/const_added_spec.rb'
+ - 'core/module/define_method_spec.rb'
+ - 'core/module/fixtures/classes.rb'
+ - 'core/module/method_added_spec.rb'
+ - 'core/module/name_spec.rb'
+ - 'core/proc/new_spec.rb'
+ - 'core/unboundmethod/fixtures/classes.rb'
+ - 'fixtures/class.rb'
+ - 'language/assignments_spec.rb'
+
+# Offense count: 8
+Lint/EnsureReturn:
+ Exclude:
+ - 'language/fixtures/ensure.rb'
+ - 'language/fixtures/return.rb'
+ - 'language/return_spec.rb'
+
+# Offense count: 10
+Lint/FlipFlop:
+ Exclude:
+ - 'language/if_spec.rb'
+ - 'language/precedence_spec.rb'
+
+# Offense count: 10
+Lint/FloatOutOfRange:
+ Exclude:
+ - 'core/string/modulo_spec.rb'
+
+# Offense count: 3
+# This cop supports safe autocorrection (--autocorrect).
+Lint/ImplicitStringConcatenation:
+ Exclude:
+ - 'core/string/chilled_string_spec.rb'
+ - 'language/string_spec.rb'
+
+# Offense count: 4
+Lint/IneffectiveAccessModifier:
+ Exclude:
+ - 'core/kernel/fixtures/classes.rb'
+ - 'core/module/fixtures/classes.rb'
+ - 'language/fixtures/private.rb'
+
+# Offense count: 12
+# This cop supports safe autocorrection (--autocorrect).
+Lint/LiteralInInterpolation:
+ Exclude:
+ - 'core/module/refine_spec.rb'
+ - 'core/string/shared/to_sym.rb'
+ - 'language/alias_spec.rb'
+ - 'language/defined_spec.rb'
+ - 'language/fixtures/squiggly_heredoc.rb'
+ - 'language/string_spec.rb'
+ - 'language/symbol_spec.rb'
+ - 'language/undef_spec.rb'
+
+# Offense count: 8
+# This cop supports safe autocorrection (--autocorrect).
+Lint/MultipleComparison:
+ Exclude:
+ - 'language/precedence_spec.rb'
+
+# Offense count: 9
+# This cop supports safe autocorrection (--autocorrect).
+Lint/ParenthesesAsGroupedExpression:
+ Exclude:
+ - 'language/block_spec.rb'
+ - 'language/method_spec.rb'
+
+# Offense count: 1
+# This cop supports unsafe autocorrection (--autocorrect-all).
+# Configuration parameters: AllowedMethods, InferNonNilReceiver, AdditionalNilMethods.
+# AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal?
+# AdditionalNilMethods: present?, blank?, try, try!
+Lint/RedundantSafeNavigation:
+ Exclude:
+ - 'language/safe_navigator_spec.rb'
+ - 'language/fixtures/rescue_captures.rb'
+
+# Offense count: 2
+# This cop supports safe autocorrection (--autocorrect).
+Lint/RedundantStringCoercion:
+ Exclude:
+ - 'core/io/print_spec.rb'
+
+# Offense count: 1
+# Configuration parameters: AllowRBSInlineAnnotation.
+Lint/SelfAssignment:
+ Exclude:
+ - 'core/gc/auto_compact_spec.rb'
+
+# Offense count: 4
+# Configuration parameters: IgnoreImplicitReferences.
+Lint/ShadowedArgument:
+ Exclude:
+ - 'language/fixtures/super.rb'
+
+# Offense count: 49
+# Configuration parameters: AllowComments, AllowNil.
+Lint/SuppressedException:
+ Enabled: false
+
+# Offense count: 9
+# Configuration parameters: AllowKeywordBlockArguments.
+Lint/UnderscorePrefixedVariableName:
+ Exclude:
+ - 'core/io/pipe_spec.rb'
+ - 'core/io/popen_spec.rb'
+ - 'language/block_spec.rb'
+
+# Offense count: 9
+# This cop supports safe autocorrection (--autocorrect).
+# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods.
+Lint/UselessAccessModifier:
+ Exclude:
+ - 'core/module/define_method_spec.rb'
+ - 'core/module/fixtures/classes.rb'
+ - 'core/module/module_function_spec.rb'
+ - 'core/module/private_class_method_spec.rb'
+ - 'language/fixtures/def.rb'
+ - 'language/fixtures/send.rb'
diff --git a/spec/ruby/CONTRIBUTING.md b/spec/ruby/CONTRIBUTING.md
new file mode 100644
index 0000000000..a474e205f0
--- /dev/null
+++ b/spec/ruby/CONTRIBUTING.md
@@ -0,0 +1,297 @@
+Contributions are much appreciated.
+Please open a pull request or add an issue to discuss what you intend to work on.
+If the pull requests passes the CI and conforms to the existing style of specs, it will be merged.
+
+### File organization
+
+Spec are grouped in 5 separate top-level groups:
+
+* `command_line`: for the ruby executable command-line flags (`-v`, `-e`, etc)
+* `language`: for the language keywords and syntax constructs (`if`, `def`, `A::B`, etc)
+* `core`: for the core methods (`Integer#+`, `String#upcase`, no need to require anything)
+* `library`: for the standard libraries methods (`CSV.new`, `YAML.parse`, need to require the stdlib)
+* `optional/capi`: for functions available to the Ruby C-extension API
+
+The exact file for methods is decided by the `#owner` of a method, for instance for `#group_by`:
+
+```ruby
+> [].method(:group_by)
+=> #<Method: Array(Enumerable)#group_by>
+> [].method(:group_by).owner
+=> Enumerable
+```
+
+Which should therefore be specified in `core/enumerable/group_by_spec.rb`.
+
+### MkSpec - a tool to generate the spec structure
+
+If you want to create new specs, you should use `mkspec`, part of [MSpec](http://github.com/ruby/mspec).
+
+ $ ../mspec/bin/mkspec -h
+
+#### Creating files for unspecified modules or classes
+
+For instance, to create specs for `forwardable`:
+
+ $ ../mspec/bin/mkspec -b library -rforwardable -c Forwardable
+
+Specify `core` or `library` as the `base`.
+
+#### Finding unspecified core methods
+
+This is very easy, just run the command below in your `spec` directory.
+`ruby` must be a recent version of MRI.
+
+ $ ruby --disable-gem ../mspec/bin/mkspec
+
+You might also want to search for:
+
+ it "needs to be reviewed for spec completeness"
+
+which indicates the file was generated but the method unspecified.
+
+### Matchers and expectations
+
+Here is a list of frequently-used matchers, which should be enough for most specs.
+There are a few extra specific matchers used in the couple specs that need it.
+
+The general idea is: add `.should` just before the predicate you expect to be truthy, and done!
+This works for most needs and provides a great error when it fails.
+It's immediately clear which method is used and there no need to remember a mapping like in RSpec between e.g. `eq` and `==`.
+See [this blog post](https://eregon.me/blog/2019/10/07/a-new-should-syntax.html) for the motivation behind that syntax.
+
+#### Comparison matchers
+
+```ruby
+(1 + 2).should == 3 # Calls #==
+(1 + 2).should_not == 5
+
+File.should.equal?(File) # Calls #equal? (tests identity)
+(1 + 2).should.eql?(3) # Calls #eql? (Hash equality)
+
+1.should < 2
+2.should <= 2
+3.should >= 3
+4.should > 3
+
+"Hello".should =~ /l{2}/ # Calls #=~ (Regexp match)
+```
+
+#### Predicate matchers
+
+```ruby
+[].should.empty?
+[1,2,3].should.include?(2)
+
+"hello".should.start_with?("h")
+"hello".should.end_with?("o")
+
+(0.1 + 0.2).should be_close(0.3, TOLERANCE) # (0.2-0.1).abs < TOLERANCE
+(0.0/0.0).should.nan?
+
+3.14.should.instance_of?(Float) # Calls #instance_of?
+3.14.should.is_a?(Numeric) # Calls #is_a?
+
+3.14.should.respond_to?(:to_i)
+Integer.should.method_defined?(:+, false)
+```
+
+#### Exception matchers
+
+```ruby
+-> {
+ raise "oops"
+}.should.raise(RuntimeError, /oops/)
+
+-> {
+ raise "oops"
+}.should.raise(RuntimeError) { |e|
+ # Custom checks on the Exception object
+ e.message.should.include?("oops")
+ e.cause.should == nil
+}
+```
+
+##### `should_not.raise`
+
+**Avoid this!** Instead, use an expectation testing what the code in the lambda does.
+If an exception is raised, it will fail the example anyway.
+
+```ruby
+-> { ... }.should_not.raise
+```
+
+#### Warning matcher
+
+```ruby
+-> {
+ Fixnum
+}.should complain(/constant ::Fixnum is deprecated/) # Expect a warning
+```
+
+### Guards
+
+Different guards are available as defined by mspec.
+Here is a list of the most commonly-used guards:
+
+#### Version guards
+
+```ruby
+ruby_version_is ""..."3.2" do
+ # Specs for RUBY_VERSION < 3.2
+end
+
+ruby_version_is "3.2" do
+ # Specs for RUBY_VERSION >= 3.2
+end
+```
+
+#### Platform guards
+
+```ruby
+platform_is :windows do
+ # Specs only valid on Windows
+end
+
+platform_is_not :windows do
+ # Specs valid on platforms other than Windows
+end
+
+platform_is :linux, :darwin do # OR
+end
+
+platform_is_not :linux, :darwin do # Not Linux and not Darwin
+end
+
+platform_is pointer_size: 64 do
+ # 64-bit platform
+end
+
+big_endian do
+ # Big-endian platform
+end
+```
+
+#### Guard for bug
+
+In case there is a bug in MRI and the fix will be backported to previous versions.
+If it is not backported or not likely, use `ruby_version_is` instead.
+First, file a bug at https://bugs.ruby-lang.org/.
+The problem is `ruby_bug` would make non-MRI implementations fail this spec while MRI itself does not pass it, so it should only be used if the bug is/will be fixed and backported.
+Otherwise, non-MRI implementations would have to choose between being incompatible with the latest release of MRI (which has the bug) to pass the spec, or behave the same as the latest release of MRI (which has the bug) and fail the spec, both which make no sense.
+
+IOW, `ruby_bug '#NN', ''...'X.Y' do` is equivalent to `guard_not { RUBY_ENGINE == "ruby" && ruby_version_is ''...'X.Y' } do`. So it skips tests on MRI on specified versions (where a bug is present) and runs tests on alternative implementations only.
+
+```ruby
+ruby_bug '#13669', ''...'3.2' do
+ it "works like this" do
+ # Specify the expected behavior here, not the bug
+ end
+end
+```
+
+#### Combining guards
+
+```ruby
+guard -> { platform_is :windows and ruby_version_is ""..."3.2" } do
+ # Windows and RUBY_VERSION < 3.2
+end
+
+guard_not -> { platform_is :windows and ruby_version_is ""..."3.2" } do
+ # The opposite
+end
+```
+
+#### Custom guard
+
+```ruby
+max_uint = (1 << 32) - 1
+guard -> { max_uint <= fixnum_max } do
+end
+```
+
+Custom guards are better than a simple `if` as they allow [mspec commands](https://github.com/ruby/mspec/issues/30#issuecomment-312487779) to work properly.
+
+#### Implementation-specific behaviors
+
+In general, the usage of guards should be minimized as possible.
+
+There are no guards to define implementation-specific behavior because
+the Ruby Spec Suite defines common behavior and not implementation details.
+Use the implementation test suite for these.
+
+If an implementation does not support some feature, simply tag the related specs as failing instead.
+
+### Shared Specs
+
+Often throughout Ruby, identical functionality is used by different methods and modules. In order
+to avoid duplication of specs, we have shared specs that are re-used in other specs. The use is a
+bit tricky however, so let's go over it.
+
+Commonly, if a shared spec is only reused within its own module, the shared spec will live within a
+shared directory inside that module's directory. For example, the `core/hash/shared/iteration.rb` spec is
+only used by `Hash` specs, and so it lives inside `core/hash/shared/`.
+
+When a shared spec is used across multiple modules or classes, it lives within the `shared/` directory.
+An example of this is the `shared/file/socket.rb` which is used by `core/file/socket_spec.rb`,
+`core/filetest/socket_spec.rb`, and `core/file/state/socket_spec.rb` and so it lives in the root `shared/`.
+
+Defining a shared spec involves adding a `shared: true` option to the top-level `describe` block. This
+will signal not to run the specs directly by the runner. Shared specs have access to two instance
+variables from the implementor spec: `@method` and `@object`, which the implementor spec will pass in.
+
+Here's an example of a snippet of a shared spec and two specs which integrates it:
+
+```ruby
+# core/hash/shared/iteration.rb
+describe :hash_iteration_no_block, shared: true do
+ it "returns an Enumerator if called on a non-empty hash without a block" do
+ { 1 => 2 }.send(@method).should.instance_of?(Enumerator)
+ end
+end
+
+# core/hash/select_spec.rb
+describe "Hash#select" do
+ it_behaves_like :hash_iteration_no_block, :select
+end
+
+# core/hash/reject_spec.rb
+describe "Hash#reject" do
+ it_behaves_like :hash_iteration_no_block, :reject
+end
+```
+
+In the example, the first `describe` defines the shared spec `:hash_iteration_no_block`, which defines a spec that
+calls the `@method` method with an expectation. In the implementor spec, we use `it_behaves_like` to
+integrate the shared spec. `it_behaves_like` takes 3 parameters: the key of the shared spec, a method,
+and an object. These last two parameters are accessible via `@method` and `@object` in the shared spec.
+
+Sometimes, shared specs require more context from the implementor class than a simple object. We can address
+this by passing a lambda as the method, which will have the scope of the implementor. Here's an example of
+how this is used currently:
+
+```ruby
+describe :kernel_sprintf, shared: true do
+ it "raises TypeError exception if cannot convert to Integer" do
+ -> { @method.call("%b", Object.new) }.should.raise(TypeError)
+ end
+end
+
+describe "Kernel#sprintf" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ sprintf(format, *args)
+ }
+end
+
+describe "Kernel.sprintf" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ Kernel.sprintf(format, *args)
+ }
+end
+```
+
+In the above example, the method being passed is a lambda that triggers the specific conditions of the shared spec.
+
+### Style
+
+Do not leave any trailing space and follow the existing style.
diff --git a/spec/ruby/LICENSE b/spec/ruby/LICENSE
new file mode 100644
index 0000000000..d581dd1c9f
--- /dev/null
+++ b/spec/ruby/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2008 Engine Yard, Inc. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/spec/ruby/README.md b/spec/ruby/README.md
new file mode 100644
index 0000000000..b259a97e21
--- /dev/null
+++ b/spec/ruby/README.md
@@ -0,0 +1,167 @@
+# The Ruby Spec Suite
+
+[![Actions Build Status](https://github.com/ruby/spec/workflows/CI/badge.svg)](https://github.com/ruby/spec/actions)
+
+The Ruby Spec Suite, abbreviated `ruby/spec`, is a test suite for the behavior of the Ruby programming language.
+
+### Description and Motivation
+
+It is not a standardized specification like the ISO one, and does not aim to become one.
+Instead, it is a practical tool to describe and test the behavior of Ruby with code.
+
+Every example code has a textual description, which presents several advantages:
+
+* It is easier to understand the intent of the author
+* It documents how recent versions of Ruby should behave
+* It helps Ruby implementations to agree on a common behavior
+
+The specs are written with syntax similar to RSpec 2.
+They are run with MSpec, the purpose-built framework for running the Ruby Spec Suite.
+For more information, see the [MSpec](https://github.com/ruby/mspec) project.
+
+The specs describe the [language syntax](language/), the [core library](core/), the [standard library](library/), the [C API for extensions](optional/capi) and the [command line flags](command_line/).
+The language specs are grouped by keyword while the core and standard library specs are grouped by class and method.
+
+ruby/spec is known to be tested in these implementations for every commit:
+
+* [MRI](https://rubyci.org/) on 30 platforms and 4 versions
+* [JRuby](https://github.com/jruby/jruby/tree/master/spec/ruby) for both 1.7 and 9.x
+* [TruffleRuby](https://github.com/truffleruby/truffleruby/tree/master/spec/ruby)
+* [Opal](https://github.com/opal/opal/tree/master/spec)
+* [Artichoke](https://github.com/artichoke/spec/tree/artichoke-vendor)
+
+ruby/spec describes the behavior of Ruby 3.3 and more recent Ruby versions.
+More precisely, every latest stable MRI release should [pass](https://github.com/ruby/spec/actions/workflows/ci.yml) all specs of ruby/spec (3.3.x, 3.4.x, etc), and those are tested in CI.
+
+### Synchronization with Ruby Implementations
+
+The specs are synchronized both ways around once a month by @andrykonchin between ruby/spec, MRI, JRuby and TruffleRuby,
+using [this script](https://github.com/ruby/mspec/blob/master/tool/sync/sync-rubyspec.rb).
+Each of these repositories has a full copy of the specs under `spec/ruby` to ease editing specs.
+Any of these repositories can be used to add or edit specs, use what is most convenient for you.
+
+For *testing* the development version of a Ruby implementation, one should always test against that implementation's copy of the specs under `spec/ruby`, as that's what the Ruby implementation tests against in their CI.
+Also, this repository doesn't always contain the latest spec changes from MRI (it's synchronized monthly), and does not contain tags (specs marked as failing on that Ruby implementation).
+Running specs on a Ruby implementation can be done with:
+
+```console
+$ cd ruby_implementation/spec/ruby
+# Add ../ruby_implementation/bin in PATH, or pass -t /path/to/bin/ruby
+$ ../mspec/bin/mspec
+```
+
+### Specs for old Ruby versions
+
+For older specs try these commits:
+
+* Ruby 2.0.0-p647 - [Suite](https://github.com/ruby/spec/commit/245862558761d5abc676843ef74f86c9bcc8ea8d) using [MSpec](https://github.com/ruby/mspec/commit/f90efa068791064f955de7a843e96e2d7d3041c2) (may encounter 2 failures)
+* Ruby 2.1.9 - [Suite](https://github.com/ruby/spec/commit/f029e65241374386077ac500add557ae65069b55) using [MSpec](https://github.com/ruby/mspec/commit/55568ea3918c6380e64db8c567d732fa5781efed)
+* Ruby 2.2.10 - [Suite](https://github.com/ruby/spec/commit/cbaa0e412270c944df0c2532fc500c920dba0e92) using [MSpec](https://github.com/ruby/mspec/commit/d84d7668449e96856c5f6bac8cb1526b6d357ce3)
+* Ruby 2.3.8 - [Suite](https://github.com/ruby/spec/commit/dc733114d8ae66a3368ba3a98422c50147a76ba5) using [MSpec](https://github.com/ruby/mspec/commit/4599bc195fb109f2a482a01c32a7d659518369ea)
+* Ruby 2.4.10 - [Suite](https://github.com/ruby/spec/commit/bce4f2b81d6c31db67cf4d023a0625ceadde59bd) using [MSpec](https://github.com/ruby/mspec/commit/e7eb8aa4c26495b7b461e687d950b96eb08b3ff2)
+* Ruby 2.5.9 - [Suite](https://github.com/ruby/spec/commit/c503335d3d9f6ec6ef24de60a0716c34af69b64f) using [MSpec](https://github.com/ruby/mspec/commit/0091e8a62e954717cd54641f935eaf1403692041)
+* Ruby 2.6.10 - [Suite](https://github.com/ruby/spec/commit/aaf998fb8c92c4e63ad423a2e7ca6e6921818c6e) using [MSpec](https://github.com/ruby/mspec/commit/5e36c684e9e2b92b1187589bba1df22c640a8661)
+* Ruby 2.7.8 - [Suite](https://github.com/ruby/spec/commit/93787e6035c925b593a9c0c6fb0e7e07a6f1df1f) using [MSpec](https://github.com/ruby/mspec/commit/1d8cf64722d8a7529f7cd205be5f16a89b7a67fd)
+* Ruby 3.0.7 - [Suite](https://github.com/ruby/spec/commit/affef93d9940f615e4836f64b011da211f570913) using [MSpec](https://github.com/ruby/mspec/commit/0aabb3e548eb5ea6cad0125f8f46cee34542b6b7)
+* Ruby 3.1.6 - [Suite](https://github.com/ruby/spec/commit/ec960f2389d1c2265d32397fa8afa6d462014efc) using [MSpec](https://github.com/ruby/mspec/commit/484310dbed35b84c74484fd674602f88c42d063a)
+* Ruby 3.2.9 - [Suite](https://github.com/ruby/spec/commit/97f076242b7fc6e60703e6a6053365065cd6fc30) using [MSpec](https://github.com/ruby/mspec/commit/54704795e21128a930af2021c72c49cb87065134)
+
+### Running the specs
+
+First, clone this repository:
+
+ $ git clone https://github.com/ruby/spec.git
+
+Then move to it:
+
+ $ cd spec
+
+Clone [MSpec](https://github.com/ruby/mspec):
+
+ $ git clone https://github.com/ruby/mspec.git ../mspec
+
+And run the spec suite:
+
+ $ ../mspec/bin/mspec
+
+This will execute all the specs using the executable named `ruby` on your current PATH.
+
+### Running Specs with a Specific Ruby Implementation
+
+Use the `-t` option to specify the Ruby implementation with which to run the specs.
+The argument is either a full path to the Ruby binary, or an executable in `$PATH`.
+
+ $ ../mspec/bin/mspec -t /path/to/some/bin/ruby
+
+### Running Selected Specs
+
+To run a single spec file, pass the filename to `mspec`:
+
+ $ ../mspec/bin/mspec core/kernel/kind_of_spec.rb
+
+You can also pass a directory, in which case all specs in that directories will be run:
+
+ $ ../mspec/bin/mspec core/kernel
+
+Finally, you can also run them per group as defined in `default.mspec`.
+The following command will run all language specs:
+
+ $ ../mspec/bin/mspec :language
+
+In similar fashion, the following commands run the respective specs:
+
+ $ ../mspec/bin/mspec :core
+ $ ../mspec/bin/mspec :library
+ $ ../mspec/bin/mspec :capi
+
+### Sanity Checks When Running Specs
+
+A number of checks for various kind of "leaks" (file descriptors, temporary files,
+threads, subprocesses, `ENV`, `ARGV`, global encodings, top-level constants) can be
+enabled with `CHECK_LEAKS=true`:
+
+ $ CHECK_LEAKS=true ../mspec/bin/mspec
+
+New top-level constants should only be introduced when needed or follow the
+pattern `<ClassBeingTested>Specs` such as `module StringSpecs`.
+Other constants used for testing should be nested under such a module.
+
+Exceptions to these rules are contained in the file `.mspec.constants`.
+MSpec can automatically add new top-level constants in this file with:
+
+ $ CHECK_LEAKS=save mspec ../mspec/bin/mspec file
+
+### Running Specs on S390x CPU Architecture
+
+Run the specs with `DFLTCC=0` if you see failing specs related to the zlib library on s390x CPU architecture. The failures can happen with the zlib library applying the patch madler/zlib#410 to enable the deflate algorithm producing a different compressed byte stream.
+
+ $ DFLTCC=0 ../mspec/bin/mspec
+
+### Contributing and Writing Specs
+
+See [CONTRIBUTING.md](https://github.com/ruby/spec/blob/master/CONTRIBUTING.md) for documentation about contributing and writing specs (guards, matchers, etc).
+
+### Dependencies
+
+These command-line executables are needed to run the specs.
+
+* `echo`
+* `stat` for `core/file/*time_spec.rb`
+* `find` for `core/file/fixtures/file_types.rb` (package `findutils`, not needed on Windows)
+
+The file `/etc/services` is required for socket specs (package `netbase` on Debian, not needed on Windows).
+
+### Socket specs from rubysl-socket
+
+Most specs under `library/socket` were imported from the rubysl-socket project (which is no longer on GitHub).
+The 3 copyright holders of rubysl-socket, Yorick Peterse, Chuck Remes and
+Brian Shirai, agreed to relicense those specs under the MIT license in ruby/spec.
+
+### History and RubySpec
+
+This project was originally born from [Rubinius](https://github.com/rubinius/rubinius) tests being converted to the spec style.
+The revision history of these specs is available [here](https://github.com/ruby/spec/blob/2b886623/CHANGES.before-2008-05-10).
+These specs were later extracted to their own project, RubySpec, with a specific vision and principles.
+At the end of 2014, Brian Shirai, the creator of RubySpec, decided to [end RubySpec](http://rubinius.com/2014/12/31/matz-s-ruby-developers-don-t-use-rubyspec/).
+A couple months later, the different repositories were merged and [the project was revived](https://eregon.github.io/rubyspec/2015/07/29/rubyspec-is-reborn.html).
+On 12 January 2016, the name was changed to "The Ruby Spec Suite" for clarity and to let the RubySpec ideology rest in peace.
diff --git a/spec/ruby/TODO b/spec/ruby/TODO
new file mode 100644
index 0000000000..f81f070d71
--- /dev/null
+++ b/spec/ruby/TODO
@@ -0,0 +1,8 @@
+* Decide a way to test methods that are only visible given a specific
+ command-line option. For example, Kernel#gsub with -n/-p on 1.9.
+* Look at automating discovery of guarded bugs which have been fixed.
+* Use mocks for all Math functions that coerce with #to_f; currently a fixture
+ is used.
+* investigate slow specs (run with -fp) and make them faster.
+* restore some caller specs from 642bf529
+* restore refinements specs and update. See 56c5528f and f20a62e8.
diff --git a/spec/ruby/bin/rubocop b/spec/ruby/bin/rubocop
new file mode 100755
index 0000000000..0937c47906
--- /dev/null
+++ b/spec/ruby/bin/rubocop
@@ -0,0 +1,12 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require 'bundler/inline'
+
+gemfile do
+ source 'https://rubygems.org'
+
+ gem 'rubocop', '1.86.2'
+end
+
+exec(Gem.bin_path('rubocop', 'rubocop'), *ARGV)
diff --git a/spec/ruby/command_line/backtrace_limit_spec.rb b/spec/ruby/command_line/backtrace_limit_spec.rb
new file mode 100644
index 0000000000..4d57bc268b
--- /dev/null
+++ b/spec/ruby/command_line/backtrace_limit_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../spec_helper'
+
+describe "The --backtrace-limit command line option" do
+ ruby_version_is ""..."3.4" do
+ it "limits top-level backtraces to a given number of entries" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "top 2>&1", exit_status: 1)
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+top
+/fixtures/backtrace.rb:2:in `a': oops (RuntimeError)
+\tfrom /fixtures/backtrace.rb:6:in `b'
+\tfrom /fixtures/backtrace.rb:10:in `c'
+\t ... 2 levels...
+ MSG
+ end
+
+ it "affects Exception#full_message" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "full_message 2>&1")
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+full_message
+/fixtures/backtrace.rb:2:in `a': oops (RuntimeError)
+\tfrom /fixtures/backtrace.rb:6:in `b'
+\tfrom /fixtures/backtrace.rb:10:in `c'
+\t ... 2 levels...
+ MSG
+ end
+
+ it "does not affect Exception#backtrace" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "backtrace 2>&1")
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+backtrace
+/fixtures/backtrace.rb:2:in `a'
+/fixtures/backtrace.rb:6:in `b'
+/fixtures/backtrace.rb:10:in `c'
+/fixtures/backtrace.rb:14:in `d'
+/fixtures/backtrace.rb:29:in `<main>'
+ MSG
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "limits top-level backtraces to a given number of entries" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "top 2>&1", exit_status: 1)
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+top
+/fixtures/backtrace.rb:2:in 'Object#a': oops (RuntimeError)
+\tfrom /fixtures/backtrace.rb:6:in 'Object#b'
+\tfrom /fixtures/backtrace.rb:10:in 'Object#c'
+\t ... 2 levels...
+ MSG
+ end
+
+ it "affects Exception#full_message" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "full_message 2>&1")
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+full_message
+/fixtures/backtrace.rb:2:in 'Object#a': oops (RuntimeError)
+\tfrom /fixtures/backtrace.rb:6:in 'Object#b'
+\tfrom /fixtures/backtrace.rb:10:in 'Object#c'
+\t ... 2 levels...
+ MSG
+ end
+
+ it "does not affect Exception#backtrace" do
+ file = fixture(__FILE__ , "backtrace.rb")
+ out = ruby_exe(file, options: "--backtrace-limit=2", args: "backtrace 2>&1")
+ out = out.gsub(__dir__, '')
+
+ out.should == <<-MSG
+backtrace
+/fixtures/backtrace.rb:2:in 'Object#a'
+/fixtures/backtrace.rb:6:in 'Object#b'
+/fixtures/backtrace.rb:10:in 'Object#c'
+/fixtures/backtrace.rb:14:in 'Object#d'
+/fixtures/backtrace.rb:29:in '<main>'
+ MSG
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_0_spec.rb b/spec/ruby/command_line/dash_0_spec.rb
new file mode 100755
index 0000000000..2ce4f49b5e
--- /dev/null
+++ b/spec/ruby/command_line/dash_0_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "The -0 command line option" do
+ it "sets $/ and $-0" do
+ ruby_exe("puts $/, $-0", options: "-072").should == ":\n:\n"
+ end
+
+ ruby_version_is "4.0" do
+ it "sets $/ and $-0 as a frozen string" do
+ ruby_exe("puts $/.frozen?, $-0.frozen?", options: "-072").should == "true\ntrue\n"
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_a_spec.rb b/spec/ruby/command_line/dash_a_spec.rb
new file mode 100644
index 0000000000..43d923ce16
--- /dev/null
+++ b/spec/ruby/command_line/dash_a_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../spec_helper'
+
+describe "The -a command line option" do
+ before :each do
+ @names = fixture __FILE__, "full_names.txt"
+ end
+
+ it "runs the code in loop conditional on Kernel.gets()" do
+ ruby_exe("puts $F.last", options: "-n -a",
+ args: " < #{@names}").should ==
+ "jones\nfield\ngrey\n"
+ end
+
+ it "sets $-a" do
+ ruby_exe("puts $-a", options: "-n -a",
+ args: " < #{@names}").should ==
+ "true\ntrue\ntrue\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_c_spec.rb b/spec/ruby/command_line/dash_c_spec.rb
new file mode 100644
index 0000000000..6b3a5de685
--- /dev/null
+++ b/spec/ruby/command_line/dash_c_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "The -c command line option" do
+ it "checks syntax in given file" do
+ ruby_exe(nil, args: "-c #{__FILE__}").chomp.should == "Syntax OK"
+ end
+
+ it "checks syntax in -e strings" do
+ ruby_exe(nil, args: "-c -e 'puts 1' -e 'hello world'").chomp.should == "Syntax OK"
+ end
+
+ #Also needs spec for reading from STDIN
+end
diff --git a/spec/ruby/command_line/dash_d_spec.rb b/spec/ruby/command_line/dash_d_spec.rb
new file mode 100644
index 0000000000..26891b4791
--- /dev/null
+++ b/spec/ruby/command_line/dash_d_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../spec_helper'
+
+describe "The -d command line option" do
+ before :each do
+ @script = fixture __FILE__, "debug.rb"
+ end
+
+ it "sets $DEBUG to true" do
+ ruby_exe(@script, options: "-d",
+ args: "0 2> #{File::NULL}").chomp.should == "$DEBUG true"
+ end
+
+ it "sets $VERBOSE to true" do
+ ruby_exe(@script, options: "-d",
+ args: "1 2> #{File::NULL}").chomp.should == "$VERBOSE true"
+ end
+
+ it "sets $-d to true" do
+ ruby_exe(@script, options: "-d",
+ args: "2 2> #{File::NULL}").chomp.should == "$-d true"
+ end
+end
diff --git a/spec/ruby/command_line/dash_e_spec.rb b/spec/ruby/command_line/dash_e_spec.rb
new file mode 100644
index 0000000000..24ed34376d
--- /dev/null
+++ b/spec/ruby/command_line/dash_e_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../spec_helper'
+
+describe "The -e command line option" do
+ it "evaluates the given string" do
+ ruby_exe("puts 'foo'").chomp.should == "foo"
+ end
+
+ it "joins multiple strings with newlines" do
+ ruby_exe(nil, args: %Q{-e "puts 'hello" -e "world'" 2>&1}).chomp.should == "hello\nworld"
+ end
+
+ it "uses 'main' as self" do
+ ruby_exe("puts self", escape: false).chomp.should == "main"
+ end
+
+ it "uses '-e' as file" do
+ ruby_exe("puts __FILE__", escape: false).chomp.should == "-e"
+ end
+
+ it "uses '-e' in $0" do
+ system(*ruby_exe, '-e', 'exit $0 == "-e"').should == true
+ end
+
+ #needs to test return => LocalJumpError
+
+ describe "with -n and an Integer range" do
+ before :each do
+ @script = "-ne 'print if %s' #{fixture(__FILE__, "conditional_range.txt")}"
+ end
+
+ it "mimics an awk conditional by comparing an inclusive-end range with $." do
+ ruby_exe(nil, args: (@script % "2..3")).should == "2\n3\n"
+ ruby_exe(nil, args: (@script % "2..2")).should == "2\n"
+ end
+
+ it "mimics a sed conditional by comparing an exclusive-end range with $." do
+ ruby_exe(nil, args: (@script % "2...3")).should == "2\n3\n"
+ ruby_exe(nil, args: (@script % "2...2")).should == "2\n3\n4\n5\n"
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_encoding_spec.rb b/spec/ruby/command_line/dash_encoding_spec.rb
new file mode 100644
index 0000000000..5803d328c1
--- /dev/null
+++ b/spec/ruby/command_line/dash_encoding_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../spec_helper'
+
+describe "The --encoding command line option" do
+ before :each do
+ @test_string = "print [Encoding.default_external.name, Encoding.default_internal&.name].inspect"
+ @enc2 = Encoding::ISO_8859_1
+ end
+
+ describe "sets Encoding.default_external and optionally Encoding.default_internal" do
+ it "if given a single encoding with an =" do
+ ruby_exe(@test_string, options: "--disable-gems --encoding=big5").should == [Encoding::Big5.name, nil].inspect
+ end
+
+ it "if given a single encoding as a separate argument" do
+ ruby_exe(@test_string, options: "--disable-gems --encoding big5").should == [Encoding::Big5.name, nil].inspect
+ end
+
+ it "if given two encodings with an =" do
+ ruby_exe(@test_string, options: "--disable-gems --encoding=big5:#{@enc2}").should == [Encoding::Big5.name, @enc2.name].inspect
+ end
+
+ it "if given two encodings as a separate argument" do
+ ruby_exe(@test_string, options: "--disable-gems --encoding big5:#{@enc2}").should == [Encoding::Big5.name, @enc2.name].inspect
+ end
+ end
+
+ it "does not accept a third encoding" do
+ options = {
+ options: "--disable-gems --encoding big5:#{@enc2}:utf-32le",
+ args: "2>&1",
+ exit_status: 1
+ }
+
+ ruby_exe(@test_string, options).should =~ /extra argument for --encoding: utf-32le/
+ end
+end
diff --git a/spec/ruby/command_line/dash_external_encoding_spec.rb b/spec/ruby/command_line/dash_external_encoding_spec.rb
new file mode 100644
index 0000000000..f052674dc8
--- /dev/null
+++ b/spec/ruby/command_line/dash_external_encoding_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+
+describe 'The --external-encoding command line option sets Encoding.default_external' do
+ before :each do
+ @test_string = "print Encoding.default_external.name"
+ end
+
+ it "if given an encoding with an =" do
+ ruby_exe(@test_string, options: '--external-encoding=big5').should == Encoding::Big5.name
+ end
+
+ it "if given an encoding as a separate argument" do
+ ruby_exe(@test_string, options: '--external-encoding big5').should == Encoding::Big5.name
+ end
+end
diff --git a/spec/ruby/command_line/dash_internal_encoding_spec.rb b/spec/ruby/command_line/dash_internal_encoding_spec.rb
new file mode 100644
index 0000000000..3049040bb4
--- /dev/null
+++ b/spec/ruby/command_line/dash_internal_encoding_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+
+describe 'The --internal-encoding command line option sets Encoding.default_internal' do
+ before :each do
+ @test_string = "print Encoding.default_internal.name"
+ end
+
+ it "if given an encoding with an =" do
+ ruby_exe(@test_string, options: '--internal-encoding=big5').should == Encoding::Big5.name
+ end
+
+ it "if given an encoding as a separate argument" do
+ ruby_exe(@test_string, options: '--internal-encoding big5').should == Encoding::Big5.name
+ end
+end
diff --git a/spec/ruby/command_line/dash_l_spec.rb b/spec/ruby/command_line/dash_l_spec.rb
new file mode 100644
index 0000000000..44a98445f3
--- /dev/null
+++ b/spec/ruby/command_line/dash_l_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../spec_helper'
+
+describe "The -l command line option" do
+ before :each do
+ @names = fixture __FILE__, "full_names.txt"
+ end
+
+ it "chomps lines with default separator" do
+ ruby_exe('puts $_.end_with?("\n")', options: "-n -l",
+ args: " < #{@names}").should ==
+ "false\nfalse\nfalse\n"
+ end
+
+ it "chomps last line based on $/" do
+ ruby_exe('BEGIN { $/ = "ones\n" }; puts $_', options: "-W0 -n -l",
+ args: " < #{@names}").should ==
+ "alice j\nbob field\njames grey\n"
+ end
+
+ it "sets $\\ to the value of $/" do
+ ruby_exe("puts $\\ == $/", options: "-W0 -n -l",
+ args: " < #{@names}").should ==
+ "true\ntrue\ntrue\n"
+ end
+
+ it "sets $-l" do
+ ruby_exe("puts $-l", options: "-n -l",
+ args: " < #{@names}").should ==
+ "true\ntrue\ntrue\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_n_spec.rb b/spec/ruby/command_line/dash_n_spec.rb
new file mode 100644
index 0000000000..1dd9379259
--- /dev/null
+++ b/spec/ruby/command_line/dash_n_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../spec_helper'
+
+describe "The -n command line option" do
+ before :each do
+ @names = fixture __FILE__, "names.txt"
+ end
+
+ it "runs the code in loop conditional on Kernel.gets()" do
+ ruby_exe("puts $_", options: "-n",
+ args: " < #{@names}").should ==
+ "alice\nbob\njames\n"
+ end
+
+ it "only evaluates BEGIN blocks once" do
+ ruby_exe("BEGIN { puts \"hi\" }; puts $_", options: "-n",
+ args: " < #{@names}").should ==
+ "hi\nalice\nbob\njames\n"
+ end
+
+ it "only evaluates END blocks once" do
+ ruby_exe("puts $_; END {puts \"bye\"}", options: "-n",
+ args: " < #{@names}").should ==
+ "alice\nbob\njames\nbye\n"
+ end
+
+ it "allows summing over a whole file" do
+ script = <<-script
+ BEGIN { $total = 0 }
+ $total += 1
+ END { puts $total }
+ script
+ ruby_exe(script, options: "-n",
+ args: " < #{@names}").should ==
+ "3\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_p_spec.rb b/spec/ruby/command_line/dash_p_spec.rb
new file mode 100644
index 0000000000..967e3796de
--- /dev/null
+++ b/spec/ruby/command_line/dash_p_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../spec_helper'
+
+describe "The -p command line option" do
+ before :each do
+ @names = fixture __FILE__, "names.txt"
+ end
+
+ it "runs the code in loop conditional on Kernel.gets() and prints $_" do
+ ruby_exe("$_ = $_.upcase", options: "-p",
+ args: " < #{@names}").should ==
+ "ALICE\nBOB\nJAMES\n"
+ end
+
+ it "sets $-p" do
+ ruby_exe("$_ = $-p", options: "-p",
+ args: " < #{@names}").should ==
+ "truetruetrue"
+ end
+end
diff --git a/spec/ruby/command_line/dash_r_spec.rb b/spec/ruby/command_line/dash_r_spec.rb
new file mode 100644
index 0000000000..0de9ba2e24
--- /dev/null
+++ b/spec/ruby/command_line/dash_r_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../spec_helper'
+
+describe "The -r command line option" do
+ before :each do
+ @script = fixture __FILE__, "require.rb"
+ @test_file = fixture __FILE__, "test_file"
+ end
+
+ it "requires the specified file" do
+ out = ruby_exe(@script, options: "-r #{@test_file}")
+ out.should.include?("REQUIRED")
+ out.should.include?(@test_file + ".rb")
+ end
+
+ it "requires the file before parsing the main script" do
+ out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), options: "-r #{@test_file}", args: "2>&1", exit_status: 1)
+ $?.should_not.success?
+ out.should.include?("REQUIRED")
+ out.should.include?("SyntaxError")
+ end
+
+ it "does not require the file if the main script file does not exist" do
+ out = `#{ruby_exe.to_a.join(' ')} -r #{@test_file} #{fixture(__FILE__, "does_not_exist.rb")} 2>&1`
+ $?.should_not.success?
+ out.should_not.include?("REQUIRED")
+ out.should.include?("No such file or directory")
+ end
+end
diff --git a/spec/ruby/command_line/dash_s_spec.rb b/spec/ruby/command_line/dash_s_spec.rb
new file mode 100644
index 0000000000..eaaeea7c96
--- /dev/null
+++ b/spec/ruby/command_line/dash_s_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../spec_helper'
+
+describe "The -s command line option" do
+ describe "when using -- to stop parsing" do
+ it "sets the value to true without an explicit value" do
+ ruby_exe(nil, options: "-s -e 'p $n'",
+ args: "-- -n").chomp.should == "true"
+ end
+
+ it "parses single letter args into globals" do
+ ruby_exe(nil, options: "-s -e 'puts $n'",
+ args: "-- -n=blah").chomp.should == "blah"
+ end
+
+ it "parses long args into globals" do
+ ruby_exe(nil, options: "-s -e 'puts $_name'",
+ args: "-- --name=blah").chomp.should == "blah"
+ end
+
+ it "converts extra dashes into underscores" do
+ ruby_exe(nil, options: "-s -e 'puts $___name__test__'",
+ args: "-- ----name--test--=blah").chomp.should == "blah"
+ end
+ end
+
+ describe "when running a script" do
+ before :all do
+ @script = fixture __FILE__, "dash_s_script.rb"
+ end
+
+ it "sets the value to true without an explicit value" do
+ ruby_exe(@script, options: "-s",
+ args: "-n 0").chomp.should == "true"
+ end
+
+ it "parses single letter args into globals" do
+ ruby_exe(@script, options: "-s",
+ args: "-n=blah 1").chomp.should == "blah"
+ end
+
+ it "parses long args into globals" do
+ ruby_exe(@script, options: "-s",
+ args: "--name=blah 2").chomp.should == "blah"
+ end
+
+ it "converts extra dashes into underscores" do
+ ruby_exe(@script, options: "-s",
+ args: "----name--test--=blah 3").chomp.should == "blah"
+ end
+
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_c_spec.rb b/spec/ruby/command_line/dash_upper_c_spec.rb
new file mode 100644
index 0000000000..ece1b32105
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_c_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../spec_helper'
+require_relative 'shared/change_directory'
+
+describe "The -C command line option" do
+ it_behaves_like :command_line_change_directory, "-C"
+end
diff --git a/spec/ruby/command_line/dash_upper_e_spec.rb b/spec/ruby/command_line/dash_upper_e_spec.rb
new file mode 100644
index 0000000000..5a83962583
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_e_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../spec_helper'
+
+describe "ruby -E" do
+ it "sets the external encoding with '-E external'" do
+ result = ruby_exe("print Encoding.default_external", options: '-E euc-jp')
+ result.should == "EUC-JP"
+ end
+
+ platform_is_not :windows do
+ it "also sets the filesystem encoding with '-E external'" do
+ result = ruby_exe("print Encoding.find('filesystem')", options: '-E euc-jp')
+ result.should == "EUC-JP"
+ end
+ end
+
+ it "sets the external encoding with '-E external:'" do
+ result = ruby_exe("print Encoding.default_external", options: '-E Shift_JIS:')
+ result.should == "Shift_JIS"
+ end
+
+ it "sets the internal encoding with '-E :internal'" do
+ ruby_exe("print Encoding.default_internal", options: '-E :SHIFT_JIS').
+ should == 'Shift_JIS'
+ end
+
+ it "sets the external and internal encodings with '-E external:internal'" do
+ ruby_exe("puts Encoding.default_external, Encoding.default_internal", options: '-E euc-jp:SHIFT_JIS').
+ should == "EUC-JP\nShift_JIS\n"
+ end
+
+ it "raises a RuntimeError if used with -U" do
+ ruby_exe("p 1",
+ options: '-Eascii:ascii -U',
+ args: '2>&1',
+ exit_status: 1).should =~ /RuntimeError/
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_f_spec.rb b/spec/ruby/command_line/dash_upper_f_spec.rb
new file mode 100644
index 0000000000..5c10a7140d
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_f_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "the -F command line option" do
+ before :each do
+ @passwd = fixture __FILE__, "passwd_file.txt"
+ end
+
+ it "specifies the field separator pattern for -a" do
+ ruby_exe("puts $F[0]", options: "-naF:",
+ args: " < #{@passwd}").should ==
+ "nobody\nroot\ndaemon\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_i_spec.rb b/spec/ruby/command_line/dash_upper_i_spec.rb
new file mode 100644
index 0000000000..4005a27d23
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_i_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../spec_helper'
+
+describe "The -I command line option" do
+ before :each do
+ @script = fixture __FILE__, "loadpath.rb"
+ end
+
+ it "adds the path to the load path ($:)" do
+ ruby_exe(@script, options: "-I fixtures").should.include?("fixtures")
+ end
+
+ it "adds the path at the front of $LOAD_PATH" do
+ lines = ruby_exe(@script, options: "-I fixtures").lines
+ if PlatformGuard.implementation? :ruby
+ # In a MRI checkout, $PWD ends up as the first entry in $LOAD_PATH.
+ # So just assert that it's at the beginning.
+ idx = lines.index { |l| l.include?("fixtures") }
+ idx.should < 2
+ idx.should < lines.size-1
+ else
+ lines[0].should.include?("fixtures")
+ end
+ end
+
+ it "adds the path expanded from CWD to $LOAD_PATH" do
+ ruby_exe(@script, options: "-I fixtures").lines.should.include? "#{Dir.pwd}/fixtures\n"
+ end
+
+ it "expands a path from CWD even if it does not exist" do
+ ruby_exe(@script, options: "-I not_exist/not_exist").lines.should.include? "#{Dir.pwd}/not_exist/not_exist\n"
+ end
+end
+
+platform_is_not :windows do
+ describe "The -I command line option" do
+ before :each do
+ @script = fixture __FILE__, "loadpath.rb"
+ @fixtures = File.dirname(@script)
+ @symlink = tmp("loadpath_symlink")
+ File.symlink(@fixtures, @symlink)
+ end
+
+ after :each do
+ rm_r @symlink
+ end
+
+ it "does not expand symlinks" do
+ ruby_exe(@script, options: "-I #{@symlink}").lines.should.include? "#{@symlink}\n"
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_k_spec.rb b/spec/ruby/command_line/dash_upper_k_spec.rb
new file mode 100644
index 0000000000..7e71532295
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_k_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../spec_helper'
+
+describe 'The -K command line option' do
+ before :each do
+ @test_string = "print [__ENCODING__&.name, Encoding.default_external&.name, Encoding.default_internal&.name].inspect"
+ end
+
+ describe 'sets __ENCODING__ and Encoding.default_external' do
+ it "to Encoding::BINARY with -Ka" do
+ ruby_exe(@test_string, options: '-Ka').should ==
+ [Encoding::BINARY.name, Encoding::BINARY.name, nil].inspect
+ end
+
+ it "to Encoding::BINARY with -KA" do
+ ruby_exe(@test_string, options: '-KA').should ==
+ [Encoding::BINARY.name, Encoding::BINARY.name, nil].inspect
+ end
+
+ it "to Encoding::BINARY with -Kn" do
+ ruby_exe(@test_string, options: '-Kn').should ==
+ [Encoding::BINARY.name, Encoding::BINARY.name, nil].inspect
+ end
+
+ it "to Encoding::BINARY with -KN" do
+ ruby_exe(@test_string, options: '-KN').should ==
+ [Encoding::BINARY.name, Encoding::BINARY.name, nil].inspect
+ end
+
+ it "to Encoding::EUC_JP with -Ke" do
+ ruby_exe(@test_string, options: '-Ke').should ==
+ [Encoding::EUC_JP.name, Encoding::EUC_JP.name, nil].inspect
+ end
+
+ it "to Encoding::EUC_JP with -KE" do
+ ruby_exe(@test_string, options: '-KE').should ==
+ [Encoding::EUC_JP.name, Encoding::EUC_JP.name, nil].inspect
+ end
+
+ it "to Encoding::UTF_8 with -Ku" do
+ ruby_exe(@test_string, options: '-Ku').should ==
+ [Encoding::UTF_8.name, Encoding::UTF_8.name, nil].inspect
+ end
+
+ it "to Encoding::UTF_8 with -KU" do
+ ruby_exe(@test_string, options: '-KU').should ==
+ [Encoding::UTF_8.name, Encoding::UTF_8.name, nil].inspect
+ end
+
+ it "to Encoding::Windows_31J with -Ks" do
+ ruby_exe(@test_string, options: '-Ks').should ==
+ [Encoding::Windows_31J.name, Encoding::Windows_31J.name, nil].inspect
+ end
+
+ it "to Encoding::Windows_31J with -KS" do
+ ruby_exe(@test_string, options: '-KS').should ==
+ [Encoding::Windows_31J.name, Encoding::Windows_31J.name, nil].inspect
+ end
+ end
+
+ it "ignores unknown codes" do
+ external = Encoding.find('external')
+ ruby_exe(@test_string, options: '-KZ').should ==
+ [Encoding::UTF_8.name, external.name, nil].inspect
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_s_spec.rb b/spec/ruby/command_line/dash_upper_s_spec.rb
new file mode 100644
index 0000000000..71c6016659
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_s_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../spec_helper'
+
+describe 'The -S command line option' do
+ before :each do
+ @bin = fixture(__FILE__, "bin")
+ @path = [ENV['PATH'], @bin].join(File::PATH_SEPARATOR)
+ end
+
+ platform_is_not :windows do
+ # On VirtualBox shared directory (vboxsf) all files are world writable
+ # and MRI shows warning when including world writable path in ENV['PATH'].
+ # This is why we are using /success$/ matching in the following cases.
+
+ it "runs launcher found in RUBYPATH, but only code after the first /\#!.*ruby.*/-ish line in target file" do
+ result = ruby_exe(nil, options: '-S hybrid_launcher.sh', env: { 'RUBYPATH' => @bin }, args: '2>&1')
+ result.should =~ /success$/
+ end
+
+ it "runs launcher found in PATH, but only code after the first /\#!.*ruby.*/-ish line in target file" do
+ result = ruby_exe(nil, options: '-S hybrid_launcher.sh', env: { 'PATH' => @path }, args: '2>&1')
+ result.should =~ /success$/
+ end
+
+ it "runs launcher found in RUBYPATH" do
+ result = ruby_exe(nil, options: '-S launcher.rb', env: { 'RUBYPATH' => @bin }, args: '2>&1')
+ result.should =~ /success$/
+ end
+
+ it "runs launcher found in PATH" do
+ result = ruby_exe(nil, options: '-S launcher.rb', env: { 'PATH' => @path }, args: '2>&1')
+ result.should =~ /success$/
+ end
+
+ it "runs launcher found in RUBYPATH and sets the exit status to 1 if it fails" do
+ result = ruby_exe(nil, options: '-S dash_s_fail', env: { 'RUBYPATH' => @bin }, args: '2>&1', exit_status: 1)
+ result.should =~ /\bdie\b/
+ $?.exitstatus.should == 1
+ end
+
+ it "runs launcher found in PATH and sets the exit status to 1 if it fails" do
+ result = ruby_exe(nil, options: '-S dash_s_fail', env: { 'PATH' => @path }, args: '2>&1', exit_status: 1)
+ result.should =~ /\bdie\b/
+ $?.exitstatus.should == 1
+ end
+
+ ruby_version_is "4.1" do
+ describe "if the script name contains separator" do
+ before(:each) do
+ @bin = File.dirname(@bin)
+ @path = [ENV['PATH'], @bin].join(File::PATH_SEPARATOR)
+ end
+
+ it "does not search launcher in RUBYPATH" do
+ result = ruby_exe(nil, options: '-S bin/launcher.rb', env: { 'RUBYPATH' => @bin }, args: '2>&1', exit_status: 1)
+ result.should =~ /LoadError/
+ $?.should_not.success?
+ end
+
+ it "does not search launcher in PATH" do
+ result = ruby_exe(nil, options: '-S bin/launcher.rb', env: { 'PATH' => @path }, args: '2>&1', exit_status: 1)
+ result.should =~ /LoadError/
+ $?.should_not.success?
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_u_spec.rb b/spec/ruby/command_line/dash_upper_u_spec.rb
new file mode 100644
index 0000000000..2c210eb603
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_u_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../spec_helper'
+
+describe "ruby -U" do
+ it "sets Encoding.default_internal to UTF-8" do
+ ruby_exe('print Encoding.default_internal.name',
+ options: '-U').should == 'UTF-8'
+ end
+
+ it "sets Encoding.default_internal to UTF-8 when RUBYOPT is empty or only spaces" do
+ ruby_exe('p Encoding.default_internal',
+ options: '-U', env: { 'RUBYOPT' => '' }).should == "#<Encoding:UTF-8>\n"
+ ruby_exe('p Encoding.default_internal',
+ options: '-U', env: { 'RUBYOPT' => ' ' }).should == "#<Encoding:UTF-8>\n"
+ end
+
+ it "does nothing different if specified multiple times" do
+ ruby_exe('print Encoding.default_internal.name',
+ options: '-U -U').should == 'UTF-8'
+ end
+
+ it "is overruled by Encoding.default_internal=" do
+ ruby_exe('Encoding.default_internal="ascii"; print Encoding.default_internal.name',
+ options: '-U').should == 'US-ASCII'
+ end
+
+ it "does not affect the default external encoding" do
+ ruby_exe('Encoding.default_external="ascii"; print Encoding.default_external.name',
+ options: '-U').should == 'US-ASCII'
+ end
+
+ it "does not affect the source encoding" do
+ ruby_exe("print __ENCODING__.name",
+ options: '-U -KE').should == 'EUC-JP'
+ ruby_exe("print __ENCODING__.name",
+ options: '-KE -U').should == 'EUC-JP'
+ end
+
+ # I assume IO redirection will break on Windows...
+ it "raises a RuntimeError if used with -Eext:int" do
+ ruby_exe("p 1",
+ options: '-U -Eascii:ascii',
+ args: '2>&1',
+ exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError if used with -E:int" do
+ ruby_exe("p 1",
+ options: '-U -E:ascii',
+ args: '2>&1',
+ exit_status: 1).should =~ /RuntimeError/
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_w_spec.rb b/spec/ruby/command_line/dash_upper_w_spec.rb
new file mode 100644
index 0000000000..4019510d42
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_w_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../spec_helper'
+require_relative 'shared/verbose'
+
+describe "The -W command line option" do
+ before :each do
+ @script = fixture __FILE__, "verbose.rb"
+ end
+
+ it "with 0 sets $VERBOSE to nil" do
+ ruby_exe(@script, options: "-W0").chomp.should == "nil"
+ end
+
+ it "with 1 sets $VERBOSE to false" do
+ ruby_exe(@script, options: "-W1").chomp.should == "false"
+ end
+end
+
+describe "The -W command line option with 2" do
+ it_behaves_like :command_line_verbose, "-W2"
+end
+
+describe "The -W command line option with :deprecated" do
+ it "enables deprecation warnings" do
+ ruby_exe('p Warning[:deprecated]', options: '-W:deprecated').should == "true\n"
+ end
+end
+
+describe "The -W command line option with :no-deprecated" do
+ it "suppresses deprecation warnings" do
+ ruby_exe('p Warning[:deprecated]', options: '-w -W:no-deprecated').should == "false\n"
+ end
+end
+
+describe "The -W command line option with :experimental" do
+ it "enables experimental warnings" do
+ ruby_exe('p Warning[:experimental]', options: '-W:experimental').should == "true\n"
+ end
+end
+
+describe "The -W command line option with :no-experimental" do
+ it "suppresses experimental warnings" do
+ ruby_exe('p Warning[:experimental]', options: '-w -W:no-experimental').should == "false\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_upper_x_spec.rb b/spec/ruby/command_line/dash_upper_x_spec.rb
new file mode 100644
index 0000000000..8ef9aae4b1
--- /dev/null
+++ b/spec/ruby/command_line/dash_upper_x_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../spec_helper'
+require_relative 'shared/change_directory'
+
+describe "The -X command line option" do
+ it_behaves_like :command_line_change_directory, "-X"
+end
diff --git a/spec/ruby/command_line/dash_v_spec.rb b/spec/ruby/command_line/dash_v_spec.rb
new file mode 100644
index 0000000000..6a4f7d3a15
--- /dev/null
+++ b/spec/ruby/command_line/dash_v_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../spec_helper'
+require_relative 'shared/verbose'
+
+describe "The -v command line option" do
+ it_behaves_like :command_line_verbose, "-v"
+
+ describe "when used alone" do
+ it "prints version and ends" do
+ ruby_exe(nil, args: '-v').gsub("+PRISM ", "").should.include?(RUBY_DESCRIPTION.gsub("+PRISM ", ""))
+ end unless (defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?) ||
+ (defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?) ||
+ (ENV['RUBY_GC_LIBRARY'] && ENV['RUBY_GC_LIBRARY'].length > 0) ||
+ (ENV['RUBY_MN_THREADS'] == '1')
+ end
+end
diff --git a/spec/ruby/command_line/dash_w_spec.rb b/spec/ruby/command_line/dash_w_spec.rb
new file mode 100644
index 0000000000..f310dca3ed
--- /dev/null
+++ b/spec/ruby/command_line/dash_w_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../spec_helper'
+require_relative 'shared/verbose'
+
+describe "The -w command line option" do
+ it_behaves_like :command_line_verbose, "-w"
+
+ it "enables both deprecated and experimental warnings" do
+ ruby_exe('p Warning[:deprecated]; p Warning[:experimental]', options: '-w').should == "true\ntrue\n"
+ end
+end
diff --git a/spec/ruby/command_line/dash_x_spec.rb b/spec/ruby/command_line/dash_x_spec.rb
new file mode 100644
index 0000000000..38f97a5ab1
--- /dev/null
+++ b/spec/ruby/command_line/dash_x_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../spec_helper'
+
+describe "The -x command line option" do
+ it "runs code after the first /\#!.*ruby.*/-ish line in target file" do
+ embedded_ruby = fixture __FILE__, "bin/embedded_ruby.txt"
+ result = ruby_exe(embedded_ruby, options: '-x')
+ result.should == "success\n"
+ end
+
+ it "fails when /\#!.*ruby.*/-ish line in target file is not found" do
+ bad_embedded_ruby = fixture __FILE__, "bin/bad_embedded_ruby.txt"
+ result = ruby_exe(bad_embedded_ruby, options: '-x', args: '2>&1', exit_status: 1)
+ result.should.include? "no Ruby script found in input"
+ end
+
+ it "behaves as -x was set when non-ruby shebang is encountered on first line" do
+ embedded_ruby = fixture __FILE__, "bin/hybrid_launcher.sh"
+ result = ruby_exe(embedded_ruby)
+ result.should == "success\n"
+ end
+end
diff --git a/spec/ruby/command_line/error_message_spec.rb b/spec/ruby/command_line/error_message_spec.rb
new file mode 100644
index 0000000000..157cb8969c
--- /dev/null
+++ b/spec/ruby/command_line/error_message_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../spec_helper'
+
+describe "The error message caused by an exception" do
+ it "is not printed to stdout" do
+ out = ruby_exe("this_does_not_exist", args: "2> #{File::NULL}", exit_status: 1)
+ out.chomp.should.empty?
+
+ out = ruby_exe("end #syntax error", args: "2> #{File::NULL}", exit_status: 1)
+ out.chomp.should.empty?
+ end
+
+ it "is not modified with extra escaping of control characters and backslashes" do
+ out = ruby_exe('raise "\e[31mRed\e[0m error\\\\message"', args: "2>&1", exit_status: 1)
+ out.chomp.should.include?("\e[31mRed\e[0m error\\message")
+ end
+end
diff --git a/spec/ruby/command_line/feature_spec.rb b/spec/ruby/command_line/feature_spec.rb
new file mode 100644
index 0000000000..0a3252b88d
--- /dev/null
+++ b/spec/ruby/command_line/feature_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../spec_helper'
+
+describe "The --enable and --disable flags" do
+ before :all do
+ # Since some specs disable reading RUBYOPT, we instead pass its contents as :options for those specs
+ rubyopt = [ENV["RUBYOPT"]]
+ rubyopt << ENV["#{RUBY_ENGINE.upcase}OPT"] unless RUBY_ENGINE == 'ruby'
+ @rubyopt = RUBY_ENGINE == "ruby" ? "" : rubyopt.compact.join(" ")
+ end
+
+ it "can be used with gems" do
+ ruby_exe("p defined?(Gem)", options: "--enable=gems").chomp.should == "\"constant\""
+ ruby_exe("p defined?(Gem)", options: "--disable=gems").chomp.should == "nil"
+ ruby_exe("p defined?(Gem)", options: "--enable-gems").chomp.should == "\"constant\""
+ ruby_exe("p defined?(Gem)", options: "--disable-gems").chomp.should == "nil"
+ end
+
+ it "can be used with gem" do
+ ruby_exe("p defined?(Gem)", options: "--enable=gem").chomp.should == "\"constant\""
+ ruby_exe("p defined?(Gem)", options: "--disable=gem").chomp.should == "nil"
+ ruby_exe("p defined?(Gem)", options: "--enable-gem").chomp.should == "\"constant\""
+ ruby_exe("p defined?(Gem)", options: "--disable-gem").chomp.should == "nil"
+ end
+
+ it "can be used with did_you_mean" do
+ ruby_exe("p defined?(DidYouMean)", options: "--enable=did_you_mean").chomp.should == "\"constant\""
+ ruby_exe("p defined?(DidYouMean)", options: "--disable=did_you_mean").chomp.should == "nil"
+ ruby_exe("p defined?(DidYouMean)", options: "--enable-did_you_mean").chomp.should == "\"constant\""
+ ruby_exe("p defined?(DidYouMean)", options: "--disable-did_you_mean").chomp.should == "nil"
+ end
+
+ it "can be used with rubyopt" do
+ ruby_exe("p $VERBOSE", options: "--enable=rubyopt", env: {'RUBYOPT' => '-w'}).chomp.should == "true"
+ ruby_exe("p $VERBOSE", options: "#{@rubyopt} --disable=rubyopt", env: {'RUBYOPT' => '-w'}).chomp.should == "false"
+ ruby_exe("p $VERBOSE", options: "--enable-rubyopt", env: {'RUBYOPT' => '-w'}).chomp.should == "true"
+ ruby_exe("p $VERBOSE", options: "#{@rubyopt} --disable-rubyopt", env: {'RUBYOPT' => '-w'}).chomp.should == "false"
+ end
+
+ it "can be used with frozen-string-literal" do
+ ruby_exe("p 'foo'.frozen?", options: "--enable=frozen-string-literal").chomp.should == "true"
+ ruby_exe("p 'foo'.frozen?", options: "--disable=frozen-string-literal").chomp.should == "false"
+ ruby_exe("p 'foo'.frozen?", options: "--enable-frozen-string-literal").chomp.should == "true"
+ ruby_exe("p 'foo'.frozen?", options: "--disable-frozen-string-literal").chomp.should == "false"
+ end
+
+ # frequently hangs for >60s on GitHub Actions macos-latest
+ # MinGW's YJIT support seems broken
+ platform_is_not :darwin, :mingw do
+ it "can be used with all for enable" do
+ e = "p [defined?(Gem), defined?(DidYouMean), $VERBOSE, 'foo'.frozen?]"
+ env = {'RUBYOPT' => '-w'}
+ # Use a single variant here because it can be quite slow as it might enable jit, etc
+ ruby_exe(e, options: "--enable-all", env: env).chomp.should == "[\"constant\", \"constant\", true, true]"
+ end unless defined?(RubyVM::YJIT) && defined?(RubyVM::ZJIT) && RubyVM::ZJIT.enabled? # You're not supposed to enable YJIT with --enable-all when ZJIT options are passed.
+ end
+
+ it "can be used with all for disable" do
+ e = "p [defined?(Gem), defined?(DidYouMean), $VERBOSE, 'foo'.frozen?]"
+ env = {'RUBYOPT' => '-w'}
+ ruby_exe(e, options: "#{@rubyopt} --disable=all", env: env).chomp.should == "[nil, nil, false, false]"
+ ruby_exe(e, options: "#{@rubyopt} --disable-all", env: env).chomp.should == "[nil, nil, false, false]"
+ end
+
+ it "prints a warning for unknown features" do
+ ruby_exe("p 14", options: "--enable=ruby-spec-feature-does-not-exist 2>&1").chomp.should.include?('warning: unknown argument for --enable')
+ ruby_exe("p 14", options: "--disable=ruby-spec-feature-does-not-exist 2>&1").chomp.should.include?('warning: unknown argument for --disable')
+ ruby_exe("p 14", options: "--enable-ruby-spec-feature-does-not-exist 2>&1").chomp.should.include?('warning: unknown argument for --enable')
+ ruby_exe("p 14", options: "--disable-ruby-spec-feature-does-not-exist 2>&1").chomp.should.include?('warning: unknown argument for --disable')
+ end
+
+end
diff --git a/spec/ruby/command_line/fixtures/backtrace.rb b/spec/ruby/command_line/fixtures/backtrace.rb
new file mode 100644
index 0000000000..99acae95c8
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/backtrace.rb
@@ -0,0 +1,35 @@
+def a
+ raise 'oops'
+end
+
+def b
+ a
+end
+
+def c
+ b
+end
+
+def d
+ c
+end
+
+arg = ARGV.first
+$stderr.puts arg
+
+case arg
+when 'full_message'
+ begin
+ d
+ rescue => exc
+ puts exc.full_message
+ end
+when 'backtrace'
+ begin
+ d
+ rescue => exc
+ puts exc.backtrace
+ end
+else
+ d
+end
diff --git a/spec/ruby/command_line/fixtures/bad_syntax.rb b/spec/ruby/command_line/fixtures/bad_syntax.rb
new file mode 100644
index 0000000000..e7b8c7a357
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bad_syntax.rb
@@ -0,0 +1 @@
+f {
diff --git a/spec/ruby/command_line/fixtures/bin/bad_embedded_ruby.txt b/spec/ruby/command_line/fixtures/bin/bad_embedded_ruby.txt
new file mode 100644
index 0000000000..61b946977a
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/bad_embedded_ruby.txt
@@ -0,0 +1,3 @@
+@@@This line is not valid Ruby
+#!rub_y
+puts 'success'
diff --git a/spec/ruby/command_line/fixtures/bin/dash_s_fail b/spec/ruby/command_line/fixtures/bin/dash_s_fail
new file mode 100644
index 0000000000..70c1b8759c
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/dash_s_fail
@@ -0,0 +1 @@
+raise 'die'
diff --git a/spec/ruby/command_line/fixtures/bin/embedded_ruby.txt b/spec/ruby/command_line/fixtures/bin/embedded_ruby.txt
new file mode 100644
index 0000000000..0ec0f358db
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/embedded_ruby.txt
@@ -0,0 +1,3 @@
+@@@This line is not valid Ruby
+#!ruby
+puts 'success'
diff --git a/spec/ruby/command_line/fixtures/bin/hybrid_launcher.sh b/spec/ruby/command_line/fixtures/bin/hybrid_launcher.sh
new file mode 100644
index 0000000000..fd3249f0e5
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/hybrid_launcher.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+echo 'error' && exit 1
+#!ruby
+puts 'success'
diff --git a/spec/ruby/command_line/fixtures/bin/launcher.rb b/spec/ruby/command_line/fixtures/bin/launcher.rb
new file mode 100755
index 0000000000..92a0ee2b49
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/bin/launcher.rb
@@ -0,0 +1,2 @@
+#!ruby
+puts 'success'
diff --git a/spec/ruby/command_line/fixtures/change_directory_script.rb b/spec/ruby/command_line/fixtures/change_directory_script.rb
new file mode 100644
index 0000000000..abe244705f
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/change_directory_script.rb
@@ -0,0 +1 @@
+print Dir.pwd
diff --git a/spec/ruby/command_line/fixtures/conditional_range.txt b/spec/ruby/command_line/fixtures/conditional_range.txt
new file mode 100644
index 0000000000..8a1218a102
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/conditional_range.txt
@@ -0,0 +1,5 @@
+1
+2
+3
+4
+5
diff --git a/spec/ruby/command_line/fixtures/dash_s_script.rb b/spec/ruby/command_line/fixtures/dash_s_script.rb
new file mode 100644
index 0000000000..500eccbb84
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/dash_s_script.rb
@@ -0,0 +1,12 @@
+which = ARGV.shift.to_i
+
+case which
+when 0
+ p $n
+when 1
+ puts $n
+when 2
+ puts $_name
+when 3
+ puts $___name__test__
+end
diff --git a/spec/ruby/command_line/fixtures/debug.rb b/spec/ruby/command_line/fixtures/debug.rb
new file mode 100644
index 0000000000..2d84c5faf6
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/debug.rb
@@ -0,0 +1,10 @@
+which = ARGV.first.to_i
+
+case which
+when 0
+ puts "$DEBUG #{$DEBUG}"
+when 1
+ puts "$VERBOSE #{$VERBOSE}"
+when 2
+ puts "$-d #{$-d}"
+end
diff --git a/spec/ruby/command_line/fixtures/debug_info.rb b/spec/ruby/command_line/fixtures/debug_info.rb
new file mode 100644
index 0000000000..f02b041920
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/debug_info.rb
@@ -0,0 +1,10 @@
+a = 'string'
+b = a
+c = b
+d = c
+e = d
+begin
+ a << 'new part'
+rescue Exception => e
+ print e.message
+end
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_across_files.rb b/spec/ruby/command_line/fixtures/freeze_flag_across_files.rb
new file mode 100644
index 0000000000..b258249f3a
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_across_files.rb
@@ -0,0 +1,3 @@
+require_relative 'freeze_flag_required'
+
+p "abc".object_id == $second_literal_id
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_across_files_diff_enc.rb b/spec/ruby/command_line/fixtures/freeze_flag_across_files_diff_enc.rb
new file mode 100644
index 0000000000..e9f045e9ea
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_across_files_diff_enc.rb
@@ -0,0 +1,3 @@
+require_relative 'freeze_flag_required_diff_enc'
+
+p "abc".object_id != $second_literal_id
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb b/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb
new file mode 100644
index 0000000000..3718899d61
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb
@@ -0,0 +1,2 @@
+ids = Array.new(2) { "abc".object_id }
+p ids.first == ids.last
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_required.rb b/spec/ruby/command_line/fixtures/freeze_flag_required.rb
new file mode 100644
index 0000000000..e09232a5f4
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_required.rb
@@ -0,0 +1 @@
+$second_literal_id = "abc".object_id
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb b/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb
new file mode 100644
index 0000000000..df4b952c46
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb
@@ -0,0 +1,3 @@
+# encoding: euc-jp # built-in for old regexp option
+
+$second_literal_id = "abc".object_id
diff --git a/spec/ruby/command_line/fixtures/freeze_flag_two_literals.rb b/spec/ruby/command_line/fixtures/freeze_flag_two_literals.rb
new file mode 100644
index 0000000000..f5547a5bae
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/freeze_flag_two_literals.rb
@@ -0,0 +1 @@
+p "abc".equal?("abc")
diff --git a/spec/ruby/command_line/fixtures/full_names.txt b/spec/ruby/command_line/fixtures/full_names.txt
new file mode 100644
index 0000000000..602a20b9dd
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/full_names.txt
@@ -0,0 +1,3 @@
+alice jones
+bob field
+james grey
diff --git a/spec/ruby/command_line/fixtures/loadpath.rb b/spec/ruby/command_line/fixtures/loadpath.rb
new file mode 100644
index 0000000000..d7fdf45d46
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/loadpath.rb
@@ -0,0 +1 @@
+puts $:
diff --git a/spec/ruby/command_line/fixtures/names.txt b/spec/ruby/command_line/fixtures/names.txt
new file mode 100644
index 0000000000..ae4bf4c8ad
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/names.txt
@@ -0,0 +1,3 @@
+alice
+bob
+james
diff --git a/spec/ruby/command_line/fixtures/passwd_file.txt b/spec/ruby/command_line/fixtures/passwd_file.txt
new file mode 100644
index 0000000000..08a4b23bbd
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/passwd_file.txt
@@ -0,0 +1,3 @@
+nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
+root:*:0:0:System Administrator:/var/root:/bin/sh
+daemon:*:1:1:System Services:/var/root:/usr/bin/false
diff --git a/spec/ruby/command_line/fixtures/require.rb b/spec/ruby/command_line/fixtures/require.rb
new file mode 100644
index 0000000000..0be7049c66
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/require.rb
@@ -0,0 +1 @@
+puts $"
diff --git a/spec/ruby/command_line/fixtures/rubyopt.rb b/spec/ruby/command_line/fixtures/rubyopt.rb
new file mode 100644
index 0000000000..48d81e1bca
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/rubyopt.rb
@@ -0,0 +1 @@
+puts "rubyopt.rb required"
diff --git a/spec/ruby/command_line/fixtures/string_literal_frozen_comment.rb b/spec/ruby/command_line/fixtures/string_literal_frozen_comment.rb
new file mode 100644
index 0000000000..fb84b546c0
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/string_literal_frozen_comment.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+frozen = "test".frozen?
+interned = "test".equal?("test")
+puts "frozen:#{frozen} interned:#{interned}"
diff --git a/spec/ruby/command_line/fixtures/string_literal_mutable_comment.rb b/spec/ruby/command_line/fixtures/string_literal_mutable_comment.rb
new file mode 100644
index 0000000000..381a742001
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/string_literal_mutable_comment.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: false
+frozen = "test".frozen?
+interned = "test".equal?("test")
+puts "frozen:#{frozen} interned:#{interned}"
diff --git a/spec/ruby/command_line/fixtures/string_literal_raw.rb b/spec/ruby/command_line/fixtures/string_literal_raw.rb
new file mode 100644
index 0000000000..56b1841296
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/string_literal_raw.rb
@@ -0,0 +1,3 @@
+frozen = "test".frozen?
+interned = "test".equal?("test")
+puts "frozen:#{frozen} interned:#{interned}"
diff --git a/spec/ruby/command_line/fixtures/test_file.rb b/spec/ruby/command_line/fixtures/test_file.rb
new file mode 100644
index 0000000000..30a832299e
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/test_file.rb
@@ -0,0 +1 @@
+puts "REQUIRED"
diff --git a/spec/ruby/command_line/fixtures/verbose.rb b/spec/ruby/command_line/fixtures/verbose.rb
new file mode 100644
index 0000000000..2aa99ed44d
--- /dev/null
+++ b/spec/ruby/command_line/fixtures/verbose.rb
@@ -0,0 +1 @@
+puts $VERBOSE.inspect
diff --git a/spec/ruby/command_line/frozen_strings_spec.rb b/spec/ruby/command_line/frozen_strings_spec.rb
new file mode 100644
index 0000000000..32ff7d0371
--- /dev/null
+++ b/spec/ruby/command_line/frozen_strings_spec.rb
@@ -0,0 +1,94 @@
+require_relative '../spec_helper'
+
+describe "The --enable-frozen-string-literal flag causes string literals to" do
+
+ it "produce the same object each time" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_one_literal.rb"), options: "--enable-frozen-string-literal").chomp.should == "true"
+ end
+
+ it "produce the same object for literals with the same content" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_two_literals.rb"), options: "--enable-frozen-string-literal").chomp.should == "true"
+ end
+
+ it "produce the same object for literals with the same content in different files" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_across_files.rb"), options: "--enable-frozen-string-literal").chomp.should == "true"
+ end
+
+ it "produce different objects for literals with the same content in different files if they have different encodings" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_across_files_diff_enc.rb"), options: "--enable-frozen-string-literal").chomp.should == "true"
+ end
+end
+
+describe "The --disable-frozen-string-literal flag causes string literals to" do
+
+ it "produce a different object each time" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_one_literal.rb"), options: "--disable-frozen-string-literal").chomp.should == "false"
+ end
+
+end
+
+describe "With neither --enable-frozen-string-literal nor --disable-frozen-string-literal flag set" do
+ before do
+ # disable --enable-frozen-string-literal and --disable-frozen-string-literal passed in $RUBYOPT
+ @rubyopt = ENV["RUBYOPT"]
+ ENV["RUBYOPT"] = ""
+ end
+
+ after do
+ ENV["RUBYOPT"] = @rubyopt
+ end
+
+ it "produce a different object each time" do
+ ruby_exe(fixture(__FILE__, "freeze_flag_one_literal.rb")).chomp.should == "false"
+ end
+
+ context "if file has no frozen_string_literal comment" do
+ it "produce different mutable strings each time" do
+ ruby_exe(fixture(__FILE__, "string_literal_raw.rb")).chomp.should == "frozen:false interned:false"
+ end
+
+ guard -> { ruby_version_is "3.4" and !"test".frozen? } do
+ it "complain about modification of produced mutable strings" do
+ -> { eval(<<~RUBY) }.should complain(/warning: literal string will be frozen in the future \(run with --debug-frozen-string-literal for more information\)/)
+ "test" << "!"
+ RUBY
+ end
+
+ it "does not complain about modification if Warning[:deprecated] is false" do
+ deprecated = Warning[:deprecated]
+ Warning[:deprecated] = false
+ -> { eval(<<~RUBY) }.should_not complain
+ "test" << "!"
+ RUBY
+ ensure
+ Warning[:deprecated] = deprecated
+ end
+ end
+ end
+
+ it "if file has frozen_string_literal:true comment produce same frozen strings each time" do
+ ruby_exe(fixture(__FILE__, "string_literal_frozen_comment.rb")).chomp.should == "frozen:true interned:true"
+ end
+
+ it "if file has frozen_string_literal:false comment produce different mutable strings each time" do
+ ruby_exe(fixture(__FILE__, "string_literal_mutable_comment.rb")).chomp.should == "frozen:false interned:false"
+ end
+end
+
+describe "The --debug flag produces" do
+ it "debugging info on attempted frozen string modification" do
+ error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '--enable-frozen-string-literal --debug', args: "2>&1")
+ error_str.should.include?("can't modify frozen String")
+ error_str.should.include?("created at")
+ error_str.should.include?("command_line/fixtures/debug_info.rb:1")
+ end
+
+ guard -> { ruby_version_is "3.4" and !"test".frozen? } do
+ it "debugging info on mutating chilled string" do
+ error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '-w --debug', args: "2>&1")
+ error_str.should.include?("literal string will be frozen in the future")
+ error_str.should.include?("the string was created here")
+ error_str.should.include?("command_line/fixtures/debug_info.rb:1")
+ end
+ end
+end
diff --git a/spec/ruby/command_line/rubylib_spec.rb b/spec/ruby/command_line/rubylib_spec.rb
new file mode 100644
index 0000000000..1bedd146e3
--- /dev/null
+++ b/spec/ruby/command_line/rubylib_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../spec_helper'
+
+describe "The RUBYLIB environment variable" do
+ before :each do
+ @rubylib, ENV["RUBYLIB"] = ENV["RUBYLIB"], nil
+ @pre = @rubylib.nil? ? '' : @rubylib + File::PATH_SEPARATOR
+ end
+
+ after :each do
+ ENV["RUBYLIB"] = @rubylib
+ end
+
+ it "adds a directory to $LOAD_PATH" do
+ dir = tmp("rubylib/incl")
+ ENV["RUBYLIB"] = @pre + dir
+ paths = ruby_exe("puts $LOAD_PATH").lines.map(&:chomp)
+ paths.should.include?(dir)
+ end
+
+ it "adds a File::PATH_SEPARATOR-separated list of directories to $LOAD_PATH" do
+ dir1, dir2 = tmp("rubylib/incl1"), tmp("rubylib/incl2")
+ ENV["RUBYLIB"] = @pre + "#{dir1}#{File::PATH_SEPARATOR}#{dir2}"
+ paths = ruby_exe("puts $LOAD_PATH").lines.map(&:chomp)
+ paths.should.include?(dir1)
+ paths.should.include?(dir2)
+ paths.index(dir1).should < paths.index(dir2)
+ end
+
+ it "adds the directory at the front of $LOAD_PATH" do
+ dir = tmp("rubylib/incl_front")
+ ENV["RUBYLIB"] = @pre + dir
+ paths = ruby_exe("puts $LOAD_PATH").lines.map(&:chomp)
+ paths.shift if paths.first.end_with?('/gem-rehash')
+ if PlatformGuard.implementation? :ruby
+ # In a MRI checkout, $PWD and some extra -I entries end up as
+ # the first entries in $LOAD_PATH. So just assert that it's not last.
+ idx = paths.index(dir)
+ idx.should < paths.size-1
+ else
+ paths[0].should == dir
+ end
+ end
+
+ it "adds the directory after directories added by -I" do
+ dash_i_dir = tmp("dash_I_include")
+ rubylib_dir = tmp("rubylib_include")
+ ENV["RUBYLIB"] = @pre + rubylib_dir
+ paths = ruby_exe("puts $LOAD_PATH", options: "-I #{dash_i_dir}").lines.map(&:chomp)
+ paths.should.include?(dash_i_dir)
+ paths.should.include?(rubylib_dir)
+ paths.index(dash_i_dir).should < paths.index(rubylib_dir)
+ end
+
+ it "adds the directory after directories added by -I within RUBYOPT" do
+ rubyopt_dir = tmp("rubyopt_include")
+ rubylib_dir = tmp("rubylib_include")
+ ENV["RUBYLIB"] = @pre + rubylib_dir
+ paths = ruby_exe("puts $LOAD_PATH", env: { "RUBYOPT" => "-I#{rubyopt_dir}" }).lines.map(&:chomp)
+ paths.should.include?(rubyopt_dir)
+ paths.should.include?(rubylib_dir)
+ paths.index(rubyopt_dir).should < paths.index(rubylib_dir)
+ end
+
+ it "keeps spaces in the value" do
+ ENV["RUBYLIB"] = @pre + " rubylib/incl "
+ out = ruby_exe("puts $LOAD_PATH")
+ out.should.include?(" rubylib/incl ")
+ end
+end
diff --git a/spec/ruby/command_line/rubyopt_spec.rb b/spec/ruby/command_line/rubyopt_spec.rb
new file mode 100644
index 0000000000..eb297cd6fe
--- /dev/null
+++ b/spec/ruby/command_line/rubyopt_spec.rb
@@ -0,0 +1,185 @@
+require_relative '../spec_helper'
+
+describe "Processing RUBYOPT" do
+ before :each do
+ @rubyopt, ENV["RUBYOPT"] = ENV["RUBYOPT"], nil
+ end
+
+ after :each do
+ ENV["RUBYOPT"] = @rubyopt
+ end
+
+ it "adds the -I path to $LOAD_PATH" do
+ ENV["RUBYOPT"] = "-Ioptrubyspecincl"
+ result = ruby_exe("puts $LOAD_PATH.grep(/byspecin/)")
+ result.chomp[-15..-1].should == "optrubyspecincl"
+ end
+
+ it "sets $DEBUG to true for '-d'" do
+ ENV["RUBYOPT"] = '-d'
+ command = %[puts "value of $DEBUG is \#{$DEBUG}"]
+ result = ruby_exe(command, args: "2>&1")
+ result.should =~ /value of \$DEBUG is true/
+ end
+
+ guard -> { RbConfig::CONFIG["CROSS_COMPILING"] != "yes" } do
+ it "prints the version number for '-v'" do
+ ENV["RUBYOPT"] = '-v'
+ ruby_exe("")[/\A.*/].gsub(/\s\+(YJIT( \w+)?|ZJIT( \w+)?|PRISM|GC(\[\w+\])?)(?=\s)/, "").should == RUBY_DESCRIPTION.gsub(/\s\+(YJIT( \w+)?|ZJIT( \w+)?|PRISM|GC(\[\w+\])?)(?=\s)/, "")
+ end
+
+ it "ignores whitespace around the option" do
+ ENV["RUBYOPT"] = ' -v '
+ ruby_exe("")[/\A.*/].gsub(/\s\+(YJIT( \w+)?|ZJIT( \w+)?|PRISM|GC(\[\w+\])?)(?=\s)/, "").should == RUBY_DESCRIPTION.gsub(/\s\+(YJIT( \w+)?|ZJIT( \w+)?|PRISM|GC(\[\w+\])?)(?=\s)/, "")
+ end
+ end
+
+ it "sets $VERBOSE to true for '-w'" do
+ ENV["RUBYOPT"] = '-w'
+ ruby_exe("p $VERBOSE").chomp.should == "true"
+ end
+
+ it "sets $VERBOSE to true for '-W'" do
+ ENV["RUBYOPT"] = '-W'
+ ruby_exe("p $VERBOSE").chomp.should == "true"
+ end
+
+ it "sets $VERBOSE to nil for '-W0'" do
+ ENV["RUBYOPT"] = '-W0'
+ ruby_exe("p $VERBOSE").chomp.should == "nil"
+ end
+
+ it "sets $VERBOSE to false for '-W1'" do
+ ENV["RUBYOPT"] = '-W1'
+ ruby_exe("p $VERBOSE").chomp.should == "false"
+ end
+
+ it "sets $VERBOSE to true for '-W2'" do
+ ENV["RUBYOPT"] = '-W2'
+ ruby_exe("p $VERBOSE").chomp.should == "true"
+ end
+
+ it "suppresses deprecation warnings for '-W:no-deprecated'" do
+ ENV["RUBYOPT"] = '-W:no-deprecated'
+ result = ruby_exe('$; = ""', args: '2>&1')
+ result.should == ""
+ end
+
+ it "suppresses experimental warnings for '-W:no-experimental'" do
+ ENV["RUBYOPT"] = '-W:no-experimental'
+ result = ruby_exe('case 0; in a; end', args: '2>&1')
+ result.should == ""
+ end
+
+ it "suppresses deprecation and experimental warnings for '-W:no-deprecated -W:no-experimental'" do
+ ENV["RUBYOPT"] = '-W:no-deprecated -W:no-experimental'
+ result = ruby_exe('case ($; = ""); in a; end', args: '2>&1')
+ result.should == ""
+ end
+
+ it "requires the file for '-r'" do
+ f = fixture __FILE__, "rubyopt"
+ ENV["RUBYOPT"] = "-r#{f}"
+ ruby_exe("0", args: '2>&1').should =~ /^rubyopt.rb required/
+ end
+
+ it "raises a RuntimeError for '-a'" do
+ ENV["RUBYOPT"] = '-a'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-p'" do
+ ENV["RUBYOPT"] = '-p'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-n'" do
+ ENV["RUBYOPT"] = '-n'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-y'" do
+ ENV["RUBYOPT"] = '-y'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-c'" do
+ ENV["RUBYOPT"] = '-c'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-s'" do
+ ENV["RUBYOPT"] = '-s'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-h'" do
+ ENV["RUBYOPT"] = '-h'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '--help'" do
+ ENV["RUBYOPT"] = '--help'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-l'" do
+ ENV["RUBYOPT"] = '-l'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-S'" do
+ ENV["RUBYOPT"] = '-S irb'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-e'" do
+ ENV["RUBYOPT"] = '-e0'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-i'" do
+ ENV["RUBYOPT"] = '-i.bak'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-x'" do
+ ENV["RUBYOPT"] = '-x'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-C'" do
+ ENV["RUBYOPT"] = '-C'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-X'" do
+ ENV["RUBYOPT"] = '-X.'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-F'" do
+ ENV["RUBYOPT"] = '-F'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '-0'" do
+ ENV["RUBYOPT"] = '-0'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '--copyright'" do
+ ENV["RUBYOPT"] = '--copyright'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '--version'" do
+ ENV["RUBYOPT"] = '--version'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+
+ it "raises a RuntimeError for '--yydebug'" do
+ ENV["RUBYOPT"] = '--yydebug'
+ ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/
+ end
+end
diff --git a/spec/ruby/command_line/shared/change_directory.rb b/spec/ruby/command_line/shared/change_directory.rb
new file mode 100644
index 0000000000..9cb6e90ac6
--- /dev/null
+++ b/spec/ruby/command_line/shared/change_directory.rb
@@ -0,0 +1,21 @@
+describe :command_line_change_directory, shared: true do
+ before :all do
+ @script = fixture(__FILE__, 'change_directory_script.rb')
+ @tempdir = File.dirname(@script)
+ end
+
+ it 'changes the PWD when using a file' do
+ output = ruby_exe(@script, options: "#{@method} #{@tempdir}")
+ output.should == @tempdir
+ end
+
+ it 'does not need a space after -C for the argument' do
+ output = ruby_exe(@script, options: "#{@method}#{@tempdir}")
+ output.should == @tempdir
+ end
+
+ it 'changes the PWD when using -e' do
+ output = ruby_exe(nil, options: "#{@method} #{@tempdir} -e 'print Dir.pwd'")
+ output.should == @tempdir
+ end
+end
diff --git a/spec/ruby/command_line/shared/verbose.rb b/spec/ruby/command_line/shared/verbose.rb
new file mode 100644
index 0000000000..c5c44c269e
--- /dev/null
+++ b/spec/ruby/command_line/shared/verbose.rb
@@ -0,0 +1,9 @@
+describe :command_line_verbose, shared: true do
+ before :each do
+ @script = fixture __FILE__, "verbose.rb"
+ end
+
+ it "sets $VERBOSE to true" do
+ ruby_exe(@script, options: @method).chomp.b.split.last.should == "true"
+ end
+end
diff --git a/spec/ruby/command_line/syntax_error_spec.rb b/spec/ruby/command_line/syntax_error_spec.rb
new file mode 100644
index 0000000000..88864c048e
--- /dev/null
+++ b/spec/ruby/command_line/syntax_error_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../spec_helper'
+
+describe "The interpreter" do
+ it "prints an error when given a file with invalid syntax" do
+ out = ruby_exe(fixture(__FILE__, "bad_syntax.rb"), args: "2>&1", exit_status: 1)
+ out.should.include?("SyntaxError")
+ end
+
+ it "prints an error when given code via -e with invalid syntax" do
+ out = ruby_exe(nil, args: "-e 'a{' 2>&1", exit_status: 1)
+ out.should.include?("SyntaxError")
+ end
+end
diff --git a/spec/ruby/core/argf/argf_spec.rb b/spec/ruby/core/argf/argf_spec.rb
new file mode 100644
index 0000000000..f9468539bb
--- /dev/null
+++ b/spec/ruby/core/argf/argf_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "ARGF" do
+ it "is extended by the Enumerable module" do
+ ARGF.should.is_a?(Enumerable)
+ end
+
+ it "is an instance of ARGF.class" do
+ ARGF.should.instance_of?(ARGF.class)
+ end
+end
diff --git a/spec/ruby/core/argf/argv_spec.rb b/spec/ruby/core/argf/argv_spec.rb
new file mode 100644
index 0000000000..77dfe78c21
--- /dev/null
+++ b/spec/ruby/core/argf/argv_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.argv" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "returns ARGV for the initial ARGF" do
+ ARGF.argv.should.equal? ARGV
+ end
+
+ it "returns the remaining arguments to treat" do
+ argf [@file1, @file2] do
+ # @file1 is stored in current file
+ @argf.argv.should == [@file2]
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/binmode_spec.rb b/spec/ruby/core/argf/binmode_spec.rb
new file mode 100644
index 0000000000..5288e3199d
--- /dev/null
+++ b/spec/ruby/core/argf/binmode_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.binmode" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ @bin_file = fixture __FILE__, "bin_file.txt"
+ end
+
+ it "returns self" do
+ argf [@bin_file] do
+ @argf.binmode.should.equal? @argf
+ end
+ end
+
+ platform_is :windows do
+ it "puts reading into binmode" do
+ argf [@bin_file, @bin_file] do
+ @argf.gets.should == "test\n"
+ @argf.binmode
+ @argf.gets.should == "test\r\n"
+ end
+ end
+
+ it "puts all subsequent streams reading through ARGF into binmode" do
+ argf [@bin_file, @bin_file] do
+ @argf.binmode
+ @argf.gets.should == "test\r\n"
+ @argf.gets.should == "test\r\n"
+ end
+ end
+ end
+
+ it "sets the file's encoding to BINARY" do
+ argf [@bin_file, @file1] do
+ @argf.binmode
+ @argf.should.binmode?
+ @argf.gets.encoding.should == Encoding::BINARY
+ @argf.skip
+ @argf.read.encoding.should == Encoding::BINARY
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/close_spec.rb b/spec/ruby/core/argf/close_spec.rb
new file mode 100644
index 0000000000..8ca7d71dc2
--- /dev/null
+++ b/spec/ruby/core/argf/close_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.close" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ end
+
+ it "closes the current open stream" do
+ argf [@file1_name, @file2_name] do
+ io = @argf.to_io
+ @argf.close
+ io.closed?.should == true
+ end
+ end
+
+ it "returns self" do
+ argf [@file1_name, @file2_name] do
+ @argf.close.should.equal?(@argf)
+ end
+ end
+
+ it "doesn't raise an IOError if called on a closed stream" do
+ argf [@file1_name] do
+ -> { @argf.close }.should_not.raise
+ -> { @argf.close }.should_not.raise
+ end
+ end
+end
+
+describe "ARGF.close" do
+ it "does not close STDIN" do
+ ruby_exe("ARGV.replace(['-']); ARGF.close; print ARGF.closed?").should == "false"
+ end
+end
diff --git a/spec/ruby/core/argf/closed_spec.rb b/spec/ruby/core/argf/closed_spec.rb
new file mode 100644
index 0000000000..769381e8c3
--- /dev/null
+++ b/spec/ruby/core/argf/closed_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.closed?" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ end
+
+ it "returns true if the current stream has been closed" do
+ argf [@file1_name, @file2_name] do
+ stream = @argf.to_io
+ stream.close
+
+ @argf.closed?.should == true
+ stream.reopen(@argf.filename, 'r')
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/each_byte_spec.rb b/spec/ruby/core/argf/each_byte_spec.rb
new file mode 100644
index 0000000000..d9e4e7fe5b
--- /dev/null
+++ b/spec/ruby/core/argf/each_byte_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.each_byte" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @bytes = []
+ File.read(@file1_name).each_byte { |b| @bytes << b }
+ File.read(@file2_name).each_byte { |b| @bytes << b }
+ end
+
+ it "yields each byte of all streams to the passed block" do
+ argf [@file1_name, @file2_name] do
+ bytes = []
+ @argf.each_byte { |b| bytes << b }
+ bytes.should == @bytes
+ end
+ end
+
+ it "returns self when passed a block" do
+ argf [@file1_name, @file2_name] do
+ @argf.each_byte {}.should.equal?(@argf)
+ end
+ end
+
+ it "returns an Enumerator when passed no block" do
+ argf [@file1_name, @file2_name] do
+ enum = @argf.each_byte
+ enum.should.instance_of?(Enumerator)
+
+ bytes = []
+ enum.each { |b| bytes << b }
+ bytes.should == @bytes
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ argf [@file1_name, @file2_name] do
+ enum = @argf.each_byte
+ enum.should.instance_of?(Enumerator)
+
+ bytes = []
+ enum.each { |b| bytes << b }
+ bytes.should == @bytes
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ argf [@file1_name, @file2_name] do
+ @argf.each_byte.size.should == nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/each_char_spec.rb b/spec/ruby/core/argf/each_char_spec.rb
new file mode 100644
index 0000000000..62d19b6574
--- /dev/null
+++ b/spec/ruby/core/argf/each_char_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.each_char" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @chars = []
+ File.read(@file1_name).each_char { |c| @chars << c }
+ File.read(@file2_name).each_char { |c| @chars << c }
+ end
+
+ it "yields each char of all streams to the passed block" do
+ argf [@file1_name, @file2_name] do
+ chars = []
+ @argf.each_char { |c| chars << c }
+ chars.should == @chars
+ end
+ end
+
+ it "returns self when passed a block" do
+ argf [@file1_name, @file2_name] do
+ @argf.each_char {}.should.equal?(@argf)
+ end
+ end
+
+ it "returns an Enumerator when passed no block" do
+ argf [@file1_name, @file2_name] do
+ enum = @argf.each_char
+ enum.should.instance_of?(Enumerator)
+
+ chars = []
+ enum.each { |c| chars << c }
+ chars.should == @chars
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ argf [@file1_name, @file2_name] do
+ enum = @argf.each_char
+ enum.should.instance_of?(Enumerator)
+
+ chars = []
+ enum.each { |c| chars << c }
+ chars.should == @chars
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ argf [@file1_name, @file2_name] do
+ @argf.each_char.size.should == nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/each_codepoint_spec.rb b/spec/ruby/core/argf/each_codepoint_spec.rb
new file mode 100644
index 0000000000..ad55785cba
--- /dev/null
+++ b/spec/ruby/core/argf/each_codepoint_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.each_codepoint" do
+ before :each do
+ file1_name = fixture __FILE__, "file1.txt"
+ file2_name = fixture __FILE__, "file2.txt"
+ @filenames = [file1_name, file2_name]
+
+ @codepoints = File.read(file1_name).codepoints
+ @codepoints.concat File.read(file2_name).codepoints
+ end
+
+ it "is a public method" do
+ argf @filenames do
+ @argf.public_methods(false).should.include?(:each_codepoint)
+ end
+ end
+
+ it "does not require arguments" do
+ argf @filenames do
+ @argf.method(:each_codepoint).arity.should == 0
+ end
+ end
+
+ it "returns self when passed a block" do
+ argf @filenames do
+ @argf.each_codepoint {}.should.equal?(@argf)
+ end
+ end
+
+ it "returns an Enumerator when passed no block" do
+ argf @filenames do
+ @argf.each_codepoint.should.instance_of?(Enumerator)
+ end
+ end
+
+ it "yields each codepoint of all streams" do
+ argf @filenames do
+ @argf.each_codepoint.to_a.should == @codepoints
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ argf @filenames do
+ @argf.each_codepoint.should.instance_of?(Enumerator)
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ argf @filenames do
+ @argf.each_codepoint.size.should == nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/each_line_spec.rb b/spec/ruby/core/argf/each_line_spec.rb
new file mode 100644
index 0000000000..fc4d6433b8
--- /dev/null
+++ b/spec/ruby/core/argf/each_line_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.each_line" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @lines = File.readlines @file1_name
+ @lines += File.readlines @file2_name
+ end
+
+ it "is a public method" do
+ argf [@file1_name, @file2_name] do
+ @argf.public_methods(false).should.include?(:each_line)
+ end
+ end
+
+ it "requires multiple arguments" do
+ argf [@file1_name, @file2_name] do
+ @argf.method(:each_line).arity.should < 0
+ end
+ end
+
+ it "reads each line of files" do
+ argf [@file1_name, @file2_name] do
+ lines = []
+ @argf.each_line { |b| lines << b }
+ lines.should == @lines
+ end
+ end
+
+ it "returns self when passed a block" do
+ argf [@file1_name, @file2_name] do
+ @argf.each_line {}.should.equal?(@argf)
+ end
+ end
+
+ describe "with a separator" do
+ it "yields each separated section of all streams" do
+ argf [@file1_name, @file2_name] do
+ @argf.send(:each_line, '.').to_a.should ==
+ (File.readlines(@file1_name, '.') + File.readlines(@file2_name, '.'))
+ end
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ argf [@file1_name, @file2_name] do
+ @argf.each_line.should.instance_of?(Enumerator)
+ end
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ argf [@file1_name, @file2_name] do
+ @argf.each_line.size.should == nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/each_spec.rb b/spec/ruby/core/argf/each_spec.rb
new file mode 100644
index 0000000000..25f60b31d2
--- /dev/null
+++ b/spec/ruby/core/argf/each_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.each" do
+ it "is an alias of ARGF.each_line" do
+ ARGF.method(:each).should == ARGF.method(:each_line)
+ end
+end
diff --git a/spec/ruby/core/argf/eof_spec.rb b/spec/ruby/core/argf/eof_spec.rb
new file mode 100644
index 0000000000..940d104d69
--- /dev/null
+++ b/spec/ruby/core/argf/eof_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.eof" do
+ it "is an alias of ARGF.eof?" do
+ ARGF.method(:eof).should == ARGF.method(:eof?)
+ end
+end
+
+describe "ARGF.eof?" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns true when reaching the end of a file" do
+ argf [@file1, @file2] do
+ result = []
+ while @argf.gets
+ result << @argf.eof?
+ end
+ result.should == [false, true, false, true]
+ end
+ end
+
+ it "raises IOError when called on a closed stream" do
+ argf [@file1] do
+ @argf.read
+ -> { @argf.eof? }.should.raise(IOError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/file_spec.rb b/spec/ruby/core/argf/file_spec.rb
new file mode 100644
index 0000000000..df8552d457
--- /dev/null
+++ b/spec/ruby/core/argf/file_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.file" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns the current file object on each file" do
+ argf [@file1, @file2] do
+ result = []
+ # returns first current file even when not yet open
+ result << @argf.file.path
+ result << @argf.file.path while @argf.gets
+ # returns last current file even when closed
+ result << @argf.file.path
+ result.should == [@file1, @file1, @file1, @file2, @file2, @file2]
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/filename_spec.rb b/spec/ruby/core/argf/filename_spec.rb
new file mode 100644
index 0000000000..f4b6e922c6
--- /dev/null
+++ b/spec/ruby/core/argf/filename_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.filename" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns the current file name on each file" do
+ argf [@file1, @file2] do
+ result = []
+ # returns first current file even when not yet open
+ result << @argf.filename
+ result << @argf.filename while @argf.gets
+ # returns last current file even when closed
+ result << @argf.filename
+
+ result.map! { |f| File.expand_path(f) }
+ result.should == [@file1, @file1, @file1, @file2, @file2, @file2]
+ end
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "sets the $FILENAME global variable with the current file name on each file" do
+ script = fixture __FILE__, "filename.rb"
+ out = ruby_exe(script, args: [@file1, @file2])
+ out.should == "#{@file1}\n#{@file1}\n#{@file2}\n#{@file2}\n#{@file2}\n"
+ end
+end
diff --git a/spec/ruby/core/argf/fileno_spec.rb b/spec/ruby/core/argf/fileno_spec.rb
new file mode 100644
index 0000000000..99245f043c
--- /dev/null
+++ b/spec/ruby/core/argf/fileno_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.fileno" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns the current file number on each file" do
+ argf [@file1, @file2] do
+ result = []
+ # returns first current file even when not yet open
+ result << @argf.fileno while @argf.gets
+ # returns last current file even when closed
+ result.map { |d| d.class }.should == [Integer, Integer, Integer, Integer]
+ end
+ end
+
+ it "raises an ArgumentError when called on a closed stream" do
+ argf [@file1] do
+ @argf.read
+ -> { @argf.fileno }.should.raise(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/fixtures/bin_file.txt b/spec/ruby/core/argf/fixtures/bin_file.txt
new file mode 100644
index 0000000000..2545e9037e
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/bin_file.txt
@@ -0,0 +1,2 @@
+test
+test
diff --git a/spec/ruby/core/argf/fixtures/file1.txt b/spec/ruby/core/argf/fixtures/file1.txt
new file mode 100644
index 0000000000..1c89bfbd82
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/file1.txt
@@ -0,0 +1,2 @@
+file1.1
+file1.2
diff --git a/spec/ruby/core/argf/fixtures/file2.txt b/spec/ruby/core/argf/fixtures/file2.txt
new file mode 100644
index 0000000000..62e8dba00b
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/file2.txt
@@ -0,0 +1,2 @@
+line2.1
+line2.2
diff --git a/spec/ruby/core/argf/fixtures/filename.rb b/spec/ruby/core/argf/fixtures/filename.rb
new file mode 100644
index 0000000000..599c97dd57
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/filename.rb
@@ -0,0 +1,3 @@
+puts $FILENAME while ARGF.gets
+# returns last current file even when closed
+puts $FILENAME
diff --git a/spec/ruby/core/argf/fixtures/lineno.rb b/spec/ruby/core/argf/fixtures/lineno.rb
new file mode 100644
index 0000000000..079cc92e8e
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/lineno.rb
@@ -0,0 +1,5 @@
+puts $.
+ARGF.gets
+puts $.
+ARGF.gets
+puts $.
diff --git a/spec/ruby/core/argf/fixtures/rewind.rb b/spec/ruby/core/argf/fixtures/rewind.rb
new file mode 100644
index 0000000000..90a334cd89
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/rewind.rb
@@ -0,0 +1,5 @@
+puts ARGF.lineno
+ARGF.gets
+puts ARGF.lineno
+ARGF.rewind
+puts ARGF.lineno
diff --git a/spec/ruby/core/argf/fixtures/stdin.txt b/spec/ruby/core/argf/fixtures/stdin.txt
new file mode 100644
index 0000000000..063cdcb1d4
--- /dev/null
+++ b/spec/ruby/core/argf/fixtures/stdin.txt
@@ -0,0 +1,2 @@
+stdin.1
+stdin.2
diff --git a/spec/ruby/core/argf/getc_spec.rb b/spec/ruby/core/argf/getc_spec.rb
new file mode 100644
index 0000000000..dc5de9b7df
--- /dev/null
+++ b/spec/ruby/core/argf/getc_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'shared/getc'
+
+describe "ARGF.getc" do
+ it_behaves_like :argf_getc, :getc
+end
+
+describe "ARGF.getc" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "returns nil when end of stream reached" do
+ argf [@file1, @file2] do
+ @argf.read
+ @argf.getc.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/gets_spec.rb b/spec/ruby/core/argf/gets_spec.rb
new file mode 100644
index 0000000000..cc7673b190
--- /dev/null
+++ b/spec/ruby/core/argf/gets_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gets'
+
+describe "ARGF.gets" do
+ it_behaves_like :argf_gets, :gets
+end
+
+describe "ARGF.gets" do
+ it_behaves_like :argf_gets_inplace_edit, :gets
+end
+
+describe "ARGF.gets" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ end
+
+ it "returns nil when reaching end of files" do
+ argf [@file1_name, @file2_name] do
+ total = @file1.size + @file2.size
+ total.times { @argf.gets }
+ @argf.gets.should == nil
+ end
+ end
+
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = nil
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "reads the contents of the file with default encoding" do
+ Encoding.default_external = Encoding::US_ASCII
+ argf [@file1_name, @file2_name] do
+ @argf.gets.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+end
diff --git a/spec/ruby/core/argf/inspect_spec.rb b/spec/ruby/core/argf/inspect_spec.rb
new file mode 100644
index 0000000000..df0e3ba8dc
--- /dev/null
+++ b/spec/ruby/core/argf/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.inspect" do
+ it "is an alias of ARGF.to_s" do
+ ARGF.method(:inspect).should == ARGF.method(:to_s)
+ end
+end
diff --git a/spec/ruby/core/argf/lineno_spec.rb b/spec/ruby/core/argf/lineno_spec.rb
new file mode 100644
index 0000000000..72a108c187
--- /dev/null
+++ b/spec/ruby/core/argf/lineno_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.lineno" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ # TODO: break this into four specs
+ it "returns the current line number on each file" do
+ argf [@file1, @file2] do
+ @argf.lineno = 0
+ @argf.gets
+ @argf.lineno.should == 1
+ @argf.gets
+ @argf.lineno.should == 2
+ @argf.gets
+ @argf.lineno.should == 3
+ @argf.gets
+ @argf.lineno.should == 4
+ end
+ end
+
+ it "aliases to $." do
+ script = fixture __FILE__, "lineno.rb"
+ out = ruby_exe(script, args: [@file1, @file2])
+ out.should == "0\n1\n2\n"
+ end
+end
diff --git a/spec/ruby/core/argf/path_spec.rb b/spec/ruby/core/argf/path_spec.rb
new file mode 100644
index 0000000000..2f7b91999f
--- /dev/null
+++ b/spec/ruby/core/argf/path_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.path" do
+ it "is an alias of ARGF.filename" do
+ ARGF.method(:path).should == ARGF.method(:filename)
+ end
+end
diff --git a/spec/ruby/core/argf/pos_spec.rb b/spec/ruby/core/argf/pos_spec.rb
new file mode 100644
index 0000000000..1ff16e4f17
--- /dev/null
+++ b/spec/ruby/core/argf/pos_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.pos" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "gives the correct position for each read operation" do
+ argf [@file1, @file2] do
+ size1 = File.size(@file1)
+ size2 = File.size(@file2)
+
+ @argf.read(2)
+ @argf.pos.should == 2
+ @argf.read(size1-2)
+ @argf.pos.should == size1
+ @argf.read(6)
+ @argf.pos.should == 6
+ @argf.rewind
+ @argf.pos.should == 0
+ @argf.read(size2)
+ @argf.pos.should == size2
+ end
+ end
+
+ it "raises an ArgumentError when called on a closed stream" do
+ argf [@file1] do
+ @argf.read
+ -> { @argf.pos }.should.raise(ArgumentError)
+ end
+ end
+end
+
+describe "ARGF.pos=" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "sets the correct position in files" do
+ argf [@file1_name, @file2_name] do
+ @argf.pos = @file1.first.size
+ @argf.gets.should == @file1.last
+ @argf.pos = 0
+ @argf.gets.should == @file1.first
+
+ # finish reading file1
+ @argf.gets
+
+ @argf.gets
+ @argf.pos = 1
+ @argf.gets.should == @file2.first[1..-1]
+
+ @argf.pos = @file2.first.size + @file2.last.size - 1
+ @argf.gets.should == @file2.last[-1,1]
+ @argf.pos = 1000
+ @argf.read.should == ""
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/read_nonblock_spec.rb b/spec/ruby/core/argf/read_nonblock_spec.rb
new file mode 100644
index 0000000000..5c6bd52d80
--- /dev/null
+++ b/spec/ruby/core/argf/read_nonblock_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'shared/read'
+
+platform_is_not :windows do
+ describe 'ARGF.read_nonblock' do
+ it_behaves_like :argf_read, :read_nonblock
+
+ before do
+ @file1_name = fixture(__FILE__, 'file1.txt')
+ @file2_name = fixture(__FILE__, 'file2.txt')
+
+ @file1 = File.read(@file1_name)
+ @file2 = File.read(@file2_name)
+
+ @chunk1 = File.read(@file1_name, 4)
+ @chunk2 = File.read(@file2_name, 4)
+ end
+
+ it 'reads up to the given amount of bytes' do
+ argf [@file1_name] do
+ @argf.read_nonblock(4).should == @chunk1
+ end
+ end
+
+ describe 'when using multiple files' do
+ it 'reads up to the given amount of bytes from the first file' do
+ argf [@file1_name, @file2_name] do
+ @argf.read_nonblock(4).should == @chunk1
+ end
+ end
+
+ it 'returns an empty String when reading after having read the first file in its entirety' do
+ argf [@file1_name, @file2_name] do
+ @argf.read_nonblock(File.size(@file1_name)).should == @file1
+ @argf.read_nonblock(4).should == ''
+ end
+ end
+ end
+
+ it 'reads up to the given bytes from STDIN' do
+ stdin = ruby_exe('print ARGF.read_nonblock(4)', :args => "< #{@file1_name}")
+
+ stdin.should == @chunk1
+ end
+
+ it 'reads up to the given bytes from a file when a file and STDIN are present' do
+ stdin = ruby_exe("print ARGF.read_nonblock(4)", :args => "#{@file1_name} - < #{@file2_name}")
+
+ stdin.should == @chunk1
+ end
+
+ context "with STDIN" do
+ before do
+ @r, @w = IO.pipe
+ @stdin = $stdin
+ $stdin = @r
+ end
+
+ after do
+ $stdin = @stdin
+ @w.close
+ @r.close unless @r.closed?
+ end
+
+ it 'raises IO::EAGAINWaitReadable when empty' do
+ argf ['-'] do
+ -> {
+ @argf.read_nonblock(4)
+ }.should.raise(IO::EAGAINWaitReadable)
+ end
+ end
+
+ it 'returns :wait_readable when the :exception is set to false' do
+ argf ['-'] do
+ @argf.read_nonblock(4, nil, exception: false).should == :wait_readable
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/read_spec.rb b/spec/ruby/core/argf/read_spec.rb
new file mode 100644
index 0000000000..bbeef95456
--- /dev/null
+++ b/spec/ruby/core/argf/read_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../spec_helper'
+require_relative 'shared/read'
+
+describe "ARGF.read" do
+ it_behaves_like :argf_read, :read
+
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ @stdin_name = fixture __FILE__, "stdin.txt"
+
+ @file1 = File.read @file1_name
+ @file2 = File.read @file2_name
+ @stdin = File.read @stdin_name
+ end
+
+ it "reads the contents of a file" do
+ argf [@file1_name] do
+ @argf.read().should == @file1
+ end
+ end
+
+ it "treats first nil argument as no length limit" do
+ argf [@file1_name] do
+ @argf.read(nil).should == @file1
+ end
+ end
+
+ it "reads the contents of two files" do
+ argf [@file1_name, @file2_name] do
+ @argf.read.should == @file1 + @file2
+ end
+ end
+
+ it "reads the contents of one file and some characters from the second" do
+ argf [@file1_name, @file2_name] do
+ len = @file1.size + (@file2.size / 2)
+ @argf.read(len).should == (@file1 + @file2)[0,len]
+ end
+ end
+
+ it "reads across two files consecutively" do
+ argf [@file1_name, @file2_name] do
+ @argf.read(@file1.size - 2).should == @file1[0..-3]
+ @argf.read(2+5).should == @file1[-2..-1] + @file2[0,5]
+ end
+ end
+
+ it "reads the contents of stdin" do
+ stdin = ruby_exe("print ARGF.read", args: "< #{@stdin_name}")
+ stdin.should == @stdin
+ end
+
+ it "reads the contents of one file and stdin" do
+ stdin = ruby_exe("print ARGF.read", args: "#{@file1_name} - < #{@stdin_name}")
+ stdin.should == @file1 + @stdin
+ end
+
+ it "reads the contents of the same file twice" do
+ argf [@file1_name, @file1_name] do
+ @argf.read.should == @file1 + @file1
+ end
+ end
+
+
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = nil
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "reads the contents of the file with default encoding" do
+ Encoding.default_external = Encoding::US_ASCII
+ argf [@file1_name, @file2_name] do
+ @argf.read.encoding.should == Encoding::US_ASCII
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/readchar_spec.rb b/spec/ruby/core/argf/readchar_spec.rb
new file mode 100644
index 0000000000..63632721ec
--- /dev/null
+++ b/spec/ruby/core/argf/readchar_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/getc'
+
+describe "ARGF.getc" do
+ it_behaves_like :argf_getc, :readchar
+end
+
+describe "ARGF.readchar" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "raises EOFError when end of stream reached" do
+ argf [@file1, @file2] do
+ -> { while @argf.readchar; end }.should.raise(EOFError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/readline_spec.rb b/spec/ruby/core/argf/readline_spec.rb
new file mode 100644
index 0000000000..8c23549b1b
--- /dev/null
+++ b/spec/ruby/core/argf/readline_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'shared/gets'
+
+describe "ARGF.readline" do
+ it_behaves_like :argf_gets, :readline
+end
+
+describe "ARGF.readline" do
+ it_behaves_like :argf_gets_inplace_edit, :readline
+end
+
+describe "ARGF.readline" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "raises an EOFError when reaching end of files" do
+ argf [@file1, @file2] do
+ -> { while @argf.readline; end }.should.raise(EOFError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/readlines_spec.rb b/spec/ruby/core/argf/readlines_spec.rb
new file mode 100644
index 0000000000..156bb6a33f
--- /dev/null
+++ b/spec/ruby/core/argf/readlines_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.readlines" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+
+ @lines = File.readlines(@file1)
+ @lines += File.readlines(@file2)
+ end
+
+ it "reads all lines of all files" do
+ argf [@file1, @file2] do
+ @argf.readlines.should == @lines
+ end
+ end
+
+ it "returns an empty Array when end of stream reached" do
+ argf [@file1, @file2] do
+ @argf.read
+ @argf.readlines.should == []
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/readpartial_spec.rb b/spec/ruby/core/argf/readpartial_spec.rb
new file mode 100644
index 0000000000..9f04e72cc2
--- /dev/null
+++ b/spec/ruby/core/argf/readpartial_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../../spec_helper'
+require_relative 'shared/read'
+
+describe "ARGF.readpartial" do
+ it_behaves_like :argf_read, :readpartial
+
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ @stdin_name = fixture __FILE__, "stdin.txt"
+
+ @file1 = File.read @file1_name
+ @file2 = File.read @file2_name
+ @stdin = File.read @stdin_name
+ end
+
+ it "raises an ArgumentError if called without a maximum read length" do
+ argf [@file1_name] do
+ -> { @argf.readpartial }.should.raise(ArgumentError)
+ end
+ end
+
+ it "reads maximum number of bytes from one file at a time" do
+ argf [@file1_name, @file2_name] do
+ len = @file1.size + @file2.size
+ @argf.readpartial(len).should == @file1
+ end
+ end
+
+ it "clears output buffer even if EOFError is raised because @argf is at end" do
+ begin
+ output = +"to be cleared"
+
+ argf [@file1_name] do
+ @argf.read
+ @argf.readpartial(1, output)
+ end
+ rescue EOFError
+ output.should == ""
+ end
+ end
+
+ it "reads maximum number of bytes from one file at a time" do
+ argf [@file1_name, @file2_name] do
+ len = @file1.size + @file2.size
+ @argf.readpartial(len).should == @file1
+ end
+ end
+
+ it "returns an empty string if EOFError is raised while reading any but the last file" do
+ argf [@file1_name, @file2_name] do
+ @argf.readpartial(@file1.size)
+ @argf.readpartial(1).should == ""
+ end
+ end
+
+ it "raises an EOFError if the exception was raised while reading the last file" do
+ argf [@file1_name, @file2_name] do
+ @argf.readpartial(@file1.size)
+ @argf.readpartial(1)
+ @argf.readpartial(@file2.size)
+ -> { @argf.readpartial(1) }.should.raise(EOFError)
+ -> { @argf.readpartial(1) }.should.raise(EOFError)
+ end
+ end
+
+ it "raises an EOFError if the exception was raised while reading STDIN" do
+ ruby_str = <<-STR
+ print ARGF.readpartial(#{@stdin.size})
+ ARGF.readpartial(1) rescue print $!.class
+ STR
+ stdin = ruby_exe(ruby_str, args: "< #{@stdin_name}")
+ stdin.should == @stdin + "EOFError"
+ end
+end
diff --git a/spec/ruby/core/argf/rewind_spec.rb b/spec/ruby/core/argf/rewind_spec.rb
new file mode 100644
index 0000000000..9255f790fe
--- /dev/null
+++ b/spec/ruby/core/argf/rewind_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.rewind" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "goes back to beginning of current file" do
+ argf [@file1_name, @file2_name] do
+ @argf.gets
+ @argf.rewind
+ @argf.gets.should == @file1.first
+
+ @argf.gets # finish reading file1
+
+ @argf.gets
+ @argf.rewind
+ @argf.gets.should == @file2.first
+ end
+ end
+
+ it "resets ARGF.lineno to 0" do
+ script = fixture __FILE__, "rewind.rb"
+ out = ruby_exe(script, args: [@file1_name, @file2_name])
+ out.should == "0\n1\n0\n"
+ end
+
+ it "raises an ArgumentError when end of stream reached" do
+ argf [@file1_name, @file2_name] do
+ @argf.read
+ -> { @argf.rewind }.should.raise(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/seek_spec.rb b/spec/ruby/core/argf/seek_spec.rb
new file mode 100644
index 0000000000..c1ea1ea438
--- /dev/null
+++ b/spec/ruby/core/argf/seek_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.seek" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ end
+
+ it "sets the absolute position relative to beginning of file" do
+ argf [@file1_name, @file2_name] do
+ @argf.seek 2
+ @argf.gets.should == @file1.first[2..-1]
+ @argf.seek @file1.first.size
+ @argf.gets.should == @file1.last
+ @argf.seek 0, IO::SEEK_END
+ @argf.gets.should == @file2.first
+ end
+ end
+
+ it "sets the position relative to current position in file" do
+ argf [@file1_name, @file2_name] do
+ @argf.seek(0, IO::SEEK_CUR)
+ @argf.gets.should == @file1.first
+ @argf.seek(-@file1.first.size+2, IO::SEEK_CUR)
+ @argf.gets.should == @file1.first[2..-1]
+ @argf.seek(1, IO::SEEK_CUR)
+ @argf.gets.should == @file1.last[1..-1]
+ @argf.seek(3, IO::SEEK_CUR)
+ @argf.gets.should == @file2.first
+ @argf.seek(@file1.last.size, IO::SEEK_CUR)
+ @argf.gets.should == nil
+ end
+ end
+
+ it "sets the absolute position relative to end of file" do
+ argf [@file1_name, @file2_name] do
+ @argf.seek(-@file1.first.size-@file1.last.size, IO::SEEK_END)
+ @argf.gets.should == @file1.first
+ @argf.seek(-6, IO::SEEK_END)
+ @argf.gets.should == @file1.last[-6..-1]
+ @argf.seek(-4, IO::SEEK_END)
+ @argf.gets.should == @file1.last[4..-1]
+ @argf.gets.should == @file2.first
+ @argf.seek(-6, IO::SEEK_END)
+ @argf.gets.should == @file2.last[-6..-1]
+ end
+ end
+end
+
+describe "ARGF.seek" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ end
+
+ it "takes at least one argument (offset)" do
+ argf [@file1_name] do
+ -> { @argf.seek }.should.raise(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/set_encoding_spec.rb b/spec/ruby/core/argf/set_encoding_spec.rb
new file mode 100644
index 0000000000..a871e084b6
--- /dev/null
+++ b/spec/ruby/core/argf/set_encoding_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.set_encoding" do
+ before :each do
+ @file = fixture __FILE__, "file1.txt"
+ end
+
+ it "sets the external encoding when passed an encoding instance" do
+ argf [@file] do
+ @argf.set_encoding(Encoding::US_ASCII)
+ @argf.external_encoding.should == Encoding::US_ASCII
+ @argf.gets.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "sets the external encoding when passed an encoding name" do
+ argf [@file] do
+ @argf.set_encoding("us-ascii")
+ @argf.external_encoding.should == Encoding::US_ASCII
+ @argf.gets.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "sets the external, internal encoding when passed two encoding instances" do
+ argf [@file] do
+ @argf.set_encoding(Encoding::US_ASCII, Encoding::EUC_JP)
+ @argf.external_encoding.should == Encoding::US_ASCII
+ @argf.internal_encoding.should == Encoding::EUC_JP
+ @argf.gets.encoding.should == Encoding::EUC_JP
+ end
+ end
+
+ it "sets the external, internal encoding when passed 'ext:int' String" do
+ argf [@file] do
+ @argf.set_encoding("us-ascii:euc-jp")
+ @argf.external_encoding.should == Encoding::US_ASCII
+ @argf.internal_encoding.should == Encoding::EUC_JP
+ @argf.gets.encoding.should == Encoding::EUC_JP
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/getc.rb b/spec/ruby/core/argf/shared/getc.rb
new file mode 100644
index 0000000000..d63372d9d7
--- /dev/null
+++ b/spec/ruby/core/argf/shared/getc.rb
@@ -0,0 +1,17 @@
+describe :argf_getc, shared: true do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+
+ @chars = File.read @file1
+ @chars += File.read @file2
+ end
+
+ it "reads each char of files" do
+ argf [@file1, @file2] do
+ chars = +""
+ @chars.size.times { chars << @argf.send(@method) }
+ chars.should == @chars
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/shared/gets.rb b/spec/ruby/core/argf/shared/gets.rb
new file mode 100644
index 0000000000..160d24c27b
--- /dev/null
+++ b/spec/ruby/core/argf/shared/gets.rb
@@ -0,0 +1,99 @@
+describe :argf_gets, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+ @stdin_name = fixture __FILE__, "stdin.txt"
+
+ @file1 = File.readlines @file1_name
+ @file2 = File.readlines @file2_name
+ @stdin = File.read @stdin_name
+ end
+
+ it "reads one line of a file" do
+ argf [@file1_name] do
+ @argf.send(@method).should == @file1.first
+ end
+ end
+
+ it "reads all lines of a file" do
+ argf [@file1_name] do
+ lines = []
+ @file1.size.times { lines << @argf.send(@method) }
+ lines.should == @file1
+ end
+ end
+
+ it "reads all lines of stdin" do
+ total = @stdin.count $/
+ stdin = ruby_exe(
+ "#{total}.times { print ARGF.send(#{@method.inspect}) }",
+ args: "< #{@stdin_name}")
+ stdin.should == @stdin
+ end
+
+ it "reads all lines of two files" do
+ argf [@file1_name, @file2_name] do
+ total = @file1.size + @file2.size
+ lines = []
+ total.times { lines << @argf.send(@method) }
+ lines.should == @file1 + @file2
+ end
+ end
+
+ it "sets $_ global variable with each line read" do
+ argf [@file1_name, @file2_name] do
+ total = @file1.size + @file2.size
+ total.times do
+ line = @argf.send(@method)
+ $_.should == line
+ end
+ end
+ end
+end
+
+describe :argf_gets_inplace_edit, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @tmp1_name = tmp "file1.txt"
+ @tmp2_name = tmp "file2.txt"
+
+ @tmp1_name_bak = @tmp1_name + ".bak"
+ @tmp2_name_bak = @tmp2_name + ".bak"
+
+ cp @file1_name, @tmp1_name
+ cp @file2_name, @tmp2_name
+
+ method = "ARGF.send(#{@method.inspect})"
+ @code = "begin while line = #{method} do puts 'x' end rescue EOFError; end"
+ end
+
+ after :each do
+ rm_r @tmp1_name, @tmp2_name, @tmp1_name_bak, @tmp2_name_bak
+ end
+
+ # -i with no backup extension is not supported on Windows
+ platform_is_not :windows do
+ it "modifies the files when in place edit mode is on" do
+ ruby_exe(@code,
+ options: "-i",
+ args: "#{@tmp1_name} #{@tmp2_name}")
+
+ File.read(@tmp1_name).should == "x\nx\n"
+ File.read(@tmp2_name).should == "x\nx\n"
+ end
+ end
+
+ it "modifies and backups two files when in place edit mode is on" do
+ ruby_exe(@code,
+ options: "-i.bak",
+ args: "#{@tmp1_name} #{@tmp2_name}")
+
+ File.read(@tmp1_name).should == "x\nx\n"
+ File.read(@tmp2_name).should == "x\nx\n"
+
+ File.read(@tmp1_name_bak).should == "file1.1\nfile1.2\n"
+ File.read(@tmp2_name_bak).should == "line2.1\nline2.2\n"
+ end
+end
diff --git a/spec/ruby/core/argf/shared/read.rb b/spec/ruby/core/argf/shared/read.rb
new file mode 100644
index 0000000000..e76d022139
--- /dev/null
+++ b/spec/ruby/core/argf/shared/read.rb
@@ -0,0 +1,58 @@
+describe :argf_read, shared: true do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @stdin_name = fixture __FILE__, "stdin.txt"
+
+ @file1 = File.read @file1_name
+ @stdin = File.read @stdin_name
+ end
+
+ it "treats second nil argument as no output buffer" do
+ argf [@file1_name] do
+ @argf.send(@method, @file1.size, nil).should == @file1
+ end
+ end
+
+ it "treats second argument as an output buffer" do
+ argf [@file1_name] do
+ buffer = +""
+ @argf.send(@method, @file1.size, buffer)
+ buffer.should == @file1
+ end
+ end
+
+ it "clears output buffer before appending to it" do
+ argf [@file1_name] do
+ buffer = +"to be cleared"
+ @argf.send(@method, @file1.size, buffer)
+ buffer.should == @file1
+ end
+ end
+
+ it "reads a number of bytes from the first file" do
+ argf [@file1_name] do
+ @argf.send(@method, 5).should == @file1[0, 5]
+ end
+ end
+
+ it "reads from a single file consecutively" do
+ argf [@file1_name] do
+ @argf.send(@method, 1).should == @file1[0, 1]
+ @argf.send(@method, 2).should == @file1[1, 2]
+ @argf.send(@method, 3).should == @file1[3, 3]
+ end
+ end
+
+ it "reads a number of bytes from stdin" do
+ stdin = ruby_exe("print ARGF.#{@method}(10)", :args => "< #{@stdin_name}")
+ stdin.should == @stdin[0, 10]
+ end
+
+ platform_is_not :windows do
+ it "reads the contents of a special device file" do
+ argf ['/dev/zero'] do
+ @argf.send(@method, 100).should == "\000" * 100
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/skip_spec.rb b/spec/ruby/core/argf/skip_spec.rb
new file mode 100644
index 0000000000..bb1c0ae110
--- /dev/null
+++ b/spec/ruby/core/argf/skip_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.skip" do
+ before :each do
+ @file1_name = fixture __FILE__, "file1.txt"
+ @file2_name = fixture __FILE__, "file2.txt"
+
+ @file2 = File.readlines @file2_name
+ end
+
+ it "skips the current file" do
+ argf [@file1_name, @file2_name] do
+ @argf.read(1)
+ @argf.skip
+ @argf.gets.should == @file2.first
+ end
+ end
+
+ it "has no effect when called twice in a row" do
+ argf [@file1_name, @file2_name] do
+ @argf.read(1)
+ @argf.skip
+ @argf.skip
+ @argf.gets.should == @file2.first
+ end
+ end
+
+ it "has no effect at end of stream" do
+ argf [@file1_name, @file2_name] do
+ @argf.read
+ @argf.skip
+ @argf.gets.should == nil
+ end
+ end
+
+ # This bypasses argf helper because the helper will call argf.file
+ # which as a side-effect calls argf.file which will initialize
+ # internals of ARGF enough for this to work.
+ it "has no effect when nothing has been processed yet" do
+ -> { ARGF.class.new(@file1_name).skip }.should_not.raise
+ end
+end
diff --git a/spec/ruby/core/argf/tell_spec.rb b/spec/ruby/core/argf/tell_spec.rb
new file mode 100644
index 0000000000..bb28df74a2
--- /dev/null
+++ b/spec/ruby/core/argf/tell_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.tell" do
+ it "is an alias of ARGF.pos" do
+ ARGF.method(:tell).should == ARGF.method(:pos)
+ end
+end
diff --git a/spec/ruby/core/argf/to_a_spec.rb b/spec/ruby/core/argf/to_a_spec.rb
new file mode 100644
index 0000000000..d95dc732ec
--- /dev/null
+++ b/spec/ruby/core/argf/to_a_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.to_a" do
+ it "is an alias of ARGF.readlines" do
+ ARGF.method(:to_a).should == ARGF.method(:readlines)
+ end
+end
diff --git a/spec/ruby/core/argf/to_i_spec.rb b/spec/ruby/core/argf/to_i_spec.rb
new file mode 100644
index 0000000000..e8df378f4e
--- /dev/null
+++ b/spec/ruby/core/argf/to_i_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.to_i" do
+ it "is an alias of ARGF.fileno" do
+ ARGF.method(:to_i).should == ARGF.method(:fileno)
+ end
+end
diff --git a/spec/ruby/core/argf/to_io_spec.rb b/spec/ruby/core/argf/to_io_spec.rb
new file mode 100644
index 0000000000..ab5de58bcf
--- /dev/null
+++ b/spec/ruby/core/argf/to_io_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.to_io" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ # NOTE: this test assumes that fixtures files have two lines each
+ it "returns the IO of the current file" do
+ argf [@file1, @file2] do
+ result = []
+ 4.times do
+ @argf.gets
+ result << @argf.to_io
+ end
+
+ result.each { |io| io.should.is_a?(IO) }
+ result[0].should == result[1]
+ result[2].should == result[3]
+ end
+ end
+end
diff --git a/spec/ruby/core/argf/to_s_spec.rb b/spec/ruby/core/argf/to_s_spec.rb
new file mode 100644
index 0000000000..3f505898f4
--- /dev/null
+++ b/spec/ruby/core/argf/to_s_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "ARGF.to_s" do
+ before :each do
+ @file1 = fixture __FILE__, "file1.txt"
+ @file2 = fixture __FILE__, "file2.txt"
+ end
+
+ it "returns 'ARGF'" do
+ argf [@file1, @file2] do
+ @argf.to_s.should == "ARGF"
+ end
+ end
+end
diff --git a/spec/ruby/core/array/all_spec.rb b/spec/ruby/core/array/all_spec.rb
new file mode 100644
index 0000000000..680e8c26fa
--- /dev/null
+++ b/spec/ruby/core/array/all_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#all?" do
+ @value_to_return = -> _ { true }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :all?
+
+ it "ignores the block if there is an argument" do
+ -> {
+ ['bar', 'foobar'].all?(/bar/) { false }.should == true
+ }.should complain(/given block not used/)
+ end
+end
diff --git a/spec/ruby/core/array/allocate_spec.rb b/spec/ruby/core/array/allocate_spec.rb
new file mode 100644
index 0000000000..c9eceef590
--- /dev/null
+++ b/spec/ruby/core/array/allocate_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Array.allocate" do
+ it "returns an instance of Array" do
+ ary = Array.allocate
+ ary.should.instance_of?(Array)
+ end
+
+ it "returns a fully-formed instance of Array" do
+ ary = Array.allocate
+ ary.size.should == 0
+ ary << 1
+ ary.should == [1]
+ end
+
+ it "does not accept any arguments" do
+ -> { Array.allocate(1) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/any_spec.rb b/spec/ruby/core/array/any_spec.rb
new file mode 100644
index 0000000000..b51ce62f0f
--- /dev/null
+++ b/spec/ruby/core/array/any_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#any?" do
+ describe 'with no block given (a default block of { |x| x } is implicit)' do
+ it "is false if the array is empty" do
+ empty_array = []
+ empty_array.should_not.any?
+ end
+
+ it "is false if the array is not empty, but all the members of the array are falsy" do
+ falsy_array = [false, nil, false]
+ falsy_array.should_not.any?
+ end
+
+ it "is true if the array has any truthy members" do
+ not_empty_array = ['anything', nil]
+ not_empty_array.should.any?
+ end
+ end
+
+ describe 'with a block given' do
+ @value_to_return = -> _ { false }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :any?
+
+ it 'is false if the array is empty' do
+ empty_array = []
+ empty_array.any? {|v| 1 == 1 }.should == false
+ end
+
+ it 'is true if the block returns true for any member of the array' do
+ array_with_members = [false, false, true, false]
+ array_with_members.any? {|v| v == true }.should == true
+ end
+
+ it 'is false if the block returns false for all members of the array' do
+ array_with_members = [false, false, true, false]
+ array_with_members.any? {|v| v == 42 }.should == false
+ end
+ end
+
+ describe 'when given a pattern argument' do
+ it "ignores the block if there is an argument" do
+ -> {
+ ['bar', 'foobar'].any?(/bar/) { false }.should == true
+ }.should complain(/given block not used/)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/append_spec.rb b/spec/ruby/core/array/append_spec.rb
new file mode 100644
index 0000000000..5480d9f65e
--- /dev/null
+++ b/spec/ruby/core/array/append_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#<<" do
+ it "pushes the object onto the end of the array" do
+ ([ 1, 2 ] << "c" << "d" << [ 3, 4 ]).should == [1, 2, "c", "d", [3, 4]]
+ end
+
+ it "returns self to allow chaining" do
+ a = []
+ b = a
+ (a << 1).should.equal?(b)
+ (a << 2 << 3).should.equal?(b)
+ end
+
+ it "correctly resizes the Array" do
+ a = []
+ a.size.should == 0
+ a << :foo
+ a.size.should == 1
+ a << :bar << :baz
+ a.size.should == 3
+
+ a = [1, 2, 3]
+ a.shift
+ a.shift
+ a.shift
+ a << :foo
+ a.should == [:foo]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array << 5 }.should.raise(FrozenError)
+ end
+end
+
+describe "Array#append" do
+ it "is an alias of Array#push" do
+ Array.instance_method(:append).should == Array.instance_method(:push)
+ end
+end
diff --git a/spec/ruby/core/array/array_spec.rb b/spec/ruby/core/array/array_spec.rb
new file mode 100644
index 0000000000..855f17348f
--- /dev/null
+++ b/spec/ruby/core/array/array_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Array" do
+ it "includes Enumerable" do
+ Array.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/array/assoc_spec.rb b/spec/ruby/core/array/assoc_spec.rb
new file mode 100644
index 0000000000..a5026cf5d4
--- /dev/null
+++ b/spec/ruby/core/array/assoc_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#assoc" do
+ it "returns the first array whose 1st item is == obj or nil" do
+ s1 = ["colors", "red", "blue", "green"]
+ s2 = [:letters, "a", "b", "c"]
+ s3 = [4]
+ s4 = ["colors", "cyan", "yellow", "magenta"]
+ s5 = [:letters, "a", "i", "u"]
+ s_nil = [nil, nil]
+ a = [s1, s2, s3, s4, s5, s_nil]
+ a.assoc(s1.first).should.equal?(s1)
+ a.assoc(s2.first).should.equal?(s2)
+ a.assoc(s3.first).should.equal?(s3)
+ a.assoc(s4.first).should.equal?(s1)
+ a.assoc(s5.first).should.equal?(s2)
+ a.assoc(s_nil.first).should.equal?(s_nil)
+ a.assoc(4).should.equal?(s3)
+ a.assoc("key not in array").should == nil
+ end
+
+ it "calls == on first element of each array" do
+ key1 = 'it'
+ key2 = mock('key2')
+ items = [['not it', 1], [ArraySpecs::AssocKey.new, 2], ['na', 3]]
+
+ items.assoc(key1).should.equal?(items[1])
+ items.assoc(key2).should == nil
+ end
+
+ it "ignores any non-Array elements" do
+ [1, 2, 3].assoc(2).should == nil
+ s1 = [4]
+ s2 = [5, 4, 3]
+ a = ["foo", [], s1, s2, nil, []]
+ a.assoc(s1.first).should.equal?(s1)
+ a.assoc(s2.first).should.equal?(s2)
+ end
+
+ it "calls to_ary on non-array elements" do
+ s1 = [1, 2]
+ s2 = ArraySpecs::ArrayConvertible.new(2, 3)
+ a = [s1, s2]
+
+ s1.should_not_receive(:to_ary)
+ a.assoc(s1.first).should.equal?(s1)
+
+ a.assoc(2).should == [2, 3]
+ s2.called.should.equal?(:to_ary)
+ end
+end
diff --git a/spec/ruby/core/array/at_spec.rb b/spec/ruby/core/array/at_spec.rb
new file mode 100644
index 0000000000..3c7c99fdff
--- /dev/null
+++ b/spec/ruby/core/array/at_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#at" do
+ it "returns the (n+1)'th element for the passed index n" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.at(0).should == 1
+ a.at(1).should == 2
+ a.at(5).should == 6
+ end
+
+ it "returns nil if the given index is greater than or equal to the array's length" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.at(6).should == nil
+ a.at(7).should == nil
+ end
+
+ it "returns the (-n)'th element from the last, for the given negative index n" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.at(-1).should == 6
+ a.at(-2).should == 5
+ a.at(-6).should == 1
+ end
+
+ it "returns nil if the given index is less than -len, where len is length of the array" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.at(-7).should == nil
+ a.at(-8).should == nil
+ end
+
+ it "does not extend the array unless the given index is out of range" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.length.should == 6
+ a.at(100)
+ a.length.should == 6
+ a.at(-100)
+ a.length.should == 6
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ a = ["a", "b", "c"]
+ a.at(0.5).should == "a"
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ a.at(obj).should == "c"
+ end
+
+ it "raises a TypeError when the passed argument can't be coerced to Integer" do
+ -> { [].at("cat") }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError when 2 or more arguments are passed" do
+ -> { [:a, :b].at(0,1) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/bsearch_index_spec.rb b/spec/ruby/core/array/bsearch_index_spec.rb
new file mode 100644
index 0000000000..e1d5eb66bb
--- /dev/null
+++ b/spec/ruby/core/array/bsearch_index_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#bsearch_index" do
+ context "when not passed a block" do
+ before :each do
+ @enum = [1, 2, 42, 100, 666].bsearch_index
+ end
+
+ it "returns an Enumerator" do
+ @enum.should.instance_of?(Enumerator)
+ end
+
+ it "returns an Enumerator with unknown size" do
+ @enum.size.should == nil
+ end
+
+ it "returns index of element when block condition is satisfied" do
+ @enum.each { |x| x >= 33 }.should == 2
+ end
+ end
+
+ it "raises a TypeError when block returns a String" do
+ -> { [1, 2, 3].bsearch_index { "not ok" } }.should.raise(TypeError)
+ end
+
+ it "returns nil when block is empty" do
+ [1, 2, 3].bsearch_index {}.should == nil
+ end
+
+ context "minimum mode" do
+ before :each do
+ @array = [0, 4, 7, 10, 12]
+ end
+
+ it "returns index of first element which satisfies the block" do
+ @array.bsearch_index { |x| x >= 4 }.should == 1
+ @array.bsearch_index { |x| x >= 6 }.should == 2
+ @array.bsearch_index { |x| x >= -1 }.should == 0
+ end
+
+ it "returns nil when block condition is never satisfied" do
+ @array.bsearch_index { false }.should == nil
+ @array.bsearch_index { |x| x >= 100 }.should == nil
+ end
+ end
+
+ context "find any mode" do
+ before :each do
+ @array = [0, 4, 7, 10, 12]
+ end
+
+ it "returns the index of any matched elements where element is between 4 <= x < 8" do
+ [1, 2].should.include?(@array.bsearch_index { |x| 1 - x / 4 })
+ end
+
+ it "returns the index of any matched elements where element is between 8 <= x < 10" do
+ @array.bsearch_index { |x| 4 - x / 2 }.should == nil
+ end
+
+ it "returns nil when block never returns 0" do
+ @array.bsearch_index { |x| 1 }.should == nil
+ @array.bsearch_index { |x| -1 }.should == nil
+ end
+
+ context "magnitude does not effect the result" do
+ it "returns the index of any matched elements where element is between 4n <= xn < 8n" do
+ [1, 2].should.include?(@array.bsearch_index { |x| (1 - x / 4) * (2**100) })
+ end
+
+ it "returns nil when block never returns 0" do
+ @array.bsearch_index { |x| 1 * (2**100) }.should == nil
+ @array.bsearch_index { |x| (-1) * (2**100) }.should == nil
+ end
+
+ it "handles values from Integer#coerce" do
+ [1, 2].should.include?(@array.bsearch_index { |x| (2**100).coerce((1 - x / 4) * (2**100)).first })
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/bsearch_spec.rb b/spec/ruby/core/array/bsearch_spec.rb
new file mode 100644
index 0000000000..12aec60654
--- /dev/null
+++ b/spec/ruby/core/array/bsearch_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#bsearch" do
+ it "returns an Enumerator when not passed a block" do
+ [1].bsearch.should.instance_of?(Enumerator)
+ end
+
+ it_behaves_like :enumeratorized_with_unknown_size, :bsearch, [1,2,3]
+
+ it "raises a TypeError if the block returns an Object" do
+ -> { [1].bsearch { Object.new } }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if the block returns a String" do
+ -> { [1].bsearch { "1" } }.should.raise(TypeError)
+ end
+
+ context "with a block returning true or false" do
+ it "returns nil if the block returns false for every element" do
+ [0, 1, 2, 3].bsearch { |x| x > 3 }.should == nil
+ end
+
+ it "returns nil if the block returns nil for every element" do
+ [0, 1, 2, 3].bsearch { |x| nil }.should == nil
+ end
+
+ it "returns element at zero if the block returns true for every element" do
+ [0, 1, 2, 3].bsearch { |x| x < 4 }.should == 0
+
+ end
+
+ it "returns the element at the smallest index for which block returns true" do
+ [0, 1, 3, 4].bsearch { |x| x >= 2 }.should == 3
+ [0, 1, 3, 4].bsearch { |x| x >= 1 }.should == 1
+ end
+ end
+
+ context "with a block returning negative, zero, positive numbers" do
+ it "returns nil if the block returns less than zero for every element" do
+ [0, 1, 2, 3].bsearch { |x| x <=> 5 }.should == nil
+ end
+
+ it "returns nil if the block returns greater than zero for every element" do
+ [0, 1, 2, 3].bsearch { |x| x <=> -1 }.should == nil
+
+ end
+
+ it "returns nil if the block never returns zero" do
+ [0, 1, 3, 4].bsearch { |x| x <=> 2 }.should == nil
+ end
+
+ it "accepts (+/-)Float::INFINITY from the block" do
+ [0, 1, 3, 4].bsearch { |x| Float::INFINITY }.should == nil
+ [0, 1, 3, 4].bsearch { |x| -Float::INFINITY }.should == nil
+ end
+
+ it "returns an element at an index for which block returns 0.0" do
+ result = [0, 1, 2, 3, 4].bsearch { |x| x < 2 ? 1.0 : x > 2 ? -1.0 : 0.0 }
+ result.should == 2
+ end
+
+ it "returns an element at an index for which block returns 0" do
+ result = [0, 1, 2, 3, 4].bsearch { |x| x < 1 ? 1 : x > 3 ? -1 : 0 }
+ [1, 2].should.include?(result)
+ end
+ end
+
+ context "with a block that calls break" do
+ it "returns nil if break is called without a value" do
+ ['a', 'b', 'c'].bsearch { |v| break }.should == nil
+ end
+
+ it "returns nil if break is called with a nil value" do
+ ['a', 'b', 'c'].bsearch { |v| break nil }.should == nil
+ end
+
+ it "returns object if break is called with an object" do
+ ['a', 'b', 'c'].bsearch { |v| break 1234 }.should == 1234
+ ['a', 'b', 'c'].bsearch { |v| break 'hi' }.should == 'hi'
+ ['a', 'b', 'c'].bsearch { |v| break [42] }.should == [42]
+ end
+ end
+end
diff --git a/spec/ruby/core/array/clear_spec.rb b/spec/ruby/core/array/clear_spec.rb
new file mode 100644
index 0000000000..15778f864f
--- /dev/null
+++ b/spec/ruby/core/array/clear_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#clear" do
+ it "removes all elements" do
+ a = [1, 2, 3, 4]
+ a.clear.should.equal?(a)
+ a.should == []
+ end
+
+ it "returns self" do
+ a = [1]
+ a.should.equal? a.clear
+ end
+
+ it "leaves the Array empty" do
+ a = [1]
+ a.clear
+ a.should.empty?
+ a.size.should == 0
+ end
+
+ it "does not accept any arguments" do
+ -> { [1].clear(true) }.should.raise(ArgumentError)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ a = [1]
+ a.freeze
+ -> { a.clear }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/clone_spec.rb b/spec/ruby/core/array/clone_spec.rb
new file mode 100644
index 0000000000..7ce9d40a81
--- /dev/null
+++ b/spec/ruby/core/array/clone_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/clone'
+
+describe "Array#clone" do
+ it_behaves_like :array_clone, :clone
+
+ it "copies frozen status from the original" do
+ a = [1, 2, 3, 4]
+ b = [1, 2, 3, 4]
+ a.freeze
+ aa = a.clone
+ bb = b.clone
+
+ aa.should.frozen?
+ bb.should_not.frozen?
+ end
+
+ it "copies singleton methods" do
+ a = [1, 2, 3, 4]
+ b = [1, 2, 3, 4]
+ def a.a_singleton_method; end
+ aa = a.clone
+ bb = b.clone
+
+ a.respond_to?(:a_singleton_method).should == true
+ b.respond_to?(:a_singleton_method).should == false
+ aa.respond_to?(:a_singleton_method).should == true
+ bb.respond_to?(:a_singleton_method).should == false
+ end
+end
diff --git a/spec/ruby/core/array/collect_spec.rb b/spec/ruby/core/array/collect_spec.rb
new file mode 100644
index 0000000000..43a539f805
--- /dev/null
+++ b/spec/ruby/core/array/collect_spec.rb
@@ -0,0 +1,143 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../enumerable/shared/enumeratorized'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#collect" do
+ it "returns a copy of array with each element replaced by the value returned by block" do
+ a = ['a', 'b', 'c', 'd']
+ b = a.collect { |i| i + '!' }
+ b.should == ["a!", "b!", "c!", "d!"]
+ b.should_not.equal? a
+ end
+
+ it "does not return subclass instance" do
+ ArraySpecs::MyArray[1, 2, 3].collect { |x| x + 1 }.should.instance_of?(Array)
+ end
+
+ it "does not change self" do
+ a = ['a', 'b', 'c', 'd']
+ a.collect { |i| i + '!' }
+ a.should == ['a', 'b', 'c', 'd']
+ end
+
+ it "returns the evaluated value of block if it broke in the block" do
+ a = ['a', 'b', 'c', 'd']
+ b = a.collect {|i|
+ if i == 'c'
+ break 0
+ else
+ i + '!'
+ end
+ }
+ b.should == 0
+ end
+
+ it "returns an Enumerator when no block given" do
+ a = [1, 2, 3]
+ a.collect.should.instance_of?(Enumerator)
+ end
+
+ it "raises an ArgumentError when no block and with arguments" do
+ a = [1, 2, 3]
+ -> {
+ a.collect(:foo)
+ }.should.raise(ArgumentError)
+ end
+
+ before :each do
+ @object = [1, 2, 3, 4]
+ end
+ it_behaves_like :enumeratorized_with_origin_size, :collect
+
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :collect
+end
+
+describe "Array#collect!" do
+ it "replaces each element with the value returned by block" do
+ a = [7, 9, 3, 5]
+ a.collect! { |i| i - 1 }.should.equal?(a)
+ a.should == [6, 8, 2, 4]
+ end
+
+ it "returns self" do
+ a = [1, 2, 3, 4, 5]
+ b = a.collect! {|i| i+1 }
+ a.should.equal? b
+ end
+
+ it "returns the evaluated value of block but its contents is partially modified, if it broke in the block" do
+ a = ['a', 'b', 'c', 'd']
+ b = a.collect! {|i|
+ if i == 'c'
+ break 0
+ else
+ i + '!'
+ end
+ }
+ b.should == 0
+ a.should == ['a!', 'b!', 'c', 'd']
+ end
+
+ it "returns an Enumerator when no block given, and the enumerator can modify the original array" do
+ a = [1, 2, 3]
+ enum = a.collect!
+ enum.should.instance_of?(Enumerator)
+ enum.each{|i| "#{i}!" }
+ a.should == ["1!", "2!", "3!"]
+ end
+
+ describe "when frozen" do
+ it "raises a FrozenError" do
+ -> { ArraySpecs.frozen_array.collect! {} }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError when empty" do
+ -> { ArraySpecs.empty_frozen_array.collect! {} }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError when calling #each on the returned Enumerator" do
+ enumerator = ArraySpecs.frozen_array.collect!
+ -> { enumerator.each {|x| x } }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError when calling #each on the returned Enumerator when empty" do
+ enumerator = ArraySpecs.empty_frozen_array.collect!
+ -> { enumerator.each {|x| x } }.should.raise(FrozenError)
+ end
+ end
+
+ it "does not truncate the array is the block raises an exception" do
+ a = [1, 2, 3]
+ begin
+ a.collect! { raise StandardError, 'Oops' }
+ rescue
+ end
+
+ a.should == [1, 2, 3]
+ end
+
+ it "only changes elements before error is raised, keeping the element which raised an error." do
+ a = [1, 2, 3, 4]
+ begin
+ a.collect! do |e|
+ case e
+ when 1 then -1
+ when 2 then -2
+ when 3 then raise StandardError, 'Oops'
+ else 0
+ end
+ end
+ rescue StandardError
+ end
+
+ a.should == [-1, -2, 3, 4]
+ end
+
+ before :each do
+ @object = [1, 2, 3, 4]
+ end
+ it_behaves_like :enumeratorized_with_origin_size, :collect!
+
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :collect!
+end
diff --git a/spec/ruby/core/array/combination_spec.rb b/spec/ruby/core/array/combination_spec.rb
new file mode 100644
index 0000000000..ac570687ca
--- /dev/null
+++ b/spec/ruby/core/array/combination_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+
+describe "Array#combination" do
+ before :each do
+ @array = [1, 2, 3, 4]
+ end
+
+ it "returns an enumerator when no block is provided" do
+ @array.combination(2).should.instance_of?(Enumerator)
+ end
+
+ it "returns self when a block is given" do
+ @array.combination(2){}.should.equal?(@array)
+ end
+
+ it "yields nothing for out of bounds length and return self" do
+ @array.combination(5).to_a.should == []
+ @array.combination(-1).to_a.should == []
+ end
+
+ it "yields the expected combinations" do
+ @array.combination(3).to_a.sort.should == [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
+ end
+
+ it "yields nothing if the argument is out of bounds" do
+ @array.combination(-1).to_a.should == []
+ @array.combination(5).to_a.should == []
+ end
+
+ it "yields a copy of self if the argument is the size of the receiver" do
+ r = @array.combination(4).to_a
+ r.should == [@array]
+ r[0].should_not.equal?(@array)
+ end
+
+ it "yields [] when length is 0" do
+ @array.combination(0).to_a.should == [[]] # one combination of length 0
+ [].combination(0).to_a.should == [[]] # one combination of length 0
+ end
+
+ it "yields a partition consisting of only singletons" do
+ @array.combination(1).to_a.sort.should == [[1],[2],[3],[4]]
+ end
+
+ it "generates from a defensive copy, ignoring mutations" do
+ accum = []
+ @array.combination(2) do |x|
+ accum << x
+ @array[0] = 1
+ end
+ accum.should == [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns 0 when the number of combinations is < 0" do
+ @array.combination(-1).size.should == 0
+ [].combination(-2).size.should == 0
+ end
+ it "returns the binomial coefficient between the array size the number of combinations" do
+ @array.combination(5).size.should == 0
+ @array.combination(4).size.should == 1
+ @array.combination(3).size.should == 4
+ @array.combination(2).size.should == 6
+ @array.combination(1).size.should == 4
+ @array.combination(0).size.should == 1
+ [].combination(0).size.should == 1
+ [].combination(1).size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/compact_spec.rb b/spec/ruby/core/array/compact_spec.rb
new file mode 100644
index 0000000000..dbcd16da35
--- /dev/null
+++ b/spec/ruby/core/array/compact_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#compact" do
+ it "returns a copy of array with all nil elements removed" do
+ a = [1, 2, 4]
+ a.compact.should == [1, 2, 4]
+ a = [1, nil, 2, 4]
+ a.compact.should == [1, 2, 4]
+ a = [1, 2, 4, nil]
+ a.compact.should == [1, 2, 4]
+ a = [nil, 1, 2, 4]
+ a.compact.should == [1, 2, 4]
+ end
+
+ it "does not return self" do
+ a = [1, 2, 3]
+ a.compact.should_not.equal?(a)
+ end
+
+ it "does not return subclass instance for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3, nil].compact.should.instance_of?(Array)
+ end
+end
+
+describe "Array#compact!" do
+ it "removes all nil elements" do
+ a = ['a', nil, 'b', false, 'c']
+ a.compact!.should.equal?(a)
+ a.should == ["a", "b", false, "c"]
+ a = [nil, 'a', 'b', false, 'c']
+ a.compact!.should.equal?(a)
+ a.should == ["a", "b", false, "c"]
+ a = ['a', 'b', false, 'c', nil]
+ a.compact!.should.equal?(a)
+ a.should == ["a", "b", false, "c"]
+ end
+
+ it "returns self if some nil elements are removed" do
+ a = ['a', nil, 'b', false, 'c']
+ a.compact!.should.equal? a
+ end
+
+ it "returns nil if there are no nil elements to remove" do
+ [1, 2, false, 3].compact!.should == nil
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.compact! }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/comparison_spec.rb b/spec/ruby/core/array/comparison_spec.rb
new file mode 100644
index 0000000000..14e8931e5a
--- /dev/null
+++ b/spec/ruby/core/array/comparison_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#<=>" do
+ it "calls <=> left to right and return first non-0 result" do
+ [-1, +1, nil, "foobar"].each do |result|
+ lhs = Array.new(3) { mock("#{result}") }
+ rhs = Array.new(3) { mock("#{result}") }
+
+ lhs[0].should_receive(:<=>).with(rhs[0]).and_return(0)
+ lhs[1].should_receive(:<=>).with(rhs[1]).and_return(result)
+ lhs[2].should_not_receive(:<=>)
+
+ (lhs <=> rhs).should == result
+ end
+ end
+
+ it "returns 0 if the arrays are equal" do
+ ([] <=> []).should == 0
+ ([1, 2, 3, 4, 5, 6] <=> [1, 2, 3, 4, 5.0, 6.0]).should == 0
+ end
+
+ it "returns -1 if the array is shorter than the other array" do
+ ([] <=> [1]).should == -1
+ ([1, 1] <=> [1, 1, 1]).should == -1
+ end
+
+ it "returns +1 if the array is longer than the other array" do
+ ([1] <=> []).should == +1
+ ([1, 1, 1] <=> [1, 1]).should == +1
+ end
+
+ it "returns -1 if the arrays have same length and a pair of corresponding elements returns -1 for <=>" do
+ eq_l = mock('an object equal to the other')
+ eq_r = mock('an object equal to the other')
+ eq_l.should_receive(:<=>).with(eq_r).any_number_of_times.and_return(0)
+
+ less = mock('less than the other')
+ greater = mock('greater then the other')
+ less.should_receive(:<=>).with(greater).any_number_of_times.and_return(-1)
+
+ rest = mock('an rest element of the arrays')
+ rest.should_receive(:<=>).with(rest).any_number_of_times.and_return(0)
+ lhs = [eq_l, eq_l, less, rest]
+ rhs = [eq_r, eq_r, greater, rest]
+
+ (lhs <=> rhs).should == -1
+ end
+
+ it "returns +1 if the arrays have same length and a pair of corresponding elements returns +1 for <=>" do
+ eq_l = mock('an object equal to the other')
+ eq_r = mock('an object equal to the other')
+ eq_l.should_receive(:<=>).with(eq_r).any_number_of_times.and_return(0)
+
+ greater = mock('greater then the other')
+ less = mock('less than the other')
+ greater.should_receive(:<=>).with(less).any_number_of_times.and_return(+1)
+
+ rest = mock('an rest element of the arrays')
+ rest.should_receive(:<=>).with(rest).any_number_of_times.and_return(0)
+ lhs = [eq_l, eq_l, greater, rest]
+ rhs = [eq_r, eq_r, less, rest]
+
+ (lhs <=> rhs).should == +1
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ (empty <=> empty).should == 0
+ (empty <=> []).should == 1
+ ([] <=> empty).should == -1
+
+ (ArraySpecs.recursive_array <=> []).should == 1
+ ([] <=> ArraySpecs.recursive_array).should == -1
+
+ (ArraySpecs.recursive_array <=> ArraySpecs.empty_recursive_array).should == nil
+
+ array = ArraySpecs.recursive_array
+ (array <=> array).should == 0
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('to_ary')
+ obj.stub!(:to_ary).and_return([1, 2, 3])
+ ([4, 5] <=> obj).should == ([4, 5] <=> obj.to_ary)
+ end
+
+ it "does not call #to_ary on Array subclasses" do
+ obj = ArraySpecs::ToAryArray[5, 6, 7]
+ obj.should_not_receive(:to_ary)
+ ([5, 6, 7] <=> obj).should == 0
+ end
+
+ it "returns nil when the argument is not array-like" do
+ ([] <=> false).should == nil
+ end
+end
diff --git a/spec/ruby/core/array/concat_spec.rb b/spec/ruby/core/array/concat_spec.rb
new file mode 100644
index 0000000000..1e8d20c36c
--- /dev/null
+++ b/spec/ruby/core/array/concat_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#concat" do
+ it "returns the array itself" do
+ ary = [1,2,3]
+ ary.concat([4,5,6]).equal?(ary).should == true
+ end
+
+ it "appends the elements in the other array" do
+ ary = [1, 2, 3]
+ ary.concat([9, 10, 11]).should.equal?(ary)
+ ary.should == [1, 2, 3, 9, 10, 11]
+ ary.concat([])
+ ary.should == [1, 2, 3, 9, 10, 11]
+ end
+
+ it "does not loop endlessly when argument is self" do
+ ary = ["x", "y"]
+ ary.concat(ary).should == ["x", "y", "x", "y"]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('to_ary')
+ obj.should_receive(:to_ary).and_return(["x", "y"])
+ [4, 5, 6].concat(obj).should == [4, 5, 6, "x", "y"]
+ end
+
+ it "does not call #to_ary on Array subclasses" do
+ obj = ArraySpecs::ToAryArray[5, 6, 7]
+ obj.should_not_receive(:to_ary)
+ [].concat(obj).should == [5, 6, 7]
+ end
+
+ it "raises a FrozenError when Array is frozen and modification occurs" do
+ -> { ArraySpecs.frozen_array.concat [1] }.should.raise(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError when Array is frozen and no modification occurs" do
+ -> { ArraySpecs.frozen_array.concat([]) }.should.raise(FrozenError)
+ end
+
+ it "appends elements to an Array with enough capacity that has been shifted" do
+ ary = [1, 2, 3, 4, 5]
+ 2.times { ary.shift }
+ 2.times { ary.pop }
+ ary.concat([5, 6]).should == [3, 5, 6]
+ end
+
+ it "appends elements to an Array without enough capacity that has been shifted" do
+ ary = [1, 2, 3, 4]
+ 3.times { ary.shift }
+ ary.concat([5, 6]).should == [4, 5, 6]
+ end
+
+ it "takes multiple arguments" do
+ ary = [1, 2]
+ ary.concat [3, 4]
+ ary.should == [1, 2, 3, 4]
+ end
+
+ it "concatenates the initial value when given arguments contain 2 self" do
+ ary = [1, 2]
+ ary.concat ary, ary
+ ary.should == [1, 2, 1, 2, 1, 2]
+ end
+
+ it "returns self when given no arguments" do
+ ary = [1, 2]
+ ary.concat.should.equal?(ary)
+ ary.should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/array/constructor_spec.rb b/spec/ruby/core/array/constructor_spec.rb
new file mode 100644
index 0000000000..c4398c535d
--- /dev/null
+++ b/spec/ruby/core/array/constructor_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array.[]" do
+ it "returns a new array populated with the given elements" do
+ obj = Object.new
+ Array.[](5, true, nil, 'a', "Ruby", obj).should == [5, true, nil, "a", "Ruby", obj]
+
+ a = ArraySpecs::MyArray.[](5, true, nil, 'a', "Ruby", obj)
+ a.should.instance_of?(ArraySpecs::MyArray)
+ a.inspect.should == [5, true, nil, "a", "Ruby", obj].inspect
+ end
+end
+
+describe "Array[]" do
+ it "is a synonym for .[]" do
+ obj = Object.new
+ Array[5, true, nil, 'a', "Ruby", obj].should == Array.[](5, true, nil, "a", "Ruby", obj)
+
+ a = ArraySpecs::MyArray[5, true, nil, 'a', "Ruby", obj]
+ a.should.instance_of?(ArraySpecs::MyArray)
+ a.inspect.should == [5, true, nil, "a", "Ruby", obj].inspect
+ end
+end
diff --git a/spec/ruby/core/array/count_spec.rb b/spec/ruby/core/array/count_spec.rb
new file mode 100644
index 0000000000..e778233c16
--- /dev/null
+++ b/spec/ruby/core/array/count_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#count" do
+ it "returns the number of elements" do
+ [:a, :b, :c].count.should == 3
+ end
+
+ it "returns the number of elements that equal the argument" do
+ [:a, :b, :b, :c].count(:b).should == 2
+ end
+
+ it "returns the number of element for which the block evaluates to true" do
+ [:a, :b, :c].count { |s| s != :b }.should == 2
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ [:a, :b, :b, :c].count(:b) { |e| e.size > 10 }.should == 2
+ }.should complain(/given block not used/)
+ end
+
+ context "when a block argument given" do
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :count
+ end
+end
diff --git a/spec/ruby/core/array/cycle_spec.rb b/spec/ruby/core/array/cycle_spec.rb
new file mode 100644
index 0000000000..29284257e9
--- /dev/null
+++ b/spec/ruby/core/array/cycle_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#cycle" do
+ before :each do
+ ScratchPad.record []
+
+ @array = [1, 2, 3]
+ @prc = -> x { ScratchPad << x }
+ end
+
+ it "does not yield and returns nil when the array is empty and passed value is an integer" do
+ [].cycle(6, &@prc).should == nil
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not yield and returns nil when the array is empty and passed value is nil" do
+ [].cycle(nil, &@prc).should == nil
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not yield and returns nil when passed 0" do
+ @array.cycle(0, &@prc).should == nil
+ ScratchPad.recorded.should == []
+ end
+
+ it "iterates the array 'count' times yielding each item to the block" do
+ @array.cycle(2, &@prc)
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3]
+ end
+
+ it "iterates indefinitely when not passed a count" do
+ @array.cycle do |x|
+ ScratchPad << x
+ break if ScratchPad.recorded.size > 7
+ end
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3, 1, 2]
+ end
+
+ it "iterates indefinitely when passed nil" do
+ @array.cycle(nil) do |x|
+ ScratchPad << x
+ break if ScratchPad.recorded.size > 7
+ end
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3, 1, 2]
+ end
+
+ it "does not rescue StopIteration when not passed a count" do
+ -> do
+ @array.cycle { raise StopIteration }
+ end.should.raise(StopIteration)
+ end
+
+ it "does not rescue StopIteration when passed a count" do
+ -> do
+ @array.cycle(3) { raise StopIteration }
+ end.should.raise(StopIteration)
+ end
+
+ it "iterates the array Integer(count) times when passed a Float count" do
+ @array.cycle(2.7, &@prc)
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3]
+ end
+
+ it "calls #to_int to convert count to an Integer" do
+ count = mock("cycle count 2")
+ count.should_receive(:to_int).and_return(2)
+
+ @array.cycle(count, &@prc)
+ ScratchPad.recorded.should == [1, 2, 3, 1, 2, 3]
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ count = mock("cycle count 2")
+ count.should_receive(:to_int).and_return("2")
+
+ -> { @array.cycle(count, &@prc) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed a String" do
+ -> { @array.cycle("4") { } }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed an Object" do
+ -> { @array.cycle(mock("cycle count")) { } }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed true" do
+ -> { @array.cycle(true) { } }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed false" do
+ -> { @array.cycle(false) { } }.should.raise(TypeError)
+ end
+
+ before :all do
+ @object = [1, 2, 3, 4]
+ @empty_object = []
+ end
+ it_should_behave_like :enumeratorized_with_cycle_size
+end
diff --git a/spec/ruby/core/array/deconstruct_spec.rb b/spec/ruby/core/array/deconstruct_spec.rb
new file mode 100644
index 0000000000..11bb8e72c4
--- /dev/null
+++ b/spec/ruby/core/array/deconstruct_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Array#deconstruct" do
+ it "returns self" do
+ array = [1]
+
+ array.deconstruct.should.equal? array
+ end
+end
diff --git a/spec/ruby/core/array/delete_at_spec.rb b/spec/ruby/core/array/delete_at_spec.rb
new file mode 100644
index 0000000000..1e298b6730
--- /dev/null
+++ b/spec/ruby/core/array/delete_at_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#delete_at" do
+ it "removes the element at the specified index" do
+ a = [1, 2, 3, 4]
+ a.delete_at(2)
+ a.should == [1, 2, 4]
+ a.delete_at(-1)
+ a.should == [1, 2]
+ end
+
+ it "returns the removed element at the specified index" do
+ a = [1, 2, 3, 4]
+ a.delete_at(2).should == 3
+ a.delete_at(-1).should == 4
+ end
+
+ it "returns nil and makes no modification if the index is out of range" do
+ a = [1, 2]
+ a.delete_at(3).should == nil
+ a.should == [1, 2]
+ a.delete_at(-3).should == nil
+ a.should == [1, 2]
+ 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(-1)
+ [1, 2].delete_at(obj).should == 2
+ end
+
+ it "accepts negative indices" do
+ a = [1, 2]
+ a.delete_at(-2).should == 1
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { [1,2,3].freeze.delete_at(0) }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/delete_if_spec.rb b/spec/ruby/core/array/delete_if_spec.rb
new file mode 100644
index 0000000000..701a612395
--- /dev/null
+++ b/spec/ruby/core/array/delete_if_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative 'shared/delete_if'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#delete_if" do
+ before do
+ @a = [ "a", "b", "c" ]
+ end
+
+ it "removes each element for which block returns true" do
+ @a = [ "a", "b", "c" ]
+ @a.delete_if { |x| x >= "b" }
+ @a.should == ["a"]
+ end
+
+ it "returns self" do
+ @a.delete_if{ true }.equal?(@a).should == true
+ end
+
+ it_behaves_like :enumeratorize, :delete_if
+
+ it "returns self when called on an Array emptied with #shift" do
+ array = [1]
+ array.shift
+ array.delete_if { |x| true }.should.equal?(array)
+ end
+
+ it "returns an Enumerator if no block given, and the enumerator can modify the original array" do
+ enum = @a.delete_if
+ enum.should.instance_of?(Enumerator)
+ @a.should_not.empty?
+ enum.each { true }
+ @a.should.empty?
+ end
+
+ it "returns an Enumerator if no block given, and the array is frozen" do
+ @a.freeze.delete_if.should.instance_of?(Enumerator)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.delete_if {} }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.delete_if {} }.should.raise(FrozenError)
+ end
+
+ it "does not truncate the array is the block raises an exception" do
+ a = [1, 2, 3]
+ begin
+ a.delete_if { raise StandardError, 'Oops' }
+ rescue
+ end
+
+ a.should == [1, 2, 3]
+ end
+
+ it "only removes elements for which the block returns true, keeping the element which raised an error." do
+ a = [1, 2, 3, 4]
+ begin
+ a.delete_if do |e|
+ case e
+ when 2 then true
+ when 3 then raise StandardError, 'Oops'
+ else false
+ end
+ end
+ rescue StandardError
+ end
+
+ a.should == [1, 3, 4]
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :delete_if, [1,2,3]
+ it_behaves_like :delete_if, :delete_if
+
+ @value_to_return = -> _ { false }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :delete_if
+end
diff --git a/spec/ruby/core/array/delete_spec.rb b/spec/ruby/core/array/delete_spec.rb
new file mode 100644
index 0000000000..0d80b2839d
--- /dev/null
+++ b/spec/ruby/core/array/delete_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#delete" do
+ it "removes elements that are #== to object" do
+ x = mock('delete')
+ def x.==(other) 3 == other end
+
+ a = [1, 2, 3, x, 4, 3, 5, x]
+ a.delete mock('not contained')
+ a.should == [1, 2, 3, x, 4, 3, 5, x]
+
+ a.delete 3
+ a.should == [1, 2, 4, 5]
+ end
+
+ it "calculates equality correctly for reference values" do
+ a = ["foo", "bar", "foo", "quux", "foo"]
+ a.delete "foo"
+ a.should == ["bar","quux"]
+ end
+
+ it "returns object or nil if no elements match object" do
+ [1, 2, 4, 5].delete(1).should == 1
+ [1, 2, 4, 5].delete(3).should == nil
+ end
+
+ it "may be given a block that is executed if no element matches object" do
+ [1].delete(1) {:not_found}.should == 1
+ [].delete('a') {:not_found}.should == :not_found
+ end
+
+ it "returns nil if the array is empty due to a shift" do
+ a = [1]
+ a.shift
+ a.delete(nil).should == nil
+ end
+
+ it "returns nil on a frozen array if a modification does not take place" do
+ [1, 2, 3].freeze.delete(0).should == nil
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { [1, 2, 3].freeze.delete(1) }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/difference_spec.rb b/spec/ruby/core/array/difference_spec.rb
new file mode 100644
index 0000000000..63e32feca0
--- /dev/null
+++ b/spec/ruby/core/array/difference_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/difference'
+
+describe "Array#difference" do
+ it_behaves_like :array_binary_difference, :difference
+
+ it "returns a copy when called without any parameter" do
+ x = [1, 2, 3, 2]
+ x.difference.should == x
+ x.difference.should_not.equal? x
+ end
+
+ it "does not return subclass instances for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].difference.should.instance_of?(Array)
+ end
+
+ it "accepts multiple arguments" do
+ x = [1, 2, 3, 1]
+ x.difference([], [0, 1], [3, 4], [3]).should == [2]
+ end
+end
diff --git a/spec/ruby/core/array/dig_spec.rb b/spec/ruby/core/array/dig_spec.rb
new file mode 100644
index 0000000000..4166ff9f1f
--- /dev/null
+++ b/spec/ruby/core/array/dig_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+
+describe "Array#dig" do
+
+ it "returns #at with one arg" do
+ ['a'].dig(0).should == 'a'
+ ['a'].dig(1).should == nil
+ end
+
+ it "recurses array elements" do
+ a = [ [ 1, [2, '3'] ] ]
+ a.dig(0, 0).should == 1
+ a.dig(0, 1, 1).should == '3'
+ a.dig(0, -1, 0).should == 2
+ end
+
+ it "returns the nested value specified if the sequence includes a key" do
+ a = [42, { foo: :bar }]
+ a.dig(1, :foo).should == :bar
+ end
+
+ it "raises a TypeError for a non-numeric index" do
+ -> {
+ ['a'].dig(:first)
+ }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if any intermediate step does not respond to #dig" do
+ a = [1, 2]
+ -> {
+ a.dig(0, 1)
+ }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError if no arguments provided" do
+ -> {
+ [10].dig()
+ }.should.raise(ArgumentError)
+ end
+
+ it "returns nil if any intermediate step is nil" do
+ a = [[1, [2, 3]]]
+ a.dig(1, 2, 3).should == nil
+ end
+
+ it "calls #dig on the result of #at with the remaining arguments" do
+ h = [[nil, [nil, nil, 42]]]
+ h[0].should_receive(:dig).with(1, 2).and_return(42)
+ h.dig(0, 1, 2).should == 42
+ end
+
+end
diff --git a/spec/ruby/core/array/drop_spec.rb b/spec/ruby/core/array/drop_spec.rb
new file mode 100644
index 0000000000..c0e1c9edce
--- /dev/null
+++ b/spec/ruby/core/array/drop_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#drop" do
+ it "removes the specified number of elements from the start of the array" do
+ [1, 2, 3, 4, 5].drop(2).should == [3, 4, 5]
+ end
+
+ it "raises an ArgumentError if the number of elements specified is negative" do
+ -> { [1, 2].drop(-3) }.should.raise(ArgumentError)
+ end
+
+ it "returns an empty Array if all elements are dropped" do
+ [1, 2].drop(2).should == []
+ end
+
+ it "returns an empty Array when called on an empty Array" do
+ [].drop(0).should == []
+ end
+
+ it "does not remove any elements when passed zero" do
+ [1, 2].drop(0).should == [1, 2]
+ end
+
+ it "returns an empty Array if more elements than exist are dropped" do
+ [1, 2].drop(3).should == []
+ end
+
+ it 'acts correctly after a shift' do
+ ary = [nil, 1, 2]
+ ary.shift
+ ary.drop(1).should == [2]
+ 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)
+
+ [1, 2, 3].drop(obj).should == [3]
+ end
+
+ it "raises a TypeError when the passed argument can't be coerced to Integer" do
+ -> { [1, 2].drop("cat") }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when the passed argument isn't an integer and #to_int returns non-Integer" do
+ obj = mock("to_int")
+ obj.should_receive(:to_int).and_return("cat")
+
+ -> { [1, 2].drop(obj) }.should.raise(TypeError)
+ end
+
+ it 'returns a Array instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].drop(1).should.instance_of?(Array)
+ end
+end
diff --git a/spec/ruby/core/array/drop_while_spec.rb b/spec/ruby/core/array/drop_while_spec.rb
new file mode 100644
index 0000000000..4fead3ff06
--- /dev/null
+++ b/spec/ruby/core/array/drop_while_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#drop_while" do
+ @value_to_return = -> _ { true }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :drop_while
+
+ it "removes elements from the start of the array while the block evaluates to true" do
+ [1, 2, 3, 4].drop_while { |n| n < 4 }.should == [4]
+ end
+
+ it "removes elements from the start of the array until the block returns nil" do
+ [1, 2, 3, nil, 5].drop_while { |n| n }.should == [nil, 5]
+ end
+
+ it "removes elements from the start of the array until the block returns false" do
+ [1, 2, 3, false, 5].drop_while { |n| n }.should == [false, 5]
+ end
+
+ it 'returns a Array instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].drop_while { |n| n < 4 }.should.instance_of?(Array)
+ end
+end
diff --git a/spec/ruby/core/array/dup_spec.rb b/spec/ruby/core/array/dup_spec.rb
new file mode 100644
index 0000000000..f14aeca3b5
--- /dev/null
+++ b/spec/ruby/core/array/dup_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/clone'
+
+describe "Array#dup" do
+ it_behaves_like :array_clone, :dup # FIX: no, clone and dup are not alike
+
+ it "does not copy frozen status from the original" do
+ a = [1, 2, 3, 4]
+ b = [1, 2, 3, 4]
+ a.freeze
+ aa = a.dup
+ bb = b.dup
+
+ aa.frozen?.should == false
+ bb.frozen?.should == false
+ end
+
+ it "does not copy singleton methods" do
+ a = [1, 2, 3, 4]
+ b = [1, 2, 3, 4]
+ def a.a_singleton_method; end
+ aa = a.dup
+ bb = b.dup
+
+ a.respond_to?(:a_singleton_method).should == true
+ b.respond_to?(:a_singleton_method).should == false
+ aa.respond_to?(:a_singleton_method).should == false
+ bb.respond_to?(:a_singleton_method).should == false
+ end
+end
diff --git a/spec/ruby/core/array/each_index_spec.rb b/spec/ruby/core/array/each_index_spec.rb
new file mode 100644
index 0000000000..b238a89d8a
--- /dev/null
+++ b/spec/ruby/core/array/each_index_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative '../enumerable/shared/enumeratorized'
+
+# Modifying a collection while the contents are being iterated
+# gives undefined behavior. See
+# https://blade.ruby-lang.org/ruby-core/23633
+
+describe "Array#each_index" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "passes the index of each element to the block" do
+ a = ['a', 'b', 'c', 'd']
+ a.each_index { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [0, 1, 2, 3]
+ end
+
+ it "returns self" do
+ a = [:a, :b, :c]
+ a.each_index { |i| }.should.equal?(a)
+ end
+
+ it "is not confused by removing elements from the front" do
+ a = [1, 2, 3]
+
+ a.shift
+ ScratchPad.record []
+ a.each_index { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [0, 1]
+
+ a.shift
+ ScratchPad.record []
+ a.each_index { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [0]
+ end
+
+ it_behaves_like :enumeratorize, :each_index
+ it_behaves_like :enumeratorized_with_origin_size, :each_index, [1,2,3]
+end
+
+describe "Array#each_index" do
+ it "tolerates increasing an array size during iteration" do
+ array = [:a, :b, :c]
+ ScratchPad.record []
+ i = 0
+
+ array.each_index do |index|
+ ScratchPad << index
+ array << i if i < 100
+ i += 1
+ end
+
+ ScratchPad.recorded.should == (0..102).to_a # element indices
+ end
+end
diff --git a/spec/ruby/core/array/each_spec.rb b/spec/ruby/core/array/each_spec.rb
new file mode 100644
index 0000000000..73a4c36b17
--- /dev/null
+++ b/spec/ruby/core/array/each_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+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]; i += 1; end`
+
+describe "Array#each" do
+ it "yields each element to the block" do
+ a = []
+ x = [1, 2, 3]
+ x.each { |item| a << item }.should.equal?(x)
+ a.should == [1, 2, 3]
+ end
+
+ it "yields each element to the block even if the array is changed during iteration" do
+ a = [1, 2, 3, 4, 5]
+ iterated = []
+ a.each { |x| iterated << x; a << x+5 if x.even? }
+ iterated.should == [1, 2, 3, 4, 5, 7, 9]
+ end
+
+ it "yields only elements that are still in the array" do
+ a = [0, 1, 2, 3, 4]
+ iterated = []
+ a.each { |x| iterated << x; a.pop if x.even? }
+ iterated.should == [0, 1, 2]
+ end
+
+ it "yields elements based on an internal index" do
+ a = [0, 1, 2, 3, 4]
+ iterated = []
+ a.each { |x| iterated << x; a.shift if x.even? }
+ iterated.should == [0, 2, 4]
+ end
+
+ it "yields the same element multiple times if inserting while iterating" do
+ a = [1, 2]
+ iterated = []
+ a.each { |x| iterated << x; a.unshift(0) if a.size == 2 }
+ iterated.should == [1, 1, 2]
+ end
+
+ it "yields each element to a block that takes multiple arguments" do
+ a = [[1, 2], :a, [3, 4]]
+ b = []
+
+ a.each { |x, y| b << x }
+ b.should == [1, :a, 3]
+
+ b = []
+ a.each { |x, y| b << y }
+ b.should == [2, nil, 4]
+ end
+
+ it "yields elements added to the end of the array by the block" do
+ a = [2]
+ iterated = []
+ a.each { |x| iterated << x; x.times { a << 0 } }
+
+ iterated.should == [2, 0, 0]
+ end
+
+ it "does not yield elements deleted from the end of the array" do
+ a = [2, 3, 1]
+ iterated = []
+ a.each { |x| iterated << x; a.delete_at(2) if x == 3 }
+
+ iterated.should == [2, 3]
+ end
+
+ it_behaves_like :enumeratorize, :each
+ it_behaves_like :enumeratorized_with_origin_size, :each, [1,2,3]
+end
+
+describe "Array#each" do
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :each
+end
diff --git a/spec/ruby/core/array/element_reference_spec.rb b/spec/ruby/core/array/element_reference_spec.rb
new file mode 100644
index 0000000000..d5f4b54961
--- /dev/null
+++ b/spec/ruby/core/array/element_reference_spec.rb
@@ -0,0 +1,903 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#[]" do
+ it "returns the element at index with [index]" do
+ [ "a", "b", "c", "d", "e" ][1].should == "b"
+
+ a = [1, 2, 3, 4]
+
+ a[0].should == 1
+ a[1].should == 2
+ a[2].should == 3
+ a[3].should == 4
+ a[4].should == nil
+ a[10].should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns the element at index from the end of the array with [-index]" do
+ [ "a", "b", "c", "d", "e" ][-2].should == "d"
+
+ a = [1, 2, 3, 4]
+
+ a[-1].should == 4
+ a[-2].should == 3
+ a[-3].should == 2
+ a[-4].should == 1
+ a[-5].should == nil
+ a[-10].should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns count elements starting from index with [index, count]" do
+ [ "a", "b", "c", "d", "e" ][2, 3].should == ["c", "d", "e"]
+
+ a = [1, 2, 3, 4]
+
+ a[0, 0].should == []
+ a[0, 1].should == [1]
+ a[0, 2].should == [1, 2]
+ a[0, 4].should == [1, 2, 3, 4]
+ a[0, 6].should == [1, 2, 3, 4]
+ a[0, -1].should == nil
+ a[0, -2].should == nil
+ a[0, -4].should == nil
+
+ a[2, 0].should == []
+ a[2, 1].should == [3]
+ a[2, 2].should == [3, 4]
+ a[2, 4].should == [3, 4]
+ a[2, -1].should == nil
+
+ a[4, 0].should == []
+ a[4, 2].should == []
+ a[4, -1].should == nil
+
+ a[5, 0].should == nil
+ a[5, 2].should == nil
+ a[5, -1].should == nil
+
+ a[6, 0].should == nil
+ a[6, 2].should == nil
+ a[6, -1].should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns count elements starting at index from the end of array with [-index, count]" do
+ [ "a", "b", "c", "d", "e" ][-2, 2].should == ["d", "e"]
+
+ a = [1, 2, 3, 4]
+
+ a[-1, 0].should == []
+ a[-1, 1].should == [4]
+ a[-1, 2].should == [4]
+ a[-1, -1].should == nil
+
+ a[-2, 0].should == []
+ a[-2, 1].should == [3]
+ a[-2, 2].should == [3, 4]
+ a[-2, 4].should == [3, 4]
+ a[-2, -1].should == nil
+
+ a[-4, 0].should == []
+ a[-4, 1].should == [1]
+ a[-4, 2].should == [1, 2]
+ a[-4, 4].should == [1, 2, 3, 4]
+ a[-4, 6].should == [1, 2, 3, 4]
+ a[-4, -1].should == nil
+
+ a[-5, 0].should == nil
+ a[-5, 1].should == nil
+ a[-5, 10].should == nil
+ a[-5, -1].should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns the first count elements with [0, count]" do
+ [ "a", "b", "c", "d", "e" ][0, 3].should == ["a", "b", "c"]
+ end
+
+ it "returns the subarray which is independent to self with [index,count]" do
+ a = [1, 2, 3]
+ sub = a[1, 2]
+ sub.replace([:a, :b])
+ a.should == [1, 2, 3]
+ end
+
+ it "tries to convert the passed argument to an Integer using #to_int" do
+ obj = mock('to_int')
+ obj.stub!(:to_int).and_return(2)
+
+ a = [1, 2, 3, 4]
+ a[obj].should == 3
+ a[obj, 1].should == [3]
+ a[obj, obj].should == [3, 4]
+ a[0, obj].should == [1, 2]
+ end
+
+ it "raises TypeError if to_int returns non-integer" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ a = [1, 2, 3, 4, 5]
+
+ def from.to_int() 'cat' end
+ def to.to_int() -2 end
+
+ -> { a[from..to] }.should.raise(TypeError)
+
+ def from.to_int() 1 end
+ def to.to_int() 'cat' end
+
+ -> { a[from..to] }.should.raise(TypeError)
+ end
+
+ it "returns the elements specified by Range indexes with [m..n]" do
+ [ "a", "b", "c", "d", "e" ][1..3].should == ["b", "c", "d"]
+ [ "a", "b", "c", "d", "e" ][4..-1].should == ['e']
+ [ "a", "b", "c", "d", "e" ][3..3].should == ['d']
+ [ "a", "b", "c", "d", "e" ][3..-2].should == ['d']
+ ['a'][0..-1].should == ['a']
+
+ a = [1, 2, 3, 4]
+
+ a[0..-10].should == []
+ a[0..0].should == [1]
+ a[0..1].should == [1, 2]
+ a[0..2].should == [1, 2, 3]
+ a[0..3].should == [1, 2, 3, 4]
+ a[0..4].should == [1, 2, 3, 4]
+ a[0..10].should == [1, 2, 3, 4]
+
+ a[2..-10].should == []
+ a[2..0].should == []
+ a[2..2].should == [3]
+ a[2..3].should == [3, 4]
+ a[2..4].should == [3, 4]
+
+ a[3..0].should == []
+ a[3..3].should == [4]
+ a[3..4].should == [4]
+
+ a[4..0].should == []
+ a[4..4].should == []
+ a[4..5].should == []
+
+ a[5..0].should == nil
+ a[5..5].should == nil
+ a[5..6].should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns elements specified by Range indexes except the element at index n with [m...n]" do
+ [ "a", "b", "c", "d", "e" ][1...3].should == ["b", "c"]
+
+ a = [1, 2, 3, 4]
+
+ a[0...-10].should == []
+ a[0...0].should == []
+ a[0...1].should == [1]
+ a[0...2].should == [1, 2]
+ a[0...3].should == [1, 2, 3]
+ a[0...4].should == [1, 2, 3, 4]
+ a[0...10].should == [1, 2, 3, 4]
+
+ a[2...-10].should == []
+ a[2...0].should == []
+ a[2...2].should == []
+ a[2...3].should == [3]
+ a[2...4].should == [3, 4]
+
+ a[3...0].should == []
+ a[3...3].should == []
+ a[3...4].should == [4]
+
+ a[4...0].should == []
+ a[4...4].should == []
+ a[4...5].should == []
+
+ a[5...0].should == nil
+ a[5...5].should == nil
+ a[5...6].should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns elements that exist if range start is in the array but range end is not with [m..n]" do
+ [ "a", "b", "c", "d", "e" ][4..7].should == ["e"]
+ end
+
+ it "accepts Range instances having a negative m and both signs for n with [m..n] and [m...n]" do
+ a = [1, 2, 3, 4]
+
+ a[-1..-1].should == [4]
+ a[-1...-1].should == []
+ a[-1..3].should == [4]
+ a[-1...3].should == []
+ a[-1..4].should == [4]
+ a[-1...4].should == [4]
+ a[-1..10].should == [4]
+ a[-1...10].should == [4]
+ a[-1..0].should == []
+ a[-1..-4].should == []
+ a[-1...-4].should == []
+ a[-1..-6].should == []
+ a[-1...-6].should == []
+
+ a[-2..-2].should == [3]
+ a[-2...-2].should == []
+ a[-2..-1].should == [3, 4]
+ a[-2...-1].should == [3]
+ a[-2..10].should == [3, 4]
+ a[-2...10].should == [3, 4]
+
+ a[-4..-4].should == [1]
+ a[-4..-2].should == [1, 2, 3]
+ a[-4...-2].should == [1, 2]
+ a[-4..-1].should == [1, 2, 3, 4]
+ a[-4...-1].should == [1, 2, 3]
+ a[-4..3].should == [1, 2, 3, 4]
+ a[-4...3].should == [1, 2, 3]
+ a[-4..4].should == [1, 2, 3, 4]
+ a[-4...4].should == [1, 2, 3, 4]
+ a[-4...4].should == [1, 2, 3, 4]
+ a[-4..0].should == [1]
+ a[-4...0].should == []
+ a[-4..1].should == [1, 2]
+ a[-4...1].should == [1]
+
+ a[-5..-5].should == nil
+ a[-5...-5].should == nil
+ a[-5..-4].should == nil
+ a[-5..-1].should == nil
+ a[-5..10].should == nil
+
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "returns the subarray which is independent to self with [m..n]" do
+ a = [1, 2, 3]
+ sub = a[1..2]
+ sub.replace([:a, :b])
+ a.should == [1, 2, 3]
+ end
+
+ it "tries to convert Range elements to Integers using #to_int with [m..n] and [m...n]" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ a = [1, 2, 3, 4]
+
+ a[from..to].should == [2, 3]
+ a[from...to].should == [2]
+ a[1..0].should == []
+ a[1...0].should == []
+
+ -> { a["a" .. "b"] }.should.raise(TypeError)
+ -> { a["a" ... "b"] }.should.raise(TypeError)
+ -> { a[from .. "b"] }.should.raise(TypeError)
+ -> { a[from ... "b"] }.should.raise(TypeError)
+ end
+
+ it "returns the same elements as [m..n] and [m...n] with Range subclasses" do
+ a = [1, 2, 3, 4]
+ range_incl = ArraySpecs::MyRange.new(1, 2)
+ range_excl = ArraySpecs::MyRange.new(-3, -1, true)
+
+ a[range_incl].should == [2, 3]
+ a[range_excl].should == [2, 3]
+ end
+
+ it "returns nil for a requested index not in the array with [index]" do
+ [ "a", "b", "c", "d", "e" ][5].should == nil
+ end
+
+ it "returns [] if the index is valid but length is zero with [index, length]" do
+ [ "a", "b", "c", "d", "e" ][0, 0].should == []
+ [ "a", "b", "c", "d", "e" ][2, 0].should == []
+ end
+
+ it "returns nil if length is zero but index is invalid with [index, length]" do
+ [ "a", "b", "c", "d", "e" ][100, 0].should == nil
+ [ "a", "b", "c", "d", "e" ][-50, 0].should == nil
+ end
+
+ # This is by design. It is in the official documentation.
+ it "returns [] if index == array.size with [index, length]" do
+ %w|a b c d e|[5, 2].should == []
+ end
+
+ it "returns nil if index > array.size with [index, length]" do
+ %w|a b c d e|[6, 2].should == nil
+ end
+
+ it "returns nil if length is negative with [index, length]" do
+ %w|a b c d e|[3, -1].should == nil
+ %w|a b c d e|[2, -2].should == nil
+ %w|a b c d e|[1, -100].should == nil
+ end
+
+ it "returns nil if no requested index is in the array with [m..n]" do
+ [ "a", "b", "c", "d", "e" ][6..10].should == nil
+ end
+
+ it "returns nil if range start is not in the array with [m..n]" do
+ [ "a", "b", "c", "d", "e" ][-10..2].should == nil
+ [ "a", "b", "c", "d", "e" ][10..12].should == nil
+ end
+
+ it "returns an empty array when m == n with [m...n]" do
+ [1, 2, 3, 4, 5][1...1].should == []
+ end
+
+ it "returns an empty array with [0...0]" do
+ [1, 2, 3, 4, 5][0...0].should == []
+ end
+
+ it "returns a subarray where m, n negatives and m < n with [m..n]" do
+ [ "a", "b", "c", "d", "e" ][-3..-2].should == ["c", "d"]
+ end
+
+ it "returns an array containing the first element with [0..0]" do
+ [1, 2, 3, 4, 5][0..0].should == [1]
+ end
+
+ it "returns the entire array with [0..-1]" do
+ [1, 2, 3, 4, 5][0..-1].should == [1, 2, 3, 4, 5]
+ end
+
+ it "returns all but the last element with [0...-1]" do
+ [1, 2, 3, 4, 5][0...-1].should == [1, 2, 3, 4]
+ end
+
+ it "returns [3] for [2..-1] out of [1, 2, 3]" do
+ [1,2,3][2..-1].should == [3]
+ end
+
+ it "returns an empty array when m > n and m, n are positive with [m..n]" do
+ [1, 2, 3, 4, 5][3..2].should == []
+ end
+
+ it "returns an empty array when m > n and m, n are negative with [m..n]" do
+ [1, 2, 3, 4, 5][-2..-3].should == []
+ end
+
+ it "does not expand array when the indices are outside of the array bounds" do
+ a = [1, 2]
+ a[4].should == nil
+ a.should == [1, 2]
+ a[4, 0].should == nil
+ a.should == [1, 2]
+ a[6, 1].should == nil
+ a.should == [1, 2]
+ a[8...8].should == nil
+ a.should == [1, 2]
+ a[10..10].should == nil
+ a.should == [1, 2]
+ end
+
+ describe "with a subclass of Array" do
+ before :each do
+ ScratchPad.clear
+
+ @array = ArraySpecs::MyArray[1, 2, 3, 4, 5]
+ end
+
+ it "returns a Array instance with [n, m]" do
+ @array[0, 2].should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [-n, m]" do
+ @array[-3, 2].should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [n..m]" do
+ @array[1..3].should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [n...m]" do
+ @array[1...3].should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [-n..-m]" do
+ @array[-3..-1].should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [-n...-m]" do
+ @array[-3...-1].should.instance_of?(Array)
+ end
+
+ it "returns an empty array when m == n with [m...n]" do
+ @array[1...1].should == []
+ ScratchPad.recorded.should == nil
+ end
+
+ it "returns an empty array with [0...0]" do
+ @array[0...0].should == []
+ ScratchPad.recorded.should == nil
+ end
+
+ it "returns an empty array when m > n and m, n are positive with [m..n]" do
+ @array[3..2].should == []
+ ScratchPad.recorded.should == nil
+ end
+
+ it "returns an empty array when m > n and m, n are negative with [m..n]" do
+ @array[-2..-3].should == []
+ ScratchPad.recorded.should == nil
+ end
+
+ it "returns [] if index == array.size with [index, length]" do
+ @array[5, 2].should == []
+ ScratchPad.recorded.should == nil
+ end
+
+ it "returns [] if the index is valid but length is zero with [index, length]" do
+ @array[0, 0].should == []
+ @array[2, 0].should == []
+ ScratchPad.recorded.should == nil
+ end
+
+ it "does not call #initialize on the subclass instance" do
+ @array[0, 3].should == [1, 2, 3]
+ ScratchPad.recorded.should == nil
+ end
+ end
+
+ it "raises a RangeError when the start index is out of range of Fixnum" do
+ array = [1, 2, 3, 4, 5, 6]
+ obj = mock('large value')
+ obj.should_receive(:to_int).and_return(bignum_value)
+ -> { array[obj] }.should.raise(RangeError)
+
+ obj = 8e19
+ -> { array[obj] }.should.raise(RangeError)
+
+ # boundary value when longs are 64 bits
+ -> { array[2.0**63] }.should.raise(RangeError)
+
+ # just under the boundary value when longs are 64 bits
+ array[max_long.to_f.prev_float].should == nil
+ end
+
+ it "raises a RangeError when the length is out of range of Fixnum" do
+ array = [1, 2, 3, 4, 5, 6]
+ obj = mock('large value')
+ obj.should_receive(:to_int).and_return(bignum_value)
+ -> { array[1, obj] }.should.raise(RangeError)
+
+ obj = 8e19
+ -> { array[1, obj] }.should.raise(RangeError)
+ end
+
+ it "raises a type error if a range is passed with a length" do
+ ->{ [1, 2, 3][1..2, 1] }.should.raise(TypeError)
+ end
+
+ it "raises a RangeError if passed a range with a bound that is too large" do
+ array = [1, 2, 3, 4, 5, 6]
+ -> { array[bignum_value..(bignum_value + 1)] }.should.raise(RangeError)
+ -> { array[0..bignum_value] }.should.raise(RangeError)
+ end
+
+ it "can accept endless ranges" do
+ a = [0, 1, 2, 3, 4, 5]
+ a[eval("(2..)")].should == [2, 3, 4, 5]
+ a[eval("(2...)")].should == [2, 3, 4, 5]
+ a[eval("(-2..)")].should == [4, 5]
+ a[eval("(-2...)")].should == [4, 5]
+ a[eval("(9..)")].should == nil
+ a[eval("(9...)")].should == nil
+ a[eval("(-9..)")].should == nil
+ a[eval("(-9...)")].should == nil
+ end
+
+ describe "can be sliced with Enumerator::ArithmeticSequence" do
+ before :each do
+ @array = [0, 1, 2, 3, 4, 5]
+ end
+
+ it "has endless range and positive steps" do
+ @array[eval("(0..).step(1)")].should == [0, 1, 2, 3, 4, 5]
+ @array[eval("(0..).step(2)")].should == [0, 2, 4]
+ @array[eval("(0..).step(10)")].should == [0]
+
+ @array[eval("(2..).step(1)")].should == [2, 3, 4, 5]
+ @array[eval("(2..).step(2)")].should == [2, 4]
+ @array[eval("(2..).step(10)")].should == [2]
+
+ @array[eval("(-3..).step(1)")].should == [3, 4, 5]
+ @array[eval("(-3..).step(2)")].should == [3, 5]
+ @array[eval("(-3..).step(10)")].should == [3]
+ end
+
+ it "has beginless range and positive steps" do
+ # end with zero index
+ @array[(..0).step(1)].should == [0]
+ @array[(...0).step(1)].should == []
+
+ @array[(..0).step(2)].should == [0]
+ @array[(...0).step(2)].should == []
+
+ @array[(..0).step(10)].should == [0]
+ @array[(...0).step(10)].should == []
+
+ # end with positive index
+ @array[(..3).step(1)].should == [0, 1, 2, 3]
+ @array[(...3).step(1)].should == [0, 1, 2]
+
+ @array[(..3).step(2)].should == [0, 2]
+ @array[(...3).step(2)].should == [0, 2]
+
+ @array[(..3).step(10)].should == [0]
+ @array[(...3).step(10)].should == [0]
+
+ # end with negative index
+ @array[(..-2).step(1)].should == [0, 1, 2, 3, 4,]
+ @array[(...-2).step(1)].should == [0, 1, 2, 3]
+
+ @array[(..-2).step(2)].should == [0, 2, 4]
+ @array[(...-2).step(2)].should == [0, 2]
+
+ @array[(..-2).step(10)].should == [0]
+ @array[(...-2).step(10)].should == [0]
+ end
+
+ it "has endless range and negative steps" do
+ @array[eval("(0..).step(-1)")].should == [0]
+ @array[eval("(0..).step(-2)")].should == [0]
+ @array[eval("(0..).step(-10)")].should == [0]
+
+ @array[eval("(2..).step(-1)")].should == [2, 1, 0]
+ @array[eval("(2..).step(-2)")].should == [2, 0]
+
+ @array[eval("(-3..).step(-1)")].should == [3, 2, 1, 0]
+ @array[eval("(-3..).step(-2)")].should == [3, 1]
+ end
+
+ it "has closed range and positive steps" do
+ # start and end with 0
+ @array[eval("(0..0).step(1)")].should == [0]
+ @array[eval("(0...0).step(1)")].should == []
+
+ @array[eval("(0..0).step(2)")].should == [0]
+ @array[eval("(0...0).step(2)")].should == []
+
+ @array[eval("(0..0).step(10)")].should == [0]
+ @array[eval("(0...0).step(10)")].should == []
+
+ # start and end with positive index
+ @array[eval("(1..3).step(1)")].should == [1, 2, 3]
+ @array[eval("(1...3).step(1)")].should == [1, 2]
+
+ @array[eval("(1..3).step(2)")].should == [1, 3]
+ @array[eval("(1...3).step(2)")].should == [1]
+
+ @array[eval("(1..3).step(10)")].should == [1]
+ @array[eval("(1...3).step(10)")].should == [1]
+
+ # start with positive index, end with negative index
+ @array[eval("(1..-2).step(1)")].should == [1, 2, 3, 4]
+ @array[eval("(1...-2).step(1)")].should == [1, 2, 3]
+
+ @array[eval("(1..-2).step(2)")].should == [1, 3]
+ @array[eval("(1...-2).step(2)")].should == [1, 3]
+
+ @array[eval("(1..-2).step(10)")].should == [1]
+ @array[eval("(1...-2).step(10)")].should == [1]
+
+ # start with negative index, end with positive index
+ @array[eval("(-4..4).step(1)")].should == [2, 3, 4]
+ @array[eval("(-4...4).step(1)")].should == [2, 3]
+
+ @array[eval("(-4..4).step(2)")].should == [2, 4]
+ @array[eval("(-4...4).step(2)")].should == [2]
+
+ @array[eval("(-4..4).step(10)")].should == [2]
+ @array[eval("(-4...4).step(10)")].should == [2]
+
+ # start with negative index, end with negative index
+ @array[eval("(-4..-2).step(1)")].should == [2, 3, 4]
+ @array[eval("(-4...-2).step(1)")].should == [2, 3]
+
+ @array[eval("(-4..-2).step(2)")].should == [2, 4]
+ @array[eval("(-4...-2).step(2)")].should == [2]
+
+ @array[eval("(-4..-2).step(10)")].should == [2]
+ @array[eval("(-4...-2).step(10)")].should == [2]
+ end
+
+ it "has closed range and negative steps" do
+ # start and end with 0
+ @array[eval("(0..0).step(-1)")].should == [0]
+ @array[eval("(0...0).step(-1)")].should == []
+
+ @array[eval("(0..0).step(-2)")].should == [0]
+ @array[eval("(0...0).step(-2)")].should == []
+
+ @array[eval("(0..0).step(-10)")].should == [0]
+ @array[eval("(0...0).step(-10)")].should == []
+
+ # start and end with positive index
+ @array[eval("(1..3).step(-1)")].should == []
+ @array[eval("(1...3).step(-1)")].should == []
+
+ @array[eval("(1..3).step(-2)")].should == []
+ @array[eval("(1...3).step(-2)")].should == []
+
+ @array[eval("(1..3).step(-10)")].should == []
+ @array[eval("(1...3).step(-10)")].should == []
+
+ # start with positive index, end with negative index
+ @array[eval("(1..-2).step(-1)")].should == []
+ @array[eval("(1...-2).step(-1)")].should == []
+
+ @array[eval("(1..-2).step(-2)")].should == []
+ @array[eval("(1...-2).step(-2)")].should == []
+
+ @array[eval("(1..-2).step(-10)")].should == []
+ @array[eval("(1...-2).step(-10)")].should == []
+
+ # start with negative index, end with positive index
+ @array[eval("(-4..4).step(-1)")].should == []
+ @array[eval("(-4...4).step(-1)")].should == []
+
+ @array[eval("(-4..4).step(-2)")].should == []
+ @array[eval("(-4...4).step(-2)")].should == []
+
+ @array[eval("(-4..4).step(-10)")].should == []
+ @array[eval("(-4...4).step(-10)")].should == []
+
+ # start with negative index, end with negative index
+ @array[eval("(-4..-2).step(-1)")].should == []
+ @array[eval("(-4...-2).step(-1)")].should == []
+
+ @array[eval("(-4..-2).step(-2)")].should == []
+ @array[eval("(-4...-2).step(-2)")].should == []
+
+ @array[eval("(-4..-2).step(-10)")].should == []
+ @array[eval("(-4...-2).step(-10)")].should == []
+ end
+
+ it "has inverted closed range and positive steps" do
+ # start and end with positive index
+ @array[eval("(3..1).step(1)")].should == []
+ @array[eval("(3...1).step(1)")].should == []
+
+ @array[eval("(3..1).step(2)")].should == []
+ @array[eval("(3...1).step(2)")].should == []
+
+ @array[eval("(3..1).step(10)")].should == []
+ @array[eval("(3...1).step(10)")].should == []
+
+ # start with negative index, end with positive index
+ @array[eval("(-2..1).step(1)")].should == []
+ @array[eval("(-2...1).step(1)")].should == []
+
+ @array[eval("(-2..1).step(2)")].should == []
+ @array[eval("(-2...1).step(2)")].should == []
+
+ @array[eval("(-2..1).step(10)")].should == []
+ @array[eval("(-2...1).step(10)")].should == []
+
+ # start with positive index, end with negative index
+ @array[eval("(4..-4).step(1)")].should == []
+ @array[eval("(4...-4).step(1)")].should == []
+
+ @array[eval("(4..-4).step(2)")].should == []
+ @array[eval("(4...-4).step(2)")].should == []
+
+ @array[eval("(4..-4).step(10)")].should == []
+ @array[eval("(4...-4).step(10)")].should == []
+
+ # start with negative index, end with negative index
+ @array[eval("(-2..-4).step(1)")].should == []
+ @array[eval("(-2...-4).step(1)")].should == []
+
+ @array[eval("(-2..-4).step(2)")].should == []
+ @array[eval("(-2...-4).step(2)")].should == []
+
+ @array[eval("(-2..-4).step(10)")].should == []
+ @array[eval("(-2...-4).step(10)")].should == []
+ end
+
+ it "has range with bounds outside of array" do
+ # end is equal to array's length
+ @array[(0..6).step(1)].should == [0, 1, 2, 3, 4, 5]
+ -> { @array[(0..6).step(2)] }.should.raise(RangeError)
+
+ # end is greater than length with positive steps
+ @array[(1..6).step(2)].should == [1, 3, 5]
+ @array[(2..7).step(2)].should == [2, 4]
+ -> { @array[(2..8).step(2)] }.should.raise(RangeError)
+
+ # begin is greater than length with negative steps
+ @array[(6..1).step(-2)].should == [5, 3, 1]
+ @array[(7..2).step(-2)].should == [5, 3]
+ -> { @array[(8..2).step(-2)] }.should.raise(RangeError)
+ end
+
+ it "has endless range with start outside of array's bounds" do
+ @array[eval("(6..).step(1)")].should == []
+ @array[eval("(7..).step(1)")].should == nil
+
+ @array[eval("(6..).step(2)")].should == []
+ -> { @array[eval("(7..).step(2)")] }.should.raise(RangeError)
+ end
+ end
+
+ it "can accept beginless ranges" do
+ a = [0, 1, 2, 3, 4, 5]
+ a[(..3)].should == [0, 1, 2, 3]
+ a[(...3)].should == [0, 1, 2]
+ a[(..-3)].should == [0, 1, 2, 3]
+ a[(...-3)].should == [0, 1, 2]
+ a[(..0)].should == [0]
+ a[(...0)].should == []
+ a[(..9)].should == [0, 1, 2, 3, 4, 5]
+ a[(...9)].should == [0, 1, 2, 3, 4, 5]
+ a[(..-9)].should == []
+ a[(...-9)].should == []
+ 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[(2..).step(-1)].should == [2, 1, 0]
+ array[(2..).step(-2)].should == [2, 0]
+ array[(2..).step(-3)].should == [2]
+ array[(2..).step(-4)].should == [2]
+
+ array[(-3..).step(-1)].should == [3, 2, 1, 0]
+ array[(-3..).step(-2)].should == [3, 1]
+ array[(-3..).step(-3)].should == [3, 0]
+ array[(-3..).step(-4)].should == [3]
+ array[(-3..).step(-5)].should == [3]
+
+ array[(..0).step(-1)].should == [5, 4, 3, 2, 1, 0]
+ array[(..0).step(-2)].should == [5, 3, 1]
+ array[(..0).step(-3)].should == [5, 2]
+ array[(..0).step(-4)].should == [5, 1]
+ array[(..0).step(-5)].should == [5, 0]
+ array[(..0).step(-6)].should == [5]
+ array[(..0).step(-7)].should == [5]
+
+ array[(...0).step(-1)].should == [5, 4, 3, 2, 1]
+ array[(...0).step(-2)].should == [5, 3, 1]
+ array[(...0).step(-3)].should == [5, 2]
+ array[(...0).step(-4)].should == [5, 1]
+ array[(...0).step(-5)].should == [5]
+ array[(...0).step(-6)].should == [5]
+
+ array[(...1).step(-1)].should == [5, 4, 3, 2]
+ array[(...1).step(-2)].should == [5, 3]
+ array[(...1).step(-3)].should == [5, 2]
+ array[(...1).step(-4)].should == [5]
+ array[(...1).step(-5)].should == [5]
+
+ array[(..-5).step(-1)].should == [5, 4, 3, 2, 1]
+ array[(..-5).step(-2)].should == [5, 3, 1]
+ array[(..-5).step(-3)].should == [5, 2]
+ array[(..-5).step(-4)].should == [5, 1]
+ array[(..-5).step(-5)].should == [5]
+ array[(..-5).step(-6)].should == [5]
+
+ array[(...-5).step(-1)].should == [5, 4, 3, 2]
+ array[(...-5).step(-2)].should == [5, 3]
+ array[(...-5).step(-3)].should == [5, 2]
+ array[(...-5).step(-4)].should == [5]
+ array[(...-5).step(-5)].should == [5]
+
+ array[(4..1).step(-1)].should == [4, 3, 2, 1]
+ array[(4..1).step(-2)].should == [4, 2]
+ array[(4..1).step(-3)].should == [4, 1]
+ array[(4..1).step(-4)].should == [4]
+ array[(4..1).step(-5)].should == [4]
+
+ array[(4...1).step(-1)].should == [4, 3, 2]
+ array[(4...1).step(-2)].should == [4, 2]
+ array[(4...1).step(-3)].should == [4]
+ array[(4...1).step(-4)].should == [4]
+
+ array[(-2..1).step(-1)].should == [4, 3, 2, 1]
+ array[(-2..1).step(-2)].should == [4, 2]
+ array[(-2..1).step(-3)].should == [4, 1]
+ array[(-2..1).step(-4)].should == [4]
+ array[(-2..1).step(-5)].should == [4]
+
+ array[(-2...1).step(-1)].should == [4, 3, 2]
+ array[(-2...1).step(-2)].should == [4, 2]
+ array[(-2...1).step(-3)].should == [4]
+ array[(-2...1).step(-4)].should == [4]
+
+ array[(4..-5).step(-1)].should == [4, 3, 2, 1]
+ array[(4..-5).step(-2)].should == [4, 2]
+ array[(4..-5).step(-3)].should == [4, 1]
+ array[(4..-5).step(-4)].should == [4]
+ array[(4..-5).step(-5)].should == [4]
+
+ array[(4...-5).step(-1)].should == [4, 3, 2]
+ array[(4...-5).step(-2)].should == [4, 2]
+ array[(4...-5).step(-3)].should == [4]
+ array[(4...-5).step(-4)].should == [4]
+
+ array[(-2..-5).step(-1)].should == [4, 3, 2, 1]
+ array[(-2..-5).step(-2)].should == [4, 2]
+ array[(-2..-5).step(-3)].should == [4, 1]
+ array[(-2..-5).step(-4)].should == [4]
+ array[(-2..-5).step(-5)].should == [4]
+
+ array[(-2...-5).step(-1)].should == [4, 3, 2]
+ array[(-2...-5).step(-2)].should == [4, 2]
+ array[(-2...-5).step(-3)].should == [4]
+ array[(-2...-5).step(-4)].should == [4]
+ end
+ end
+
+ it "can accept nil...nil ranges" do
+ a = [0, 1, 2, 3, 4, 5]
+ a[eval("(nil...nil)")].should == a
+ a[(...nil)].should == a
+ a[eval("(nil..)")].should == a
+ end
+end
+
+describe "Array.[]" do
+ it "[] should return a new array populated with the given elements" do
+ array = Array[1, 'a', nil]
+ array[0].should == 1
+ array[1].should == 'a'
+ array[2].should == nil
+ end
+
+ it "when applied to a literal nested array, unpacks its elements into the containing array" do
+ Array[1, 2, *[3, 4, 5]].should == [1, 2, 3, 4, 5]
+ end
+
+ it "when applied to a nested referenced array, unpacks its elements into the containing array" do
+ splatted_array = Array[3, 4, 5]
+ Array[1, 2, *splatted_array].should == [1, 2, 3, 4, 5]
+ end
+
+ it "can unpack 2 or more nested referenced array" do
+ splatted_array = Array[3, 4, 5]
+ splatted_array2 = Array[6, 7, 8]
+ Array[1, 2, *splatted_array, *splatted_array2].should == [1, 2, 3, 4, 5, 6, 7, 8]
+ end
+
+ it "constructs a nested Hash for tailing key-value pairs" do
+ Array[1, 2, 3 => 4, 5 => 6].should == [1, 2, { 3 => 4, 5 => 6 }]
+ end
+
+ describe "with a subclass of Array" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "returns an instance of the subclass" do
+ ArraySpecs::MyArray[1, 2, 3].should.instance_of?(ArraySpecs::MyArray)
+ end
+
+ it "does not call #initialize on the subclass instance" do
+ ArraySpecs::MyArray[1, 2, 3].should == [1, 2, 3]
+ ScratchPad.recorded.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/array/element_set_spec.rb b/spec/ruby/core/array/element_set_spec.rb
new file mode 100644
index 0000000000..671e4338de
--- /dev/null
+++ b/spec/ruby/core/array/element_set_spec.rb
@@ -0,0 +1,537 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#[]=" do
+ it "sets the value of the element at index" do
+ a = [1, 2, 3, 4]
+ a[2] = 5
+ a[-1] = 6
+ a[5] = 3
+ a.should == [1, 2, 5, 6, nil, 3]
+
+ a = []
+ a[4] = "e"
+ a.should == [nil, nil, nil, nil, "e"]
+ a[3] = "d"
+ a.should == [nil, nil, nil, "d", "e"]
+ a[0] = "a"
+ a.should == ["a", nil, nil, "d", "e"]
+ a[-3] = "C"
+ a.should == ["a", nil, "C", "d", "e"]
+ a[-1] = "E"
+ a.should == ["a", nil, "C", "d", "E"]
+ a[-5] = "A"
+ a.should == ["A", nil, "C", "d", "E"]
+ a[5] = "f"
+ a.should == ["A", nil, "C", "d", "E", "f"]
+ a[1] = []
+ a.should == ["A", [], "C", "d", "E", "f"]
+ a[-1] = nil
+ a.should == ["A", [], "C", "d", "E", nil]
+ end
+
+ it "sets the section defined by [start,length] to other" do
+ a = [1, 2, 3, 4, 5, 6]
+ a[0, 1] = 2
+ a[3, 2] = ['a', 'b', 'c', 'd']
+ a.should == [2, 2, 3, "a", "b", "c", "d", 6]
+ end
+
+ it "replaces the section defined by [start,length] with the given values" do
+ a = [1, 2, 3, 4, 5, 6]
+ a[3, 2] = 'a', 'b', 'c', 'd'
+ a.should == [1, 2, 3, "a", "b", "c", "d", 6]
+ end
+
+ it "just sets the section defined by [start,length] to other even if other is nil" do
+ a = ['a', 'b', 'c', 'd', 'e']
+ a[1, 3] = nil
+ a.should == ["a", nil, "e"]
+ end
+
+ it "returns nil if the rhs is nil" do
+ a = [1, 2, 3]
+ (a[1, 3] = nil).should == nil
+ (a[1..3] = nil).should == nil
+ end
+
+ it "sets the section defined by range to other" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[1...2] = 9
+ a[3..6] = [6, 6, 6]
+ a.should == [6, 9, 4, 6, 6, 6]
+ end
+
+ it "replaces the section defined by range with the given values" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[3..6] = :a, :b, :c
+ a.should == [6, 5, 4, :a, :b, :c]
+ end
+
+ it "just sets the section defined by range to other even if other is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[0..1] = nil
+ a.should == [nil, 3, 4, 5]
+ end
+
+ it 'expands and nil-pads the array if section assigned by range is outside array boundaries' do
+ a = ['a']
+ a[3..4] = ['b', 'c']
+ a.should == ['a', nil, nil, 'b', 'c']
+ end
+
+ it "calls to_int on its start and length arguments" do
+ obj = mock('to_int')
+ obj.stub!(:to_int).and_return(2)
+
+ a = [1, 2, 3, 4]
+ a[obj, 0] = [9]
+ a.should == [1, 2, 9, 3, 4]
+ a[obj, obj] = []
+ a.should == [1, 2, 4]
+ a[obj] = -1
+ a.should == [1, 2, -1]
+ end
+
+ it "checks frozen before attempting to coerce arguments" do
+ a = [1,2,3,4].freeze
+ -> {a[:foo] = 1}.should.raise(FrozenError)
+ -> {a[:foo, :bar] = 1}.should.raise(FrozenError)
+ end
+
+ it "sets elements in the range arguments when passed ranges" do
+ ary = [1, 2, 3]
+ rhs = [nil, [], ["x"], ["x", "y"]]
+ (0 .. ary.size + 2).each do |a|
+ (a .. ary.size + 3).each do |b|
+ rhs.each do |c|
+ ary1 = ary.dup
+ ary1[a .. b] = c
+ ary2 = ary.dup
+ ary2[a, 1 + b-a] = c
+ ary1.should == ary2
+
+ ary1 = ary.dup
+ ary1[a ... b] = c
+ ary2 = ary.dup
+ ary2[a, b-a] = c
+ ary1.should == ary2
+ end
+ end
+ end
+ end
+
+ it "inserts the given elements with [range] which the range is zero-width" do
+ ary = [1, 2, 3]
+ ary[1...1] = 0
+ ary.should == [1, 0, 2, 3]
+ ary[1...1] = [5]
+ ary.should == [1, 5, 0, 2, 3]
+ ary[1...1] = :a, :b, :c
+ ary.should == [1, :a, :b, :c, 5, 0, 2, 3]
+ end
+
+ it "inserts the given elements with [start, length] which length is zero" do
+ ary = [1, 2, 3]
+ ary[1, 0] = 0
+ ary.should == [1, 0, 2, 3]
+ ary[1, 0] = [5]
+ ary.should == [1, 5, 0, 2, 3]
+ ary[1, 0] = :a, :b, :c
+ ary.should == [1, :a, :b, :c, 5, 0, 2, 3]
+ end
+
+ # Now we only have to test cases where the start, length interface would
+ # have raise an exception because of negative size
+ it "inserts the given elements with [range] which the range has negative width" do
+ ary = [1, 2, 3]
+ ary[1..0] = 0
+ ary.should == [1, 0, 2, 3]
+ ary[1..0] = [4, 3]
+ ary.should == [1, 4, 3, 0, 2, 3]
+ ary[1..0] = :a, :b, :c
+ ary.should == [1, :a, :b, :c, 4, 3, 0, 2, 3]
+ end
+
+ it "just inserts nil if the section defined by range is zero-width and the rhs is nil" do
+ ary = [1, 2, 3]
+ ary[1...1] = nil
+ ary.should == [1, nil, 2, 3]
+ end
+
+ it "just inserts nil if the section defined by range has negative width and the rhs is nil" do
+ ary = [1, 2, 3]
+ ary[1..0] = nil
+ ary.should == [1, nil, 2, 3]
+ end
+
+ it "does nothing if the section defined by range is zero-width and the rhs is an empty array" do
+ ary = [1, 2, 3]
+ ary[1...1] = []
+ ary.should == [1, 2, 3]
+ end
+
+ it "does nothing if the section defined by range has negative width and the rhs is an empty array" do
+ ary = [1, 2, 3, 4, 5]
+ ary[1...0] = []
+ ary.should == [1, 2, 3, 4, 5]
+ ary[-2..2] = []
+ ary.should == [1, 2, 3, 4, 5]
+ end
+
+ it "tries to convert Range elements to Integers using #to_int with [m..n] and [m...n]" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ a = [1, 2, 3, 4]
+
+ a[from .. to] = ["a", "b", "c"]
+ a.should == [1, "a", "b", "c", 4]
+
+ a[to .. from] = ["x"]
+ a.should == [1, "a", "b", "x", "c", 4]
+ -> { a["a" .. "b"] = [] }.should.raise(TypeError)
+ -> { a[from .. "b"] = [] }.should.raise(TypeError)
+ end
+
+ it "raises an IndexError when passed indexes out of bounds" do
+ a = [1, 2, 3, 4]
+ -> { a[-5] = "" }.should.raise(IndexError)
+ -> { a[-5, -1] = "" }.should.raise(IndexError)
+ -> { a[-5, 0] = "" }.should.raise(IndexError)
+ -> { a[-5, 1] = "" }.should.raise(IndexError)
+ -> { a[-5, 2] = "" }.should.raise(IndexError)
+ -> { a[-5, 10] = "" }.should.raise(IndexError)
+
+ -> { a[-5..-5] = "" }.should.raise(RangeError)
+ -> { a[-5...-5] = "" }.should.raise(RangeError)
+ -> { a[-5..-4] = "" }.should.raise(RangeError)
+ -> { a[-5...-4] = "" }.should.raise(RangeError)
+ -> { a[-5..10] = "" }.should.raise(RangeError)
+ -> { a[-5...10] = "" }.should.raise(RangeError)
+
+ # ok
+ a[0..-9] = [1]
+ a.should == [1, 1, 2, 3, 4]
+ end
+
+ it "calls to_ary on its rhs argument for multi-element sets" do
+ obj = mock('to_ary')
+ def obj.to_ary() [1, 2, 3] end
+ ary = [1, 2]
+ ary[0, 0] = obj
+ ary.should == [1, 2, 3, 1, 2]
+ ary[1, 10] = obj
+ ary.should == [1, 1, 2, 3]
+ end
+
+ it "does not call to_ary on rhs array subclasses for multi-element sets" do
+ ary = []
+ ary[0, 0] = ArraySpecs::ToAryArray[5, 6, 7]
+ ary.should == [5, 6, 7]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array[0, 0] = [] }.should.raise(FrozenError)
+ end
+end
+
+describe "Array#[]= with [index]" do
+ it "returns value assigned if idx is inside array" do
+ a = [1, 2, 3, 4, 5]
+ (a[3] = 6).should == 6
+ end
+
+ it "returns value assigned if idx is right beyond right array boundary" do
+ a = [1, 2, 3, 4, 5]
+ (a[5] = 6).should == 6
+ end
+
+ it "returns value assigned if idx far beyond right array boundary" do
+ a = [1, 2, 3, 4, 5]
+ (a[10] = 6).should == 6
+ end
+
+ it "sets the value of the element at index" do
+ a = [1, 2, 3, 4]
+ a[2] = 5
+ a[-1] = 6
+ a[5] = 3
+ a.should == [1, 2, 5, 6, nil, 3]
+ end
+
+ it "sets the value of the element if it is right beyond the array boundary" do
+ a = [1, 2, 3, 4]
+ a[4] = 8
+ a.should == [1, 2, 3, 4, 8]
+ end
+
+end
+
+describe "Array#[]= with [index, count]" do
+ it "returns non-array value if non-array value assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[2, 3] = 10).should == 10
+ end
+
+ it "returns array if array assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[2, 3] = [4, 5]).should == [4, 5]
+ end
+
+ it "accepts a frozen String literal as RHS" do
+ a = ['a', 'b', 'c']
+ a[0, 2] = 'd'.freeze
+ a.should == ['d', 'c']
+ end
+
+ it "just sets the section defined by [start,length] to nil even if the rhs is nil" do
+ a = ['a', 'b', 'c', 'd', 'e']
+ a[1, 3] = nil
+ a.should == ["a", nil, "e"]
+ end
+
+ it "just sets the section defined by [start,length] to nil if negative index within bounds, cnt > 0 and the rhs is nil" do
+ a = ['a', 'b', 'c', 'd', 'e']
+ a[-3, 2] = nil
+ a.should == ["a", "b", nil, "e"]
+ end
+
+ it "replaces the section defined by [start,length] to other" do
+ a = [1, 2, 3, 4, 5, 6]
+ a[0, 1] = 2
+ a[3, 2] = ['a', 'b', 'c', 'd']
+ a.should == [2, 2, 3, "a", "b", "c", "d", 6]
+ end
+
+ it "replaces the section to other if idx < 0 and cnt > 0" do
+ a = [1, 2, 3, 4, 5, 6]
+ a[-3, 2] = ["x", "y", "z"]
+ a.should == [1, 2, 3, "x", "y", "z", 6]
+ end
+
+ it "replaces the section to other even if cnt spanning beyond the array boundary" do
+ a = [1, 2, 3, 4, 5]
+ a[-1, 3] = [7, 8]
+ a.should == [1, 2, 3, 4, 7, 8]
+ end
+
+ it "pads the Array with nils if the span is past the end" do
+ a = [1, 2, 3, 4, 5]
+ a[10, 1] = [1]
+ a.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil, 1]
+
+ b = [1, 2, 3, 4, 5]
+ b[10, 0] = [1]
+ a.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil, 1]
+
+ c = [1, 2, 3, 4, 5]
+ c[10, 0] = []
+ c.should == [1, 2, 3, 4, 5, nil, nil, nil, nil, nil]
+ end
+
+ it "inserts other section in place defined by idx" do
+ a = [1, 2, 3, 4, 5]
+ a[3, 0] = [7, 8]
+ a.should == [1, 2, 3, 7, 8, 4, 5]
+
+ b = [1, 2, 3, 4, 5]
+ b[1, 0] = b
+ b.should == [1, 1, 2, 3, 4, 5, 2, 3, 4, 5]
+ end
+
+ it "raises an IndexError when passed start and negative length" do
+ a = [1, 2, 3, 4]
+ -> { a[-2, -1] = "" }.should.raise(IndexError)
+ -> { a[0, -1] = "" }.should.raise(IndexError)
+ -> { a[2, -1] = "" }.should.raise(IndexError)
+ -> { a[4, -1] = "" }.should.raise(IndexError)
+ -> { a[10, -1] = "" }.should.raise(IndexError)
+ -> { [1, 2, 3, 4, 5][2, -1] = [7, 8] }.should.raise(IndexError)
+ end
+end
+
+describe "Array#[]= with [m..n]" do
+ it "returns non-array value if non-array value assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[2..4] = 10).should == 10
+ (a.[]=(2..4, 10)).should == 10
+ end
+
+ it "returns array if array assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[2..4] = [7, 8]).should == [7, 8]
+ (a.[]=(2..4, [7, 8])).should == [7, 8]
+ end
+
+ it "just sets the section defined by range to nil even if the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[0..1] = nil
+ a.should == [nil, 3, 4, 5]
+ end
+
+ it "just sets the section defined by range to nil if m and n < 0 and the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[-3..-2] = nil
+ a.should == [1, 2, nil, 5]
+ end
+
+ it "replaces the section defined by range" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[1...2] = 9
+ a[3..6] = [6, 6, 6]
+ a.should == [6, 9, 4, 6, 6, 6]
+ end
+
+ it "replaces the section if m and n < 0" do
+ a = [1, 2, 3, 4, 5]
+ a[-3..-2] = [7, 8, 9]
+ a.should == [1, 2, 7, 8, 9, 5]
+ end
+
+ it "replaces the section if m < 0 and n > 0" do
+ a = [1, 2, 3, 4, 5]
+ a[-4..3] = [8]
+ a.should == [1, 8, 5]
+ end
+
+ it "inserts the other section at m if m > n" do
+ a = [1, 2, 3, 4, 5]
+ a[3..1] = [8]
+ a.should == [1, 2, 3, 8, 4, 5]
+ end
+
+ it "inserts at the end if m > the array size" do
+ a = [1, 2, 3]
+ a[3..3] = [4]
+ a.should == [1, 2, 3, 4]
+ a[5..7] = [6]
+ a.should == [1, 2, 3, 4, nil, 6]
+ end
+
+ describe "Range subclasses" do
+ before :each do
+ @range_incl = ArraySpecs::MyRange.new(1, 2)
+ @range_excl = ArraySpecs::MyRange.new(-3, -1, true)
+ end
+
+ it "accepts Range subclasses" do
+ a = [1, 2, 3, 4]
+
+ a[@range_incl] = ["a", "b"]
+ a.should == [1, "a", "b", 4]
+ a[@range_excl] = ["A", "B"]
+ a.should == [1, "A", "B", 4]
+ end
+
+ it "returns non-array value if non-array value assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[@range_incl] = 10).should == 10
+ (a.[]=(@range_incl, 10)).should == 10
+ end
+
+ it "returns array if array assigned" do
+ a = [1, 2, 3, 4, 5]
+ (a[@range_incl] = [7, 8]).should == [7, 8]
+ a.[]=(@range_incl, [7, 8]).should == [7, 8]
+ end
+ end
+end
+
+describe "Array#[]= with [m..]" do
+ it "just sets the section defined by range to nil even if the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[eval("(2..)")] = nil
+ a.should == [1, 2, nil]
+ end
+
+ it "just sets the section defined by range to nil if m and n < 0 and the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[eval("(-3..)")] = nil
+ a.should == [1, 2, nil]
+ end
+
+ it "replaces the section defined by range" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[eval("(3...)")] = 9
+ a.should == [6, 5, 4, 9]
+ a[eval("(2..)")] = [7, 7, 7]
+ a.should == [6, 5, 7, 7, 7]
+ end
+
+ it "replaces the section if m and n < 0" do
+ a = [1, 2, 3, 4, 5]
+ a[eval("(-3..)")] = [7, 8, 9]
+ a.should == [1, 2, 7, 8, 9]
+ end
+
+ it "inserts at the end if m > the array size" do
+ a = [1, 2, 3]
+ a[eval("(3..)")] = [4]
+ a.should == [1, 2, 3, 4]
+ a[eval("(5..)")] = [6]
+ a.should == [1, 2, 3, 4, nil, 6]
+ end
+end
+
+describe "Array#[]= with [..n] and [...n]" do
+ it "just sets the section defined by range to nil even if the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[(..2)] = nil
+ a.should == [nil, 4, 5]
+ a[(...2)] = nil
+ a.should == [nil, 5]
+ end
+
+ it "just sets the section defined by range to nil if n < 0 and the rhs is nil" do
+ a = [1, 2, 3, 4, 5]
+ a[(..-3)] = nil
+ a.should == [nil, 4, 5]
+ a[(...-1)] = [nil, 5]
+ end
+
+ it "replaces the section defined by range" do
+ a = [6, 5, 4, 3, 2, 1]
+ a[(...3)] = 9
+ a.should == [9, 3, 2, 1]
+ a[(..2)] = [7, 7, 7, 7, 7]
+ a.should == [7, 7, 7, 7, 7, 1]
+ end
+
+ it "replaces the section if n < 0" do
+ a = [1, 2, 3, 4, 5]
+ a[(..-2)] = [7, 8, 9]
+ a.should == [7, 8, 9, 5]
+ end
+
+ it "replaces everything if n > the array size" do
+ a = [1, 2, 3]
+ a[(...7)] = [4]
+ a.should == [4]
+ end
+
+ it "inserts at the beginning if n < negative the array size" do
+ a = [1, 2, 3]
+ a[(..-7)] = [4]
+ a.should == [4, 1, 2, 3]
+ a[(...-10)] = [6]
+ a.should == [6, 4, 1, 2, 3]
+ end
+end
+
+describe "Array#[] after a shift" do
+ it "works for insertion" do
+ a = [1,2]
+ a.shift
+ a.shift
+ a[0,0] = [3,4]
+ a.should == [3,4]
+ end
+end
diff --git a/spec/ruby/core/array/empty_spec.rb b/spec/ruby/core/array/empty_spec.rb
new file mode 100644
index 0000000000..f70b1b6ebe
--- /dev/null
+++ b/spec/ruby/core/array/empty_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#empty?" do
+ it "returns true if the array has no elements" do
+ [].should.empty?
+ [1].should_not.empty?
+ [1, 2].should_not.empty?
+ end
+end
diff --git a/spec/ruby/core/array/eql_spec.rb b/spec/ruby/core/array/eql_spec.rb
new file mode 100644
index 0000000000..9a7447c2e8
--- /dev/null
+++ b/spec/ruby/core/array/eql_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eql'
+
+describe "Array#eql?" do
+ it_behaves_like :array_eql, :eql?
+
+ it "returns false if any corresponding elements are not #eql?" do
+ [1, 2, 3, 4].should_not.eql?([1, 2, 3, 4.0])
+ end
+
+ it "returns false if other is not a kind of Array" do
+ obj = mock("array eql?")
+ obj.should_not_receive(:to_ary)
+ obj.should_not_receive(:eql?)
+
+ [1, 2, 3].should_not.eql?(obj)
+ end
+end
diff --git a/spec/ruby/core/array/equal_value_spec.rb b/spec/ruby/core/array/equal_value_spec.rb
new file mode 100644
index 0000000000..4f7c0ce5e3
--- /dev/null
+++ b/spec/ruby/core/array/equal_value_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eql'
+
+describe "Array#==" do
+ it_behaves_like :array_eql, :==
+
+ it "compares with an equivalent Array-like object using #to_ary" do
+ obj = mock('array-like')
+ obj.should_receive(:respond_to?).at_least(1).with(:to_ary).and_return(true)
+ obj.should_receive(:==).with([1]).at_least(1).and_return(true)
+
+ ([1] == obj).should == true
+ ([[1]] == [obj]).should == true
+ ([[[1], 3], 2] == [[obj, 3], 2]).should == true
+
+ # recursive arrays
+ arr1 = [[1]]
+ arr1 << arr1
+ arr2 = [obj]
+ arr2 << arr2
+ (arr1 == arr2).should == true
+ (arr2 == arr1).should == true
+ end
+
+ it "returns false if any corresponding elements are not #==" do
+ a = ["a", "b", "c"]
+ b = ["a", "b", "not equal value"]
+ a.should_not == b
+
+ c = mock("c")
+ c.should_receive(:==).and_return(false)
+ ["a", "b", c].should_not == a
+ end
+
+ it "returns true if corresponding elements are #==" do
+ [].should == []
+ ["a", "c", 7].should == ["a", "c", 7]
+
+ [1, 2, 3].should == [1.0, 2.0, 3.0]
+
+ obj = mock('5')
+ obj.should_receive(:==).and_return(true)
+ [obj].should == [5]
+ end
+
+ # See https://bugs.ruby-lang.org/issues/1720
+ it "returns true for [NaN] == [NaN] because Array#== first checks with #equal? and NaN.equal?(NaN) is true" do
+ [Float::NAN].should == [Float::NAN]
+ end
+end
diff --git a/spec/ruby/core/array/fetch_spec.rb b/spec/ruby/core/array/fetch_spec.rb
new file mode 100644
index 0000000000..ee94cfcbb2
--- /dev/null
+++ b/spec/ruby/core/array/fetch_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#fetch" do
+ it "returns the element at the passed index" do
+ [1, 2, 3].fetch(1).should == 2
+ [nil].fetch(0).should == nil
+ end
+
+ it "counts negative indices backwards from end" do
+ [1, 2, 3, 4].fetch(-1).should == 4
+ end
+
+ it "raises an IndexError if there is no element at index" do
+ -> { [1, 2, 3].fetch(3) }.should.raise(IndexError, "index 3 outside of array bounds: -3...3")
+ -> { [1, 2, 3].fetch(-4) }.should.raise(IndexError, "index -4 outside of array bounds: -3...3")
+ -> { [].fetch(0) }.should.raise(IndexError, "index 0 outside of array bounds: 0...0")
+ end
+
+ it "returns default if there is no element at index if passed a default value" do
+ [1, 2, 3].fetch(5, :not_found).should == :not_found
+ [1, 2, 3].fetch(5, nil).should == nil
+ [1, 2, 3].fetch(-4, :not_found).should == :not_found
+ [nil].fetch(0, :not_found).should == nil
+ end
+
+ it "returns the value of block if there is no element at index if passed a block" do
+ [1, 2, 3].fetch(9) { |i| i * i }.should == 81
+ [1, 2, 3].fetch(-9) { |i| i * i }.should == 81
+ end
+
+ it "passes the original index argument object to the block, not the converted Integer" do
+ o = mock('5')
+ def o.to_int(); 5; end
+
+ [1, 2, 3].fetch(o) { |i| i }.should.equal?(o)
+ end
+
+ it "gives precedence to the default block over the default argument" do
+ -> {
+ @result = [1, 2, 3].fetch(9, :foo) { |i| i * i }
+ }.should complain(/block supersedes default value argument/)
+ @result.should == 81
+ 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)
+ ["a", "b", "c"].fetch(obj).should == "c"
+ end
+
+ it "raises a TypeError when the passed argument can't be coerced to Integer" do
+ -> { [].fetch("cat") }.should.raise(TypeError, "no implicit conversion of String into Integer")
+ end
+end
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..237d6ad7f7
--- /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(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(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(TypeError, "no implicit conversion of String into Integer")
+ end
+ end
+end
diff --git a/spec/ruby/core/array/fill_spec.rb b/spec/ruby/core/array/fill_spec.rb
new file mode 100644
index 0000000000..e4d51bd998
--- /dev/null
+++ b/spec/ruby/core/array/fill_spec.rb
@@ -0,0 +1,374 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#fill" do
+ before :all do
+ @never_passed = -> i do
+ raise ExpectationNotMetError, "the control path should not pass here"
+ end
+ end
+
+ it "returns self" do
+ ary = [1, 2, 3]
+ ary.fill(:a).should.equal?(ary)
+ end
+
+ it "is destructive" do
+ ary = [1, 2, 3]
+ ary.fill(:a)
+ ary.should == [:a, :a, :a]
+ end
+
+ it "does not replicate the filler" do
+ ary = [1, 2, 3, 4]
+ str = +"x"
+ ary.fill(str).should == [str, str, str, str]
+ str << "y"
+ ary.should == [str, str, str, str]
+ ary[0].should.equal?(str)
+ ary[1].should.equal?(str)
+ ary[2].should.equal?(str)
+ ary[3].should.equal?(str)
+ end
+
+ it "replaces all elements in the array with the filler if not given a index nor a length" do
+ ary = ['a', 'b', 'c', 'duh']
+ ary.fill(8).should == [8, 8, 8, 8]
+
+ str = "x"
+ ary.fill(str).should == [str, str, str, str]
+ end
+
+ it "replaces all elements with the value of block (index given to block)" do
+ [nil, nil, nil, nil].fill { |i| i * 2 }.should == [0, 2, 4, 6]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.fill('x') }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.fill('x') }.should.raise(FrozenError)
+ end
+
+ it "raises an ArgumentError if 4 or more arguments are passed when no block given" do
+ [].fill('a').should == []
+ [].fill('a', 1).should == []
+ [].fill('a', 1, 2).should == [nil, 'a', 'a']
+ -> { [].fill('a', 1, 2, true) }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if no argument passed and no block given" do
+ -> { [].fill }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if 3 or more arguments are passed when a block given" do
+ [].fill() {|i|}.should == []
+ [].fill(1) {|i|}.should == []
+ [].fill(1, 2) {|i|}.should == [nil, nil, nil]
+ -> { [].fill(1, 2, true) {|i|} }.should.raise(ArgumentError)
+ end
+
+ it "does not truncate the array is the block raises an exception" do
+ a = [1, 2, 3]
+ begin
+ a.fill { raise StandardError, 'Oops' }
+ rescue
+ end
+
+ a.should == [1, 2, 3]
+ end
+
+ it "only changes elements before error is raised, keeping the element which raised an error." do
+ a = [1, 2, 3, 4]
+ begin
+ a.fill do |i|
+ case i
+ when 0 then -1
+ when 1 then -2
+ when 2 then raise StandardError, 'Oops'
+ else 0
+ end
+ end
+ rescue StandardError
+ end
+
+ a.should == [-1, -2, 3, 4]
+ end
+
+ it "tolerates increasing an array size during iteration" do
+ array = [:a, :b, :c]
+ ScratchPad.record []
+ i = 0
+
+ array.fill do |index|
+ ScratchPad << index
+ array << i if i < 100
+ i++
+ index
+ end
+
+ ScratchPad.recorded.should == [0, 1, 2]
+ end
+end
+
+describe "Array#fill with (filler, index, length)" do
+ it "replaces length elements beginning with the index with the filler if given an index and a length" do
+ ary = [1, 2, 3, 4, 5, 6]
+ ary.fill('x', 2, 3).should == [1, 2, 'x', 'x', 'x', 6]
+ end
+
+ it "replaces length elements beginning with the index with the value of block" do
+ [true, false, true, false, true, false, true].fill(1, 4) { |i| i + 3 }.should == [true, 4, 5, 6, 7, false, true]
+ end
+
+ it "replaces all elements after the index if given an index and no length" do
+ ary = [1, 2, 3]
+ ary.fill('x', 1).should == [1, 'x', 'x']
+ ary.fill(1){|i| i*2}.should == [1, 2, 4]
+ end
+
+ it "replaces all elements after the index if given an index and nil as a length" do
+ a = [1, 2, 3]
+ a.fill('x', 1, nil).should == [1, 'x', 'x']
+ a.fill(1, nil){|i| i*2}.should == [1, 2, 4]
+ a.fill('y', nil).should == ['y', 'y', 'y']
+ end
+
+ it "replaces the last (-n) elements if given an index n which is negative and no length" do
+ a = [1, 2, 3, 4, 5]
+ a.fill('x', -2).should == [1, 2, 3, 'x', 'x']
+ a.fill(-2){|i| i.to_s}.should == [1, 2, 3, '3', '4']
+ end
+
+ it "replaces the last (-n) elements if given an index n which is negative and nil as a length" do
+ a = [1, 2, 3, 4, 5]
+ a.fill('x', -2, nil).should == [1, 2, 3, 'x', 'x']
+ a.fill(-2, nil){|i| i.to_s}.should == [1, 2, 3, '3', '4']
+ end
+
+ it "makes no modifications if given an index greater than end and no length" do
+ [1, 2, 3, 4, 5].fill('a', 5).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(5, &@never_passed).should == [1, 2, 3, 4, 5]
+ end
+
+ it "makes no modifications if given an index greater than end and nil as a length" do
+ [1, 2, 3, 4, 5].fill('a', 5, nil).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(5, nil, &@never_passed).should == [1, 2, 3, 4, 5]
+ end
+
+ it "replaces length elements beginning with start index if given an index >= 0 and a length >= 0" do
+ [1, 2, 3, 4, 5].fill('a', 2, 0).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill('a', 2, 2).should == [1, 2, "a", "a", 5]
+
+ [1, 2, 3, 4, 5].fill(2, 0, &@never_passed).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(2, 2){|i| i*2}.should == [1, 2, 4, 6, 5]
+ end
+
+ it "increases the Array size when necessary" do
+ a = [1, 2, 3]
+ a.size.should == 3
+ a.fill 'a', 0, 10
+ a.size.should == 10
+ end
+
+ it "pads between the last element and the index with nil if given an index which is greater than size of the array" do
+ [1, 2, 3, 4, 5].fill('a', 8, 5).should == [1, 2, 3, 4, 5, nil, nil, nil, 'a', 'a', 'a', 'a', 'a']
+ [1, 2, 3, 4, 5].fill(8, 5){|i| 'a'}.should == [1, 2, 3, 4, 5, nil, nil, nil, 'a', 'a', 'a', 'a', 'a']
+ end
+
+ it "replaces length elements beginning with the (-n)th if given an index n < 0 and a length > 0" do
+ [1, 2, 3, 4, 5].fill('a', -2, 2).should == [1, 2, 3, "a", "a"]
+ [1, 2, 3, 4, 5].fill('a', -2, 4).should == [1, 2, 3, "a", "a", "a", "a"]
+
+ [1, 2, 3, 4, 5].fill(-2, 2){|i| 'a'}.should == [1, 2, 3, "a", "a"]
+ [1, 2, 3, 4, 5].fill(-2, 4){|i| 'a'}.should == [1, 2, 3, "a", "a", "a", "a"]
+ end
+
+ it "starts at 0 if the negative index is before the start of the array" do
+ [1, 2, 3, 4, 5].fill('a', -25, 3).should == ['a', 'a', 'a', 4, 5]
+ [1, 2, 3, 4, 5].fill('a', -10, 10).should == %w|a a a a a a a a a a|
+
+ [1, 2, 3, 4, 5].fill(-25, 3){|i| 'a'}.should == ['a', 'a', 'a', 4, 5]
+ [1, 2, 3, 4, 5].fill(-10, 10){|i| 'a'}.should == %w|a a a a a a a a a a|
+ end
+
+ it "makes no modifications if the given length <= 0" do
+ [1, 2, 3, 4, 5].fill('a', 2, 0).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill('a', -2, 0).should == [1, 2, 3, 4, 5]
+
+ [1, 2, 3, 4, 5].fill('a', 2, -2).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill('a', -2, -2).should == [1, 2, 3, 4, 5]
+
+ [1, 2, 3, 4, 5].fill(2, 0, &@never_passed).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(-2, 0, &@never_passed).should == [1, 2, 3, 4, 5]
+
+ [1, 2, 3, 4, 5].fill(2, -2, &@never_passed).should == [1, 2, 3, 4, 5]
+ [1, 2, 3, 4, 5].fill(-2, -2, &@never_passed).should == [1, 2, 3, 4, 5]
+ end
+
+ # See: https://blade.ruby-lang.org/ruby-core/17481
+ it "does not raise an exception if the given length is negative and its absolute value does not exceed the index" do
+ [1, 2, 3, 4].fill('a', 3, -1).should == [1, 2, 3, 4]
+ [1, 2, 3, 4].fill('a', 3, -2).should == [1, 2, 3, 4]
+ [1, 2, 3, 4].fill('a', 3, -3).should == [1, 2, 3, 4]
+
+ [1, 2, 3, 4].fill(3, -1, &@never_passed).should == [1, 2, 3, 4]
+ [1, 2, 3, 4].fill(3, -2, &@never_passed).should == [1, 2, 3, 4]
+ [1, 2, 3, 4].fill(3, -3, &@never_passed).should == [1, 2, 3, 4]
+ end
+
+ it "does not raise an exception even if the given length is negative and its absolute value exceeds the index" do
+ [1, 2, 3, 4].fill('a', 3, -4).should == [1, 2, 3, 4]
+ [1, 2, 3, 4].fill('a', 3, -5).should == [1, 2, 3, 4]
+ [1, 2, 3, 4].fill('a', 3, -10000).should == [1, 2, 3, 4]
+
+ [1, 2, 3, 4].fill(3, -4, &@never_passed).should == [1, 2, 3, 4]
+ [1, 2, 3, 4].fill(3, -5, &@never_passed).should == [1, 2, 3, 4]
+ [1, 2, 3, 4].fill(3, -10000, &@never_passed).should == [1, 2, 3, 4]
+ end
+
+ it "tries to convert the second and third arguments to Integers using #to_int" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2, 2)
+ filler = mock('filler')
+ filler.should_not_receive(:to_int)
+ [1, 2, 3, 4, 5].fill(filler, obj, obj).should == [1, 2, filler, filler, 5]
+ end
+
+ it "raises a TypeError if the index is not numeric" do
+ -> { [].fill 'a', true }.should.raise(TypeError)
+
+ obj = mock('nonnumeric')
+ -> { [].fill('a', obj) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when the length is not numeric" do
+ -> { [1, 2, 3].fill("x", 1, "foo") }.should.raise(TypeError, /no implicit conversion of String into Integer/)
+ -> { [1, 2, 3].fill("x", 1, :"foo") }.should.raise(TypeError, /no implicit conversion of Symbol into Integer/)
+ -> { [1, 2, 3].fill("x", 1, Object.new) }.should.raise(TypeError, /no implicit conversion of Object into Integer/)
+ end
+
+ not_supported_on :opal do
+ it "raises an ArgumentError or RangeError for too-large sizes" do
+ error_types = [RangeError, ArgumentError]
+ arr = [1, 2, 3]
+ -> { arr.fill(10, 1, fixnum_max) }.should.raise { |err| error_types.should.include?(err.class) }
+ -> { arr.fill(10, 1, bignum_value) }.should.raise(RangeError)
+ end
+ end
+end
+
+describe "Array#fill with (filler, range)" do
+ it "replaces elements in range with object" do
+ [1, 2, 3, 4, 5, 6].fill(8, 0..3).should == [8, 8, 8, 8, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(8, 0...3).should == [8, 8, 8, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 4..6).should == [1, 2, 3, 4, 'x', 'x', 'x']
+ [1, 2, 3, 4, 5, 6].fill('x', 4...6).should == [1, 2, 3, 4, 'x', 'x']
+ [1, 2, 3, 4, 5, 6].fill('x', -2..-1).should == [1, 2, 3, 4, 'x', 'x']
+ [1, 2, 3, 4, 5, 6].fill('x', -2...-1).should == [1, 2, 3, 4, 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -2...-2).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -2..-2).should == [1, 2, 3, 4, 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -2..0).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 0...0).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 1..1).should == [1, 'x', 3, 4, 5, 6]
+ end
+
+ it "replaces all elements in range with the value of block" do
+ [1, 1, 1, 1, 1, 1].fill(1..6) { |i| i + 1 }.should == [1, 2, 3, 4, 5, 6, 7]
+ end
+
+ it "increases the Array size when necessary" do
+ [1, 2, 3].fill('x', 1..6).should == [1, 'x', 'x', 'x', 'x', 'x', 'x']
+ [1, 2, 3].fill(1..6){|i| i+1}.should == [1, 2, 3, 4, 5, 6, 7]
+ end
+
+ it "raises a TypeError with range and length argument" do
+ -> { [].fill('x', 0 .. 2, 5) }.should.raise(TypeError)
+ end
+
+ it "replaces elements between the (-m)th to the last and the (n+1)th from the first if given an range m..n where m < 0 and n >= 0" do
+ [1, 2, 3, 4, 5, 6].fill('x', -4..4).should == [1, 2, 'x', 'x', 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4...4).should == [1, 2, 'x', 'x', 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(-4..4){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6]
+ [1, 2, 3, 4, 5, 6].fill(-4...4){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6]
+ end
+
+ it "replaces elements between the (-m)th and (-n)th to the last if given an range m..n where m < 0 and n < 0" do
+ [1, 2, 3, 4, 5, 6].fill('x', -4..-2).should == [1, 2, 'x', 'x', 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4...-2).should == [1, 2, 'x', 'x', 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(-4..-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6]
+ [1, 2, 3, 4, 5, 6].fill(-4...-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6]
+ end
+
+ it "replaces elements between the (m+1)th from the first and (-n)th to the last if given an range m..n where m >= 0 and n < 0" do
+ [1, 2, 3, 4, 5, 6].fill('x', 2..-2).should == [1, 2, 'x', 'x', 'x', 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 2...-2).should == [1, 2, 'x', 'x', 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(2..-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', '5', 6]
+ [1, 2, 3, 4, 5, 6].fill(2...-2){|i| (i+1).to_s}.should == [1, 2, '3', '4', 5, 6]
+ end
+
+ it "makes no modifications if given an range which implies a section of zero width" do
+ [1, 2, 3, 4, 5, 6].fill('x', 2...2).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4...2).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4...-4).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 2...-4).should == [1, 2, 3, 4, 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(2...2, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(-4...2, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(-4...-4, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(2...-4, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "makes no modifications if given an range which implies a section of negative width" do
+ [1, 2, 3, 4, 5, 6].fill('x', 2..1).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -4..1).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', -2..-4).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill('x', 2..-5).should == [1, 2, 3, 4, 5, 6]
+
+ [1, 2, 3, 4, 5, 6].fill(2..1, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(-4..1, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(-2..-4, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ [1, 2, 3, 4, 5, 6].fill(2..-5, &@never_passed).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "raises an exception if some of the given range lies before the first of the array" do
+ -> { [1, 2, 3].fill('x', -5..-3) }.should.raise(RangeError)
+ -> { [1, 2, 3].fill('x', -5...-3) }.should.raise(RangeError)
+ -> { [1, 2, 3].fill('x', -5..-4) }.should.raise(RangeError)
+
+ -> { [1, 2, 3].fill(-5..-3, &@never_passed) }.should.raise(RangeError)
+ -> { [1, 2, 3].fill(-5...-3, &@never_passed) }.should.raise(RangeError)
+ -> { [1, 2, 3].fill(-5..-4, &@never_passed) }.should.raise(RangeError)
+ end
+
+ it "tries to convert the start and end of the passed range to Integers using #to_int" do
+ obj = mock('to_int')
+ def obj.<=>(rhs); rhs == self ? 0 : nil end
+ obj.should_receive(:to_int).twice.and_return(2)
+ filler = mock('filler')
+ filler.should_not_receive(:to_int)
+ [1, 2, 3, 4, 5].fill(filler, obj..obj).should == [1, 2, filler, 4, 5]
+ end
+
+ it "raises a TypeError if the start or end of the passed range is not numeric" do
+ obj = mock('nonnumeric')
+ def obj.<=>(rhs); rhs == self ? 0 : nil end
+ -> { [].fill('a', obj..obj) }.should.raise(TypeError)
+ end
+
+ it "works with endless ranges" do
+ [1, 2, 3, 4].fill('x', eval("(1..)")).should == [1, 'x', 'x', 'x']
+ [1, 2, 3, 4].fill('x', eval("(3...)")).should == [1, 2, 3, 'x']
+ [1, 2, 3, 4].fill(eval("(1..)")) { |x| x + 2 }.should == [1, 3, 4, 5]
+ [1, 2, 3, 4].fill(eval("(3...)")) { |x| x + 2 }.should == [1, 2, 3, 5]
+ end
+
+ it "works with beginless ranges" do
+ [1, 2, 3, 4].fill('x', (..2)).should == ["x", "x", "x", 4]
+ [1, 2, 3, 4].fill((...2)) { |x| x + 2 }.should == [2, 3, 3, 4]
+ end
+end
diff --git a/spec/ruby/core/array/filter_spec.rb b/spec/ruby/core/array/filter_spec.rb
new file mode 100644
index 0000000000..6ebda61069
--- /dev/null
+++ b/spec/ruby/core/array/filter_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Array#filter" do
+ it "is an alias of Array#select" do
+ Array.instance_method(:filter).should == Array.instance_method(:select)
+ end
+end
+
+describe "Array#filter!" do
+ it "is an alias of Array#select!" do
+ Array.instance_method(:filter!).should == Array.instance_method(:select!)
+ end
+end
diff --git a/spec/ruby/core/array/find_index_spec.rb b/spec/ruby/core/array/find_index_spec.rb
new file mode 100644
index 0000000000..17ff6c04a7
--- /dev/null
+++ b/spec/ruby/core/array/find_index_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#find_index" do
+ it "returns the index of the first element == to object" do
+ x = mock('3')
+ def x.==(obj) 3 == obj; end
+
+ [2, x, 3, 1, 3, 1].find_index(3).should == 1
+ [2, 3.0, 3, x, 1, 3, 1].find_index(x).should == 1
+ end
+
+ it "returns 0 if first element == to object" do
+ [2, 1, 3, 2, 5].find_index(2).should == 0
+ end
+
+ it "returns size-1 if only last element == to object" do
+ [2, 1, 3, 1, 5].find_index(5).should == 4
+ end
+
+ it "returns nil if no element == to object" do
+ [2, 1, 1, 1, 1].find_index(3).should == nil
+ end
+
+ it "accepts a block instead of an argument" do
+ [4, 2, 1, 5, 1, 3].find_index {|x| x < 2}.should == 2
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ [4, 2, 1, 5, 1, 3].find_index(5) {|x| x < 2}.should == 3
+ }.should complain(/given block not used/)
+ end
+
+ describe "given no argument and no block" do
+ it "produces an Enumerator" do
+ [].find_index.should.instance_of?(Enumerator)
+ end
+ end
+
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :find_index
+end
diff --git a/spec/ruby/core/array/first_spec.rb b/spec/ruby/core/array/first_spec.rb
new file mode 100644
index 0000000000..2c343ac8f6
--- /dev/null
+++ b/spec/ruby/core/array/first_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#first" do
+ it "returns the first element" do
+ %w{a b c}.first.should == 'a'
+ [nil].first.should == nil
+ end
+
+ it "returns nil if self is empty" do
+ [].first.should == nil
+ end
+
+ it "returns the first count elements if given a count" do
+ [true, false, true, nil, false].first(2).should == [true, false]
+ end
+
+ it "returns an empty array when passed count on an empty array" do
+ [].first(0).should == []
+ [].first(1).should == []
+ [].first(2).should == []
+ end
+
+ it "returns an empty array when passed count == 0" do
+ [1, 2, 3, 4, 5].first(0).should == []
+ end
+
+ it "returns an array containing the first element when passed count == 1" do
+ [1, 2, 3, 4, 5].first(1).should == [1]
+ end
+
+ it "raises an ArgumentError when count is negative" do
+ -> { [1, 2].first(-1) }.should.raise(ArgumentError)
+ end
+
+ it "raises a RangeError when count is a Bignum" do
+ -> { [].first(bignum_value) }.should.raise(RangeError)
+ end
+
+ it "returns the entire array when count > length" do
+ [1, 2, 3, 4, 5, 9].first(10).should == [1, 2, 3, 4, 5, 9]
+ end
+
+ it "returns an array which is independent to the original when passed count" do
+ ary = [1, 2, 3, 4, 5]
+ ary.first(0).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ ary.first(1).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ ary.first(6).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.first.should.equal?(empty)
+
+ ary = ArraySpecs.head_recursive_array
+ ary.first.should.equal?(ary)
+ 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)
+ [1, 2, 3, 4, 5].first(obj).should == [1, 2]
+ end
+
+ it "raises a TypeError if the passed argument is not numeric" do
+ -> { [1,2].first(nil) }.should.raise(TypeError)
+ -> { [1,2].first("a") }.should.raise(TypeError)
+
+ obj = mock("nonnumeric")
+ -> { [1,2].first(obj) }.should.raise(TypeError)
+ end
+
+ it "does not return subclass instance when passed count on Array subclasses" do
+ ArraySpecs::MyArray[].first(0).should.instance_of?(Array)
+ ArraySpecs::MyArray[].first(2).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].first(0).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].first(1).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].first(2).should.instance_of?(Array)
+ end
+
+ it "is not destructive" do
+ a = [1, 2, 3]
+ a.first
+ a.should == [1, 2, 3]
+ a.first(2)
+ a.should == [1, 2, 3]
+ a.first(3)
+ a.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/array/fixtures/classes.rb b/spec/ruby/core/array/fixtures/classes.rb
new file mode 100644
index 0000000000..05283c0f74
--- /dev/null
+++ b/spec/ruby/core/array/fixtures/classes.rb
@@ -0,0 +1,592 @@
+class Object
+ # This helper is defined here rather than in MSpec because
+ # it is only used in #pack specs.
+ def pack_format(count=nil, repeat=nil)
+ format = instance_variable_get(:@method)
+ format += count.to_s unless format == 'P' || format == 'p'
+ format *= repeat if repeat
+ format.dup # because it may then become tainted
+ end
+end
+
+module ArraySpecs
+ SampleRange = 0..1000
+ SampleCount = 1000
+
+ def self.frozen_array
+ [1,2,3].freeze
+ end
+
+ def self.empty_frozen_array
+ [].freeze
+ end
+
+ def self.recursive_array
+ a = [1, 'two', 3.0]
+ 5.times { a << a }
+ a
+ end
+
+ def self.head_recursive_array
+ a = []
+ 5.times { a << a }
+ a << 1 << 'two' << 3.0
+ a
+ end
+
+ def self.empty_recursive_array
+ a = []
+ a << a
+ a
+ end
+
+ # Chi squared critical values for tests with n degrees of freedom at 99% confidence.
+ # Values obtained from NIST Engineering Statistic Handbook at
+ # https://www.itl.nist.gov/div898/handbook/eda/section3/eda3674.htm
+
+ CHI_SQUARED_CRITICAL_VALUES = [
+ 0,
+ 6.635, 9.210, 11.345, 13.277, 15.086, 16.812, 18.475, 20.090, 21.666, 23.209,
+ 24.725, 26.217, 27.688, 29.141, 30.578, 32.000, 33.409, 34.805, 36.191, 37.566,
+ 38.932, 40.289, 41.638, 42.980, 44.314, 45.642, 46.963, 48.278, 49.588, 50.892,
+ 52.191, 53.486, 54.776, 56.061, 57.342, 58.619, 59.893, 61.162, 62.428, 63.691,
+ 64.950, 66.206, 67.459, 68.710, 69.957, 71.201, 72.443, 73.683, 74.919, 76.154,
+ 77.386, 78.616, 79.843, 81.069, 82.292, 83.513, 84.733, 85.950, 87.166, 88.379,
+ 89.591, 90.802, 92.010, 93.217, 94.422, 95.626, 96.828, 98.028, 99.228, 100.425,
+ 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)
+ iters.times do
+ x = ary.sample(samples)[i]
+ counts[x] += 1
+ end
+ chi_squared = counts.sum {|count| (count - expected) ** 2} / expected
+ chi_results << chi_squared
+ break if chi_squared <= CHI_SQUARED_CRITICAL_VALUES[size]
+ end
+
+ chi_results.min.should <= CHI_SQUARED_CRITICAL_VALUES[size]
+ end
+ end
+
+ 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).fdiv size
+ iters.times do
+ ary.sample(samples).each do |sample|
+ counts[sample] += 1
+ end
+ 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
+ # https://www.itl.nist.gov/div898/handbook/eda/section3/eda3674.htm
+
+ chi_squared.should <= CHI_SQUARED_CRITICAL_VALUES[size]
+ end
+
+ class MyArray < Array
+ # The #initialize method has a different signature than Array to help
+ # catch places in the specs that do not assert the #initialize is not
+ # called when Array methods make new instances.
+ def initialize(a, b)
+ self << a << b
+ ScratchPad.record :my_array_initialize
+ end
+ end
+
+ class Sexp < Array
+ def initialize(*args)
+ super(args)
+ end
+ end
+
+ # TODO: replace specs that use this with #should_not_receive(:to_ary)
+ # expectations on regular objects (e.g. Array instances).
+ class ToAryArray < Array
+ def to_ary() ["to_ary", "was", "called!"] end
+ end
+
+ class MyRange < Range; end
+
+ class AssocKey
+ def ==(other); other == 'it'; end
+ end
+
+ class D
+ def <=>(obj)
+ return 4 <=> obj unless obj.class == D
+ 0
+ end
+ end
+
+ class SubArray < Array
+ def initialize(*args)
+ ScratchPad.record args
+ end
+ end
+
+ class ArrayConvertible
+ attr_accessor :called
+ def initialize(*values, &block)
+ @values = values;
+ end
+
+ def to_a
+ self.called = :to_a
+ @values
+ end
+
+ def to_ary
+ self.called = :to_ary
+ @values
+ end
+ end
+
+ class ArrayMethodMissing
+ def initialize(*values, &block)
+ @values = values;
+ end
+
+ def method_missing(name, *args)
+ @values
+ end
+ end
+
+ class SortSame
+ def <=>(other); 0; end
+ def ==(other); true; end
+ end
+
+ class UFOSceptic
+ def <=>(other); raise "N-uh, UFO:s do not exist!"; end
+ end
+
+ class MockForCompared
+ @@count = 0
+ @@compared = false
+ def initialize
+ @@compared = false
+ @order = (@@count += 1)
+ end
+ def <=>(rhs)
+ @@compared = true
+ return rhs.order <=> self.order
+ end
+ def self.compared?
+ @@compared
+ end
+
+ protected
+ attr_accessor :order
+ end
+
+ class ComparableWithInteger
+ include Comparable
+ def initialize(num)
+ @num = num
+ end
+
+ def <=>(fixnum)
+ @num <=> fixnum
+ end
+ end
+
+ class Uncomparable
+ def <=>(obj)
+ nil
+ end
+ end
+
+ def self.universal_pack_object
+ obj = mock("string float int".freeze)
+ obj.stub!(:to_int).and_return(1)
+ obj.stub!(:to_str).and_return("1")
+ obj.stub!(:to_f).and_return(1.0)
+ 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",
+ ]
+
+ class PrivateToAry
+ private
+
+ def to_ary
+ [1, 2, 3]
+ end
+ end
+end
diff --git a/spec/ruby/core/array/fixtures/encoded_strings.rb b/spec/ruby/core/array/fixtures/encoded_strings.rb
new file mode 100644
index 0000000000..b5888d86ae
--- /dev/null
+++ b/spec/ruby/core/array/fixtures/encoded_strings.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+module ArraySpecs
+ def self.array_with_usascii_and_7bit_utf8_strings
+ [
+ 'foo'.dup.force_encoding('US-ASCII'),
+ 'bar'
+ ]
+ end
+
+ def self.array_with_usascii_and_utf8_strings
+ [
+ 'foo'.dup.force_encoding('US-ASCII'),
+ 'báz'
+ ]
+ end
+
+ def self.array_with_7bit_utf8_and_usascii_strings
+ [
+ 'bar',
+ 'foo'.dup.force_encoding('US-ASCII')
+ ]
+ end
+
+ def self.array_with_utf8_and_usascii_strings
+ [
+ 'báz',
+ 'bar',
+ 'foo'.dup.force_encoding('US-ASCII')
+ ]
+ end
+
+ def self.array_with_usascii_and_utf8_strings
+ [
+ 'foo'.dup.force_encoding('US-ASCII'),
+ 'bar',
+ 'báz'
+ ]
+ end
+
+ def self.array_with_utf8_and_7bit_binary_strings
+ [
+ 'bar',
+ 'báz',
+ 'foo'.dup.force_encoding('BINARY')
+ ]
+ end
+
+ def self.array_with_utf8_and_binary_strings
+ [
+ 'bar',
+ 'báz',
+ [255].pack('C').force_encoding('BINARY')
+ ]
+ end
+
+ def self.array_with_usascii_and_7bit_binary_strings
+ [
+ 'bar'.dup.force_encoding('US-ASCII'),
+ 'foo'.dup.force_encoding('BINARY')
+ ]
+ end
+
+ def self.array_with_usascii_and_binary_strings
+ [
+ 'bar'.dup.force_encoding('US-ASCII'),
+ [255].pack('C').force_encoding('BINARY')
+ ]
+ end
+end
diff --git a/spec/ruby/core/array/flatten_spec.rb b/spec/ruby/core/array/flatten_spec.rb
new file mode 100644
index 0000000000..272406b8f9
--- /dev/null
+++ b/spec/ruby/core/array/flatten_spec.rb
@@ -0,0 +1,266 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#flatten" do
+ it "returns a one-dimensional flattening recursively" do
+ [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []].flatten.should == [1, 2, 3, 2, 3, 4, 4, 5, 5, 1, 2, 3, 4]
+ end
+
+ it "takes an optional argument that determines the level of recursion" do
+ [ 1, 2, [3, [4, 5] ] ].flatten(1).should == [1, 2, 3, [4, 5]]
+ end
+
+ it "returns dup when the level of recursion is 0" do
+ a = [ 1, 2, [3, [4, 5] ] ]
+ a.flatten(0).should == a
+ a.flatten(0).should_not.equal?(a)
+ end
+
+ it "ignores negative levels" do
+ [ 1, 2, [ 3, 4, [5, 6] ] ].flatten(-1).should == [1, 2, 3, 4, 5, 6]
+ [ 1, 2, [ 3, 4, [5, 6] ] ].flatten(-10).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "tries to convert passed Objects to Integers using #to_int" do
+ obj = mock("Converted to Integer")
+ obj.should_receive(:to_int).and_return(1)
+
+ [ 1, 2, [3, [4, 5] ] ].flatten(obj).should == [1, 2, 3, [4, 5]]
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to an Integer" do
+ obj = mock("Not converted")
+ -> { [ 1, 2, [3, [4, 5] ] ].flatten(obj) }.should.raise(TypeError)
+ end
+
+ it "does not call flatten on elements" do
+ obj = mock('[1,2]')
+ obj.should_not_receive(:flatten)
+ [obj, obj].flatten.should == [obj, obj]
+
+ obj = [5, 4]
+ obj.should_not_receive(:flatten)
+ [obj, obj].flatten.should == [5, 4, 5, 4]
+ end
+
+ it "raises an ArgumentError on recursive arrays" do
+ x = []
+ x << x
+ -> { x.flatten }.should.raise(ArgumentError)
+
+ x = []
+ y = []
+ x << y
+ y << x
+ -> { x.flatten }.should.raise(ArgumentError)
+ end
+
+ it "flattens any element which responds to #to_ary, using the return value of said method" do
+ x = mock("[3,4]")
+ x.should_receive(:to_ary).at_least(:once).and_return([3, 4])
+ [1, 2, x, 5].flatten.should == [1, 2, 3, 4, 5]
+
+ y = mock("MyArray[]")
+ y.should_receive(:to_ary).at_least(:once).and_return(ArraySpecs::MyArray[])
+ [y].flatten.should == []
+
+ z = mock("[2,x,y,5]")
+ z.should_receive(:to_ary).and_return([2, x, y, 5])
+ [1, z, 6].flatten.should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "does not call #to_ary on elements beyond the given level" do
+ obj = mock("1")
+ obj.should_not_receive(:to_ary)
+ [[obj]].flatten(1)
+ end
+
+ it "returns Array instance for Array subclasses" do
+ ArraySpecs::MyArray[].flatten.should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].flatten.should.instance_of?(Array)
+ ArraySpecs::MyArray[1, [2], 3].flatten.should.instance_of?(Array)
+ ArraySpecs::MyArray[1, [2, 3], 4].flatten.should == [1, 2, 3, 4]
+ [ArraySpecs::MyArray[1, 2, 3]].flatten.should.instance_of?(Array)
+ end
+
+ it "is not destructive" do
+ ary = [1, [2, 3]]
+ ary.flatten
+ ary.should == [1, [2, 3]]
+ end
+
+ describe "with a non-Array object in the Array" do
+ before :each do
+ @obj = mock("Array#flatten")
+ ScratchPad.record []
+ end
+
+ it "does not call #to_ary if the method is not defined" do
+ [@obj].flatten.should == [@obj]
+ end
+
+ it "does not raise an exception if #to_ary returns nil" do
+ @obj.should_receive(:to_ary).and_return(nil)
+ [@obj].flatten.should == [@obj]
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ @obj.should_receive(:to_ary).and_return(1)
+ -> { [@obj].flatten }.should.raise(TypeError)
+ end
+
+ it "calls respond_to_missing?(:to_ary, true) to try coercing" do
+ def @obj.respond_to_missing?(*args) ScratchPad << args; false end
+ [@obj].flatten.should == [@obj]
+ ScratchPad.recorded.should == [[:to_ary, true]]
+ end
+
+ it "does not call #to_ary if not defined when #respond_to_missing? returns false" do
+ def @obj.respond_to_missing?(name, priv) ScratchPad << name; false end
+
+ [@obj].flatten.should == [@obj]
+ ScratchPad.recorded.should == [:to_ary]
+ end
+
+ it "calls #to_ary if not defined when #respond_to_missing? returns true" do
+ def @obj.respond_to_missing?(name, priv) ScratchPad << name; true end
+
+ -> { [@obj].flatten }.should.raise(NoMethodError)
+ ScratchPad.recorded.should == [:to_ary]
+ end
+
+ it "calls #method_missing if defined" do
+ @obj.should_receive(:method_missing).with(:to_ary).and_return([1, 2, 3])
+ [@obj].flatten.should == [1, 2, 3]
+ end
+ end
+
+ it "performs respond_to? and method_missing-aware checks when coercing elements to array" do
+ bo = BasicObject.new
+ [bo].flatten.should == [bo]
+
+ def bo.method_missing(name, *)
+ [1,2]
+ end
+
+ [bo].flatten.should == [1,2]
+
+ def bo.respond_to?(name, *)
+ false
+ end
+
+ [bo].flatten.should == [bo]
+
+ def bo.respond_to?(name, *)
+ true
+ end
+
+ [bo].flatten.should == [1,2]
+ end
+end
+
+describe "Array#flatten!" do
+ it "modifies array to produce a one-dimensional flattening recursively" do
+ a = [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []]
+ a.flatten!
+ a.should == [1, 2, 3, 2, 3, 4, 4, 5, 5, 1, 2, 3, 4]
+ end
+
+ it "returns self if made some modifications" do
+ a = [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []]
+ a.flatten!.should.equal?(a)
+ end
+
+ it "returns nil if no modifications took place" do
+ a = [1, 2, 3]
+ a.flatten!.should == nil
+ a = [1, [2, 3]]
+ a.flatten!.should_not == nil
+ end
+
+ it "should not check modification by size" do
+ a = [1, 2, [3]]
+ a.flatten!.should_not == nil
+ a.should == [1, 2, 3]
+ end
+
+ it "takes an optional argument that determines the level of recursion" do
+ [ 1, 2, [3, [4, 5] ] ].flatten!(1).should == [1, 2, 3, [4, 5]]
+ end
+
+ # redmine #1440
+ it "returns nil when the level of recursion is 0" do
+ a = [ 1, 2, [3, [4, 5] ] ]
+ a.flatten!(0).should == nil
+ end
+
+ it "treats negative levels as no arguments" do
+ [ 1, 2, [ 3, 4, [5, 6] ] ].flatten!(-1).should == [1, 2, 3, 4, 5, 6]
+ [ 1, 2, [ 3, 4, [5, 6] ] ].flatten!(-10).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "tries to convert passed Objects to Integers using #to_int" do
+ obj = mock("Converted to Integer")
+ obj.should_receive(:to_int).and_return(1)
+
+ [ 1, 2, [3, [4, 5] ] ].flatten!(obj).should == [1, 2, 3, [4, 5]]
+ end
+
+ it "raises a TypeError when the passed Object can't be converted to an Integer" do
+ obj = mock("Not converted")
+ -> { [ 1, 2, [3, [4, 5] ] ].flatten!(obj) }.should.raise(TypeError)
+ end
+
+ it "does not call flatten! on elements" do
+ obj = mock('[1,2]')
+ obj.should_not_receive(:flatten!)
+ [obj, obj].flatten!.should == nil
+
+ obj = [5, 4]
+ obj.should_not_receive(:flatten!)
+ [obj, obj].flatten!.should == [5, 4, 5, 4]
+ end
+
+ it "raises an ArgumentError on recursive arrays" do
+ x = []
+ x << x
+ -> { x.flatten! }.should.raise(ArgumentError)
+
+ x = []
+ y = []
+ x << y
+ y << x
+ -> { x.flatten! }.should.raise(ArgumentError)
+ end
+
+ it "flattens any elements which responds to #to_ary, using the return value of said method" do
+ x = mock("[3,4]")
+ x.should_receive(:to_ary).at_least(:once).and_return([3, 4])
+ [1, 2, x, 5].flatten!.should == [1, 2, 3, 4, 5]
+
+ y = mock("MyArray[]")
+ y.should_receive(:to_ary).at_least(:once).and_return(ArraySpecs::MyArray[])
+ [y].flatten!.should == []
+
+ z = mock("[2,x,y,5]")
+ z.should_receive(:to_ary).and_return([2, x, y, 5])
+ [1, z, 6].flatten!.should == [1, 2, 3, 4, 5, 6]
+
+ ary = [ArraySpecs::MyArray[1, 2, 3]]
+ ary.flatten!
+ ary.should.instance_of?(Array)
+ ary.should == [1, 2, 3]
+ end
+
+ it "raises a FrozenError on frozen arrays when the array is modified" do
+ nested_ary = [1, 2, []]
+ nested_ary.freeze
+ -> { nested_ary.flatten! }.should.raise(FrozenError)
+ end
+
+ # see [ruby-core:23663]
+ it "raises a FrozenError on frozen arrays when the array would not be modified" do
+ -> { ArraySpecs.frozen_array.flatten! }.should.raise(FrozenError)
+ -> { ArraySpecs.empty_frozen_array.flatten! }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/frozen_spec.rb b/spec/ruby/core/array/frozen_spec.rb
new file mode 100644
index 0000000000..3ba54be46b
--- /dev/null
+++ b/spec/ruby/core/array/frozen_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#frozen?" do
+ it "returns true if array is frozen" do
+ a = [1, 2, 3]
+ a.should_not.frozen?
+ a.freeze
+ a.should.frozen?
+ end
+
+ it "returns false for an array being sorted by #sort" do
+ a = [1, 2, 3]
+ a.sort { |x,y| a.should_not.frozen?; x <=> y }
+ end
+end
diff --git a/spec/ruby/core/array/hash_spec.rb b/spec/ruby/core/array/hash_spec.rb
new file mode 100644
index 0000000000..3b7b6d5bed
--- /dev/null
+++ b/spec/ruby/core/array/hash_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#hash" do
+ it "returns the same fixnum for arrays with the same content" do
+ [].respond_to?(:hash).should == true
+
+ [[], [1, 2, 3]].each do |ary|
+ ary.hash.should == ary.dup.hash
+ ary.hash.should.instance_of?(Integer)
+ end
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ -> { empty.hash }.should_not.raise
+
+ array = ArraySpecs.recursive_array
+ -> { array.hash }.should_not.raise
+ end
+
+ it "returns the same hash for equal recursive arrays" do
+ rec = []; rec << rec
+ rec.hash.should == [rec].hash
+ rec.hash.should == [[rec]].hash
+ # This is because rec.eql?([[rec]])
+ # Remember that if two objects are eql?
+ # then the need to have the same hash
+ # Check the Array#eql? specs!
+ end
+
+ it "returns the same hash for equal recursive arrays through hashes" do
+ h = {} ; rec = [h] ; h[:x] = rec
+ rec.hash.should == [h].hash
+ rec.hash.should == [{x: rec}].hash
+ # Like above, this is because rec.eql?([{x: rec}])
+ end
+
+ it "calls to_int on result of calling hash on each element" do
+ ary = Array.new(5) do
+ obj = mock('0')
+ obj.should_receive(:hash).and_return(obj)
+ obj.should_receive(:to_int).and_return(0)
+ obj
+ end
+
+ ary.hash
+
+
+ hash = mock('1')
+ hash.should_receive(:to_int).and_return(1.hash)
+
+ obj = mock('@hash')
+ obj.instance_variable_set(:@hash, hash)
+ def obj.hash() @hash end
+
+ [obj].hash.should == [1].hash
+ end
+
+ it "ignores array class differences" do
+ ArraySpecs::MyArray[].hash.should == [].hash
+ ArraySpecs::MyArray[1, 2].hash.should == [1, 2].hash
+ end
+
+ it "returns same hash code for arrays with the same content" do
+ a = [1, 2, 3, 4]
+ a.fill 'a', 0..3
+ b = %w|a a a a|
+ a.hash.should == b.hash
+ end
+
+ it "returns the same value if arrays are #eql?" do
+ a = [1, 2, 3, 4]
+ a.fill 'a', 0..3
+ b = %w|a a a a|
+ a.hash.should == b.hash
+ a.should.eql?(b)
+ end
+
+ it "produces different hashes for nested arrays with different values and empty terminator" do
+ [1, [1, []]].hash.should_not == [2, [2, []]].hash
+ end
+end
diff --git a/spec/ruby/core/array/include_spec.rb b/spec/ruby/core/array/include_spec.rb
new file mode 100644
index 0000000000..227173218f
--- /dev/null
+++ b/spec/ruby/core/array/include_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#include?" do
+ it "returns true if object is present, false otherwise" do
+ [1, 2, "a", "b"].include?("c").should == false
+ [1, 2, "a", "b"].include?("a").should == true
+ end
+
+ it "determines presence by using element == obj" do
+ o = mock('')
+
+ [1, 2, "a", "b"].include?(o).should == false
+
+ def o.==(other); other == 'a'; end
+
+ [1, 2, o, "b"].include?('a').should == true
+
+ [1, 2.0, 3].include?(2).should == true
+ end
+
+ it "calls == on elements from left to right until success" do
+ key = "x"
+ one = mock('one')
+ two = mock('two')
+ three = mock('three')
+ one.should_receive(:==).any_number_of_times.and_return(false)
+ two.should_receive(:==).any_number_of_times.and_return(true)
+ three.should_not_receive(:==)
+ ary = [one, two, three]
+ ary.include?(key).should == true
+ end
+end
diff --git a/spec/ruby/core/array/index_spec.rb b/spec/ruby/core/array/index_spec.rb
new file mode 100644
index 0000000000..b66cb6eb53
--- /dev/null
+++ b/spec/ruby/core/array/index_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Array#index" do
+ it "is an alias of Array#find_index" do
+ Array.instance_method(:index).should == Array.instance_method(:find_index)
+ end
+end
diff --git a/spec/ruby/core/array/initialize_spec.rb b/spec/ruby/core/array/initialize_spec.rb
new file mode 100644
index 0000000000..19ee37825e
--- /dev/null
+++ b/spec/ruby/core/array/initialize_spec.rb
@@ -0,0 +1,158 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#initialize" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is private" do
+ Array.private_instance_methods(false).should.include?(:initialize)
+ end
+
+ it "is called on subclasses" do
+ b = ArraySpecs::SubArray.new :size_or_array, :obj
+
+ b.should == []
+ ScratchPad.recorded.should == [:size_or_array, :obj]
+ end
+
+ it "preserves the object's identity even when changing its value" do
+ a = [1, 2, 3]
+ a.send(:initialize).should.equal?(a)
+ a.should_not == [1, 2, 3]
+ end
+
+ it "raises an ArgumentError if passed 3 or more arguments" do
+ -> do
+ [1, 2].send :initialize, 1, 'x', true
+ end.should.raise(ArgumentError)
+ -> do
+ [1, 2].send(:initialize, 1, 'x', true) {}
+ end.should.raise(ArgumentError)
+ end
+
+ it "raises a FrozenError on frozen arrays" do
+ -> do
+ ArraySpecs.frozen_array.send :initialize
+ end.should.raise(FrozenError)
+ -> do
+ ArraySpecs.frozen_array.send :initialize, ArraySpecs.frozen_array
+ end.should.raise(FrozenError)
+ end
+
+ it "calls #to_ary to convert the value to an array, even if it's private" do
+ a = ArraySpecs::PrivateToAry.new
+ [].send(:initialize, a).should == [1, 2, 3]
+ end
+end
+
+describe "Array#initialize with no arguments" do
+ it "makes the array empty" do
+ [1, 2, 3].send(:initialize).should.empty?
+ end
+
+ it "does not use the given block" do
+ -> {
+ -> { [1, 2, 3].send(:initialize) { raise } }.should_not.raise
+ }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true)
+ end
+end
+
+describe "Array#initialize with (array)" do
+ it "replaces self with the other array" do
+ b = [4, 5, 6]
+ [1, 2, 3].send(:initialize, b).should == b
+ end
+
+ it "does not use the given block" do
+ ->{ [1, 2, 3].send(:initialize) { raise } }.should_not.raise
+ end
+
+ it "calls #to_ary to convert the value to an array" do
+ a = mock("array")
+ a.should_receive(:to_ary).and_return([1, 2])
+ a.should_not_receive(:to_int)
+ [].send(:initialize, a).should == [1, 2]
+ end
+
+ it "does not call #to_ary on instances of Array or subclasses of Array" do
+ a = [1, 2]
+ a.should_not_receive(:to_ary)
+ [].send(:initialize, a).should == a
+ end
+
+ it "raises a TypeError if an Array type argument and a default object" do
+ -> { [].send(:initialize, [1, 2], 1) }.should.raise(TypeError)
+ end
+end
+
+describe "Array#initialize with (size, object=nil)" do
+ it "sets the array to size and fills with the object" do
+ a = []
+ obj = [3]
+ a.send(:initialize, 2, obj).should == [obj, obj]
+ a[0].should.equal?(obj)
+ a[1].should.equal?(obj)
+
+ b = []
+ b.send(:initialize, 3, 14).should == [14, 14, 14]
+ b.should == [14, 14, 14]
+ end
+
+ it "sets the array to size and fills with nil when object is omitted" do
+ [].send(:initialize, 3).should == [nil, nil, nil]
+ end
+
+ it "raises an ArgumentError if size is negative" do
+ -> { [].send(:initialize, -1, :a) }.should.raise(ArgumentError)
+ -> { [].send(:initialize, -1) }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if size is too large" do
+ -> { [].send(:initialize, fixnum_max+1) }.should.raise(ArgumentError)
+ end
+
+ it "calls #to_int to convert the size argument to an Integer when object is given" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ [].send(:initialize, obj, :a).should == [:a]
+ end
+
+ it "calls #to_int to convert the size argument to an Integer when object is not given" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ [].send(:initialize, obj).should == [nil]
+ end
+
+ it "raises a TypeError if the size argument is not an Integer type" do
+ obj = mock('nonnumeric')
+ obj.stub!(:to_ary).and_return([1, 2])
+ ->{ [].send(:initialize, obj, :a) }.should.raise(TypeError)
+ end
+
+ it "yields the index of the element and sets the element to the value of the block" do
+ [].send(:initialize, 3) { |i| i.to_s }.should == ['0', '1', '2']
+ end
+
+ it "uses the block value instead of using the default value" do
+ -> {
+ @result = [].send(:initialize, 3, :obj) { |i| i.to_s }
+ }.should complain(/block supersedes default value argument/)
+ @result.should == ['0', '1', '2']
+ end
+
+ it "returns the value passed to break" do
+ [].send(:initialize, 3) { break :a }.should == :a
+ end
+
+ it "sets the array to the values returned by the block before break is executed" do
+ a = [1, 2, 3]
+ a.send(:initialize, 3) do |i|
+ break if i == 2
+ i.to_s
+ end
+
+ a.should == ['0', '1']
+ end
+end
diff --git a/spec/ruby/core/array/insert_spec.rb b/spec/ruby/core/array/insert_spec.rb
new file mode 100644
index 0000000000..38e132fd25
--- /dev/null
+++ b/spec/ruby/core/array/insert_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#insert" do
+ it "returns self" do
+ ary = []
+ ary.insert(0).should.equal?(ary)
+ ary.insert(0, :a).should.equal?(ary)
+ end
+
+ it "inserts objects before the element at index for non-negative index" do
+ ary = []
+ ary.insert(0, 3).should == [3]
+ ary.insert(0, 1, 2).should == [1, 2, 3]
+ ary.insert(0).should == [1, 2, 3]
+
+ # Let's just assume insert() always modifies the array from now on.
+ ary.insert(1, 'a').should == [1, 'a', 2, 3]
+ ary.insert(0, 'b').should == ['b', 1, 'a', 2, 3]
+ ary.insert(5, 'c').should == ['b', 1, 'a', 2, 3, 'c']
+ ary.insert(7, 'd').should == ['b', 1, 'a', 2, 3, 'c', nil, 'd']
+ ary.insert(10, 5, 4).should == ['b', 1, 'a', 2, 3, 'c', nil, 'd', nil, nil, 5, 4]
+ end
+
+ it "appends objects to the end of the array for index == -1" do
+ [1, 3, 3].insert(-1, 2, 'x', 0.5).should == [1, 3, 3, 2, 'x', 0.5]
+ end
+
+ it "inserts objects after the element at index with negative index" do
+ ary = []
+ ary.insert(-1, 3).should == [3]
+ ary.insert(-2, 2).should == [2, 3]
+ ary.insert(-3, 1).should == [1, 2, 3]
+ ary.insert(-2, -3).should == [1, 2, -3, 3]
+ ary.insert(-1, []).should == [1, 2, -3, 3, []]
+ ary.insert(-2, 'x', 'y').should == [1, 2, -3, 3, 'x', 'y', []]
+ ary = [1, 2, 3]
+ end
+
+ it "pads with nils if the index to be inserted to is past the end" do
+ [].insert(5, 5).should == [nil, nil, nil, nil, nil, 5]
+ end
+
+ it "can insert before the first element with a negative index" do
+ [1, 2, 3].insert(-4, -3).should == [-3, 1, 2, 3]
+ end
+
+ it "raises an IndexError if the negative index is out of bounds" do
+ -> { [].insert(-2, 1) }.should.raise(IndexError)
+ -> { [1].insert(-3, 2) }.should.raise(IndexError)
+ end
+
+ it "does nothing of no object is passed" do
+ [].insert(0).should == []
+ [].insert(-1).should == []
+ [].insert(10).should == []
+ [].insert(-2).should == []
+ end
+
+ it "tries to convert the passed position argument to an Integer using #to_int" do
+ obj = mock('2')
+ obj.should_receive(:to_int).and_return(2)
+ [].insert(obj, 'x').should == [nil, nil, 'x']
+ end
+
+ it "raises an ArgumentError if no argument passed" do
+ -> { [].insert() }.should.raise(ArgumentError)
+ end
+
+ it "raises a FrozenError on frozen arrays when the array is modified" do
+ -> { ArraySpecs.frozen_array.insert(0, 'x') }.should.raise(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on frozen arrays when the array would not be modified" do
+ -> { ArraySpecs.frozen_array.insert(0) }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/inspect_spec.rb b/spec/ruby/core/array/inspect_spec.rb
new file mode 100644
index 0000000000..e5dca82889
--- /dev/null
+++ b/spec/ruby/core/array/inspect_spec.rb
@@ -0,0 +1,108 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#inspect" do
+ it "returns a string" do
+ [1, 2, 3].inspect.should.instance_of?(String)
+ end
+
+ it "returns '[]' for an empty Array" do
+ [].inspect.should == "[]"
+ end
+
+ it "calls inspect on its elements and joins the results with commas" do
+ items = Array.new(3) do |i|
+ obj = mock(i.to_s)
+ obj.should_receive(:inspect).and_return(i.to_s)
+ obj
+ end
+ items.inspect.should == "[0, 1, 2]"
+ end
+
+ it "does not call #to_s on a String returned from #inspect" do
+ str = +"abc"
+ str.should_not_receive(:to_s)
+
+ [str].inspect.should == '["abc"]'
+ end
+
+ it "calls #to_s on the object returned from #inspect if the Object isn't a String" do
+ obj = mock("Array#inspect/to_s calls #to_s")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_return("abc")
+
+ [obj].inspect.should == "[abc]"
+ end
+
+ it "does not call #to_str on the object returned from #inspect when it is not a String" do
+ obj = mock("Array#inspect/to_s does not call #to_str")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_not_receive(:to_str)
+
+ [obj].inspect.should =~ /^\[#<MockObject:0x[0-9a-f]+>\]$/
+ end
+
+ it "does not call #to_str on the object returned from #to_s when it is not a String" do
+ obj = mock("Array#inspect/to_s does not call #to_str on #to_s result")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_return(obj)
+ obj.should_not_receive(:to_str)
+
+ [obj].inspect.should =~ /^\[#<MockObject:0x[0-9a-f]+>\]$/
+ end
+
+ it "does not swallow exceptions raised by #to_s" do
+ obj = mock("Array#inspect/to_s does not swallow #to_s exceptions")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_raise(Exception)
+
+ -> { [obj].inspect }.should.raise(Exception)
+ end
+
+ it "represents a recursive element with '[...]'" do
+ ArraySpecs.recursive_array.inspect.should == "[1, \"two\", 3.0, [...], [...], [...], [...], [...]]"
+ ArraySpecs.head_recursive_array.inspect.should == "[[...], [...], [...], [...], [...], 1, \"two\", 3.0]"
+ ArraySpecs.empty_recursive_array.inspect.should == "[[...]]"
+ end
+
+ describe "with encoding" do
+ before :each do
+ @default_external_encoding = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @default_external_encoding
+ end
+
+ it "returns a US-ASCII string for an empty Array" do
+ [].inspect.encoding.should == Encoding::US_ASCII
+ end
+
+ it "use the default external encoding if it is ascii compatible" do
+ Encoding.default_external = Encoding.find('UTF-8')
+
+ utf8 = "utf8".encode("UTF-8")
+ jp = "jp".encode("EUC-JP")
+ array = [jp, utf8]
+
+ array.inspect.encoding.name.should == "UTF-8"
+ end
+
+ it "use US-ASCII encoding if the default external encoding is not ascii compatible" do
+ Encoding.default_external = Encoding.find('UTF-32')
+
+ utf8 = "utf8".encode("UTF-8")
+ jp = "jp".encode("EUC-JP")
+ array = [jp, utf8]
+
+ array.inspect.encoding.name.should == "US-ASCII"
+ end
+
+ it "does not raise if inspected result is not default external encoding" do
+ utf_16be = mock(+"utf_16be")
+ utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode(Encoding::UTF_16BE))
+
+ [utf_16be].inspect.should == '["utf_16be \u3042"]'
+ end
+ end
+end
diff --git a/spec/ruby/core/array/intersect_spec.rb b/spec/ruby/core/array/intersect_spec.rb
new file mode 100644
index 0000000000..456aa26c6e
--- /dev/null
+++ b/spec/ruby/core/array/intersect_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Array#intersect?' do
+ 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
+ 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])
+
+ [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)
+
+ [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].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 "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
+
+ 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/intersection_spec.rb b/spec/ruby/core/array/intersection_spec.rb
new file mode 100644
index 0000000000..e01a68d389
--- /dev/null
+++ b/spec/ruby/core/array/intersection_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/intersection'
+
+describe "Array#&" do
+ it_behaves_like :array_intersection, :&
+end
+
+describe "Array#intersection" do
+ it_behaves_like :array_intersection, :intersection
+
+ it "accepts multiple arguments" do
+ [1, 2, 3, 4].intersection([1, 2, 3], [2, 3, 4]).should == [2, 3]
+ end
+
+ it "preserves elements order from original array" do
+ [1, 2, 3, 4].intersection([3, 2, 1]).should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/array/join_spec.rb b/spec/ruby/core/array/join_spec.rb
new file mode 100644
index 0000000000..3b4946a99f
--- /dev/null
+++ b/spec/ruby/core/array/join_spec.rb
@@ -0,0 +1,146 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/join'
+
+describe "Array#join" do
+ it_behaves_like :array_join_with_string_separator, :join
+
+ it "does not separate elements when the passed separator is nil" do
+ [1, 2, 3].join(nil).should == '123'
+ end
+
+ it "calls #to_str to convert the separator to a String" do
+ sep = mock("separator")
+ sep.should_receive(:to_str).and_return(", ")
+ [1, 2].join(sep).should == "1, 2"
+ end
+
+ it "does not call #to_str on the separator if the array is empty" do
+ sep = mock("separator")
+ sep.should_not_receive(:to_str)
+ [].join(sep).should == ""
+ end
+
+ it "raises a TypeError if the separator cannot be coerced to a String by calling #to_str" do
+ obj = mock("not a string")
+ -> { [1, 2].join(obj) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed false as the separator" do
+ -> { [1, 2].join(false) }.should.raise(TypeError)
+ end
+end
+
+describe "Array#join with default separator" do
+ before :each do
+ @separator = $,
+ end
+
+ after :each do
+ $, = @separator
+ end
+
+ it "returns an empty string if the Array is empty" do
+ [].join.should == ''
+ end
+
+ it "returns a US-ASCII string for an empty Array" do
+ [].join.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns a string formed by concatenating each String element separated by $," do
+ suppress_warning {
+ $, = " | "
+ ["1", "2", "3"].join.should == "1 | 2 | 3"
+ }
+ end
+
+ it "attempts coercion via #to_str first" do
+ obj = mock('foo')
+ obj.should_receive(:to_str).any_number_of_times.and_return("foo")
+ [obj].join.should == "foo"
+ end
+
+ it "attempts coercion via #to_ary second" do
+ obj = mock('foo')
+ obj.should_receive(:to_str).any_number_of_times.and_return(nil)
+ obj.should_receive(:to_ary).any_number_of_times.and_return(["foo"])
+ [obj].join.should == "foo"
+ end
+
+ it "attempts coercion via #to_s third" do
+ obj = mock('foo')
+ obj.should_receive(:to_str).any_number_of_times.and_return(nil)
+ obj.should_receive(:to_ary).any_number_of_times.and_return(nil)
+ obj.should_receive(:to_s).any_number_of_times.and_return("foo")
+ [obj].join.should == "foo"
+ end
+
+ it "raises a NoMethodError if an element does not respond to #to_str, #to_ary, or #to_s" do
+ obj = mock('o')
+ class << obj; undef :to_s; end
+ -> { [1, obj].join }.should.raise(NoMethodError)
+ end
+
+ it "raises an ArgumentError when the Array is recursive" do
+ -> { ArraySpecs.recursive_array.join }.should.raise(ArgumentError)
+ -> { ArraySpecs.head_recursive_array.join }.should.raise(ArgumentError)
+ -> { ArraySpecs.empty_recursive_array.join }.should.raise(ArgumentError)
+ end
+
+ it "uses the first encoding when other strings are compatible" do
+ ary1 = ArraySpecs.array_with_7bit_utf8_and_usascii_strings
+ ary2 = ArraySpecs.array_with_usascii_and_7bit_utf8_strings
+ ary3 = ArraySpecs.array_with_utf8_and_7bit_binary_strings
+ ary4 = ArraySpecs.array_with_usascii_and_7bit_binary_strings
+
+ ary1.join.encoding.should == Encoding::UTF_8
+ ary2.join.encoding.should == Encoding::US_ASCII
+ ary3.join.encoding.should == Encoding::UTF_8
+ ary4.join.encoding.should == Encoding::US_ASCII
+ end
+
+ it "uses the widest common encoding when other strings are incompatible" do
+ ary1 = ArraySpecs.array_with_utf8_and_usascii_strings
+ ary2 = ArraySpecs.array_with_usascii_and_utf8_strings
+
+ ary1.join.encoding.should == Encoding::UTF_8
+ ary2.join.encoding.should == Encoding::UTF_8
+ end
+
+ it "fails for arrays with incompatibly-encoded strings" do
+ ary_utf8_bad_binary = ArraySpecs.array_with_utf8_and_binary_strings
+
+ -> { ary_utf8_bad_binary.join }.should.raise(EncodingError)
+ end
+
+ context "when $, is not nil" do
+ before do
+ suppress_warning do
+ $, = '*'
+ end
+ end
+
+ it "warns" do
+ -> { [].join }.should complain(/warning: \$, is set to non-nil value/)
+ -> { [].join(nil) }.should complain(/warning: \$, is set to non-nil value/)
+ end
+ end
+end
+
+describe "Array#join with $," do
+ before :each do
+ @before_separator = $,
+ end
+
+ after :each do
+ suppress_warning {$, = @before_separator}
+ end
+
+ it "separates elements with default separator when the passed separator is nil" do
+ suppress_warning {
+ $, = "_"
+ [1, 2, 3].join(nil).should == '1_2_3'
+ }
+ end
+end
diff --git a/spec/ruby/core/array/keep_if_spec.rb b/spec/ruby/core/array/keep_if_spec.rb
new file mode 100644
index 0000000000..62a65a04e8
--- /dev/null
+++ b/spec/ruby/core/array/keep_if_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'shared/keep_if'
+
+describe "Array#keep_if" do
+ it "returns the same array if no changes were made" do
+ array = [1, 2, 3]
+ array.keep_if { true }.should.equal?(array)
+ end
+
+ it_behaves_like :keep_if, :keep_if
+end
diff --git a/spec/ruby/core/array/last_spec.rb b/spec/ruby/core/array/last_spec.rb
new file mode 100644
index 0000000000..ed417bcd2a
--- /dev/null
+++ b/spec/ruby/core/array/last_spec.rb
@@ -0,0 +1,87 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#last" do
+ it "returns the last element" do
+ [1, 1, 1, 1, 2].last.should == 2
+ end
+
+ it "returns nil if self is empty" do
+ [].last.should == nil
+ end
+
+ it "returns the last count elements if given a count" do
+ [1, 2, 3, 4, 5, 9].last(3).should == [4, 5, 9]
+ end
+
+ it "returns an empty array when passed a count on an empty array" do
+ [].last(0).should == []
+ [].last(1).should == []
+ end
+
+ it "returns an empty array when count == 0" do
+ [1, 2, 3, 4, 5].last(0).should == []
+ end
+
+ it "returns an array containing the last element when passed count == 1" do
+ [1, 2, 3, 4, 5].last(1).should == [5]
+ end
+
+ it "raises an ArgumentError when count is negative" do
+ -> { [1, 2].last(-1) }.should.raise(ArgumentError)
+ end
+
+ it "returns the entire array when count > length" do
+ [1, 2, 3, 4, 5, 9].last(10).should == [1, 2, 3, 4, 5, 9]
+ end
+
+ it "returns an array which is independent to the original when passed count" do
+ ary = [1, 2, 3, 4, 5]
+ ary.last(0).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ ary.last(1).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ ary.last(6).replace([1,2])
+ ary.should == [1, 2, 3, 4, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.last.should.equal?(empty)
+
+ array = ArraySpecs.recursive_array
+ array.last.should.equal?(array)
+ 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)
+ [1, 2, 3, 4, 5].last(obj).should == [4, 5]
+ end
+
+ it "raises a TypeError if the passed argument is not numeric" do
+ -> { [1,2].last(nil) }.should.raise(TypeError)
+ -> { [1,2].last("a") }.should.raise(TypeError)
+
+ obj = mock("nonnumeric")
+ -> { [1,2].last(obj) }.should.raise(TypeError)
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[].last(0).should.instance_of?(Array)
+ ArraySpecs::MyArray[].last(2).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].last(0).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].last(1).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].last(2).should.instance_of?(Array)
+ end
+
+ it "is not destructive" do
+ a = [1, 2, 3]
+ a.last
+ a.should == [1, 2, 3]
+ a.last(2)
+ a.should == [1, 2, 3]
+ a.last(3)
+ a.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/array/length_spec.rb b/spec/ruby/core/array/length_spec.rb
new file mode 100644
index 0000000000..74b2eb3a08
--- /dev/null
+++ b/spec/ruby/core/array/length_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#length" do
+ it "returns the number of elements" do
+ [].length.should == 0
+ [1, 2, 3].length.should == 3
+ end
+
+ it "properly handles recursive arrays" do
+ ArraySpecs.empty_recursive_array.length.should == 1
+ ArraySpecs.recursive_array.length.should == 8
+ end
+end
diff --git a/spec/ruby/core/array/map_spec.rb b/spec/ruby/core/array/map_spec.rb
new file mode 100644
index 0000000000..f5e88c8624
--- /dev/null
+++ b/spec/ruby/core/array/map_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Array#map" do
+ it "is an alias of Array#collect" do
+ Array.instance_method(:map).should == Array.instance_method(:collect)
+ end
+end
+
+describe "Array#map!" do
+ it "is an alias of Array#collect!" do
+ Array.instance_method(:map!).should == Array.instance_method(:collect!)
+ end
+end
diff --git a/spec/ruby/core/array/max_spec.rb b/spec/ruby/core/array/max_spec.rb
new file mode 100644
index 0000000000..868275a748
--- /dev/null
+++ b/spec/ruby/core/array/max_spec.rb
@@ -0,0 +1,116 @@
+require_relative '../../spec_helper'
+
+describe "Array#max" do
+ it "is defined on Array" do
+ [1].method(:max).owner.should.equal? Array
+ end
+
+ it "returns nil with no values" do
+ [].max.should == nil
+ end
+
+ it "returns only element in one element array" do
+ [1].max.should == 1
+ end
+
+ it "returns largest value with multiple elements" do
+ [1,2].max.should == 2
+ [2,1].max.should == 2
+ end
+
+ describe "given a block with one argument" do
+ it "yields in turn the last length-1 values from the array" do
+ ary = []
+ result = [1,2,3,4,5].max {|x| ary << x; x}
+
+ ary.should == [2,3,4,5]
+ result.should == 5
+ end
+ end
+end
+
+# From Enumerable#max, copied for better readability
+describe "Array#max" do
+ before :each do
+ @a = [2, 4, 6, 8, 10]
+
+ @e_strs = ["333", "22", "666666", "1", "55555", "1010101010"]
+ @e_ints = [333, 22, 666666, 55555, 1010101010]
+ end
+
+ it "max should return the maximum element" do
+ [18, 42].max.should == 42
+ [2, 5, 3, 6, 1, 4].max.should == 6
+ end
+
+ it "returns the maximum element (basics cases)" do
+ [55].max.should == 55
+
+ [11,99].max.should == 99
+ [99,11].max.should == 99
+ [2, 33, 4, 11].max.should == 33
+
+ [1,2,3,4,5].max.should == 5
+ [5,4,3,2,1].max.should == 5
+ [1,4,3,5,2].max.should == 5
+ [5,5,5,5,5].max.should == 5
+
+ ["aa","tt"].max.should == "tt"
+ ["tt","aa"].max.should == "tt"
+ ["2","33","4","11"].max.should == "4"
+
+ @e_strs.max.should == "666666"
+ @e_ints.max.should == 1010101010
+ end
+
+ it "returns nil for an empty Enumerable" do
+ [].max.should == nil
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> do
+ [BasicObject.new, BasicObject.new].max
+ end.should.raise(NoMethodError)
+ end
+
+ it "raises an ArgumentError for incomparable elements" do
+ -> do
+ [11,"22"].max
+ end.should.raise(ArgumentError)
+ -> do
+ [11,12,22,33].max{|a, b| nil}
+ end.should.raise(ArgumentError)
+ end
+
+ it "returns the maximum element (with block)" do
+ # with a block
+ ["2","33","4","11"].max {|a,b| a <=> b }.should == "4"
+ [ 2 , 33 , 4 , 11 ].max {|a,b| a <=> b }.should == 33
+
+ ["2","33","4","11"].max {|a,b| b <=> a }.should == "11"
+ [ 2 , 33 , 4 , 11 ].max {|a,b| b <=> a }.should == 2
+
+ @e_strs.max {|a,b| a.length <=> b.length }.should == "1010101010"
+
+ @e_strs.max {|a,b| a <=> b }.should == "666666"
+ @e_strs.max {|a,b| a.to_i <=> b.to_i }.should == "1010101010"
+
+ @e_ints.max {|a,b| a <=> b }.should == 1010101010
+ @e_ints.max {|a,b| a.to_s <=> b.to_s }.should == 666666
+ end
+
+ it "returns the minimum for enumerables that contain nils" do
+ arr = [nil, nil, true]
+ arr.max { |a, b|
+ x = a.nil? ? 1 : a ? 0 : -1
+ y = b.nil? ? 1 : b ? 0 : -1
+ x <=> y
+ }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = [[1,2], [3,4,5], [6,7,8,9]]
+ multi.max.should == [6, 7, 8, 9]
+ end
+
+end
diff --git a/spec/ruby/core/array/min_spec.rb b/spec/ruby/core/array/min_spec.rb
new file mode 100644
index 0000000000..5913e08cf8
--- /dev/null
+++ b/spec/ruby/core/array/min_spec.rb
@@ -0,0 +1,121 @@
+require_relative '../../spec_helper'
+
+describe "Array#min" do
+ it "is defined on Array" do
+ [1].method(:max).owner.should.equal? Array
+ end
+
+ it "returns nil with no values" do
+ [].min.should == nil
+ end
+
+ it "returns only element in one element array" do
+ [1].min.should == 1
+ end
+
+ it "returns smallest value with multiple elements" do
+ [1,2].min.should == 1
+ [2,1].min.should == 1
+ end
+
+ describe "given a block with one argument" do
+ it "yields in turn the last length-1 values from the array" do
+ ary = []
+ result = [1,2,3,4,5].min {|x| ary << x; x}
+
+ ary.should == [2,3,4,5]
+ result.should == 1
+ end
+ end
+end
+
+# From Enumerable#min, copied for better readability
+describe "Array#min" do
+ before :each do
+ @a = [2, 4, 6, 8, 10]
+
+ @e_strs = ["333", "22", "666666", "1", "55555", "1010101010"]
+ @e_ints = [ 333, 22, 666666, 55555, 1010101010]
+ end
+
+ it "min should return the minimum element" do
+ [18, 42].min.should == 18
+ [2, 5, 3, 6, 1, 4].min.should == 1
+ end
+
+ it "returns the minimum (basic cases)" do
+ [55].min.should == 55
+
+ [11,99].min.should == 11
+ [99,11].min.should == 11
+ [2, 33, 4, 11].min.should == 2
+
+ [1,2,3,4,5].min.should == 1
+ [5,4,3,2,1].min.should == 1
+ [4,1,3,5,2].min.should == 1
+ [5,5,5,5,5].min.should == 5
+
+ ["aa","tt"].min.should == "aa"
+ ["tt","aa"].min.should == "aa"
+ ["2","33","4","11"].min.should == "11"
+
+ @e_strs.min.should == "1"
+ @e_ints.min.should == 22
+ end
+
+ it "returns nil for an empty Enumerable" do
+ [].min.should == nil
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> do
+ [BasicObject.new, BasicObject.new].min
+ end.should.raise(NoMethodError)
+ end
+
+ it "raises an ArgumentError for incomparable elements" do
+ -> do
+ [11,"22"].min
+ end.should.raise(ArgumentError)
+ -> do
+ [11,12,22,33].min{|a, b| nil}
+ end.should.raise(ArgumentError)
+ end
+
+ it "returns the minimum when using a block rule" do
+ ["2","33","4","11"].min {|a,b| a <=> b }.should == "11"
+ [ 2 , 33 , 4 , 11 ].min {|a,b| a <=> b }.should == 2
+
+ ["2","33","4","11"].min {|a,b| b <=> a }.should == "4"
+ [ 2 , 33 , 4 , 11 ].min {|a,b| b <=> a }.should == 33
+
+ [ 1, 2, 3, 4 ].min {|a,b| 15 }.should == 1
+
+ [11,12,22,33].min{|a, b| 2 }.should == 11
+ @i = -2
+ [11,12,22,33].min{|a, b| @i += 1 }.should == 12
+
+ @e_strs.min {|a,b| a.length <=> b.length }.should == "1"
+
+ @e_strs.min {|a,b| a <=> b }.should == "1"
+ @e_strs.min {|a,b| a.to_i <=> b.to_i }.should == "1"
+
+ @e_ints.min {|a,b| a <=> b }.should == 22
+ @e_ints.min {|a,b| a.to_s <=> b.to_s }.should == 1010101010
+ end
+
+ it "returns the minimum for enumerables that contain nils" do
+ arr = [nil, nil, true]
+ arr.min { |a, b|
+ x = a.nil? ? -1 : a ? 0 : 1
+ y = b.nil? ? -1 : b ? 0 : 1
+ x <=> y
+ }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = [[1,2], [3,4,5], [6,7,8,9]]
+ multi.min.should == [1, 2]
+ end
+
+end
diff --git a/spec/ruby/core/array/minmax_spec.rb b/spec/ruby/core/array/minmax_spec.rb
new file mode 100644
index 0000000000..e11fe63347
--- /dev/null
+++ b/spec/ruby/core/array/minmax_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/enumerable/minmax'
+
+describe "Array#minmax" do
+ before :each do
+ @enum = [6, 4, 5, 10, 8]
+ @empty_enum = []
+ @incomparable_enum = [BasicObject.new, BasicObject.new]
+ @incompatible_enum = [11, "22"]
+ @strs = ["333", "2", "60", "55555", "1010", "111"]
+ end
+
+ it_behaves_like :enumerable_minmax, :minmax
+end
diff --git a/spec/ruby/core/array/minus_spec.rb b/spec/ruby/core/array/minus_spec.rb
new file mode 100644
index 0000000000..cb1bf56d76
--- /dev/null
+++ b/spec/ruby/core/array/minus_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/difference'
+
+describe "Array#-" do
+ it_behaves_like :array_binary_difference, :-
+end
diff --git a/spec/ruby/core/array/multiply_spec.rb b/spec/ruby/core/array/multiply_spec.rb
new file mode 100644
index 0000000000..1ac14e1b09
--- /dev/null
+++ b/spec/ruby/core/array/multiply_spec.rb
@@ -0,0 +1,94 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/join'
+
+describe "Array#*" do
+ it "tries to convert the passed argument to a String using #to_str" do
+ obj = mock('separator')
+ obj.should_receive(:to_str).and_return('::')
+ ([1, 2, 3, 4] * obj).should == '1::2::3::4'
+ end
+
+ it "tires to convert the passed argument to an Integer using #to_int" do
+ obj = mock('count')
+ obj.should_receive(:to_int).and_return(2)
+ ([1, 2, 3, 4] * obj).should == [1, 2, 3, 4, 1, 2, 3, 4]
+ end
+
+ it "raises a TypeError if the argument can neither be converted to a string nor an integer" do
+ obj = mock('not a string or integer')
+ ->{ [1,2] * obj }.should.raise(TypeError)
+ end
+
+ it "converts the passed argument to a String rather than an Integer" do
+ obj = mock('2')
+ def obj.to_int() 2 end
+ def obj.to_str() "2" end
+ ([:a, :b, :c] * obj).should == "a2b2c"
+ end
+
+ it "raises a TypeError is the passed argument is nil" do
+ ->{ [1,2] * nil }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError when passed 2 or more arguments" do
+ ->{ [1,2].send(:*, 1, 2) }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError when passed no arguments" do
+ ->{ [1,2].send(:*) }.should.raise(ArgumentError)
+ end
+end
+
+describe "Array#* with an integer" do
+ it "concatenates n copies of the array when passed an integer" do
+ ([ 1, 2, 3 ] * 0).should == []
+ ([ 1, 2, 3 ] * 1).should == [1, 2, 3]
+ ([ 1, 2, 3 ] * 3).should == [1, 2, 3, 1, 2, 3, 1, 2, 3]
+ ([] * 10).should == []
+ end
+
+ it "does not return self even if the passed integer is 1" do
+ ary = [1, 2, 3]
+ (ary * 1).should_not.equal?(ary)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ (empty * 0).should == []
+ (empty * 1).should == empty
+ (empty * 3).should == [empty, empty, empty]
+
+ array = ArraySpecs.recursive_array
+ (array * 0).should == []
+ (array * 1).should == array
+ end
+
+ it "raises an ArgumentError when passed a negative integer" do
+ -> { [ 1, 2, 3 ] * -1 }.should.raise(ArgumentError)
+ -> { [] * -1 }.should.raise(ArgumentError)
+ end
+
+ describe "with a subclass of Array" do
+ before :each do
+ ScratchPad.clear
+
+ @array = ArraySpecs::MyArray[1, 2, 3, 4, 5]
+ end
+
+ it "returns an Array instance" do
+ (@array * 0).should.instance_of?(Array)
+ (@array * 1).should.instance_of?(Array)
+ (@array * 2).should.instance_of?(Array)
+ end
+
+ it "does not call #initialize on the subclass instance" do
+ (@array * 2).should == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
+ ScratchPad.recorded.should == nil
+ end
+ end
+end
+
+describe "Array#* with a string" do
+ it_behaves_like :array_join_with_string_separator, :*
+end
diff --git a/spec/ruby/core/array/new_spec.rb b/spec/ruby/core/array/new_spec.rb
new file mode 100644
index 0000000000..b2f23e2f6b
--- /dev/null
+++ b/spec/ruby/core/array/new_spec.rb
@@ -0,0 +1,124 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array.new" do
+ it "returns an instance of Array" do
+ Array.new.should.instance_of?(Array)
+ end
+
+ it "returns an instance of a subclass" do
+ ArraySpecs::MyArray.new(1, 2).should.instance_of?(ArraySpecs::MyArray)
+ end
+
+ it "raises an ArgumentError if passed 3 or more arguments" do
+ -> do
+ [1, 2].send :initialize, 1, 'x', true
+ end.should.raise(ArgumentError)
+ -> do
+ [1, 2].send(:initialize, 1, 'x', true) {}
+ end.should.raise(ArgumentError)
+ end
+end
+
+describe "Array.new with no arguments" do
+ it "returns an empty array" do
+ Array.new.should.empty?
+ end
+
+ it "does not use the given block" do
+ -> {
+ -> { Array.new { raise } }.should_not.raise
+ }.should complain(/warning: given block not used/, verbose: true)
+ end
+end
+
+describe "Array.new with (array)" do
+ it "returns an array initialized to the other array" do
+ b = [4, 5, 6]
+ Array.new(b).should == b
+ end
+
+ it "does not use the given block" do
+ ->{ Array.new([1, 2]) { raise } }.should_not.raise
+ end
+
+ it "calls #to_ary to convert the value to an array" do
+ a = mock("array")
+ a.should_receive(:to_ary).and_return([1, 2])
+ a.should_not_receive(:to_int)
+ Array.new(a).should == [1, 2]
+ end
+
+ it "does not call #to_ary on instances of Array or subclasses of Array" do
+ a = [1, 2]
+ a.should_not_receive(:to_ary)
+ Array.new(a)
+ end
+
+ it "raises a TypeError if an Array type argument and a default object" do
+ -> { Array.new([1, 2], 1) }.should.raise(TypeError)
+ end
+end
+
+describe "Array.new with (size, object=nil)" do
+ it "returns an array of size filled with object" do
+ obj = [3]
+ a = Array.new(2, obj)
+ a.should == [obj, obj]
+ a[0].should.equal?(obj)
+ a[1].should.equal?(obj)
+
+ Array.new(3, 14).should == [14, 14, 14]
+ end
+
+ it "returns an array of size filled with nil when object is omitted" do
+ Array.new(3).should == [nil, nil, nil]
+ end
+
+ it "raises an ArgumentError if size is negative" do
+ -> { Array.new(-1, :a) }.should.raise(ArgumentError)
+ -> { Array.new(-1) }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if size is too large" do
+ -> { Array.new(fixnum_max+1) }.should.raise(ArgumentError)
+ end
+
+ it "calls #to_int to convert the size argument to an Integer when object is given" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ Array.new(obj, :a).should == [:a]
+ end
+
+ it "calls #to_int to convert the size argument to an Integer when object is not given" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ Array.new(obj).should == [nil]
+ end
+
+ it "raises a TypeError if the size argument is not an Integer type" do
+ obj = mock('nonnumeric')
+ obj.stub!(:to_ary).and_return([1, 2])
+ ->{ Array.new(obj, :a) }.should.raise(TypeError)
+ end
+
+ it "yields the index of the element and sets the element to the value of the block" do
+ Array.new(3) { |i| i.to_s }.should == ['0', '1', '2']
+ end
+
+ it "uses the block value instead of using the default value" do
+ -> {
+ @result = Array.new(3, :obj) { |i| i.to_s }
+ }.should complain(/block supersedes default value argument/)
+ @result.should == ['0', '1', '2']
+ end
+
+ it "returns the value passed to break" do
+ a = Array.new(3) do |i|
+ break if i == 2
+ i.to_s
+ end
+
+ a.should == nil
+ end
+end
diff --git a/spec/ruby/core/array/none_spec.rb b/spec/ruby/core/array/none_spec.rb
new file mode 100644
index 0000000000..31cd8c46d6
--- /dev/null
+++ b/spec/ruby/core/array/none_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#none?" do
+ @value_to_return = -> _ { false }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :none?
+
+ it "ignores the block if there is an argument" do
+ -> {
+ ['bar', 'foobar'].none?(/baz/) { true }.should == true
+ }.should complain(/given block not used/)
+ end
+end
diff --git a/spec/ruby/core/array/one_spec.rb b/spec/ruby/core/array/one_spec.rb
new file mode 100644
index 0000000000..0c61907881
--- /dev/null
+++ b/spec/ruby/core/array/one_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#one?" do
+ @value_to_return = -> _ { false }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :one?
+
+ it "ignores the block if there is an argument" do
+ -> {
+ ['bar', 'foobar'].one?(/foo/) { false }.should == true
+ }.should complain(/given block not used/)
+ end
+end
diff --git a/spec/ruby/core/array/pack/a_spec.rb b/spec/ruby/core/array/pack/a_spec.rb
new file mode 100644
index 0000000000..03bfd8214c
--- /dev/null
+++ b/spec/ruby/core/array/pack/a_spec.rb
@@ -0,0 +1,73 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/string'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'A'" do
+ it_behaves_like :array_pack_basic, 'A'
+ it_behaves_like :array_pack_basic_non_float, 'A'
+ it_behaves_like :array_pack_no_platform, 'A'
+ it_behaves_like :array_pack_string, 'A'
+ it_behaves_like :array_pack_taint, 'A'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack A string")
+ obj.should_receive(:to_str).and_return("``abcdef")
+ [obj].pack("A*").should == "``abcdef"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('A') }.should.raise(TypeError)
+ -> { [0].pack('a') }.should.raise(TypeError)
+ end
+
+ it "adds all the bytes to the output when passed the '*' modifier" do
+ ["abc"].pack("A*").should == "abc"
+ end
+
+ it "pads the output with spaces when the count exceeds the size of the String" do
+ ["abc"].pack("A6").should == "abc "
+ end
+
+ it "adds a space when the value is nil" do
+ [nil].pack("A").should == " "
+ end
+
+ it "pads the output with spaces when the value is nil" do
+ [nil].pack("A3").should == " "
+ end
+
+ it "does not pad with spaces when passed the '*' modifier and the value is nil" do
+ [nil].pack("A*").should == ""
+ end
+end
+
+describe "Array#pack with format 'a'" do
+ it_behaves_like :array_pack_basic, 'a'
+ it_behaves_like :array_pack_basic_non_float, 'a'
+ it_behaves_like :array_pack_no_platform, 'a'
+ it_behaves_like :array_pack_string, 'a'
+ it_behaves_like :array_pack_taint, 'a'
+
+ it "adds all the bytes to the output when passed the '*' modifier" do
+ ["abc"].pack("a*").should == "abc"
+ end
+
+ 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
+
+ it "adds a NULL byte when the value is nil" do
+ [nil].pack("a").should == "\x00"
+ end
+
+ it "pads the output with NULL bytes when the value is nil" do
+ [nil].pack("a3").should == "\x00\x00\x00"
+ end
+
+ it "does not pad with NULL bytes when passed the '*' modifier and the value is nil" do
+ [nil].pack("a*").should == ""
+ end
+end
diff --git a/spec/ruby/core/array/pack/at_spec.rb b/spec/ruby/core/array/pack/at_spec.rb
new file mode 100644
index 0000000000..bb9801440a
--- /dev/null
+++ b/spec/ruby/core/array/pack/at_spec.rb
@@ -0,0 +1,30 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+
+describe "Array#pack with format '@'" do
+ it_behaves_like :array_pack_basic, '@'
+ it_behaves_like :array_pack_basic_non_float, '@'
+ it_behaves_like :array_pack_no_platform, '@'
+
+ it "moves the insertion point to the index specified by the count modifier" do
+ [1, 2, 3, 4, 5].pack("C4@2C").should == "\x01\x02\x05"
+ end
+
+ it "does not consume any elements" do
+ [1, 2, 3].pack("C@3C").should == "\x01\x00\x00\x02"
+ end
+
+ it "extends the string with NULL bytes if the string size is less than the count" do
+ [1, 2, 3].pack("@3C*").should == "\x00\x00\x00\x01\x02\x03"
+ end
+
+ it "truncates the string if the string size is greater than the count" do
+ [1, 2, 3].pack("Cx5@2C").should == "\x01\x00\x02"
+ end
+
+ it "implicitly has a count of one when no count modifier is passed" do
+ [1, 2, 3].pack("C*@").should == "\x01"
+ end
+end
diff --git a/spec/ruby/core/array/pack/b_spec.rb b/spec/ruby/core/array/pack/b_spec.rb
new file mode 100644
index 0000000000..f7576846ef
--- /dev/null
+++ b/spec/ruby/core/array/pack/b_spec.rb
@@ -0,0 +1,113 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/encodings'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'B'" do
+ it_behaves_like :array_pack_basic, 'B'
+ it_behaves_like :array_pack_basic_non_float, 'B'
+ it_behaves_like :array_pack_arguments, 'B'
+ it_behaves_like :array_pack_hex, 'B'
+ it_behaves_like :array_pack_taint, 'B'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack B string")
+ obj.should_receive(:to_str).and_return("``abcdef")
+ [obj].pack("B*").should == "\x2a"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('B') }.should.raise(TypeError)
+ -> { [0].pack('b') }.should.raise(TypeError)
+ end
+
+ it "encodes one bit for each character starting with the most significant bit" do
+ [ [["0"], "\x00"],
+ [["1"], "\x80"]
+ ].should be_computed_by(:pack, "B")
+ end
+
+ it "implicitly has a count of one when not passed a count modifier" do
+ ["1"].pack("B").should == "\x80"
+ end
+
+ it "implicitly has count equal to the string length when passed the '*' modifier" do
+ [ [["00101010"], "\x2a"],
+ [["00000000"], "\x00"],
+ [["11111111"], "\xff"],
+ [["10000000"], "\x80"],
+ [["00000001"], "\x01"]
+ ].should be_computed_by(:pack, "B*")
+ end
+
+ it "encodes the least significant bit of a character other than 0 or 1" do
+ [ [["bbababab"], "\x2a"],
+ [["^&#&#^#^"], "\x2a"],
+ [["(()()()("], "\x2a"],
+ [["@@%@%@%@"], "\x2a"],
+ [["ppqrstuv"], "\x2a"],
+ [["rqtvtrqp"], "\x42"]
+ ].should be_computed_by(:pack, "B*")
+ end
+
+ it "returns a binary string" do
+ ["1"].pack("B").encoding.should == Encoding::BINARY
+ end
+
+ it "encodes the string as a sequence of bytes" do
+ ["ああああああああ"].pack("B*").should == "\xdbm\xb6"
+ end
+end
+
+describe "Array#pack with format 'b'" do
+ it_behaves_like :array_pack_basic, 'b'
+ it_behaves_like :array_pack_basic_non_float, 'b'
+ it_behaves_like :array_pack_arguments, 'b'
+ it_behaves_like :array_pack_hex, 'b'
+ it_behaves_like :array_pack_taint, 'b'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack H string")
+ obj.should_receive(:to_str).and_return("`abcdef`")
+ [obj].pack("b*").should == "\x2a"
+ end
+
+ it "encodes one bit for each character starting with the least significant bit" do
+ [ [["0"], "\x00"],
+ [["1"], "\x01"]
+ ].should be_computed_by(:pack, "b")
+ end
+
+ it "implicitly has a count of one when not passed a count modifier" do
+ ["1"].pack("b").should == "\x01"
+ end
+
+ it "implicitly has count equal to the string length when passed the '*' modifier" do
+ [ [["0101010"], "\x2a"],
+ [["00000000"], "\x00"],
+ [["11111111"], "\xff"],
+ [["10000000"], "\x01"],
+ [["00000001"], "\x80"]
+ ].should be_computed_by(:pack, "b*")
+ end
+
+ it "encodes the least significant bit of a character other than 0 or 1" do
+ [ [["bababab"], "\x2a"],
+ [["&#&#^#^"], "\x2a"],
+ [["()()()("], "\x2a"],
+ [["@%@%@%@"], "\x2a"],
+ [["pqrstuv"], "\x2a"],
+ [["qrtrtvs"], "\x41"]
+ ].should be_computed_by(:pack, "b*")
+ end
+
+ it "returns a binary string" do
+ ["1"].pack("b").encoding.should == Encoding::BINARY
+ end
+
+ it "encodes the string as a sequence of bytes" do
+ ["ああああああああ"].pack("b*").should == "\xdb\xb6m"
+ end
+end
diff --git a/spec/ruby/core/array/pack/buffer_spec.rb b/spec/ruby/core/array/pack/buffer_spec.rb
new file mode 100644
index 0000000000..d104c80186
--- /dev/null
+++ b/spec/ruby/core/array/pack/buffer_spec.rb
@@ -0,0 +1,60 @@
+# encoding: binary
+
+require_relative '../../../spec_helper'
+
+describe "Array#pack with :buffer option" do
+ it "returns specified buffer" do
+ n = [ 65, 66, 67 ]
+ buffer = " "*3
+ result = n.pack("ccc", buffer: buffer) #=> "ABC"
+ result.should.equal?(buffer)
+ end
+
+ it "adds result at the end of buffer content" do
+ n = [ 65, 66, 67 ] # result without buffer is "ABC"
+
+ buffer = +""
+ n.pack("ccc", buffer: buffer).should == "ABC"
+
+ buffer = +"123"
+ n.pack("ccc", buffer: buffer).should == "123ABC"
+
+ buffer = +"12345"
+ n.pack("ccc", buffer: buffer).should == "12345ABC"
+ end
+
+ it "raises TypeError exception if buffer is not String" do
+ -> { [65].pack("ccc", buffer: []) }.should.raise(
+ 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(FrozenError)
+ end
+
+ it "preserves the encoding of the given buffer" do
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ [65, 66, 67].pack("ccc", buffer: buffer)
+ buffer.encoding.should == Encoding::ISO_8859_1
+ end
+
+ context "offset (@) is specified" do
+ it 'keeps buffer content if it is longer than offset' do
+ n = [ 65, 66, 67 ]
+ buffer = +"123456"
+ n.pack("@3ccc", buffer: buffer).should == "123ABC"
+ end
+
+ it "fills the gap with \\0 if buffer content is shorter than offset" do
+ n = [ 65, 66, 67 ]
+ buffer = +"123"
+ n.pack("@6ccc", buffer: buffer).should == "123\0\0\0ABC"
+ end
+
+ it 'does not keep buffer content if it is longer than offset + result' do
+ n = [ 65, 66, 67 ]
+ buffer = +"1234567890"
+ n.pack("@3ccc", buffer: buffer).should == "123ABC"
+ end
+ end
+end
diff --git a/spec/ruby/core/array/pack/c_spec.rb b/spec/ruby/core/array/pack/c_spec.rb
new file mode 100644
index 0000000000..de06207a23
--- /dev/null
+++ b/spec/ruby/core/array/pack/c_spec.rb
@@ -0,0 +1,77 @@
+# encoding: binary
+
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+
+describe :array_pack_8bit, shared: true do
+ it "encodes the least significant eight bits of a positive number" do
+ [ [[49], "1"],
+ [[0b11111111], "\xFF"],
+ [[0b100000000], "\x00"],
+ [[0b100000001], "\x01"]
+ ].should be_computed_by(:pack, pack_format)
+ end
+
+ it "encodes the least significant eight bits of a negative number" do
+ [ [[-1], "\xFF"],
+ [[-0b10000000], "\x80"],
+ [[-0b11111111], "\x01"],
+ [[-0b100000000], "\x00"],
+ [[-0b100000001], "\xFF"]
+ ].should be_computed_by(:pack, pack_format)
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[5.2], "\x05"],
+ [[5.8], "\x05"]
+ ].should be_computed_by(:pack, pack_format)
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(5)
+ [obj].pack(pack_format).should == "\x05"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [ [[1, 2, 3], pack_format(3), "\x01\x02\x03"],
+ [[1, 2, 3], pack_format(2) + pack_format(1), "\x01\x02\x03"]
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [1, 2, 3, 4, 5].pack(pack_format('*')).should == "\x01\x02\x03\x04\x05"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [1, 2, 3].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ [1, 2, 3].pack(pack_format(' ', 2)).should == "\x01\x02"
+ end
+end
+
+describe "Array#pack with format 'C'" do
+ it_behaves_like :array_pack_basic, 'C'
+ it_behaves_like :array_pack_basic_non_float, 'C'
+ it_behaves_like :array_pack_8bit, 'C'
+ it_behaves_like :array_pack_arguments, 'C'
+ it_behaves_like :array_pack_numeric_basic, 'C'
+ it_behaves_like :array_pack_integer, 'C'
+ it_behaves_like :array_pack_no_platform, 'C'
+end
+
+describe "Array#pack with format 'c'" do
+ it_behaves_like :array_pack_basic, 'c'
+ it_behaves_like :array_pack_basic_non_float, 'c'
+ it_behaves_like :array_pack_8bit, 'c'
+ it_behaves_like :array_pack_arguments, 'c'
+ it_behaves_like :array_pack_numeric_basic, 'c'
+ it_behaves_like :array_pack_integer, 'c'
+ it_behaves_like :array_pack_no_platform, 'c'
+end
diff --git a/spec/ruby/core/array/pack/comment_spec.rb b/spec/ruby/core/array/pack/comment_spec.rb
new file mode 100644
index 0000000000..daf1cff06a
--- /dev/null
+++ b/spec/ruby/core/array/pack/comment_spec.rb
@@ -0,0 +1,25 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe "Array#pack" do
+ it "ignores directives text from '#' to the first newline" do
+ [1, 2, 3].pack("c#this is a comment\nc").should == "\x01\x02"
+ end
+
+ it "ignores directives text from '#' to the end if no newline is present" do
+ [1, 2, 3].pack("c#this is a comment c").should == "\x01"
+ end
+
+ it "ignores comments at the start of the directives string" do
+ [1, 2, 3].pack("#this is a comment\nc").should == "\x01"
+ end
+
+ it "ignores the entire directive string if it is a comment" do
+ [1, 2, 3].pack("#this is a comment").should == ""
+ end
+
+ it "ignores multiple comments" do
+ [1, 2, 3].pack("c#comment\nc#comment\nc#c").should == "\x01\x02\x03"
+ end
+end
diff --git a/spec/ruby/core/array/pack/d_spec.rb b/spec/ruby/core/array/pack/d_spec.rb
new file mode 100644
index 0000000000..8bb3654633
--- /dev/null
+++ b/spec/ruby/core/array/pack/d_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/float'
+
+describe "Array#pack with format 'D'" do
+ it_behaves_like :array_pack_basic, 'D'
+ it_behaves_like :array_pack_basic_float, 'D'
+ it_behaves_like :array_pack_arguments, 'D'
+ it_behaves_like :array_pack_no_platform, 'D'
+ it_behaves_like :array_pack_numeric_basic, 'D'
+ it_behaves_like :array_pack_float, 'D'
+
+ little_endian do
+ it_behaves_like :array_pack_double_le, 'D'
+ end
+
+ big_endian do
+ it_behaves_like :array_pack_double_be, 'D'
+ end
+end
+
+describe "Array#pack with format 'd'" do
+ it_behaves_like :array_pack_basic, 'd'
+ it_behaves_like :array_pack_basic_float, 'd'
+ it_behaves_like :array_pack_arguments, 'd'
+ it_behaves_like :array_pack_no_platform, 'd'
+ it_behaves_like :array_pack_numeric_basic, 'd'
+ it_behaves_like :array_pack_float, 'd'
+
+ little_endian do
+ it_behaves_like :array_pack_double_le, 'd'
+ end
+
+ big_endian do
+ it_behaves_like :array_pack_double_be, 'd'
+ end
+end
diff --git a/spec/ruby/core/array/pack/e_spec.rb b/spec/ruby/core/array/pack/e_spec.rb
new file mode 100644
index 0000000000..ab61ef578f
--- /dev/null
+++ b/spec/ruby/core/array/pack/e_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/float'
+
+describe "Array#pack with format 'E'" do
+ it_behaves_like :array_pack_basic, 'E'
+ it_behaves_like :array_pack_basic_float, 'E'
+ it_behaves_like :array_pack_arguments, 'E'
+ it_behaves_like :array_pack_no_platform, 'E'
+ it_behaves_like :array_pack_numeric_basic, 'E'
+ it_behaves_like :array_pack_float, 'E'
+ it_behaves_like :array_pack_double_le, 'E'
+end
+
+describe "Array#pack with format 'e'" do
+ it_behaves_like :array_pack_basic, 'e'
+ it_behaves_like :array_pack_basic_float, 'e'
+ it_behaves_like :array_pack_arguments, 'e'
+ it_behaves_like :array_pack_no_platform, 'e'
+ it_behaves_like :array_pack_numeric_basic, 'e'
+ it_behaves_like :array_pack_float, 'e'
+ it_behaves_like :array_pack_float_le, 'e'
+end
diff --git a/spec/ruby/core/array/pack/empty_spec.rb b/spec/ruby/core/array/pack/empty_spec.rb
new file mode 100644
index 0000000000..d635d6a563
--- /dev/null
+++ b/spec/ruby/core/array/pack/empty_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+describe "Array#pack with empty format" do
+ it "returns an empty String" do
+ [1, 2, 3].pack("").should == ""
+ end
+
+ it "returns a String with US-ASCII encoding" do
+ [1, 2, 3].pack("").encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/array/pack/f_spec.rb b/spec/ruby/core/array/pack/f_spec.rb
new file mode 100644
index 0000000000..d436e0787c
--- /dev/null
+++ b/spec/ruby/core/array/pack/f_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/float'
+
+describe "Array#pack with format 'F'" do
+ it_behaves_like :array_pack_basic, 'F'
+ it_behaves_like :array_pack_basic_float, 'F'
+ it_behaves_like :array_pack_arguments, 'F'
+ it_behaves_like :array_pack_no_platform, 'F'
+ it_behaves_like :array_pack_numeric_basic, 'F'
+ it_behaves_like :array_pack_float, 'F'
+
+ little_endian do
+ it_behaves_like :array_pack_float_le, 'F'
+ end
+
+ big_endian do
+ it_behaves_like :array_pack_float_be, 'F'
+ end
+end
+
+describe "Array#pack with format 'f'" do
+ it_behaves_like :array_pack_basic, 'f'
+ it_behaves_like :array_pack_basic_float, 'f'
+ it_behaves_like :array_pack_arguments, 'f'
+ it_behaves_like :array_pack_no_platform, 'f'
+ it_behaves_like :array_pack_numeric_basic, 'f'
+ it_behaves_like :array_pack_float, 'f'
+
+ little_endian do
+ it_behaves_like :array_pack_float_le, 'f'
+ end
+
+ big_endian do
+ it_behaves_like :array_pack_float_be, 'f'
+ end
+end
diff --git a/spec/ruby/core/array/pack/g_spec.rb b/spec/ruby/core/array/pack/g_spec.rb
new file mode 100644
index 0000000000..83b7f81acc
--- /dev/null
+++ b/spec/ruby/core/array/pack/g_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/float'
+
+describe "Array#pack with format 'G'" do
+ it_behaves_like :array_pack_basic, 'G'
+ it_behaves_like :array_pack_basic_float, 'G'
+ it_behaves_like :array_pack_arguments, 'G'
+ it_behaves_like :array_pack_no_platform, 'G'
+ it_behaves_like :array_pack_numeric_basic, 'G'
+ it_behaves_like :array_pack_float, 'G'
+ it_behaves_like :array_pack_double_be, 'G'
+end
+
+describe "Array#pack with format 'g'" do
+ it_behaves_like :array_pack_basic, 'g'
+ it_behaves_like :array_pack_basic_float, 'g'
+ it_behaves_like :array_pack_arguments, 'g'
+ it_behaves_like :array_pack_no_platform, 'g'
+ it_behaves_like :array_pack_numeric_basic, 'g'
+ it_behaves_like :array_pack_float, 'g'
+ it_behaves_like :array_pack_float_be, 'g'
+end
diff --git a/spec/ruby/core/array/pack/h_spec.rb b/spec/ruby/core/array/pack/h_spec.rb
new file mode 100644
index 0000000000..1492d02b1f
--- /dev/null
+++ b/spec/ruby/core/array/pack/h_spec.rb
@@ -0,0 +1,205 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/encodings'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'H'" do
+ it_behaves_like :array_pack_basic, 'H'
+ it_behaves_like :array_pack_basic_non_float, 'H'
+ it_behaves_like :array_pack_arguments, 'H'
+ it_behaves_like :array_pack_hex, 'H'
+ it_behaves_like :array_pack_taint, 'H'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack H string")
+ obj.should_receive(:to_str).and_return("a")
+ [obj].pack("H").should == "\xa0"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('H') }.should.raise(TypeError)
+ -> { [0].pack('h') }.should.raise(TypeError)
+ end
+
+ it "encodes the first character as the most significant nibble when passed no count modifier" do
+ ["ab"].pack("H").should == "\xa0"
+ end
+
+ it "implicitly has count equal to the string length when passed the '*' modifier" do
+ ["deadbeef"].pack("H*").should == "\xde\xad\xbe\xef"
+ end
+
+ it "encodes count nibbles when passed a count modifier exceeding the string length" do
+ ["ab"].pack('H8').should == "\xab\x00\x00\x00"
+ end
+
+ it "encodes the first character as the most significant nibble of a hex value" do
+ [ [["0"], "\x00"],
+ [["1"], "\x10"],
+ [["2"], "\x20"],
+ [["3"], "\x30"],
+ [["4"], "\x40"],
+ [["5"], "\x50"],
+ [["6"], "\x60"],
+ [["7"], "\x70"],
+ [["8"], "\x80"],
+ [["9"], "\x90"],
+ [["a"], "\xa0"],
+ [["b"], "\xb0"],
+ [["c"], "\xc0"],
+ [["d"], "\xd0"],
+ [["e"], "\xe0"],
+ [["f"], "\xf0"],
+ [["A"], "\xa0"],
+ [["B"], "\xb0"],
+ [["C"], "\xc0"],
+ [["D"], "\xd0"],
+ [["E"], "\xe0"],
+ [["F"], "\xf0"]
+ ].should be_computed_by(:pack, "H")
+ end
+
+ it "encodes the second character as the least significant nibble of a hex value" do
+ [ [["00"], "\x00"],
+ [["01"], "\x01"],
+ [["02"], "\x02"],
+ [["03"], "\x03"],
+ [["04"], "\x04"],
+ [["05"], "\x05"],
+ [["06"], "\x06"],
+ [["07"], "\x07"],
+ [["08"], "\x08"],
+ [["09"], "\x09"],
+ [["0a"], "\x0a"],
+ [["0b"], "\x0b"],
+ [["0c"], "\x0c"],
+ [["0d"], "\x0d"],
+ [["0e"], "\x0e"],
+ [["0f"], "\x0f"],
+ [["0A"], "\x0a"],
+ [["0B"], "\x0b"],
+ [["0C"], "\x0c"],
+ [["0D"], "\x0d"],
+ [["0E"], "\x0e"],
+ [["0F"], "\x0f"]
+ ].should be_computed_by(:pack, "H2")
+ end
+
+ it "encodes the least significant nibble of a non alphanumeric character as the most significant nibble of the hex value" do
+ [ [["^"], "\xe0"],
+ [["*"], "\xa0"],
+ [["#"], "\x30"],
+ [["["], "\xb0"],
+ [["]"], "\xd0"],
+ [["@"], "\x00"],
+ [["!"], "\x10"],
+ [["H"], "\x10"],
+ [["O"], "\x80"],
+ [["T"], "\xd0"],
+ [["Z"], "\x30"],
+ ].should be_computed_by(:pack, "H")
+ end
+
+ it "returns a binary string" do
+ ["41"].pack("H").encoding.should == Encoding::BINARY
+ end
+end
+
+describe "Array#pack with format 'h'" do
+ it_behaves_like :array_pack_basic, 'h'
+ it_behaves_like :array_pack_basic_non_float, 'h'
+ it_behaves_like :array_pack_arguments, 'h'
+ it_behaves_like :array_pack_hex, 'h'
+ it_behaves_like :array_pack_taint, 'h'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack H string")
+ obj.should_receive(:to_str).and_return("a")
+ [obj].pack("h").should == "\x0a"
+ end
+
+ it "encodes the first character as the least significant nibble when passed no count modifier" do
+ ["ab"].pack("h").should == "\x0a"
+ end
+
+ it "implicitly has count equal to the string length when passed the '*' modifier" do
+ ["deadbeef"].pack("h*").should == "\xed\xda\xeb\xfe"
+ end
+
+ it "encodes count nibbles when passed a count modifier exceeding the string length" do
+ ["ab"].pack('h8').should == "\xba\x00\x00\x00"
+ end
+
+ it "encodes the first character as the least significant nibble of a hex value" do
+ [ [["0"], "\x00"],
+ [["1"], "\x01"],
+ [["2"], "\x02"],
+ [["3"], "\x03"],
+ [["4"], "\x04"],
+ [["5"], "\x05"],
+ [["6"], "\x06"],
+ [["7"], "\x07"],
+ [["8"], "\x08"],
+ [["9"], "\x09"],
+ [["a"], "\x0a"],
+ [["b"], "\x0b"],
+ [["c"], "\x0c"],
+ [["d"], "\x0d"],
+ [["e"], "\x0e"],
+ [["f"], "\x0f"],
+ [["A"], "\x0a"],
+ [["B"], "\x0b"],
+ [["C"], "\x0c"],
+ [["D"], "\x0d"],
+ [["E"], "\x0e"],
+ [["F"], "\x0f"]
+ ].should be_computed_by(:pack, "h")
+ end
+
+ it "encodes the second character as the most significant nibble of a hex value" do
+ [ [["00"], "\x00"],
+ [["01"], "\x10"],
+ [["02"], "\x20"],
+ [["03"], "\x30"],
+ [["04"], "\x40"],
+ [["05"], "\x50"],
+ [["06"], "\x60"],
+ [["07"], "\x70"],
+ [["08"], "\x80"],
+ [["09"], "\x90"],
+ [["0a"], "\xa0"],
+ [["0b"], "\xb0"],
+ [["0c"], "\xc0"],
+ [["0d"], "\xd0"],
+ [["0e"], "\xe0"],
+ [["0f"], "\xf0"],
+ [["0A"], "\xa0"],
+ [["0B"], "\xb0"],
+ [["0C"], "\xc0"],
+ [["0D"], "\xd0"],
+ [["0E"], "\xe0"],
+ [["0F"], "\xf0"]
+ ].should be_computed_by(:pack, "h2")
+ end
+
+ it "encodes the least significant nibble of a non alphanumeric character as the least significant nibble of the hex value" do
+ [ [["^"], "\x0e"],
+ [["*"], "\x0a"],
+ [["#"], "\x03"],
+ [["["], "\x0b"],
+ [["]"], "\x0d"],
+ [["@"], "\x00"],
+ [["!"], "\x01"],
+ [["H"], "\x01"],
+ [["O"], "\x08"],
+ [["T"], "\x0d"],
+ [["Z"], "\x03"],
+ ].should be_computed_by(:pack, "h")
+ end
+
+ it "returns a binary string" do
+ ["41"].pack("h").encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/array/pack/i_spec.rb b/spec/ruby/core/array/pack/i_spec.rb
new file mode 100644
index 0000000000..a237071227
--- /dev/null
+++ b/spec/ruby/core/array/pack/i_spec.rb
@@ -0,0 +1,133 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'I'" do
+ it_behaves_like :array_pack_basic, 'I'
+ it_behaves_like :array_pack_basic_non_float, 'I'
+ it_behaves_like :array_pack_arguments, 'I'
+ it_behaves_like :array_pack_numeric_basic, 'I'
+ it_behaves_like :array_pack_integer, 'I'
+end
+
+describe "Array#pack with format 'i'" do
+ it_behaves_like :array_pack_basic, 'i'
+ it_behaves_like :array_pack_basic_non_float, 'i'
+ it_behaves_like :array_pack_arguments, 'i'
+ it_behaves_like :array_pack_numeric_basic, 'i'
+ it_behaves_like :array_pack_integer, 'i'
+end
+
+describe "Array#pack with format 'I'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_32bit_le, 'I<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'I<_'
+ it_behaves_like :array_pack_32bit_le, 'I_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'I<!'
+ it_behaves_like :array_pack_32bit_le, 'I!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_32bit_be, 'I>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'I>_'
+ it_behaves_like :array_pack_32bit_be, 'I_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'I>!'
+ it_behaves_like :array_pack_32bit_be, 'I!>'
+ end
+end
+
+describe "Array#pack with format 'i'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_32bit_le, 'i<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'i<_'
+ it_behaves_like :array_pack_32bit_le, 'i_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'i<!'
+ it_behaves_like :array_pack_32bit_le, 'i!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_32bit_be, 'i>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'i>_'
+ it_behaves_like :array_pack_32bit_be, 'i_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'i>!'
+ it_behaves_like :array_pack_32bit_be, 'i!>'
+ end
+end
+
+little_endian do
+ describe "Array#pack with format 'I'" do
+ it_behaves_like :array_pack_32bit_le, 'I'
+ end
+
+ describe "Array#pack with format 'I' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le_platform, 'I_'
+ end
+
+ describe "Array#pack with format 'I' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le_platform, 'I!'
+ end
+
+ describe "Array#pack with format 'i'" do
+ it_behaves_like :array_pack_32bit_le, 'i'
+ end
+
+ describe "Array#pack with format 'i' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le_platform, 'i_'
+ end
+
+ describe "Array#pack with format 'i' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le_platform, 'i!'
+ end
+end
+
+big_endian do
+ describe "Array#pack with format 'I'" do
+ it_behaves_like :array_pack_32bit_be, 'I'
+ end
+
+ describe "Array#pack with format 'I' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be_platform, 'I_'
+ end
+
+ describe "Array#pack with format 'I' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be_platform, 'I!'
+ end
+
+ describe "Array#pack with format 'i'" do
+ it_behaves_like :array_pack_32bit_be, 'i'
+ end
+
+ describe "Array#pack with format 'i' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be_platform, 'i_'
+ end
+
+ describe "Array#pack with format 'i' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be_platform, 'i!'
+ end
+end
diff --git a/spec/ruby/core/array/pack/j_spec.rb b/spec/ruby/core/array/pack/j_spec.rb
new file mode 100644
index 0000000000..7b62d5efdf
--- /dev/null
+++ b/spec/ruby/core/array/pack/j_spec.rb
@@ -0,0 +1,217 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+platform_is pointer_size: 64 do
+ describe "Array#pack with format 'J'" do
+ it_behaves_like :array_pack_basic, 'J'
+ it_behaves_like :array_pack_basic_non_float, 'J'
+ it_behaves_like :array_pack_arguments, 'J'
+ it_behaves_like :array_pack_numeric_basic, 'J'
+ it_behaves_like :array_pack_integer, 'J'
+ end
+
+ describe "Array#pack with format 'j'" do
+ it_behaves_like :array_pack_basic, 'j'
+ it_behaves_like :array_pack_basic_non_float, 'j'
+ it_behaves_like :array_pack_arguments, 'j'
+ it_behaves_like :array_pack_numeric_basic, 'j'
+ it_behaves_like :array_pack_integer, 'j'
+ end
+
+ little_endian do
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_64bit_le, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_64bit_le, 'J!'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_64bit_le, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_64bit_le, 'j!'
+ end
+ end
+ end
+
+ big_endian do
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_64bit_be, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_64bit_be, 'J!'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_64bit_be, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_64bit_be, 'j!'
+ end
+ end
+ end
+
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_64bit_le, 'J<_'
+ it_behaves_like :array_pack_64bit_le, 'J_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_64bit_le, 'J<!'
+ it_behaves_like :array_pack_64bit_le, 'J!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_64bit_be, 'J>_'
+ it_behaves_like :array_pack_64bit_be, 'J_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_64bit_be, 'J>!'
+ it_behaves_like :array_pack_64bit_be, 'J!>'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_64bit_le, 'j<_'
+ it_behaves_like :array_pack_64bit_le, 'j_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_64bit_le, 'j<!'
+ it_behaves_like :array_pack_64bit_le, 'j!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_64bit_be, 'j>_'
+ it_behaves_like :array_pack_64bit_be, 'j_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_64bit_be, 'j>!'
+ it_behaves_like :array_pack_64bit_be, 'j!>'
+ end
+ end
+end
+
+platform_is pointer_size: 32 do
+ describe "Array#pack with format 'J'" do
+ it_behaves_like :array_pack_basic, 'J'
+ it_behaves_like :array_pack_basic_non_float, 'J'
+ it_behaves_like :array_pack_arguments, 'J'
+ it_behaves_like :array_pack_numeric_basic, 'J'
+ it_behaves_like :array_pack_integer, 'J'
+ end
+
+ describe "Array#pack with format 'j'" do
+ it_behaves_like :array_pack_basic, 'j'
+ it_behaves_like :array_pack_basic_non_float, 'j'
+ it_behaves_like :array_pack_arguments, 'j'
+ it_behaves_like :array_pack_numeric_basic, 'j'
+ it_behaves_like :array_pack_integer, 'j'
+ end
+
+ big_endian do
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be, 'J!'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be, 'j!'
+ end
+ end
+ end
+
+ little_endian do
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le, 'J_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le, 'J!'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le, 'j_'
+ end
+
+ describe "with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le, 'j!'
+ end
+ end
+ end
+
+ describe "Array#pack with format 'J'" do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'J<_'
+ it_behaves_like :array_pack_32bit_le, 'J_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'J<!'
+ it_behaves_like :array_pack_32bit_le, 'J!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'J>_'
+ it_behaves_like :array_pack_32bit_be, 'J_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'J>!'
+ it_behaves_like :array_pack_32bit_be, 'J!>'
+ end
+ end
+
+ describe "Array#pack with format 'j'" do
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_32bit_le, 'j<_'
+ it_behaves_like :array_pack_32bit_le, 'j_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'j<!'
+ it_behaves_like :array_pack_32bit_le, 'j!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'j>_'
+ it_behaves_like :array_pack_32bit_be, 'j_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'j>!'
+ it_behaves_like :array_pack_32bit_be, 'j!>'
+ end
+ end
+end
diff --git a/spec/ruby/core/array/pack/l_spec.rb b/spec/ruby/core/array/pack/l_spec.rb
new file mode 100644
index 0000000000..f6dfb1da83
--- /dev/null
+++ b/spec/ruby/core/array/pack/l_spec.rb
@@ -0,0 +1,221 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'L'" do
+ it_behaves_like :array_pack_basic, 'L'
+ it_behaves_like :array_pack_basic_non_float, 'L'
+ it_behaves_like :array_pack_arguments, 'L'
+ it_behaves_like :array_pack_numeric_basic, 'L'
+ it_behaves_like :array_pack_integer, 'L'
+end
+
+describe "Array#pack with format 'l'" do
+ it_behaves_like :array_pack_basic, 'l'
+ it_behaves_like :array_pack_basic_non_float, 'l'
+ it_behaves_like :array_pack_arguments, 'l'
+ it_behaves_like :array_pack_numeric_basic, 'l'
+ it_behaves_like :array_pack_integer, 'l'
+end
+
+describe "Array#pack with format 'L'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_32bit_le, 'L<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_32bit_be, 'L>'
+ end
+
+ 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_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'L<!'
+ it_behaves_like :array_pack_32bit_le, 'L!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'L>_'
+ it_behaves_like :array_pack_32bit_be, 'L_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'L>!'
+ it_behaves_like :array_pack_32bit_be, 'L!>'
+ end
+ end
+
+ 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_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_64bit_le, 'L<!'
+ it_behaves_like :array_pack_64bit_le, 'L!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_64bit_be, 'L>_'
+ it_behaves_like :array_pack_64bit_be, 'L_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_64bit_be, 'L>!'
+ it_behaves_like :array_pack_64bit_be, 'L!>'
+ end
+ end
+end
+
+describe "Array#pack with format 'l'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_32bit_le, 'l<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_32bit_be, 'l>'
+ end
+
+ 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_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_32bit_le, 'l<!'
+ it_behaves_like :array_pack_32bit_le, 'l!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_32bit_be, 'l>_'
+ it_behaves_like :array_pack_32bit_be, 'l_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_32bit_be, 'l>!'
+ it_behaves_like :array_pack_32bit_be, 'l!>'
+ end
+ end
+
+ 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_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_64bit_le, 'l<!'
+ it_behaves_like :array_pack_64bit_le, 'l!<'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_64bit_be, 'l>_'
+ it_behaves_like :array_pack_64bit_be, 'l_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_64bit_be, 'l>!'
+ it_behaves_like :array_pack_64bit_be, 'l!>'
+ end
+ end
+end
+
+little_endian do
+ describe "Array#pack with format 'L'" do
+ it_behaves_like :array_pack_32bit_le, 'L'
+ end
+
+ describe "Array#pack with format 'l'" do
+ it_behaves_like :array_pack_32bit_le, 'l'
+ end
+
+ 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
+
+ describe "Array#pack with format 'L' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le, 'L!'
+ end
+
+ describe "Array#pack with format 'l' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_le, 'l_'
+ end
+
+ describe "Array#pack with format 'l' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_le, 'l!'
+ end
+ end
+
+ 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
+
+ describe "Array#pack with format 'L' with modifier '!'" do
+ it_behaves_like :array_pack_64bit_le, 'L!'
+ end
+
+ describe "Array#pack with format 'l' with modifier '_'" do
+ it_behaves_like :array_pack_64bit_le, 'l_'
+ end
+
+ describe "Array#pack with format 'l' with modifier '!'" do
+ it_behaves_like :array_pack_64bit_le, 'l!'
+ end
+ end
+end
+
+big_endian do
+ describe "Array#pack with format 'L'" do
+ it_behaves_like :array_pack_32bit_be, 'L'
+ end
+
+ describe "Array#pack with format 'l'" do
+ it_behaves_like :array_pack_32bit_be, 'l'
+ end
+
+ 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
+
+ describe "Array#pack with format 'L' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be, 'L!'
+ end
+
+ describe "Array#pack with format 'l' with modifier '_'" do
+ it_behaves_like :array_pack_32bit_be, 'l_'
+ end
+
+ describe "Array#pack with format 'l' with modifier '!'" do
+ it_behaves_like :array_pack_32bit_be, 'l!'
+ end
+ end
+
+ 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
+
+ describe "Array#pack with format 'L' with modifier '!'" do
+ it_behaves_like :array_pack_64bit_be, 'L!'
+ end
+
+ describe "Array#pack with format 'l' with modifier '_'" do
+ it_behaves_like :array_pack_64bit_be, 'l_'
+ end
+
+ describe "Array#pack with format 'l' with modifier '!'" do
+ it_behaves_like :array_pack_64bit_be, 'l!'
+ end
+ end
+end
diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb
new file mode 100644
index 0000000000..fb670d120e
--- /dev/null
+++ b/spec/ruby/core/array/pack/m_spec.rb
@@ -0,0 +1,317 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'M'" do
+ it_behaves_like :array_pack_basic, 'M'
+ it_behaves_like :array_pack_basic_non_float, 'M'
+ it_behaves_like :array_pack_arguments, 'M'
+ it_behaves_like :array_pack_taint, 'M'
+
+ it "encodes an empty string as an empty string" do
+ [""].pack("M").should == ""
+ end
+
+ it "encodes nil as an empty string" do
+ [nil].pack("M").should == ""
+ end
+
+ it "appends a soft line break at the end of an encoded string" do
+ ["a"].pack("M").should == "a=\n"
+ end
+
+ it "does not append a soft break if the string ends with a newline" do
+ ["a\n"].pack("M").should == "a\n"
+ end
+
+ it "encodes one element for each directive" do
+ ["a", "b", "c"].pack("MM").should == "a=\nb=\n"
+ end
+
+ it "encodes byte values 33..60 directly" do
+ [ [["!\"\#$%&'()*+,-./"], "!\"\#$%&'()*+,-./=\n"],
+ [["0123456789"], "0123456789=\n"],
+ [[":;<"], ":;<=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "encodes byte values 62..126 directly" do
+ [ [[">?@"], ">?@=\n"],
+ [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], "ABCDEFGHIJKLMNOPQRSTUVWXYZ=\n"],
+ [["[\\]^_`"], "[\\]^_`=\n"],
+ [["abcdefghijklmnopqrstuvwxyz"], "abcdefghijklmnopqrstuvwxyz=\n"],
+ [["{|}~"], "{|}~=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "encodes an '=' character in hex format" do
+ ["="].pack("M").should == "=3D=\n"
+ end
+
+ it "encodes an embedded space directly" do
+ ["a b"].pack("M").should == "a b=\n"
+ end
+
+ it "encodes a space at the end of the string directly" do
+ ["a "].pack("M").should == "a =\n"
+ end
+
+ it "encodes an embedded tab directly" do
+ ["a\tb"].pack("M").should == "a\tb=\n"
+ end
+
+ it "encodes a tab at the end of the string directly" do
+ ["a\t"].pack("M").should == "a\t=\n"
+ end
+
+ it "encodes an embedded newline directly" do
+ ["a\nb"].pack("M").should == "a\nb=\n"
+ end
+
+ it "encodes 0..31 except tab and newline in hex format" do
+ [ [["\x00\x01\x02\x03\x04\x05\x06"], "=00=01=02=03=04=05=06=\n"],
+ [["\a\b\v\f\r"], "=07=08=0B=0C=0D=\n"],
+ [["\x0e\x0f\x10\x11\x12\x13\x14"], "=0E=0F=10=11=12=13=14=\n"],
+ [["\x15\x16\x17\x18\x19\x1a"], "=15=16=17=18=19=1A=\n"],
+ [["\e"], "=1B=\n"],
+ [["\x1c\x1d\x1e\x1f"], "=1C=1D=1E=1F=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "encodes a tab at the end of a line with an encoded newline" do
+ ["\t"].pack("M").should == "\t=\n"
+ ["\t\n"].pack("M").should == "\t=\n\n"
+ ["abc\t\nxyz"].pack("M").should == "abc\t=\n\nxyz=\n"
+ end
+
+ it "encodes a space at the end of a line with an encoded newline" do
+ [" "].pack("M").should == " =\n"
+ [" \n"].pack("M").should == " =\n\n"
+ ["abc \nxyz"].pack("M").should == "abc =\n\nxyz=\n"
+ end
+
+ it "encodes 127..255 in hex format" do
+ [ [["\x7f\x80\x81\x82\x83\x84\x85\x86"], "=7F=80=81=82=83=84=85=86=\n"],
+ [["\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"], "=87=88=89=8A=8B=8C=8D=8E=\n"],
+ [["\x8f\x90\x91\x92\x93\x94\x95\x96"], "=8F=90=91=92=93=94=95=96=\n"],
+ [["\x97\x98\x99\x9a\x9b\x9c\x9d\x9e"], "=97=98=99=9A=9B=9C=9D=9E=\n"],
+ [["\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6"], "=9F=A0=A1=A2=A3=A4=A5=A6=\n"],
+ [["\xa7\xa8\xa9\xaa\xab\xac\xad\xae"], "=A7=A8=A9=AA=AB=AC=AD=AE=\n"],
+ [["\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6"], "=AF=B0=B1=B2=B3=B4=B5=B6=\n"],
+ [["\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe"], "=B7=B8=B9=BA=BB=BC=BD=BE=\n"],
+ [["\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6"], "=BF=C0=C1=C2=C3=C4=C5=C6=\n"],
+ [["\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce"], "=C7=C8=C9=CA=CB=CC=CD=CE=\n"],
+ [["\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6"], "=CF=D0=D1=D2=D3=D4=D5=D6=\n"],
+ [["\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde"], "=D7=D8=D9=DA=DB=DC=DD=DE=\n"],
+ [["\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6"], "=DF=E0=E1=E2=E3=E4=E5=E6=\n"],
+ [["\xe7\xe8\xe9\xea\xeb\xec\xed\xee"], "=E7=E8=E9=EA=EB=EC=ED=EE=\n"],
+ [["\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"], "=EF=F0=F1=F2=F3=F4=F5=F6=\n"],
+ [["\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe"], "=F7=F8=F9=FA=FB=FC=FD=FE=\n"],
+ [["\xff"], "=FF=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "emits a soft line break when the output exceeds 72 characters when passed '*', 0, 1, or no count modifier" do
+ s1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ r1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=\na=\n"
+ s2 = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19"
+ r2 = "=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=19=\n=19=\n"
+ s3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x15a"
+ r3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=15=\na=\n"
+ s4 = "\x15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x15a"
+ r4 = "=15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=\na=15a=\n"
+
+ [ [[s1], "M", r1],
+ [[s1], "M0", r1],
+ [[s1], "M1", r1],
+ [[s2], "M", r2],
+ [[s2], "M0", r2],
+ [[s2], "M1", r2],
+ [[s3], "M", r3],
+ [[s3], "M0", r3],
+ [[s3], "M1", r3],
+ [[s4], "M", r4],
+ [[s4], "M0", r4],
+ [[s4], "M1", r4]
+ ].should be_computed_by(:pack)
+ end
+
+ it "emits a soft line break when the output exceeds count characters" do
+ [ [["abcdefghi"], "M2", "abc=\ndef=\nghi=\n"],
+ [["abcdefghi"], "M3", "abcd=\nefgh=\ni=\n"],
+ [["abcdefghi"], "M4", "abcde=\nfghi=\n"],
+ [["abcdefghi"], "M5", "abcdef=\nghi=\n"],
+ [["abcdefghi"], "M6", "abcdefg=\nhi=\n"],
+ [["\x19\x19\x19\x19"], "M2", "=19=\n=19=\n=19=\n=19=\n"],
+ [["\x19\x19\x19\x19"], "M3", "=19=19=\n=19=19=\n"],
+ [["\x19\x19\x19\x19"], "M4", "=19=19=\n=19=19=\n"],
+ [["\x19\x19\x19\x19"], "M5", "=19=19=\n=19=19=\n"],
+ [["\x19\x19\x19\x19"], "M6", "=19=19=19=\n=19=\n"],
+ [["\x19\x19\x19\x19"], "M7", "=19=19=19=\n=19=\n"]
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes a recursive array" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.pack('M').should.instance_of?(String)
+
+ array = ArraySpecs.recursive_array
+ array.pack('M').should == "1=\n"
+ end
+
+ it "calls #to_s to convert an object to a String" do
+ obj = mock("pack M string")
+ obj.should_receive(:to_s).and_return("packing")
+
+ [obj].pack("M").should == "packing=\n"
+ end
+
+ it "converts the object to a String representation if #to_s does not return a String" do
+ obj = mock("pack M non-string")
+ obj.should_receive(:to_s).and_return(2)
+
+ [obj].pack("M").should.instance_of?(String)
+ end
+
+ it "encodes a Symbol as a String" do
+ [:symbol].pack("M").should == "symbol=\n"
+ end
+
+ it "encodes an Integer as a String" do
+ [ [[1], "1=\n"],
+ [[bignum_value], "#{bignum_value}=\n"]
+ ].should be_computed_by(:pack, "M")
+ end
+
+ it "encodes a Float as a String" do
+ [1.0].pack("M").should == "1.0=\n"
+ end
+
+ it "converts Floats to the minimum unique representation" do
+ [1.0 / 3.0].pack("M").should == "0.3333333333333333=\n"
+ end
+
+ it "sets the output string to US-ASCII encoding" do
+ ["abcd"].pack("M").encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe "Array#pack with format 'm'" do
+ it_behaves_like :array_pack_basic, 'm'
+ it_behaves_like :array_pack_basic_non_float, 'm'
+ it_behaves_like :array_pack_arguments, 'm'
+ it_behaves_like :array_pack_taint, 'm'
+
+ it "encodes an empty string as an empty string" do
+ [""].pack("m").should == ""
+ end
+
+ it "appends a newline to the end of the encoded string" do
+ ["a"].pack("m").should == "YQ==\n"
+ end
+
+ it "encodes one element per directive" do
+ ["abc", "DEF"].pack("mm").should == "YWJj\nREVG\n"
+ end
+
+ it "encodes 1, 2, or 3 characters in 4 output characters (Base64 encoding)" do
+ [ [["a"], "YQ==\n"],
+ [["ab"], "YWI=\n"],
+ [["abc"], "YWJj\n"],
+ [["abcd"], "YWJjZA==\n"],
+ [["abcde"], "YWJjZGU=\n"],
+ [["abcdef"], "YWJjZGVm\n"],
+ [["abcdefg"], "YWJjZGVmZw==\n"],
+ ].should be_computed_by(:pack, "m")
+ end
+
+ it "emits a newline after complete groups of count / 3 input characters when passed a count modifier" do
+ ["abcdefg"].pack("m3").should == "YWJj\nZGVm\nZw==\n"
+ end
+
+ it "implicitly has a count of 45 when passed '*', 1, 2 or no count modifier" do
+ s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ r = "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\nYWFhYWE=\n"
+ [ [[s], "m", r],
+ [[s], "m*", r],
+ [[s], "m1", r],
+ [[s], "m2", r],
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes all ascii characters" do
+ [ [["\x00\x01\x02\x03\x04\x05\x06"], "AAECAwQFBg==\n"],
+ [["\a\b\t\n\v\f\r"], "BwgJCgsMDQ==\n"],
+ [["\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"], "Dg8QERITFBUW\n"],
+ [["\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f"], "FxgZGhscHR4f\n"],
+ [["!\"\#$%&'()*+,-./"], "ISIjJCUmJygpKissLS4v\n"],
+ [["0123456789"], "MDEyMzQ1Njc4OQ==\n"],
+ [[":;<=>?@"], "Ojs8PT4/QA==\n"],
+ [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=\n"],
+ [["[\\]^_`"], "W1xdXl9g\n"],
+ [["abcdefghijklmnopqrstuvwxyz"], "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=\n"],
+ [["{|}~"], "e3x9fg==\n"],
+ [["\x7f\xc2\x80\xc2\x81\xc2\x82\xc2\x83"], "f8KAwoHCgsKD\n"],
+ [["\xc2\x84\xc2\x85\xc2\x86\xc2\x87\xc2"], "woTChcKGwofC\n"],
+ [["\x88\xc2\x89\xc2\x8a\xc2\x8b\xc2\x8c"], "iMKJworCi8KM\n"],
+ [["\xc2\x8d\xc2\x8e\xc2\x8f\xc2\x90\xc2"], "wo3CjsKPwpDC\n"],
+ [["\x91\xc2\x92\xc2\x93\xc2\x94\xc2\x95"], "kcKSwpPClMKV\n"],
+ [["\xc2\x96\xc2\x97\xc2\x98\xc2\x99\xc2"], "wpbCl8KYwpnC\n"],
+ [["\x9a\xc2\x9b\xc2\x9c\xc2\x9d\xc2\x9e"], "msKbwpzCncKe\n"],
+ [["\xc2\x9f\xc2\xa0\xc2\xa1\xc2\xa2\xc2"], "wp/CoMKhwqLC\n"],
+ [["\xa3\xc2\xa4\xc2\xa5\xc2\xa6\xc2\xa7"], "o8KkwqXCpsKn\n"],
+ [["\xc2\xa8\xc2\xa9\xc2\xaa\xc2\xab\xc2"], "wqjCqcKqwqvC\n"],
+ [["\xac\xc2\xad\xc2\xae\xc2\xaf\xc2\xb0"], "rMKtwq7Cr8Kw\n"],
+ [["\xc2\xb1\xc2\xb2\xc2\xb3\xc2\xb4\xc2"], "wrHCssKzwrTC\n"],
+ [["\xb5\xc2\xb6\xc2\xb7\xc2\xb8\xc2\xb9"], "tcK2wrfCuMK5\n"],
+ [["\xc2\xba\xc2\xbb\xc2\xbc\xc2\xbd\xc2"], "wrrCu8K8wr3C\n"],
+ [["\xbe\xc2\xbf\xc3\x80\xc3\x81\xc3\x82"], "vsK/w4DDgcOC\n"],
+ [["\xc3\x83\xc3\x84\xc3\x85\xc3\x86\xc3"], "w4PDhMOFw4bD\n"],
+ [["\x87\xc3\x88\xc3\x89\xc3\x8a\xc3\x8b"], "h8OIw4nDisOL\n"],
+ [["\xc3\x8c\xc3\x8d\xc3\x8e\xc3\x8f\xc3"], "w4zDjcOOw4/D\n"],
+ [["\x90\xc3\x91\xc3\x92\xc3\x93\xc3\x94"], "kMORw5LDk8OU\n"],
+ [["\xc3\x95\xc3\x96\xc3\x97\xc3\x98\xc3"], "w5XDlsOXw5jD\n"],
+ [["\x99\xc3\x9a\xc3\x9b\xc3\x9c\xc3\x9d"], "mcOaw5vDnMOd\n"],
+ [["\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3"], "w57Dn8Ogw6HD\n"],
+ [["\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6"], "osOjw6TDpcOm\n"],
+ [["\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3"], "w6fDqMOpw6rD\n"],
+ [["\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf"], "q8Osw63DrsOv\n"],
+ [["\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3"], "w7DDscOyw7PD\n"],
+ [["\xb4\xc3\xb5\xc3\xb6\xc3\xb7\xc3\xb8"], "tMO1w7bDt8O4\n"],
+ [["\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3"], "w7nDusO7w7zD\n"],
+ [["\xbd\xc3\xbe\xc3\xbf"], "vcO+w78=\n"]
+ ].should be_computed_by(:pack, "m")
+ end
+
+ it "calls #to_str to convert an object to a String" do
+ obj = mock("pack m string")
+ obj.should_receive(:to_str).and_return("abc")
+ [obj].pack("m").should == "YWJj\n"
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("pack m non-string")
+ -> { [obj].pack("m") }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> { [nil].pack("m") }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> { [0].pack("m") }.should.raise(TypeError)
+ -> { [bignum_value].pack("m") }.should.raise(TypeError)
+ end
+
+ it "does not emit a newline if passed zero as the count modifier" do
+ s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ r = "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE="
+ [s].pack("m0").should == r
+ end
+
+ it "sets the output string to US-ASCII encoding" do
+ ["abcd"].pack("m").encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/array/pack/n_spec.rb b/spec/ruby/core/array/pack/n_spec.rb
new file mode 100644
index 0000000000..ab9409fc1e
--- /dev/null
+++ b/spec/ruby/core/array/pack/n_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'N'" do
+ it_behaves_like :array_pack_basic, 'N'
+ it_behaves_like :array_pack_basic_non_float, 'N'
+ it_behaves_like :array_pack_arguments, 'N'
+ it_behaves_like :array_pack_numeric_basic, 'N'
+ it_behaves_like :array_pack_integer, 'N'
+ it_behaves_like :array_pack_no_platform, 'N'
+ it_behaves_like :array_pack_32bit_be, 'N'
+end
+
+describe "Array#pack with format 'n'" do
+ it_behaves_like :array_pack_basic, 'n'
+ it_behaves_like :array_pack_basic_non_float, 'n'
+ it_behaves_like :array_pack_arguments, 'n'
+ it_behaves_like :array_pack_numeric_basic, 'n'
+ it_behaves_like :array_pack_integer, 'n'
+ it_behaves_like :array_pack_no_platform, 'n'
+ it_behaves_like :array_pack_16bit_be, 'n'
+end
diff --git a/spec/ruby/core/array/pack/p_spec.rb b/spec/ruby/core/array/pack/p_spec.rb
new file mode 100644
index 0000000000..b023bf9110
--- /dev/null
+++ b/spec/ruby/core/array/pack/p_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'P'" do
+ it_behaves_like :array_pack_basic_non_float, 'P'
+ it_behaves_like :array_pack_taint, 'P'
+
+ it "produces as many bytes as there are in a pointer" do
+ ["hello"].pack("P").size.should == [0].pack("J").size
+ end
+
+ it "round-trips a string through pack and unpack" do
+ ["hello"].pack("P").unpack("P5").should == ["hello"]
+ end
+
+ it "with nil gives a null pointer" do
+ [nil].pack("P").unpack("J").should == [0]
+ end
+end
+
+describe "Array#pack with format 'p'" do
+ it_behaves_like :array_pack_basic_non_float, 'p'
+ it_behaves_like :array_pack_taint, 'p'
+
+ it "produces as many bytes as there are in a pointer" do
+ ["hello"].pack("p").size.should == [0].pack("J").size
+ end
+
+ it "round-trips a string through pack and unpack" do
+ ["hello"].pack("p").unpack("p").should == ["hello"]
+ end
+
+ it "with nil gives a null pointer" do
+ [nil].pack("p").unpack("J").should == [0]
+ end
+end
diff --git a/spec/ruby/core/array/pack/percent_spec.rb b/spec/ruby/core/array/pack/percent_spec.rb
new file mode 100644
index 0000000000..29b119732a
--- /dev/null
+++ b/spec/ruby/core/array/pack/percent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+
+describe "Array#pack with format '%'" do
+ it "raises an Argument Error" do
+ -> { [1].pack("%") }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/q_spec.rb b/spec/ruby/core/array/pack/q_spec.rb
new file mode 100644
index 0000000000..bd6b2a4b71
--- /dev/null
+++ b/spec/ruby/core/array/pack/q_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'Q'" do
+ it_behaves_like :array_pack_basic, 'Q'
+ it_behaves_like :array_pack_basic_non_float, 'Q'
+ it_behaves_like :array_pack_arguments, 'Q'
+ it_behaves_like :array_pack_numeric_basic, 'Q'
+ it_behaves_like :array_pack_integer, 'Q'
+end
+
+describe "Array#pack with format 'q'" do
+ it_behaves_like :array_pack_basic, 'q'
+ it_behaves_like :array_pack_basic_non_float, 'q'
+ it_behaves_like :array_pack_arguments, 'q'
+ it_behaves_like :array_pack_numeric_basic, 'q'
+ it_behaves_like :array_pack_integer, 'q'
+end
+
+describe "Array#pack with format 'Q'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_64bit_le, 'Q<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_64bit_be, 'Q>'
+ end
+end
+
+describe "Array#pack with format 'q'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_64bit_le, 'q<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_64bit_be, 'q>'
+ end
+end
+
+little_endian do
+ describe "Array#pack with format 'Q'" do
+ it_behaves_like :array_pack_64bit_le, 'Q'
+ end
+
+ describe "Array#pack with format 'q'" do
+ it_behaves_like :array_pack_64bit_le, 'q'
+ end
+end
+
+big_endian do
+ describe "Array#pack with format 'Q'" do
+ it_behaves_like :array_pack_64bit_be, 'Q'
+ end
+
+ describe "Array#pack with format 'q'" do
+ it_behaves_like :array_pack_64bit_be, 'q'
+ end
+end
diff --git a/spec/ruby/core/array/pack/r_spec.rb b/spec/ruby/core/array/pack/r_spec.rb
new file mode 100644
index 0000000000..cefe1388d2
--- /dev/null
+++ b/spec/ruby/core/array/pack/r_spec.rb
@@ -0,0 +1,89 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+ruby_version_is "4.1" do
+ describe "Array#pack with format 'R'" do
+ it_behaves_like :array_pack_basic, 'R'
+ it_behaves_like :array_pack_basic_non_float, 'R'
+ it_behaves_like :array_pack_arguments, 'R'
+ it_behaves_like :array_pack_numeric_basic, 'R'
+ it_behaves_like :array_pack_integer, 'R'
+
+ it "encodes a ULEB128 integer" do
+ [ [[0], "\x00"],
+ [[1], "\x01"],
+ [[127], "\x7f"],
+ [[128], "\x80\x01"],
+ [[0x3fff], "\xff\x7f"],
+ [[0x4000], "\x80\x80\x01"],
+ [[0xffffffff], "\xff\xff\xff\xff\x0f"],
+ [[0x100000000], "\x80\x80\x80\x80\x10"],
+ [[0xffff_ffff_ffff_ffff], "\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"],
+ [[0xffff_ffff_ffff_ffff_ffff_ffff], "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f"],
+ ].should be_computed_by(:pack, "R")
+ end
+
+ it "encodes multiple values with '*' modifier" do
+ [1, 2].pack("R*").should == "\x01\x02"
+ [127, 128].pack("R*").should == "\x7f\x80\x01"
+ end
+
+ it "raises an ArgumentError when passed a negative value" do
+ -> { [-1].pack("R") }.should.raise(ArgumentError)
+ -> { [-100].pack("R") }.should.raise(ArgumentError)
+ end
+
+ it "round-trips values through pack and unpack" do
+ values = [0, 1, 127, 128, 0x3fff, 0x4000, 0xffffffff, 0x100000000]
+ values.pack("R*").unpack("R*").should == values
+ end
+ end
+
+ describe "Array#pack with format 'r'" do
+ it_behaves_like :array_pack_basic, 'r'
+ it_behaves_like :array_pack_basic_non_float, 'r'
+ it_behaves_like :array_pack_arguments, 'r'
+ it_behaves_like :array_pack_numeric_basic, 'r'
+ it_behaves_like :array_pack_integer, 'r'
+
+ it "encodes a SLEB128 integer" do
+ [ [[0], "\x00"],
+ [[1], "\x01"],
+ [[-1], "\x7f"],
+ [[-2], "\x7e"],
+ [[127], "\xff\x00"],
+ [[128], "\x80\x01"],
+ [[-127], "\x81\x7f"],
+ [[-128], "\x80\x7f"],
+ ].should be_computed_by(:pack, "r")
+ end
+
+ it "encodes larger positive numbers" do
+ [0x3fff].pack("r").should == "\xff\xff\x00"
+ [0x4000].pack("r").should == "\x80\x80\x01"
+ end
+
+ it "encodes larger negative numbers" do
+ [-0x3fff].pack("r").should == "\x81\x80\x7f"
+ [-0x4000].pack("r").should == "\x80\x80\x7f"
+ end
+
+ it "encodes very large numbers" do
+ [0xffff_ffff_ffff_ffff_ffff_ffff].pack("r").should == "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x1F"
+ [-0xffff_ffff_ffff_ffff_ffff_ffff].pack("r").should == "\x81\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x60"
+ end
+
+ it "encodes multiple values with '*' modifier" do
+ [0, 1, -1].pack("r*").should == "\x00\x01\x7f"
+ end
+
+ it "round-trips values through pack and unpack" do
+ values = [0, 1, -1, 127, -127, 128, -128, 0x3fff, -0x3fff, 0x4000, -0x4000]
+ values.pack("r*").unpack("r*").should == values
+ end
+ end
+end
diff --git a/spec/ruby/core/array/pack/s_spec.rb b/spec/ruby/core/array/pack/s_spec.rb
new file mode 100644
index 0000000000..4212d6a0b1
--- /dev/null
+++ b/spec/ruby/core/array/pack/s_spec.rb
@@ -0,0 +1,133 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'S'" do
+ it_behaves_like :array_pack_basic, 'S'
+ it_behaves_like :array_pack_basic_non_float, 'S'
+ it_behaves_like :array_pack_arguments, 'S'
+ it_behaves_like :array_pack_numeric_basic, 'S'
+ it_behaves_like :array_pack_integer, 'S'
+end
+
+describe "Array#pack with format 's'" do
+ it_behaves_like :array_pack_basic, 's'
+ it_behaves_like :array_pack_basic_non_float, 's'
+ it_behaves_like :array_pack_arguments, 's'
+ it_behaves_like :array_pack_numeric_basic, 's'
+ it_behaves_like :array_pack_integer, 's'
+end
+
+describe "Array#pack with format 'S'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_16bit_le, 'S<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_16bit_le, 'S<_'
+ it_behaves_like :array_pack_16bit_le, 'S_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_16bit_le, 'S<!'
+ it_behaves_like :array_pack_16bit_le, 'S!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_16bit_be, 'S>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_16bit_be, 'S>_'
+ it_behaves_like :array_pack_16bit_be, 'S_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_16bit_be, 'S>!'
+ it_behaves_like :array_pack_16bit_be, 'S!>'
+ end
+end
+
+describe "Array#pack with format 's'" do
+ describe "with modifier '<'" do
+ it_behaves_like :array_pack_16bit_le, 's<'
+ end
+
+ describe "with modifier '<' and '_'" do
+ it_behaves_like :array_pack_16bit_le, 's<_'
+ it_behaves_like :array_pack_16bit_le, 's_<'
+ end
+
+ describe "with modifier '<' and '!'" do
+ it_behaves_like :array_pack_16bit_le, 's<!'
+ it_behaves_like :array_pack_16bit_le, 's!<'
+ end
+
+ describe "with modifier '>'" do
+ it_behaves_like :array_pack_16bit_be, 's>'
+ end
+
+ describe "with modifier '>' and '_'" do
+ it_behaves_like :array_pack_16bit_be, 's>_'
+ it_behaves_like :array_pack_16bit_be, 's_>'
+ end
+
+ describe "with modifier '>' and '!'" do
+ it_behaves_like :array_pack_16bit_be, 's>!'
+ it_behaves_like :array_pack_16bit_be, 's!>'
+ end
+end
+
+little_endian do
+ describe "Array#pack with format 'S'" do
+ it_behaves_like :array_pack_16bit_le, 'S'
+ end
+
+ describe "Array#pack with format 'S' with modifier '_'" do
+ it_behaves_like :array_pack_16bit_le, 'S_'
+ end
+
+ describe "Array#pack with format 'S' with modifier '!'" do
+ it_behaves_like :array_pack_16bit_le, 'S!'
+ end
+
+ describe "Array#pack with format 's'" do
+ it_behaves_like :array_pack_16bit_le, 's'
+ end
+
+ describe "Array#pack with format 's' with modifier '_'" do
+ it_behaves_like :array_pack_16bit_le, 's_'
+ end
+
+ describe "Array#pack with format 's' with modifier '!'" do
+ it_behaves_like :array_pack_16bit_le, 's!'
+ end
+end
+
+big_endian do
+ describe "Array#pack with format 'S'" do
+ it_behaves_like :array_pack_16bit_be, 'S'
+ end
+
+ describe "Array#pack with format 'S' with modifier '_'" do
+ it_behaves_like :array_pack_16bit_be, 'S_'
+ end
+
+ describe "Array#pack with format 'S' with modifier '!'" do
+ it_behaves_like :array_pack_16bit_be, 'S!'
+ end
+
+ describe "Array#pack with format 's'" do
+ it_behaves_like :array_pack_16bit_be, 's'
+ end
+
+ describe "Array#pack with format 's' with modifier '_'" do
+ it_behaves_like :array_pack_16bit_be, 's_'
+ end
+
+ describe "Array#pack with format 's' with modifier '!'" do
+ it_behaves_like :array_pack_16bit_be, 's!'
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/basic.rb b/spec/ruby/core/array/pack/shared/basic.rb
new file mode 100644
index 0000000000..2894369c71
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/basic.rb
@@ -0,0 +1,73 @@
+describe :array_pack_arguments, shared: true do
+ it "raises an ArgumentError if there are fewer elements than the format requires" do
+ -> { [].pack(pack_format(1)) }.should.raise(ArgumentError)
+ end
+end
+
+describe :array_pack_basic, shared: true do
+ before :each do
+ @obj = ArraySpecs.universal_pack_object
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { [@obj].pack(nil) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed an Integer" do
+ -> { [@obj].pack(1) }.should.raise(TypeError)
+ end
+end
+
+describe :array_pack_basic_non_float, shared: true do
+ before :each do
+ @obj = ArraySpecs.universal_pack_object
+ end
+
+ it "ignores whitespace in the format string" do
+ [@obj, @obj].pack("a \t\n\v\f\r"+pack_format).should.instance_of?(String)
+ end
+
+ it "ignores comments in the format string" do
+ # 2 additional directives ('a') are required for the X directive
+ [@obj, @obj, @obj, @obj].pack("aa #{pack_format} # some comment \n#{pack_format}").should.instance_of?(String)
+ end
+
+ it "raise ArgumentError when a directive is unknown" do
+ # additional directive ('a') is required for the X directive
+ -> { [@obj, @obj].pack("a K" + pack_format) }.should.raise(ArgumentError, /unknown pack directive 'K'/)
+ -> { [@obj, @obj].pack("a 0" + pack_format) }.should.raise(ArgumentError, /unknown pack directive '0'/)
+ -> { [@obj, @obj].pack("a :" + pack_format) }.should.raise(ArgumentError, /unknown pack directive ':'/)
+ end
+
+ it "calls #to_str to coerce the directives string" do
+ d = mock("pack directive")
+ d.should_receive(:to_str).and_return("x"+pack_format)
+ [@obj, @obj].pack(d).should.instance_of?(String)
+ end
+end
+
+describe :array_pack_basic_float, shared: true do
+ it "ignores whitespace in the format string" do
+ [9.3, 4.7].pack(" \t\n\v\f\r"+pack_format).should.instance_of?(String)
+ end
+
+ it "ignores comments in the format string" do
+ [9.3, 4.7].pack(pack_format + "# some comment \n" + pack_format).should.instance_of?(String)
+ end
+
+ it "calls #to_str to coerce the directives string" do
+ d = mock("pack directive")
+ d.should_receive(:to_str).and_return("x"+pack_format)
+ [1.2, 4.7].pack(d).should.instance_of?(String)
+ end
+end
+
+describe :array_pack_no_platform, shared: true do
+ it "raises ArgumentError when the format modifier is '_'" do
+ ->{ [1].pack(pack_format("_")) }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError when the format modifier is '!'" do
+ ->{ [1].pack(pack_format("!")) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/encodings.rb b/spec/ruby/core/array/pack/shared/encodings.rb
new file mode 100644
index 0000000000..0b5a5cc8a0
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/encodings.rb
@@ -0,0 +1,16 @@
+describe :array_pack_hex, shared: true do
+ it "encodes no bytes when passed zero as the count modifier" do
+ ["abc"].pack(pack_format(0)).should == ""
+ end
+
+ it "raises a TypeError if the object does not respond to #to_str" do
+ obj = mock("pack hex non-string")
+ -> { [obj].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("pack hex non-string")
+ obj.should_receive(:to_str).and_return(1)
+ -> { [obj].pack(pack_format) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/float.rb b/spec/ruby/core/array/pack/shared/float.rb
new file mode 100644
index 0000000000..c1efcd7677
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/float.rb
@@ -0,0 +1,255 @@
+# encoding: binary
+
+describe :array_pack_float_le, shared: true do
+ it "encodes a positive Float" do
+ [1.42].pack(pack_format).should == "\x8f\xc2\xb5?"
+ end
+
+ it "encodes a negative Float" do
+ [-34.2].pack(pack_format).should == "\xcd\xcc\x08\xc2"
+ end
+
+ it "converts an Integer to a Float" do
+ [8].pack(pack_format).should == "\x00\x00\x00A"
+ end
+
+ it "raises a TypeError if passed a String representation of a floating point number" do
+ -> { ["13"].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "\x9a\x999@33\xb3?"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "\x9a\x999@33\xb3?33\x03A"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [5.3, 9.2].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ [5.3, 9.2].pack(pack_format(" ", 2)).should == "\x9a\x99\xa9@33\x13A"
+ end
+
+ it "encodes positive Infinity" do
+ [infinity_value].pack(pack_format).should == "\x00\x00\x80\x7f"
+ end
+
+ it "encodes negative Infinity" do
+ [-infinity_value].pack(pack_format).should == "\x00\x00\x80\xff"
+ end
+
+ it "encodes NaN" do
+ nans = ["\x00\x00\xc0\xff", "\x00\x00\xc0\x7f", "\xFF\xFF\xFF\x7F"]
+ nans.should.include?([nan_value].pack(pack_format))
+ end
+
+ it "encodes a positive Float outside the range of a single precision float" do
+ [1e150].pack(pack_format).should == "\x00\x00\x80\x7f"
+ end
+
+ it "encodes a negative Float outside the range of a single precision float" do
+ [-1e150].pack(pack_format).should == "\x00\x00\x80\xff"
+ end
+
+ it "encodes a bignum as a float" do
+ [2 ** 65].pack(pack_format).should == [(2 ** 65).to_f].pack(pack_format)
+ end
+
+ it "encodes a rational as a float" do
+ [Rational(3, 4)].pack(pack_format).should == [Rational(3, 4).to_f].pack(pack_format)
+ end
+end
+
+describe :array_pack_float_be, shared: true do
+ it "encodes a positive Float" do
+ [1.42].pack(pack_format).should == "?\xb5\xc2\x8f"
+ end
+
+ it "encodes a negative Float" do
+ [-34.2].pack(pack_format).should == "\xc2\x08\xcc\xcd"
+ end
+
+ it "converts an Integer to a Float" do
+ [8].pack(pack_format).should == "A\x00\x00\x00"
+ [bignum_value].pack(pack_format).should == "_\x80\x00\x00"
+ end
+
+ it "converts a Rational to a Float" do
+ [Rational(8)].pack(pack_format).should == "A\x00\x00\x00"
+ end
+
+ it "raises a TypeError if passed a String representation of a floating point number" do
+ -> { ["13"].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "@9\x99\x9a?\xb333"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "@9\x99\x9a?\xb333A\x0333"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [5.3, 9.2].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ [5.3, 9.2].pack(pack_format(" ", 2)).should == "@\xa9\x99\x9aA\x1333"
+ end
+
+ it "encodes positive Infinity" do
+ [infinity_value].pack(pack_format).should == "\x7f\x80\x00\x00"
+ end
+
+ it "encodes negative Infinity" do
+ [-infinity_value].pack(pack_format).should == "\xff\x80\x00\x00"
+ end
+
+ it "encodes NaN" do
+ nans = ["\xff\xc0\x00\x00", "\x7f\xc0\x00\x00", "\x7F\xFF\xFF\xFF"]
+ nans.should.include?([nan_value].pack(pack_format))
+ end
+
+ it "encodes a positive Float outside the range of a single precision float" do
+ [1e150].pack(pack_format).should == "\x7f\x80\x00\x00"
+ end
+
+ it "encodes a negative Float outside the range of a single precision float" do
+ [-1e150].pack(pack_format).should == "\xff\x80\x00\x00"
+ end
+end
+
+describe :array_pack_double_le, shared: true do
+ it "encodes a positive Float" do
+ [1.42].pack(pack_format).should == "\xb8\x1e\x85\xebQ\xb8\xf6?"
+ end
+
+ it "encodes a negative Float" do
+ [-34.2].pack(pack_format).should == "\x9a\x99\x99\x99\x99\x19A\xc0"
+ end
+
+ it "converts an Integer to a Float" do
+ [8].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\x20@"
+ [bignum_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xF0C"
+ end
+
+ it "converts a Rational to a Float" do
+ [Rational(8)].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00 @"
+ end
+
+ it "raises a TypeError if passed a String representation of a floating point number" do
+ -> { ["13"].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "333333\x07@ffffff\xf6?"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "333333\x07@ffffff\xf6?ffffff\x20@"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [5.3, 9.2].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ [5.3, 9.2].pack(pack_format(" ", 2)).should == "333333\x15@ffffff\x22@"
+ end
+
+ it "encodes positive Infinity" do
+ [infinity_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xf0\x7f"
+ end
+
+ it "encodes negative Infinity" do
+ [-infinity_value].pack(pack_format).should == "\x00\x00\x00\x00\x00\x00\xf0\xff"
+ end
+
+ it "encodes NaN" do
+ nans = [
+ "\x00\x00\x00\x00\x00\x00\xf8\xff",
+ "\x00\x00\x00\x00\x00\x00\xf8\x7f",
+ "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"
+ ]
+ nans.should.include?([nan_value].pack(pack_format))
+ end
+
+ it "encodes a positive Float outside the range of a single precision float" do
+ [1e150].pack(pack_format).should == "\xaf\x96P\x2e5\x8d\x13_"
+ end
+
+ it "encodes a negative Float outside the range of a single precision float" do
+ [-1e150].pack(pack_format).should == "\xaf\x96P\x2e5\x8d\x13\xdf"
+ end
+end
+
+describe :array_pack_double_be, shared: true do
+ it "encodes a positive Float" do
+ [1.42].pack(pack_format).should == "?\xf6\xb8Q\xeb\x85\x1e\xb8"
+ end
+
+ it "encodes a negative Float" do
+ [-34.2].pack(pack_format).should == "\xc0A\x19\x99\x99\x99\x99\x9a"
+ end
+
+ it "converts an Integer to a Float" do
+ [8].pack(pack_format).should == "@\x20\x00\x00\x00\x00\x00\x00"
+ end
+
+ it "raises a TypeError if passed a String representation of a floating point number" do
+ -> { ["13"].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format(nil, 2)).should == "@\x07333333?\xf6ffffff"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [2.9, 1.4, 8.2].pack(pack_format("*")).should == "@\x07333333?\xf6ffffff@\x20ffffff"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [5.3, 9.2].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ [5.3, 9.2].pack(pack_format(" ", 2)).should == "@\x15333333@\x22ffffff"
+ end
+
+ it "encodes positive Infinity" do
+ [infinity_value].pack(pack_format).should == "\x7f\xf0\x00\x00\x00\x00\x00\x00"
+ end
+
+ it "encodes negative Infinity" do
+ [-infinity_value].pack(pack_format).should == "\xff\xf0\x00\x00\x00\x00\x00\x00"
+ end
+
+ it "encodes NaN" do
+ nans = [
+ "\xff\xf8\x00\x00\x00\x00\x00\x00",
+ "\x7f\xf8\x00\x00\x00\x00\x00\x00",
+ "\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+ ]
+ nans.should.include?([nan_value].pack(pack_format))
+ end
+
+ it "encodes a positive Float outside the range of a single precision float" do
+ [1e150].pack(pack_format).should == "_\x13\x8d5\x2eP\x96\xaf"
+ end
+
+ it "encodes a negative Float outside the range of a single precision float" do
+ [-1e150].pack(pack_format).should == "\xdf\x13\x8d5\x2eP\x96\xaf"
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/integer.rb b/spec/ruby/core/array/pack/shared/integer.rb
new file mode 100644
index 0000000000..1cdd386cc1
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/integer.rb
@@ -0,0 +1,387 @@
+# encoding: binary
+
+describe :array_pack_16bit_le, shared: true do
+ it "encodes the least significant 16 bits of a positive number" do
+ [ [[0x0000_0021], "\x21\x00"],
+ [[0x0000_4321], "\x21\x43"],
+ [[0x0065_4321], "\x21\x43"],
+ [[0x7865_4321], "\x21\x43"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 16 bits of a negative number" do
+ [ [[-0x0000_0021], "\xdf\xff"],
+ [[-0x0000_4321], "\xdf\xbc"],
+ [[-0x0065_4321], "\xdf\xbc"],
+ [[-0x7865_4321], "\xdf\xbc"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[2019902241.2], "\x21\x43"],
+ [[2019902241.8], "\x21\x43"],
+ [[-2019902241.2], "\xdf\xbc"],
+ [[-2019902241.8], "\xdf\xbc"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678)
+ [obj].pack(pack_format()).should == "\x78\x56"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x78\x65\xcd\xab"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x78\x65\xcd\xab\x21\x43"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2))
+ str.should == "\x78\x65\xcd\xab"
+ end
+end
+
+describe :array_pack_16bit_be, shared: true do
+ it "encodes the least significant 16 bits of a positive number" do
+ [ [[0x0000_0021], "\x00\x21"],
+ [[0x0000_4321], "\x43\x21"],
+ [[0x0065_4321], "\x43\x21"],
+ [[0x7865_4321], "\x43\x21"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 16 bits of a negative number" do
+ [ [[-0x0000_0021], "\xff\xdf"],
+ [[-0x0000_4321], "\xbc\xdf"],
+ [[-0x0065_4321], "\xbc\xdf"],
+ [[-0x7865_4321], "\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[2019902241.2], "\x43\x21"],
+ [[2019902241.8], "\x43\x21"],
+ [[-2019902241.2], "\xbc\xdf"],
+ [[-2019902241.8], "\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678)
+ [obj].pack(pack_format()).should == "\x56\x78"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x65\x78\xab\xcd"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x65\x78\xab\xcd\x43\x21"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2))
+ str.should == "\x65\x78\xab\xcd"
+ end
+end
+
+describe :array_pack_32bit_le, shared: true do
+ it "encodes the least significant 32 bits of a positive number" do
+ [ [[0x0000_0021], "\x21\x00\x00\x00"],
+ [[0x0000_4321], "\x21\x43\x00\x00"],
+ [[0x0065_4321], "\x21\x43\x65\x00"],
+ [[0x7865_4321], "\x21\x43\x65\x78"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 32 bits of a negative number" do
+ [ [[-0x0000_0021], "\xdf\xff\xff\xff"],
+ [[-0x0000_4321], "\xdf\xbc\xff\xff"],
+ [[-0x0065_4321], "\xdf\xbc\x9a\xff"],
+ [[-0x7865_4321], "\xdf\xbc\x9a\x87"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[2019902241.2], "\x21\x43\x65\x78"],
+ [[2019902241.8], "\x21\x43\x65\x78"],
+ [[-2019902241.2], "\xdf\xbc\x9a\x87"],
+ [[-2019902241.8], "\xdf\xbc\x9a\x87"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678)
+ [obj].pack(pack_format()).should == "\x78\x56\x34\x12"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde"
+ end
+end
+
+describe :array_pack_32bit_be, shared: true do
+ it "encodes the least significant 32 bits of a positive number" do
+ [ [[0x0000_0021], "\x00\x00\x00\x21"],
+ [[0x0000_4321], "\x00\x00\x43\x21"],
+ [[0x0065_4321], "\x00\x65\x43\x21"],
+ [[0x7865_4321], "\x78\x65\x43\x21"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 32 bits of a negative number" do
+ [ [[-0x0000_0021], "\xff\xff\xff\xdf"],
+ [[-0x0000_4321], "\xff\xff\xbc\xdf"],
+ [[-0x0065_4321], "\xff\x9a\xbc\xdf"],
+ [[-0x7865_4321], "\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[2019902241.2], "\x78\x65\x43\x21"],
+ [[2019902241.8], "\x78\x65\x43\x21"],
+ [[-2019902241.2], "\x87\x9a\xbc\xdf"],
+ [[-2019902241.8], "\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678)
+ [obj].pack(pack_format()).should == "\x12\x34\x56\x78"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ str = [0x1243_6578, 0xdef0_abcd].pack(pack_format(' ', 2))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd"
+ end
+end
+
+describe :array_pack_32bit_le_platform, shared: true do
+ it "encodes the least significant 32 bits of a number" do
+ [ [[0x7865_4321], "\x21\x43\x65\x78"],
+ [[-0x7865_4321], "\xdf\xbc\x9a\x87"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78"
+ end
+
+ 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"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+ end
+end
+
+describe :array_pack_32bit_be_platform, shared: true do
+ it "encodes the least significant 32 bits of a number" do
+ [ [[0x7865_4321], "\x78\x65\x43\x21"],
+ [[-0x7865_4321], "\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format(2))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0x1243_6578, 0xdef0_abcd, 0x7865_4321].pack(pack_format('*'))
+ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21"
+ end
+
+ 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"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+ end
+end
+
+describe :array_pack_64bit_le, shared: true do
+ it "encodes the least significant 64 bits of a positive number" do
+ [ [[0x0000_0000_0000_0021], "\x21\x00\x00\x00\x00\x00\x00\x00"],
+ [[0x0000_0000_0000_4321], "\x21\x43\x00\x00\x00\x00\x00\x00"],
+ [[0x0000_0000_0065_4321], "\x21\x43\x65\x00\x00\x00\x00\x00"],
+ [[0x0000_0000_7865_4321], "\x21\x43\x65\x78\x00\x00\x00\x00"],
+ [[0x0000_0090_7865_4321], "\x21\x43\x65\x78\x90\x00\x00\x00"],
+ [[0x0000_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\x00\x00"],
+ [[0x00dc_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\xdc\x00"],
+ [[0x7edc_ba90_7865_4321], "\x21\x43\x65\x78\x90\xba\xdc\x7e"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 64 bits of a negative number" do
+ [ [[-0x0000_0000_0000_0021], "\xdf\xff\xff\xff\xff\xff\xff\xff"],
+ [[-0x0000_0000_0000_4321], "\xdf\xbc\xff\xff\xff\xff\xff\xff"],
+ [[-0x0000_0000_0065_4321], "\xdf\xbc\x9a\xff\xff\xff\xff\xff"],
+ [[-0x0000_0000_7865_4321], "\xdf\xbc\x9a\x87\xff\xff\xff\xff"],
+ [[-0x0000_0090_7865_4321], "\xdf\xbc\x9a\x87\x6f\xff\xff\xff"],
+ [[-0x0000_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\xff\xff"],
+ [[-0x00dc_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\x23\xff"],
+ [[-0x7edc_ba90_7865_4321], "\xdf\xbc\x9a\x87\x6f\x45\x23\x81"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[9.14138647331322368e+18], "\x00\x44\x65\x78\x90\xba\xdc\x7e"],
+ [[-9.14138647331322368e+18], "\x00\xbc\x9a\x87\x6f\x45\x23\x81"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678_90ab_cdef)
+ [obj].pack(pack_format()).should == "\xef\xcd\xab\x90\x78\x56\x34\x12"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1234_5678_90ab_cdef,
+ 0xdef0_abcd_3412_7856,
+ 0x7865_4321_dcba_def0].pack(pack_format(2))
+ str.should == "\xef\xcd\xab\x90\x78\x56\x34\x12\x56\x78\x12\x34\xcd\xab\xf0\xde"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format('*'))
+ str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format(' ', 2))
+ str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78"
+ end
+end
+
+describe :array_pack_64bit_be, shared: true do
+ it "encodes the least significant 64 bits of a positive number" do
+ [ [[0x0000_0000_0000_0021], "\x00\x00\x00\x00\x00\x00\x00\x21"],
+ [[0x0000_0000_0000_4321], "\x00\x00\x00\x00\x00\x00\x43\x21"],
+ [[0x0000_0000_0065_4321], "\x00\x00\x00\x00\x00\x65\x43\x21"],
+ [[0x0000_0000_7865_4321], "\x00\x00\x00\x00\x78\x65\x43\x21"],
+ [[0x0000_0090_7865_4321], "\x00\x00\x00\x90\x78\x65\x43\x21"],
+ [[0x0000_ba90_7865_4321], "\x00\x00\xba\x90\x78\x65\x43\x21"],
+ [[0x00dc_ba90_7865_4321], "\x00\xdc\xba\x90\x78\x65\x43\x21"],
+ [[0x7edc_ba90_7865_4321], "\x7e\xdc\xba\x90\x78\x65\x43\x21"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes the least significant 64 bits of a negative number" do
+ [ [[-0x0000_0000_0000_0021], "\xff\xff\xff\xff\xff\xff\xff\xdf"],
+ [[-0x0000_0000_0000_4321], "\xff\xff\xff\xff\xff\xff\xbc\xdf"],
+ [[-0x0000_0000_0065_4321], "\xff\xff\xff\xff\xff\x9a\xbc\xdf"],
+ [[-0x0000_0000_7865_4321], "\xff\xff\xff\xff\x87\x9a\xbc\xdf"],
+ [[-0x0000_0090_7865_4321], "\xff\xff\xff\x6f\x87\x9a\xbc\xdf"],
+ [[-0x0000_ba90_7865_4321], "\xff\xff\x45\x6f\x87\x9a\xbc\xdf"],
+ [[-0x00dc_ba90_7865_4321], "\xff\x23\x45\x6f\x87\x9a\xbc\xdf"],
+ [[-0x7edc_ba90_7865_4321], "\x81\x23\x45\x6f\x87\x9a\xbc\xdf"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "encodes a Float truncated as an Integer" do
+ [ [[9.14138647331322368e+18], "\x7e\xdc\xba\x90\x78\x65\x44\x00"],
+ [[-9.14138647331322368e+18], "\x81\x23\x45\x6f\x87\x9a\xbc\x00"]
+ ].should be_computed_by(:pack, pack_format())
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(0x1234_5678_90ab_cdef)
+ [obj].pack(pack_format()).should == "\x12\x34\x56\x78\x90\xab\xcd\xef"
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ str = [0x1234_5678_90ab_cdef,
+ 0xdef0_abcd_3412_7856,
+ 0x7865_4321_dcba_def0].pack(pack_format(2))
+ str.should == "\x12\x34\x56\x78\x90\xab\xcd\xef\xde\xf0\xab\xcd\x34\x12\x78\x56"
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format('*'))
+ str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2))
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format(' ', 2))
+ str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0"
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/numeric_basic.rb b/spec/ruby/core/array/pack/shared/numeric_basic.rb
new file mode 100644
index 0000000000..6594914933
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/numeric_basic.rb
@@ -0,0 +1,50 @@
+describe :array_pack_numeric_basic, shared: true do
+ it "returns an empty String if count is zero" do
+ [1].pack(pack_format(0)).should == ""
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { [nil].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed true" do
+ -> { [true].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed false" do
+ -> { [false].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "returns a binary string" do
+ [0xFF].pack(pack_format).encoding.should == Encoding::BINARY
+ [0xE3, 0x81, 0x82].pack(pack_format(3)).encoding.should == Encoding::BINARY
+ end
+end
+
+describe :array_pack_integer, shared: true do
+ it "raises a TypeError when the object does not respond to #to_int" do
+ obj = mock('not an integer')
+ -> { [obj].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { ["5"].pack(pack_format) }.should.raise(TypeError)
+ end
+end
+
+describe :array_pack_float, shared: true do
+ it "raises a TypeError if a String does not represent a floating point number" do
+ -> { ["a"].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when the object is not Numeric" do
+ obj = Object.new
+ -> { [obj].pack(pack_format) }.should.raise(TypeError, /can't convert Object into Float/)
+ end
+
+ it "raises a TypeError when the Numeric object does not respond to #to_f" do
+ klass = Class.new(Numeric)
+ obj = klass.new
+ -> { [obj].pack(pack_format) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/string.rb b/spec/ruby/core/array/pack/shared/string.rb
new file mode 100644
index 0000000000..b02257059f
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/string.rb
@@ -0,0 +1,48 @@
+# 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"
+ end
+
+ it "implicitly has a count of one when no count is specified" do
+ ["abc"].pack(pack_format).should == "a"
+ end
+
+ it "does not add any bytes when the count is zero" do
+ ["abc"].pack(pack_format(0)).should == ""
+ end
+
+ it "is not affected by a previous count modifier" do
+ ["abcde", "defg"].pack(pack_format(3)+pack_format).should == "abcd"
+ end
+
+ it "raises an ArgumentError when the Array is empty" do
+ -> { [].pack(pack_format) }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError when the Array has too few elements" do
+ -> { ["a"].pack(pack_format(nil, 2)) }.should.raise(ArgumentError)
+ end
+
+ it "calls #to_str to convert the element to a String" do
+ obj = mock('pack string')
+ obj.should_receive(:to_str).and_return("abc")
+
+ [obj].pack(pack_format).should == "a"
+ end
+
+ it "raises a TypeError when the object does not respond to #to_str" do
+ obj = mock("not a string")
+ -> { [obj].pack(pack_format) }.should.raise(TypeError)
+ end
+
+ it "returns a string in encoding of common to the concatenated results" do
+ f = pack_format("*")
+ [ [["\u{3042 3044 3046 3048}", 0x2000B].pack(f+"U"), Encoding::BINARY],
+ [["abcde\xd1", "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY],
+ [["a".dup.force_encoding("ascii"), "\xFF\xFe\x81\x82"].pack(f+"u"), Encoding::BINARY],
+ # under discussion [ruby-dev:37294]
+ [["\u{3042 3044 3046 3048}", 1].pack(f+"N"), Encoding::BINARY]
+ ].should be_computed_by(:encoding)
+ end
+end
diff --git a/spec/ruby/core/array/pack/shared/taint.rb b/spec/ruby/core/array/pack/shared/taint.rb
new file mode 100644
index 0000000000..2c2b011c34
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/taint.rb
@@ -0,0 +1,2 @@
+describe :array_pack_taint, shared: true do
+end
diff --git a/spec/ruby/core/array/pack/shared/unicode.rb b/spec/ruby/core/array/pack/shared/unicode.rb
new file mode 100644
index 0000000000..58ba8a8b23
--- /dev/null
+++ b/spec/ruby/core/array/pack/shared/unicode.rb
@@ -0,0 +1,96 @@
+# -*- encoding: utf-8 -*-
+
+describe :array_pack_unicode, shared: true do
+ it "encodes ASCII values as a Unicode codepoint" do
+ [ [[0], "\x00"],
+ [[1], "\x01"],
+ [[8], "\x08"],
+ [[15], "\x0f"],
+ [[24], "\x18"],
+ [[31], "\x1f"],
+ [[127], "\x7f"],
+ [[128], "\xc2\x80"],
+ [[129], "\xc2\x81"],
+ [[255], "\xc3\xbf"]
+ ].should be_computed_by(:pack, "U")
+ end
+
+ it "encodes UTF-8 BMP codepoints" do
+ [ [[0x80], "\xc2\x80"],
+ [[0x7ff], "\xdf\xbf"],
+ [[0x800], "\xe0\xa0\x80"],
+ [[0xffff], "\xef\xbf\xbf"]
+ ].should be_computed_by(:pack, "U")
+ end
+
+ it "constructs strings with valid encodings" do
+ str = [0x85].pack("U*")
+ str.should == "\xc2\x85"
+ str.valid_encoding?.should == true
+ end
+
+ it "encodes values larger than UTF-8 max codepoints" do
+ [
+ [[0x00110000], [244, 144, 128, 128].pack('C*').force_encoding('utf-8')],
+ [[0x04000000], [252, 132, 128, 128, 128, 128].pack('C*').force_encoding('utf-8')],
+ [[0x7FFFFFFF], [253, 191, 191, 191, 191, 191].pack('C*').force_encoding('utf-8')]
+ ].should be_computed_by(:pack, "U")
+ end
+
+ it "encodes UTF-8 max codepoints" do
+ [ [[0x10000], "\xf0\x90\x80\x80"],
+ [[0xfffff], "\xf3\xbf\xbf\xbf"],
+ [[0x100000], "\xf4\x80\x80\x80"],
+ [[0x10ffff], "\xf4\x8f\xbf\xbf"]
+ ].should be_computed_by(:pack, "U")
+ end
+
+ it "encodes the number of array elements specified by the count modifier" do
+ [ [[0x41, 0x42, 0x43, 0x44], "U2", "\x41\x42"],
+ [[0x41, 0x42, 0x43, 0x44], "U2U", "\x41\x42\x43"]
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes all remaining elements when passed the '*' modifier" do
+ [0x41, 0x42, 0x43, 0x44].pack("U*").should == "\x41\x42\x43\x44"
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(5)
+ [obj].pack("U").should == "\x05"
+ end
+
+ it "raises a TypeError if #to_int does not return an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return("5")
+ -> { [obj].pack("U") }.should.raise(TypeError)
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [1, 2, 3].pack("U\x00U")
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ [1, 2, 3].pack("U U").should == "\x01\x02"
+ end
+
+ it "raises a RangeError if passed a negative number" do
+ -> { [-1].pack("U") }.should.raise(RangeError)
+ end
+
+ it "raises a RangeError if passed a number larger than an unsigned 32-bit integer" do
+ -> { [2**32].pack("U") }.should.raise(RangeError)
+ end
+
+ it "sets the output string to UTF-8 encoding" do
+ [ [[0x00].pack("U"), Encoding::UTF_8],
+ [[0x41].pack("U"), Encoding::UTF_8],
+ [[0x7F].pack("U"), Encoding::UTF_8],
+ [[0x80].pack("U"), Encoding::UTF_8],
+ [[0x10FFFF].pack("U"), Encoding::UTF_8]
+ ].should be_computed_by(:encoding)
+ end
+end
diff --git a/spec/ruby/core/array/pack/u_spec.rb b/spec/ruby/core/array/pack/u_spec.rb
new file mode 100644
index 0000000000..c6a0d77eb2
--- /dev/null
+++ b/spec/ruby/core/array/pack/u_spec.rb
@@ -0,0 +1,140 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/unicode'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'U'" do
+ it_behaves_like :array_pack_basic, 'U'
+ it_behaves_like :array_pack_basic_non_float, 'U'
+ it_behaves_like :array_pack_arguments, 'U'
+ it_behaves_like :array_pack_unicode, 'U'
+end
+
+describe "Array#pack with format 'u'" do
+ it_behaves_like :array_pack_basic, 'u'
+ it_behaves_like :array_pack_basic_non_float, 'u'
+ it_behaves_like :array_pack_arguments, 'u'
+ it_behaves_like :array_pack_taint, 'u'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack u string")
+ obj.should_receive(:to_str).and_return("``abcdef")
+ [obj].pack("u*").should == "(8&!A8F-D968`\n"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('u') }.should.raise(TypeError)
+ end
+
+ it "encodes an empty string as an empty string" do
+ [""].pack("u").should == ""
+ end
+
+ it "appends a newline to the end of the encoded string" do
+ ["a"].pack("u").should == "!80``\n"
+ end
+
+ it "encodes one element per directive" do
+ ["abc", "DEF"].pack("uu").should == "#86)C\n#1$5&\n"
+ end
+
+ it "prepends the length of each segment of the input string as the first character (+32) in each line of the output" do
+ ["abcdefghijklm"].pack("u7").should == "&86)C9&5F\n&9VAI:FML\n!;0``\n"
+ end
+
+ it "encodes 1, 2, or 3 characters in 4 output characters (uuencoding)" do
+ [ [["a"], "!80``\n"],
+ [["ab"], "\"86(`\n"],
+ [["abc"], "#86)C\n"],
+ [["abcd"], "$86)C9```\n"],
+ [["abcde"], "%86)C9&4`\n"],
+ [["abcdef"], "&86)C9&5F\n"],
+ [["abcdefg"], "'86)C9&5F9P``\n"],
+ ].should be_computed_by(:pack, "u")
+ end
+
+ it "emits a newline after complete groups of count / 3 input characters when passed a count modifier" do
+ ["abcdefg"].pack("u3").should == "#86)C\n#9&5F\n!9P``\n"
+ end
+
+ it "implicitly has a count of 45 when passed '*', 0, 1, 2 or no count modifier" do
+ s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ r = "M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n%86%A86$`\n"
+ [ [[s], "u", r],
+ [[s], "u*", r],
+ [[s], "u0", r],
+ [[s], "u1", r],
+ [[s], "u2", r],
+ ].should be_computed_by(:pack)
+ end
+
+ it "encodes all ascii characters" do
+ [ [["\x00\x01\x02\x03\x04\x05\x06"], "'``$\"`P0%!@``\n"],
+ [["\a\b\t\n\v\f\r"], "'!P@)\"@L,#0``\n"],
+ [["\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"], ")\#@\\0$1(3%!46\n"],
+ [["\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f"], ")%Q@9&AL<'1X?\n"],
+ [["!\"\#$%&'()*+,-./"], "/(2(C)\"4F)R@I*BLL+2XO\n"],
+ [["0123456789"], "*,\#$R,S0U-C<X.0``\n"],
+ [[":;<=>?@"], "'.CL\\/3X_0```\n"],
+ [["ABCDEFGHIJKLMNOPQRSTUVWXYZ"], ":04)#1$5&1TA)2DM,34Y/4%%24U155E=865H`\n"],
+ [["[\\]^_`"], "&6UQ=7E]@\n"],
+ [["abcdefghijklmnopqrstuvwxyz"], ":86)C9&5F9VAI:FML;6YO<'%R<W1U=G=X>7H`\n"],
+ [["{|}~"], "$>WQ]?@``\n"],
+ [["\x7f\xc2\x80\xc2\x81\xc2\x82\xc2\x83"], ")?\\*`PH'\"@L*#\n"],
+ [["\xc2\x84\xc2\x85\xc2\x86\xc2\x87\xc2"], ")PH3\"A<*&PH?\"\n"],
+ [["\x88\xc2\x89\xc2\x8a\xc2\x8b\xc2\x8c"], ")B,*)PHK\"B\\*,\n"],
+ [["\xc2\x8d\xc2\x8e\xc2\x8f\xc2\x90\xc2"], ")PHW\"CL*/PI#\"\n"],
+ [["\x91\xc2\x92\xc2\x93\xc2\x94\xc2\x95"], ")D<*2PI/\"E,*5\n"],
+ [["\xc2\x96\xc2\x97\xc2\x98\xc2\x99\xc2"], ")PI;\"E\\*8PIG\"\n"],
+ [["\x9a\xc2\x9b\xc2\x9c\xc2\x9d\xc2\x9e"], ")FL*;PIS\"G<*>\n"],
+ [["\xc2\x9f\xc2\xa0\xc2\xa1\xc2\xa2\xc2"], ")PI_\"H,*APJ+\"\n"],
+ [["\xa3\xc2\xa4\xc2\xa5\xc2\xa6\xc2\xa7"], ")H\\*DPJ7\"IL*G\n"],
+ [["\xc2\xa8\xc2\xa9\xc2\xaa\xc2\xab\xc2"], ")PJC\"J<*JPJO\"\n"],
+ [["\xac\xc2\xad\xc2\xae\xc2\xaf\xc2\xb0"], ")K,*MPJ[\"K\\*P\n"],
+ [["\xc2\xb1\xc2\xb2\xc2\xb3\xc2\xb4\xc2"], ")PK'\"LL*SPK3\"\n"],
+ [["\xb5\xc2\xb6\xc2\xb7\xc2\xb8\xc2\xb9"], ")M<*VPK?\"N,*Y\n"],
+ [["\xc2\xba\xc2\xbb\xc2\xbc\xc2\xbd\xc2"], ")PKK\"N\\*\\PKW\"\n"],
+ [["\xbe\xc2\xbf\xc3\x80\xc3\x81\xc3\x82"], ")OL*_PX#\#@<.\"\n"],
+ [["\xc3\x83\xc3\x84\xc3\x85\xc3\x86\xc3"], ")PX/#A,.%PX;#\n"],
+ [["\x87\xc3\x88\xc3\x89\xc3\x8a\xc3\x8b"], ")A\\.(PXG#BL.+\n"],
+ [["\xc3\x8c\xc3\x8d\xc3\x8e\xc3\x8f\xc3"], ")PXS#C<..PX_#\n"],
+ [["\x90\xc3\x91\xc3\x92\xc3\x93\xc3\x94"], ")D,.1PY+#D\\.4\n"],
+ [["\xc3\x95\xc3\x96\xc3\x97\xc3\x98\xc3"], ")PY7#EL.7PYC#\n"],
+ [["\x99\xc3\x9a\xc3\x9b\xc3\x9c\xc3\x9d"], ")F<.:PYO#G,.=\n"],
+ [["\xc3\x9e\xc3\x9f\xc3\xa0\xc3\xa1\xc3"], ")PY[#G\\.@PZ'#\n"],
+ [["\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6"], ")HL.CPZ3#I<.F\n"],
+ [["\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3"], ")PZ?#J,.IPZK#\n"],
+ [["\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf"], ")J\\.LPZW#KL.O\n"],
+ [["\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3"], ")P[##L<.RP[/#\n"],
+ [["\xb4\xc3\xb5\xc3\xb6\xc3\xb7\xc3\xb8"], ")M,.UP[;#M\\.X\n"],
+ [["\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3"], ")P[G#NL.[P[S#\n"],
+ [["\xbd\xc3\xbe\xc3\xbf"], "%O<.^P[\\`\n"]
+ ].should be_computed_by(:pack, "u")
+ end
+
+ it "calls #to_str to convert an object to a String" do
+ obj = mock("pack m string")
+ obj.should_receive(:to_str).and_return("abc")
+ [obj].pack("u").should == "#86)C\n"
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("pack m non-string")
+ -> { [obj].pack("u") }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed nil" do
+ -> { [nil].pack("u") }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> { [0].pack("u") }.should.raise(TypeError)
+ -> { [bignum_value].pack("u") }.should.raise(TypeError)
+ end
+
+ it "sets the output string to US-ASCII encoding" do
+ ["abcd"].pack("u").encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/array/pack/v_spec.rb b/spec/ruby/core/array/pack/v_spec.rb
new file mode 100644
index 0000000000..d3932c84af
--- /dev/null
+++ b/spec/ruby/core/array/pack/v_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+require_relative 'shared/integer'
+
+describe "Array#pack with format 'V'" do
+ it_behaves_like :array_pack_basic, 'V'
+ it_behaves_like :array_pack_basic_non_float, 'V'
+ it_behaves_like :array_pack_arguments, 'V'
+ it_behaves_like :array_pack_numeric_basic, 'V'
+ it_behaves_like :array_pack_integer, 'V'
+ it_behaves_like :array_pack_no_platform, 'V'
+ it_behaves_like :array_pack_32bit_le, 'V'
+end
+
+describe "Array#pack with format 'v'" do
+ it_behaves_like :array_pack_basic, 'v'
+ it_behaves_like :array_pack_basic_non_float, 'v'
+ it_behaves_like :array_pack_arguments, 'v'
+ it_behaves_like :array_pack_numeric_basic, 'v'
+ it_behaves_like :array_pack_integer, 'v'
+ it_behaves_like :array_pack_no_platform, 'v'
+ it_behaves_like :array_pack_16bit_le, 'v'
+end
diff --git a/spec/ruby/core/array/pack/w_spec.rb b/spec/ruby/core/array/pack/w_spec.rb
new file mode 100644
index 0000000000..263e2a2288
--- /dev/null
+++ b/spec/ruby/core/array/pack/w_spec.rb
@@ -0,0 +1,44 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/numeric_basic'
+
+describe "Array#pack with format 'w'" do
+ it_behaves_like :array_pack_basic, 'w'
+ it_behaves_like :array_pack_basic_non_float, 'w'
+ it_behaves_like :array_pack_arguments, 'w'
+ it_behaves_like :array_pack_numeric_basic, 'w'
+
+ it "encodes a BER-compressed integer" do
+ [ [[0], "\x00"],
+ [[1], "\x01"],
+ [[9999], "\xce\x0f"],
+ [[2**65], "\x84\x80\x80\x80\x80\x80\x80\x80\x80\x00"]
+ ].should be_computed_by(:pack, "w")
+ end
+
+ it "calls #to_int to convert the pack argument to an Integer" do
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(5)
+ [obj].pack("w").should == "\x05"
+ end
+
+ it "raise ArgumentError for NULL bytes between directives" do
+ -> {
+ [1, 2, 3].pack("w\x00w")
+ }.should.raise(ArgumentError, /unknown pack directive/)
+ end
+
+ it "ignores spaces between directives" do
+ [1, 2, 3].pack("w w").should == "\x01\x02"
+ end
+
+ it "raises an ArgumentError when passed a negative value" do
+ -> { [-1].pack("w") }.should.raise(ArgumentError)
+ end
+
+ it "returns a binary string" do
+ [1].pack('w').encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/array/pack/x_spec.rb b/spec/ruby/core/array/pack/x_spec.rb
new file mode 100644
index 0000000000..7ff587a01e
--- /dev/null
+++ b/spec/ruby/core/array/pack/x_spec.rb
@@ -0,0 +1,65 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+
+describe "Array#pack with format 'x'" do
+ it_behaves_like :array_pack_basic, 'x'
+ it_behaves_like :array_pack_basic_non_float, 'x'
+ it_behaves_like :array_pack_no_platform, 'x'
+
+ it "adds a NULL byte with an empty array" do
+ [].pack("x").should == "\x00"
+ end
+
+ it "adds a NULL byte without consuming an element" do
+ [1, 2].pack("CxC").should == "\x01\x00\x02"
+ end
+
+ it "is not affected by a previous count modifier" do
+ [].pack("x3x").should == "\x00\x00\x00\x00"
+ end
+
+ it "adds multiple NULL bytes when passed a count modifier" do
+ [].pack("x3").should == "\x00\x00\x00"
+ end
+
+ it "does not add a NULL byte if the count modifier is zero" do
+ [].pack("x0").should == ""
+ end
+
+ it "does not add a NULL byte when passed the '*' modifier" do
+ [].pack("x*").should == ""
+ [1, 2].pack("Cx*C").should == "\x01\x02"
+ end
+end
+
+describe "Array#pack with format 'X'" do
+ it_behaves_like :array_pack_basic, 'X'
+ it_behaves_like :array_pack_basic_non_float, 'X'
+ it_behaves_like :array_pack_no_platform, 'X'
+
+ it "reduces the output string by one byte at the point it is encountered" do
+ [1, 2, 3].pack("C2XC").should == "\x01\x03"
+ end
+
+ it "does not consume any elements" do
+ [1, 2, 3].pack("CXC").should == "\x02"
+ end
+
+ it "reduces the output string by multiple bytes when passed a count modifier" do
+ [1, 2, 3, 4, 5].pack("C2X2C").should == "\x03"
+ end
+
+ it "has no affect when passed the '*' modifier" do
+ [1, 2, 3].pack("C2X*C").should == "\x01\x02\x03"
+ end
+
+ it "raises an ArgumentError if the output string is empty" do
+ -> { [1, 2, 3].pack("XC") }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if the count modifier is greater than the bytes in the string" do
+ -> { [1, 2, 3].pack("C2X3") }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/array/pack/z_spec.rb b/spec/ruby/core/array/pack/z_spec.rb
new file mode 100644
index 0000000000..5cd084c825
--- /dev/null
+++ b/spec/ruby/core/array/pack/z_spec.rb
@@ -0,0 +1,44 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+require_relative 'shared/basic'
+require_relative 'shared/string'
+require_relative 'shared/taint'
+
+describe "Array#pack with format 'Z'" do
+ it_behaves_like :array_pack_basic, 'Z'
+ it_behaves_like :array_pack_basic_non_float, 'Z'
+ it_behaves_like :array_pack_no_platform, 'Z'
+ it_behaves_like :array_pack_string, 'Z'
+ it_behaves_like :array_pack_taint, 'Z'
+
+ it "calls #to_str to convert an Object to a String" do
+ obj = mock("pack Z string")
+ obj.should_receive(:to_str).and_return("``abcdef")
+ [obj].pack("Z*").should == "``abcdef\x00"
+ end
+
+ it "will not implicitly convert a number to a string" do
+ -> { [0].pack('Z') }.should.raise(TypeError)
+ end
+
+ it "adds all the bytes and appends a NULL byte when passed the '*' modifier" do
+ ["abc"].pack("Z*").should == "abc\x00"
+ end
+
+ 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
+
+ it "adds a NULL byte when the value is nil" do
+ [nil].pack("Z").should == "\x00"
+ end
+
+ it "pads the output with NULL bytes when the value is nil" do
+ [nil].pack("Z3").should == "\x00\x00\x00"
+ end
+
+ it "does not append a NULL byte when passed the '*' modifier and the value is nil" do
+ [nil].pack("Z*").should == "\x00"
+ end
+end
diff --git a/spec/ruby/core/array/partition_spec.rb b/spec/ruby/core/array/partition_spec.rb
new file mode 100644
index 0000000000..bd3f3a6b6f
--- /dev/null
+++ b/spec/ruby/core/array/partition_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#partition" do
+ it "returns two arrays" do
+ [].partition {}.should == [[], []]
+ end
+
+ it "returns in the left array values for which the block evaluates to true" do
+ ary = [0, 1, 2, 3, 4, 5]
+
+ ary.partition { |i| true }.should == [ary, []]
+ ary.partition { |i| 5 }.should == [ary, []]
+ ary.partition { |i| false }.should == [[], ary]
+ ary.partition { |i| nil }.should == [[], ary]
+ ary.partition { |i| i % 2 == 0 }.should == [[0, 2, 4], [1, 3, 5]]
+ ary.partition { |i| i / 3 == 0 }.should == [[0, 1, 2], [3, 4, 5]]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.partition { true }.should == [[empty], []]
+ empty.partition { false }.should == [[], [empty]]
+
+ array = ArraySpecs.recursive_array
+ array.partition { true }.should == [
+ [1, 'two', 3.0, array, array, array, array, array],
+ []
+ ]
+ condition = true
+ array.partition { condition = !condition }.should == [
+ ['two', array, array, array],
+ [1, 3.0, array, array]
+ ]
+ end
+
+ it "does not return subclass instances on Array subclasses" do
+ result = ArraySpecs::MyArray[1, 2, 3].partition { |x| x % 2 == 0 }
+ result.should.instance_of?(Array)
+ result[0].should.instance_of?(Array)
+ result[1].should.instance_of?(Array)
+ end
+end
diff --git a/spec/ruby/core/array/permutation_spec.rb b/spec/ruby/core/array/permutation_spec.rb
new file mode 100644
index 0000000000..b5df84b52b
--- /dev/null
+++ b/spec/ruby/core/array/permutation_spec.rb
@@ -0,0 +1,138 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+
+describe "Array#permutation" do
+
+ before :each do
+ @numbers = (1..3).to_a
+ @yielded = []
+ end
+
+ it "returns an Enumerator of all permutations when called without a block or arguments" do
+ enum = @numbers.permutation
+ enum.should.instance_of?(Enumerator)
+ enum.to_a.sort.should == [
+ [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
+ ].sort
+ end
+
+ it "returns an Enumerator of permutations of given length when called with an argument but no block" do
+ enum = @numbers.permutation(1)
+ enum.should.instance_of?(Enumerator)
+ enum.to_a.sort.should == [[1],[2],[3]]
+ end
+
+ it "yields all permutations to the block then returns self when called with block but no arguments" do
+ array = @numbers.permutation {|n| @yielded << n}
+ array.should.instance_of?(Array)
+ array.sort.should == @numbers.sort
+ @yielded.sort.should == [
+ [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
+ ].sort
+ end
+
+ it "yields all permutations of given length to the block then returns self when called with block and argument" do
+ array = @numbers.permutation(2) {|n| @yielded << n}
+ array.should.instance_of?(Array)
+ array.sort.should == @numbers.sort
+ @yielded.sort.should == [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]].sort
+ end
+
+ it "returns the empty permutation ([[]]) when the given length is 0" do
+ @numbers.permutation(0).to_a.should == [[]]
+ @numbers.permutation(0) { |n| @yielded << n }
+ @yielded.should == [[]]
+ end
+
+ it "returns the empty permutation([]) when called on an empty Array" do
+ [].permutation.to_a.should == [[]]
+ [].permutation { |n| @yielded << n }
+ @yielded.should == [[]]
+ end
+
+ it "returns no permutations when the given length has no permutations" do
+ @numbers.permutation(9).entries.size.should == 0
+ @numbers.permutation(9) { |n| @yielded << n }
+ @yielded.should == []
+ end
+
+ it "handles duplicate elements correctly" do
+ @numbers << 1
+ @numbers.permutation(2).sort.should == [
+ [1,1],[1,1],[1,2],[1,2],[1,3],[1,3],
+ [2,1],[2,1],[2,3],
+ [3,1],[3,1],[3,2]
+ ].sort
+ end
+
+ it "handles nested Arrays correctly" do
+ # The ugliness is due to the order of permutations returned by
+ # permutation being undefined combined with #sort croaking on Arrays of
+ # Arrays.
+ @numbers << [4,5]
+ got = @numbers.permutation(2).to_a
+ expected = [
+ [1, 2], [1, 3], [1, [4, 5]],
+ [2, 1], [2, 3], [2, [4, 5]],
+ [3, 1], [3, 2], [3, [4, 5]],
+ [[4, 5], 1], [[4, 5], 2], [[4, 5], 3]
+ ]
+ expected.each {|e| got.include?(e).should == true}
+ got.size.should == expected.size
+ end
+
+ it "truncates Float arguments" do
+ @numbers.permutation(3.7).to_a.sort.should ==
+ @numbers.permutation(3).to_a.sort
+ end
+
+ it "returns an Enumerator which works as expected even when the array was modified" do
+ @numbers = [1, 2]
+ enum = @numbers.permutation
+ @numbers << 3
+ enum.to_a.sort.should == [
+ [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
+ ].sort
+ end
+
+ it "generates from a defensive copy, ignoring mutations" do
+ accum = []
+ ary = [1,2,3]
+ ary.permutation(3) do |x|
+ accum << x
+ ary[0] = 5
+ end
+
+ accum.should == [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ describe "with an array size greater than 0" do
+ it "returns the descending factorial of array size and given length" do
+ @numbers.permutation(4).size.should == 0
+ @numbers.permutation(3).size.should == 6
+ @numbers.permutation(2).size.should == 6
+ @numbers.permutation(1).size.should == 3
+ @numbers.permutation(0).size.should == 1
+ end
+ it "returns the descending factorial of array size with array size when there's no param" do
+ @numbers.permutation.size.should == 6
+ [1,2,3,4].permutation.size.should == 24
+ [1].permutation.size.should == 1
+ end
+ end
+ describe "with an empty array" do
+ it "returns 1 when the given length is 0" do
+ [].permutation(0).size.should == 1
+ end
+ it "returns 1 when there's param" do
+ [].permutation.size.should == 1
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/plus_spec.rb b/spec/ruby/core/array/plus_spec.rb
new file mode 100644
index 0000000000..7ead927fc0
--- /dev/null
+++ b/spec/ruby/core/array/plus_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#+" do
+ it "concatenates two arrays" do
+ ([ 1, 2, 3 ] + [ 3, 4, 5 ]).should == [1, 2, 3, 3, 4, 5]
+ ([ 1, 2, 3 ] + []).should == [1, 2, 3]
+ ([] + [ 1, 2, 3 ]).should == [1, 2, 3]
+ ([] + []).should == []
+ end
+
+ it "can concatenate an array with itself" do
+ ary = [1, 2, 3]
+ (ary + ary).should == [1, 2, 3, 1, 2, 3]
+ end
+
+ describe "converts the passed argument to an Array using #to_ary" do
+ it "successfully concatenates the resulting array from the #to_ary call" do
+ obj = mock('["x", "y"]')
+ obj.should_receive(:to_ary).and_return(["x", "y"])
+ ([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
+ -> { [1, 2, 3] + nil }.should.raise(TypeError)
+ -> { [1, 2, 3] + "abc" }.should.raise(TypeError)
+ end
+
+ it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to an Array" do
+ obj = mock("hello")
+ obj.should_receive(:to_ary).and_raise(NoMethodError)
+ -> { [1, 2, 3] + obj }.should.raise(NoMethodError)
+ end
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ (empty + empty).should == [empty, empty]
+
+ array = ArraySpecs.recursive_array
+ (empty + array).should == [empty, 1, 'two', 3.0, array, array, array, array, array]
+ (array + array).should == [
+ 1, 'two', 3.0, array, array, array, array, array,
+ 1, 'two', 3.0, array, array, array, array, array]
+ end
+
+ it "does return subclass instances with Array subclasses" do
+ (ArraySpecs::MyArray[1, 2, 3] + []).should.instance_of?(Array)
+ (ArraySpecs::MyArray[1, 2, 3] + ArraySpecs::MyArray[]).should.instance_of?(Array)
+ ([1, 2, 3] + ArraySpecs::MyArray[]).should.instance_of?(Array)
+ end
+
+ it "does not call to_ary on array subclasses" do
+ ([5, 6] + ArraySpecs::ToAryArray[1, 2]).should == [5, 6, 1, 2]
+ end
+end
diff --git a/spec/ruby/core/array/pop_spec.rb b/spec/ruby/core/array/pop_spec.rb
new file mode 100644
index 0000000000..069083331c
--- /dev/null
+++ b/spec/ruby/core/array/pop_spec.rb
@@ -0,0 +1,124 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#pop" do
+ it "removes and returns the last element of the array" do
+ a = ["a", 1, nil, true]
+
+ a.pop.should == true
+ a.should == ["a", 1, nil]
+
+ a.pop.should == nil
+ a.should == ["a", 1]
+
+ a.pop.should == 1
+ a.should == ["a"]
+
+ a.pop.should == "a"
+ a.should == []
+ end
+
+ it "returns nil if there are no more elements" do
+ [].pop.should == nil
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.pop.should == []
+
+ array = ArraySpecs.recursive_array
+ array.pop.should == [1, 'two', 3.0, array, array, array, array]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.pop }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.pop }.should.raise(FrozenError)
+ end
+
+ describe "passed a number n as an argument" do
+ it "removes and returns an array with the last n elements of the array" do
+ a = [1, 2, 3, 4, 5, 6]
+
+ a.pop(0).should == []
+ a.should == [1, 2, 3, 4, 5, 6]
+
+ a.pop(1).should == [6]
+ a.should == [1, 2, 3, 4, 5]
+
+ a.pop(2).should == [4, 5]
+ a.should == [1, 2, 3]
+
+ a.pop(3).should == [1, 2, 3]
+ a.should == []
+ end
+
+ it "returns an array with the last n elements even if shift was invoked" do
+ a = [1, 2, 3, 4]
+ a.shift
+ a.pop(3).should == [2, 3, 4]
+ end
+
+ it "returns a new empty array if there are no more elements" do
+ a = []
+ popped1 = a.pop(1)
+ popped1.should == []
+ a.should == []
+
+ popped2 = a.pop(2)
+ popped2.should == []
+ a.should == []
+
+ popped1.should_not.equal?(popped2)
+ end
+
+ it "returns whole elements if n exceeds size of the array" do
+ a = [1, 2, 3, 4, 5]
+ a.pop(6).should == [1, 2, 3, 4, 5]
+ a.should == []
+ end
+
+ it "does not return self even when it returns whole elements" do
+ a = [1, 2, 3, 4, 5]
+ a.pop(5).should_not.equal?(a)
+
+ a = [1, 2, 3, 4, 5]
+ a.pop(6).should_not.equal?(a)
+ end
+
+ it "raises an ArgumentError if n is negative" do
+ ->{ [1, 2, 3].pop(-1) }.should.raise(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ a = [1, 2, 3, 4]
+ a.pop(2.3).should == [3, 4]
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ a.should == [1, 2]
+ a.pop(obj).should == [1, 2]
+ a.should == []
+ end
+
+ it "raises a TypeError when the passed n cannot be coerced to Integer" do
+ ->{ [1, 2].pop("cat") }.should.raise(TypeError)
+ ->{ [1, 2].pop(nil) }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError if more arguments are passed" do
+ ->{ [1, 2].pop(1, 2) }.should.raise(ArgumentError)
+ end
+
+ it "does not return subclass instances with Array subclass" do
+ ArraySpecs::MyArray[1, 2, 3].pop(2).should.instance_of?(Array)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.pop(2) }.should.raise(FrozenError)
+ -> { ArraySpecs.frozen_array.pop(0) }.should.raise(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/prepend_spec.rb b/spec/ruby/core/array/prepend_spec.rb
new file mode 100644
index 0000000000..2d0ce31c71
--- /dev/null
+++ b/spec/ruby/core/array/prepend_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Array#prepend" do
+ it "is an alias of Array#unshift" do
+ Array.instance_method(:prepend).should == Array.instance_method(:unshift)
+ end
+end
diff --git a/spec/ruby/core/array/product_spec.rb b/spec/ruby/core/array/product_spec.rb
new file mode 100644
index 0000000000..837f0eaf34
--- /dev/null
+++ b/spec/ruby/core/array/product_spec.rb
@@ -0,0 +1,73 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#product" do
+ it "returns converted arguments using :to_ary" do
+ ->{ [1].product(2..3) }.should.raise(TypeError)
+ ar = ArraySpecs::ArrayConvertible.new(2,3)
+ [1].product(ar).should == [[1,2],[1,3]]
+ ar.called.should == :to_ary
+ end
+
+ it "returns converted arguments using :method_missing" do
+ ar = ArraySpecs::ArrayMethodMissing.new(2,3)
+ [1].product(ar).should == [[1,2],[1,3]]
+ end
+
+ it "returns the expected result" do
+ [1,2].product([3,4,5],[6,8]).should == [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8],
+ [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]]
+ end
+
+ it "has no required argument" do
+ [1,2].product.should == [[1],[2]]
+ end
+
+ it "returns an empty array when the argument is an empty array" do
+ [1, 2].product([]).should == []
+ end
+
+ it "does not attempt to produce an unreasonable number of products" do
+ a = (0..100).to_a
+ -> do
+ a.product(a, a, a, a, a, a, a, a, a, a)
+ end.should.raise(RangeError)
+ end
+
+ describe "when given a block" do
+ it "yields all combinations in turn" do
+ acc = []
+ [1,2].product([3,4,5],[6,8]){|array| acc << array}
+ acc.should == [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8],
+ [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]]
+
+ acc = []
+ [1,2].product([3,4,5],[],[6,8]){|array| acc << array}
+ acc.should.empty?
+ end
+
+ it "returns self" do
+ a = [1, 2, 3].freeze
+
+ a.product([1, 2]) { |p| p.first }.should == a
+ end
+
+ it "will ignore unreasonable numbers of products and yield anyway" do
+ a = (0..100).to_a
+ -> do
+ a.product(a, a, a, a, a, a, a, a, a, a)
+ end.should.raise(RangeError)
+ end
+ end
+
+ describe "when given an empty block" do
+ it "returns self" do
+ arr = [1,2]
+ arr.product([3,4,5],[6,8]){}.should.equal?(arr)
+ arr = []
+ arr.product([3,4,5],[6,8]){}.should.equal?(arr)
+ arr = [1,2]
+ arr.product([]){}.should.equal?(arr)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/push_spec.rb b/spec/ruby/core/array/push_spec.rb
new file mode 100644
index 0000000000..6255a84371
--- /dev/null
+++ b/spec/ruby/core/array/push_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#push" do
+ it "appends the arguments to the array" do
+ a = [ "a", "b", "c" ]
+ a.push("d", "e", "f").should.equal?(a)
+ a.push.should == ["a", "b", "c", "d", "e", "f"]
+ a.push(5)
+ a.should == ["a", "b", "c", "d", "e", "f", 5]
+
+ a = [0, 1]
+ a.push(2)
+ a.should == [0, 1, 2]
+ end
+
+ it "isn't confused by previous shift" do
+ a = [ "a", "b", "c" ]
+ a.shift
+ a.push("foo")
+ a.should == ["b", "c", "foo"]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.push(:last).should == [empty, :last]
+
+ array = ArraySpecs.recursive_array
+ array.push(:last).should == [1, 'two', 3.0, array, array, array, array, array, :last]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.push(1) }.should.raise(FrozenError)
+ -> { ArraySpecs.frozen_array.push }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/rassoc_spec.rb b/spec/ruby/core/array/rassoc_spec.rb
new file mode 100644
index 0000000000..95e4ed1892
--- /dev/null
+++ b/spec/ruby/core/array/rassoc_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#rassoc" do
+ it "returns the first contained array whose second element is == object" do
+ ary = [[1, "a", 0.5], [2, "b"], [3, "b"], [4, "c"], [], [5], [6, "d"]]
+ ary.rassoc("a").should == [1, "a", 0.5]
+ ary.rassoc("b").should == [2, "b"]
+ ary.rassoc("d").should == [6, "d"]
+ ary.rassoc("z").should == nil
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.rassoc([]).should == nil
+ [[empty, empty]].rassoc(empty).should == [empty, empty]
+
+ array = ArraySpecs.recursive_array
+ array.rassoc(array).should == nil
+ [[empty, array]].rassoc(array).should == [empty, array]
+ end
+
+ it "calls elem == obj on the second element of each contained array" do
+ key = 'foobar'
+ o = mock('foobar')
+ def o.==(other); other == 'foobar'; end
+
+ [[1, :foobar], [2, o], [3, mock('foo')]].rassoc(key).should == [2, o]
+ end
+
+ it "does not check the last element in each contained but specifically the second" do
+ key = 'foobar'
+ o = mock('foobar')
+ def o.==(other); other == 'foobar'; end
+
+ [[1, :foobar, o], [2, o, 1], [3, mock('foo')]].rassoc(key).should == [2, o, 1]
+ end
+
+ it "calls to_ary on non-array elements" do
+ s1 = [1, 2]
+ s2 = ArraySpecs::ArrayConvertible.new(2, 3)
+ a = [s1, s2]
+
+ s1.should_not_receive(:to_ary)
+ a.rassoc(2).should.equal?(s1)
+
+ a.rassoc(3).should == [2, 3]
+ s2.called.should.equal?(:to_ary)
+ end
+end
diff --git a/spec/ruby/core/array/reject_spec.rb b/spec/ruby/core/array/reject_spec.rb
new file mode 100644
index 0000000000..8d237b3a75
--- /dev/null
+++ b/spec/ruby/core/array/reject_spec.rb
@@ -0,0 +1,158 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative 'shared/delete_if'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#reject" do
+ it "returns a new array without elements for which block is true" do
+ ary = [1, 2, 3, 4, 5]
+ ary.reject { true }.should == []
+ ary.reject { false }.should == ary
+ ary.reject { false }.should_not.equal? ary
+ ary.reject { nil }.should == ary
+ ary.reject { nil }.should_not.equal? ary
+ ary.reject { 5 }.should == []
+ ary.reject { |i| i < 3 }.should == [3, 4, 5]
+ ary.reject { |i| i % 2 == 0 }.should == [1, 3, 5]
+ end
+
+ it "returns self when called on an Array emptied with #shift" do
+ array = [1]
+ array.shift
+ array.reject { |x| true }.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.reject { false }.should == [empty]
+ empty.reject { true }.should == []
+
+ array = ArraySpecs.recursive_array
+ array.reject { false }.should == [1, 'two', 3.0, array, array, array, array, array]
+ array.reject { true }.should == []
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].reject { |x| x % 2 == 0 }.should.instance_of?(Array)
+ end
+
+ it "does not retain instance variables" do
+ array = []
+ array.instance_variable_set("@variable", "value")
+ array.reject { false }.instance_variable_get("@variable").should == nil
+ end
+
+ it_behaves_like :enumeratorize, :reject
+ it_behaves_like :enumeratorized_with_origin_size, :reject, [1,2,3]
+end
+
+describe "Array#reject" do
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :reject
+end
+
+describe "Array#reject!" do
+ it "removes elements for which block is true" do
+ a = [3, 4, 5, 6, 7, 8, 9, 10, 11]
+ a.reject! { |i| i % 2 == 0 }.should.equal?(a)
+ a.should == [3, 5, 7, 9, 11]
+ a.reject! { |i| i > 8 }
+ a.should == [3, 5, 7]
+ a.reject! { |i| i < 4 }
+ a.should == [5, 7]
+ a.reject! { |i| i == 5 }
+ a.should == [7]
+ a.reject! { true }
+ a.should == []
+ a.reject! { true }
+ a.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty_dup = empty.dup
+ empty.reject! { false }.should == nil
+ empty.should == empty_dup
+
+ empty = ArraySpecs.empty_recursive_array
+ empty.reject! { true }.should == []
+ empty.should == []
+
+ array = ArraySpecs.recursive_array
+ array_dup = array.dup
+ array.reject! { false }.should == nil
+ array.should == array_dup
+
+ array = ArraySpecs.recursive_array
+ array.reject! { true }.should == []
+ array.should == []
+ end
+
+ it "returns nil when called on an Array emptied with #shift" do
+ array = [1]
+ array.shift
+ array.reject! { |x| true }.should == nil
+ end
+
+ it "returns nil if no changes are made" do
+ a = [1, 2, 3]
+
+ a.reject! { |i| i < 0 }.should == nil
+
+ a.reject! { true }
+ a.reject! { true }.should == nil
+ end
+
+ it "returns an Enumerator if no block given, and the array is frozen" do
+ ArraySpecs.frozen_array.reject!.should.instance_of?(Enumerator)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.reject! {} }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.reject! {} }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError on a frozen array only during iteration if called without a block" do
+ enum = ArraySpecs.frozen_array.reject!
+ -> { enum.each {} }.should.raise(FrozenError)
+ end
+
+ it "does not truncate the array is the block raises an exception" do
+ a = [1, 2, 3]
+ begin
+ a.reject! { raise StandardError, 'Oops' }
+ rescue
+ end
+
+ a.should == [1, 2, 3]
+ end
+
+ it "only removes elements for which the block returns true, keeping the element which raised an error." do
+ a = [1, 2, 3, 4]
+ begin
+ a.reject! do |x|
+ case x
+ when 2 then true
+ when 3 then raise StandardError, 'Oops'
+ else false
+ end
+ end
+ rescue StandardError
+ end
+
+ a.should == [1, 3, 4]
+ end
+
+ it_behaves_like :enumeratorize, :reject!
+ it_behaves_like :enumeratorized_with_origin_size, :reject!, [1,2,3]
+ it_behaves_like :delete_if, :reject!
+end
+
+describe "Array#reject!" do
+ @value_to_return = -> _ { false }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :reject!
+end
diff --git a/spec/ruby/core/array/repeated_combination_spec.rb b/spec/ruby/core/array/repeated_combination_spec.rb
new file mode 100644
index 0000000000..a714f05f54
--- /dev/null
+++ b/spec/ruby/core/array/repeated_combination_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+
+describe "Array#repeated_combination" do
+ before :each do
+ @array = [10, 11, 12]
+ end
+
+ it "returns an enumerator when no block is provided" do
+ @array.repeated_combination(2).should.instance_of?(Enumerator)
+ end
+
+ it "returns self when a block is given" do
+ @array.repeated_combination(2){}.should.equal?(@array)
+ end
+
+ it "yields nothing for negative length and return self" do
+ @array.repeated_combination(-1){ fail }.should.equal?(@array)
+ @array.repeated_combination(-10){ fail }.should.equal?(@array)
+ end
+
+ it "yields the expected repeated_combinations" do
+ @array.repeated_combination(2).to_a.sort.should == [[10, 10], [10, 11], [10, 12], [11, 11], [11, 12], [12, 12]]
+ @array.repeated_combination(3).to_a.sort.should == [[10, 10, 10], [10, 10, 11], [10, 10, 12], [10, 11, 11], [10, 11, 12],
+ [10, 12, 12], [11, 11, 11], [11, 11, 12], [11, 12, 12], [12, 12, 12]]
+ end
+
+ it "yields [] when length is 0" do
+ @array.repeated_combination(0).to_a.should == [[]] # one repeated_combination of length 0
+ [].repeated_combination(0).to_a.should == [[]] # one repeated_combination of length 0
+ end
+
+ it "yields nothing when the array is empty and num is non zero" do
+ [].repeated_combination(5).to_a.should == [] # one repeated_combination of length 0
+ end
+
+ it "yields a partition consisting of only singletons" do
+ @array.repeated_combination(1).sort.to_a.should == [[10],[11],[12]]
+ end
+
+ it "accepts sizes larger than the original array" do
+ @array.repeated_combination(4).to_a.sort.should ==
+ [[10, 10, 10, 10], [10, 10, 10, 11], [10, 10, 10, 12],
+ [10, 10, 11, 11], [10, 10, 11, 12], [10, 10, 12, 12],
+ [10, 11, 11, 11], [10, 11, 11, 12], [10, 11, 12, 12],
+ [10, 12, 12, 12], [11, 11, 11, 11], [11, 11, 11, 12],
+ [11, 11, 12, 12], [11, 12, 12, 12], [12, 12, 12, 12]]
+ end
+
+ it "generates from a defensive copy, ignoring mutations" do
+ accum = []
+ @array.repeated_combination(2) do |x|
+ accum << x
+ @array[0] = 1
+ end
+ accum.sort.should == [[10, 10], [10, 11], [10, 12], [11, 11], [11, 12], [12, 12]]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns 0 when the combination_size is < 0" do
+ @array.repeated_combination(-1).size.should == 0
+ [].repeated_combination(-2).size.should == 0
+ end
+
+ it "returns 1 when the combination_size is 0" do
+ @array.repeated_combination(0).size.should == 1
+ [].repeated_combination(0).size.should == 1
+ end
+
+ it "returns the binomial coefficient between combination_size and array size + combination_size -1" do
+ @array.repeated_combination(5).size.should == 21
+ @array.repeated_combination(4).size.should == 15
+ @array.repeated_combination(3).size.should == 10
+ @array.repeated_combination(2).size.should == 6
+ @array.repeated_combination(1).size.should == 3
+ @array.repeated_combination(0).size.should == 1
+ [].repeated_combination(0).size.should == 1
+ [].repeated_combination(1).size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/repeated_permutation_spec.rb b/spec/ruby/core/array/repeated_permutation_spec.rb
new file mode 100644
index 0000000000..c54a8c0c2b
--- /dev/null
+++ b/spec/ruby/core/array/repeated_permutation_spec.rb
@@ -0,0 +1,94 @@
+require_relative '../../spec_helper'
+
+
+describe "Array#repeated_permutation" do
+
+ before :each do
+ @numbers = [10, 11, 12]
+ @permutations = [[10, 10], [10, 11], [10, 12], [11, 10], [11, 11], [11, 12], [12, 10], [12, 11], [12, 12]]
+ end
+
+ it "returns an Enumerator of all repeated permutations of given length when called without a block" do
+ enum = @numbers.repeated_permutation(2)
+ enum.should.instance_of?(Enumerator)
+ enum.to_a.sort.should == @permutations
+ end
+
+ it "yields all repeated_permutations to the block then returns self when called with block but no arguments" do
+ yielded = []
+ @numbers.repeated_permutation(2) {|n| yielded << n}.should.equal?(@numbers)
+ yielded.sort.should == @permutations
+ end
+
+ it "yields the empty repeated_permutation ([[]]) when the given length is 0" do
+ @numbers.repeated_permutation(0).to_a.should == [[]]
+ [].repeated_permutation(0).to_a.should == [[]]
+ end
+
+ it "does not yield when called on an empty Array with a nonzero argument" do
+ [].repeated_permutation(10).to_a.should == []
+ end
+
+ it "handles duplicate elements correctly" do
+ @numbers[-1] = 10
+ @numbers.repeated_permutation(2).sort.should ==
+ [[10, 10], [10, 10], [10, 10], [10, 10], [10, 11], [10, 11], [11, 10], [11, 10], [11, 11]]
+ end
+
+ it "truncates Float arguments" do
+ @numbers.repeated_permutation(3.7).to_a.sort.should ==
+ @numbers.repeated_permutation(3).to_a.sort
+ end
+
+ it "returns an Enumerator which works as expected even when the array was modified" do
+ @numbers.shift
+ enum = @numbers.repeated_permutation(2)
+ @numbers.unshift 10
+ enum.to_a.sort.should == @permutations
+ end
+
+ it "allows permutations larger than the number of elements" do
+ [1,2].repeated_permutation(3).sort.should ==
+ [[1, 1, 1], [1, 1, 2], [1, 2, 1],
+ [1, 2, 2], [2, 1, 1], [2, 1, 2],
+ [2, 2, 1], [2, 2, 2]]
+ end
+
+ it "generates from a defensive copy, ignoring mutations" do
+ accum = []
+ ary = [1,2]
+ ary.repeated_permutation(3) do |x|
+ accum << x
+ ary[0] = 5
+ end
+
+ accum.sort.should ==
+ [[1, 1, 1], [1, 1, 2], [1, 2, 1],
+ [1, 2, 2], [2, 1, 1], [2, 1, 2],
+ [2, 2, 1], [2, 2, 2]]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns 0 when combination_size is < 0" do
+ @numbers.repeated_permutation(-1).size.should == 0
+ [].repeated_permutation(-1).size.should == 0
+ end
+
+ it "returns array size ** combination_size" do
+ @numbers.repeated_permutation(4).size.should == 81
+ @numbers.repeated_permutation(3).size.should == 27
+ @numbers.repeated_permutation(2).size.should == 9
+ @numbers.repeated_permutation(1).size.should == 3
+ @numbers.repeated_permutation(0).size.should == 1
+ [].repeated_permutation(4).size.should == 0
+ [].repeated_permutation(3).size.should == 0
+ [].repeated_permutation(2).size.should == 0
+ [].repeated_permutation(1).size.should == 0
+ [].repeated_permutation(0).size.should == 1
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/array/replace_spec.rb b/spec/ruby/core/array/replace_spec.rb
new file mode 100644
index 0000000000..ee6a98a646
--- /dev/null
+++ b/spec/ruby/core/array/replace_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#replace" do
+ it "replaces the elements with elements from other array" do
+ a = [1, 2, 3, 4, 5]
+ b = ['a', 'b', 'c']
+ a.replace(b).should.equal?(a)
+ a.should == b
+ a.should_not.equal?(b)
+
+ a.replace([4] * 10)
+ a.should == [4] * 10
+
+ a.replace([])
+ a.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ orig = [1, 2, 3]
+ empty = ArraySpecs.empty_recursive_array
+ orig.replace(empty)
+ orig.should == empty
+
+ array = ArraySpecs.recursive_array
+ orig.replace(array)
+ orig.should == array
+ end
+
+ it "returns self" do
+ ary = [1, 2, 3]
+ other = [:a, :b, :c]
+ ary.replace(other).should.equal?(ary)
+ end
+
+ it "does not make self dependent to the original array" do
+ ary = [1, 2, 3]
+ other = [:a, :b, :c]
+ ary.replace(other)
+ ary.should == [:a, :b, :c]
+ ary << :d
+ ary.should == [:a, :b, :c, :d]
+ other.should == [:a, :b, :c]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('to_ary')
+ obj.stub!(:to_ary).and_return([1, 2, 3])
+ [].replace(obj).should == [1, 2, 3]
+ end
+
+ it "does not call #to_ary on Array subclasses" do
+ obj = ArraySpecs::ToAryArray[5, 6, 7]
+ obj.should_not_receive(:to_ary)
+ [].replace(ArraySpecs::ToAryArray[5, 6, 7]).should == [5, 6, 7]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> {
+ ArraySpecs.frozen_array.replace(ArraySpecs.frozen_array)
+ }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/reverse_each_spec.rb b/spec/ruby/core/array/reverse_each_spec.rb
new file mode 100644
index 0000000000..8fa5ce6da1
--- /dev/null
+++ b/spec/ruby/core/array/reverse_each_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative '../enumerable/shared/enumeratorized'
+
+# Modifying a collection while the contents are being iterated
+# gives undefined behavior. See
+# https://blade.ruby-lang.org/ruby-core/23633
+
+describe "Array#reverse_each" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "traverses array in reverse order and pass each element to block" do
+ [1, 3, 4, 6].reverse_each { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [6, 4, 3, 1]
+ end
+
+ it "returns self" do
+ a = [:a, :b, :c]
+ a.reverse_each { |x| }.should.equal?(a)
+ end
+
+ it "yields only the top level element of an empty recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.reverse_each { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [empty]
+ end
+
+ it "yields only the top level element of a recursive array" do
+ array = ArraySpecs.recursive_array
+ array.reverse_each { |i| ScratchPad << i }
+ ScratchPad.recorded.should == [array, array, array, array, array, 3.0, 'two', 1]
+ end
+
+ it "returns the correct size when no block is given" do
+ [1, 2, 3].reverse_each.size.should == 3
+ end
+
+ it "tolerates increasing an array size during iteration" do
+ array = [:a, :b, :c]
+ ScratchPad.record []
+ i = 0
+
+ array.reverse_each do |e|
+ ScratchPad << e
+ array.prepend i if i < 100
+ i += 1
+ end
+
+ ScratchPad.recorded.should == [:c, :a, 1]
+ end
+
+ it_behaves_like :enumeratorize, :reverse_each
+ it_behaves_like :enumeratorized_with_origin_size, :reverse_each, [1,2,3]
+end
diff --git a/spec/ruby/core/array/reverse_spec.rb b/spec/ruby/core/array/reverse_spec.rb
new file mode 100644
index 0000000000..f25a484be8
--- /dev/null
+++ b/spec/ruby/core/array/reverse_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#reverse" do
+ it "returns a new array with the elements in reverse order" do
+ [].reverse.should == []
+ [1, 3, 5, 2].reverse.should == [2, 5, 3, 1]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.reverse.should == empty
+
+ array = ArraySpecs.recursive_array
+ array.reverse.should == [array, array, array, array, array, 3.0, 'two', 1]
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].reverse.should.instance_of?(Array)
+ end
+end
+
+describe "Array#reverse!" do
+ it "reverses the elements in place" do
+ a = [6, 3, 4, 2, 1]
+ a.reverse!.should.equal?(a)
+ a.should == [1, 2, 4, 3, 6]
+ [].reverse!.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.reverse!.should == [empty]
+
+ array = ArraySpecs.recursive_array
+ array.reverse!.should == [array, array, array, array, array, 3.0, 'two', 1]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.reverse! }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/rindex_spec.rb b/spec/ruby/core/array/rindex_spec.rb
new file mode 100644
index 0000000000..858c39dc92
--- /dev/null
+++ b/spec/ruby/core/array/rindex_spec.rb
@@ -0,0 +1,95 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../enumerable/shared/enumeratorized'
+
+# Modifying a collection while the contents are being iterated
+# gives undefined behavior. See
+# https://blade.ruby-lang.org/ruby-core/23633
+
+describe "Array#rindex" do
+ it "returns the first index backwards from the end where element == to object" do
+ key = 3
+ uno = mock('one')
+ dos = mock('two')
+ tres = mock('three')
+ tres.should_receive(:==).any_number_of_times.and_return(false)
+ dos.should_receive(:==).any_number_of_times.and_return(true)
+ uno.should_not_receive(:==)
+ ary = [uno, dos, tres]
+
+ ary.rindex(key).should == 1
+ end
+
+ it "returns size-1 if last element == to object" do
+ [2, 1, 3, 2, 5].rindex(5).should == 4
+ end
+
+ it "returns 0 if only first element == to object" do
+ [2, 1, 3, 1, 5].rindex(2).should == 0
+ end
+
+ it "returns nil if no element == to object" do
+ [1, 1, 3, 2, 1, 3].rindex(4).should == nil
+ end
+
+ it "returns correct index even after delete_at" do
+ array = ["fish", "bird", "lion", "cat"]
+ array.delete_at(0)
+ array.rindex("lion").should == 1
+ end
+
+ it "properly handles empty recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.rindex(empty).should == 0
+ empty.rindex(1).should == nil
+ end
+
+ it "properly handles recursive arrays" do
+ array = ArraySpecs.recursive_array
+ array.rindex(1).should == 0
+ array.rindex(array).should == 7
+ end
+
+ it "accepts a block instead of an argument" do
+ [4, 2, 1, 5, 1, 3].rindex { |x| x < 2 }.should == 4
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ [4, 2, 1, 5, 1, 3].rindex(5) { |x| x < 2 }.should == 3
+ }.should complain(/given block not used/)
+ end
+
+ it "rechecks the array size during iteration" do
+ ary = [4, 2, 1, 5, 1, 3]
+ seen = []
+ ary.rindex { |x| seen << x; ary.clear; false }
+
+ seen.should == [3]
+ end
+
+ it "tolerates increasing an array size during iteration" do
+ array = [:a, :b, :c]
+ ScratchPad.record []
+ i = 0
+
+ array.rindex do |e|
+ ScratchPad << e
+ array.prepend i if i < 100
+ i += 1
+ false
+ end
+
+ ScratchPad.recorded.should == [:c, :a, 1]
+ end
+
+ describe "given no argument and no block" do
+ it "produces an Enumerator" do
+ enum = [4, 2, 1, 5, 1, 3].rindex
+ enum.should.instance_of?(Enumerator)
+ enum.each { |x| x < 2 }.should == 4
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_unknown_size, :bsearch, [1,2,3]
+end
diff --git a/spec/ruby/core/array/rotate_spec.rb b/spec/ruby/core/array/rotate_spec.rb
new file mode 100644
index 0000000000..009ce5ed49
--- /dev/null
+++ b/spec/ruby/core/array/rotate_spec.rb
@@ -0,0 +1,129 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#rotate" do
+ describe "when passed no argument" do
+ it "returns a copy of the array with the first element moved at the end" do
+ [1, 2, 3, 4, 5].rotate.should == [2, 3, 4, 5, 1]
+ end
+ end
+
+ describe "with an argument n" do
+ it "returns a copy of the array with the first (n % size) elements moved at the end" do
+ a = [1, 2, 3, 4, 5]
+ a.rotate( 2).should == [3, 4, 5, 1, 2]
+ a.rotate( -1).should == [5, 1, 2, 3, 4]
+ a.rotate(-21).should == [5, 1, 2, 3, 4]
+ a.rotate( 13).should == [4, 5, 1, 2, 3]
+ a.rotate( 0).should == a
+ end
+
+ it "coerces the argument using to_int" do
+ [1, 2, 3].rotate(2.6).should == [3, 1, 2]
+
+ obj = mock('integer_like')
+ obj.should_receive(:to_int).and_return(2)
+ [1, 2, 3].rotate(obj).should == [3, 1, 2]
+ end
+
+ it "raises a TypeError if not passed an integer-like argument" do
+ -> {
+ [1, 2].rotate(nil)
+ }.should.raise(TypeError)
+ -> {
+ [1, 2].rotate("4")
+ }.should.raise(TypeError)
+ end
+ end
+
+ it "returns a copy of the array when its length is one or zero" do
+ [1].rotate.should == [1]
+ [1].rotate(2).should == [1]
+ [1].rotate(-42).should == [1]
+ [ ].rotate.should == []
+ [ ].rotate(2).should == []
+ [ ].rotate(-42).should == []
+ end
+
+ it "does not mutate the receiver" do
+ -> {
+ [].freeze.rotate
+ [2].freeze.rotate(2)
+ [1,2,3].freeze.rotate(-3)
+ }.should_not.raise
+ end
+
+ it "does not return self" do
+ a = [1, 2, 3]
+ a.rotate.should_not.equal?(a)
+ a = []
+ a.rotate(0).should_not.equal?(a)
+ end
+
+ it "does not return subclass instance for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].rotate.should.instance_of?(Array)
+ end
+end
+
+describe "Array#rotate!" do
+ describe "when passed no argument" do
+ it "moves the first element to the end and returns self" do
+ a = [1, 2, 3, 4, 5]
+ a.rotate!.should.equal?(a)
+ a.should == [2, 3, 4, 5, 1]
+ end
+ end
+
+ describe "with an argument n" do
+ it "moves the first (n % size) elements at the end and returns self" do
+ a = [1, 2, 3, 4, 5]
+ a.rotate!(2).should.equal?(a)
+ a.should == [3, 4, 5, 1, 2]
+ a.rotate!(-12).should.equal?(a)
+ a.should == [1, 2, 3, 4, 5]
+ a.rotate!(13).should.equal?(a)
+ a.should == [4, 5, 1, 2, 3]
+ end
+
+ it "coerces the argument using to_int" do
+ [1, 2, 3].rotate!(2.6).should == [3, 1, 2]
+
+ obj = mock('integer_like')
+ obj.should_receive(:to_int).and_return(2)
+ [1, 2, 3].rotate!(obj).should == [3, 1, 2]
+ end
+
+ it "raises a TypeError if not passed an integer-like argument" do
+ -> {
+ [1, 2].rotate!(nil)
+ }.should.raise(TypeError)
+ -> {
+ [1, 2].rotate!("4")
+ }.should.raise(TypeError)
+ end
+ end
+
+ it "does nothing and returns self when the length is zero or one" do
+ a = [1]
+ a.rotate!.should.equal?(a)
+ a.should == [1]
+ a.rotate!(2).should.equal?(a)
+ a.should == [1]
+ a.rotate!(-21).should.equal?(a)
+ a.should == [1]
+
+ a = []
+ a.rotate!.should.equal?(a)
+ a.should == []
+ a.rotate!(2).should.equal?(a)
+ a.should == []
+ a.rotate!(-21).should.equal?(a)
+ a.should == []
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { [1, 2, 3].freeze.rotate!(0) }.should.raise(FrozenError)
+ -> { [1].freeze.rotate!(42) }.should.raise(FrozenError)
+ -> { [].freeze.rotate! }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/array/sample_spec.rb b/spec/ruby/core/array/sample_spec.rb
new file mode 100644
index 0000000000..fd443b47de
--- /dev/null
+++ b/spec/ruby/core/array/sample_spec.rb
@@ -0,0 +1,155 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#sample" do
+ it "samples evenly" do
+ ArraySpecs.measure_sample_fairness(4, 1, 400)
+ ArraySpecs.measure_sample_fairness(4, 2, 400)
+ ArraySpecs.measure_sample_fairness(4, 3, 400)
+ ArraySpecs.measure_sample_fairness(40, 3, 400)
+ ArraySpecs.measure_sample_fairness(40, 4, 400)
+ ArraySpecs.measure_sample_fairness(40, 8, 400)
+ ArraySpecs.measure_sample_fairness(40, 16, 400)
+ ArraySpecs.measure_sample_fairness_large_sample_size(100, 80, 4000)
+ end
+
+ it "returns nil for an empty Array" do
+ [].sample.should == nil
+ end
+
+ it "returns nil for an empty array when called without n and a Random is given" do
+ [].sample(random: Random.new(42)).should == nil
+ end
+
+ it "returns a single value when not passed a count" do
+ [4].sample.should.equal?(4)
+ end
+
+ it "returns a single value when not passed a count and a Random is given" do
+ [4].sample(random: Random.new(42)).should.equal?(4)
+ end
+
+ it "returns a single value when not passed a count and a Random class is given" do
+ [4].sample(random: Random).should.equal?(4)
+ end
+
+ it "returns an empty Array when passed zero" do
+ [4].sample(0).should == []
+ end
+
+ it "returns an Array of elements when passed a count" do
+ [1, 2, 3, 4].sample(3).should.instance_of?(Array)
+ end
+
+ it "returns elements from the Array" do
+ array = [1, 2, 3, 4]
+ array.sample(3).all? { |x| array.should.include?(x) }
+ end
+
+ it "returns at most the number of elements in the Array" do
+ array = [1, 2, 3, 4]
+ result = array.sample(20)
+ result.size.should == 4
+ end
+
+ it "does not return the same value if the Array has unique values" do
+ array = [1, 2, 3, 4]
+ result = array.sample(20)
+ result.sort.should == array
+ end
+
+ it "may return the same value if the array is not unique" do
+ [4, 4].sample(2).should == [4,4]
+ end
+
+ it "calls #to_int to convert the count when passed an Object" do
+ [1, 2, 3, 4].sample(mock_int(2)).size.should == 2
+ end
+
+ it "raises ArgumentError when passed a negative count" do
+ -> { [1, 2].sample(-1) }.should.raise(ArgumentError)
+ end
+
+ it "does not return subclass instances with Array subclass" do
+ ArraySpecs::MyArray[1, 2, 3].sample(2).should.instance_of?(Array)
+ end
+
+ describe "with options" do
+ it "calls #rand on the Object passed by the :random key in the arguments Hash" do
+ obj = mock("array_sample_random")
+ obj.should_receive(:rand).and_return(0.5)
+
+ [1, 2].sample(random: obj).should.instance_of?(Integer)
+ end
+
+ it "raises a NoMethodError if an object passed for the RNG does not define #rand" do
+ obj = BasicObject.new
+
+ -> { [1, 2].sample(random: obj) }.should.raise(NoMethodError)
+ end
+
+ describe "when the object returned by #rand is an Integer" do
+ it "uses the integer as index" do
+ random = mock("array_sample_random_ret")
+ random.should_receive(:rand).and_return(0)
+
+ [1, 2].sample(random: random).should == 1
+
+ random = mock("array_sample_random_ret")
+ random.should_receive(:rand).and_return(1)
+
+ [1, 2].sample(random: random).should == 2
+ end
+
+ it "raises a RangeError if the value is less than zero" do
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(-1)
+
+ -> { [1, 2].sample(random: random) }.should.raise(RangeError)
+ end
+
+ it "raises a RangeError if the value is equal to the Array size" do
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(2)
+
+ -> { [1, 2].sample(random: random) }.should.raise(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(RangeError)
+ end
+ end
+ end
+
+ describe "when the object returned by #rand is not an Integer but responds to #to_int" do
+ it "calls #to_int on the Object" do
+ value = mock("array_sample_random_value")
+ value.should_receive(:to_int).and_return(1)
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(value)
+
+ [1, 2].sample(random: random).should == 2
+ end
+
+ it "raises a RangeError if the value is less than zero" do
+ value = mock("array_sample_random_value")
+ value.should_receive(:to_int).and_return(-1)
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(value)
+
+ -> { [1, 2].sample(random: random) }.should.raise(RangeError)
+ end
+
+ it "raises a RangeError if the value is equal to the Array size" do
+ value = mock("array_sample_random_value")
+ value.should_receive(:to_int).and_return(2)
+ random = mock("array_sample_random")
+ random.should_receive(:rand).and_return(value)
+
+ -> { [1, 2].sample(random: random) }.should.raise(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/select_spec.rb b/spec/ruby/core/array/select_spec.rb
new file mode 100644
index 0000000000..57ec0b2540
--- /dev/null
+++ b/spec/ruby/core/array/select_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorize'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+require_relative 'shared/keep_if'
+
+describe "Array#select" do
+ it_behaves_like :enumeratorize, :select
+
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :select
+
+ before :each do
+ @object = [1,2,3]
+ end
+ it_behaves_like :enumeratorized_with_origin_size, :select
+
+ it "returns a new array of elements for which block is true" do
+ [1, 3, 4, 5, 6, 9].select { |i| i % ((i + 1) / 2) == 0}.should == [1, 4, 6]
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].select { true }.should.instance_of?(Array)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.select { true }.should == empty
+ empty.select { false }.should == []
+
+ array = ArraySpecs.recursive_array
+ array.select { true }.should == [1, 'two', 3.0, array, array, array, array, array]
+ array.select { false }.should == []
+ end
+end
+
+describe "Array#select!" do
+ it "returns nil if no changes were made in the array" do
+ [1, 2, 3].select! { true }.should == nil
+ end
+
+ it_behaves_like :keep_if, :select!
+end
diff --git a/spec/ruby/core/array/shared/clone.rb b/spec/ruby/core/array/shared/clone.rb
new file mode 100644
index 0000000000..1a45c2fe2c
--- /dev/null
+++ b/spec/ruby/core/array/shared/clone.rb
@@ -0,0 +1,20 @@
+describe :array_clone, shared: true do
+ it "returns an Array or a subclass instance" do
+ [].send(@method).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2].send(@method).should.instance_of?(ArraySpecs::MyArray)
+ end
+
+ it "produces a shallow copy where the references are directly copied" do
+ a = [mock('1'), mock('2')]
+ b = a.send @method
+ b.first.should.equal? a.first
+ b.last.should.equal? a.last
+ end
+
+ it "creates a new array containing all elements or the original" do
+ a = [1, 2, 3, 4]
+ b = a.send @method
+ b.should == a
+ b.__id__.should_not == a.__id__
+ end
+end
diff --git a/spec/ruby/core/array/shared/delete_if.rb b/spec/ruby/core/array/shared/delete_if.rb
new file mode 100644
index 0000000000..a3fdcf4fac
--- /dev/null
+++ b/spec/ruby/core/array/shared/delete_if.rb
@@ -0,0 +1,13 @@
+describe :delete_if, shared: true do
+ before :each do
+ @object = [1,2,3]
+ end
+
+ it "updates the receiver after all blocks" do
+ @object.send(@method) do |e|
+ @object.length.should == 3
+ true
+ end
+ @object.length.should == 0
+ end
+end
diff --git a/spec/ruby/core/array/shared/difference.rb b/spec/ruby/core/array/shared/difference.rb
new file mode 100644
index 0000000000..3fe22331bd
--- /dev/null
+++ b/spec/ruby/core/array/shared/difference.rb
@@ -0,0 +1,78 @@
+describe :array_binary_difference, shared: true do
+ it "creates an array minus any items from other array" do
+ [].send(@method, [ 1, 2, 4 ]).should == []
+ [1, 2, 4].send(@method, []).should == [1, 2, 4]
+ [ 1, 2, 3, 4, 5 ].send(@method, [ 1, 2, 4 ]).should == [3, 5]
+ end
+
+ it "removes multiple items on the lhs equal to one on the rhs" do
+ [1, 1, 2, 2, 3, 3, 4, 5].send(@method, [1, 2, 4]).should == [3, 3, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method, empty).should == []
+
+ [].send(@method, ArraySpecs.recursive_array).should == []
+
+ array = ArraySpecs.recursive_array
+ array.send(@method, array).should == []
+ end
+
+ it "tries to convert the passed arguments to Arrays using #to_ary" do
+ obj = mock('[2,3,3,4]')
+ obj.should_receive(:to_ary).and_return([2, 3, 3, 4])
+ [1, 1, 2, 2, 3, 4].send(@method, obj).should == [1, 1]
+ end
+
+ it "raises a TypeError if the argument cannot be coerced to an Array by calling #to_ary" do
+ obj = mock('not an array')
+ -> { [1, 2, 3].send(@method, obj) }.should.raise(TypeError)
+ end
+
+ it "does not return subclass instance for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, []).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[]).should.instance_of?(Array)
+ [1, 2, 3].send(@method, ArraySpecs::MyArray[]).should.instance_of?(Array)
+ end
+
+ it "does not call to_ary on array subclasses" do
+ [5, 6, 7].send(@method, ArraySpecs::ToAryArray[7]).should == [5, 6]
+ end
+
+ it "removes an item identified as equivalent via #hash and #eql?" do
+ obj1 = mock('1')
+ obj2 = mock('2')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj1.should_receive(:eql?).at_least(1).and_return(true)
+
+ [obj1].send(@method, [obj2]).should == []
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == []
+ end
+
+ it "doesn't remove an item with the same hash but not #eql?" do
+ obj1 = mock('1')
+ obj2 = mock('2')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj1.should_receive(:eql?).at_least(1).and_return(false)
+
+ [obj1].send(@method, [obj2]).should == [obj1]
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1, obj1]
+ end
+
+ it "removes 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].send(@method, [x]).should == []
+ end
+
+ it "is not destructive" do
+ a = [1, 2, 3]
+ a.send(@method, [1])
+ a.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/array/shared/enumeratorize.rb b/spec/ruby/core/array/shared/enumeratorize.rb
new file mode 100644
index 0000000000..5beab5c4c4
--- /dev/null
+++ b/spec/ruby/core/array/shared/enumeratorize.rb
@@ -0,0 +1,5 @@
+describe :enumeratorize, shared: true do
+ it "returns an Enumerator if no block given" do
+ [1,2].send(@method).should.instance_of?(Enumerator)
+ end
+end
diff --git a/spec/ruby/core/array/shared/eql.rb b/spec/ruby/core/array/shared/eql.rb
new file mode 100644
index 0000000000..5e770bf167
--- /dev/null
+++ b/spec/ruby/core/array/shared/eql.rb
@@ -0,0 +1,92 @@
+describe :array_eql, shared: true do
+ it "returns true if other is the same array" do
+ a = [1]
+ a.send(@method, a).should == true
+ end
+
+ it "returns true if corresponding elements are #eql?" do
+ [].send(@method, []).should == true
+ [1, 2, 3, 4].send(@method, [1, 2, 3, 4]).should == true
+ end
+
+ it "returns false if other is shorter than self" do
+ [1, 2, 3, 4].send(@method, [1, 2, 3]).should == false
+ end
+
+ it "returns false if other is longer than self" do
+ [1, 2, 3, 4].send(@method, [1, 2, 3, 4, 5]).should == false
+ end
+
+ it "returns false immediately when sizes of the arrays differ" do
+ obj = mock('1')
+ obj.should_not_receive(@method)
+
+ [] .send(@method, [obj] ).should == false
+ [obj] .send(@method, [] ).should == false
+ end
+
+ it "handles well recursive arrays" do
+ a = ArraySpecs.empty_recursive_array
+ a .send(@method, [a] ).should == true
+ a .send(@method, [[a]] ).should == true
+ [a] .send(@method, a ).should == true
+ [[a]] .send(@method, a ).should == true
+ # These may be surprising, but no difference can be
+ # found between these arrays, so they are ==.
+ # There is no "path" that will lead to a difference
+ # (contrary to other examples below)
+
+ a2 = ArraySpecs.empty_recursive_array
+ a .send(@method, a2 ).should == true
+ a .send(@method, [a2] ).should == true
+ a .send(@method, [[a2]] ).should == true
+ [a] .send(@method, a2 ).should == true
+ [[a]] .send(@method, a2 ).should == true
+
+ back = []
+ forth = [back]; back << forth;
+ back .send(@method, a ).should == true
+
+ x = []; x << x << x
+ x .send(@method, a ).should == false # since x.size != a.size
+ x .send(@method, [a, a] ).should == false # since x[0].size != [a, a][0].size
+ x .send(@method, [x, a] ).should == false # since x[1].size != [x, a][1].size
+ [x, a] .send(@method, [a, x] ).should == false # etc...
+ x .send(@method, [x, x] ).should == true
+ x .send(@method, [[x, x], [x, x]] ).should == true
+
+ tree = [];
+ branch = []; branch << tree << tree; tree << branch
+ tree2 = [];
+ branch2 = []; branch2 << tree2 << tree2; tree2 << branch2
+ forest = [tree, branch, :bird, a]; forest << forest
+ forest2 = [tree2, branch2, :bird, a2]; forest2 << forest2
+
+ forest .send(@method, forest2 ).should == true
+ forest .send(@method, [tree2, branch, :bird, a, forest2]).should == true
+
+ diffforest = [branch2, tree2, :bird, a2]; diffforest << forest2
+ forest .send(@method, diffforest ).should == false # since forest[0].size == 1 != 3 == diffforest[0]
+ forest .send(@method, [nil] ).should == false
+ forest .send(@method, [forest] ).should == false
+ end
+
+ it "does not call #to_ary on its argument" do
+ obj = mock('to_ary')
+ obj.should_not_receive(:to_ary)
+
+ [1, 2, 3].send(@method, obj).should == false
+ end
+
+ it "does not call #to_ary on Array subclasses" do
+ ary = ArraySpecs::ToAryArray[5, 6, 7]
+ ary.should_not_receive(:to_ary)
+ [5, 6, 7].send(@method, ary).should == true
+ end
+
+ it "ignores array class differences" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, [1, 2, 3]).should == true
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should == true
+ [1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should == true
+ end
+end
diff --git a/spec/ruby/core/array/shared/intersection.rb b/spec/ruby/core/array/shared/intersection.rb
new file mode 100644
index 0000000000..dda72e8bd7
--- /dev/null
+++ b/spec/ruby/core/array/shared/intersection.rb
@@ -0,0 +1,85 @@
+describe :array_intersection, shared: true do
+ it "creates an array with elements common to both arrays (intersection)" do
+ [].send(@method, []).should == []
+ [1, 2].send(@method, []).should == []
+ [].send(@method, [1, 2]).should == []
+ [ 1, 3, 5 ].send(@method, [ 1, 2, 3 ]).should == [1, 3]
+ end
+
+ it "creates an array with no duplicates" do
+ [ 1, 1, 3, 5 ].send(@method, [ 1, 2, 3 ]).uniq!.should == nil
+ end
+
+ it "creates an array with elements in order they are first encountered" do
+ [ 1, 2, 3, 2, 5, 6, 7, 8 ].send(@method, [ 5, 2, 3, 4 ]).should == [2, 3, 5] # array > other
+ [ 5, 2, 3, 4 ].send(@method, [ 1, 2, 3, 2, 5, 6, 7, 8 ]).should == [5, 2, 3] # array < other
+ end
+
+ it "does not modify the original Array" do
+ a = [1, 1, 3, 5]
+ a.send(@method, [1, 2, 3]).should == [1, 3]
+ a.should == [1, 1, 3, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method, empty).should == empty
+
+ ArraySpecs.recursive_array.send(@method, []).should == []
+ [].send(@method, ArraySpecs.recursive_array).should == []
+
+ ArraySpecs.recursive_array.send(@method, ArraySpecs.recursive_array).should == [1, 'two', 3.0, ArraySpecs.recursive_array]
+ 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])
+ [1, 2].send(@method, obj).should == ([1, 2])
+ end
+
+ it "determines equivalence between elements in the sense of eql?" do
+ not_supported_on :opal do
+ [5.0, 4.0].send(@method, [5, 4]).should == []
+ end
+
+ str = "x"
+ [str].send(@method, [str.dup]).should == [str]
+
+ obj1 = mock('1')
+ obj2 = mock('2')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj1.should_receive(:eql?).at_least(1).and_return(true)
+ obj2.stub!(:eql?).and_return(true)
+
+ [obj1].send(@method, [obj2]).should == [obj1]
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1]
+
+ obj1 = mock('3')
+ obj2 = mock('4')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj1.should_receive(:eql?).at_least(1).and_return(false)
+
+ [obj1].send(@method, [obj2]).should == []
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj2]
+ end
+
+ it "does return subclass instances for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, []).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should.instance_of?(Array)
+ [].send(@method, ArraySpecs::MyArray[1, 2, 3]).should.instance_of?(Array)
+ end
+
+ it "does not call to_ary on array subclasses" do
+ [5, 6].send(@method, ArraySpecs::ToAryArray[1, 2, 5, 6]).should == [5, 6]
+ 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.
+
+ [x].send(@method, [x]).should == [x]
+ end
+end
diff --git a/spec/ruby/core/array/shared/iterable_and_tolerating_size_increasing.rb b/spec/ruby/core/array/shared/iterable_and_tolerating_size_increasing.rb
new file mode 100644
index 0000000000..3e73bad44b
--- /dev/null
+++ b/spec/ruby/core/array/shared/iterable_and_tolerating_size_increasing.rb
@@ -0,0 +1,25 @@
+describe :array_iterable_and_tolerating_size_increasing, shared: true do
+ before do
+ @value_to_return ||= -> _ { nil }
+ end
+
+ it "tolerates increasing an array size during iteration" do
+ # The goal is to trigger potential reallocation of internal array storage, so we:
+ # - use elements of different types, starting with the less generic (Integer)
+ # - add reasonably big number of new elements (~ 100)
+ array = [1, 2, 3] # to test some methods we need several uniq elements
+ array_to_join = [:a, :b, :c] + (4..100).to_a
+
+ ScratchPad.record []
+ i = 0
+
+ array.send(@method) do |e|
+ ScratchPad << e
+ array << array_to_join[i] if i < array_to_join.size
+ i += 1
+ @value_to_return.call(e)
+ end
+
+ ScratchPad.recorded.should == [1, 2, 3] + array_to_join
+ end
+end
diff --git a/spec/ruby/core/array/shared/join.rb b/spec/ruby/core/array/shared/join.rb
new file mode 100644
index 0000000000..93d5329ee3
--- /dev/null
+++ b/spec/ruby/core/array/shared/join.rb
@@ -0,0 +1,15 @@
+require_relative '../fixtures/classes'
+require_relative '../fixtures/encoded_strings'
+
+describe :array_join_with_string_separator, shared: true do
+ it "returns a string formed by concatenating each element.to_str separated by separator" do
+ obj = mock('foo')
+ obj.should_receive(:to_str).and_return("foo")
+ [1, 2, 3, 4, obj].send(@method, ' | ').should == '1 | 2 | 3 | 4 | foo'
+ end
+
+ it "uses the same separator with nested arrays" do
+ [1, [2, [3, 4], 5], 6].send(@method, ":").should == "1:2:3:4:5:6"
+ [1, [2, ArraySpecs::MyArray[3, 4], 5], 6].send(@method, ":").should == "1:2:3:4:5:6"
+ end
+end
diff --git a/spec/ruby/core/array/shared/keep_if.rb b/spec/ruby/core/array/shared/keep_if.rb
new file mode 100644
index 0000000000..44625eebd1
--- /dev/null
+++ b/spec/ruby/core/array/shared/keep_if.rb
@@ -0,0 +1,95 @@
+require_relative '../../enumerable/shared/enumeratorized'
+require_relative '../shared/iterable_and_tolerating_size_increasing'
+
+describe :keep_if, shared: true do
+ it "deletes elements for which the block returns a false value" do
+ array = [1, 2, 3, 4, 5]
+ array.send(@method) {|item| item > 3 }.should.equal?(array)
+ array.should == [4, 5]
+ end
+
+ it "returns an enumerator if no block is given" do
+ [1, 2, 3].send(@method).should.instance_of?(Enumerator)
+ end
+
+ it "updates the receiver after all blocks" do
+ a = [1, 2, 3]
+ a.send(@method) do |e|
+ a.length.should == 3
+ false
+ end
+ a.length.should == 0
+ end
+
+ before :all do
+ @object = [1,2,3]
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+
+ describe "on frozen objects" do
+ before :each do
+ @origin = [true, false]
+ @frozen = @origin.dup.freeze
+ end
+
+ it "returns an Enumerator if no block is given" do
+ @frozen.send(@method).should.instance_of?(Enumerator)
+ end
+
+ describe "with truthy block" do
+ it "keeps elements after any exception" do
+ -> { @frozen.send(@method) { true } }.should.raise(Exception)
+ @frozen.should == @origin
+ end
+
+ it "raises a FrozenError" do
+ -> { @frozen.send(@method) { true } }.should.raise(FrozenError)
+ end
+ end
+
+ describe "with falsy block" do
+ it "keeps elements after any exception" do
+ -> { @frozen.send(@method) { false } }.should.raise(Exception)
+ @frozen.should == @origin
+ end
+
+ it "raises a FrozenError" do
+ -> { @frozen.send(@method) { false } }.should.raise(FrozenError)
+ end
+ end
+
+ it "raises a FrozenError on a frozen array only during iteration if called without a block" do
+ enum = @frozen.send(@method)
+ -> { enum.each {} }.should.raise(FrozenError)
+ end
+ end
+
+ it "does not truncate the array is the block raises an exception" do
+ a = [1, 2, 3]
+ begin
+ a.send(@method) { raise StandardError, 'Oops' }
+ rescue
+ end
+
+ a.should == [1, 2, 3]
+ end
+
+ it "only changes elements before error is raised, keeping the element which raised an error." do
+ a = [1, 2, 3, 4]
+ begin
+ a.send(@method) do |e|
+ case e
+ when 2 then false
+ when 3 then raise StandardError, 'Oops'
+ else true
+ end
+ end
+ rescue StandardError
+ end
+
+ a.should == [1, 3, 4]
+ end
+
+ @value_to_return = -> _ { true }
+ it_should_behave_like :array_iterable_and_tolerating_size_increasing
+end
diff --git a/spec/ruby/core/array/shared/union.rb b/spec/ruby/core/array/shared/union.rb
new file mode 100644
index 0000000000..0b225b9a31
--- /dev/null
+++ b/spec/ruby/core/array/shared/union.rb
@@ -0,0 +1,79 @@
+describe :array_binary_union, shared: true do
+ it "returns an array of elements that appear in either array (union)" do
+ [].send(@method, []).should == []
+ [1, 2].send(@method, []).should == [1, 2]
+ [].send(@method, [1, 2]).should == [1, 2]
+ [ 1, 2, 3, 4 ].send(@method, [ 3, 4, 5 ]).should == [1, 2, 3, 4, 5]
+ end
+
+ it "creates an array with no duplicates" do
+ [ 1, 2, 3, 1, 4, 5 ].send(@method, [ 1, 3, 4, 5, 3, 6 ]).should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "creates an array with elements in order they are first encountered" do
+ [ 1, 2, 3, 1 ].send(@method, [ 1, 3, 4, 5 ]).should == [1, 2, 3, 4, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.send(@method, empty).should == empty
+
+ array = ArraySpecs.recursive_array
+ array.send(@method, []).should == [1, 'two', 3.0, array]
+ [].send(@method, array).should == [1, 'two', 3.0, array]
+ array.send(@method, array).should == [1, 'two', 3.0, array]
+ array.send(@method, empty).should == [1, 'two', 3.0, array, empty]
+ 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])
+ [0].send(@method, obj).should == ([0] | [1, 2, 3])
+ end
+
+ # MRI follows hashing semantics here, so doesn't actually call eql?/hash for Integer/Symbol
+ it "acts as if using an intermediate hash to collect values" do
+ not_supported_on :opal do
+ [5.0, 4.0].send(@method, [5, 4]).should == [5.0, 4.0, 5, 4]
+ end
+
+ str = "x"
+ [str].send(@method, [str.dup]).should == [str]
+
+ obj1 = mock('1')
+ obj2 = mock('2')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj2.should_receive(:eql?).at_least(1).and_return(true)
+
+ [obj1].send(@method, [obj2]).should == [obj1]
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1]
+
+ obj1 = mock('3')
+ obj2 = mock('4')
+ obj1.stub!(:hash).and_return(0)
+ obj2.stub!(:hash).and_return(0)
+ obj2.should_receive(:eql?).at_least(1).and_return(false)
+
+ [obj1].send(@method, [obj2]).should == [obj1, obj2]
+ [obj1, obj1, obj2, obj2].send(@method, [obj2]).should == [obj1, obj2]
+ end
+
+ it "does not return subclass instances for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].send(@method, []).should.instance_of?(Array)
+ ArraySpecs::MyArray[1, 2, 3].send(@method, ArraySpecs::MyArray[1, 2, 3]).should.instance_of?(Array)
+ [].send(@method, ArraySpecs::MyArray[1, 2, 3]).should.instance_of?(Array)
+ end
+
+ it "does not call to_ary on array subclasses" do
+ [1, 2].send(@method, ArraySpecs::ToAryArray[5, 6]).should == [1, 2, 5, 6]
+ 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.
+
+ [x].send(@method, [x]).should == [x]
+ end
+end
diff --git a/spec/ruby/core/array/shift_spec.rb b/spec/ruby/core/array/shift_spec.rb
new file mode 100644
index 0000000000..09dfa79c45
--- /dev/null
+++ b/spec/ruby/core/array/shift_spec.rb
@@ -0,0 +1,120 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#shift" do
+ it "removes and returns the first element" do
+ a = [5, 1, 1, 5, 4]
+ a.shift.should == 5
+ a.should == [1, 1, 5, 4]
+ a.shift.should == 1
+ a.should == [1, 5, 4]
+ a.shift.should == 1
+ a.should == [5, 4]
+ a.shift.should == 5
+ a.should == [4]
+ a.shift.should == 4
+ a.should == []
+ end
+
+ it "returns nil when the array is empty" do
+ [].shift.should == nil
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.shift.should == []
+ empty.should == []
+
+ array = ArraySpecs.recursive_array
+ array.shift.should == 1
+ array[0..2].should == ['two', 3.0, array]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.shift }.should.raise(FrozenError)
+ end
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.shift }.should.raise(FrozenError)
+ end
+
+ describe "passed a number n as an argument" do
+ it "removes and returns an array with the first n element of the array" do
+ a = [1, 2, 3, 4, 5, 6]
+
+ a.shift(0).should == []
+ a.should == [1, 2, 3, 4, 5, 6]
+
+ a.shift(1).should == [1]
+ a.should == [2, 3, 4, 5, 6]
+
+ a.shift(2).should == [2, 3]
+ a.should == [4, 5, 6]
+
+ a.shift(3).should == [4, 5, 6]
+ a.should == []
+ end
+
+ it "does not corrupt the array when shift without arguments is followed by shift with an argument" do
+ a = [1, 2, 3, 4, 5]
+
+ a.shift.should == 1
+ a.shift(3).should == [2, 3, 4]
+ a.should == [5]
+ end
+
+ it "returns a new empty array if there are no more elements" do
+ a = []
+ popped1 = a.shift(1)
+ popped1.should == []
+ a.should == []
+
+ popped2 = a.shift(2)
+ popped2.should == []
+ a.should == []
+
+ popped1.should_not.equal?(popped2)
+ end
+
+ it "returns whole elements if n exceeds size of the array" do
+ a = [1, 2, 3, 4, 5]
+ a.shift(6).should == [1, 2, 3, 4, 5]
+ a.should == []
+ end
+
+ it "does not return self even when it returns whole elements" do
+ a = [1, 2, 3, 4, 5]
+ a.shift(5).should_not.equal?(a)
+
+ a = [1, 2, 3, 4, 5]
+ a.shift(6).should_not.equal?(a)
+ end
+
+ it "raises an ArgumentError if n is negative" do
+ ->{ [1, 2, 3].shift(-1) }.should.raise(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ a = [1, 2, 3, 4]
+ a.shift(2.3).should == [1, 2]
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ a.should == [3, 4]
+ a.shift(obj).should == [3, 4]
+ a.should == []
+ end
+
+ it "raises a TypeError when the passed n cannot be coerced to Integer" do
+ ->{ [1, 2].shift("cat") }.should.raise(TypeError)
+ ->{ [1, 2].shift(nil) }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError if more arguments are passed" do
+ ->{ [1, 2].shift(1, 2) }.should.raise(ArgumentError)
+ end
+
+ it "does not return subclass instances with Array subclass" do
+ ArraySpecs::MyArray[1, 2, 3].shift(2).should.instance_of?(Array)
+ end
+ end
+end
diff --git a/spec/ruby/core/array/shuffle_spec.rb b/spec/ruby/core/array/shuffle_spec.rb
new file mode 100644
index 0000000000..9bc9df73ac
--- /dev/null
+++ b/spec/ruby/core/array/shuffle_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#shuffle" do
+ it "returns the same values, in a usually different order" do
+ a = [1, 2, 3, 4]
+ different = false
+ 10.times do
+ s = a.shuffle
+ s.sort.should == a
+ different ||= (a != s)
+ end
+ different.should == true # Will fail once in a blue moon (4!^10)
+ end
+
+ it "is not destructive" do
+ a = [1, 2, 3]
+ 10.times do
+ a.shuffle
+ a.should == [1, 2, 3]
+ end
+ end
+
+ it "does not return subclass instances with Array subclass" do
+ ArraySpecs::MyArray[1, 2, 3].shuffle.should.instance_of?(Array)
+ end
+
+ it "calls #rand on the Object passed by the :random key in the arguments Hash" do
+ obj = mock("array_shuffle_random")
+ obj.should_receive(:rand).at_least(1).times.and_return(0.5)
+
+ result = [1, 2].shuffle(random: obj)
+ result.size.should == 2
+ result.sort.should == [1, 2]
+ end
+
+ it "raises a NoMethodError if an object passed for the RNG does not define #rand" do
+ obj = BasicObject.new
+
+ -> { [1, 2].shuffle(random: obj) }.should.raise(NoMethodError)
+ end
+
+ it "accepts a Float for the value returned by #rand" do
+ random = mock("array_shuffle_random")
+ random.should_receive(:rand).at_least(1).times.and_return(0.3)
+
+ [1, 2].shuffle(random: random).should.instance_of?(Array)
+ end
+
+ it "accepts a Random class for the value for random: argument" do
+ [1, 2].shuffle(random: Random).should.instance_of?(Array)
+ end
+
+ it "calls #to_int on the Object returned by #rand" do
+ value = mock("array_shuffle_random_value")
+ value.should_receive(:to_int).at_least(1).times.and_return(0)
+ random = mock("array_shuffle_random")
+ random.should_receive(:rand).at_least(1).times.and_return(value)
+
+ [1, 2].shuffle(random: random).should.instance_of?(Array)
+ end
+
+ it "raises a RangeError if the value is less than zero" do
+ value = mock("array_shuffle_random_value")
+ value.should_receive(:to_int).and_return(-1)
+ random = mock("array_shuffle_random")
+ random.should_receive(:rand).and_return(value)
+
+ -> { [1, 2].shuffle(random: random) }.should.raise(RangeError)
+ end
+
+ 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(2)
+ random = mock("array_shuffle_random")
+ random.should_receive(:rand).at_least(1).times.and_return(value)
+
+ -> { [1, 2].shuffle(random: random) }.should.raise(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)
+
+ -> { [1, 2].shuffle(random: random) }.should.raise(RangeError)
+ end
+end
+
+describe "Array#shuffle!" do
+ it "returns the same values, in a usually different order" do
+ a = [1, 2, 3, 4]
+ original = a
+ different = false
+ 10.times do
+ a = a.shuffle!
+ a.sort.should == [1, 2, 3, 4]
+ different ||= (a != [1, 2, 3, 4])
+ end
+ different.should == true # Will fail once in a blue moon (4!^10)
+ a.should.equal?(original)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.shuffle! }.should.raise(FrozenError)
+ -> { ArraySpecs.empty_frozen_array.shuffle! }.should.raise(FrozenError)
+ end
+
+ it "matches CRuby with random:" do
+ %w[a b c].shuffle(random: Random.new(1)).should == %w[a c b]
+ (0..10).to_a.shuffle(random: Random.new(10)).should == [2, 6, 8, 5, 7, 10, 3, 1, 0, 4, 9]
+ end
+
+ it "matches CRuby with srand" do
+ srand(123)
+ %w[a b c d e f g h i j k].shuffle.should == %w[a e f h i j d b g k c]
+ end
+end
diff --git a/spec/ruby/core/array/size_spec.rb b/spec/ruby/core/array/size_spec.rb
new file mode 100644
index 0000000000..83e8969012
--- /dev/null
+++ b/spec/ruby/core/array/size_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Array#size" do
+ it "is an alias of Array#length" do
+ Array.instance_method(:size).should == Array.instance_method(:length)
+ end
+end
diff --git a/spec/ruby/core/array/slice_spec.rb b/spec/ruby/core/array/slice_spec.rb
new file mode 100644
index 0000000000..230d1dc5d1
--- /dev/null
+++ b/spec/ruby/core/array/slice_spec.rb
@@ -0,0 +1,219 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#slice!" do
+ it "removes and return the element at index" do
+ a = [1, 2, 3, 4]
+ a.slice!(10).should == nil
+ a.should == [1, 2, 3, 4]
+ a.slice!(-10).should == nil
+ a.should == [1, 2, 3, 4]
+ a.slice!(2).should == 3
+ a.should == [1, 2, 4]
+ a.slice!(-1).should == 4
+ a.should == [1, 2]
+ a.slice!(1).should == 2
+ a.should == [1]
+ a.slice!(-1).should == 1
+ a.should == []
+ a.slice!(-1).should == nil
+ a.should == []
+ a.slice!(0).should == nil
+ a.should == []
+ end
+
+ it "removes and returns length elements beginning at start" do
+ a = [1, 2, 3, 4, 5, 6]
+ a.slice!(2, 3).should == [3, 4, 5]
+ a.should == [1, 2, 6]
+ a.slice!(1, 1).should == [2]
+ a.should == [1, 6]
+ a.slice!(1, 0).should == []
+ a.should == [1, 6]
+ a.slice!(2, 0).should == []
+ a.should == [1, 6]
+ a.slice!(0, 4).should == [1, 6]
+ a.should == []
+ a.slice!(0, 4).should == []
+ a.should == []
+
+ a = [1]
+ a.slice!(0, 1).should == [1]
+ a.should == []
+ a[-1].should == nil
+
+ a = [1, 2, 3]
+ a.slice!(0,1).should == [1]
+ a.should == [2, 3]
+ end
+
+ it "returns nil if length is negative" do
+ a = [1, 2, 3]
+ a.slice!(2, -1).should == nil
+ a.should == [1, 2, 3]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.slice(0).should == empty
+
+ array = ArraySpecs.recursive_array
+ array.slice(4).should == array
+ array.slice(0..3).should == [1, 'two', 3.0, array]
+ end
+
+ it "calls to_int on start and length arguments" do
+ obj = mock('2')
+ def obj.to_int() 2 end
+
+ a = [1, 2, 3, 4, 5]
+ a.slice!(obj).should == 3
+ a.should == [1, 2, 4, 5]
+ a.slice!(obj, obj).should == [4, 5]
+ a.should == [1, 2]
+ a.slice!(0, obj).should == [1, 2]
+ a.should == []
+ end
+
+ it "removes and return elements in range" do
+ a = [1, 2, 3, 4, 5, 6, 7, 8]
+ a.slice!(1..4).should == [2, 3, 4, 5]
+ a.should == [1, 6, 7, 8]
+ a.slice!(1...3).should == [6, 7]
+ a.should == [1, 8]
+ a.slice!(-1..-1).should == [8]
+ a.should == [1]
+ a.slice!(0...0).should == []
+ a.should == [1]
+ a.slice!(0..0).should == [1]
+ a.should == []
+
+ a = [1,2,3]
+ a.slice!(0..3).should == [1,2,3]
+ a.should == []
+ end
+
+ it "removes and returns elements in end-exclusive ranges" do
+ a = [1, 2, 3, 4, 5, 6, 7, 8]
+ a.slice!(4...a.length).should == [5, 6, 7, 8]
+ a.should == [1, 2, 3, 4]
+ end
+
+ it "calls to_int on range arguments" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ a = [1, 2, 3, 4, 5]
+
+ a.slice!(from .. to).should == [2, 3, 4]
+ a.should == [1, 5]
+
+ -> { a.slice!("a" .. "b") }.should.raise(TypeError)
+ -> { a.slice!(from .. "b") }.should.raise(TypeError)
+ end
+
+ it "returns last element for consecutive calls at zero index" do
+ a = [ 1, 2, 3 ]
+ a.slice!(0).should == 1
+ a.slice!(0).should == 2
+ a.slice!(0).should == 3
+ a.should == []
+ end
+
+ it "does not expand array with indices out of bounds" do
+ a = [1, 2]
+ a.slice!(4).should == nil
+ a.should == [1, 2]
+ a.slice!(4, 0).should == nil
+ a.should == [1, 2]
+ a.slice!(6, 1).should == nil
+ a.should == [1, 2]
+ a.slice!(8...8).should == nil
+ a.should == [1, 2]
+ a.slice!(10..10).should == nil
+ a.should == [1, 2]
+ end
+
+ it "does not expand array with negative indices out of bounds" do
+ a = [1, 2]
+ a.slice!(-3, 1).should == nil
+ a.should == [1, 2]
+ a.slice!(-3..2).should == nil
+ a.should == [1, 2]
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.slice!(0, 0) }.should.raise(FrozenError)
+ end
+
+ it "works with endless ranges" do
+ a = [1, 2, 3]
+ a.slice!(eval("(1..)")).should == [2, 3]
+ a.should == [1]
+
+ a = [1, 2, 3]
+ a.slice!(eval("(2...)")).should == [3]
+ a.should == [1, 2]
+
+ a = [1, 2, 3]
+ a.slice!(eval("(-2..)")).should == [2, 3]
+ a.should == [1]
+
+ a = [1, 2, 3]
+ a.slice!(eval("(-1...)")).should == [3]
+ a.should == [1, 2]
+ end
+
+ it "works with beginless ranges" do
+ a = [0,1,2,3,4]
+ a.slice!((..3)).should == [0, 1, 2, 3]
+ a.should == [4]
+
+ a = [0,1,2,3,4]
+ a.slice!((...-2)).should == [0, 1, 2]
+ a.should == [3, 4]
+ end
+
+ describe "with a subclass of Array" do
+ before :each do
+ @array = ArraySpecs::MyArray[1, 2, 3, 4, 5]
+ end
+
+ it "returns a Array instance with [n, m]" do
+ @array.slice!(0, 2).should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [-n, m]" do
+ @array.slice!(-3, 2).should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [n..m]" do
+ @array.slice!(1..3).should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [n...m]" do
+ @array.slice!(1...3).should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [-n..-m]" do
+ @array.slice!(-3..-1).should.instance_of?(Array)
+ end
+
+ it "returns a Array instance with [-n...-m]" do
+ @array.slice!(-3...-1).should.instance_of?(Array)
+ end
+ end
+end
+
+describe "Array#slice" do
+ it "is an alias of Array#[]" do
+ Array.instance_method(:slice).should == Array.instance_method(:[])
+ end
+end
diff --git a/spec/ruby/core/array/sort_by_spec.rb b/spec/ruby/core/array/sort_by_spec.rb
new file mode 100644
index 0000000000..132abb028a
--- /dev/null
+++ b/spec/ruby/core/array/sort_by_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Array#sort_by!" do
+ it "sorts array in place by passing each element to the given block" do
+ a = [-100, -2, 1, 200, 30000]
+ a.sort_by!{ |e| e.to_s.size }
+ a.should == [1, -2, 200, -100, 30000]
+ end
+
+ it "returns an Enumerator if not given a block" do
+ (1..10).to_a.sort_by!.should.instance_of?(Enumerator)
+ end
+
+ it "completes when supplied a block that always returns the same result" do
+ a = [2, 3, 5, 1, 4]
+ a.sort_by!{ 1 }
+ a.should.instance_of?(Array)
+ a.sort_by!{ 0 }
+ a.should.instance_of?(Array)
+ a.sort_by!{ -1 }
+ a.should.instance_of?(Array)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.sort_by! {}}.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError on an empty frozen array" do
+ -> { ArraySpecs.empty_frozen_array.sort_by! {}}.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError on a frozen array only during iteration if called without a block" do
+ enum = ArraySpecs.frozen_array.sort_by!
+ -> { enum.each {} }.should.raise(FrozenError)
+ end
+
+ it "returns the specified value when it would break in the given block" do
+ [1, 2, 3].sort_by!{ break :a }.should == :a
+ end
+
+ it "makes some modification even if finished sorting when it would break in the given block" do
+ partially_sorted = (1..5).map{|i|
+ ary = [5, 4, 3, 2, 1]
+ ary.sort_by!{|x,y| break if x==i; x<=>y}
+ ary
+ }
+ partially_sorted.any?{|ary| ary != [1, 2, 3, 4, 5]}.should == true
+ end
+
+ it "changes nothing when called on a single element array" do
+ [1].sort_by!(&:to_s).should == [1]
+ end
+
+ it "does not truncate the array is the block raises an exception" do
+ a = [1, 2, 3]
+ begin
+ a.sort_by! { raise StandardError, 'Oops' }
+ rescue
+ end
+
+ a.should == [1, 2, 3]
+ end
+
+ it "doesn't change array if error is raised" do
+ a = [4, 3, 2, 1]
+ begin
+ a.sort_by! do |e|
+ raise StandardError, 'Oops' if e == 1
+ e
+ end
+ rescue StandardError
+ end
+
+ a.should == [4, 3, 2, 1]
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :sort_by!, [1,2,3]
+end
+
+describe "Array#sort_by!" do
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :sort_by!
+end
diff --git a/spec/ruby/core/array/sort_spec.rb b/spec/ruby/core/array/sort_spec.rb
new file mode 100644
index 0000000000..27300c3385
--- /dev/null
+++ b/spec/ruby/core/array/sort_spec.rb
@@ -0,0 +1,252 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#sort" do
+ it "returns a new array sorted based on comparing elements with <=>" do
+ a = [1, -2, 3, 9, 1, 5, -5, 1000, -5, 2, -10, 14, 6, 23, 0]
+ a.sort.should == [-10, -5, -5, -2, 0, 1, 1, 2, 3, 5, 6, 9, 14, 23, 1000]
+ end
+
+ it "does not affect the original Array" do
+ a = [3, 1, 2]
+ a.sort.should == [1, 2, 3]
+ a.should == [3, 1, 2]
+
+ a = [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13]
+ b = a.sort
+ a.should == [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13]
+ b.should == (0..15).to_a
+ end
+
+ it "sorts already-sorted Arrays" do
+ (0..15).to_a.sort.should == (0..15).to_a
+ end
+
+ it "sorts reverse-sorted Arrays" do
+ (0..15).to_a.reverse.sort.should == (0..15).to_a
+ end
+
+ it "sorts Arrays that consist entirely of equal elements" do
+ a = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+ a.sort.should == a
+ b = Array.new(15).map { ArraySpecs::SortSame.new }
+ b.sort.should == b
+ end
+
+ it "sorts Arrays that consist mostly of equal elements" do
+ a = [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+ a.sort.should == [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
+ end
+
+ it "does not return self even if the array would be already sorted" do
+ a = [1, 2, 3]
+ sorted = a.sort
+ sorted.should == a
+ sorted.should_not.equal?(a)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.sort.should == empty
+
+ array = [[]]; array << array
+ array.sort.should == [[], array]
+ end
+
+ it "uses #<=> of elements in order to sort" do
+ a = ArraySpecs::MockForCompared.new
+ b = ArraySpecs::MockForCompared.new
+ c = ArraySpecs::MockForCompared.new
+
+ ArraySpecs::MockForCompared.should_not.compared?
+ [a, b, c].sort.should == [c, b, a]
+ ArraySpecs::MockForCompared.should.compared?
+ end
+
+ it "does not deal with exceptions raised by unimplemented or incorrect #<=>" do
+ o = Object.new
+
+ -> {
+ [o, 1].sort
+ }.should.raise(ArgumentError)
+ end
+
+ it "may take a block which is used to determine the order of objects a and b described as -1, 0 or +1" do
+ a = [5, 1, 4, 3, 2]
+ a.sort.should == [1, 2, 3, 4, 5]
+ a.sort {|x, y| y <=> x}.should == [5, 4, 3, 2, 1]
+ end
+
+ it "raises an error when a given block returns nil" do
+ -> { [1, 2].sort {} }.should.raise(ArgumentError)
+ end
+
+ it "does not call #<=> on contained objects when invoked with a block" do
+ a = Array.new(25)
+ (0...25).each {|i| a[i] = ArraySpecs::UFOSceptic.new }
+
+ a.sort { -1 }.should.instance_of?(Array)
+ end
+
+ it "does not call #<=> on elements when invoked with a block even if Array is large (Rubinius #412)" do
+ a = Array.new(1500)
+ (0...1500).each {|i| a[i] = ArraySpecs::UFOSceptic.new }
+
+ a.sort { -1 }.should.instance_of?(Array)
+ end
+
+ it "completes when supplied a block that always returns the same result" do
+ a = [2, 3, 5, 1, 4]
+ a.sort { 1 }.should.instance_of?(Array)
+ a.sort { 0 }.should.instance_of?(Array)
+ a.sort { -1 }.should.instance_of?(Array)
+ end
+
+ it "does not freezes self during being sorted" do
+ a = [1, 2, 3]
+ a.sort { |x,y| a.should_not.frozen?; x <=> y }
+ end
+
+ it "returns the specified value when it would break in the given block" do
+ [1, 2, 3].sort{ break :a }.should == :a
+ end
+
+ it "uses the sign of Integer block results as the sort result" do
+ a = [1, 2, 5, 10, 7, -4, 12]
+ begin
+ class Integer
+ alias old_spaceship <=>
+ def <=>(other)
+ raise
+ end
+ end
+ a.sort {|n, m| (n - m) * (2 ** 200)}.should == [-4, 1, 2, 5, 7, 10, 12]
+ ensure
+ class Integer
+ alias <=> old_spaceship
+ end
+ end
+ end
+
+ it "compares values returned by block with 0" do
+ a = [1, 2, 5, 10, 7, -4, 12]
+ a.sort { |n, m| n - m }.should == [-4, 1, 2, 5, 7, 10, 12]
+ a.sort { |n, m|
+ ArraySpecs::ComparableWithInteger.new(n-m)
+ }.should == [-4, 1, 2, 5, 7, 10, 12]
+ -> {
+ a.sort { |n, m| (n - m).to_s }
+ }.should.raise(ArgumentError)
+ end
+
+ it "sorts an array that has a value shifted off without a block" do
+ a = Array.new(20, 1)
+ a.shift
+ a[0] = 2
+ a.sort.last.should == 2
+ end
+
+ it "sorts an array that has a value shifted off with a block" do
+ a = Array.new(20, 1)
+ a.shift
+ a[0] = 2
+ a.sort {|x, y| x <=> y }.last.should == 2
+ end
+
+ it "raises an error if objects can't be compared" do
+ a=[ArraySpecs::Uncomparable.new, ArraySpecs::Uncomparable.new]
+ -> {a.sort}.should.raise(ArgumentError)
+ end
+
+ # From a strange Rubinius bug
+ it "handles a large array that has been pruned" do
+ pruned = ArraySpecs::LargeArray.dup.delete_if { |n| n !~ /^test./ }
+ pruned.sort.should == ArraySpecs::LargeTestArraySorted
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ary = ArraySpecs::MyArray[1, 2, 3]
+ ary.sort.should.instance_of?(Array)
+ end
+end
+
+describe "Array#sort!" do
+ it "sorts array in place using <=>" do
+ a = [1, -2, 3, 9, 1, 5, -5, 1000, -5, 2, -10, 14, 6, 23, 0]
+ a.sort!
+ a.should == [-10, -5, -5, -2, 0, 1, 1, 2, 3, 5, 6, 9, 14, 23, 1000]
+ end
+
+ it "sorts array in place using block value if a block given" do
+ a = [0, 15, 2, 3, 4, 6, 14, 5, 7, 12, 8, 9, 1, 10, 11, 13]
+ a.sort! { |x, y| y <=> x }.should == (0..15).to_a.reverse
+ end
+
+ it "returns self if the order of elements changed" do
+ a = [6, 7, 2, 3, 7]
+ a.sort!.should.equal?(a)
+ a.should == [2, 3, 6, 7, 7]
+ end
+
+ it "returns self even if makes no modification" do
+ a = [1, 2, 3, 4, 5]
+ a.sort!.should.equal?(a)
+ a.should == [1, 2, 3, 4, 5]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.sort!.should == empty
+
+ array = [[]]; array << array
+ array.sort!.should == array
+ end
+
+ it "uses #<=> of elements in order to sort" do
+ a = ArraySpecs::MockForCompared.new
+ b = ArraySpecs::MockForCompared.new
+ c = ArraySpecs::MockForCompared.new
+
+ ArraySpecs::MockForCompared.should_not.compared?
+ [a, b, c].sort!.should == [c, b, a]
+ ArraySpecs::MockForCompared.should.compared?
+ end
+
+ it "does not call #<=> on contained objects when invoked with a block" do
+ a = Array.new(25)
+ (0...25).each {|i| a[i] = ArraySpecs::UFOSceptic.new }
+
+ a.sort! { -1 }.should.instance_of?(Array)
+ end
+
+ it "does not call #<=> on elements when invoked with a block even if Array is large (Rubinius #412)" do
+ a = Array.new(1500)
+ (0...1500).each {|i| a[i] = ArraySpecs::UFOSceptic.new }
+
+ a.sort! { -1 }.should.instance_of?(Array)
+ end
+
+ it "completes when supplied a block that always returns the same result" do
+ a = [2, 3, 5, 1, 4]
+ a.sort!{ 1 }.should.instance_of?(Array)
+ a.sort!{ 0 }.should.instance_of?(Array)
+ a.sort!{ -1 }.should.instance_of?(Array)
+ end
+
+ it "raises a FrozenError on a frozen array" do
+ -> { ArraySpecs.frozen_array.sort! }.should.raise(FrozenError)
+ end
+
+ it "returns the specified value when it would break in the given block" do
+ [1, 2, 3].sort{ break :a }.should == :a
+ end
+
+ it "makes some modification even if finished sorting when it would break in the given block" do
+ partially_sorted = (1..5).map{|i|
+ ary = [5, 4, 3, 2, 1]
+ ary.sort!{|x,y| break if x==i; x<=>y}
+ ary
+ }
+ partially_sorted.any?{|ary| ary != [1, 2, 3, 4, 5]}.should == true
+ end
+end
diff --git a/spec/ruby/core/array/sum_spec.rb b/spec/ruby/core/array/sum_spec.rb
new file mode 100644
index 0000000000..cd4ba4c2d8
--- /dev/null
+++ b/spec/ruby/core/array/sum_spec.rb
@@ -0,0 +1,88 @@
+require_relative '../../spec_helper'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#sum" do
+ it "returns the sum of elements" do
+ [1, 2, 3].sum.should == 6
+ end
+
+ it "applies a block to each element before adding if it's given" do
+ [1, 2, 3].sum { |i| i * 10 }.should == 60
+ end
+
+ it "doesn't apply the block init" do
+ [1, 2, 3].sum(1) { |i| i * 10 }.should == 61
+ end
+
+ # https://bugs.ruby-lang.org/issues/12217
+ # https://github.com/ruby/ruby/blob/master/doc/ChangeLog/ChangeLog-2.4.0#L6208-L6214
+ it "uses Kahan's compensated summation algorithm for precise sum of float numbers" do
+ floats = [2.7800000000000002, 5.0, 2.5, 4.44, 3.89, 3.89, 4.44, 7.78, 5.0, 2.7800000000000002, 5.0, 2.5]
+ naive_sum = floats.reduce { |sum, e| sum + e }
+ naive_sum.should == 50.00000000000001
+ floats.sum.should == 50.0
+ end
+
+ it "handles infinite values and NaN" do
+ [1.0, Float::INFINITY].sum.should == Float::INFINITY
+ [1.0, -Float::INFINITY].sum.should == -Float::INFINITY
+ [1.0, Float::NAN].sum.should.nan?
+
+ [Float::INFINITY, 1.0].sum.should == Float::INFINITY
+ [-Float::INFINITY, 1.0].sum.should == -Float::INFINITY
+ [Float::NAN, 1.0].sum.should.nan?
+
+ [Float::NAN, Float::INFINITY].sum.should.nan?
+ [Float::INFINITY, Float::NAN].sum.should.nan?
+
+ [Float::INFINITY, -Float::INFINITY].sum.should.nan?
+ [-Float::INFINITY, Float::INFINITY].sum.should.nan?
+
+ [Float::INFINITY, Float::INFINITY].sum.should == Float::INFINITY
+ [-Float::INFINITY, -Float::INFINITY].sum.should == -Float::INFINITY
+ [Float::NAN, Float::NAN].sum.should.nan?
+ end
+
+ it "returns init value if array is empty" do
+ [].sum(-1).should == -1
+ end
+
+ it "returns 0 if array is empty and init is omitted" do
+ [].sum.should == 0
+ end
+
+ it "adds init value to the sum of elements" do
+ [1, 2, 3].sum(10).should == 16
+ end
+
+ it "can be used for non-numeric objects by providing init value" do
+ ["a", "b", "c"].sum("").should == "abc"
+ end
+
+ it 'raises TypeError if any element are not numeric' do
+ -> { ["a"].sum }.should.raise(TypeError)
+ end
+
+ it 'raises TypeError if any element cannot be added to init value' do
+ -> { [1].sum([]) }.should.raise(TypeError)
+ end
+
+ it "calls + to sum the elements" do
+ a = mock("a")
+ b = mock("b")
+ a.should_receive(:+).with(b).and_return(42)
+ [b].sum(a).should == 42
+ end
+
+ it "calls + on the init value" do
+ a = mock("a")
+ b = mock("b")
+ a.should_receive(:+).with(42).and_return(b)
+ [42].sum(a).should == b
+ end
+end
+
+describe "Array#sum" do
+ @value_to_return = -> _ { 1 }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :sum
+end
diff --git a/spec/ruby/core/array/take_spec.rb b/spec/ruby/core/array/take_spec.rb
new file mode 100644
index 0000000000..837c734b77
--- /dev/null
+++ b/spec/ruby/core/array/take_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#take" do
+ it "returns the first specified number of elements" do
+ [1, 2, 3].take(2).should == [1, 2]
+ end
+
+ it "returns all elements when the argument is greater than the Array size" do
+ [1, 2].take(99).should == [1, 2]
+ end
+
+ it "returns all elements when the argument is less than the Array size" do
+ [1, 2].take(4).should == [1, 2]
+ end
+
+ it "returns an empty Array when passed zero" do
+ [1].take(0).should == []
+ end
+
+ it "returns an empty Array when called on an empty Array" do
+ [].take(3).should == []
+ end
+
+ it "raises an ArgumentError when the argument is negative" do
+ ->{ [1].take(-3) }.should.raise(ArgumentError)
+ end
+
+ it 'returns a Array instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].take(1).should.instance_of?(Array)
+ end
+end
diff --git a/spec/ruby/core/array/take_while_spec.rb b/spec/ruby/core/array/take_while_spec.rb
new file mode 100644
index 0000000000..7811edab9e
--- /dev/null
+++ b/spec/ruby/core/array/take_while_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#take_while" do
+ it "returns all elements until the block returns false" do
+ [1, 2, 3].take_while{ |element| element < 3 }.should == [1, 2]
+ end
+
+ it "returns all elements until the block returns nil" do
+ [1, 2, nil, 4].take_while{ |element| element }.should == [1, 2]
+ end
+
+ it "returns all elements until the block returns false" do
+ [1, 2, false, 4].take_while{ |element| element }.should == [1, 2]
+ end
+
+ it 'returns a Array instance for Array subclasses' do
+ ArraySpecs::MyArray[1, 2, 3, 4, 5].take_while { |n| n < 4 }.should.instance_of?(Array)
+ end
+end
+
+describe "Array#take_while" do
+ @value_to_return = -> _ { true }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :take_while
+end
diff --git a/spec/ruby/core/array/to_a_spec.rb b/spec/ruby/core/array/to_a_spec.rb
new file mode 100644
index 0000000000..078de1638a
--- /dev/null
+++ b/spec/ruby/core/array/to_a_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#to_a" do
+ it "returns self" do
+ a = [1, 2, 3]
+ a.to_a.should == [1, 2, 3]
+ a.should.equal?(a.to_a)
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ e = ArraySpecs::MyArray.new(1, 2)
+ e.to_a.should.instance_of?(Array)
+ e.to_a.should == [1, 2]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.to_a.should == empty
+
+ array = ArraySpecs.recursive_array
+ array.to_a.should == array
+ end
+end
diff --git a/spec/ruby/core/array/to_ary_spec.rb b/spec/ruby/core/array/to_ary_spec.rb
new file mode 100644
index 0000000000..dc5193158d
--- /dev/null
+++ b/spec/ruby/core/array/to_ary_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#to_ary" do
+ it "returns self" do
+ a = [1, 2, 3]
+ a.should.equal?(a.to_ary)
+ a = ArraySpecs::MyArray[1, 2, 3]
+ a.should.equal?(a.to_ary)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.to_ary.should == empty
+
+ array = ArraySpecs.recursive_array
+ array.to_ary.should == array
+ end
+
+end
diff --git a/spec/ruby/core/array/to_h_spec.rb b/spec/ruby/core/array/to_h_spec.rb
new file mode 100644
index 0000000000..1d626763c2
--- /dev/null
+++ b/spec/ruby/core/array/to_h_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#to_h" do
+ it "converts empty array to empty hash" do
+ [].to_h.should == {}
+ end
+
+ it "converts [key, value] pairs to a hash" do
+ hash = [[:a, 1], [:b, 2]].to_h
+ hash.should == { a: 1, b: 2 }
+ end
+
+ it "uses the last value of a duplicated key" do
+ hash = [[:a, 1], [:b, 2], [:a, 3]].to_h
+ hash.should == { a: 3, b: 2 }
+ end
+
+ it "calls #to_ary on contents" do
+ pair = mock('to_ary')
+ pair.should_receive(:to_ary).and_return([:b, 2])
+ hash = [[:a, 1], pair].to_h
+ hash.should == { a: 1, b: 2 }
+ end
+
+ it "raises TypeError if an element is not an array" do
+ -> { [:x].to_h }.should.raise(TypeError)
+ end
+
+ it "raises ArgumentError if an element is not a [key, value] pair" do
+ -> { [[:x]].to_h }.should.raise(ArgumentError)
+ end
+
+ it "does not accept arguments" do
+ -> { [].to_h(:a, :b) }.should.raise(ArgumentError)
+ end
+
+ it "produces a hash that returns nil for a missing element" do
+ [[:a, 1], [:b, 2]].to_h[:c].should == nil
+ end
+
+ context "with block" do
+ it "converts [key, value] pairs returned by the block to a Hash" 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] }
+ end.should.raise(ArgumentError, /wrong array length at 0/)
+
+ -> do
+ [:a, :b].to_h { |k| [k] }
+ end.should.raise(ArgumentError, /wrong array length at 0/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ [:a, :b].to_h { |k| "not-array" }
+ end.should.raise(TypeError, /wrong element type String at 0/)
+ end
+
+ it "coerces returned pair to Array with #to_ary" do
+ x = mock('x')
+ x.stub!(:to_ary).and_return([:b, 'b'])
+
+ [:a].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'])
+
+ -> do
+ [:a].to_h { |k| x }
+ end.should.raise(TypeError, /wrong element type MockObject at 0/)
+ end
+ end
+end
+
+describe "Array#to_h" do
+ @value_to_return = -> e { [e, e.to_s] }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :to_h
+end
diff --git a/spec/ruby/core/array/to_s_spec.rb b/spec/ruby/core/array/to_s_spec.rb
new file mode 100644
index 0000000000..483e14f902
--- /dev/null
+++ b/spec/ruby/core/array/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Array#to_s" do
+ it "is an alias of Array#inspect" do
+ Array.instance_method(:to_s).should == Array.instance_method(:inspect)
+ end
+end
diff --git a/spec/ruby/core/array/transpose_spec.rb b/spec/ruby/core/array/transpose_spec.rb
new file mode 100644
index 0000000000..d45e9c351c
--- /dev/null
+++ b/spec/ruby/core/array/transpose_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#transpose" do
+ it "assumes an array of arrays and returns the result of transposing rows and columns" do
+ [[1, 'a'], [2, 'b'], [3, 'c']].transpose.should == [[1, 2, 3], ["a", "b", "c"]]
+ [[1, 2, 3], ["a", "b", "c"]].transpose.should == [[1, 'a'], [2, 'b'], [3, 'c']]
+ [].transpose.should == []
+ [[]].transpose.should == []
+ [[], []].transpose.should == []
+ [[0]].transpose.should == [[0]]
+ [[0], [1]].transpose.should == [[0, 1]]
+ end
+
+ it "tries to convert the passed argument to an Array using #to_ary" do
+ obj = mock('[1,2]')
+ obj.should_receive(:to_ary).and_return([1, 2])
+ [obj, [:a, :b]].transpose.should == [[1, :a], [2, :b]]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.transpose.should == empty
+
+ a = []; a << a
+ b = []; b << b
+ [a, b].transpose.should == [[a, b]]
+
+ a = [1]; a << a
+ b = [2]; b << b
+ [a, b].transpose.should == [ [1, 2], [a, b] ]
+ end
+
+ it "raises a TypeError if the passed Argument does not respond to #to_ary" do
+ -> { [Object.new, [:a, :b]].transpose }.should.raise(TypeError)
+ end
+
+ it "does not call to_ary on array subclass elements" do
+ ary = [ArraySpecs::ToAryArray[1, 2], ArraySpecs::ToAryArray[4, 6]]
+ ary.transpose.should == [[1, 4], [2, 6]]
+ end
+
+ it "raises an IndexError if the arrays are not of the same length" do
+ -> { [[1, 2], [:a]].transpose }.should.raise(IndexError)
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ result = ArraySpecs::MyArray[ArraySpecs::MyArray[1, 2, 3], ArraySpecs::MyArray[4, 5, 6]].transpose
+ result.should.instance_of?(Array)
+ result[0].should.instance_of?(Array)
+ result[1].should.instance_of?(Array)
+ end
+end
diff --git a/spec/ruby/core/array/try_convert_spec.rb b/spec/ruby/core/array/try_convert_spec.rb
new file mode 100644
index 0000000000..3eaa0f4b7c
--- /dev/null
+++ b/spec/ruby/core/array/try_convert_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array.try_convert" do
+ it "returns the argument if it's an Array" do
+ x = Array.new
+ Array.try_convert(x).should.equal?(x)
+ end
+
+ it "returns the argument if it's a kind of Array" do
+ x = ArraySpecs::MyArray[]
+ Array.try_convert(x).should.equal?(x)
+ end
+
+ it "returns nil when the argument does not respond to #to_ary" do
+ Array.try_convert(Object.new).should == nil
+ end
+
+ it "sends #to_ary to the argument and returns the result if it's nil" do
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(nil)
+ Array.try_convert(obj).should == nil
+ end
+
+ it "sends #to_ary to the argument and returns the result if it's an Array" do
+ x = Array.new
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(x)
+ Array.try_convert(obj).should.equal?(x)
+ end
+
+ it "sends #to_ary to the argument and returns the result if it's a kind of Array" do
+ x = ArraySpecs::MyArray[]
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(x)
+ Array.try_convert(obj).should.equal?(x)
+ end
+
+ it "sends #to_ary to the argument and raises TypeError if it's not a kind of Array" do
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_return(Object.new)
+ -> { Array.try_convert obj }.should raise_consistent_error(TypeError, "can't convert MockObject into Array (MockObject#to_ary gives Object)")
+ end
+
+ it "does not rescue exceptions raised by #to_ary" do
+ obj = mock("to_ary")
+ obj.should_receive(:to_ary).and_raise(RuntimeError)
+ -> { Array.try_convert obj }.should.raise(RuntimeError)
+ end
+end
diff --git a/spec/ruby/core/array/union_spec.rb b/spec/ruby/core/array/union_spec.rb
new file mode 100644
index 0000000000..110894e83d
--- /dev/null
+++ b/spec/ruby/core/array/union_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/union'
+
+describe "Array#|" do
+ it_behaves_like :array_binary_union, :|
+end
+
+describe "Array#union" do
+ it_behaves_like :array_binary_union, :union
+
+ it "returns unique elements when given no argument" do
+ x = [1, 2, 3, 2]
+ x.union.should == [1, 2, 3]
+ end
+
+ it "does not return subclass instances for Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].union.should.instance_of?(Array)
+ end
+
+ it "accepts multiple arguments" do
+ x = [1, 2, 3]
+ x.union(x, x, x, x, [3, 4], x).should == [1, 2, 3, 4]
+ end
+end
diff --git a/spec/ruby/core/array/uniq_spec.rb b/spec/ruby/core/array/uniq_spec.rb
new file mode 100644
index 0000000000..0289bee7c2
--- /dev/null
+++ b/spec/ruby/core/array/uniq_spec.rb
@@ -0,0 +1,243 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iterable_and_tolerating_size_increasing'
+
+describe "Array#uniq" do
+ it "returns an array with no duplicates" do
+ ["a", "a", "b", "b", "c"].uniq.should == ["a", "b", "c"]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.uniq.should == [empty]
+
+ array = ArraySpecs.recursive_array
+ array.uniq.should == [1, 'two', 3.0, array]
+ end
+
+ it "uses eql? semantics" do
+ [1.0, 1].uniq.should == [1.0, 1]
+ end
+
+ it "compares elements first with hash" do
+ x = mock('0')
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y = mock('0')
+ y.should_receive(:hash).at_least(1).and_return(0)
+
+ [x, y].uniq.should == [x, y]
+ end
+
+ it "does not compare elements with different hash codes via eql?" do
+ x = mock('0')
+ x.should_not_receive(:eql?)
+ y = mock('1')
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y.should_receive(:hash).at_least(1).and_return(1)
+
+ [x, y].uniq.should == [x, y]
+ end
+
+ it "compares elements with matching hash codes with #eql?" do
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ false
+ end
+
+ obj
+ end
+
+ a.uniq.should == a
+
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ true
+ end
+
+ obj
+ end
+
+ a.uniq.size.should == 1
+ end
+
+ it "compares elements based on the value returned from the block" do
+ a = [1, 2, 3, 4]
+ a.uniq { |x| x >= 2 ? 1 : 0 }.should == [1, 2]
+ end
+
+ it "yields items in order" do
+ a = [1, 2, 3]
+ yielded = []
+ a.uniq { |v| yielded << v }
+ yielded.should == a
+ end
+
+ it "handles nil and false like any other values" do
+ [nil, false, 42].uniq { :foo }.should == [nil]
+ [false, nil, 42].uniq { :bar }.should == [false]
+ end
+
+ it "returns Array instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].uniq.should.instance_of?(Array)
+ end
+
+ it "properly handles an identical item 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.
+
+ [x, x].uniq.should == [x]
+ end
+
+ describe "given an array of BasicObject subclasses that define ==, eql?, and hash" do
+ # jruby/jruby#3227
+ it "filters equivalent elements using those definitions" do
+
+ basic = Class.new(BasicObject) do
+ attr_reader :x
+
+ def initialize(x)
+ @x = x
+ end
+
+ def ==(rhs)
+ @x == rhs.x
+ end
+ alias_method :eql?, :==
+
+ def hash
+ @x.hash
+ end
+ end
+
+ a = [basic.new(3), basic.new(2), basic.new(1), basic.new(4), basic.new(1), basic.new(2), basic.new(3)]
+ a.uniq.should == [basic.new(3), basic.new(2), basic.new(1), basic.new(4)]
+ end
+ end
+end
+
+describe "Array#uniq" do
+ @value_to_return = -> e { e }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :uniq
+end
+
+describe "Array#uniq!" do
+ it "modifies the array in place" do
+ a = [ "a", "a", "b", "b", "c" ]
+ a.uniq!
+ a.should == ["a", "b", "c"]
+ end
+
+ it "returns self" do
+ a = [ "a", "a", "b", "b", "c" ]
+ a.should.equal?(a.uniq!)
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty_dup = empty.dup
+ empty.uniq!
+ empty.should == empty_dup
+
+ array = ArraySpecs.recursive_array
+ expected = array[0..3]
+ array.uniq!
+ array.should == expected
+ end
+
+ it "compares elements first with hash" do
+ x = mock('0')
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y = mock('0')
+ y.should_receive(:hash).at_least(1).and_return(0)
+
+ a = [x, y]
+ a.uniq!
+ a.should == [x, y]
+ end
+
+ it "does not compare elements with different hash codes via eql?" do
+ x = mock('0')
+ x.should_not_receive(:eql?)
+ y = mock('1')
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y.should_receive(:hash).at_least(1).and_return(1)
+
+ a = [x, y]
+ a.uniq!
+ a.should == [x, y]
+ end
+
+ it "returns nil if no changes are made to the array" do
+ [ "a", "b", "c" ].uniq!.should == nil
+ end
+
+ it "raises a FrozenError on a frozen array when the array is modified" do
+ dup_ary = [1, 1, 2]
+ dup_ary.freeze
+ -> { dup_ary.uniq! }.should.raise(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen array when the array would not be modified" do
+ -> { ArraySpecs.frozen_array.uniq!}.should.raise(FrozenError)
+ -> { ArraySpecs.empty_frozen_array.uniq!}.should.raise(FrozenError)
+ end
+
+ it "doesn't yield to the block on a frozen array" do
+ -> { ArraySpecs.frozen_array.uniq!{ raise RangeError, "shouldn't yield"}}.should.raise(FrozenError)
+ end
+
+ it "compares elements based on the value returned from the block" do
+ a = [1, 2, 3, 4]
+ a.uniq! { |x| x >= 2 ? 1 : 0 }.should == [1, 2]
+ end
+
+ it "properly handles an identical item 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.
+
+ a = [x, x]
+ a.uniq!
+ a.should == [x]
+ end
+
+ it "does not truncate the array is the block raises an exception" do
+ a = [1, 2, 3]
+ begin
+ a.send(@method) { raise StandardError, 'Oops' }
+ rescue
+ end
+
+ a.should == [1, 2, 3]
+ end
+
+ it "doesn't change array if error is raised" do
+ a = [1, 1, 2, 2, 3, 3, 4, 4]
+ begin
+ a.send(@method) do |e|
+ raise StandardError, 'Oops' if e == 3
+ e
+ end
+ rescue StandardError
+ end
+
+ a.should == [1, 1, 2, 2, 3, 3, 4, 4]
+ end
+end
+
+describe "Array#uniq!" do
+ @value_to_return = -> e { e }
+ it_behaves_like :array_iterable_and_tolerating_size_increasing, :uniq!
+end
diff --git a/spec/ruby/core/array/unshift_spec.rb b/spec/ruby/core/array/unshift_spec.rb
new file mode 100644
index 0000000000..c190db4d02
--- /dev/null
+++ b/spec/ruby/core/array/unshift_spec.rb
@@ -0,0 +1,67 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#unshift" do
+ it "prepends object to the original array" do
+ a = [1, 2, 3]
+ a.unshift("a").should.equal?(a)
+ a.should == ['a', 1, 2, 3]
+ a.unshift.should.equal?(a)
+ a.should == ['a', 1, 2, 3]
+ a.unshift(5, 4, 3)
+ a.should == [5, 4, 3, 'a', 1, 2, 3]
+
+ # shift all but one element
+ a = [1, 2]
+ a.shift
+ a.unshift(3, 4)
+ a.should == [3, 4, 2]
+
+ # now shift all elements
+ a.shift
+ a.shift
+ a.shift
+ a.unshift(3, 4)
+ a.should == [3, 4]
+ end
+
+ it "returns self" do
+ a = [1, 2, 3]
+ a.unshift("a").should.equal?(a)
+ end
+
+ it "quietly ignores unshifting nothing" do
+ [].unshift.should == []
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.unshift(:new).should == [:new, empty]
+
+ array = ArraySpecs.recursive_array
+ array.unshift(:new)
+ array[0..5].should == [:new, 1, 'two', 3.0, array, array]
+ end
+
+ it "raises a FrozenError on a frozen array when the array is modified" do
+ -> { ArraySpecs.frozen_array.unshift(1) }.should.raise(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen array when the array would not be modified" do
+ -> { ArraySpecs.frozen_array.unshift }.should.raise(FrozenError)
+ end
+
+ # 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 []=(*)
+ raise "[]= is called"
+ end
+ end
+
+ array = subclass.new
+ array.unshift(1)
+ array.should == [1]
+ end
+end
diff --git a/spec/ruby/core/array/values_at_spec.rb b/spec/ruby/core/array/values_at_spec.rb
new file mode 100644
index 0000000000..e11e7e4451
--- /dev/null
+++ b/spec/ruby/core/array/values_at_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# Should be synchronized with core/struct/values_at_spec.rb
+describe "Array#values_at" do
+ it "returns an array of elements at the indexes when passed indexes" do
+ [1, 2, 3, 4, 5].values_at().should == []
+ [1, 2, 3, 4, 5].values_at(1, 0, 5, -1, -8, 10).should == [2, 1, nil, 5, nil, nil]
+ end
+
+ it "calls to_int on its indices" do
+ obj = mock('1')
+ def obj.to_int() 1 end
+ [1, 2].values_at(obj, obj, obj).should == [2, 2, 2]
+ end
+
+ it "properly handles recursive arrays" do
+ empty = ArraySpecs.empty_recursive_array
+ empty.values_at(0, 1, 2).should == [empty, nil, nil]
+
+ array = ArraySpecs.recursive_array
+ array.values_at(0, 1, 2, 3).should == [1, 'two', 3.0, array]
+ end
+
+ describe "when passed ranges" do
+ it "returns an array of elements in the ranges" do
+ [1, 2, 3, 4, 5].values_at(0..2, 1...3, 2..-2).should == [1, 2, 3, 2, 3, 3, 4]
+ [1, 2, 3, 4, 5].values_at(6..4).should == []
+ end
+
+ it "calls to_int on arguments of ranges" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ def from.<=>(o) 0 end
+ def to.<=>(o) 0 end
+
+ def from.to_int() 1 end
+ def to.to_int() -2 end
+
+ ary = [1, 2, 3, 4, 5]
+ ary.values_at(from .. to, from ... to, to .. from).should == [2, 3, 4, 2, 3]
+ end
+ end
+
+ describe "when passed a range" do
+ it "fills with nil if the index is out of the range" do
+ [0, 1].values_at(0..3).should == [0, 1, nil, nil]
+ [0, 1].values_at(2..4).should == [nil, nil, nil]
+ end
+
+ describe "on an empty array" do
+ it "fills with nils if the index is out of the range" do
+ [].values_at(0..2).should == [nil, nil, nil]
+ [].values_at(1..3).should == [nil, nil, nil]
+ end
+ end
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].values_at(0, 1..2, 1).should.instance_of?(Array)
+ end
+
+ it "works when given endless ranges" do
+ [1, 2, 3, 4].values_at(eval("(1..)")).should == [2, 3, 4]
+ [1, 2, 3, 4].values_at(eval("(3...)")).should == [4]
+ end
+
+ it "works when given beginless ranges" do
+ [1, 2, 3, 4].values_at((..2)).should == [1, 2, 3]
+ [1, 2, 3, 4].values_at((...2)).should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/array/zip_spec.rb b/spec/ruby/core/array/zip_spec.rb
new file mode 100644
index 0000000000..3ccdf143d6
--- /dev/null
+++ b/spec/ruby/core/array/zip_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Array#zip" do
+ it "returns an array of arrays containing corresponding elements of each array" do
+ [1, 2, 3, 4].zip(["a", "b", "c", "d", "e"]).should ==
+ [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
+ end
+
+ it "fills in missing values with nil" do
+ [1, 2, 3, 4, 5].zip(["a", "b", "c", "d"]).should ==
+ [[1, "a"], [2, "b"], [3, "c"], [4, "d"], [5, nil]]
+ end
+
+ it "properly handles recursive arrays" do
+ a = []; a << a
+ b = [1]; b << b
+
+ a.zip(a).should == [ [a[0], a[0]] ]
+ a.zip(b).should == [ [a[0], b[0]] ]
+ b.zip(a).should == [ [b[0], a[0]], [b[1], a[1]] ]
+ b.zip(b).should == [ [b[0], b[0]], [b[1], b[1]] ]
+ end
+
+ it "calls #to_ary to convert the argument to an Array" do
+ obj = mock('[3,4]')
+ obj.should_receive(:to_ary).and_return([3, 4])
+ [1, 2].zip(obj).should == [[1, 3], [2, 4]]
+ end
+
+ it "uses #each to extract arguments' elements when #to_ary fails" do
+ obj = Class.new do
+ def each(&b)
+ [3,4].each(&b)
+ end
+ end.new
+
+ [1, 2].zip(obj).should == [[1, 3], [2, 4]]
+ end
+
+ it "stops at own size when given an infinite enumerator" do
+ [1, 2].zip(10.upto(Float::INFINITY)).should == [[1, 10], [2, 11]]
+ end
+
+ it "fills nil when the given enumerator is shorter than self" do
+ obj = Object.new
+ def obj.each
+ yield 10
+ end
+ [1, 2].zip(obj).should == [[1, 10], [2, nil]]
+ end
+
+ it "calls block if supplied" do
+ values = []
+ [1, 2, 3, 4].zip(["a", "b", "c", "d", "e"]) { |value|
+ values << value
+ }.should == nil
+
+ values.should == [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
+ end
+
+ it "does not return subclass instance on Array subclasses" do
+ ArraySpecs::MyArray[1, 2, 3].zip(["a", "b"]).should.instance_of?(Array)
+ end
+
+ it "raises TypeError when some argument isn't Array and doesn't respond to #to_ary and #to_enum" do
+ -> { [1, 2, 3].zip(Object.new) }.should.raise(TypeError, "wrong argument type Object (must respond to :each)")
+ -> { [1, 2, 3].zip(1) }.should.raise(TypeError, "wrong argument type Integer (must respond to :each)")
+ -> { [1, 2, 3].zip(true) }.should.raise(TypeError, "wrong argument type TrueClass (must respond to :each)")
+ end
+end
diff --git a/spec/ruby/core/basicobject/__id__spec.rb b/spec/ruby/core/basicobject/__id__spec.rb
new file mode 100644
index 0000000000..6766db4e82
--- /dev/null
+++ b/spec/ruby/core/basicobject/__id__spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/object_id'
+
+describe "BasicObject#__id__" do
+ it_behaves_like :object_id, :__id__, BasicObject
+end
diff --git a/spec/ruby/core/basicobject/__send___spec.rb b/spec/ruby/core/basicobject/__send___spec.rb
new file mode 100644
index 0000000000..2f814e448c
--- /dev/null
+++ b/spec/ruby/core/basicobject/__send___spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/basicobject/send'
+
+describe "BasicObject#__send__" do
+ it "is a public instance method" do
+ BasicObject.public_instance_methods(false).should.include?(:__send__)
+ end
+
+ it_behaves_like :basicobject_send, :__send__
+end
diff --git a/spec/ruby/core/basicobject/basicobject_spec.rb b/spec/ruby/core/basicobject/basicobject_spec.rb
new file mode 100644
index 0000000000..af28de0687
--- /dev/null
+++ b/spec/ruby/core/basicobject/basicobject_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "BasicObject" do
+ it "raises NoMethodError for nonexistent methods after #method_missing is removed" do
+ script = fixture __FILE__, "remove_method_missing.rb"
+ ruby_exe(script).chomp.should == "NoMethodError"
+ end
+
+ it "raises NameError when referencing built-in constants" do
+ -> { class BasicObjectSpecs::BOSubclass; Kernel; end }.should.raise(NameError)
+ end
+
+ it "does not define built-in constants (according to const_defined?)" do
+ BasicObject.const_defined?(:Kernel).should == false
+ end
+
+ it "does not define built-in constants (according to defined?)" do
+ BasicObjectSpecs::BOSubclass.kernel_defined?.should == nil
+ end
+
+ it "is included in Object's list of constants" do
+ Object.constants(false).should.include?(:BasicObject)
+ end
+
+ it "includes itself in its list of constants" do
+ BasicObject.constants(false).should.include?(:BasicObject)
+ end
+end
+
+describe "BasicObject metaclass" do
+ before :each do
+ @meta = class << BasicObject; self; end
+ end
+
+ it "is an instance of Class" do
+ @meta.should.instance_of?(Class)
+ end
+
+ it "has Class as superclass" do
+ @meta.superclass.should.equal?(Class)
+ end
+
+ it "contains methods for the BasicObject class" do
+ @meta.class_eval do
+ def rubyspec_test_method() :test end
+ end
+
+ BasicObject.rubyspec_test_method.should == :test
+ end
+end
+
+describe "BasicObject instance metaclass" do
+ before :each do
+ @object = BasicObject.new
+ @meta = class << @object; self; end
+ end
+
+ it "is an instance of Class" do
+ @meta.should.instance_of?(Class)
+ end
+
+ it "has BasicObject as superclass" do
+ @meta.superclass.should.equal?(BasicObject)
+ end
+
+ it "contains methods defined for the BasicObject instance" do
+ @meta.class_eval do
+ def test_method() :test end
+ end
+
+ @object.test_method.should == :test
+ end
+end
+
+describe "BasicObject subclass" do
+ it "contains Kernel methods when including Kernel" do
+ obj = BasicObjectSpecs::BOSubclass.new
+
+ obj.instance_variable_set(:@test, :value)
+ obj.instance_variable_get(:@test).should == :value
+
+ obj.respond_to?(:hash).should == true
+ end
+
+ describe "BasicObject references" do
+ it "can refer to BasicObject from within itself" do
+ -> { BasicObject::BasicObject }.should_not.raise
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/equal_spec.rb b/spec/ruby/core/basicobject/equal_spec.rb
new file mode 100644
index 0000000000..c0f41dc0c0
--- /dev/null
+++ b/spec/ruby/core/basicobject/equal_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/equal'
+
+describe "BasicObject#equal?" do
+ it "is a public instance method" do
+ BasicObject.public_instance_methods(false).should.include?(:equal?)
+ end
+
+ it_behaves_like :object_equal, :equal?
+
+ it "is unaffected by overriding __id__" do
+ o1 = mock("object")
+ o2 = mock("object")
+ suppress_warning {
+ def o1.__id__; 10; end
+ def o2.__id__; 10; end
+ }
+ o1.equal?(o2).should == false
+ end
+
+ it "is unaffected by overriding object_id" do
+ o1 = mock("object")
+ o1.stub!(:object_id).and_return(10)
+ o2 = mock("object")
+ o2.stub!(:object_id).and_return(10)
+ o1.equal?(o2).should == false
+ end
+
+ it "is unaffected by overriding ==" do
+ # different objects, overriding == to return true
+ o1 = mock("object")
+ o1.stub!(:==).and_return(true)
+ o2 = mock("object")
+ o1.equal?(o2).should == false
+
+ # same objects, overriding == to return false
+ o3 = mock("object")
+ o3.stub!(:==).and_return(false)
+ o3.equal?(o3).should == true
+ end
+
+ it "is unaffected by overriding eql?" do
+ # different objects, overriding eql? to return true
+ o1 = mock("object")
+ o1.stub!(:eql?).and_return(true)
+ o2 = mock("object")
+ o1.equal?(o2).should == false
+
+ # same objects, overriding eql? to return false
+ o3 = mock("object")
+ o3.stub!(:eql?).and_return(false)
+ o3.equal?(o3).should == true
+ end
+end
diff --git a/spec/ruby/core/basicobject/equal_value_spec.rb b/spec/ruby/core/basicobject/equal_value_spec.rb
new file mode 100644
index 0000000000..eb951a8305
--- /dev/null
+++ b/spec/ruby/core/basicobject/equal_value_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/equal'
+
+describe "BasicObject#==" do
+ it "is a public instance method" do
+ BasicObject.public_instance_methods(false).should.include?(:==)
+ end
+
+ it_behaves_like :object_equal, :==
+end
diff --git a/spec/ruby/core/basicobject/fixtures/classes.rb b/spec/ruby/core/basicobject/fixtures/classes.rb
new file mode 100644
index 0000000000..ed5a2dda17
--- /dev/null
+++ b/spec/ruby/core/basicobject/fixtures/classes.rb
@@ -0,0 +1,255 @@
+module BasicObjectSpecs
+ class IVars
+ def initialize
+ @secret = 99
+ end
+ end
+
+ module InstExec
+ def self.included(base)
+ base.instance_exec { @@count = 2 }
+ end
+ end
+
+ module InstExecIncluded
+ include InstExec
+ end
+
+ module InstEval
+ module CVar
+ module Get
+ class ReceiverScope
+ @@cvar = :value_defined_in_receiver_scope
+ end
+
+ class BlockDefinitionScope
+ @@cvar = :value_defined_in_block_definition_scope
+
+ def block
+ -> * { @@cvar }
+ end
+ end
+
+ class CallerScope
+ @@cvar = :value_defined_in_caller_scope
+
+ def get_class_variable_with_string(obj)
+ obj.instance_eval("@@cvar")
+ end
+
+ def get_class_variable_with_block(obj, block)
+ obj.instance_eval(&block)
+ end
+ end
+
+ class CallerWithoutCVarScope
+ def get_class_variable_with_string(obj)
+ obj.instance_eval("@@cvar")
+ end
+ end
+
+ ReceiverWithCVarDefinedInSingletonClass = Class.new.new.tap do |obj|
+ obj.singleton_class.class_variable_set(:@@cvar, :value_defined_in_receiver_singleton_class)
+ end
+ end
+
+ module Set
+ class ReceiverScope
+ end
+
+ class BlockDefinitionScope
+ def self.get_class_variable
+ @@cvar
+ end
+
+ def block_to_assign(value)
+ -> * { @@cvar = value }
+ end
+ end
+
+ class CallerScope
+ def self.get_class_variable
+ @@cvar
+ end
+
+ def set_class_variable_with_string(obj, value)
+ obj.instance_eval("@@cvar=#{value.inspect}")
+ end
+
+ def set_class_variable_with_block(obj, block)
+ obj.instance_eval(&block)
+ end
+ end
+ end
+ end
+ end
+
+ module InstEval
+ module Constants
+ module ConstantInReceiverSingletonClass
+ module ReceiverScope
+ FOO = :ReceiverScope
+
+ class ReceiverParent
+ FOO = :ReceiverParent
+ end
+
+ class Receiver < ReceiverParent
+ FOO = :Receiver
+
+ def initialize
+ self.singleton_class.const_set(:FOO, :singleton_class)
+ end
+ end
+ end
+
+ module CallerScope
+ FOO = :CallerScope
+
+ class CallerParent
+ FOO = :CallerParent
+ end
+
+ class Caller < CallerParent
+ FOO = :Caller
+
+ def get_constant_with_string(receiver)
+ receiver.instance_eval("FOO")
+ end
+ end
+ end
+ end
+
+ module ConstantInReceiverClass
+ module ReceiverScope
+ FOO = :ReceiverScope
+
+ class ReceiverParent
+ FOO = :ReceiverParent
+ end
+
+ class Receiver < ReceiverParent
+ FOO = :Receiver
+ end
+ end
+
+ module CallerScope
+ FOO = :CallerScope
+
+ class CallerParent
+ FOO = :CallerParent
+ end
+
+ class Caller < CallerParent
+ FOO = :Caller
+
+ def get_constant_with_string(receiver)
+ receiver.instance_eval("FOO")
+ end
+ end
+ end
+ end
+
+ module ConstantInCallerClass
+ module ReceiverScope
+ FOO = :ReceiverScope
+
+ class ReceiverParent
+ FOO = :ReceiverParent
+ end
+
+ class Receiver < ReceiverParent
+ # FOO is not declared in a receiver class
+ end
+ end
+
+ module CallerScope
+ FOO = :CallerScope
+
+ class CallerParent
+ FOO = :CallerParent
+ end
+
+ class Caller < CallerParent
+ FOO = :Caller
+
+ def get_constant_with_string(receiver)
+ receiver.instance_eval("FOO")
+ end
+ end
+ end
+ end
+
+ module ConstantInCallerOuterScopes
+ module ReceiverScope
+ FOO = :ReceiverScope
+
+ class ReceiverParent
+ FOO = :ReceiverParent
+ end
+
+ class Receiver < ReceiverParent
+ # FOO is not declared in a receiver class
+ end
+ end
+
+ module CallerScope
+ FOO = :CallerScope
+
+ class CallerParent
+ FOO = :CallerParent
+ end
+
+ class Caller < CallerParent
+ # FOO is not declared in a caller class
+
+ def get_constant_with_string(receiver)
+ receiver.instance_eval("FOO")
+ end
+ end
+ end
+ end
+
+ module ConstantInReceiverParentClass
+ module ReceiverScope
+ FOO = :ReceiverScope
+
+ class ReceiverParent
+ FOO = :ReceiverParent
+ end
+
+ class Receiver < ReceiverParent
+ # FOO is not declared in a receiver class
+ end
+ end
+
+ module CallerScope
+ # FOO is not declared in a caller outer scopes
+
+ class CallerParent
+ FOO = :CallerParent
+ end
+
+ class Caller < CallerParent
+ # FOO is not declared in a caller class
+
+ def get_constant_with_string(receiver)
+ receiver.instance_eval("FOO")
+ end
+ end
+ end
+ end
+ end
+ end
+
+ class InstEvalConst
+ INST_EVAL_CONST_X = 2
+ end
+
+ module InstEvalOuter
+ module Inner
+ obj = InstEvalConst.new
+ X_BY_BLOCK = obj.instance_eval { INST_EVAL_CONST_X } rescue nil
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/fixtures/common.rb b/spec/ruby/core/basicobject/fixtures/common.rb
new file mode 100644
index 0000000000..3447a3a5e7
--- /dev/null
+++ b/spec/ruby/core/basicobject/fixtures/common.rb
@@ -0,0 +1,9 @@
+module BasicObjectSpecs
+ class BOSubclass < BasicObject
+ def self.kernel_defined?
+ defined?(Kernel)
+ end
+
+ include ::Kernel
+ end
+end
diff --git a/spec/ruby/core/basicobject/fixtures/remove_method_missing.rb b/spec/ruby/core/basicobject/fixtures/remove_method_missing.rb
new file mode 100644
index 0000000000..095b982d3a
--- /dev/null
+++ b/spec/ruby/core/basicobject/fixtures/remove_method_missing.rb
@@ -0,0 +1,9 @@
+class BasicObject
+ remove_method :method_missing
+end
+
+begin
+ Object.new.test_method
+rescue NoMethodError => e
+ puts e.class.name
+end
diff --git a/spec/ruby/core/basicobject/fixtures/singleton_method.rb b/spec/ruby/core/basicobject/fixtures/singleton_method.rb
new file mode 100644
index 0000000000..0e00e035fa
--- /dev/null
+++ b/spec/ruby/core/basicobject/fixtures/singleton_method.rb
@@ -0,0 +1,10 @@
+module BasicObjectSpecs
+ class SingletonMethod
+ def self.singleton_method_added name
+ ScratchPad.record [:singleton_method_added, name]
+ end
+
+ def self.singleton_method_to_alias
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/initialize_spec.rb b/spec/ruby/core/basicobject/initialize_spec.rb
new file mode 100644
index 0000000000..09e86676b6
--- /dev/null
+++ b/spec/ruby/core/basicobject/initialize_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#initialize" do
+ it "is a private instance method" do
+ BasicObject.private_instance_methods(false).should.include?(:initialize)
+ end
+
+ it "does not accept arguments" do
+ -> {
+ BasicObject.new("This", "makes it easier", "to call super", "from other constructors")
+ }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/basicobject/instance_eval_spec.rb b/spec/ruby/core/basicobject/instance_eval_spec.rb
new file mode 100644
index 0000000000..124d179b5a
--- /dev/null
+++ b/spec/ruby/core/basicobject/instance_eval_spec.rb
@@ -0,0 +1,327 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "BasicObject#instance_eval" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a public instance method" do
+ BasicObject.public_instance_methods(false).should.include?(:instance_eval)
+ end
+
+ it "sets self to the receiver in the context of the passed block" do
+ a = BasicObject.new
+ a.instance_eval { self }.equal?(a).should == true
+ end
+
+ it "evaluates strings" do
+ a = BasicObject.new
+ a.instance_eval('self').equal?(a).should == true
+ end
+
+ it "raises an ArgumentError when no arguments and no block are given" do
+ -> { "hola".instance_eval }.should.raise(ArgumentError, "wrong number of arguments (given 0, expected 1..3)")
+ end
+
+ it "raises an ArgumentError when a block and normal arguments are given" do
+ -> { "hola".instance_eval(4, 5) {|a,b| a + b } }.should.raise(ArgumentError, "wrong number of arguments (given 2, expected 0)")
+ end
+
+ it "raises an ArgumentError when more than 3 arguments are given" do
+ -> {
+ "hola".instance_eval("1 + 1", "some file", 0, "bogus")
+ }.should.raise(ArgumentError, "wrong number of arguments (given 4, expected 1..3)")
+ end
+
+ it "yields the object to the block" do
+ "hola".instance_eval {|o| ScratchPad.record o }
+ ScratchPad.recorded.should == "hola"
+ end
+
+ it "returns the result of the block" do
+ "hola".instance_eval { :result }.should == :result
+ end
+
+ it "only binds the eval to the receiver" do
+ f = Object.new
+ f.instance_eval do
+ def foo
+ 1
+ end
+ end
+ f.foo.should == 1
+ -> { Object.new.foo }.should.raise(NoMethodError)
+ end
+
+ it "preserves self in the original block when passed a block argument" do
+ prc = proc { self }
+
+ old_self = prc.call
+
+ new_self = Object.new
+ new_self.instance_eval(&prc).should == new_self
+
+ prc.call.should == old_self
+ end
+
+ # TODO: This should probably be replaced with a "should behave like" that uses
+ # the many scoping/binding specs from kernel/eval_spec, since most of those
+ # behaviors are the same for instance_eval. See also module_eval/class_eval.
+
+ it "binds self to the receiver" do
+ s = "hola"
+ (s == s.instance_eval { self }).should == true
+ o = mock('o')
+ (o == o.instance_eval("self")).should == true
+ end
+
+ it "executes in the context of the receiver" do
+ "Ruby-fu".instance_eval { size }.should == 7
+ "hola".instance_eval("size").should == 4
+ Object.class_eval { "hola".instance_eval("to_s") }.should == "hola"
+ Object.class_eval { "Ruby-fu".instance_eval{ to_s } }.should == "Ruby-fu"
+
+ end
+
+ it "uses the caller location as default location" do
+ f = Object.new
+ f.instance_eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1]
+ end
+
+ it "has access to receiver's instance variables" do
+ BasicObjectSpecs::IVars.new.instance_eval { @secret }.should == 99
+ BasicObjectSpecs::IVars.new.instance_eval("@secret").should == 99
+ end
+
+ it "raises TypeError for frozen objects when tries to set receiver's instance variables" do
+ -> { nil.instance_eval { @foo = 42 } }.should.raise(FrozenError, "can't modify frozen NilClass: nil")
+ -> { true.instance_eval { @foo = 42 } }.should.raise(FrozenError, "can't modify frozen TrueClass: true")
+ -> { false.instance_eval { @foo = 42 } }.should.raise(FrozenError, "can't modify frozen FalseClass: false")
+ -> { 1.instance_eval { @foo = 42 } }.should.raise(FrozenError, "can't modify frozen Integer: 1")
+ -> { :symbol.instance_eval { @foo = 42 } }.should.raise(FrozenError, "can't modify frozen Symbol: :symbol")
+
+ obj = Object.new
+ obj.freeze
+ -> { obj.instance_eval { @foo = 42 } }.should.raise(FrozenError)
+ end
+
+ it "treats block-local variables as local to the block" do
+ prc = instance_eval <<-CODE
+ proc do |x, prc|
+ if x
+ n = 2
+ else
+ n = 1
+ prc.call(true, prc)
+ n
+ end
+ end
+ CODE
+
+ prc.call(false, prc).should == 1
+ end
+
+ it "makes the receiver metaclass the scoped class when used with a string" do
+ obj = Object.new
+ obj.instance_eval %{
+ class B; end
+ B
+ }
+ obj.singleton_class.const_get(:B).should.instance_of?(Class)
+ end
+
+ describe "constants lookup when a String given" do
+ it "looks in the receiver singleton class first" do
+ receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverSingletonClass::ReceiverScope::Receiver.new
+ caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverSingletonClass::CallerScope::Caller.new
+
+ caller.get_constant_with_string(receiver).should == :singleton_class
+ end
+
+ 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
+
+ it "looks in the caller class next" do
+ receiver = BasicObjectSpecs::InstEval::Constants::ConstantInCallerClass::ReceiverScope::Receiver.new
+ caller = BasicObjectSpecs::InstEval::Constants::ConstantInCallerClass::CallerScope::Caller.new
+
+ caller.get_constant_with_string(receiver).should == :Caller
+ end
+
+ it "looks in the caller outer scopes next" do
+ receiver = BasicObjectSpecs::InstEval::Constants::ConstantInCallerOuterScopes::ReceiverScope::Receiver.new
+ caller = BasicObjectSpecs::InstEval::Constants::ConstantInCallerOuterScopes::CallerScope::Caller.new
+
+ caller.get_constant_with_string(receiver).should == :CallerScope
+ end
+
+ it "looks in the receiver class hierarchy next" do
+ receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverParentClass::ReceiverScope::Receiver.new
+ caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverParentClass::CallerScope::Caller.new
+
+ caller.get_constant_with_string(receiver).should == :ReceiverParent
+ end
+ end
+
+ it "doesn't get constants in the receiver if a block given" do
+ BasicObjectSpecs::InstEvalOuter::Inner::X_BY_BLOCK.should == nil
+ end
+
+ it "raises a TypeError when defining methods on an immediate" do
+ -> do
+ 1.instance_eval { def foo; end }
+ end.should.raise(TypeError)
+ -> do
+ :foo.instance_eval { def foo; end }
+ end.should.raise(TypeError)
+ end
+
+ describe "class variables lookup" do
+ it "gets class variables in the caller class when called with a String" do
+ receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverScope.new
+ caller = BasicObjectSpecs::InstEval::CVar::Get::CallerScope.new
+
+ caller.get_class_variable_with_string(receiver).should == :value_defined_in_caller_scope
+ end
+
+ it "gets class variables in the block definition scope when called with a block" do
+ receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverScope.new
+ caller = BasicObjectSpecs::InstEval::CVar::Get::CallerScope.new
+ block = BasicObjectSpecs::InstEval::CVar::Get::BlockDefinitionScope.new.block
+
+ caller.get_class_variable_with_block(receiver, block).should == :value_defined_in_block_definition_scope
+ end
+
+ it "sets class variables in the caller class when called with a String" do
+ receiver = BasicObjectSpecs::InstEval::CVar::Set::ReceiverScope.new
+ caller = BasicObjectSpecs::InstEval::CVar::Set::CallerScope.new
+
+ caller.set_class_variable_with_string(receiver, 1)
+ BasicObjectSpecs::InstEval::CVar::Set::CallerScope.get_class_variable.should == 1
+ end
+
+ it "sets class variables in the block definition scope when called with a block" do
+ receiver = BasicObjectSpecs::InstEval::CVar::Set::ReceiverScope.new
+ caller = BasicObjectSpecs::InstEval::CVar::Set::CallerScope.new
+ block = BasicObjectSpecs::InstEval::CVar::Set::BlockDefinitionScope.new.block_to_assign(1)
+
+ caller.set_class_variable_with_block(receiver, block)
+ BasicObjectSpecs::InstEval::CVar::Set::BlockDefinitionScope.get_class_variable.should == 1
+ end
+
+ it "does not have access to class variables in the receiver class when called with a String" do
+ receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverScope.new
+ caller = BasicObjectSpecs::InstEval::CVar::Get::CallerWithoutCVarScope.new
+ -> { caller.get_class_variable_with_string(receiver) }.should.raise(NameError, /uninitialized class variable @@cvar/)
+ end
+
+ it "does not have access to class variables in the receiver's singleton class when called with a String" do
+ receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverWithCVarDefinedInSingletonClass
+ caller = BasicObjectSpecs::InstEval::CVar::Get::CallerWithoutCVarScope.new
+ -> { caller.get_class_variable_with_string(receiver) }.should.raise(NameError, /uninitialized class variable @@cvar/)
+ end
+ end
+
+ it "raises a TypeError when defining methods on numerics" do
+ -> do
+ (1.0).instance_eval { def foo; end }
+ end.should.raise(TypeError)
+ -> do
+ (1 << 64).instance_eval { def foo; end }
+ end.should.raise(TypeError)
+ end
+
+ it "evaluates procs originating from methods" do
+ def meth(arg); arg; end
+
+ m = method(:meth)
+ obj = Object.new
+
+ obj.instance_eval(&m).should == obj
+ end
+
+ it "evaluates string with given filename and linenumber" do
+ err = begin
+ Object.new.instance_eval("raise", "a_file", 10)
+ rescue => e
+ e
+ end
+ err.backtrace.first.split(":")[0..1].should == ["a_file", "10"]
+ end
+
+ it "evaluates string with given filename and negative linenumber" do
+ err = begin
+ Object.new.instance_eval("\n\nraise\n", "b_file", -100)
+ rescue => e
+ e
+ end
+ err.backtrace.first.split(":")[0..1].should == ["b_file", "-98"]
+ end
+
+ it "has access to the caller's local variables" do
+ x = nil
+
+ instance_eval "x = :value"
+
+ x.should == :value
+ end
+
+ it "converts string argument with #to_str method" do
+ source_code = Object.new
+ def source_code.to_str() "1" end
+
+ a = BasicObject.new
+ a.instance_eval(source_code).should == 1
+ end
+
+ it "raises ArgumentError if returned value is not String" do
+ source_code = Object.new
+ def source_code.to_str() :symbol end
+
+ a = BasicObject.new
+ -> { a.instance_eval(source_code) }.should raise_consistent_error(TypeError, /can't convert Object into String/)
+ end
+
+ it "converts filename argument with #to_str method" do
+ filename = Object.new
+ def filename.to_str() "file.rb" end
+
+ err = begin
+ Object.new.instance_eval("raise", filename)
+ rescue => e
+ e
+ end
+ err.backtrace.first.split(":")[0].should == "file.rb"
+ end
+
+ it "raises ArgumentError if returned value is not String" do
+ filename = Object.new
+ def filename.to_str() :symbol end
+
+ -> { Object.new.instance_eval("raise", filename) }.should raise_consistent_error(TypeError, /can't convert Object into String/)
+ end
+
+ it "converts lineno argument with #to_int method" do
+ lineno = Object.new
+ def lineno.to_int() 15 end
+
+ err = begin
+ Object.new.instance_eval("raise", "file.rb", lineno)
+ rescue => e
+ e
+ end
+ err.backtrace.first.split(":")[1].should == "15"
+ end
+
+ it "raises ArgumentError if returned value is not Integer" do
+ lineno = Object.new
+ def lineno.to_int() :symbol end
+
+ -> { Object.new.instance_eval("raise", "file.rb", lineno) }.should raise_consistent_error(TypeError, /can't convert Object into Integer/)
+ end
+end
diff --git a/spec/ruby/core/basicobject/instance_exec_spec.rb b/spec/ruby/core/basicobject/instance_exec_spec.rb
new file mode 100644
index 0000000000..cfce9a65ad
--- /dev/null
+++ b/spec/ruby/core/basicobject/instance_exec_spec.rb
@@ -0,0 +1,113 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "BasicObject#instance_exec" do
+ it "is a public instance method" do
+ BasicObject.public_instance_methods(false).should.include?(:instance_exec)
+ end
+
+ it "sets self to the receiver in the context of the passed block" do
+ a = BasicObject.new
+ a.instance_exec { self }.equal?(a).should == true
+ end
+
+ it "passes arguments to the block" do
+ a = BasicObject.new
+ a.instance_exec(1) { |b| b }.should.equal?(1)
+ end
+
+ it "raises a LocalJumpError unless given a block" do
+ -> { "hola".instance_exec }.should.raise(LocalJumpError)
+ end
+
+ it "has an arity of -1" do
+ Object.new.method(:instance_exec).arity.should == -1
+ end
+
+ it "accepts arguments with a block" do
+ -> { "hola".instance_exec(4, 5) { |a,b| a + b } }.should_not.raise
+ end
+
+ it "doesn't pass self to the block as an argument" do
+ "hola".instance_exec { |o| o }.should == nil
+ end
+
+ it "passes any arguments to the block" do
+ Object.new.instance_exec(1,2) {|one, two| one + two}.should == 3
+ end
+
+ describe "with optional argument" do
+ it "does not destructure a single array argument" do
+ Object.new.instance_exec([1, 2, 3]) { |a = 99| a }.should == [1, 2, 3]
+ end
+ end
+
+ it "only binds the exec to the receiver" do
+ f = Object.new
+ f.instance_exec do
+ def foo
+ 1
+ end
+ end
+ f.foo.should == 1
+ -> { Object.new.foo }.should.raise(NoMethodError)
+ end
+
+ # TODO: This should probably be replaced with a "should behave like" that uses
+ # the many scoping/binding specs from kernel/eval_spec, since most of those
+ # behaviors are the same for instance_exec. See also module_eval/class_eval.
+
+ it "binds self to the receiver" do
+ s = "hola"
+ (s == s.instance_exec { self }).should == true
+ end
+
+ it "binds the block's binding self to the receiver" do
+ s = "hola"
+ (s == s.instance_exec { eval "self", binding }).should == true
+ end
+
+ it "executes in the context of the receiver" do
+ "Ruby-fu".instance_exec { size }.should == 7
+ Object.class_eval { "Ruby-fu".instance_exec{ to_s } }.should == "Ruby-fu"
+ end
+
+ it "has access to receiver's instance variables" do
+ BasicObjectSpecs::IVars.new.instance_exec { @secret }.should == 99
+ end
+
+ it "sets class variables in the receiver" do
+ BasicObjectSpecs::InstExec.class_variables.should.include?(:@@count)
+ BasicObjectSpecs::InstExec.send(:class_variable_get, :@@count).should == 2
+ end
+
+ it "raises a TypeError when defining methods on an immediate" do
+ -> do
+ 1.instance_exec { def foo; end }
+ end.should.raise(TypeError)
+ -> do
+ :foo.instance_exec { def foo; end }
+ end.should.raise(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)
+ end
+ end
+
+ it "raises a TypeError when defining methods on numerics" do
+ -> do
+ (1.0).instance_exec { def foo; end }
+ end.should.raise(TypeError)
+ -> do
+ (1 << 64).instance_exec { def foo; end }
+ end.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/basicobject/method_missing_spec.rb b/spec/ruby/core/basicobject/method_missing_spec.rb
new file mode 100644
index 0000000000..8785b41b21
--- /dev/null
+++ b/spec/ruby/core/basicobject/method_missing_spec.rb
@@ -0,0 +1,40 @@
+require_relative "../../spec_helper"
+require_relative '../../shared/basicobject/method_missing'
+
+describe "BasicObject#method_missing" do
+ it "is a private method" do
+ BasicObject.private_instance_methods(false).should.include?(:method_missing)
+ end
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_class, nil, BasicObject
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_instance, nil, BasicObject
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_defined_module, nil, KernelSpecs::ModuleMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_module, nil, KernelSpecs::ModuleNoMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_defined_class, nil, KernelSpecs::ClassMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_class, nil, KernelSpecs::ClassNoMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_defined_instance, nil, KernelSpecs::ClassMM
+end
+
+describe "BasicObject#method_missing" do
+ it_behaves_like :method_missing_instance, nil, KernelSpecs::ClassNoMM
+end
diff --git a/spec/ruby/core/basicobject/not_equal_spec.rb b/spec/ruby/core/basicobject/not_equal_spec.rb
new file mode 100644
index 0000000000..29b14b0fff
--- /dev/null
+++ b/spec/ruby/core/basicobject/not_equal_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#!=" do
+ it "is a public instance method" do
+ BasicObject.public_instance_methods(false).should.include?(:'!=')
+ end
+
+ it "returns true if other is not identical to self" do
+ a = BasicObject.new
+ b = BasicObject.new
+ (a != b).should == true
+ end
+
+ it "returns true if other is an Object" do
+ a = BasicObject.new
+ b = Object.new
+ (a != b).should == true
+ end
+
+ it "returns false if other is identical to self" do
+ a = BasicObject.new
+ (a != a).should == false
+ end
+
+ it "dispatches to #==" do
+ a = mock("not_equal")
+ b = BasicObject.new
+ a.should_receive(:==).and_return(true)
+
+ (a != b).should == false
+ end
+
+ describe "when invoked using Kernel#send" do
+ it "returns true if other is not identical to self" do
+ a = Object.new
+ b = Object.new
+ a.send(:!=, b).should == true
+ end
+
+ it "returns false if other is identical to self" do
+ a = Object.new
+ a.send(:!=, a).should == false
+ end
+
+ it "dispatches to #==" do
+ a = mock("not_equal")
+ b = Object.new
+ a.should_receive(:==).and_return(true)
+
+ a.send(:!=, b).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/not_spec.rb b/spec/ruby/core/basicobject/not_spec.rb
new file mode 100644
index 0000000000..a6f58ae6f5
--- /dev/null
+++ b/spec/ruby/core/basicobject/not_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#!" do
+ it "is a public instance method" do
+ BasicObject.public_instance_methods(false).should.include?(:'!')
+ end
+
+ it "returns false" do
+ (!BasicObject.new).should == false
+ end
+end
diff --git a/spec/ruby/core/basicobject/singleton_method_added_spec.rb b/spec/ruby/core/basicobject/singleton_method_added_spec.rb
new file mode 100644
index 0000000000..f39b91768c
--- /dev/null
+++ b/spec/ruby/core/basicobject/singleton_method_added_spec.rb
@@ -0,0 +1,147 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/singleton_method'
+
+describe "BasicObject#singleton_method_added" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ BasicObject.private_instance_methods(false).should.include?(:singleton_method_added)
+ end
+
+ it "is called when a singleton method is defined on an object" do
+ obj = BasicObject.new
+
+ def obj.singleton_method_added(name)
+ ScratchPad.record [:singleton_method_added, name]
+ end
+
+ def obj.new_singleton_method
+ end
+
+ ScratchPad.recorded.should == [:singleton_method_added, :new_singleton_method]
+ end
+
+ it "is not called for instance methods" do
+ ScratchPad.record []
+
+ Module.new do
+ def self.singleton_method_added(name)
+ ScratchPad << name
+ end
+
+ def new_instance_method
+ end
+ end
+
+ ScratchPad.recorded.should_not.include?(:new_instance_method)
+ end
+
+ it "is called when a singleton method is defined on a module" do
+ class BasicObjectSpecs::SingletonMethod
+ def self.new_method_on_self
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_on_self]
+ end
+
+ it "is called when a method is defined in the singleton class" do
+ class BasicObjectSpecs::SingletonMethod
+ class << self
+ def new_method_on_singleton
+ end
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_on_singleton]
+ end
+
+ it "is called when a method is defined with alias_method in the singleton class" do
+ class BasicObjectSpecs::SingletonMethod
+ class << self
+ alias_method :new_method_on_singleton_with_alias_method, :singleton_method_to_alias
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_on_singleton_with_alias_method]
+ end
+
+ it "is called when a method is defined with syntax alias in the singleton class" do
+ class BasicObjectSpecs::SingletonMethod
+ class << self
+ alias new_method_on_singleton_with_syntax_alias singleton_method_to_alias
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_on_singleton_with_syntax_alias]
+ end
+
+ it "is called when define_method is used in the singleton class" do
+ class BasicObjectSpecs::SingletonMethod
+ class << self
+ define_method :new_method_with_define_method do
+ end
+ end
+ end
+ ScratchPad.recorded.should == [:singleton_method_added, :new_method_with_define_method]
+ end
+
+ describe "when singleton_method_added is undefined" do
+ it "raises NoMethodError for a metaclass" do
+ class BasicObjectSpecs::NoSingletonMethodAdded
+ class << self
+ undef_method :singleton_method_added
+ end
+
+ -> {
+ def self.foo
+ end
+ }.should.raise(NoMethodError, /undefined method [`']singleton_method_added' for/)
+ end
+ ensure
+ BasicObjectSpecs.send(:remove_const, :NoSingletonMethodAdded)
+ end
+
+ it "raises NoMethodError for a singleton instance" do
+ object = Object.new
+ class << object
+ undef_method :singleton_method_added
+
+ -> {
+ def foo
+ end
+ }.should.raise(NoMethodError, /undefined method [`']singleton_method_added' for #<Object:/)
+
+ -> {
+ define_method(:bar) {}
+ }.should.raise(NoMethodError, /undefined method [`']singleton_method_added' for #<Object:/)
+ end
+
+ -> {
+ object.define_singleton_method(:baz) {}
+ }.should.raise(NoMethodError, /undefined method [`']singleton_method_added' for #<Object:/)
+ end
+
+ it "calls #method_missing" do
+ ScratchPad.record []
+ object = Object.new
+ class << object
+ def method_missing(*args)
+ ScratchPad << args
+ end
+
+ undef_method :singleton_method_added
+
+ def foo
+ end
+
+ define_method(:bar) {}
+ end
+ object.define_singleton_method(:baz) {}
+
+ ScratchPad.recorded.should == [
+ [:singleton_method_added, :foo],
+ [:singleton_method_added, :bar],
+ [:singleton_method_added, :baz],
+ ]
+ end
+ end
+end
diff --git a/spec/ruby/core/basicobject/singleton_method_removed_spec.rb b/spec/ruby/core/basicobject/singleton_method_removed_spec.rb
new file mode 100644
index 0000000000..1831e3c070
--- /dev/null
+++ b/spec/ruby/core/basicobject/singleton_method_removed_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#singleton_method_removed" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ BasicObject.private_instance_methods(false).should.include?(:singleton_method_removed)
+ end
+
+ it "is called when a method is removed on self" do
+ klass = Class.new
+ def klass.singleton_method_removed(name)
+ ScratchPad.record [:singleton_method_removed, name]
+ end
+ def klass.singleton_method_to_remove
+ end
+ class << klass
+ remove_method :singleton_method_to_remove
+ end
+ ScratchPad.recorded.should == [:singleton_method_removed, :singleton_method_to_remove]
+ end
+end
diff --git a/spec/ruby/core/basicobject/singleton_method_undefined_spec.rb b/spec/ruby/core/basicobject/singleton_method_undefined_spec.rb
new file mode 100644
index 0000000000..cc47341878
--- /dev/null
+++ b/spec/ruby/core/basicobject/singleton_method_undefined_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "BasicObject#singleton_method_undefined" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "is a private method" do
+ BasicObject.private_instance_methods(false).should.include?(:singleton_method_undefined)
+ end
+
+ it "is called when a method is removed on self" do
+ klass = Class.new
+ def klass.singleton_method_undefined(name)
+ ScratchPad.record [:singleton_method_undefined, name]
+ end
+ def klass.singleton_method_to_undefine
+ end
+ class << klass
+ undef_method :singleton_method_to_undefine
+ end
+ ScratchPad.recorded.should == [:singleton_method_undefined, :singleton_method_to_undefine]
+ end
+end
diff --git a/spec/ruby/core/binding/clone_spec.rb b/spec/ruby/core/binding/clone_spec.rb
new file mode 100644
index 0000000000..f1769ac6de
--- /dev/null
+++ b/spec/ruby/core/binding/clone_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/clone'
+
+describe "Binding#clone" do
+ it_behaves_like :binding_clone, :clone
+
+ it "preserves frozen status" do
+ bind = binding.freeze
+ bind.frozen?.should == true
+ bind.clone.frozen?.should == true
+ end
+end
diff --git a/spec/ruby/core/binding/dup_spec.rb b/spec/ruby/core/binding/dup_spec.rb
new file mode 100644
index 0000000000..f5f0c72d5d
--- /dev/null
+++ b/spec/ruby/core/binding/dup_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/clone'
+
+describe "Binding#dup" do
+ it_behaves_like :binding_clone, :dup
+
+ it "resets frozen status" do
+ bind = binding.freeze
+ bind.frozen?.should == true
+ bind.dup.frozen?.should == false
+ end
+
+ it "retains original binding variables but the list is distinct" do
+ bind1 = binding
+ eval "a = 1", bind1
+
+ bind2 = bind1.dup
+ eval("a = 2", bind2)
+ eval("a", bind1).should == 2
+ eval("a", bind2).should == 2
+
+ eval("b = 2", bind2)
+ -> { eval("b", bind1) }.should.raise(NameError)
+ eval("b", bind2).should == 2
+
+ bind1.local_variables.sort.should == [:a, :bind1, :bind2]
+ bind2.local_variables.sort.should == [:a, :b, :bind1, :bind2]
+ end
+end
diff --git a/spec/ruby/core/binding/eval_spec.rb b/spec/ruby/core/binding/eval_spec.rb
new file mode 100644
index 0000000000..f1d8591320
--- /dev/null
+++ b/spec/ruby/core/binding/eval_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Binding#eval" do
+ it "behaves like Kernel.eval(..., self)" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+
+ bind.eval("@secret += square(3)").should == 10
+ bind.eval("a").should == true
+
+ bind.eval("class Inside; end")
+ bind.eval("Inside.name").should == "BindingSpecs::Demo::Inside"
+ end
+
+ it "does not leak variables to cloned bindings" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_empty_binding
+ bind2 = bind.dup
+
+ bind.eval("x = 72")
+ bind.local_variables.should == [:x]
+ bind2.local_variables.should == []
+ end
+
+ it "starts with line 1 if single argument is given" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ bind.eval("__LINE__").should == 1
+ end
+
+ it "preserves __LINE__ across multiple calls to eval" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ bind.eval("__LINE__").should == 1
+ bind.eval("__LINE__").should == 1
+ end
+
+ it "increments __LINE__ on each line of a multiline eval" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ bind.eval("#foo\n__LINE__").should == 2
+ end
+
+ it "starts with line 1 if the Binding is created with #send" do
+ obj = BindingSpecs::Demo.new(1)
+ bind, line = obj.get_binding_with_send_and_line
+ bind.eval("__LINE__").should == 1
+ end
+
+ it "starts with a __LINE__ of 1 if a filename is passed" do
+ bind = BindingSpecs::Demo.new(1).get_binding
+ bind.eval("__LINE__", "(test)").should == 1
+ bind.eval("#foo\n__LINE__", "(test)").should == 2
+ end
+
+ it "starts with a __LINE__ from the third argument if passed" do
+ bind = BindingSpecs::Demo.new(1).get_binding
+ bind.eval("__LINE__", "(test)", 88).should == 88
+ bind.eval("#foo\n__LINE__", "(test)", 88).should == 89
+ end
+
+ it "uses 1 as __LINE__" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+ suppress_warning { bind.eval("__LINE__") }.should == 1
+ end
+
+ it "uses the __FILE__ that is passed in" do
+ bind = BindingSpecs::Demo.new(1).get_binding
+ bind.eval("__FILE__", "(test)").should == "(test)"
+ end
+
+ describe "with a file given" do
+ it "does not store the filename permanently" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_binding
+
+ bind.eval("__FILE__", "test.rb").should == "test.rb"
+ suppress_warning {bind.eval("__FILE__")}.should_not == "test.rb"
+ end
+ end
+
+ it "with __method__ returns the method where the Binding was created" do
+ obj = BindingSpecs::Demo.new(1)
+ bind, meth = obj.get_binding_and_method
+ bind.eval("__method__").should == meth
+ end
+
+ it "with __method__ returns the method where the Binding was created, ignoring #send" do
+ obj = BindingSpecs::Demo.new(1)
+ bind, meth = obj.get_binding_with_send_and_method
+ bind.eval("__method__").should == meth
+ end
+
+ it "reflects refinements activated in the binding scope" do
+ bind = BindingSpecs::Refined.refined_binding
+
+ bind.eval("'bar'.foo").should == "foo"
+ end
+
+ it "uses the caller location as default filename" do
+ binding.eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1]
+ end
+end
diff --git a/spec/ruby/core/binding/fixtures/classes.rb b/spec/ruby/core/binding/fixtures/classes.rb
new file mode 100644
index 0000000000..b5f3ce9008
--- /dev/null
+++ b/spec/ruby/core/binding/fixtures/classes.rb
@@ -0,0 +1,66 @@
+module BindingSpecs
+ class Demo
+ def initialize(n)
+ @secret = n
+ end
+
+ def square(n)
+ n * n
+ end
+
+ def get_binding_and_line
+ a = true
+ [binding, __LINE__]
+ end
+
+ def get_binding
+ get_binding_and_line[0]
+ end
+
+ def get_line_of_binding
+ get_binding_and_line[1]
+ end
+
+ def get_file_of_binding
+ __FILE__
+ end
+
+ def get_binding_with_send_and_line
+ [send(:binding), __LINE__]
+ end
+
+ def get_binding_and_method
+ [binding, :get_binding_and_method]
+ end
+
+ def get_binding_with_send_and_method
+ [send(:binding), :get_binding_with_send_and_method]
+ end
+
+ def get_empty_binding
+ binding
+ end
+
+ def get_binding_in_block
+ a = true
+ 1.times do
+ b = false
+ return binding
+ end
+ end
+ end
+
+ module AddFooToString
+ refine(String) do
+ def foo
+ "foo"
+ end
+ end
+ end
+ class Refined
+ using AddFooToString
+ def self.refined_binding
+ binding
+ end
+ end
+end
diff --git a/spec/ruby/core/binding/fixtures/location.rb b/spec/ruby/core/binding/fixtures/location.rb
new file mode 100644
index 0000000000..a78ae75731
--- /dev/null
+++ b/spec/ruby/core/binding/fixtures/location.rb
@@ -0,0 +1,6 @@
+module BindingSpecs
+ module LocationMethod
+ FILE_PATH = __FILE__
+ TEST_BINDING = binding
+ end
+end
diff --git a/spec/ruby/core/binding/local_variable_defined_spec.rb b/spec/ruby/core/binding/local_variable_defined_spec.rb
new file mode 100644
index 0000000000..2fc6504ee5
--- /dev/null
+++ b/spec/ruby/core/binding/local_variable_defined_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+
+describe 'Binding#local_variable_defined?' do
+ it 'returns false when a variable is not defined' do
+ binding.local_variable_defined?(:foo).should == false
+ end
+
+ it 'returns true when a regular local variable is defined' do
+ foo = 10
+ binding.local_variable_defined?(:foo).should == true
+ end
+
+ it 'returns true when a local variable is defined using eval()' do
+ bind = binding
+ bind.eval('foo = 10')
+
+ bind.local_variable_defined?(:foo).should == true
+ end
+
+ it 'returns true when a local variable is defined using Binding#local_variable_set' do
+ bind = binding
+ bind.local_variable_set(:foo, 10)
+
+ bind.local_variable_defined?(:foo).should == true
+ end
+
+ it 'returns true when a local variable is defined in a parent scope' do
+ foo = 10
+ -> {
+ binding.local_variable_defined?(:foo)
+ }.call.should == true
+ end
+
+ it 'allows usage of a String as the variable name' do
+ foo = 10
+ binding.local_variable_defined?('foo').should == true
+ end
+
+ it 'allows usage of an object responding to #to_str as the variable name' do
+ foo = 10
+ name = mock(:obj)
+ name.stub!(:to_str).and_return('foo')
+
+ binding.local_variable_defined?(name).should == true
+ end
+end
diff --git a/spec/ruby/core/binding/local_variable_get_spec.rb b/spec/ruby/core/binding/local_variable_get_spec.rb
new file mode 100644
index 0000000000..d97100deda
--- /dev/null
+++ b/spec/ruby/core/binding/local_variable_get_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Binding#local_variable_get" do
+ it "reads local variables captured in the binding" do
+ a = 42
+ bind = binding
+ bind.local_variable_get(:a).should == 42
+ end
+
+ it "raises a NameError for missing variables" do
+ bind = BindingSpecs::Demo.new(1).get_empty_binding
+
+ -> {
+ bind.local_variable_get(:no_such_variable)
+ }.should.raise(NameError)
+ end
+
+ it "reads variables added later to the binding" do
+ bind = BindingSpecs::Demo.new(1).get_empty_binding
+
+ -> {
+ bind.local_variable_get(:a)
+ }.should.raise(NameError)
+
+ bind.local_variable_set(:a, 42)
+
+ bind.local_variable_get(:a).should == 42
+ end
+
+ it 'gets a local variable defined in a parent scope' do
+ number = 10
+
+ -> {
+ binding.local_variable_get(:number)
+ }.call.should == 10
+ end
+
+ it 'gets a local variable defined using eval()' do
+ bind = binding
+ bind.eval('number = 10')
+
+ bind.local_variable_get(:number).should == 10
+ end
+
+ it "raises a NameError on global access" do
+ bind = binding
+ -> { bind.local_variable_get(:$0) }.should.raise(NameError)
+ end
+
+ it "raises a NameError on special variable access" do
+ bind = binding
+ -> { bind.local_variable_get(:$~) }.should.raise(NameError)
+ -> { bind.local_variable_get(:$_) }.should.raise(NameError)
+ end
+end
diff --git a/spec/ruby/core/binding/local_variable_set_spec.rb b/spec/ruby/core/binding/local_variable_set_spec.rb
new file mode 100644
index 0000000000..3e4f407fc3
--- /dev/null
+++ b/spec/ruby/core/binding/local_variable_set_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Binding#local_variable_set" do
+ it "adds nonexistent variables to the binding's eval scope" do
+ obj = BindingSpecs::Demo.new(1)
+ bind = obj.get_empty_binding
+ bind.eval('local_variables').should == []
+ bind.local_variable_set :foo, 1
+ bind.eval('local_variables').should == [:foo]
+ bind.eval('foo').should == 1
+ end
+
+ it 'sets a new local variable' do
+ bind = binding
+
+ bind.local_variable_set(:number, 10)
+ bind.local_variable_get(:number).should == 10
+ end
+
+ it 'sets a local variable using a String as the variable name' do
+ bind = binding
+
+ bind.local_variable_set('number', 10)
+ bind.local_variable_get('number').should == 10
+ end
+
+ it 'sets a local variable using an object responding to #to_str as the variable name' do
+ bind = binding
+ name = mock(:obj)
+ name.stub!(:to_str).and_return('number')
+
+ bind.local_variable_set(name, 10)
+ bind.local_variable_get(name).should == 10
+ end
+
+ it 'scopes new local variables to the receiving Binding' do
+ bind = binding
+ bind.local_variable_set(:number, 10)
+
+ -> { number }.should.raise(NameError)
+ end
+
+ it 'overwrites an existing local variable defined before a Binding' do
+ number = 10
+ bind = binding
+
+ bind.local_variable_set(:number, 20)
+ number.should == 20
+ end
+
+ it 'overwrites a local variable defined using eval()' do
+ bind = binding
+ bind.eval('number = 10')
+
+ bind.local_variable_set(:number, 20)
+ bind.local_variable_get(:number).should == 20
+ end
+
+ it "raises a NameError on global access" do
+ bind = binding
+ -> { bind.local_variable_set(:$0, "") }.should.raise(NameError)
+ end
+
+ it "raises a NameError on special variable access" do
+ bind = binding
+ -> { bind.local_variable_set(:$~, "") }.should.raise(NameError)
+ -> { bind.local_variable_set(:$_, "") }.should.raise(NameError)
+ end
+
+end
diff --git a/spec/ruby/core/binding/local_variables_spec.rb b/spec/ruby/core/binding/local_variables_spec.rb
new file mode 100644
index 0000000000..0f59681342
--- /dev/null
+++ b/spec/ruby/core/binding/local_variables_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Binding#local_variables" do
+ it "returns an Array" do
+ binding.local_variables.should.is_a?(Array)
+ end
+
+ it "includes local variables in the current scope" do
+ a = 1
+ b = nil
+ binding.local_variables.should == [:a, :b]
+ end
+
+ it "includes local variables defined after calling binding.local_variables" do
+ binding.local_variables.should == [:a, :b]
+ a = 1
+ b = 2
+ end
+
+ it "includes local variables of inherited scopes and eval'ed context" do
+ p = proc { |a| b = 1; eval("c = 2; binding.local_variables") }
+ p.call.should == [:c, :a, :b, :p]
+ end
+
+ it "includes shadowed local variables only once" do
+ a = 1
+ proc { |a| binding.local_variables }.call(2).should == [:a]
+ end
+
+ it "includes new variables defined in the binding" do
+ b = binding
+ b.local_variable_set :a, 42
+ b.local_variables.should == [:a, :b]
+ end
+end
diff --git a/spec/ruby/core/binding/receiver_spec.rb b/spec/ruby/core/binding/receiver_spec.rb
new file mode 100644
index 0000000000..4bf5e7a7bd
--- /dev/null
+++ b/spec/ruby/core/binding/receiver_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Binding#receiver" do
+ it "returns the object to which binding is bound" do
+ obj = BindingSpecs::Demo.new(1)
+ obj.get_binding.receiver.should == obj
+
+ binding.receiver.should == self
+ end
+end
diff --git a/spec/ruby/core/binding/shared/clone.rb b/spec/ruby/core/binding/shared/clone.rb
new file mode 100644
index 0000000000..2d854fce96
--- /dev/null
+++ b/spec/ruby/core/binding/shared/clone.rb
@@ -0,0 +1,56 @@
+describe :binding_clone, shared: true do
+ before :each do
+ @b1 = BindingSpecs::Demo.new(99).get_binding
+ @b2 = @b1.send(@method)
+ @b3 = BindingSpecs::Demo.new(99).get_binding_in_block
+ @b4 = @b3.send(@method)
+ end
+
+ it "returns a copy of the Binding object" do
+ [[@b1, @b2, "a"],
+ [@b3, @b4, "a", "b"]].each do |b1, b2, *vars|
+ b1.should_not == b2
+
+ eval("@secret", b1).should == eval("@secret", b2)
+ eval("square(2)", b1).should == eval("square(2)", b2)
+ eval("self.square(2)", b1).should == eval("self.square(2)", b2)
+ vars.each do |v|
+ eval("#{v}", b1).should == eval("#{v}", b2)
+ end
+ end
+ end
+
+ it "is a shallow copy of the Binding object" do
+ [[@b1, @b2, "a"],
+ [@b3, @b4, "a", "b"]].each do |b1, b2, *vars|
+ vars.each do |v|
+ eval("#{v} = false", b1)
+ eval("#{v}", b2).should == false
+ end
+ b1.local_variable_set(:x, 37)
+ b2.local_variable_defined?(:x).should == false
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "copies instance variables" do
+ @b1.instance_variable_set(:@ivar, 1)
+ cl = @b1.send(@method)
+ cl.instance_variables.should == [:@ivar]
+ end
+
+ it "copies the finalizer" do
+ code = <<-'RUBY'
+ obj = binding
+
+ ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" })
+
+ obj.clone
+
+ exit 0
+ RUBY
+
+ ruby_exe(code).lines.sort.should == ["finalized\n", "finalized\n"]
+ end
+ end
+end
diff --git a/spec/ruby/core/binding/source_location_spec.rb b/spec/ruby/core/binding/source_location_spec.rb
new file mode 100644
index 0000000000..d1c8191ea8
--- /dev/null
+++ b/spec/ruby/core/binding/source_location_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/location'
+
+describe "Binding#source_location" do
+ it "returns an [file, line] pair" do
+ b = BindingSpecs::LocationMethod::TEST_BINDING
+ b.source_location.should == [BindingSpecs::LocationMethod::FILE_PATH, 4]
+ end
+
+ it "works for eval with a given line" do
+ b = eval('binding', nil, "foo", 100)
+ b.source_location.should == ["foo", 100]
+ end
+end
diff --git a/spec/ruby/core/builtin_constants/builtin_constants_spec.rb b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb
new file mode 100644
index 0000000000..67b3339aa6
--- /dev/null
+++ b/spec/ruby/core/builtin_constants/builtin_constants_spec.rb
@@ -0,0 +1,149 @@
+require_relative '../../spec_helper'
+
+describe "RUBY_VERSION" do
+ it "is a String" do
+ RUBY_VERSION.should.is_a?(String)
+ end
+
+ it "is frozen" do
+ RUBY_VERSION.should.frozen?
+ end
+end
+
+describe "RUBY_PATCHLEVEL" do
+ it "is an Integer" do
+ RUBY_PATCHLEVEL.should.is_a?(Integer)
+ end
+end
+
+describe "RUBY_COPYRIGHT" do
+ it "is a String" do
+ RUBY_COPYRIGHT.should.is_a?(String)
+ end
+
+ it "is frozen" do
+ RUBY_COPYRIGHT.should.frozen?
+ end
+end
+
+describe "RUBY_DESCRIPTION" do
+ it "is a String" do
+ RUBY_DESCRIPTION.should.is_a?(String)
+ end
+
+ it "is frozen" do
+ RUBY_DESCRIPTION.should.frozen?
+ end
+end
+
+describe "RUBY_ENGINE" do
+ it "is a String" do
+ RUBY_ENGINE.should.is_a?(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.is_a?(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.is_a?(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.is_a?(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.is_a?(String)
+ end
+
+ it "is frozen" do
+ RUBY_REVISION.should.frozen?
+ end
+end
+
+ruby_version_is "4.0" 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
diff --git a/spec/ruby/core/class/allocate_spec.rb b/spec/ruby/core/class/allocate_spec.rb
new file mode 100644
index 0000000000..b8950a678e
--- /dev/null
+++ b/spec/ruby/core/class/allocate_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Class#allocate" do
+ it "returns an instance of self" do
+ klass = Class.new
+ klass.allocate.should.instance_of?(klass)
+ end
+
+ it "returns a fully-formed instance of Module" do
+ klass = Class.allocate
+ klass.constants.should_not == nil
+ klass.methods.should_not == nil
+ end
+
+ it "throws an exception when calling a method on a new instance" do
+ klass = Class.allocate
+ -> do
+ klass.new
+ end.should.raise(Exception)
+ end
+
+ it "does not call initialize on the new instance" do
+ klass = Class.new do
+ def initialize(*args)
+ @initialized = true
+ end
+
+ def initialized?
+ @initialized || false
+ end
+ end
+
+ klass.allocate.should_not.initialized?
+ end
+
+ it "raises TypeError for #superclass" do
+ -> do
+ Class.allocate.superclass
+ end.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/class/attached_object_spec.rb b/spec/ruby/core/class/attached_object_spec.rb
new file mode 100644
index 0000000000..b75e61ca07
--- /dev/null
+++ b/spec/ruby/core/class/attached_object_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+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
+
+ 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
+
+ it "raises TypeError if the class is not a singleton class" do
+ a = Class.new
+
+ -> { a.attached_object }.should.raise(TypeError, /is not a singleton class/)
+ end
+
+ it "raises TypeError for special singleton classes" do
+ -> { nil.singleton_class.attached_object }.should.raise(TypeError, /[`']NilClass' is not a singleton class/)
+ -> { true.singleton_class.attached_object }.should.raise(TypeError, /[`']TrueClass' is not a singleton class/)
+ -> { false.singleton_class.attached_object }.should.raise(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
new file mode 100644
index 0000000000..17c0171ceb
--- /dev/null
+++ b/spec/ruby/core/class/dup_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# NOTE: This is actually implemented by Module#initialize_copy
+describe "Class#dup" do
+ it "duplicates both the class and the singleton class" do
+ klass = Class.new do
+ def hello
+ "hello"
+ end
+
+ def self.message
+ "text"
+ end
+ end
+
+ klass_dup = klass.dup
+
+ klass_dup.new.hello.should == "hello"
+ klass_dup.message.should == "text"
+ end
+
+ it "retains an included module in the ancestor chain for the singleton class" do
+ klass = Class.new
+ mod = Module.new do
+ def hello
+ "hello"
+ end
+ end
+
+ klass.extend(mod)
+ klass_dup = klass.dup
+ klass_dup.hello.should == "hello"
+ end
+
+ it "retains the correct ancestor chain for the singleton class" do
+ super_klass = Class.new do
+ def hello
+ "hello"
+ end
+
+ def self.message
+ "text"
+ end
+ end
+
+ klass = Class.new(super_klass)
+ klass_dup = klass.dup
+
+ klass_dup.new.hello.should == "hello"
+ klass_dup.message.should == "text"
+ end
+
+ it "sets the name from the class to nil if not assigned to a constant" do
+ copy = CoreClassSpecs::Record.dup
+ copy.name.should == nil
+ end
+
+ it "stores the new name if assigned to a constant" do
+ CoreClassSpecs::RecordCopy = CoreClassSpecs::Record.dup
+ CoreClassSpecs::RecordCopy.name.should == "CoreClassSpecs::RecordCopy"
+ ensure
+ CoreClassSpecs.send(:remove_const, :RecordCopy)
+ end
+
+ it "raises TypeError if called on BasicObject" do
+ -> { BasicObject.dup }.should.raise(TypeError, "can't copy the root class")
+ end
+end
diff --git a/spec/ruby/core/class/fixtures/classes.rb b/spec/ruby/core/class/fixtures/classes.rb
new file mode 100644
index 0000000000..f96db90795
--- /dev/null
+++ b/spec/ruby/core/class/fixtures/classes.rb
@@ -0,0 +1,47 @@
+module CoreClassSpecs
+ class Record
+ end
+
+ module M
+ def inherited(klass)
+ ScratchPad.record klass
+ super
+ end
+ end
+
+ class F; end
+ class << F
+ include M
+ end
+
+ class A
+ def self.inherited(klass)
+ ScratchPad.record klass
+ end
+ end
+
+ class H < A
+ def self.inherited(klass)
+ super
+ end
+ end
+
+ module Inherited
+ class A
+ SUBCLASSES = []
+ def self.inherited(subclass)
+ SUBCLASSES << [self, subclass]
+ end
+ end
+
+ class B < A; end
+ class B < A; end # reopen
+ class C < B; end
+
+ class D
+ def self.inherited(subclass)
+ ScratchPad << self
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/class/inherited_spec.rb b/spec/ruby/core/class/inherited_spec.rb
new file mode 100644
index 0000000000..c9acc740a1
--- /dev/null
+++ b/spec/ruby/core/class/inherited_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Class.inherited" do
+
+ before :each do
+ ScratchPad.record nil
+ end
+
+ it "is invoked with the child Class when self is subclassed" do
+ begin
+ top = Class.new do
+ def self.inherited(cls)
+ $child_class = cls
+ end
+ end
+
+ child = Class.new(top)
+ $child_class.should == child
+
+ other_child = Class.new(top)
+ $child_class.should == other_child
+ ensure
+ $child_class = nil
+ end
+ end
+
+ it "is invoked only once per subclass" do
+ expected = [
+ [CoreClassSpecs::Inherited::A, CoreClassSpecs::Inherited::B],
+ [CoreClassSpecs::Inherited::B, CoreClassSpecs::Inherited::C],
+ ]
+
+ CoreClassSpecs::Inherited::A::SUBCLASSES.should == expected
+ end
+
+ it "is called when marked as a private class method" do
+ a = Class.new do
+ def self.inherited(klass)
+ ScratchPad.record klass
+ end
+ end
+ a.private_class_method :inherited
+ ScratchPad.recorded.should == nil
+ b = Class.new(a)
+ ScratchPad.recorded.should == b
+ end
+
+ it "is called when marked as a protected class method" do
+ a = Class.new
+ class << a
+ def inherited(klass)
+ ScratchPad.record klass
+ end
+ protected :inherited
+ end
+ ScratchPad.recorded.should == nil
+ b = Class.new(a)
+ ScratchPad.recorded.should == b
+ end
+
+ it "is called when marked as a public class method" do
+ a = Class.new do
+ def self.inherited(klass)
+ ScratchPad.record klass
+ end
+ end
+ a.public_class_method :inherited
+ ScratchPad.recorded.should == nil
+ b = Class.new(a)
+ ScratchPad.recorded.should == b
+ end
+
+ it "is called by super from a method provided by an included module" do
+ ScratchPad.recorded.should == nil
+ e = Class.new(CoreClassSpecs::F)
+ ScratchPad.recorded.should == e
+ end
+
+ it "is called by super even when marked as a private class method" do
+ ScratchPad.recorded.should == nil
+ CoreClassSpecs::H.private_class_method :inherited
+ i = Class.new(CoreClassSpecs::H)
+ ScratchPad.recorded.should == i
+ end
+
+ it "will be invoked by child class regardless of visibility" do
+ top = Class.new do
+ class << self
+ def inherited(cls); end
+ end
+ end
+
+ class << top; private :inherited; end
+ -> { Class.new(top) }.should_not.raise
+
+ class << top; protected :inherited; end
+ -> { Class.new(top) }.should_not.raise
+ 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/initialize_spec.rb b/spec/ruby/core/class/initialize_spec.rb
new file mode 100644
index 0000000000..ab8f0a157e
--- /dev/null
+++ b/spec/ruby/core/class/initialize_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "Class#initialize" do
+ it "is private" do
+ Class.private_methods(false).should.include?(:initialize)
+ end
+
+ it "raises a TypeError when called on already initialized classes" do
+ ->{
+ Integer.send :initialize
+ }.should.raise(TypeError)
+
+ ->{
+ Object.send :initialize
+ }.should.raise(TypeError)
+ end
+
+ # See [redmine:2601]
+ it "raises a TypeError when called on BasicObject" do
+ ->{
+ BasicObject.send :initialize
+ }.should.raise(TypeError)
+ end
+
+ describe "when given the Class" do
+ before :each do
+ @uninitialized = Class.allocate
+ end
+
+ it "raises a TypeError" do
+ ->{@uninitialized.send(:initialize, Class)}.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/class/new_spec.rb b/spec/ruby/core/class/new_spec.rb
new file mode 100644
index 0000000000..111e31252e
--- /dev/null
+++ b/spec/ruby/core/class/new_spec.rb
@@ -0,0 +1,157 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Class.new with a block given" do
+ it "yields the new class as self in the block" do
+ self_in_block = nil
+ klass = Class.new do
+ self_in_block = self
+ end
+ self_in_block.should.equal? klass
+ end
+
+ it "uses the given block as the class' body" do
+ klass = Class.new do
+ def self.message
+ "text"
+ end
+
+ def hello
+ "hello again"
+ end
+ end
+
+ klass.message.should == "text"
+ klass.new.hello.should == "hello again"
+ end
+
+ it "creates a subclass of the given superclass" do
+ sc = Class.new do
+ def self.body
+ @body
+ end
+ @body = self
+ def message; "text"; end
+ end
+ klass = Class.new(sc) do
+ def self.body
+ @body
+ end
+ @body = self
+ def message2; "hello"; end
+ end
+
+ klass.body.should == klass
+ sc.body.should == sc
+ klass.superclass.should == sc
+ klass.new.message.should == "text"
+ klass.new.message2.should == "hello"
+ end
+
+ it "runs the inherited hook after yielding the block" do
+ ScratchPad.record []
+ klass = Class.new(CoreClassSpecs::Inherited::D) do
+ ScratchPad << self
+ end
+
+ ScratchPad.recorded.should == [CoreClassSpecs::Inherited::D, klass]
+ end
+end
+
+describe "Class.new" do
+ it "creates a new anonymous class" do
+ klass = Class.new
+ klass.is_a?(Class).should == true
+
+ klass_instance = klass.new
+ klass_instance.is_a?(klass).should == true
+ end
+
+ it "raises a TypeError if passed a metaclass" do
+ obj = mock("Class.new metaclass")
+ meta = obj.singleton_class
+ -> { Class.new meta }.should.raise(TypeError)
+ end
+
+ it "creates a class without a name" do
+ Class.new.name.should == nil
+ end
+
+ it "creates a class that can be given a name by assigning it to a constant" do
+ ::MyClass = Class.new
+ ::MyClass.name.should == "MyClass"
+ 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
+ top = Class.new
+ Class.new(top).superclass.should == top
+ end
+
+ it "sets the new class' superclass to Object when no class given" do
+ Class.new.superclass.should == Object
+ end
+
+ it "raises a TypeError when given a non-Class" do
+ error_msg = /superclass must be a.*Class/
+ -> { Class.new("") }.should.raise(TypeError, error_msg)
+ -> { Class.new(1) }.should.raise(TypeError, error_msg)
+ -> { Class.new(:symbol) }.should.raise(TypeError, error_msg)
+ -> { Class.new(mock('o')) }.should.raise(TypeError, error_msg)
+ -> { Class.new(Module.new) }.should.raise(TypeError, error_msg)
+ -> { Class.new(BasicObject.new) }.should.raise(TypeError, error_msg)
+ end
+end
+
+describe "Class#new" do
+ it "returns a new instance of self" do
+ klass = Class.new
+ klass.new.is_a?(klass).should == true
+ end
+
+ it "invokes #initialize on the new instance with the given args" do
+ klass = Class.new do
+ def initialize(*args)
+ @initialized = true
+ @args = args
+ end
+
+ def args
+ @args
+ end
+
+ def initialized?
+ @initialized || false
+ end
+ end
+
+ klass.new.should.initialized?
+ klass.new(1, 2, 3).args.should == [1, 2, 3]
+ end
+
+ it "uses the internal allocator and does not call #allocate" do
+ klass = Class.new do
+ def self.allocate
+ raise "allocate should not be called"
+ end
+ end
+
+ instance = klass.new
+ instance.should.is_a? klass
+ instance.class.should.equal? klass
+ end
+
+ it "passes the block to #initialize" do
+ klass = Class.new do
+ def initialize
+ yield
+ end
+ end
+
+ klass.new { break 42 }.should == 42
+ end
+end
diff --git a/spec/ruby/core/class/subclasses_spec.rb b/spec/ruby/core/class/subclasses_spec.rb
new file mode 100644
index 0000000000..c3d7b07e12
--- /dev/null
+++ b/spec/ruby/core/class/subclasses_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../spec_helper'
+require_relative '../module/fixtures/classes'
+
+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)
+
+ 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
+
+ it "does not return prepended modules from the parent" do
+ parent = Class.new
+ child = Class.new(parent)
+ mod = Module.new
+ parent.prepend(mod)
+
+ assert_subclasses(parent, [child])
+ end
+
+ it "does not return prepended modules from the child" do
+ parent = Class.new
+ child = Class.new(parent)
+ mod = Module.new
+ child.prepend(mod)
+
+ assert_subclasses(parent, [child])
+ end
+
+ it "does not return singleton classes" do
+ a = Class.new
+
+ a_obj = a.new
+ def a_obj.force_singleton_class
+ 42
+ end
+
+ a.subclasses.should_not.include?(a_obj.singleton_class)
+ end
+
+ it "has 1 entry per module or class" do
+ ModuleSpecs::Parent.subclasses.should == ModuleSpecs::Parent.subclasses.uniq
+ end
+
+ it "works when creating subclasses concurrently" do
+ t = 16
+ n = 1000
+ go = false
+ superclass = Class.new
+
+ threads = t.times.map do
+ Thread.new do
+ Thread.pass until go
+ n.times.map do
+ Class.new(superclass)
+ end
+ end
+ end
+
+ go = true
+ classes = threads.map(&:value)
+
+ superclass.subclasses.size.should == t * n
+ superclass.subclasses.each { |c| c.should.is_a?(Class) }
+ end
+
+ def assert_subclasses(mod,subclasses)
+ mod.subclasses.sort_by(&:inspect).should == subclasses.sort_by(&:inspect)
+ end
+end
diff --git a/spec/ruby/core/class/superclass_spec.rb b/spec/ruby/core/class/superclass_spec.rb
new file mode 100644
index 0000000000..87d9b20490
--- /dev/null
+++ b/spec/ruby/core/class/superclass_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Class#superclass" do
+ it "returns the superclass of self" do
+ BasicObject.superclass.should == nil
+ Object.superclass.should == BasicObject
+ Class.superclass.should == Module
+ Class.new.superclass.should == Object
+ Class.new(String).superclass.should == String
+ Class.new(Integer).superclass.should == Integer
+ end
+
+ # redmine:567
+ describe "for a singleton class" do
+ it "of an object returns the class of the object" do
+ a = CoreClassSpecs::A.new
+ sc = class << a; self; end
+ sc.superclass.should == CoreClassSpecs::A
+ end
+
+ it "of a class returns the singleton class of its superclass" do # sorry, can't find a simpler way to express this...
+ sc = class << CoreClassSpecs::H; self; end
+ sc.superclass.should == class << CoreClassSpecs::A; self; end
+ end
+ end
+end
diff --git a/spec/ruby/core/comparable/between_spec.rb b/spec/ruby/core/comparable/between_spec.rb
new file mode 100644
index 0000000000..fd79bb9b4c
--- /dev/null
+++ b/spec/ruby/core/comparable/between_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#between?" do
+ it "returns true if self is greater than or equal to the first and less than or equal to the second argument" do
+ a = ComparableSpecs::Weird.new(-1)
+ b = ComparableSpecs::Weird.new(0)
+ c = ComparableSpecs::Weird.new(1)
+ d = ComparableSpecs::Weird.new(2)
+
+ a.between?(a, a).should == true
+ a.between?(a, b).should == true
+ a.between?(a, c).should == true
+ a.between?(a, d).should == true
+ c.between?(c, d).should == true
+ d.between?(d, d).should == true
+ c.between?(a, d).should == true
+
+ a.between?(b, b).should == false
+ a.between?(b, c).should == false
+ a.between?(b, d).should == false
+ c.between?(a, a).should == false
+ c.between?(a, b).should == false
+ end
+end
diff --git a/spec/ruby/core/comparable/clamp_spec.rb b/spec/ruby/core/comparable/clamp_spec.rb
new file mode 100644
index 0000000000..eb1dc1ff98
--- /dev/null
+++ b/spec/ruby/core/comparable/clamp_spec.rb
@@ -0,0 +1,223 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Comparable#clamp' do
+ it 'raises an Argument error unless the 2 parameters are correctly ordered' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(3)
+
+ -> { c.clamp(two, one) }.should.raise(ArgumentError)
+ one.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { c.clamp(one, two) }.should.raise(ArgumentError)
+ end
+
+ it 'returns self if within the given parameters' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ three = ComparableSpecs::WithOnlyCompareDefined.new(3)
+ c = ComparableSpecs::Weird.new(2)
+
+ c.clamp(one, two).should.equal?(c)
+ c.clamp(two, two).should.equal?(c)
+ c.clamp(one, three).should.equal?(c)
+ c.clamp(two, three).should.equal?(c)
+ end
+
+ 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)
+
+ c.clamp(one, two).should.equal?(one)
+ end
+
+ it 'returns the max parameter if greater than it' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(3)
+
+ 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)
+ three = ComparableSpecs::WithOnlyCompareDefined.new(3)
+ c = ComparableSpecs::Weird.new(2)
+
+ c.clamp(one..two).should.equal?(c)
+ c.clamp(two..two).should.equal?(c)
+ c.clamp(one..three).should.equal?(c)
+ c.clamp(two..three).should.equal?(c)
+ end
+
+ 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)
+
+ c.clamp(one..two).should.equal?(one)
+ end
+
+ it 'returns the maximum value of the range parameters if greater than it' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(3)
+
+ c.clamp(one..two).should.equal?(two)
+ end
+
+ it 'raises an Argument error if the range parameter is exclusive' do
+ one = ComparableSpecs::WithOnlyCompareDefined.new(1)
+ two = ComparableSpecs::WithOnlyCompareDefined.new(2)
+ c = ComparableSpecs::Weird.new(3)
+
+ -> { c.clamp(one...two) }.should.raise(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(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/equal_value_spec.rb b/spec/ruby/core/comparable/equal_value_spec.rb
new file mode 100644
index 0000000000..3af40d1c7e
--- /dev/null
+++ b/spec/ruby/core/comparable/equal_value_spec.rb
@@ -0,0 +1,114 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#==" do
+ a = b = nil
+ before :each do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+ end
+
+ it "returns true if other is the same as self" do
+ (a == a).should == true
+ (b == b).should == true
+ end
+
+ it "calls #<=> on self with other and returns true if #<=> returns 0" do
+ a.should_receive(:<=>).once.and_return(0)
+ (a == b).should == true
+ end
+
+ it "calls #<=> on self with other and returns true if #<=> returns 0.0" do
+ a.should_receive(:<=>).once.and_return(0.0)
+ (a == b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns a positive Integer" do
+ a.should_receive(:<=>).once.and_return(1)
+ (a == b).should == false
+ end
+
+ it "returns false if calling #<=> on self returns a negative Integer" do
+ a.should_receive(:<=>).once.and_return(-1)
+ (a == b).should == false
+ end
+
+ context "when #<=> returns nil" do
+ before :each do
+ a.should_receive(:<=>).once.and_return(nil)
+ end
+
+ it "returns false" do
+ (a == b).should == false
+ end
+ end
+
+ context "when #<=> returns nor nil neither an Integer" do
+ before :each do
+ a.should_receive(:<=>).once.and_return("abc")
+ end
+
+ it "raises an ArgumentError" do
+ -> { (a == b) }.should.raise(ArgumentError)
+ end
+ end
+
+ context "when #<=> raises an exception" do
+ context "if it is a StandardError" do
+ before :each do
+ a.should_receive(:<=>).once.and_raise(StandardError)
+ end
+
+ it "lets it go through" do
+ -> { (a == b) }.should.raise(StandardError)
+ end
+ end
+
+ context "if it is a subclass of StandardError" do
+ # TypeError < StandardError
+ before :each do
+ a.should_receive(:<=>).once.and_raise(TypeError)
+ end
+
+ it "lets it go through" do
+ -> { (a == b) }.should.raise(TypeError)
+ end
+ end
+
+ it "lets it go through if it is not a StandardError" do
+ a.should_receive(:<=>).once.and_raise(Exception)
+ -> { (a == b) }.should.raise(Exception)
+ end
+ end
+
+ context "when #<=> is not defined" do
+ before :each do
+ @a = ComparableSpecs::WithoutCompareDefined.new
+ @b = ComparableSpecs::WithoutCompareDefined.new
+ end
+
+ it "returns true for identical objects" do
+ @a.should == @a
+ end
+
+ it "returns false and does not recurse infinitely" do
+ @a.should_not == @b
+ end
+ end
+
+ context "when #<=> calls super" do
+ before :each do
+ @a = ComparableSpecs::CompareCallingSuper.new
+ @b = ComparableSpecs::CompareCallingSuper.new
+ end
+
+ it "returns true for identical objects" do
+ @a.should == @a
+ end
+
+ it "calls the defined #<=> only once for different objects" do
+ @a.should_not == @b
+ @a.calls.should == 1
+ end
+ end
+end
diff --git a/spec/ruby/core/comparable/fixtures/classes.rb b/spec/ruby/core/comparable/fixtures/classes.rb
new file mode 100644
index 0000000000..2bdabbf014
--- /dev/null
+++ b/spec/ruby/core/comparable/fixtures/classes.rb
@@ -0,0 +1,37 @@
+module ComparableSpecs
+ class WithOnlyCompareDefined
+ attr_reader :value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def <=>(other)
+ return nil if other.nil?
+ self.value <=> other.value
+ end
+ end
+
+ class Weird < WithOnlyCompareDefined
+ include Comparable
+ end
+
+ class WithoutCompareDefined
+ include Comparable
+ end
+
+ class CompareCallingSuper
+ include Comparable
+
+ attr_reader :calls
+
+ def initialize
+ @calls = 0
+ end
+
+ def <=>(other)
+ @calls += 1
+ super(other)
+ end
+ end
+end
diff --git a/spec/ruby/core/comparable/gt_spec.rb b/spec/ruby/core/comparable/gt_spec.rb
new file mode 100644
index 0000000000..cebb5464ad
--- /dev/null
+++ b/spec/ruby/core/comparable/gt_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#>" do
+ it "calls #<=> on self with other and returns true if #<=> returns any Integer greater than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(1)
+ (a > b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.1)
+ (a > b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(10000000)
+ (a > b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns 0 or any Integer less than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0)
+ (a > b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.0)
+ (a > b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-1.0)
+ (a > b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-10000000)
+ (a > b).should == false
+ end
+
+ it "raises an ArgumentError if calling #<=> on self returns nil" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { (a > b) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/comparable/gte_spec.rb b/spec/ruby/core/comparable/gte_spec.rb
new file mode 100644
index 0000000000..16da81b3ea
--- /dev/null
+++ b/spec/ruby/core/comparable/gte_spec.rb
@@ -0,0 +1,47 @@
+require_relative 'fixtures/classes'
+require_relative '../../spec_helper'
+
+describe "Comparable#>=" do
+ it "calls #<=> on self with other and returns true if #<=> returns 0 or any Integer greater than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0)
+ (a >= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.0)
+ (a >= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(1)
+ (a >= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.1)
+ (a >= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(10000000)
+ (a >= b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns any Integer less than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-0.1)
+ (a >= b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-1.0)
+ (a >= b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-10000000)
+ (a >= b).should == false
+ end
+
+ it "raises an ArgumentError if calling #<=> on self returns nil" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { (a >= b) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/comparable/lt_spec.rb b/spec/ruby/core/comparable/lt_spec.rb
new file mode 100644
index 0000000000..175646d0d7
--- /dev/null
+++ b/spec/ruby/core/comparable/lt_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#<" do
+ it "calls #<=> on self with other and returns true if #<=> returns any Integer less than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-1)
+ (a < b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-0.1)
+ (a < b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-10000000)
+ (a < b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns 0 or any Integer greater than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0)
+ (a < b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.0)
+ (a < b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(1.0)
+ (a < b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(10000000)
+ (a < b).should == false
+ end
+
+ it "raises an ArgumentError if calling #<=> on self returns nil" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { (a < b) }.should.raise(ArgumentError)
+ end
+
+ it "raises an argument error with a message containing the value" do
+ -> { ("foo" < 7) }.should.raise(ArgumentError) { |e|
+ e.message.should.include? "String with 7 failed"
+ }
+ end
+end
diff --git a/spec/ruby/core/comparable/lte_spec.rb b/spec/ruby/core/comparable/lte_spec.rb
new file mode 100644
index 0000000000..8cbbd5ebb4
--- /dev/null
+++ b/spec/ruby/core/comparable/lte_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Comparable#<=" do
+ it "calls #<=> on self with other and returns true if #<=> returns 0 or any Integer less than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0)
+ (a <= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.0)
+ (a <= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-1)
+ (a <= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-0.1)
+ (a <= b).should == true
+
+ a.should_receive(:<=>).any_number_of_times.and_return(-10000000)
+ (a <= b).should == true
+ end
+
+ it "returns false if calling #<=> on self returns any Integer greater than 0" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(10)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(0.1)
+ (a <= b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(1.0)
+ (a <= b).should == false
+
+ a.should_receive(:<=>).any_number_of_times.and_return(10000000)
+ (a <= b).should == false
+ end
+
+ it "raises an ArgumentError if calling #<=> on self returns nil" do
+ a = ComparableSpecs::Weird.new(0)
+ b = ComparableSpecs::Weird.new(20)
+
+ a.should_receive(:<=>).any_number_of_times.and_return(nil)
+ -> { (a <= b) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/complex/abs2_spec.rb b/spec/ruby/core/complex/abs2_spec.rb
new file mode 100644
index 0000000000..3e5c5fd225
--- /dev/null
+++ b/spec/ruby/core/complex/abs2_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Complex#abs2" do
+ it "returns the sum of the squares of the real and imaginary parts" do
+ Complex(1, -2).abs2.should == 1 + 4
+ Complex(-0.1, 0.2).abs2.should be_close(0.01 + 0.04, TOLERANCE)
+ Complex(0).abs2.should == 0
+ end
+end
diff --git a/spec/ruby/core/complex/abs_spec.rb b/spec/ruby/core/complex/abs_spec.rb
new file mode 100644
index 0000000000..ed5aebb11d
--- /dev/null
+++ b/spec/ruby/core/complex/abs_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "Complex#abs" do
+ it "returns the modulus: |a + bi| = sqrt((a ^ 2) + (b ^ 2))" do
+ Complex(0, 0).abs.should == 0
+ Complex(3, 4).abs.should == 5 # well-known integer case
+ Complex(-3, 4).abs.should == 5
+ Complex(1, -1).abs.should be_close(Math.sqrt(2), TOLERANCE)
+ Complex(6.5, 0).abs.should be_close(6.5, TOLERANCE)
+ Complex(0, -7.2).abs.should be_close(7.2, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/complex/angle_spec.rb b/spec/ruby/core/complex/angle_spec.rb
new file mode 100644
index 0000000000..7551214d2b
--- /dev/null
+++ b/spec/ruby/core/complex/angle_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Complex#angle" do
+ it "is an alias of Complex#arg" do
+ Complex.instance_method(:angle).should == Complex.instance_method(:arg)
+ end
+end
diff --git a/spec/ruby/core/complex/arg_spec.rb b/spec/ruby/core/complex/arg_spec.rb
new file mode 100644
index 0000000000..dd64102d77
--- /dev/null
+++ b/spec/ruby/core/complex/arg_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Complex#arg" do
+ it "returns the argument -- i.e., the angle from (1, 0) in the complex plane" do
+ two_pi = 2 * Math::PI
+ (Complex(1, 0).arg % two_pi).should be_close(0, TOLERANCE)
+ (Complex(0, 2).arg % two_pi).should be_close(Math::PI * 0.5, TOLERANCE)
+ (Complex(-100, 0).arg % two_pi).should be_close(Math::PI, TOLERANCE)
+ (Complex(0, -75.3).arg % two_pi).should be_close(Math::PI * 1.5, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/complex/coerce_spec.rb b/spec/ruby/core/complex/coerce_spec.rb
new file mode 100644
index 0000000000..d4ea85a713
--- /dev/null
+++ b/spec/ruby/core/complex/coerce_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+
+describe "Complex#coerce" do
+ before :each do
+ @one = Complex(1)
+ end
+
+ it "returns an array containing other and self as Complex when other is an Integer" do
+ result = @one.coerce(2)
+ result.should == [2, 1]
+ result.first.should.is_a?(Complex)
+ result.last.should.is_a?(Complex)
+ end
+
+ it "returns an array containing other and self as Complex when other is a Float" do
+ result = @one.coerce(20.5)
+ result.should == [20.5, 1]
+ result.first.should.is_a?(Complex)
+ result.last.should.is_a?(Complex)
+ end
+
+ it "returns an array containing other and self as Complex when other is a Bignum" do
+ result = @one.coerce(4294967296)
+ result.should == [4294967296, 1]
+ result.first.should.is_a?(Complex)
+ result.last.should.is_a?(Complex)
+ end
+
+ it "returns an array containing other and self as Complex when other is a Rational" do
+ result = @one.coerce(Rational(5,6))
+ result.should == [Rational(5,6), 1]
+ result.first.should.is_a?(Complex)
+ result.last.should.is_a?(Complex)
+ end
+
+ it "returns an array containing other and self when other is a Complex" do
+ other = Complex(2)
+ result = @one.coerce(other)
+ result.should == [other, @one]
+ result.first.should.equal?(other)
+ result.last.should.equal?(@one)
+ end
+
+ it "returns an array containing other as Complex and self when other is a Numeric which responds to #real? with true" do
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(true)
+ result = @one.coerce(other)
+ result.should == [other, @one]
+ result.first.should.eql?(Complex(other))
+ result.last.should.equal?(@one)
+ end
+
+ it "raises TypeError when other is a Numeric which responds to #real? with false" do
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(false)
+ -> { @one.coerce(other) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when other is a String" do
+ -> { @one.coerce("20") }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when other is nil" do
+ -> { @one.coerce(nil) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when other is false" do
+ -> { @one.coerce(false) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/complex/comparison_spec.rb b/spec/ruby/core/complex/comparison_spec.rb
new file mode 100644
index 0000000000..3115f4e205
--- /dev/null
+++ b/spec/ruby/core/complex/comparison_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "Complex#<=>" do
+ it "returns nil if either self or argument has imaginary part" do
+ (Complex(5, 1) <=> Complex(2)).should == nil
+ (Complex(1) <=> Complex(2, 1)).should == nil
+ (5 <=> Complex(2, 1)).should == nil
+ end
+
+ it "returns nil if argument is not numeric" do
+ (Complex(5, 1) <=> "cmp").should == nil
+ (Complex(1) <=> "cmp").should == nil
+ (Complex(1) <=> Object.new).should == nil
+ end
+
+ it "returns 0, 1, or -1 if self and argument do not have imaginary part" do
+ (Complex(5) <=> Complex(2)).should == 1
+ (Complex(2) <=> Complex(3)).should == -1
+ (Complex(2) <=> Complex(2)).should == 0
+
+ (Complex(5) <=> 2).should == 1
+ (Complex(2) <=> 3).should == -1
+ (Complex(2) <=> 2).should == 0
+ end
+end
diff --git a/spec/ruby/core/complex/conj_spec.rb b/spec/ruby/core/complex/conj_spec.rb
new file mode 100644
index 0000000000..063c85faec
--- /dev/null
+++ b/spec/ruby/core/complex/conj_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Complex#conj" do
+ it "is an alias of Complex#conjugate" do
+ Complex.instance_method(:conj).should == Complex.instance_method(:conjugate)
+ end
+end
diff --git a/spec/ruby/core/complex/conjugate_spec.rb b/spec/ruby/core/complex/conjugate_spec.rb
new file mode 100644
index 0000000000..256fe4b3be
--- /dev/null
+++ b/spec/ruby/core/complex/conjugate_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+describe "Complex#conjugate" do
+ it "returns the complex conjugate: conj a + bi = a - bi" do
+ Complex(3, 5).conjugate.should == Complex(3, -5)
+ Complex(3, -5).conjugate.should == Complex(3, 5)
+ Complex(-3.0, 5.2).conjugate.should be_close(Complex(-3.0, -5.2), TOLERANCE)
+ Complex(3.0, -5.2).conjugate.should be_close(Complex(3.0, 5.2), TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/complex/constants_spec.rb b/spec/ruby/core/complex/constants_spec.rb
new file mode 100644
index 0000000000..200e97731a
--- /dev/null
+++ b/spec/ruby/core/complex/constants_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Complex::I" do
+ it "is Complex(0, 1)" do
+ Complex::I.should.eql?(Complex(0, 1))
+ end
+end
diff --git a/spec/ruby/core/complex/denominator_spec.rb b/spec/ruby/core/complex/denominator_spec.rb
new file mode 100644
index 0000000000..c1a2003820
--- /dev/null
+++ b/spec/ruby/core/complex/denominator_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Complex#denominator" do
+ it "returns the least common multiple denominator of the real and imaginary parts" do
+ Complex(3, 4).denominator.should == 1
+ Complex(3, bignum_value).denominator.should == 1
+
+ Complex(3, Rational(3,4)).denominator.should == 4
+
+ Complex(Rational(4,8), Rational(3,4)).denominator.should == 4
+ Complex(Rational(3,8), Rational(3,4)).denominator.should == 8
+ end
+end
diff --git a/spec/ruby/core/complex/divide_spec.rb b/spec/ruby/core/complex/divide_spec.rb
new file mode 100644
index 0000000000..d5b4aa3885
--- /dev/null
+++ b/spec/ruby/core/complex/divide_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+
+describe "Complex#/" do
+ describe "with Complex" do
+ it "divides according to the usual rule for complex numbers" do
+ a = Complex((1 * 10) - (2 * 20), (1 * 20) + (2 * 10))
+ b = Complex(1, 2)
+ (a / b).should == Complex(10, 20)
+
+ c = Complex((1.5 * 100.2) - (2.1 * -30.3), (1.5 * -30.3) + (2.1 * 100.2))
+ d = Complex(1.5, 2.1)
+ # remember the floating-point arithmetic
+ (c / d).should be_close(Complex(100.2, -30.3), TOLERANCE)
+ end
+ end
+
+ describe "with Fixnum" do
+ it "divides both parts of the Complex number" do
+ (Complex(20, 40) / 2).should == Complex(10, 20)
+ (Complex(30, 30) / 10).should == Complex(3, 3)
+ end
+
+ it "raises a ZeroDivisionError when given zero" do
+ -> { Complex(20, 40) / 0 }.should.raise(ZeroDivisionError)
+ end
+
+ it "produces Rational parts" do
+ (Complex(5, 9) / 2).should.eql?(Complex(Rational(5,2), Rational(9,2)))
+ end
+ end
+
+ describe "with Bignum" do
+ it "divides both parts of the Complex number" do
+ (Complex(20, 40) / 2).should == Complex(10, 20)
+ (Complex(15, 16) / 2.0).should be_close(Complex(7.5, 8), TOLERANCE)
+ end
+ end
+
+ describe "with Float" do
+ it "divides both parts of the Complex number" do
+ (Complex(3, 9) / 1.5).should == Complex(2, 6)
+ (Complex(15, 16) / 2.0).should be_close(Complex(7.5, 8), TOLERANCE)
+ end
+
+ it "returns Complex(Infinity, Infinity) when given zero" do
+ (Complex(20, 40) / 0.0).real.infinite?.should == 1
+ (Complex(20, 40) / 0.0).imag.infinite?.should == 1
+ (Complex(-20, 40) / 0.0).real.infinite?.should == -1
+ (Complex(-20, 40) / 0.0).imag.infinite?.should == 1
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([4, 2])
+ (value / obj).should == 2
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with true" do
+ it "returns Complex(real.quo(other), imag.quo(other))" do
+ other = mock_numeric('other')
+ real = mock_numeric('real')
+ imag = mock_numeric('imag')
+ other.should_receive(:real?).and_return(true)
+ real.should_receive(:quo).with(other).and_return(1)
+ imag.should_receive(:quo).with(other).and_return(2)
+ (Complex(real, imag) / other).should == Complex(1, 2)
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with false" do
+ it "coerces the passed argument to Complex and divides the resulting elements" do
+ complex = Complex(3, 0)
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(false)
+ other.should_receive(:coerce).with(complex).and_return([5, 2])
+ (complex / other).should.eql?(Rational(5, 2))
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/eql_spec.rb b/spec/ruby/core/complex/eql_spec.rb
new file mode 100644
index 0000000000..2082a22feb
--- /dev/null
+++ b/spec/ruby/core/complex/eql_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Complex#eql?" do
+ it "returns false if other is not Complex" do
+ Complex(1).eql?(1).should == false
+ end
+
+ it "returns true when the respective parts are of the same classes and self == other" do
+ Complex(1, 2).eql?(Complex(1, 2)).should == true
+ end
+
+ it "returns false when the real parts are of different classes" do
+ Complex(1).eql?(Complex(1.0)).should == false
+ end
+
+ it "returns false when the imaginary parts are of different classes" do
+ Complex(1, 2).eql?(Complex(1, 2.0)).should == false
+ end
+
+ it "returns false when self == other is false" do
+ Complex(1, 2).eql?(Complex(2, 3)).should == false
+ end
+
+ it "does NOT send #eql? to real or imaginary parts" do
+ real = mock_numeric('real')
+ imag = mock_numeric('imag')
+ real.should_not_receive(:eql?)
+ imag.should_not_receive(:eql?)
+ Complex(real, imag).eql?(Complex(real, imag)).should == true
+ end
+end
diff --git a/spec/ruby/core/complex/equal_value_spec.rb b/spec/ruby/core/complex/equal_value_spec.rb
new file mode 100644
index 0000000000..b3562ff3fb
--- /dev/null
+++ b/spec/ruby/core/complex/equal_value_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+
+describe "Complex#==" do
+ describe "with Complex" do
+ it "returns true when self and other have numerical equality" do
+ Complex(1, 2).should == Complex(1, 2)
+ Complex(3, 9).should == Complex(3, 9)
+ Complex(-3, -9).should == Complex(-3, -9)
+
+ Complex(1, 2).should_not == Complex(3, 4)
+ Complex(3, 9).should_not == Complex(9, 3)
+
+ Complex(1.0, 2.0).should == Complex(1, 2)
+ Complex(3.0, 9.0).should_not == Complex(9.0, 3.0)
+
+ Complex(1.5, 2.5).should == Complex(1.5, 2.5)
+ Complex(1.5, 2.5).should == Complex(1.5, 2.5)
+ Complex(-1.5, 2.5).should == Complex(-1.5, 2.5)
+
+ Complex(1.5, 2.5).should_not == Complex(2.5, 1.5)
+ Complex(3.75, 2.5).should_not == Complex(1.5, 2.5)
+
+ Complex(bignum_value, 2.5).should == Complex(bignum_value, 2.5)
+ Complex(3.75, bignum_value).should_not == Complex(1.5, bignum_value)
+
+ Complex(nan_value).should_not == Complex(nan_value)
+ end
+ end
+
+ describe "with Numeric" do
+ it "returns true when self's imaginary part is 0 and the real part and other have numerical equality" do
+ Complex(3, 0).should == 3
+ Complex(-3, 0).should == -3
+
+ Complex(3.5, 0).should == 3.5
+ Complex(-3.5, 0).should == -3.5
+
+ Complex(bignum_value, 0).should == bignum_value
+ Complex(-bignum_value, 0).should == -bignum_value
+
+ Complex(3.0, 0).should == 3
+ Complex(-3.0, 0).should == -3
+
+ Complex(3, 0).should_not == 4
+ Complex(-3, 0).should_not == -4
+
+ Complex(3.5, 0).should_not == -4.5
+ Complex(-3.5, 0).should_not == 2.5
+
+ Complex(bignum_value, 0).should_not == bignum_value(10)
+ Complex(-bignum_value, 0).should_not == -bignum_value(20)
+ end
+ end
+
+ describe "with Object" do
+ # Integer#== and Float#== only return booleans - Bug?
+ it "calls other#== with self" do
+ value = Complex(3, 0)
+
+ obj = mock("Object")
+ obj.should_receive(:==).with(value).and_return(:expected)
+
+ (value == obj).should_not == false
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with true" do
+ before do
+ @other = mock_numeric('other')
+ @other.should_receive(:real?).any_number_of_times.and_return(true)
+ end
+
+ it "returns real == other when the imaginary part is zero" do
+ real = mock_numeric('real')
+ real.should_receive(:==).with(@other).and_return(true)
+ (Complex(real, 0) == @other).should == true
+ end
+
+ it "returns false when the imaginary part is not zero" do
+ (Complex(3, 1) == @other).should == false
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with false" do
+ it "returns other == self" do
+ complex = Complex(3, 0)
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(false)
+ other.should_receive(:==).with(complex).and_return(true)
+ (complex == other).should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/exponent_spec.rb b/spec/ruby/core/complex/exponent_spec.rb
new file mode 100644
index 0000000000..d0db0a40c2
--- /dev/null
+++ b/spec/ruby/core/complex/exponent_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+
+describe "Complex#**" do
+ describe "with Integer 0" do
+ it "returns Complex(1)" do
+ (Complex(3, 4) ** 0).should.eql?(Complex(1))
+ end
+ end
+
+ describe "with Float 0.0" do
+ it "returns Complex(1.0, 0.0)" do
+ (Complex(3, 4) ** 0.0).should.eql?(Complex(1.0, 0.0))
+ end
+ end
+
+ describe "with Complex" do
+ it "returns self raised to the given power" do
+ (Complex(2, 1) ** Complex(2, 1)).should be_close(Complex(-0.504824688978319, 3.10414407699553), TOLERANCE)
+ (Complex(2, 1) ** Complex(3, 4)).should be_close(Complex(-0.179174656916581, -1.74071656397662), TOLERANCE)
+
+ (Complex(2, 1) ** Complex(-2, -1)).should be_close(Complex(-0.051041070450869, -0.313849223270419), TOLERANCE)
+ (Complex(-2, -1) ** Complex(2, 1)).should be_close(Complex(-11.6819929610857, 71.8320439736158), TOLERANCE)
+ end
+ end
+
+ describe "with Integer" do
+ it "returns self raised to the given power" do
+ (Complex(2, 1) ** 2).should == Complex(3, 4)
+ (Complex(3, 4) ** 2).should == Complex(-7, 24)
+ (Complex(3, 4) ** -2).should be_close(Complex(-0.0112, -0.0384), TOLERANCE)
+
+
+ (Complex(2, 1) ** 2.5).should be_close(Complex(2.99179707178602, 6.85206901006896), TOLERANCE)
+ (Complex(3, 4) ** 2.5).should be_close(Complex(-38.0, 41.0), TOLERANCE)
+ (Complex(3, 4) ** -2.5).should be_close(Complex(-0.01216, -0.01312), TOLERANCE)
+
+ (Complex(1) ** 1).should == Complex(1)
+
+ # NOTE: Takes way too long...
+ #(Complex(2, 1) ** bignum_value)
+ end
+ end
+
+ describe "with Rational" do
+ it "returns self raised to the given power" do
+ (Complex(2, 1) ** Rational(3, 4)).should be_close(Complex(1.71913265276568, 0.623124744394697), TOLERANCE)
+ (Complex(2, 1) ** Rational(4, 3)).should be_close(Complex(2.3828547125173, 1.69466313833091), TOLERANCE)
+ (Complex(2, 1) ** Rational(-4, 3)).should be_close(Complex(0.278700377879388, -0.198209003071003), TOLERANCE)
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([2, 5])
+ (value ** obj).should == 2 ** 5
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/fdiv_spec.rb b/spec/ruby/core/complex/fdiv_spec.rb
new file mode 100644
index 0000000000..fdcbc6a95d
--- /dev/null
+++ b/spec/ruby/core/complex/fdiv_spec.rb
@@ -0,0 +1,129 @@
+require_relative '../../spec_helper'
+
+describe "Complex#fdiv" do
+ it "accepts a numeric argument" do
+ -> { Complex(20).fdiv(2) }.should_not.raise(TypeError)
+ -> { Complex(20).fdiv(2.0) }.should_not.raise(TypeError)
+ -> { Complex(20).fdiv(bignum_value) }.should_not.raise(TypeError)
+ end
+
+ it "accepts a negative numeric argument" do
+ -> { Complex(20).fdiv(-2) }.should_not.raise(TypeError)
+ -> { Complex(20).fdiv(-2.0) }.should_not.raise(TypeError)
+ -> { Complex(20).fdiv(-bignum_value) }.should_not.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed a non-numeric argument" do
+ -> { Complex(20).fdiv([]) }.should.raise(TypeError)
+ -> { Complex(20).fdiv(:sym) }.should.raise(TypeError)
+ -> { Complex(20).fdiv('s') }.should.raise(TypeError)
+ end
+
+ it "sets the real part to NaN if self's real part is NaN" do
+ Complex(nan_value).fdiv(2).real.nan?.should == true
+ end
+
+ it "sets the imaginary part to NaN if self's imaginary part is NaN" do
+ Complex(2, nan_value).fdiv(2).imag.nan?.should == true
+ end
+
+ it "sets the real and imaginary part to NaN if self's real and imaginary parts are NaN" do
+ Complex(nan_value, nan_value).fdiv(2).imag.nan?.should == true
+ Complex(nan_value, nan_value).fdiv(2).real.nan?.should == true
+ end
+
+ it "sets the real and imaginary part to NaN if self's real part and the argument are both NaN" do
+ Complex(nan_value, 2).fdiv(nan_value).imag.nan?.should == true
+ Complex(nan_value, 2).fdiv(nan_value).real.nan?.should == true
+ end
+
+ it "sets the real and imaginary part to NaN if self's real part, self's imaginary part, and the argument are NaN" do
+ Complex(nan_value, nan_value).fdiv(nan_value).imag.nan?.should == true
+ Complex(nan_value, nan_value).fdiv(nan_value).real.nan?.should == true
+ end
+
+ it "sets the real part to Infinity if self's real part is Infinity" do
+ Complex(infinity_value).fdiv(2).real.infinite?.should == 1
+ Complex(infinity_value,2).fdiv(2).real.infinite?.should == 1
+ end
+
+ it "sets the imaginary part to Infinity if self's imaginary part is Infinity" do
+ Complex(2, infinity_value).fdiv(2).imag.infinite?.should == 1
+ Complex(2, infinity_value).fdiv(2).imag.infinite?.should == 1
+ end
+
+ it "sets the imaginary and real part to Infinity if self's imaginary and real parts are Infinity" do
+ Complex(infinity_value, infinity_value).fdiv(2).real.infinite?.should == 1
+ Complex(infinity_value, infinity_value).fdiv(2).imag.infinite?.should == 1
+ end
+
+ it "sets the real part to NaN and the imaginary part to NaN if self's imaginary part, self's real part, and the argument are Infinity" do
+ Complex(infinity_value, infinity_value).fdiv(infinity_value).real.nan?.should == true
+ Complex(infinity_value, infinity_value).fdiv(infinity_value).imag.nan?.should == true
+ end
+end
+
+describe "Complex#fdiv with no imaginary part" do
+ before :each do
+ @numbers = [1, 5.43, 10, bignum_value, 99872.2918710].map{|n| [n,-n]}.flatten
+ end
+
+ it "returns a Complex number" do
+ @numbers.each do |real|
+ @numbers.each do |other|
+ Complex(real).fdiv(other).should.instance_of?(Complex)
+ end
+ end
+ end
+
+ it "sets the real part to self's real part fdiv'd with the argument" do
+ @numbers.each do |real|
+ @numbers.each do |other|
+ Complex(real).fdiv(other).real.should == real.fdiv(other)
+ end
+ end
+ end
+
+ it "sets the imaginary part to 0.0" do
+ @numbers.each do |real|
+ @numbers.each do |other|
+ Complex(real).fdiv(other).imaginary.should == 0.0
+ end
+ end
+ end
+end
+
+describe "Complex#fdiv with an imaginary part" do
+ before :each do
+ @numbers = [1, 5.43, 10, bignum_value, 99872.2918710].map{|n| [n,-n]}.flatten
+ end
+
+ it "returns a Complex number" do
+ @numbers.each do |real|
+ @numbers.each_with_index do |other,idx|
+ Complex(
+ real,@numbers[idx == 0 ? -1 : idx-1]
+ ).fdiv(other).should.instance_of?(Complex)
+ end
+ end
+ end
+
+ it "sets the real part to self's real part fdiv'd with the argument" do
+ @numbers.each do |real|
+ @numbers.each_with_index do |other,idx|
+ Complex(
+ real,@numbers[idx == 0 ? -1 : idx-1]
+ ).fdiv(other).real.should == real.fdiv(other)
+ end
+ end
+ end
+
+ it "sets the imaginary part to the imaginary part fdiv'd with the argument" do
+ @numbers.each do |real|
+ @numbers.each_with_index do |other,idx|
+ im = @numbers[idx == 0 ? -1 : idx-1]
+ Complex(real, im).fdiv(other).imag.should == im.fdiv(other)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/finite_spec.rb b/spec/ruby/core/complex/finite_spec.rb
new file mode 100644
index 0000000000..7d9f82404e
--- /dev/null
+++ b/spec/ruby/core/complex/finite_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Complex#finite?" do
+ it "returns true if magnitude is finite" do
+ (1+1i).should.finite?
+ end
+
+ it "returns false for positive infinity" do
+ value = Complex(Float::INFINITY, 42)
+ value.should_not.finite?
+ end
+
+ it "returns false for positive complex with infinite imaginary" do
+ value = Complex(1, Float::INFINITY)
+ value.should_not.finite?
+ end
+
+ it "returns false for negative infinity" do
+ value = -Complex(Float::INFINITY, 42)
+ value.should_not.finite?
+ end
+
+ it "returns false for negative complex with infinite imaginary" do
+ value = -Complex(1, Float::INFINITY)
+ value.should_not.finite?
+ end
+
+ it "returns false for NaN" do
+ value = Complex(Float::NAN, Float::NAN)
+ value.should_not.finite?
+ end
+end
diff --git a/spec/ruby/core/complex/hash_spec.rb b/spec/ruby/core/complex/hash_spec.rb
new file mode 100644
index 0000000000..cad283309d
--- /dev/null
+++ b/spec/ruby/core/complex/hash_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Complex#hash" do
+ it "is static" do
+ Complex(1).hash.should == Complex(1).hash
+ Complex(1, 0).hash.should == Complex(1).hash
+ Complex(1, 1).hash.should == Complex(1, 1).hash
+ end
+
+ it "is different for different instances" do
+ Complex(1, 2).hash.should_not == Complex(1, 1).hash
+ Complex(2, 1).hash.should_not == Complex(1, 1).hash
+
+ Complex(1, 2).hash.should_not == Complex(2, 1).hash
+ end
+end
diff --git a/spec/ruby/core/complex/imag_spec.rb b/spec/ruby/core/complex/imag_spec.rb
new file mode 100644
index 0000000000..225f168e78
--- /dev/null
+++ b/spec/ruby/core/complex/imag_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Complex#imag" do
+ it "is an alias of Complex#imaginary" do
+ Complex.instance_method(:imag).should == Complex.instance_method(:imaginary)
+ end
+end
diff --git a/spec/ruby/core/complex/imaginary_spec.rb b/spec/ruby/core/complex/imaginary_spec.rb
new file mode 100644
index 0000000000..ac9284e934
--- /dev/null
+++ b/spec/ruby/core/complex/imaginary_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+
+describe "Complex#imaginary" do
+ it "returns the imaginary part of self" do
+ Complex(1, 0).imaginary.should == 0
+ Complex(2, 1).imaginary.should == 1
+ Complex(6.7, 8.9).imaginary.should == 8.9
+ Complex(1, bignum_value).imaginary.should == bignum_value
+ end
+end
diff --git a/spec/ruby/core/complex/infinite_spec.rb b/spec/ruby/core/complex/infinite_spec.rb
new file mode 100644
index 0000000000..9e48860dee
--- /dev/null
+++ b/spec/ruby/core/complex/infinite_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Complex#infinite?" do
+ it "returns nil if magnitude is finite" do
+ (1+1i).infinite?.should == nil
+ end
+
+ it "returns 1 for positive infinity" do
+ value = Complex(Float::INFINITY, 42).infinite?
+ value.should == 1
+ end
+
+ it "returns 1 for positive complex with infinite imaginary" do
+ value = Complex(1, Float::INFINITY).infinite?
+ value.should == 1
+ end
+
+ it "returns -1 for negative infinity" do
+ value = -Complex(Float::INFINITY, 42).infinite?
+ value.should == -1
+ end
+
+ it "returns -1 for negative complex with infinite imaginary" do
+ value = -Complex(1, Float::INFINITY).infinite?
+ value.should == -1
+ end
+
+ it "returns nil for NaN" do
+ value = Complex(0, Float::NAN).infinite?
+ value.should == nil
+ end
+end
diff --git a/spec/ruby/core/complex/inspect_spec.rb b/spec/ruby/core/complex/inspect_spec.rb
new file mode 100644
index 0000000000..045be94b22
--- /dev/null
+++ b/spec/ruby/core/complex/inspect_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative '../numeric/fixtures/classes'
+
+describe "Complex#inspect" do
+ it "returns (${real}+${image}i) for positive imaginary parts" do
+ Complex(1).inspect.should == "(1+0i)"
+ Complex(7).inspect.should == "(7+0i)"
+ Complex(-1, 4).inspect.should == "(-1+4i)"
+ Complex(-7, 6.7).inspect.should == "(-7+6.7i)"
+ end
+
+ it "returns (${real}-${image}i) for negative imaginary parts" do
+ Complex(0, -1).inspect.should == "(0-1i)"
+ Complex(-1, -4).inspect.should == "(-1-4i)"
+ Complex(-7, -6.7).inspect.should == "(-7-6.7i)"
+ end
+
+ it "calls #inspect on real and imaginary" do
+ real = NumericSpecs::Subclass.new
+ # + because of https://bugs.ruby-lang.org/issues/20337
+ real.should_receive(:inspect).and_return(+"1")
+ imaginary = NumericSpecs::Subclass.new
+ imaginary.should_receive(:inspect).and_return("2")
+ imaginary.should_receive(:<).any_number_of_times.and_return(false)
+ Complex(real, imaginary).inspect.should == "(1+2i)"
+ end
+
+ it "adds an `*' before the `i' if the last character of the imaginary part is not numeric" do
+ real = NumericSpecs::Subclass.new
+ # + because of https://bugs.ruby-lang.org/issues/20337
+ real.should_receive(:inspect).and_return(+"(1)")
+ imaginary = NumericSpecs::Subclass.new
+ imaginary.should_receive(:inspect).and_return("(2)")
+ imaginary.should_receive(:<).any_number_of_times.and_return(false)
+ Complex(real, imaginary).inspect.should == "((1)+(2)*i)"
+ end
+end
diff --git a/spec/ruby/core/complex/integer_spec.rb b/spec/ruby/core/complex/integer_spec.rb
new file mode 100644
index 0000000000..559bfbccfd
--- /dev/null
+++ b/spec/ruby/core/complex/integer_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Complex#integer?" do
+ it "returns false for a Complex with no imaginary part" do
+ Complex(20).integer?.should == false
+ end
+
+ it "returns false for a Complex with an imaginary part" do
+ Complex(20,3).integer?.should == false
+ end
+end
diff --git a/spec/ruby/core/complex/magnitude_spec.rb b/spec/ruby/core/complex/magnitude_spec.rb
new file mode 100644
index 0000000000..6341b4eec8
--- /dev/null
+++ b/spec/ruby/core/complex/magnitude_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Complex#magnitude" do
+ it "is an alias of Complex#abs" do
+ Complex.instance_method(:magnitude).should == Complex.instance_method(:abs)
+ end
+end
diff --git a/spec/ruby/core/complex/marshal_dump_spec.rb b/spec/ruby/core/complex/marshal_dump_spec.rb
new file mode 100644
index 0000000000..201d55e9e5
--- /dev/null
+++ b/spec/ruby/core/complex/marshal_dump_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Complex#marshal_dump" do
+ it "is a private method" do
+ Complex.private_instance_methods(false).should.include?(:marshal_dump)
+ end
+
+ it "dumps real and imaginary parts" do
+ Complex(1, 2).send(:marshal_dump).should == [1, 2]
+ end
+end
diff --git a/spec/ruby/core/complex/minus_spec.rb b/spec/ruby/core/complex/minus_spec.rb
new file mode 100644
index 0000000000..7c104ce784
--- /dev/null
+++ b/spec/ruby/core/complex/minus_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "Complex#-" do
+ describe "with Complex" do
+ it "subtracts both the real and imaginary components" do
+ (Complex(1, 2) - Complex(10, 20)).should == Complex(1 - 10, 2 - 20)
+ (Complex(1.5, 2.1) - Complex(100.2, -30.3)).should == Complex(1.5 - 100.2, 2.1 - (-30.3))
+ end
+ end
+
+ describe "with Integer" do
+ it "subtracts the real number from the real component of self" do
+ (Complex(1, 2) - 50).should == Complex(-49, 2)
+ (Complex(1, 2) - 50.5).should == Complex(-49.5, 2)
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([2, 5])
+ (value - obj).should == 2 - 5
+ end
+ end
+
+ describe "passed Numeric which responds to #real? with true" do
+ it "coerces the passed argument to the type of the real part and subtracts the resulting elements" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).and_return(true)
+ n.should_receive(:coerce).with(1).and_return([1, 4])
+ (Complex(1, 2) - n).should == Complex(-3, 2)
+ end
+ end
+
+ describe "passed Numeric which responds to #real? with false" do
+ it "coerces the passed argument to Complex and subtracts the resulting elements" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).and_return(false)
+ n.should_receive(:coerce).with(Complex(1, 2)).and_return([Complex(1, 2), Complex(3, 4)])
+ (Complex(1, 2) - n).should == Complex(-2, -2)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/multiply_spec.rb b/spec/ruby/core/complex/multiply_spec.rb
new file mode 100644
index 0000000000..35bf7c8455
--- /dev/null
+++ b/spec/ruby/core/complex/multiply_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Complex#*" do
+ describe "with Complex" do
+ it "multiplies according to the usual rule for complex numbers: (a + bi) * (c + di) = ac - bd + (ad + bc)i" do
+ (Complex(1, 2) * Complex(10, 20)).should == Complex((1 * 10) - (2 * 20), (1 * 20) + (2 * 10))
+ (Complex(1.5, 2.1) * Complex(100.2, -30.3)).should == Complex((1.5 * 100.2) - (2.1 * -30.3), (1.5 * -30.3) + (2.1 * 100.2))
+ end
+ end
+
+ describe "with Integer" do
+ it "multiplies both parts of self by the given Integer" do
+ (Complex(3, 2) * 50).should == Complex(150, 100)
+ (Complex(-3, 2) * 50.5).should == Complex(-151.5, 101)
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([2, 5])
+ (value * obj).should == 2 * 5
+ end
+ end
+
+ describe "with a Numeric which responds to #real? with true" do
+ it "multiples both parts of self by other" do
+ other = mock_numeric('other')
+ real = mock_numeric('real')
+ imag = mock_numeric('imag')
+ other.should_receive(:real?).and_return(true)
+ real.should_receive(:*).with(other).and_return(1)
+ imag.should_receive(:*).with(other).and_return(2)
+ (Complex(real, imag) * other).should == Complex(1, 2)
+ end
+
+ describe "with a Numeric which responds to #real? with false" do
+ it "coerces the passed argument to Complex and multiplies the resulting elements" do
+ complex = Complex(3, 0)
+ other = mock_numeric('other')
+ other.should_receive(:real?).any_number_of_times.and_return(false)
+ other.should_receive(:coerce).with(complex).and_return([5, 2])
+ (complex * other).should == 10
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/negative_spec.rb b/spec/ruby/core/complex/negative_spec.rb
new file mode 100644
index 0000000000..566975b8e1
--- /dev/null
+++ b/spec/ruby/core/complex/negative_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Complex#negative?" do
+ it "is undefined" do
+ c = Complex(1)
+
+ c.methods.should_not.include?(:negative?)
+
+ -> {
+ c.negative?
+ }.should.raise(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/complex/numerator_spec.rb b/spec/ruby/core/complex/numerator_spec.rb
new file mode 100644
index 0000000000..7ab66e6a61
--- /dev/null
+++ b/spec/ruby/core/complex/numerator_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Complex#numerator" do
+ it "returns self's numerator" do
+ Complex(2).numerator.should == Complex(2)
+ Complex(3, 4).numerator.should == Complex(3, 4)
+
+ Complex(Rational(3, 4), Rational(3, 4)).numerator.should == Complex(3, 3)
+ Complex(Rational(7, 4), Rational(8, 4)).numerator.should == Complex(7, 8)
+
+ Complex(Rational(7, 8), Rational(8, 4)).numerator.should == Complex(7, 16)
+ Complex(Rational(7, 4), Rational(8, 8)).numerator.should == Complex(7, 4)
+
+ # NOTE:
+ # Bug? - Fails with a MethodMissingError
+ # (undefined method `denominator' for 3.5:Float)
+ # Complex(3.5, 3.7).numerator
+ end
+end
diff --git a/spec/ruby/core/complex/phase_spec.rb b/spec/ruby/core/complex/phase_spec.rb
new file mode 100644
index 0000000000..2ab90989e1
--- /dev/null
+++ b/spec/ruby/core/complex/phase_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Complex#phase" do
+ it "is an alias of Complex#arg" do
+ Complex.instance_method(:phase).should == Complex.instance_method(:arg)
+ end
+end
diff --git a/spec/ruby/core/complex/plus_spec.rb b/spec/ruby/core/complex/plus_spec.rb
new file mode 100644
index 0000000000..2056ca786c
--- /dev/null
+++ b/spec/ruby/core/complex/plus_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "Complex#+" do
+ describe "with Complex" do
+ it "adds both the real and imaginary components" do
+ (Complex(1, 2) + Complex(10, 20)).should == Complex(1 + 10, 2 + 20)
+ (Complex(1.5, 2.1) + Complex(100.2, -30.3)).should == Complex(1.5 + 100.2, 2.1 + (-30.3))
+ end
+ end
+
+ describe "with Integer" do
+ it "adds the real number to the real component of self" do
+ (Complex(1, 2) + 50).should == Complex(51, 2)
+ (Complex(1, 2) + 50.5).should == Complex(51.5, 2)
+ end
+ end
+
+ describe "with Object" do
+ it "tries to coerce self into other" do
+ value = Complex(3, 9)
+
+ obj = mock("Object")
+ obj.should_receive(:coerce).with(value).and_return([2, 5])
+ (value + obj).should == 2 + 5
+ end
+ end
+
+ describe "passed Numeric which responds to #real? with true" do
+ it "coerces the passed argument to the type of the real part and adds the resulting elements" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).and_return(true)
+ n.should_receive(:coerce).with(1).and_return([1, 4])
+ (Complex(1, 2) + n).should == Complex(5, 2)
+ end
+ end
+
+ describe "passed Numeric which responds to #real? with false" do
+ it "coerces the passed argument to Complex and adds the resulting elements" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).and_return(false)
+ n.should_receive(:coerce).with(Complex(1, 2)).and_return([Complex(1, 2), Complex(3, 4)])
+ (Complex(1, 2) + n).should == Complex(4, 6)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/polar_spec.rb b/spec/ruby/core/complex/polar_spec.rb
new file mode 100644
index 0000000000..824211fcd0
--- /dev/null
+++ b/spec/ruby/core/complex/polar_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Complex.polar" do
+ it "returns a complex number in terms of radius and angle" do
+ Complex.polar(50, 60).should be_close(Complex(-47.6206490207578, -15.2405310551108), TOLERANCE)
+ Complex.polar(-10, -20).should be_close(Complex(-4.08082061813392, 9.12945250727628), TOLERANCE)
+ end
+
+ it "raises a TypeError when given non real arguments" do
+ ->{ Complex.polar(nil) }.should.raise(TypeError)
+ ->{ Complex.polar(nil, nil) }.should.raise(TypeError)
+ end
+
+ 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 == true
+ a.imag.real?.should == 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 == true
+ b.imag.real?.should == true
+ end
+end
+
+describe "Complex#polar" do
+ it "returns the absolute value and the argument" do
+ a = Complex(3, 4)
+ a.polar.size.should == 2
+ a.polar.first.should == 5.0
+ a.polar.last.should be_close(0.927295218001612, TOLERANCE)
+
+ b = Complex(-3.5, 4.7)
+ b.polar.size.should == 2
+ b.polar.first.should be_close(5.86003412959345, TOLERANCE)
+ b.polar.last.should be_close(2.21088447955664, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/complex/positive_spec.rb b/spec/ruby/core/complex/positive_spec.rb
new file mode 100644
index 0000000000..d2fb256538
--- /dev/null
+++ b/spec/ruby/core/complex/positive_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Complex#positive?" do
+ it "is undefined" do
+ c = Complex(1)
+
+ c.methods.should_not.include?(:positive?)
+
+ -> {
+ c.positive?
+ }.should.raise(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/complex/quo_spec.rb b/spec/ruby/core/complex/quo_spec.rb
new file mode 100644
index 0000000000..be0a44d532
--- /dev/null
+++ b/spec/ruby/core/complex/quo_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Complex#quo" do
+ it "is an alias of Complex#/" do
+ Complex.instance_method(:quo).should == Complex.instance_method(:/)
+ end
+end
diff --git a/spec/ruby/core/complex/rationalize_spec.rb b/spec/ruby/core/complex/rationalize_spec.rb
new file mode 100644
index 0000000000..d49bb52def
--- /dev/null
+++ b/spec/ruby/core/complex/rationalize_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Complex#rationalize" do
+ it "raises RangeError if self has non-zero imaginary part" do
+ -> { Complex(1,5).rationalize }.should.raise(RangeError)
+ end
+
+ it "raises RangeError if self has 0.0 imaginary part" do
+ -> { Complex(1,0.0).rationalize }.should.raise(RangeError)
+ end
+
+ it "returns a Rational if self has zero imaginary part" do
+ Complex(1,0).rationalize.should == Rational(1,1)
+ Complex(2<<63+5).rationalize.should == Rational(2<<63+5,1)
+ end
+
+ it "sends #rationalize to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:rationalize).with(0.1).and_return(:result)
+ Complex(real, 0).rationalize(0.1).should == :result
+ end
+
+ it "ignores a single argument" do
+ Complex(1,0).rationalize(0.1).should == Rational(1,1)
+ end
+
+ it "raises ArgumentError when passed more than one argument" do
+ -> { Complex(1,0).rationalize(0.1, 0.1) }.should.raise(ArgumentError)
+ -> { Complex(1,0).rationalize(0.1, 0.1, 2) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/complex/real_spec.rb b/spec/ruby/core/complex/real_spec.rb
new file mode 100644
index 0000000000..41734b23f0
--- /dev/null
+++ b/spec/ruby/core/complex/real_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+describe "Complex#real" do
+ it "returns the real part of self" do
+ Complex(1, 0).real.should == 1
+ Complex(2, 1).real.should == 2
+ Complex(6.7, 8.9).real.should == 6.7
+ Complex(bignum_value, 3).real.should == bignum_value
+ end
+end
+
+describe "Complex#real?" do
+ it "returns false if there is an imaginary part" do
+ Complex(2,3).real?.should == false
+ end
+
+ it "returns false if there is not an imaginary part" do
+ Complex(2).real?.should == false
+ end
+
+ it "returns false if the real part is Infinity" do
+ Complex(infinity_value).real?.should == false
+ end
+
+ it "returns false if the real part is NaN" do
+ Complex(nan_value).real?.should == false
+ end
+end
diff --git a/spec/ruby/core/complex/rect_spec.rb b/spec/ruby/core/complex/rect_spec.rb
new file mode 100644
index 0000000000..72f2bf9930
--- /dev/null
+++ b/spec/ruby/core/complex/rect_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Complex#rect" do
+ it "is an alias of Complex#rectangular" do
+ Complex.instance_method(:rect).should == Complex.instance_method(:rectangular)
+ end
+end
+
+describe "Complex.rect" do
+ it "is an alias of Complex#rectangular" do
+ Complex.method(:rect).should == Complex.method(:rectangular)
+ end
+end
diff --git a/spec/ruby/core/complex/rectangular_spec.rb b/spec/ruby/core/complex/rectangular_spec.rb
new file mode 100644
index 0000000000..7789bf925e
--- /dev/null
+++ b/spec/ruby/core/complex/rectangular_spec.rb
@@ -0,0 +1,114 @@
+require_relative '../../spec_helper'
+
+describe "Complex#rectangular" do
+ before :each do
+ @numbers = [
+ Complex(1),
+ Complex(0, 20),
+ Complex(0, 0),
+ Complex(0.0),
+ Complex(9999999**99),
+ Complex(-20),
+ Complex.polar(76, 10)
+ ]
+ end
+
+ it "returns an Array" do
+ @numbers.each do |number|
+ number.rectangular.should.instance_of?(Array)
+ end
+ end
+
+ it "returns a two-element Array" do
+ @numbers.each do |number|
+ number.rectangular.size.should == 2
+ end
+ end
+
+ it "returns the real part of self as the first element" do
+ @numbers.each do |number|
+ number.rectangular.first.should == number.real
+ end
+ end
+
+ it "returns the imaginary part of self as the last element" do
+ @numbers.each do |number|
+ number.rectangular.last.should == number.imaginary
+ end
+ end
+
+ it "raises an ArgumentError if given any arguments" do
+ @numbers.each do |number|
+ -> { number.rectangular(number) }.should.raise(ArgumentError)
+ end
+ end
+end
+
+describe "Complex.rectangular" do
+ describe "passed a Numeric n which responds to #real? with true" do
+ it "returns a Complex with real part n and imaginary part 0" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).any_number_of_times.and_return(true)
+ result = Complex.rectangular(n)
+ result.real.should == n
+ result.imag.should == 0
+ end
+ end
+
+ describe "passed a Numeric which responds to #real? with false" do
+ it "raises TypeError" do
+ n = mock_numeric('n')
+ n.should_receive(:real?).any_number_of_times.and_return(false)
+ -> { Complex.rectangular(n) }.should.raise(TypeError)
+ end
+ end
+
+ describe "passed Numerics n1 and n2 and at least one responds to #real? with false" do
+ [[false, false], [false, true], [true, false]].each do |r1, r2|
+ it "raises TypeError" do
+ n1 = mock_numeric('n1')
+ n2 = mock_numeric('n2')
+ n1.should_receive(:real?).any_number_of_times.and_return(r1)
+ n2.should_receive(:real?).any_number_of_times.and_return(r2)
+ -> { Complex.rectangular(n1, n2) }.should.raise(TypeError)
+ end
+ end
+ end
+
+ describe "passed Numerics n1 and n2 and both respond to #real? with true" do
+ it "returns a Complex with real part n1 and imaginary part n2" do
+ n1 = mock_numeric('n1')
+ n2 = mock_numeric('n2')
+ n1.should_receive(:real?).any_number_of_times.and_return(true)
+ n2.should_receive(:real?).any_number_of_times.and_return(true)
+ result = Complex.rectangular(n1, n2)
+ result.real.should == n1
+ result.imag.should == n2
+ end
+ end
+
+ describe "when passed a Complex" do
+ it "raises a TypeError when the imaginary part is not zero" do
+ -> {
+ Complex.rectangular(1.0+1i, 2)
+ }.should.raise(TypeError)
+
+ -> {
+ Complex.rectangular(1.0, 2i)
+ }.should.raise(TypeError)
+ end
+
+ it "ignores the imaginary part if it is zero" do
+ result = Complex.rectangular(1.0+0i, 2+0.0i)
+ result.real.should == 1.0
+ result.imag.should == 2
+ end
+ end
+
+ describe "passed a non-Numeric" do
+ it "raises TypeError" do
+ -> { Complex.rectangular(:sym) }.should.raise(TypeError)
+ -> { Complex.rectangular(0, :sym) }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/to_c_spec.rb b/spec/ruby/core/complex/to_c_spec.rb
new file mode 100644
index 0000000000..cd7195556c
--- /dev/null
+++ b/spec/ruby/core/complex/to_c_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_c" do
+ it "returns self" do
+ value = Complex(1, 5)
+ value.to_c.should.equal?(value)
+ end
+
+ it 'returns the same value' do
+ Complex(1, 5).to_c.should == Complex(1, 5)
+ end
+end
diff --git a/spec/ruby/core/complex/to_f_spec.rb b/spec/ruby/core/complex/to_f_spec.rb
new file mode 100644
index 0000000000..9f3265cdb9
--- /dev/null
+++ b/spec/ruby/core/complex/to_f_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_f" do
+ describe "when the imaginary part is Integer 0" do
+ it "returns the result of sending #to_f to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_f).and_return(:f)
+ Complex(real, 0).to_f.should == :f
+ end
+ end
+
+ describe "when the imaginary part is Rational 0" do
+ it "returns the result of sending #to_f to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_f).and_return(:f)
+ Complex(real, Rational(0)).to_f.should == :f
+ end
+ end
+
+ describe "when the imaginary part responds to #== 0 with true" do
+ it "returns the result of sending #to_f to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_f).and_return(:f)
+ imag = mock_numeric('imag')
+ imag.should_receive(:==).with(0).any_number_of_times.and_return(true)
+ Complex(real, imag).to_f.should == :f
+ end
+ end
+
+ describe "when the imaginary part is non-zero" do
+ it "raises RangeError" do
+ -> { Complex(0, 1).to_f }.should.raise(RangeError)
+ end
+ end
+
+ describe "when the imaginary part is Float 0.0" do
+ it "raises RangeError" do
+ -> { Complex(0, 0.0).to_f }.should.raise(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/to_i_spec.rb b/spec/ruby/core/complex/to_i_spec.rb
new file mode 100644
index 0000000000..9149ffbbaa
--- /dev/null
+++ b/spec/ruby/core/complex/to_i_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_i" do
+ describe "when the imaginary part is Integer 0" do
+ it "returns the result of sending #to_i to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_i).and_return(:i)
+ Complex(real, 0).to_i.should == :i
+ end
+ end
+
+ describe "when the imaginary part is Rational 0" do
+ it "returns the result of sending #to_i to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_i).and_return(:i)
+ Complex(real, Rational(0)).to_i.should == :i
+ end
+ end
+
+ describe "when the imaginary part responds to #== 0 with true" do
+ it "returns the result of sending #to_i to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_i).and_return(:i)
+ imag = mock_numeric('imag')
+ imag.should_receive(:==).with(0).any_number_of_times.and_return(true)
+ Complex(real, imag).to_i.should == :i
+ end
+ end
+
+ describe "when the imaginary part is non-zero" do
+ it "raises RangeError" do
+ -> { Complex(0, 1).to_i }.should.raise(RangeError)
+ end
+ end
+
+ describe "when the imaginary part is Float 0.0" do
+ it "raises RangeError" do
+ -> { Complex(0, 0.0).to_i }.should.raise(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/to_r_spec.rb b/spec/ruby/core/complex/to_r_spec.rb
new file mode 100644
index 0000000000..6587ae9e2e
--- /dev/null
+++ b/spec/ruby/core/complex/to_r_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Complex#to_r" do
+ describe "when the imaginary part is Integer 0" do
+ it "returns the result of sending #to_r to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_r).and_return(:r)
+ Complex(real, 0).to_r.should == :r
+ end
+ end
+
+ describe "when the imaginary part is Rational 0" do
+ it "returns the result of sending #to_r to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_r).and_return(:r)
+ Complex(real, Rational(0)).to_r.should == :r
+ end
+ end
+
+ describe "when the imaginary part responds to #== 0 with true" do
+ it "returns the result of sending #to_r to the real part" do
+ real = mock_numeric('real')
+ real.should_receive(:to_r).and_return(:r)
+ imag = mock_numeric('imag')
+ imag.should_receive(:==).with(0).any_number_of_times.and_return(true)
+ Complex(real, imag).to_r.should == :r
+ end
+ end
+
+ describe "when the imaginary part is non-zero" do
+ it "raises RangeError" do
+ -> { Complex(0, 1).to_r }.should.raise(RangeError)
+ end
+ end
+
+ describe "when the imaginary part is Float 0.0" do
+ ruby_version_is ''...'3.4' do
+ it "raises RangeError" do
+ -> { Complex(0, 0.0).to_r }.should.raise(RangeError)
+ end
+ end
+
+ ruby_version_is '3.4' do
+ it "returns a Rational" do
+ Complex(0, 0.0).to_r.should == 0r
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/complex/to_s_spec.rb b/spec/ruby/core/complex/to_s_spec.rb
new file mode 100644
index 0000000000..ceccffe470
--- /dev/null
+++ b/spec/ruby/core/complex/to_s_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require_relative '../numeric/fixtures/classes'
+
+describe "Complex#to_s" do
+ describe "when self's real component is 0" do
+ it "returns both the real and imaginary component even when the real is 0" do
+ Complex(0, 5).to_s.should == "0+5i"
+ Complex(0, -3.2).to_s.should == "0-3.2i"
+ end
+ end
+
+ it "returns self as String" do
+ Complex(1, 5).to_s.should == "1+5i"
+ Complex(-2.5, 1.5).to_s.should == "-2.5+1.5i"
+
+ Complex(1, -5).to_s.should == "1-5i"
+ Complex(-2.5, -1.5).to_s.should == "-2.5-1.5i"
+
+ # Guard against the Mathn library
+ guard -> { !defined?(Math.rsqrt) } do
+ Complex(1, 0).to_s.should == "1+0i"
+ Complex(1, -0).to_s.should == "1+0i"
+ end
+ end
+
+ it "returns 1+0.0i for Complex(1, 0.0)" do
+ Complex(1, 0.0).to_s.should == "1+0.0i"
+ end
+
+ it "returns 1-0.0i for Complex(1, -0.0)" do
+ Complex(1, -0.0).to_s.should == "1-0.0i"
+ end
+
+ it "returns 1+Infinity*i for Complex(1, Infinity)" do
+ Complex(1, infinity_value).to_s.should == "1+Infinity*i"
+ end
+
+ it "returns 1-Infinity*i for Complex(1, -Infinity)" do
+ Complex(1, -infinity_value).to_s.should == "1-Infinity*i"
+ end
+
+ it "returns 1+NaN*i for Complex(1, NaN)" do
+ Complex(1, nan_value).to_s.should == "1+NaN*i"
+ end
+
+ it "treats real and imaginary parts as strings" do
+ real = NumericSpecs::Subclass.new
+ # + because of https://bugs.ruby-lang.org/issues/20337
+ real.should_receive(:to_s).and_return(+"1")
+ imaginary = NumericSpecs::Subclass.new
+ imaginary.should_receive(:to_s).and_return("2")
+ imaginary.should_receive(:<).any_number_of_times.and_return(false)
+ Complex(real, imaginary).to_s.should == "1+2i"
+ end
+end
diff --git a/spec/ruby/core/complex/uminus_spec.rb b/spec/ruby/core/complex/uminus_spec.rb
new file mode 100644
index 0000000000..c0184e11de
--- /dev/null
+++ b/spec/ruby/core/complex/uminus_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Complex#-@" do
+ it "sends #-@ to the real and imaginary parts and returns a Complex with the resulting respective parts" do
+ real = mock_numeric('real')
+ imag = mock_numeric('imag')
+ real.should_receive(:-@).and_return(-1)
+ imag.should_receive(:-@).and_return(-2)
+ Complex(real, imag).send(:-@).should == Complex(-1, -2)
+ end
+end
diff --git a/spec/ruby/core/conditionvariable/broadcast_spec.rb b/spec/ruby/core/conditionvariable/broadcast_spec.rb
new file mode 100644
index 0000000000..16c7b4cc8d
--- /dev/null
+++ b/spec/ruby/core/conditionvariable/broadcast_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "ConditionVariable#broadcast" do
+ it "releases all threads waiting in line for this resource" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ threads = []
+ r1 = []
+ r2 = []
+
+ # large number to attempt to cause race conditions
+ 100.times do |i|
+ threads << Thread.new(i) do |tid|
+ m.synchronize do
+ r1 << tid
+ cv.wait(m)
+ r2 << tid
+ end
+ end
+ end
+
+ # wait for all threads to acquire the mutex the first time
+ Thread.pass until m.synchronize { r1.size == threads.size }
+ # wait until all threads are sleeping (ie waiting)
+ Thread.pass until threads.all?(&:stop?)
+
+ r2.should.empty?
+ m.synchronize do
+ cv.broadcast
+ end
+
+ threads.each {|t| t.join }
+
+ # ensure that all threads that enter cv.wait are released
+ r2.sort.should == r1.sort
+ # note that order is not specified as broadcast results in a race
+ # condition on regaining the lock m
+ end
+end
diff --git a/spec/ruby/core/conditionvariable/marshal_dump_spec.rb b/spec/ruby/core/conditionvariable/marshal_dump_spec.rb
new file mode 100644
index 0000000000..594c178e88
--- /dev/null
+++ b/spec/ruby/core/conditionvariable/marshal_dump_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "ConditionVariable#marshal_dump" do
+ it "raises a TypeError" do
+ cv = ConditionVariable.new
+ -> { cv.marshal_dump }.should.raise(TypeError, /can't dump/)
+ end
+end
diff --git a/spec/ruby/core/conditionvariable/signal_spec.rb b/spec/ruby/core/conditionvariable/signal_spec.rb
new file mode 100644
index 0000000000..3b266cf8c6
--- /dev/null
+++ b/spec/ruby/core/conditionvariable/signal_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+
+describe "ConditionVariable#signal" do
+ it "releases the first thread waiting in line for this resource" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ threads = []
+ r1 = []
+ r2 = []
+
+ # large number to attempt to cause race conditions
+ 100.times do |i|
+ threads << Thread.new(i) do |tid|
+ m.synchronize do
+ r1 << tid
+ cv.wait(m)
+ r2 << tid
+ end
+ end
+ end
+
+ # wait for all threads to acquire the mutex the first time
+ Thread.pass until m.synchronize { r1.size == threads.size }
+ # wait until all threads are sleeping (ie waiting)
+ Thread.pass until threads.all?(&:stop?)
+
+ r2.should.empty?
+ 100.times do |i|
+ m.synchronize do
+ cv.signal
+ end
+ Thread.pass until r2.size == i+1
+ end
+
+ threads.each {|t| t.join }
+
+ # ensure that all the threads that went into the cv.wait are
+ # released in the same order
+ r2.should == r1
+ end
+
+ it "allows control to be passed between a pair of threads" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ repeats = 100
+ in_synchronize = false
+
+ t1 = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ repeats.times do
+ cv.wait(m)
+ cv.signal
+ end
+ end
+ end
+
+ # Make sure t1 is waiting for a signal before launching t2.
+ Thread.pass until in_synchronize
+ Thread.pass until t1.stop?
+
+ t2 = Thread.new do
+ m.synchronize do
+ repeats.times do
+ cv.signal
+ cv.wait(m)
+ end
+ end
+ end
+
+ # Check that both threads terminated without exception
+ t1.join
+ t2.join
+ m.should_not.locked?
+ end
+end
diff --git a/spec/ruby/core/conditionvariable/wait_spec.rb b/spec/ruby/core/conditionvariable/wait_spec.rb
new file mode 100644
index 0000000000..1af53a15a2
--- /dev/null
+++ b/spec/ruby/core/conditionvariable/wait_spec.rb
@@ -0,0 +1,174 @@
+require_relative '../../spec_helper'
+
+describe "ConditionVariable#wait" do
+ it "calls #sleep on the given object" do
+ o = Object.new
+ o.should_receive(:sleep).with(1234)
+
+ cv = ConditionVariable.new
+
+ cv.wait(o, 1234)
+ end
+
+ it "can be woken up by ConditionVariable#signal" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ cv.wait(m)
+ end
+ :success
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ m.synchronize { cv.signal }
+ th.value.should == :success
+ end
+
+ it "can be interrupted by Thread#run" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ cv.wait(m)
+ end
+ :success
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ th.run
+ th.value.should == :success
+ end
+
+ it "can be interrupted by Thread#wakeup" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ cv.wait(m)
+ end
+ :success
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ th.wakeup
+ th.value.should == :success
+ end
+
+ it "reacquires the lock even if the thread is killed" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+ owned = nil
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ begin
+ cv.wait(m)
+ ensure
+ owned = m.owned?
+ $stderr.puts "\nThe Thread doesn't own the Mutex!" unless owned
+ end
+ end
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ th.kill
+ th.join
+
+ owned.should == true
+ end
+
+ it "reacquires the lock even if the thread is killed after being signaled" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ in_synchronize = false
+ owned = nil
+
+ th = Thread.new do
+ m.synchronize do
+ in_synchronize = true
+ begin
+ cv.wait(m)
+ ensure
+ owned = m.owned?
+ $stderr.puts "\nThe Thread doesn't own the Mutex!" unless owned
+ end
+ end
+ end
+
+ # wait for m to acquire the mutex
+ Thread.pass until in_synchronize
+ # wait until th is sleeping (ie waiting)
+ Thread.pass until th.stop?
+
+ m.synchronize {
+ cv.signal
+ # Wait that the thread is blocked on acquiring the Mutex
+ sleep 0.001
+ # Kill the thread, yet the thread should first acquire the Mutex before going on
+ th.kill
+ }
+
+ th.join
+ owned.should == true
+ end
+
+ it "supports multiple Threads waiting on the same ConditionVariable and Mutex" do
+ m = Mutex.new
+ cv = ConditionVariable.new
+ n_threads = 4
+ events = []
+
+ threads = n_threads.times.map {
+ Thread.new {
+ m.synchronize {
+ events << :t_in_synchronize
+ cv.wait(m)
+ }
+ }
+ }
+
+ Thread.pass until m.synchronize { events.size } == n_threads
+ Thread.pass until threads.any?(&:stop?)
+ m.synchronize do
+ threads.each { |t|
+ # Cause interactions with the waiting threads.
+ # On TruffleRuby, this causes a safepoint which has interesting
+ # interactions with the ConditionVariable.
+ bt = t.backtrace
+ bt.should.is_a?(Array)
+ bt.size.should >= 2
+ }
+ end
+
+ cv.broadcast
+ threads.each(&:join)
+ end
+end
diff --git a/spec/ruby/core/data/constants_spec.rb b/spec/ruby/core/data/constants_spec.rb
new file mode 100644
index 0000000000..ad0b1ddea7
--- /dev/null
+++ b/spec/ruby/core/data/constants_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Data" do
+ it "is a new constant" do
+ Data.superclass.should == Object
+ 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..7e81f966ea
--- /dev/null
+++ b/spec/ruby/core/data/deconstruct_keys_spec.rb
@@ -0,0 +1,110 @@
+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(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 "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 "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
+
+ ruby_version_is "4.0" do # https://bugs.ruby-lang.org/issues/21844
+ it "tries to convert a key with #to_str if index is not a String nor a Symbol, but responds to #to_str" do
+ klass = Data.define(:x, :y)
+ d = klass.new(1, 2)
+
+ key = mock("to_str")
+ key.should_receive(:to_str).and_return("y")
+
+ d.deconstruct_keys([key]).should == { "y" => 2 }
+ end
+
+ it "raise an error on argument position number" do
+ klass = Data.define(:x, :y)
+ d = klass.new(1, 2)
+
+ -> {
+ d.deconstruct_keys([0, 1])
+ }.should.raise(TypeError, "0 is not a symbol nor a string")
+ end
+
+ it "raises a TypeError if the conversion with #to_str does not return a String" do
+ klass = Data.define(:x, :y)
+ d = klass.new(1, 2)
+
+ key = mock("to_str")
+ key.should_receive(:to_str).and_return(0)
+
+ -> {
+ d.deconstruct_keys([key])
+ }.should raise_consistent_error(TypeError, /can't convert MockObject into String/)
+ end
+
+ it "raises TypeError if index is not a Symbol and not convertible to String" do
+ klass = Data.define(:x, :y)
+ d = klass.new(1, 2)
+
+ -> {
+ d.deconstruct_keys([0, []])
+ }.should.raise(TypeError, "0 is not a symbol nor a string")
+ end
+ 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(TypeError, /expected Array or nil/)
+ -> { d.deconstruct_keys(1) }.should.raise(TypeError, /expected Array or nil/)
+ -> { d.deconstruct_keys(:x) }.should.raise(TypeError, /expected Array or nil/)
+ -> { d.deconstruct_keys({}) }.should.raise(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
new file mode 100644
index 0000000000..c0b4671e39
--- /dev/null
+++ b/spec/ruby/core/data/define_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+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 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 block" do
+ movie = Data.define(:title, :year) do
+ def title_with_year
+ "#{title} (#{year})"
+ end
+ 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
new file mode 100644
index 0000000000..147293ee45
--- /dev/null
+++ b/spec/ruby/core/data/fixtures/classes.rb
@@ -0,0 +1,41 @@
+module DataSpecs
+ if Data.respond_to?(:define)
+ Measure = Data.define(:amount, :unit)
+ Single = Data.define(:value)
+
+ class MeasureWithOverriddenName < Measure
+ def self.name
+ "A"
+ end
+ end
+
+ class SingleWithOverriddenName < Single
+ 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..bab146c92e
--- /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.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
new file mode 100644
index 0000000000..0320ca880c
--- /dev/null
+++ b/spec/ruby/core/data/initialize_spec.rb
@@ -0,0 +1,204 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Data#initialize" do
+ context "with no members" do
+ ruby_bug "#21819", ""..."4.0.1" do
+ it "is frozen" do
+ data = Data.define
+
+ data.new.should.frozen?
+ end
+ end
+ end
+
+ it "accepts positional arguments" do
+ data = DataSpecs::Measure.new(42, "km")
+
+ data.amount.should == 42
+ data.unit.should == "km"
+ end
+
+ it "accepts alternative positional arguments" do
+ data = DataSpecs::Measure[42, "km"]
+
+ data.amount.should == 42
+ data.unit.should == "km"
+ end
+
+ it "accepts keyword arguments" do
+ data = DataSpecs::Measure.new(amount: 42, unit: "km")
+
+ data.amount.should == 42
+ data.unit.should == "km"
+ end
+
+ it "accepts alternative keyword arguments" do
+ data = DataSpecs::Measure[amount: 42, unit: "km"]
+
+ data.amount.should == 42
+ data.unit.should == "km"
+ end
+
+ it "accepts String keyword arguments" do
+ data = DataSpecs::Measure.new("amount" => 42, "unit" => "km")
+
+ data.amount.should == 42
+ data.unit.should == "km"
+ end
+
+ it "accepts the last entry when a keyword is given as both String and Symbol" do
+ data = DataSpecs::Single.new("value" => -1, value: 42)
+
+ data.value.should == 42
+ end
+
+ it "accepts positional arguments with empty keyword arguments" do
+ data = DataSpecs::Single.new(42, **{})
+
+ data.value.should == 42
+
+ data = DataSpecs::Measure.new(42, "km", **{})
+
+ data.amount.should == 42
+ data.unit.should == "km"
+ end
+
+ it "raises ArgumentError if no arguments are given" do
+ -> {
+ DataSpecs::Measure.new
+ }.should.raise(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(ArgumentError) { |e|
+ e.message.should.include?("missing keyword: :amount")
+ }
+ end
+
+ ruby_version_is "4.0" do # https://bugs.ruby-lang.org/issues/21844
+ it "raises ArgumentError if at least one argument is missing and other is provided as both String and Symbol" do
+ -> {
+ DataSpecs::Measure.new(unit: "km", "unit" => "km")
+ }.should.raise(ArgumentError) { |e|
+ e.message.should.include?("missing keyword: :amount")
+ }
+ end
+ end
+
+ it "raises ArgumentError if unknown keyword is given" do
+ -> {
+ DataSpecs::Measure.new(amount: 42, unit: "km", system: "metric")
+ }.should.raise(ArgumentError) { |e|
+ e.message.should.include?("unknown keyword: :system")
+ }
+ end
+
+ ruby_version_is "4.0" do # https://bugs.ruby-lang.org/issues/21844
+ it "raises ArgumentError if unknown keyword is given which is convertable to String" do
+ key = mock("to_str")
+ key.should_receive(:to_str).and_return("system")
+
+ -> {
+ DataSpecs::Measure.new(amount: 42, unit: "km", key => "metric")
+ }.should.raise(ArgumentError) { |e|
+ e.message.should.include?('unknown keyword: "system"')
+ }
+ end
+
+ it "raises TypeError when the keyword is not convertable to String" do
+ -> {
+ DataSpecs::Measure.new(1 => 2)
+ }.should.raise(TypeError) { |e|
+ e.message.should == "1 is not a symbol nor a string"
+ }
+ end
+
+ it "raises TypeError if the conversion with #to_str does not return a String" do
+ klass = Data.define(:x, :y)
+
+ key = mock("to_str")
+ key.should_receive(:to_str).and_return(0)
+
+ -> {
+ klass.new(key => 2)
+ }.should raise_consistent_error(TypeError, /can't convert MockObject into String/)
+ end
+ 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
+ end
+
+ it "can be overridden" do
+ ScratchPad.record []
+
+ measure_class = Data.define(:amount, :unit) do
+ def initialize(*, **)
+ super
+ ScratchPad << :initialize
+ end
+ end
+
+ 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
+
+ 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 "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 "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 "accepts positional arguments with empty keyword arguments" do
+ data = DataSpecs::SingleWithOverriddenName.new(42, **{})
+
+ data.value.should == 42
+
+ data = DataSpecs::MeasureWithOverriddenName.new(42, "km", **{})
+
+ data.amount.should == 42
+ data.unit.should == "km"
+ end
+
+ # See https://github.com/ruby/psych/pull/765
+ it "can be deserialized by calling Data.instance_method(:initialize)" do
+ d1 = DataSpecs::Area.new(width: 2, height: 3)
+ d1.area.should == 6
+
+ d2 = DataSpecs::Area.allocate
+ Data.instance_method(:initialize).bind_call(d2, **d1.to_h)
+ d2.should == d1
+ end
+ end
+end
diff --git a/spec/ruby/core/data/inspect_spec.rb b/spec/ruby/core/data/inspect_spec.rb
new file mode 100644
index 0000000000..6c97a719e3
--- /dev/null
+++ b/spec/ruby/core/data/inspect_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Data#inspect" do
+ it "returns a string representation showing members and values" do
+ a = DataSpecs::Measure.new(42, "km")
+ a.inspect.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("").inspect.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("").inspect.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("").inspect.should == '#<data a="">'
+ end
+
+ it "does not call #name method" do
+ struct = DataSpecs::MeasureWithOverriddenName.new(42, "km")
+ struct.inspect.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.inspect.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.inspect.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.inspect.should =~ /#<data amount=42, unit=#<data #<Class:0x.+?>:\.\.\.>>/
+ end
+ end
+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/to_h_spec.rb b/spec/ruby/core/data/to_h_spec.rb
new file mode 100644
index 0000000000..41925cf3b2
--- /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(ArgumentError, /element has wrong array length/)
+
+ -> do
+ data.to_h { |k, v| [k] }
+ end.should.raise(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(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(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..a552e4659b
--- /dev/null
+++ b/spec/ruby/core/data/to_s_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Data#to_s" do
+ it "is an alias of Data#inspect" do
+ a = DataSpecs::Measure.new(42, "km")
+ a.method(:to_s).should == a.method(:inspect)
+ end
+end
diff --git a/spec/ruby/core/data/with_spec.rb b/spec/ruby/core/data/with_spec.rb
new file mode 100644
index 0000000000..b74df185c7
--- /dev/null
+++ b/spec/ruby/core/data/with_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Data#with" do
+ it "returns self if given no arguments" do
+ data = DataSpecs::Measure.new(amount: 42, unit: "km")
+ data = data.with.should.equal?(data)
+ end
+
+ it "accepts keyword arguments" do
+ data = DataSpecs::Measure.new(amount: 42, unit: "km")
+ data = data.with(amount: 4, unit: "m")
+
+ data.amount.should == 4
+ data.unit.should == "m"
+ end
+
+ it "accepts String keyword arguments" do
+ data = DataSpecs::Measure.new(amount: 42, unit: "km")
+ data = data.with("amount" => 4, "unit" => "m")
+
+ data.amount.should == 4
+ data.unit.should == "m"
+ end
+
+ it "raises ArgumentError if no keyword arguments are given" do
+ data = DataSpecs::Measure.new(amount: 42, unit: "km")
+
+ -> {
+ data.with(4, "m")
+ }.should.raise(ArgumentError, "wrong number of arguments (given 2, expected 0)")
+ end
+
+ it "does not depend on the Data.new method" do
+ subclass = Class.new(DataSpecs::Measure)
+ data = subclass.new(amount: 42, unit: "km")
+
+ def subclass.new(*)
+ raise "Data.new is called"
+ end
+
+ data_copy = data.with(unit: "m")
+ data_copy.amount.should == 42
+ data_copy.unit.should == "m"
+ end
+
+ it "calls #initialize" do
+ data = DataSpecs::DataWithOverriddenInitialize.new(42, "m")
+ ScratchPad.clear
+
+ data.with(amount: 0)
+
+ ScratchPad.recorded.should == [:initialize, [], {amount: 0, unit: "m"}]
+ end
+end
diff --git a/spec/ruby/core/dir/chdir_spec.rb b/spec/ruby/core/dir/chdir_spec.rb
new file mode 100644
index 0000000000..2dc598e2a9
--- /dev/null
+++ b/spec/ruby/core/dir/chdir_spec.rb
@@ -0,0 +1,218 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+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 "defaults to $HOME with no arguments" do
+ skip "$HOME not valid directory" unless ENV['HOME'] && File.directory?(ENV['HOME'])
+
+ 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
+ Dir.chdir DirSpecs.mock_dir
+ Dir.pwd.should == DirSpecs.mock_dir
+ end
+
+ it "returns 0 when successfully changing directory" do
+ Dir.chdir(@original).should == 0
+ end
+
+ it "calls #to_str on the argument if it's not a String" do
+ obj = mock('path')
+ obj.should_receive(:to_str).and_return(Dir.pwd)
+ Dir.chdir(obj)
+ end
+
+ it "calls #to_str on the argument if it's not a String and a block is given" do
+ obj = mock('path')
+ obj.should_receive(:to_str).and_return(Dir.pwd)
+ Dir.chdir(obj) { }
+ end
+
+ it "calls #to_path on the argument if it's not a String" do
+ obj = mock('path')
+ obj.should_receive(:to_path).and_return(Dir.pwd)
+ Dir.chdir(obj)
+ end
+
+ it "prefers #to_path over #to_str" do
+ obj = Class.new do
+ def to_path; Dir.pwd; end
+ def to_str; DirSpecs.mock_dir; end
+ end
+ Dir.chdir(obj.new)
+ Dir.pwd.should == @original
+ end
+
+ it "returns the value of the block when a block is given" do
+ Dir.chdir(@original) { :block_value }.should == :block_value
+ 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 = ""
+ Dir.chdir { current_dir = Dir.pwd }
+
+ Dir.chdir(ENV['HOME'])
+ home = Dir.pwd
+ current_dir.should == home
+ end
+
+ it "changes to the specified directory for the duration of the block" do
+ ar = Dir.chdir(DirSpecs.mock_dir) { |dir| [dir, Dir.pwd] }
+ ar.should == [DirSpecs.mock_dir, DirSpecs.mock_dir]
+
+ Dir.pwd.should == @original
+ end
+
+ it "raises an Errno::ENOENT if the directory does not exist" do
+ -> { Dir.chdir DirSpecs.nonexistent }.should.raise(Errno::ENOENT)
+ -> { Dir.chdir(DirSpecs.nonexistent) { } }.should.raise(Errno::ENOENT)
+ end
+
+ it "raises an Errno::ENOENT if the original directory no longer exists" do
+ dir1 = tmp('testdir1')
+ dir2 = tmp('testdir2')
+ Dir.should_not.exist?(dir1)
+ Dir.should_not.exist?(dir2)
+ Dir.mkdir dir1
+ Dir.mkdir dir2
+ begin
+ -> {
+ Dir.chdir dir1 do
+ Dir.chdir(dir2) { Dir.unlink dir1 }
+ end
+ }.should.raise(Errno::ENOENT)
+ ensure
+ Dir.unlink dir1 if Dir.exist?(dir1)
+ Dir.unlink dir2 if Dir.exist?(dir2)
+ end
+ end
+
+ it "always returns to the original directory when given a block" do
+ begin
+ Dir.chdir(DirSpecs.mock_dir) do
+ raise StandardError, "something bad happened"
+ end
+ rescue StandardError
+ end
+
+ Dir.pwd.should == @original
+ end
+end
+
+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
diff --git a/spec/ruby/core/dir/children_spec.rb b/spec/ruby/core/dir/children_spec.rb
new file mode 100644
index 0000000000..6e6da1dd44
--- /dev/null
+++ b/spec/ruby/core/dir/children_spec.rb
@@ -0,0 +1,147 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.children" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns an Array of filenames in an existing directory including dotfiles" do
+ a = Dir.children(DirSpecs.mock_dir).sort
+
+ a.should == DirSpecs.expected_paths - %w[. ..]
+
+ a = Dir.children("#{DirSpecs.mock_dir}/deeply/nested").sort
+ a.should == %w|.dotfile.ext directory|
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.children(p)
+ end
+
+ it "accepts an options Hash" do
+ a = Dir.children("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").sort
+ a.should == %w|.dotfile.ext directory|
+ end
+
+ it "returns children encoded with the filesystem encoding by default" do
+ # This spec depends on the locale not being US-ASCII because if it is, the
+ # children that are not ascii_only? will be BINARY encoded.
+ children = Dir.children(File.join(DirSpecs.mock_dir, 'special')).sort
+ encoding = Encoding.find("filesystem")
+ encoding = Encoding::BINARY if encoding == Encoding::US_ASCII
+ platform_is_not :windows do
+ children.should.include?("こんにちは.txt".dup.force_encoding(encoding))
+ end
+ children.first.encoding.should.equal?(Encoding.find("filesystem"))
+ end
+
+ it "returns children encoded with the specified encoding" do
+ dir = File.join(DirSpecs.mock_dir, 'special')
+ children = Dir.children(dir, encoding: "euc-jp").sort
+ children.first.encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it "returns children transcoded to the default internal encoding" do
+ Encoding.default_internal = Encoding::EUC_KR
+ children = Dir.children(File.join(DirSpecs.mock_dir, 'special')).sort
+ children.first.encoding.should.equal?(Encoding::EUC_KR)
+ end
+
+ it "raises a SystemCallError if called with a nonexistent directory" do
+ -> { Dir.children DirSpecs.nonexistent }.should.raise(SystemCallError)
+ end
+end
+
+describe "Dir#children" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ @dir.close if @dir
+ end
+
+ it "returns an Array of filenames in an existing directory including dotfiles" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+ a = @dir.children.sort
+ @dir.close
+
+ a.should == DirSpecs.expected_paths - %w[. ..]
+
+ @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested")
+ a = @dir.children.sort
+ a.should == %w|.dotfile.ext directory|
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8")
+ dirs = @dir.to_a.sort
+ dirs.each { |d| d.encoding.should == Encoding::UTF_8 }
+ end
+
+ it "returns children encoded with the filesystem encoding by default" do
+ # This spec depends on the locale not being US-ASCII because if it is, the
+ # children that are not ascii_only? will be BINARY encoded.
+ @dir = Dir.new(File.join(DirSpecs.mock_dir, 'special'))
+ children = @dir.children.sort
+ encoding = Encoding.find("filesystem")
+ encoding = Encoding::BINARY if encoding == Encoding::US_ASCII
+ platform_is_not :windows do
+ children.should.include?("こんにちは.txt".dup.force_encoding(encoding))
+ end
+ children.first.encoding.should.equal?(Encoding.find("filesystem"))
+ end
+
+ it "returns children encoded with the specified encoding" do
+ path = File.join(DirSpecs.mock_dir, 'special')
+ @dir = Dir.new(path, encoding: "euc-jp")
+ children = @dir.children.sort
+ children.first.encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it "returns children transcoded to the default internal encoding" do
+ Encoding.default_internal = Encoding::EUC_KR
+ @dir = Dir.new(File.join(DirSpecs.mock_dir, 'special'))
+ children = @dir.children.sort
+ children.first.encoding.should.equal?(Encoding::EUC_KR)
+ end
+
+ it "returns the same result when called repeatedly" do
+ @dir = Dir.open DirSpecs.mock_dir
+
+ a = []
+ @dir.each {|dir| a << dir}
+
+ b = []
+ @dir.each {|dir| b << dir}
+
+ a.sort.should == b.sort
+ a.sort.should == DirSpecs.expected_paths
+ end
+end
diff --git a/spec/ruby/core/dir/chroot_spec.rb b/spec/ruby/core/dir/chroot_spec.rb
new file mode 100644
index 0000000000..79ad9759b0
--- /dev/null
+++ b/spec/ruby/core/dir/chroot_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/chroot'
+
+platform_is_not :windows do
+ as_superuser do
+ describe "Dir.chroot as root" do
+ it_behaves_like :dir_chroot_as_root, :chroot
+ end
+ end
+
+ platform_is_not :cygwin, :android do
+ as_user do
+ describe "Dir.chroot as regular user" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "raises an Errno::EPERM exception if the directory exists" do
+ -> { Dir.chroot('.') }.should.raise(Errno::EPERM)
+ end
+
+ it "raises a SystemCallError if the directory doesn't exist" do
+ -> { Dir.chroot('xgwhwhsjai2222jg') }.should.raise(SystemCallError)
+ end
+
+ it "calls #to_path on non-String argument" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return('.')
+ -> { Dir.chroot(p) }.should.raise(Errno::EPERM)
+ end
+ end
+ end
+ end
+
+ platform_is :cygwin do
+ as_user do
+ describe "Dir.chroot as regular user" do
+ it_behaves_like :dir_chroot_as_root, :chroot
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/close_spec.rb b/spec/ruby/core/dir/close_spec.rb
new file mode 100644
index 0000000000..9902d98934
--- /dev/null
+++ b/spec/ruby/core/dir/close_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+describe "Dir#close" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "does not raise an IOError even if the Dir instance is closed" do
+ dir = Dir.open DirSpecs.mock_dir
+ dir.close.should == nil
+ dir.close.should == nil
+
+ platform_is_not :windows do
+ -> { dir.fileno }.should.raise(IOError, /closed directory/)
+ end
+ end
+
+ it "returns nil" do
+ dir = Dir.open DirSpecs.mock_dir
+ dir.close.should == nil
+ end
+
+ ruby_version_is ''...'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(IOError, /closed directory/)
+ -> { dir_new.fileno }.should.raise(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(Errno::EBADF, 'Bad file descriptor - closedir')
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/delete_spec.rb b/spec/ruby/core/dir/delete_spec.rb
new file mode 100644
index 0000000000..a0020788ca
--- /dev/null
+++ b/spec/ruby/core/dir/delete_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/delete'
+
+describe "Dir.delete" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_delete, :delete
+end
diff --git a/spec/ruby/core/dir/dir_spec.rb b/spec/ruby/core/dir/dir_spec.rb
new file mode 100644
index 0000000000..7d55ea26d4
--- /dev/null
+++ b/spec/ruby/core/dir/dir_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Dir" do
+ it "includes Enumerable" do
+ Dir.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/dir/each_child_spec.rb b/spec/ruby/core/dir/each_child_spec.rb
new file mode 100644
index 0000000000..4d6575df39
--- /dev/null
+++ b/spec/ruby/core/dir/each_child_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.each_child" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ dirs = Dir.each_child("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
+ dirs.each {|dir| dir.encoding.should == Encoding::UTF_8}
+ end
+
+ it "yields all names in an existing directory to the provided block" do
+ a, b = [], []
+
+ Dir.each_child(DirSpecs.mock_dir) {|f| a << f}
+ Dir.each_child("#{DirSpecs.mock_dir}/deeply/nested") {|f| b << f}
+
+ a.sort.should == DirSpecs.expected_paths - %w[. ..]
+ b.sort.should == %w|.dotfile.ext directory|
+ end
+
+ it "returns nil when successful" do
+ Dir.each_child(DirSpecs.mock_dir) {|f| f}.should == nil
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.each_child(p).to_a
+ end
+
+ it "raises a SystemCallError if passed a nonexistent directory" do
+ -> { Dir.each_child(DirSpecs.nonexistent) {} }.should.raise(SystemCallError)
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ Dir.each_child(DirSpecs.mock_dir).should.instance_of?(Enumerator)
+ Dir.each_child(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths - %w[. ..]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ Dir.each_child(DirSpecs.mock_dir).size.should == nil
+ end
+ end
+ end
+ end
+end
+
+describe "Dir#each_child" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ after :each do
+ @dir.close if @dir
+ end
+
+ it "yields all names in an existing directory to the provided block" do
+ a, b = [], []
+ @dir = Dir.new(DirSpecs.mock_dir)
+ @dir2 = Dir.new("#{DirSpecs.mock_dir}/deeply/nested")
+
+ @dir.each_child { |f| a << f }
+ @dir2.each_child { |f| b << f }
+ @dir2.close
+
+ a.sort.should == DirSpecs.expected_paths - %w|. ..|
+ b.sort.should == %w|.dotfile.ext directory|
+ end
+
+ it "returns self when successful" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+ @dir.each_child { |f| f }.should == @dir
+ end
+
+ it "returns the same result when called repeatedly" do
+ @dir = Dir.open DirSpecs.mock_dir
+
+ a = []
+ @dir.each {|dir| a << dir}
+
+ b = []
+ @dir.each {|dir| b << dir}
+
+ a.sort.should == b.sort
+ a.sort.should == DirSpecs.expected_paths
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+
+ @dir.each_child.should.instance_of?(Enumerator)
+ @dir.each_child.to_a.sort.should == DirSpecs.expected_paths - %w|. ..|
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+ @dir.each_child.size.should == nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/each_spec.rb b/spec/ruby/core/dir/each_spec.rb
new file mode 100644
index 0000000000..e997e340b1
--- /dev/null
+++ b/spec/ruby/core/dir/each_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+
+describe "Dir#each" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ before :each do
+ @dir = Dir.open DirSpecs.mock_dir
+ end
+
+ after :each do
+ @dir.close
+ end
+
+ it "yields each directory entry in succession" do
+ a = []
+ @dir.each {|dir| a << dir}
+
+ a.sort.should == DirSpecs.expected_paths
+ end
+
+ it "returns the directory which remains open" do
+ # an FS does not necessarily impose order
+ ls = Dir.entries(DirSpecs.mock_dir)
+ @dir.each {}.should == @dir
+ @dir.read.should == nil
+ @dir.rewind
+ ls.should.include?(@dir.read)
+ end
+
+ it "returns the same result when called repeatedly" do
+ a = []
+ @dir.each {|dir| a << dir}
+
+ b = []
+ @dir.each {|dir| b << dir}
+
+ a.sort.should == b.sort
+ a.sort.should == DirSpecs.expected_paths
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ @dir.each.should.instance_of?(Enumerator)
+ @dir.each.to_a.sort.should == DirSpecs.expected_paths
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @dir.each.size.should == nil
+ end
+ end
+ end
+ end
+end
+
+describe "Dir#each" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_closed, :each
+end
diff --git a/spec/ruby/core/dir/element_reference_spec.rb b/spec/ruby/core/dir/element_reference_spec.rb
new file mode 100644
index 0000000000..092114bed4
--- /dev/null
+++ b/spec/ruby/core/dir/element_reference_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/glob'
+
+describe "Dir.[]" do
+ it_behaves_like :dir_glob, :[]
+end
+
+describe "Dir.[]" do
+ it_behaves_like :dir_glob_recursive, :[]
+end
+
+describe "Dir.[]" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ @cwd = Dir.pwd
+ Dir.chdir DirSpecs.mock_dir
+ end
+
+ after :all do
+ Dir.chdir @cwd
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "calls #to_path to convert multiple patterns" do
+ pat1 = mock('file_one.ext')
+ pat1.should_receive(:to_path).and_return('file_one.ext')
+ pat2 = mock('file_two.ext')
+ pat2.should_receive(:to_path).and_return('file_two.ext')
+
+ Dir[pat1, pat2].should == %w[file_one.ext file_two.ext]
+ end
+end
diff --git a/spec/ruby/core/dir/empty_spec.rb b/spec/ruby/core/dir/empty_spec.rb
new file mode 100644
index 0000000000..3b6b2bac85
--- /dev/null
+++ b/spec/ruby/core/dir/empty_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Dir.empty?" do
+ before :all do
+ @empty_dir = tmp("empty_dir")
+ mkdir_p @empty_dir
+ end
+
+ after :all do
+ rm_r @empty_dir
+ end
+
+ it "returns true for empty directories" do
+ result = Dir.empty? @empty_dir
+ result.should == true
+ end
+
+ it "returns false for non-empty directories" do
+ result = Dir.empty? __dir__
+ result.should == false
+ end
+
+ it "returns false for a non-directory" do
+ result = Dir.empty? __FILE__
+ result.should == false
+ end
+
+ it "raises ENOENT for nonexistent directories" do
+ -> { Dir.empty? tmp("nonexistent") }.should.raise(Errno::ENOENT)
+ end
+end
diff --git a/spec/ruby/core/dir/entries_spec.rb b/spec/ruby/core/dir/entries_spec.rb
new file mode 100644
index 0000000000..f3ca49b26d
--- /dev/null
+++ b/spec/ruby/core/dir/entries_spec.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.entries" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns an Array of filenames in an existing directory including dotfiles" do
+ a = Dir.entries(DirSpecs.mock_dir).sort
+
+ a.should == DirSpecs.expected_paths
+
+ a = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested").sort
+ a.should == %w|. .. .dotfile.ext directory|
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.entries(p)
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ dirs = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
+ dirs.each {|dir| dir.encoding.should == Encoding::UTF_8}
+ end
+
+ it "returns entries encoded with the filesystem encoding by default" do
+ # This spec depends on the locale not being US-ASCII because if it is, the
+ # entries that are not ascii_only? will be BINARY encoded.
+ entries = Dir.entries(File.join(DirSpecs.mock_dir, 'special')).sort
+ encoding = Encoding.find("filesystem")
+ encoding = Encoding::BINARY if encoding == Encoding::US_ASCII
+ platform_is_not :windows do
+ entries.should.include?("こんにちは.txt".dup.force_encoding(encoding))
+ end
+ entries.first.encoding.should.equal?(Encoding.find("filesystem"))
+ end
+
+ it "returns entries encoded with the specified encoding" do
+ dir = File.join(DirSpecs.mock_dir, 'special')
+ entries = Dir.entries(dir, encoding: "euc-jp").sort
+ entries.first.encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it "returns entries transcoded to the default internal encoding" do
+ Encoding.default_internal = Encoding::EUC_KR
+ entries = Dir.entries(File.join(DirSpecs.mock_dir, 'special')).sort
+ entries.first.encoding.should.equal?(Encoding::EUC_KR)
+ end
+
+ it "raises a SystemCallError if called with a nonexistent directory" do
+ -> { Dir.entries DirSpecs.nonexistent }.should.raise(SystemCallError)
+ end
+end
diff --git a/spec/ruby/core/dir/exist_spec.rb b/spec/ruby/core/dir/exist_spec.rb
new file mode 100644
index 0000000000..05ad67dd03
--- /dev/null
+++ b/spec/ruby/core/dir/exist_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.exist?" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "returns true if the given directory exists" do
+ Dir.exist?(__dir__).should == true
+ end
+
+ it "returns true for '.'" do
+ Dir.exist?('.').should == true
+ end
+
+ it "returns true for '..'" do
+ Dir.exist?('..').should == true
+ end
+
+ it "understands non-ASCII paths" do
+ subdir = File.join(tmp("\u{9876}\u{665}"))
+ Dir.exist?(subdir).should == false
+ Dir.mkdir(subdir)
+ Dir.exist?(subdir).should == true
+ Dir.rmdir(subdir)
+ end
+
+ it "understands relative paths" do
+ Dir.exist?(__dir__ + '/../').should == true
+ end
+
+ it "returns false if the given directory doesn't exist" do
+ Dir.exist?('y26dg27n2nwjs8a/').should == false
+ end
+
+ it "doesn't require the name to have a trailing slash" do
+ dir = __dir__
+ dir.sub!(/\/$/,'')
+ Dir.exist?(dir).should == true
+ end
+
+ it "doesn't expand paths" do
+ skip "$HOME not valid directory" unless ENV['HOME'] && File.directory?(ENV['HOME'])
+ Dir.exist?(File.expand_path('~')).should == true
+ Dir.exist?('~').should == false
+ end
+
+ it "returns false if the argument exists but is a file" do
+ File.should.exist?(__FILE__)
+ Dir.exist?(__FILE__).should == false
+ end
+
+ it "doesn't set $! when file doesn't exist" do
+ Dir.exist?("/path/to/non/existent/dir")
+ $!.should == nil
+ end
+
+ it "calls #to_path on non String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(__dir__)
+ Dir.exist?(p)
+ end
+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
new file mode 100644
index 0000000000..bd1a92b05e
--- /dev/null
+++ b/spec/ruby/core/dir/fchdir_spec.rb
@@ -0,0 +1,71 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+platform_is_not :windows do
+ describe "Dir.fchdir" 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 the directory specified by the integer file descriptor" do
+ dir = Dir.new(DirSpecs.mock_dir)
+ Dir.fchdir dir.fileno
+ Dir.pwd.should == DirSpecs.mock_dir
+ ensure
+ dir.close
+ end
+
+ it "returns 0 when successfully changing directory" do
+ 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 = 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
+ dir = Dir.new(DirSpecs.mock_dir)
+ Dir.fchdir(dir.fileno) { Dir.pwd }.should == DirSpecs.mock_dir
+ 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(SystemCallError, "Bad file descriptor - fchdir")
+ -> { Dir.fchdir(-1) { } }.should.raise(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(SystemCallError, /(Not a directory|Invalid argument) - fchdir/)
+ -> { Dir.fchdir($stdout.fileno) { } }.should.raise(SystemCallError, /(Not a directory|Invalid argument) - fchdir/)
+ end
+ end
+end
+
+platform_is :windows do
+ describe "Dir.fchdir" do
+ it "raises NotImplementedError" do
+ -> { Dir.fchdir 1 }.should.raise(NotImplementedError)
+ -> { Dir.fchdir(1) { } }.should.raise(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/fileno_spec.rb b/spec/ruby/core/dir/fileno_spec.rb
new file mode 100644
index 0000000000..3b563eb18f
--- /dev/null
+++ b/spec/ruby/core/dir/fileno_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+has_dir_fileno = begin
+ dir = Dir.new('.')
+ dir.fileno
+ true
+rescue NotImplementedError
+ false
+rescue Exception
+ true
+ensure
+ dir.close
+end
+
+describe "Dir#fileno" do
+ before :each do
+ @name = tmp("fileno")
+ mkdir_p @name
+ @dir = Dir.new(@name)
+ end
+
+ after :each do
+ @dir.close
+ rm_r @name
+ end
+
+ if has_dir_fileno
+ it "returns the file descriptor of the dir" do
+ @dir.fileno.should.is_a?(Integer)
+ end
+ else
+ it "raises an error when not implemented on the platform" do
+ -> { @dir.fileno }.should.raise(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/fixtures/common.rb b/spec/ruby/core/dir/fixtures/common.rb
new file mode 100644
index 0000000000..cfec91f68f
--- /dev/null
+++ b/spec/ruby/core/dir/fixtures/common.rb
@@ -0,0 +1,203 @@
+# encoding: utf-8
+
+module DirSpecs
+ def self.mock_dir(dirs = ['dir_specs_mock'])
+ @mock_dir ||= tmp("")
+ File.join @mock_dir, dirs
+ end
+
+ def self.nonexistent
+ name = File.join mock_dir, "nonexistent00"
+ name = name.next while File.exist? name
+ name
+ end
+
+ # TODO: make these relative to the mock_dir
+ def self.clear_dirs
+ [ 'nonexisting',
+ 'default_perms',
+ 'reduced',
+ 'always_returns_0',
+ '???',
+ [0xe9].pack('U')
+ ].each do |dir|
+ begin
+ Dir.rmdir mock_dir(dir)
+ rescue
+ end
+ end
+ end
+
+ # The names of the fixture directories and files used by
+ # various Dir specs.
+ def self.mock_dir_files
+ unless @mock_dir_files
+ @mock_dir_files = %w[
+ .dotfile
+ .dotsubdir/.dotfile
+ .dotsubdir/nondotfile
+ nested/.dotsubir/.dotfile
+ nested/.dotsubir/nondotfile
+
+ deeply/.dotfile
+ deeply/nested/.dotfile.ext
+ deeply/nested/directory/structure/.ext
+ deeply/nested/directory/structure/bar
+ deeply/nested/directory/structure/baz
+ deeply/nested/directory/structure/file_one
+ deeply/nested/directory/structure/file_one.ext
+ deeply/nested/directory/structure/foo
+ deeply/nondotfile
+
+ file_one.ext
+ file_two.ext
+
+ dir_filename_ordering
+ dir/filename_ordering
+
+ nondotfile
+
+ subdir_one/.dotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ subdir_two/nondotfile.ext
+
+ brace/a
+ brace/a.js
+ brace/a.erb
+ brace/a.js.rjs
+ brace/a.html.erb
+
+ special/+
+
+ special/^
+ special/$
+
+ special/(
+ special/)
+ special/[
+ special/]
+ special/{
+ special/}
+
+ special/test{1}/file[1]
+ special/{}/special
+ special/test\ +()[]{}/hello_world.erb
+ ]
+
+ platform_is_not :windows do
+ @mock_dir_files += %w[
+ special/*
+ special/?
+
+ special/|
+
+ special/こんにちは.txt
+ special/\a
+ ]
+ @mock_dir_files << "special/_\u{1f60e}.erb"
+ end
+ end
+
+ @mock_dir_files
+ end
+
+ def self.mock_dir_links
+ unless @mock_dir_links
+ @mock_dir_links = []
+ platform_is_not :windows do
+ @mock_dir_links += [
+ ['special/ln', 'subdir_one']
+ ]
+ end
+ end
+ @mock_dir_links
+ end
+
+ def self.create_mock_dirs
+ delete_mock_dirs
+ mock_dir_files.each do |name|
+ file = File.join mock_dir, name
+ mkdir_p File.dirname(file)
+ touch file
+ end
+ mock_dir_links.each do |link, target|
+ full_link = File.join mock_dir, link
+ full_target = File.join mock_dir, target
+
+ File.symlink full_target, full_link
+ end
+ end
+
+ def self.delete_mock_dirs
+ begin
+ rm_r mock_dir
+ rescue Errno::ENOTEMPTY => e
+ puts Dir["#{mock_dir}/**/*"]
+ raise e
+ end
+ end
+
+ def self.mock_rmdir(*dirs)
+ mock_dir ['rmdir_dirs'].concat(dirs)
+ end
+
+ def self.rmdir_dirs(create = true)
+ dirs = %w[
+ empty
+ nonempty
+ nonempty/child
+ noperm
+ noperm/child
+ ]
+
+ base_dir = mock_dir ['rmdir_dirs']
+
+ dirs.reverse_each do |d|
+ dir = File.join base_dir, d
+ if File.exist? dir
+ File.chmod 0777, dir
+ rm_r dir
+ end
+ end
+ rm_r base_dir
+
+ if create
+ dirs.each do |d|
+ dir = File.join base_dir, d
+ unless File.exist? dir
+ mkdir_p dir
+ File.chmod 0777, dir
+ end
+ end
+ end
+ end
+
+ def self.expected_paths_with_type
+ [
+ [".", :directory],
+ ["..", :directory],
+ [".dotfile", :file],
+ [".dotsubdir", :directory],
+ ["brace", :directory],
+ ["deeply", :directory],
+ ["dir", :directory],
+ ["dir_filename_ordering", :file],
+ ["file_one.ext", :file],
+ ["file_two.ext", :file],
+ ["nested", :directory],
+ ["nondotfile", :file],
+ ["special", :directory],
+ ["subdir_one", :directory],
+ ["subdir_two", :directory],
+ ]
+ end
+
+ def self.expected_paths
+ expected_paths_with_type.map(&:first)
+ 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..bbc75e0f8f
--- /dev/null
+++ b/spec/ruby/core/dir/for_fd_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+quarantine! do # leads to "Errno::EBADF: Bad file descriptor - closedir" in DirSpecs.delete_mock_dirs
+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_consistent_error(TypeError, "no implicit conversion of nil into Integer")
+ end
+
+ it "raises a SystemCallError if the file descriptor given is not valid" do
+ -> { Dir.for_fd(-1) }.should.raise(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(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(NotImplementedError)
+ end
+ end
+end
+end
diff --git a/spec/ruby/core/dir/foreach_spec.rb b/spec/ruby/core/dir/foreach_spec.rb
new file mode 100644
index 0000000000..2a2265a029
--- /dev/null
+++ b/spec/ruby/core/dir/foreach_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.foreach" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "yields all names in an existing directory to the provided block" do
+ a, b = [], []
+
+ Dir.foreach(DirSpecs.mock_dir) {|f| a << f}
+ Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested") {|f| b << f}
+
+ a.sort.should == DirSpecs.expected_paths
+ b.sort.should == %w|. .. .dotfile.ext directory|
+ end
+
+ it "returns nil when successful" do
+ Dir.foreach(DirSpecs.mock_dir) {|f| f}.should == nil
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.foreach(p).to_a
+ end
+
+ it "raises a SystemCallError if passed a nonexistent directory" do
+ -> { Dir.foreach(DirSpecs.nonexistent) {} }.should.raise(SystemCallError)
+ end
+
+ it "returns an Enumerator if no block given" do
+ Dir.foreach(DirSpecs.mock_dir).should.instance_of?(Enumerator)
+ Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
+ dirs.each { |dir| dir.encoding.should == Encoding::UTF_8 }
+
+ dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: Encoding::ISO_8859_1).to_a.sort
+ dirs.each { |dir| dir.encoding.should == Encoding::ISO_8859_1 }
+
+ Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: Encoding::ISO_8859_1) do |f|
+ f.encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ Dir.foreach(DirSpecs.mock_dir).should.instance_of?(Enumerator)
+ Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ Dir.foreach(DirSpecs.mock_dir).size.should == nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/getwd_spec.rb b/spec/ruby/core/dir/getwd_spec.rb
new file mode 100644
index 0000000000..138481821f
--- /dev/null
+++ b/spec/ruby/core/dir/getwd_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Dir.getwd" do
+ it "is an alias of Dir.pwd" do
+ Dir.method(:getwd).should == Dir.method(:pwd)
+ end
+end
diff --git a/spec/ruby/core/dir/glob_spec.rb b/spec/ruby/core/dir/glob_spec.rb
new file mode 100644
index 0000000000..9e81feb15f
--- /dev/null
+++ b/spec/ruby/core/dir/glob_spec.rb
@@ -0,0 +1,362 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/glob'
+
+describe "Dir.glob" do
+ it_behaves_like :dir_glob, :glob
+end
+
+describe "Dir.glob" do
+ it_behaves_like :dir_glob_recursive, :glob
+end
+
+describe "Dir.glob" do
+ before :each do
+ DirSpecs.create_mock_dirs
+
+ @cwd = Dir.pwd
+ Dir.chdir DirSpecs.mock_dir
+ end
+
+ after :each do
+ Dir.chdir @cwd
+
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "can take an array of patterns" do
+ Dir.glob(["file_o*", "file_t*"]).should ==
+ %w!file_one.ext file_two.ext!
+ end
+
+ it 'returns matching file paths when supplied :base keyword argument' do
+ dir = tmp('dir_glob_base')
+ file_1 = "#{dir}/lib/bloop.rb"
+ file_2 = "#{dir}/lib/soup.rb"
+ file_3 = "#{dir}/lib/mismatched_file_type.txt"
+ file_4 = "#{dir}/mismatched_directory.rb"
+
+ touch file_1
+ touch file_2
+ touch file_3
+ touch file_4
+
+ Dir.glob('**/*.rb', base: "#{dir}/lib").sort.should == ["bloop.rb", "soup.rb"].sort
+ ensure
+ rm_r dir
+ end
+
+ it "calls #to_path to convert multiple patterns" do
+ pat1 = mock('file_one.ext')
+ pat1.should_receive(:to_path).and_return('file_one.ext')
+ pat2 = mock('file_two.ext')
+ pat2.should_receive(:to_path).and_return('file_two.ext')
+
+ Dir.glob([pat1, pat2]).should == %w[file_one.ext file_two.ext]
+ end
+
+ it "matches both dot and non-dotfiles with '*' and option File::FNM_DOTMATCH" do
+ Dir.glob('*', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_glob_paths
+ end
+
+ it "matches files with any beginning with '*<non-special characters>' and option File::FNM_DOTMATCH" do
+ Dir.glob('*file', File::FNM_DOTMATCH).sort.should == %w|.dotfile nondotfile|.sort
+ end
+
+ it "matches any files in the current directory with '**' and option File::FNM_DOTMATCH" do
+ Dir.glob('**', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_glob_paths
+ end
+
+ it "recursively matches any subdirectories except './' or '../' with '**/' from the current directory and option File::FNM_DOTMATCH" do
+ expected = %w[
+ .dotsubdir/
+ brace/
+ deeply/
+ deeply/nested/
+ deeply/nested/directory/
+ deeply/nested/directory/structure/
+ dir/
+ nested/
+ nested/.dotsubir/
+ special/
+ special/test\ +()[]{}/
+ special/test{1}/
+ special/{}/
+ subdir_one/
+ subdir_two/
+ ]
+
+ Dir.glob('**/', File::FNM_DOTMATCH).sort.should == expected
+ end
+
+ 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
+
+ # This is a separate case to check **/ coming after a constant
+ # directory as well.
+ it "recursively matches any subdirectories except './' or '../' with '**/' and option File::FNM_DOTMATCH" do
+ expected = %w[
+ ./
+ ./.dotsubdir/
+ ./brace/
+ ./deeply/
+ ./deeply/nested/
+ ./deeply/nested/directory/
+ ./deeply/nested/directory/structure/
+ ./dir/
+ ./nested/
+ ./nested/.dotsubir/
+ ./special/
+ ./special/test\ +()[]{}/
+ ./special/test{1}/
+ ./special/{}/
+ ./subdir_one/
+ ./subdir_two/
+ ]
+
+ Dir.glob('./**/', File::FNM_DOTMATCH).sort.should == expected
+ end
+
+ it "matches a list of paths by concatenating their individual results" do
+ expected = %w[
+ deeply/
+ deeply/nested/
+ deeply/nested/directory/
+ deeply/nested/directory/structure/
+ subdir_two/nondotfile
+ subdir_two/nondotfile.ext
+ ]
+
+ Dir.glob('{deeply/**/,subdir_two/*}').sort.should == expected
+ end
+
+ it "preserves multiple /s before a **" do
+ expected = %w[
+ deeply//nested/directory/structure
+ ]
+
+ Dir.glob('{deeply//**/structure}').sort.should == expected
+ end
+
+ it "accepts a block and yields it with each elements" do
+ ary = []
+ ret = Dir.glob(["file_o*", "file_t*"]) { |t| ary << t }
+ ret.should == nil
+ ary.should == %w!file_one.ext file_two.ext!
+ end
+
+ it "ignores non-dirs when traversing recursively" do
+ touch "spec"
+ Dir.glob("spec/**/*.rb").should == []
+ end
+
+ it "matches nothing when given an empty list of paths" do
+ Dir.glob('{}').should == []
+ end
+
+ it "handles infinite directory wildcards" do
+ Dir.glob('**/**/**').should_not.empty?
+ end
+
+ it "handles **/** with base keyword argument" do
+ Dir.glob('**/**', base: "dir").should == ["filename_ordering"]
+
+ expected = %w[
+ nested
+ nested/directory
+ nested/directory/structure
+ nested/directory/structure/bar
+ nested/directory/structure/baz
+ nested/directory/structure/file_one
+ nested/directory/structure/file_one.ext
+ nested/directory/structure/foo
+ nondotfile
+ ].sort
+
+ Dir.glob('**/**', base: "deeply").sort.should == expected
+ end
+
+ it "handles **/ with base keyword argument" do
+ expected = %w[
+ /
+ directory/
+ directory/structure/
+ ]
+ Dir.glob('**/', base: "deeply/nested").sort.should == expected
+ end
+
+ it "handles **/nondotfile with base keyword argument" do
+ expected = %w[
+ deeply/nondotfile
+ nondotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ ]
+ Dir.glob('**/nondotfile', base: ".").sort.should == expected
+ end
+
+ it "handles **/nondotfile with base keyword argument and FNM_DOTMATCH" do
+ expected = %w[
+ .dotsubdir/nondotfile
+ deeply/nondotfile
+ nested/.dotsubir/nondotfile
+ nondotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ ]
+ Dir.glob('**/nondotfile', File::FNM_DOTMATCH, base: ".").sort.should == expected
+ end
+
+ it "handles **/.dotfile with base keyword argument" do
+ expected = %w[
+ .dotfile
+ deeply/.dotfile
+ subdir_one/.dotfile
+ ]
+ Dir.glob('**/.dotfile', base: ".").sort.should == expected
+ end
+
+ it "handles **/.dotfile with base keyword argument and FNM_DOTMATCH" do
+ expected = %w[
+ .dotfile
+ .dotsubdir/.dotfile
+ deeply/.dotfile
+ nested/.dotsubir/.dotfile
+ subdir_one/.dotfile
+ ]
+ Dir.glob('**/.dotfile', File::FNM_DOTMATCH, base: ".").sort.should == expected
+ end
+
+ it "handles **/.* with base keyword argument" do
+ expected = %w[
+ .dotfile.ext
+ directory/structure/.ext
+ ].sort
+
+ Dir.glob('**/.*', base: "deeply/nested").sort.should == expected
+ end
+
+ 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
+
+ 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
+ expected = %w[
+ .dotfile.ext
+ directory/structure/file_one
+ directory/structure/file_one.ext
+ ]
+
+ Dir.glob('**/*file*', File::FNM_DOTMATCH, base: "deeply/nested").sort.should == expected
+ end
+
+ it "handles **/glob with base keyword argument and FNM_EXTGLOB" do
+ expected = %w[
+ directory/structure/bar
+ directory/structure/file_one
+ directory/structure/file_one.ext
+ ]
+
+ Dir.glob('**/*{file,bar}*', File::FNM_EXTGLOB, base: "deeply/nested").sort.should == expected
+ end
+
+ it "handles simple filename patterns" do
+ Dir.glob('.dotfile').should == ['.dotfile']
+ end
+
+ it "handles simple directory patterns" do
+ Dir.glob('.dotsubdir/').should == ['.dotsubdir/']
+ end
+
+ it "handles simple directory patterns applied to non-directories" do
+ Dir.glob('nondotfile/').should == []
+ end
+
+ platform_is_not(:windows) do
+ it "matches the literal character '\\' with option File::FNM_NOESCAPE" do
+ Dir.mkdir 'foo?bar'
+
+ begin
+ Dir.glob('foo?bar', File::FNM_NOESCAPE).should == %w|foo?bar|
+ Dir.glob('foo\?bar', File::FNM_NOESCAPE).should == []
+ ensure
+ Dir.rmdir 'foo?bar'
+ end
+
+ Dir.mkdir 'foo\?bar'
+
+ begin
+ Dir.glob('foo\?bar', File::FNM_NOESCAPE).should == %w|foo\\?bar|
+ ensure
+ Dir.rmdir 'foo\?bar'
+ end
+ end
+
+ it "returns nil for directories current user has no permission to read" do
+ Dir.mkdir('no_permission')
+ File.chmod(0, 'no_permission')
+
+ begin
+ Dir.glob('no_permission/*').should == []
+ ensure
+ Dir.rmdir('no_permission')
+ end
+ end
+
+ it "will follow symlinks when processing a `*/` pattern." do
+ expected = ['special/ln/nondotfile']
+ Dir.glob('special/*/nondotfile').should == expected
+ end
+
+ it "will not follow symlinks when recursively traversing directories" do
+ expected = %w[
+ deeply/nondotfile
+ nondotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ ]
+ Dir.glob('**/nondotfile').sort.should == expected
+ end
+
+ it "will follow symlinks when testing directory after recursive directory in pattern" do
+ expected = %w[
+ deeply/nondotfile
+ special/ln/nondotfile
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ ]
+ Dir.glob('**/*/nondotfile').sort.should == expected
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/home_spec.rb b/spec/ruby/core/dir/home_spec.rb
new file mode 100644
index 0000000000..f0b20e0687
--- /dev/null
+++ b/spec/ruby/core/dir/home_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.home" do
+ before :each do
+ @home = ENV['HOME']
+ ENV['HOME'] = "/rubyspec_home"
+ end
+
+ after :each do
+ ENV['HOME'] = @home
+ end
+
+ describe "when called without arguments" do
+ it "returns the current user's home directory, reading $HOME first" do
+ Dir.home.should == "/rubyspec_home"
+ end
+
+ it "returns a non-frozen string" do
+ Dir.home.should_not.frozen?
+ end
+
+ it "returns a string with the filesystem encoding" do
+ Dir.home.encoding.should == Encoding.find("filesystem")
+ end
+
+ platform_is_not :windows do
+ it "works even if HOME is unset" do
+ ENV.delete('HOME')
+ Dir.home.should.start_with?('/')
+ Dir.home.encoding.should == Encoding.find("filesystem")
+ end
+ end
+
+ platform_is :windows 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 "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
+ end
+ end
+
+ describe "when called with the current user name" 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
+ end
+
+ it "returns a non-frozen string" do
+ Dir.home(ENV['USER']).should_not.frozen?
+ end
+
+ it "returns a string with the filesystem encoding" do
+ Dir.home(ENV['USER']).encoding.should == Encoding.find("filesystem")
+ end
+ end
+
+ it "raises an ArgumentError if the named user doesn't exist" do
+ -> { Dir.home('geuw2n288dh2k') }.should.raise(ArgumentError)
+ end
+
+ describe "when called with a nil user name" do
+ it "returns the current user's home directory, reading $HOME first" do
+ Dir.home(nil).should == "/rubyspec_home"
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/initialize_spec.rb b/spec/ruby/core/dir/initialize_spec.rb
new file mode 100644
index 0000000000..547b7dc18e
--- /dev/null
+++ b/spec/ruby/core/dir/initialize_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir#initialize" do
+ before :each do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :each do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.stub!(:to_path).and_return(DirSpecs.mock_dir)
+ dir = Dir.new(p)
+ begin
+ dir.path.should == DirSpecs.mock_dir
+ ensure
+ dir.close
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/inspect_spec.rb b/spec/ruby/core/dir/inspect_spec.rb
new file mode 100644
index 0000000000..eabaa54ce0
--- /dev/null
+++ b/spec/ruby/core/dir/inspect_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir#inspect" do
+ before :each do
+ @dir = Dir.new(Dir.getwd)
+ end
+
+ after :each do
+ @dir.close
+ end
+
+ it "returns a String" do
+ @dir.inspect.should.instance_of?(String)
+ end
+
+ it "includes the class name" do
+ @dir.inspect.should =~ /Dir/
+ end
+
+ it "includes the directory name" do
+ @dir.inspect.should.include?(Dir.getwd)
+ end
+end
diff --git a/spec/ruby/core/dir/mkdir_spec.rb b/spec/ruby/core/dir/mkdir_spec.rb
new file mode 100644
index 0000000000..37513e417a
--- /dev/null
+++ b/spec/ruby/core/dir/mkdir_spec.rb
@@ -0,0 +1,107 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.mkdir" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "creates the named directory with the given permissions" do
+ DirSpecs.clear_dirs
+
+ nonexisting = DirSpecs.mock_dir('nonexisting')
+ default_perms = DirSpecs.mock_dir('default_perms')
+ reduced = DirSpecs.mock_dir('reduced')
+ begin
+ File.should_not.exist?(nonexisting)
+ Dir.mkdir nonexisting
+ File.should.exist?(nonexisting)
+ platform_is_not :windows do
+ Dir.mkdir default_perms
+ a = File.stat(default_perms).mode
+ Dir.mkdir reduced, (a - 1)
+ File.stat(reduced).mode.should_not == a
+ end
+ platform_is :windows do
+ Dir.mkdir default_perms, 0666
+ a = File.stat(default_perms).mode
+ Dir.mkdir reduced, 0444
+ File.stat(reduced).mode.should_not == a
+ end
+
+ always_returns_0 = DirSpecs.mock_dir('always_returns_0')
+ Dir.mkdir(always_returns_0).should == 0
+ platform_is_not(:windows) do
+ File.chmod(0777, nonexisting, default_perms, reduced, always_returns_0)
+ end
+ platform_is_not(:windows) do
+ File.chmod(0644, nonexisting, default_perms, reduced, always_returns_0)
+ end
+ ensure
+ DirSpecs.clear_dirs
+ end
+ end
+
+ it "calls #to_path on non-String path arguments" do
+ DirSpecs.clear_dirs
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir('nonexisting'))
+ Dir.mkdir(p)
+ DirSpecs.clear_dirs
+ end
+
+ it "calls #to_int on non-Integer permissions argument" do
+ DirSpecs.clear_dirs
+ path = DirSpecs.mock_dir('nonexisting')
+ permissions = mock('permissions')
+ permissions.should_receive(:to_int).and_return(0666)
+ Dir.mkdir(path, permissions)
+ DirSpecs.clear_dirs
+ end
+
+ it "raises TypeError if non-Integer permissions argument does not have #to_int method" do
+ path = DirSpecs.mock_dir('nonexisting')
+ permissions = Object.new
+
+ -> { Dir.mkdir(path, permissions) }.should.raise(TypeError, 'no implicit conversion of Object into Integer')
+ end
+
+ it "raises a SystemCallError if any of the directories in the path before the last does not exist" do
+ -> { Dir.mkdir "#{DirSpecs.nonexistent}/subdir" }.should.raise(SystemCallError)
+ end
+
+ it "raises Errno::EEXIST if the specified directory already exists" do
+ -> { Dir.mkdir("#{DirSpecs.mock_dir}/dir") }.should.raise(Errno::EEXIST)
+ end
+
+ it "raises Errno::EEXIST if the argument points to the existing file" do
+ -> { Dir.mkdir("#{DirSpecs.mock_dir}/file_one.ext") }.should.raise(Errno::EEXIST)
+ end
+end
+
+# The permissions flag are not supported on Windows as stated in documentation:
+# The permissions may be modified by the value of File.umask, and are ignored on NT.
+platform_is_not :windows do
+ as_user do
+ describe "Dir.mkdir" do
+ before :each do
+ @dir = tmp "noperms"
+ end
+
+ after :each do
+ File.chmod 0777, @dir
+ rm_r @dir
+ end
+
+ it "raises a SystemCallError when lacking adequate permissions in the parent dir" do
+ Dir.mkdir @dir, 0000
+
+ -> { Dir.mkdir "#{@dir}/subdir" }.should.raise(SystemCallError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/open_spec.rb b/spec/ruby/core/dir/open_spec.rb
new file mode 100644
index 0000000000..be01638fbc
--- /dev/null
+++ b/spec/ruby/core/dir/open_spec.rb
@@ -0,0 +1,84 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.open" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "returns a Dir instance representing the specified directory" do
+ dir = Dir.open(DirSpecs.mock_dir)
+ dir.should.is_a?(Dir)
+ dir.close
+ end
+
+ it "raises a SystemCallError if the directory does not exist" do
+ -> do
+ Dir.open(DirSpecs.nonexistent)
+ end.should.raise(SystemCallError)
+ end
+
+ it "may take a block which is yielded to with the Dir instance" do
+ Dir.open(DirSpecs.mock_dir) {|dir| dir.should.is_a?(Dir)}
+ end
+
+ it "returns the value of the block if a block is given" do
+ Dir.open(DirSpecs.mock_dir) {|dir| :value }.should == :value
+ end
+
+ it "closes the Dir instance when the block exits if given a block" do
+ closed_dir = Dir.open(DirSpecs.mock_dir) { |dir| dir }
+ -> { closed_dir.read }.should.raise(IOError)
+ end
+
+ it "closes the Dir instance when the block exits the block even due to an exception" do
+ closed_dir = nil
+
+ -> do
+ Dir.open(DirSpecs.mock_dir) do |dir|
+ closed_dir = dir
+ raise "dir specs"
+ end
+ end.should.raise(RuntimeError, "dir specs")
+
+ -> { closed_dir.read }.should.raise(IOError)
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.open(p) { true }
+ end
+
+ it "accepts an options Hash" do
+ dir = Dir.open(DirSpecs.mock_dir, encoding: "utf-8") {|d| d }
+ dir.should.is_a?(Dir)
+ end
+
+ it "calls #to_hash to convert the options object" do
+ options = mock("dir_open")
+ options.should_receive(:to_hash).and_return({ encoding: Encoding::UTF_8 })
+
+ dir = Dir.open(DirSpecs.mock_dir, **options) {|d| d }
+ dir.should.is_a?(Dir)
+ end
+
+ it "ignores the :encoding option if it is nil" do
+ dir = Dir.open(DirSpecs.mock_dir, encoding: nil) {|d| d }
+ dir.should.is_a?(Dir)
+ end
+
+ platform_is_not :windows do
+ it 'sets the close-on-exec flag for the directory file descriptor' do
+ Dir.open(DirSpecs.mock_dir) do |dir|
+ io = IO.for_fd(dir.fileno)
+ io.autoclose = false
+ io.should.close_on_exec?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/path_spec.rb b/spec/ruby/core/dir/path_spec.rb
new file mode 100644
index 0000000000..02ddd2cd42
--- /dev/null
+++ b/spec/ruby/core/dir/path_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir#path" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "returns the path that was supplied to .new or .open" do
+ dir = Dir.open DirSpecs.mock_dir
+ begin
+ dir.path.should == DirSpecs.mock_dir
+ ensure
+ dir.close rescue nil
+ end
+ end
+
+ it "returns the path even when called on a closed Dir instance" do
+ dir = Dir.open DirSpecs.mock_dir
+ dir.close
+ dir.path.should == DirSpecs.mock_dir
+ end
+
+ it "returns a String with the same encoding as the argument to .open" do
+ path = DirSpecs.mock_dir.force_encoding Encoding::IBM866
+ dir = Dir.open path
+ begin
+ dir.path.encoding.should.equal?(Encoding::IBM866)
+ ensure
+ dir.close
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/pos_spec.rb b/spec/ruby/core/dir/pos_spec.rb
new file mode 100644
index 0000000000..f8ca06d778
--- /dev/null
+++ b/spec/ruby/core/dir/pos_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/pos'
+
+describe "Dir#pos" do
+ it "is an alias of Dir#tell" do
+ Dir.instance_method(:pos).should == Dir.instance_method(:tell)
+ end
+end
+
+describe "Dir#pos=" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_pos_set, :pos=
+end
diff --git a/spec/ruby/core/dir/pwd_spec.rb b/spec/ruby/core/dir/pwd_spec.rb
new file mode 100644
index 0000000000..70208b03d6
--- /dev/null
+++ b/spec/ruby/core/dir/pwd_spec.rb
@@ -0,0 +1,80 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Dir.pwd" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ before :each do
+ @fs_encoding = Encoding.find('filesystem')
+ end
+
+ it "returns the current working directory" do
+ pwd = Dir.pwd
+
+ File.directory?(pwd).should == true
+
+ # On ubuntu gutsy, for example, /bin/pwd does not
+ # understand -P. With just `pwd -P`, /bin/pwd is run.
+
+ # The following uses inode rather than file names to account for
+ # case insensitive file systems like default OS/X file systems
+ platform_is_not :windows do
+ File.stat(pwd).ino.should == File.stat(`/bin/sh -c "pwd -P"`.chomp).ino
+ end
+ platform_is :windows do
+ File.stat(pwd).ino.should == File.stat(File.expand_path(`cd`.chomp)).ino
+ end
+ end
+
+ it "returns an absolute path" do
+ pwd = Dir.pwd
+ pwd.should == File.expand_path(pwd)
+ end
+
+ it "returns an absolute path even when chdir to a relative path" do
+ Dir.chdir(".") do
+ pwd = Dir.pwd
+ File.directory?(pwd).should == true
+ pwd.should == File.expand_path(pwd)
+ end
+ end
+
+ it "returns a String with the filesystem encoding" do
+ enc = Dir.pwd.encoding
+ if @fs_encoding == Encoding::US_ASCII
+ [Encoding::US_ASCII, Encoding::BINARY].should.include?(enc)
+ else
+ enc.should.equal?(@fs_encoding)
+ end
+ end
+end
+
+describe "Dir.pwd" do
+ before :each do
+ @name = tmp("あ").force_encoding('binary')
+ @fs_encoding = Encoding.find('filesystem')
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ platform_is_not :windows do
+ it "correctly handles dirs with unicode characters in them" do
+ Dir.mkdir @name
+ Dir.chdir @name do
+ if @fs_encoding == Encoding::UTF_8
+ Dir.pwd.encoding.should == Encoding::UTF_8
+ end
+ Dir.pwd.force_encoding('binary').should == @name
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/read_spec.rb b/spec/ruby/core/dir/read_spec.rb
new file mode 100644
index 0000000000..3f842457f4
--- /dev/null
+++ b/spec/ruby/core/dir/read_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+
+describe "Dir#read" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "returns the file name in the current seek position" do
+ # an FS does not necessarily impose order
+ ls = Dir.entries DirSpecs.mock_dir
+ dir = Dir.open DirSpecs.mock_dir
+ ls.should.include?(dir.read)
+ dir.close
+ end
+
+ it "returns nil when there are no more entries" do
+ dir = Dir.open DirSpecs.mock_dir
+ DirSpecs.expected_paths.size.times do
+ dir.read.should_not == nil
+ end
+ dir.read.should == nil
+ dir.close
+ end
+
+ it "returns each entry successively" do
+ dir = Dir.open DirSpecs.mock_dir
+ entries = []
+ while entry = dir.read
+ entries << entry
+ end
+ dir.close
+
+ entries.sort.should == DirSpecs.expected_paths
+ end
+
+ platform_is_not :windows do
+ it "returns all directory entries even when encoding conversion will fail" do
+ dir = Dir.open(File.join(DirSpecs.mock_dir, 'special'))
+ utf8_entries = []
+ begin
+ while entry = dir.read
+ utf8_entries << entry
+ end
+ ensure
+ dir.close
+ end
+ old_internal_encoding = Encoding::default_internal
+ old_external_encoding = Encoding::default_external
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::SHIFT_JIS
+ shift_jis_entries = []
+ begin
+ Dir.open(File.join(DirSpecs.mock_dir, 'special')) do |d|
+ -> {
+ while entry = d.read
+ shift_jis_entries << entry
+ end
+ }.should_not.raise
+ end
+ ensure
+ Encoding.default_internal = old_internal_encoding
+ Encoding.default_external = old_external_encoding
+ end
+ shift_jis_entries.size.should == utf8_entries.size
+ shift_jis_entries.filter { |f| f.encoding == Encoding::SHIFT_JIS }.size.should == 1
+ end
+ end
+
+ it_behaves_like :dir_closed, :read
+end
diff --git a/spec/ruby/core/dir/rewind_spec.rb b/spec/ruby/core/dir/rewind_spec.rb
new file mode 100644
index 0000000000..220d7f5372
--- /dev/null
+++ b/spec/ruby/core/dir/rewind_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+
+describe "Dir#rewind" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ before :each do
+ @dir = Dir.open DirSpecs.mock_dir
+ end
+
+ after :each do
+ @dir.close
+ end
+
+ it "resets the next read to start from the first entry" do
+ a = @dir.read
+ b = @dir.read
+ a.should_not == b
+ @dir.rewind
+ c = @dir.read
+ c.should == a
+ end
+
+ it "returns the Dir instance" do
+ @dir.rewind.should == @dir
+ end
+
+ it_behaves_like :dir_closed, :rewind
+end
diff --git a/spec/ruby/core/dir/rmdir_spec.rb b/spec/ruby/core/dir/rmdir_spec.rb
new file mode 100644
index 0000000000..08cd1a5bc6
--- /dev/null
+++ b/spec/ruby/core/dir/rmdir_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/delete'
+
+describe "Dir.rmdir" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_delete, :rmdir
+end
diff --git a/spec/ruby/core/dir/scan_spec.rb b/spec/ruby/core/dir/scan_spec.rb
new file mode 100644
index 0000000000..3aa071337b
--- /dev/null
+++ b/spec/ruby/core/dir/scan_spec.rb
@@ -0,0 +1,224 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative '../file/fixtures/file_types'
+
+ruby_version_is "4.1" do
+ describe "Dir.scan" do
+ before :all do
+ FileSpecs.configure_types
+ end
+
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns an Array of filename and type pairs in an existing directory including dotfiles" do
+ a = Dir.scan(DirSpecs.mock_dir).sort
+
+ a.should == DirSpecs.expected_paths_with_type - [[".", :directory], ["..", :directory]]
+
+ a = Dir.scan("#{DirSpecs.mock_dir}/deeply/nested").sort
+ a.should == [[".dotfile.ext", :file], ["directory", :directory]]
+ end
+
+ it "yields filename and type in an existing directory including dotfiles" do
+ a = []
+ Dir.scan(DirSpecs.mock_dir) do |n, t|
+ a << [n, t]
+ end
+ a.sort!
+ a.should == DirSpecs.expected_paths_with_type - [[".", :directory], ["..", :directory]]
+
+ a = []
+ Dir.scan("#{DirSpecs.mock_dir}/deeply/nested") do |n, t|
+ a << [n, t]
+ end
+ a.sort!
+ a.should == [[".dotfile.ext", :file], ["directory", :directory]]
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_dir)
+ Dir.scan(p)
+ end
+
+ it "accepts an options Hash" do
+ a = Dir.scan("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").sort
+ a.should == [[".dotfile.ext", :file], ["directory", :directory]]
+ end
+
+ it "returns children names encoded with the filesystem encoding by default" do
+ # This spec depends on the locale not being US-ASCII because if it is, the
+ # children that are not ascii_only? will be BINARY encoded.
+ children = Dir.scan(File.join(DirSpecs.mock_dir, 'special')).sort
+ encoding = Encoding.find("filesystem")
+ encoding = Encoding::BINARY if encoding == Encoding::US_ASCII
+ platform_is_not :windows do
+ children.should.include?(["こんにちは.txt".dup.force_encoding(encoding), :file])
+ end
+ children.first.first.encoding.should.equal?(Encoding.find("filesystem"))
+ end
+
+ it "returns children names encoded with the specified encoding" do
+ dir = File.join(DirSpecs.mock_dir, 'special')
+ children = Dir.scan(dir, encoding: "euc-jp").sort
+ children.first.first.encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it "returns children names transcoded to the default internal encoding" do
+ Encoding.default_internal = Encoding::EUC_KR
+ children = Dir.scan(File.join(DirSpecs.mock_dir, 'special')).sort
+ children.first.first.encoding.should.equal?(Encoding::EUC_KR)
+ end
+
+ it "raises a SystemCallError if called with a nonexistent directory" do
+ -> { Dir.scan DirSpecs.nonexistent }.should.raise(SystemCallError)
+ end
+
+ it "handles symlink" do
+ FileSpecs.symlink do |path|
+ Dir.scan(File.dirname(path)).map(&:last).should.include?(:link)
+ end
+ end
+
+ platform_is_not :windows do
+ it "handles socket" do
+ FileSpecs.socket do |path|
+ Dir.scan(File.dirname(path)).map(&:last).should.include?(:socket)
+ end
+ end
+
+ it "handles FIFO" do
+ FileSpecs.fifo do |path|
+ Dir.scan(File.dirname(path)).map(&:last).should.include?(:fifo)
+ end
+ end
+
+ it "handles character devices" do
+ FileSpecs.character_device do |path|
+ Dir.scan(File.dirname(path)).map(&:last).should.include?(:characterSpecial)
+ end
+ end
+ end
+
+ platform_is_not :freebsd, :windows do
+ with_block_device do
+ it "handles block devices" do
+ FileSpecs.block_device do |path|
+ Dir.scan(File.dirname(path)).map(&:last).should.include?(:blockSpecial)
+ end
+ end
+ end
+ end
+ end
+
+ describe "Dir#scan" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ @dir.close if @dir
+ end
+
+ it "returns an Array of filenames in an existing directory including dotfiles" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+ a = @dir.scan.sort
+ @dir.close
+
+ a.should == DirSpecs.expected_paths_with_type - [[".", :directory], ["..", :directory]]
+
+ @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested")
+ a = @dir.scan.sort
+ a.should == [[".dotfile.ext", :file], ["directory", :directory]]
+ end
+
+ it "yields filename and type in an existing directory including dotfiles" do
+ @dir = Dir.new(DirSpecs.mock_dir)
+ a = []
+ @dir.scan do |n, t|
+ a << [n, t]
+ end
+ a.sort!
+ a.should == DirSpecs.expected_paths_with_type - [[".", :directory], ["..", :directory]]
+
+ @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested")
+ a = []
+ @dir.scan do |n, t|
+ a << [n, t]
+ end
+ a.sort!
+ a.should == [[".dotfile.ext", :file], ["directory", :directory]]
+ end
+
+ it "accepts an encoding keyword for the encoding of the entries" do
+ @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8")
+ dirs = @dir.to_a.sort
+ dirs.each { |d| d.encoding.should == Encoding::UTF_8 }
+ end
+
+ it "returns children names encoded with the filesystem encoding by default" do
+ # This spec depends on the locale not being US-ASCII because if it is, the
+ # children that are not ascii_only? will be BINARY encoded.
+ @dir = Dir.new(File.join(DirSpecs.mock_dir, 'special'))
+ children = @dir.scan.sort
+ encoding = Encoding.find("filesystem")
+ encoding = Encoding::BINARY if encoding == Encoding::US_ASCII
+ platform_is_not :windows do
+ children.should.include?(["こんにちは.txt".dup.force_encoding(encoding), :file])
+ end
+ children.first.first.encoding.should.equal?(Encoding.find("filesystem"))
+ end
+
+ it "returns children names encoded with the specified encoding" do
+ path = File.join(DirSpecs.mock_dir, 'special')
+ @dir = Dir.new(path, encoding: "euc-jp")
+ children = @dir.children.sort
+ children.first.encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it "returns children names transcoded to the default internal encoding" do
+ Encoding.default_internal = Encoding::EUC_KR
+ @dir = Dir.new(File.join(DirSpecs.mock_dir, 'special'))
+ children = @dir.scan.sort
+ children.first.first.encoding.should.equal?(Encoding::EUC_KR)
+ end
+
+ it "returns the same result when called repeatedly" do
+ @dir = Dir.open DirSpecs.mock_dir
+
+ a = []
+ @dir.each {|dir| a << dir}
+
+ b = []
+ @dir.each {|dir| b << dir}
+
+ a.sort.should == b.sort
+ a.sort.should == DirSpecs.expected_paths
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/seek_spec.rb b/spec/ruby/core/dir/seek_spec.rb
new file mode 100644
index 0000000000..ed409897cd
--- /dev/null
+++ b/spec/ruby/core/dir/seek_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/pos'
+
+describe "Dir#seek" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "returns the Dir instance" do
+ @dir.seek(@dir.pos).should == @dir
+ end
+
+ it_behaves_like :dir_pos_set, :seek
+end
diff --git a/spec/ruby/core/dir/shared/chroot.rb b/spec/ruby/core/dir/shared/chroot.rb
new file mode 100644
index 0000000000..e4e6103799
--- /dev/null
+++ b/spec/ruby/core/dir/shared/chroot.rb
@@ -0,0 +1,44 @@
+describe :dir_chroot_as_root, shared: true do
+ before :all do
+ DirSpecs.create_mock_dirs
+
+ @real_root = "../" * (__dir__.count('/') - 1)
+ @ref_dir = File.join("/", File.basename(Dir["/*"].first))
+ end
+
+ after :all do
+ until File.exist?(@ref_dir)
+ Dir.send(@method, "../") or break
+ end
+
+ DirSpecs.delete_mock_dirs
+ end
+
+ # Pending until https://github.com/ruby/ruby/runs/8075149420 is fixed
+ compilations_ci = ENV["GITHUB_WORKFLOW"] == "Compilations"
+
+ it "can be used to change the process' root directory" do
+ -> { Dir.send(@method, __dir__) }.should_not.raise
+ File.should.exist?("/#{File.basename(__FILE__)}")
+ end unless compilations_ci
+
+ it "returns 0 if successful" do
+ Dir.send(@method, '/').should == 0
+ end
+
+ it "raises an Errno::ENOENT exception if the directory doesn't exist" do
+ -> { Dir.send(@method, 'xgwhwhsjai2222jg') }.should.raise(Errno::ENOENT)
+ end
+
+ it "can be escaped from with ../" do
+ Dir.send(@method, @real_root)
+ File.should.exist?(@ref_dir)
+ File.should_not.exist?("/#{File.basename(__FILE__)}")
+ end unless compilations_ci
+
+ it "calls #to_path on non-String argument" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(@real_root)
+ Dir.send(@method, p)
+ end
+end
diff --git a/spec/ruby/core/dir/shared/closed.rb b/spec/ruby/core/dir/shared/closed.rb
new file mode 100644
index 0000000000..c868fd6e6d
--- /dev/null
+++ b/spec/ruby/core/dir/shared/closed.rb
@@ -0,0 +1,9 @@
+describe :dir_closed, shared: true do
+ it "raises an IOError when called on a closed Dir instance" do
+ -> {
+ dir = Dir.open DirSpecs.mock_dir
+ dir.close
+ dir.send(@method) {}
+ }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/dir/shared/delete.rb b/spec/ruby/core/dir/shared/delete.rb
new file mode 100644
index 0000000000..ba013e8615
--- /dev/null
+++ b/spec/ruby/core/dir/shared/delete.rb
@@ -0,0 +1,53 @@
+describe :dir_delete, shared: true do
+ before :each do
+ DirSpecs.rmdir_dirs true
+ end
+
+ after :each do
+ DirSpecs.rmdir_dirs false
+ end
+
+ it "removes empty directories" do
+ Dir.send(@method, DirSpecs.mock_rmdir("empty")).should == 0
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(DirSpecs.mock_rmdir("empty"))
+ Dir.send(@method, p)
+ 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(Errno::ENOTEMPTY)
+ end
+
+ it "raises an Errno::ENOENT when trying to remove a non-existing directory" do
+ -> do
+ Dir.send @method, DirSpecs.nonexistent
+ end.should.raise(Errno::ENOENT)
+ end
+
+ it "raises an Errno::ENOTDIR when trying to remove a non-directory" do
+ file = DirSpecs.mock_rmdir("nonempty/regular")
+ touch(file)
+ -> do
+ Dir.send @method, file
+ end.should.raise(Errno::ENOTDIR)
+ end
+
+ # this won't work on Windows, since chmod(0000) does not remove all permissions
+ platform_is_not :windows do
+ as_user do
+ it "raises an Errno::EACCES if lacking adequate permissions to remove the directory" do
+ parent = DirSpecs.mock_rmdir("noperm")
+ child = DirSpecs.mock_rmdir("noperm", "child")
+ File.chmod(0000, parent)
+ -> do
+ Dir.send @method, child
+ end.should.raise(Errno::EACCES)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/shared/glob.rb b/spec/ruby/core/dir/shared/glob.rb
new file mode 100644
index 0000000000..86aa105259
--- /dev/null
+++ b/spec/ruby/core/dir/shared/glob.rb
@@ -0,0 +1,441 @@
+# -*- encoding: utf-8 -*-
+describe :dir_glob, shared: true do
+ before :all do
+ DirSpecs.create_mock_dirs
+ @cwd = Dir.pwd
+ Dir.chdir DirSpecs.mock_dir
+ end
+
+ after :all do
+ Dir.chdir @cwd
+ DirSpecs.delete_mock_dirs
+ end
+
+ it "raises an Encoding::CompatibilityError if the argument encoding is not compatible with US-ASCII" do
+ pattern = "files*".dup.force_encoding Encoding::UTF_16BE
+ -> { Dir.send(@method, pattern) }.should.raise(Encoding::CompatibilityError)
+ end
+
+ it "calls #to_path to convert a pattern" do
+ obj = mock('file_one.ext')
+ obj.should_receive(:to_path).and_return('file_one.ext')
+
+ Dir.send(@method, obj).should == %w[file_one.ext]
+ end
+
+ it "raises an ArgumentError if the string contains \\0" do
+ -> {Dir.send(@method, "file_o*\0file_t*")}.should.raise ArgumentError, /nul-separated/
+ end
+
+ it "result is sorted by default" do
+ result = Dir.send(@method, '*')
+ result.should == result.sort
+ end
+
+ it "result is sorted with sort: true" do
+ result = Dir.send(@method, '*', sort: true)
+ result.should == result.sort
+ end
+
+ it "sort: false returns same files" do
+ result = Dir.send(@method,'*', sort: false)
+ result.sort.should == Dir.send(@method, '*').sort
+ end
+
+ it "raises an ArgumentError if sort: is not true or false" do
+ -> { Dir.send(@method, '*', sort: 0) }.should.raise ArgumentError, /expected true or false/
+ -> { Dir.send(@method, '*', sort: nil) }.should.raise ArgumentError, /expected true or false/
+ -> { Dir.send(@method, '*', sort: 'false') }.should.raise ArgumentError, /expected true or false/
+ end
+
+ it "matches non-dotfiles with '*'" do
+ expected = %w[
+ brace
+ deeply
+ dir
+ dir_filename_ordering
+ file_one.ext
+ file_two.ext
+ nested
+ nondotfile
+ special
+ subdir_one
+ subdir_two
+ ]
+
+ Dir.send(@method,'*').sort.should == expected
+ end
+
+ it "returns empty array when empty pattern provided" do
+ Dir.send(@method, '').should == []
+ end
+
+ it "matches regexp special +" do
+ Dir.send(@method, 'special/+').should == ['special/+']
+ end
+
+ it "matches directories with special characters when escaped" do
+ Dir.send(@method, 'special/\{}/special').should == ["special/{}/special"]
+ end
+
+ platform_is_not :windows do
+ it "matches regexp special *" do
+ Dir.send(@method, 'special/\*').should == ['special/*']
+ end
+
+ it "matches regexp special ?" do
+ Dir.send(@method, 'special/\?').should == ['special/?']
+ end
+
+ it "matches regexp special |" do
+ Dir.send(@method, 'special/|').should == ['special/|']
+ end
+
+ it "matches files with backslashes in their name" do
+ Dir.glob('special/\\\\{a,b}').should == ['special/\a']
+ end
+
+ it "matches directory with special characters in their name in complex patterns" do
+ Dir.glob("special/test +()\\[\\]\\{\\}/hello_world{.{en},}{.{html},}{+{phone},}{.{erb},}").should == ['special/test +()[]{}/hello_world.erb']
+ end
+ end
+
+ it "matches regexp special ^" do
+ Dir.send(@method, 'special/^').should == ['special/^']
+ end
+
+ it "matches regexp special $" do
+ Dir.send(@method, 'special/$').should == ['special/$']
+ end
+
+ it "matches regexp special (" do
+ Dir.send(@method, 'special/(').should == ['special/(']
+ end
+
+ it "matches regexp special )" do
+ Dir.send(@method, 'special/)').should == ['special/)']
+ end
+
+ it "matches regexp special [" do
+ Dir.send(@method, 'special/\[').should == ['special/[']
+ end
+
+ it "matches regexp special ]" do
+ Dir.send(@method, 'special/]').should == ['special/]']
+ end
+
+ it "matches regexp special {" do
+ Dir.send(@method, 'special/\{').should == ['special/{']
+ end
+
+ it "matches regexp special }" do
+ Dir.send(@method, 'special/\}').should == ['special/}']
+ end
+
+ it "matches paths with glob patterns" do
+ Dir.send(@method, 'special/test\{1\}/*').should == ['special/test{1}/file[1]']
+ 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
+ Dir.send(@method, '*file').sort.should == %w|nondotfile|.sort
+ end
+
+ it "matches dotfiles with '.*<non-special characters>'" do
+ Dir.send(@method, '.*file').sort.should == %w|.dotfile|.sort
+ end
+
+ it "matches files with any ending with '<non-special characters>*'" do
+ Dir.send(@method, 'file*').sort.should == %w|file_one.ext file_two.ext|.sort
+ end
+
+ it "matches files with any middle with '<non-special characters>*<non-special characters>'" do
+ Dir.send(@method, 'sub*_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "handles directories with globs" do
+ Dir.send(@method, 'sub*/*').sort.should == %w!subdir_one/nondotfile subdir_two/nondotfile subdir_two/nondotfile.ext!
+ end
+
+ it "matches files with multiple '*' special characters" do
+ Dir.send(@method, '*fi*e*').sort.should == %w|dir_filename_ordering nondotfile file_one.ext file_two.ext|.sort
+ end
+
+ it "matches non-dotfiles in the current directory with '**'" do
+ expected = %w[
+ brace
+ deeply
+ dir
+ dir_filename_ordering
+ file_one.ext
+ file_two.ext
+ nested
+ nondotfile
+ special
+ subdir_one
+ subdir_two
+ ]
+
+ Dir.send(@method, '**').sort.should == expected
+ 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
+ expected = %w[
+ brace/
+ deeply/
+ deeply/nested/
+ deeply/nested/directory/
+ deeply/nested/directory/structure/
+ dir/
+ nested/
+ special/
+ special/test\ +()[]{}/
+ special/test{1}/
+ special/{}/
+ subdir_one/
+ subdir_two/
+ ]
+
+ Dir.send(@method, '**/').sort.should == expected
+ end
+
+ it "recursively matches any subdirectories except './' or '../' with '**/' from the base directory if that is specified" do
+ expected = %w[
+ nested/directory
+ ]
+
+ Dir.send(@method, '**/*ory', base: 'deeply').sort.should == expected
+ end
+
+ it "recursively matches any subdirectories including ./ with '.**/'" do
+ Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do
+ Dir.send(@method, '.**/').should == ['./']
+ end
+ end
+
+ it "matches a single character except leading '.' with '?'" do
+ Dir.send(@method, '?ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "accepts multiple '?' characters in a pattern" do
+ Dir.send(@method, 'subdir_???').sort.should == %w|subdir_one subdir_two|.sort
+ end
+
+ it "matches any characters in a set with '[<characters>]'" do
+ Dir.send(@method, '[stfu]ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "matches any characters in a range with '[<character>-<character>]'" do
+ Dir.send(@method, '[a-zA-Z]ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "matches any characters except those in a set with '[^<characters>]'" do
+ Dir.send(@method, '[^wtf]ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "matches any characters except those in a range with '[^<character>-<character]'" do
+ Dir.send(@method, '[^0-9]ubdir_one').sort.should == %w|subdir_one|.sort
+ end
+
+ it "matches any one of the strings in a set with '{<string>,<other>,...}'" do
+ Dir.send(@method, 'subdir_{one,two,three}').sort.should == %w|subdir_one subdir_two|.sort
+ end
+
+ it "matches a set '{<string>,<other>,...}' which also uses a glob" do
+ Dir.send(@method, 'sub*_{one,two,three}').sort.should == %w|subdir_one subdir_two|.sort
+ end
+
+ it "accepts string sets with empty strings with {<string>,,<other>}" do
+ a = Dir.send(@method, 'deeply/nested/directory/structure/file_one{.ext,}').sort
+ a.should == %w|deeply/nested/directory/structure/file_one.ext
+ deeply/nested/directory/structure/file_one|.sort
+ end
+
+ it "matches dot or non-dotfiles with '{,.}*'" do
+ Dir.send(@method, '{,.}*').sort.should == DirSpecs.expected_glob_paths
+ end
+
+ it "respects the order of {} expressions, expanding left most first" do
+ files = Dir.send(@method, "brace/a{.js,.html}{.erb,.rjs}")
+ files.should == %w!brace/a.js.rjs brace/a.html.erb!
+ end
+
+ it "respects the optional nested {} expressions" do
+ files = Dir.send(@method, "brace/a{.{js,html},}{.{erb,rjs},}")
+ files.should == %w!brace/a.js.rjs brace/a.js brace/a.html.erb brace/a.erb brace/a!
+ end
+
+ it "matches special characters by escaping with a backslash with '\\<character>'" do
+ Dir.mkdir 'foo^bar'
+
+ begin
+ Dir.send(@method, 'foo?bar').should == %w|foo^bar|
+ Dir.send(@method, 'foo\?bar').should == []
+ Dir.send(@method, 'nond\otfile').should == %w|nondotfile|
+ ensure
+ Dir.rmdir 'foo^bar'
+ end
+ end
+
+ it "recursively matches directories with '**/<characters>'" do
+ Dir.send(@method, '**/*fil?{,.}*').uniq.sort.should ==
+ %w[deeply/nested/directory/structure/file_one
+ deeply/nested/directory/structure/file_one.ext
+ deeply/nondotfile
+
+ dir/filename_ordering
+ dir_filename_ordering
+
+ file_one.ext
+ file_two.ext
+
+ nondotfile
+
+ special/test{1}/file[1]
+
+ subdir_one/nondotfile
+ subdir_two/nondotfile
+ subdir_two/nondotfile.ext]
+ end
+
+ it "ignores matching through directories that doesn't exist" do
+ Dir.send(@method, "deeply/notthere/blah*/whatever").should == []
+ end
+
+ it "ignores matching only directories under an nonexistent path" do
+ Dir.send(@method, "deeply/notthere/blah/").should == []
+ end
+
+ platform_is_not :windows do
+ it "matches UTF-8 paths" do
+ Dir.send(@method, "special/こんにちは{,.txt}").should == ["special/こんにちは.txt"]
+ end
+ end
+
+ context ":base option passed" do
+ before :each do
+ @mock_dir = File.expand_path tmp('dir_glob_mock')
+
+ %w[
+ a/b/x
+ a/b/c/y
+ a/b/c/d/z
+ ].each do |path|
+ file = File.join @mock_dir, path
+ mkdir_p File.dirname(file)
+ touch file
+ end
+ end
+
+ after :each do
+ rm_r @mock_dir
+ end
+
+ it "matches entries only from within the specified directory" do
+ path = File.join(@mock_dir, "a/b/c")
+ Dir.send(@method, "*", base: path).sort.should == %w( d y )
+ end
+
+ it "accepts both relative and absolute paths" do
+ require 'pathname'
+
+ path_abs = File.join(@mock_dir, "a/b/c")
+ path_rel = Pathname.new(path_abs).relative_path_from(Pathname.new(Dir.pwd))
+
+ result_abs = Dir.send(@method, "*", base: path_abs).sort
+ result_rel = Dir.send(@method, "*", base: path_rel).sort
+
+ result_abs.should == %w( d y )
+ result_rel.should == %w( d y )
+ end
+
+ it "returns [] if specified path does not exist" do
+ path = File.join(@mock_dir, "fake-name")
+ File.should_not.exist?(path)
+
+ Dir.send(@method, "*", base: path).should == []
+ end
+
+ it "returns [] if specified path is a file" do
+ path = File.join(@mock_dir, "a/b/x")
+ File.should.exist?(path)
+
+ Dir.send(@method, "*", base: path).should == []
+ end
+
+ it "raises TypeError when cannot convert value to string" do
+ -> {
+ Dir.send(@method, "*", base: [])
+ }.should.raise(TypeError)
+ end
+
+ it "handles '' as current directory path" do
+ Dir.chdir @mock_dir do
+ Dir.send(@method, "*", base: "").should == %w( a )
+ end
+ end
+
+ it "handles nil as current directory path" do
+ Dir.chdir @mock_dir do
+ Dir.send(@method, "*", base: nil).should == %w( a )
+ end
+ end
+ end
+end
+
+describe :dir_glob_recursive, shared: true do
+ before :each do
+ @cwd = Dir.pwd
+ @mock_dir = File.expand_path tmp('dir_glob_mock')
+
+ %w[
+ a/x/b/y/e
+ a/x/b/y/b/z/e
+ ].each do |path|
+ file = File.join @mock_dir, path
+ mkdir_p File.dirname(file)
+ touch file
+ end
+
+ Dir.chdir @mock_dir
+ end
+
+ after :each do
+ Dir.chdir @cwd
+ rm_r @mock_dir
+ end
+
+ it "matches multiple recursives" do
+ expected = %w[
+ a/x/b/y/b/z/e
+ a/x/b/y/e
+ ]
+
+ Dir.send(@method, 'a/**/b/**/e').uniq.sort.should == expected
+ end
+
+ platform_is_not :windows do
+ it "ignores symlinks" do
+ file = File.join @mock_dir, 'b/z/e'
+ link = File.join @mock_dir, 'a/y'
+
+ mkdir_p File.dirname(file)
+ touch file
+ File.symlink(File.dirname(file), link)
+
+ expected = %w[
+ a/x/b/y/b/z/e
+ a/x/b/y/e
+ ]
+
+ Dir.send(@method, 'a/**/e').uniq.sort.should == expected
+ end
+ end
+end
diff --git a/spec/ruby/core/dir/shared/pos.rb b/spec/ruby/core/dir/shared/pos.rb
new file mode 100644
index 0000000000..741de8918c
--- /dev/null
+++ b/spec/ruby/core/dir/shared/pos.rb
@@ -0,0 +1,24 @@
+describe :dir_pos_set, shared: true do
+ before :each do
+ @dir = Dir.open DirSpecs.mock_dir
+ end
+
+ after :each do
+ @dir.close
+ end
+
+ # NOTE: #seek/#pos= to a position not returned by #tell/#pos is undefined
+ # and should not be spec'd.
+
+ it "moves the read position to a previously obtained position" do
+ pos = @dir.pos
+ a = @dir.read
+ b = @dir.read
+ @dir.send @method, pos
+ c = @dir.read
+
+ a.should_not == b
+ b.should_not == c
+ c.should == a
+ end
+end
diff --git a/spec/ruby/core/dir/tell_spec.rb b/spec/ruby/core/dir/tell_spec.rb
new file mode 100644
index 0000000000..dcbb40438f
--- /dev/null
+++ b/spec/ruby/core/dir/tell_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/closed'
+require_relative 'shared/pos'
+
+describe "Dir#tell" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_closed, :tell
+
+ before :each do
+ @dir = Dir.open DirSpecs.mock_dir
+ end
+
+ after :each do
+ @dir.close rescue nil
+ end
+
+ it "returns an Integer representing the current position in the directory" do
+ @dir.tell.should.is_a?(Integer)
+ @dir.tell.should.is_a?(Integer)
+ @dir.tell.should.is_a?(Integer)
+ end
+
+ it "returns a different Integer if moved from previous position" do
+ a = @dir.tell
+ @dir.read
+ b = @dir.tell
+
+ a.should.is_a?(Integer)
+ b.should.is_a?(Integer)
+
+ a.should_not == b
+ end
+end
diff --git a/spec/ruby/core/dir/to_path_spec.rb b/spec/ruby/core/dir/to_path_spec.rb
new file mode 100644
index 0000000000..2ed533e757
--- /dev/null
+++ b/spec/ruby/core/dir/to_path_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Dir#to_path" do
+ it "is an alias of Dir#path" do
+ Dir.instance_method(:to_path).should == Dir.instance_method(:path)
+ end
+end
diff --git a/spec/ruby/core/dir/unlink_spec.rb b/spec/ruby/core/dir/unlink_spec.rb
new file mode 100644
index 0000000000..79027e020c
--- /dev/null
+++ b/spec/ruby/core/dir/unlink_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/delete'
+
+describe "Dir.unlink" do
+ before :all do
+ DirSpecs.create_mock_dirs
+ end
+
+ after :all do
+ DirSpecs.delete_mock_dirs
+ end
+
+ it_behaves_like :dir_delete, :unlink
+end
diff --git a/spec/ruby/core/encoding/_dump_spec.rb b/spec/ruby/core/encoding/_dump_spec.rb
new file mode 100644
index 0000000000..623fe88ec9
--- /dev/null
+++ b/spec/ruby/core/encoding/_dump_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#_dump" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/encoding/_load_spec.rb b/spec/ruby/core/encoding/_load_spec.rb
new file mode 100644
index 0000000000..608098d34b
--- /dev/null
+++ b/spec/ruby/core/encoding/_load_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Encoding._load" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/encoding/aliases_spec.rb b/spec/ruby/core/encoding/aliases_spec.rb
new file mode 100644
index 0000000000..12c6c6cf85
--- /dev/null
+++ b/spec/ruby/core/encoding/aliases_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.aliases" do
+ it "returns a Hash" do
+ Encoding.aliases.should.instance_of?(Hash)
+ end
+
+ it "has Strings as keys" do
+ Encoding.aliases.keys.each do |key|
+ key.should.instance_of?(String)
+ end
+ end
+
+ it "has Strings as values" do
+ Encoding.aliases.values.each do |value|
+ value.should.instance_of?(String)
+ end
+ end
+
+ it "has alias names as its keys" do
+ Encoding.aliases.key?('BINARY').should == true
+ Encoding.aliases.key?('ASCII').should == true
+ end
+
+ it "has the names of the aliased encoding as its values" do
+ Encoding.aliases['BINARY'].should == 'ASCII-8BIT'
+ Encoding.aliases['ASCII'].should == 'US-ASCII'
+ end
+
+ it "has an 'external' key with the external default encoding as its value" do
+ Encoding.aliases['external'].should == Encoding.default_external.name
+ end
+
+ it "has a 'locale' key and its value equals the name of the encoding found by the locale charmap" do
+ Encoding.aliases['locale'].should == Encoding.find(Encoding.locale_charmap).name
+ end
+
+ it "only contains valid aliased encodings" do
+ Encoding.aliases.each do |aliased, original|
+ Encoding.find(aliased).should == Encoding.find(original)
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/ascii_compatible_spec.rb b/spec/ruby/core/encoding/ascii_compatible_spec.rb
new file mode 100644
index 0000000000..04fc159bb8
--- /dev/null
+++ b/spec/ruby/core/encoding/ascii_compatible_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#ascii_compatible?" do
+ it "returns true if self represents an ASCII-compatible encoding" do
+ Encoding::UTF_8.ascii_compatible?.should == true
+ end
+
+ it "returns false if self does not represent an ASCII-compatible encoding" do
+ Encoding::UTF_16LE.ascii_compatible?.should == false
+ end
+
+ it "returns false for UTF_16 and UTF_32" do
+ Encoding::UTF_16.should_not.ascii_compatible?
+ Encoding::UTF_32.should_not.ascii_compatible?
+ end
+
+ it "is always false for dummy encodings" do
+ Encoding.list.select(&:dummy?).each do |encoding|
+ encoding.should_not.ascii_compatible?
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/compatible_spec.rb b/spec/ruby/core/encoding/compatible_spec.rb
new file mode 100644
index 0000000000..0d620e5bf3
--- /dev/null
+++ b/spec/ruby/core/encoding/compatible_spec.rb
@@ -0,0 +1,772 @@
+# encoding: binary
+
+require_relative '../../spec_helper'
+
+# TODO: add IO
+
+describe "Encoding.compatible? String, String" do
+ describe "when the first's Encoding is valid US-ASCII" do
+ before :each do
+ @str = "abc".dup.force_encoding Encoding::US_ASCII
+ end
+
+ it "returns US-ASCII when the second's is US-ASCII" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == Encoding::US_ASCII
+ end
+
+ it "returns US-ASCII if the second String is BINARY and ASCII only" do
+ Encoding.compatible?(@str, "\x7f").should == Encoding::US_ASCII
+ end
+
+ it "returns BINARY if the second String is BINARY but not ASCII only" do
+ Encoding.compatible?(@str, "\xff").should == Encoding::BINARY
+ end
+
+ it "returns US-ASCII if the second String is UTF-8 and ASCII only" do
+ Encoding.compatible?(@str, "\x7f".encode("utf-8")).should == Encoding::US_ASCII
+ end
+
+ it "returns UTF-8 if the second String is UTF-8 but not ASCII only" do
+ Encoding.compatible?(@str, "\u3042".encode("utf-8")).should == Encoding::UTF_8
+ end
+ end
+
+ describe "when the first's Encoding is ASCII compatible and ASCII only" do
+ it "returns the first's Encoding if the second is ASCII compatible and ASCII only" do
+ [ [Encoding, "abc".dup.force_encoding("UTF-8"), "123".dup.force_encoding("Shift_JIS"), Encoding::UTF_8],
+ [Encoding, "123".dup.force_encoding("Shift_JIS"), "abc".dup.force_encoding("UTF-8"), Encoding::Shift_JIS]
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns the first's Encoding if the second is ASCII compatible and ASCII only" do
+ [ [Encoding, "abc".dup.force_encoding("BINARY"), "123".dup.force_encoding("US-ASCII"), Encoding::BINARY],
+ [Encoding, "123".dup.force_encoding("US-ASCII"), "abc".dup.force_encoding("BINARY"), Encoding::US_ASCII]
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns the second's Encoding if the second is ASCII compatible but not ASCII only" do
+ [ [Encoding, "abc".dup.force_encoding("UTF-8"), "\xff".dup.force_encoding("Shift_JIS"), Encoding::Shift_JIS],
+ [Encoding, "123".dup.force_encoding("Shift_JIS"), "\xff".dup.force_encoding("UTF-8"), Encoding::UTF_8],
+ [Encoding, "abc".dup.force_encoding("BINARY"), "\xff".dup.force_encoding("US-ASCII"), Encoding::US_ASCII],
+ [Encoding, "123".dup.force_encoding("US-ASCII"), "\xff".dup.force_encoding("BINARY"), Encoding::BINARY],
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns nil if the second's Encoding is not ASCII compatible" do
+ a = "abc".dup.force_encoding("UTF-8")
+ b = "1234".dup.force_encoding("UTF-16LE")
+ Encoding.compatible?(a, b).should == nil
+ end
+ end
+
+ describe "when the first's Encoding is ASCII compatible but not ASCII only" do
+ it "returns the first's Encoding if the second's is valid US-ASCII" do
+ Encoding.compatible?("\xff", "def".encode("us-ascii")).should == Encoding::BINARY
+ end
+
+ it "returns the first's Encoding if the second's is UTF-8 and ASCII only" do
+ Encoding.compatible?("\xff", "\u{7f}".encode("utf-8")).should == Encoding::BINARY
+ end
+
+ it "returns nil if the second encoding is ASCII compatible but neither String's encoding is ASCII only" do
+ Encoding.compatible?("\xff", "\u3042".encode("utf-8")).should == nil
+ end
+ end
+
+ describe "when the first's Encoding is not ASCII compatible" do
+ before :each do
+ @str = "abc".dup.force_encoding Encoding::UTF_7
+ end
+
+ it "returns nil when the second String is US-ASCII" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == nil
+ end
+
+ it "returns nil when the second String is BINARY and ASCII only" do
+ Encoding.compatible?(@str, "\x7f").should == nil
+ end
+
+ it "returns nil when the second String is BINARY but not ASCII only" do
+ Encoding.compatible?(@str, "\xff").should == nil
+ end
+
+ it "returns the Encoding when the second's Encoding is not ASCII compatible but the same as the first's Encoding" do
+ encoding = Encoding.compatible?(@str, "def".dup.force_encoding("utf-7"))
+ encoding.should == Encoding::UTF_7
+ end
+ end
+
+ describe "when the first's Encoding is invalid" do
+ before :each do
+ @str = "\xff".dup.force_encoding Encoding::UTF_8
+ end
+
+ it "returns the first's Encoding when the second's Encoding is US-ASCII" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == Encoding::UTF_8
+ end
+
+ it "returns the first's Encoding when the second String is ASCII only" do
+ Encoding.compatible?(@str, "\x7f").should == Encoding::UTF_8
+ end
+
+ it "returns nil when the second's Encoding is BINARY but not ASCII only" do
+ Encoding.compatible?(@str, "\xff").should == nil
+ end
+
+ it "returns nil when the second's Encoding is invalid and ASCII only" do
+ Encoding.compatible?(@str, "\x7f\x7f".dup.force_encoding("utf-16be")).should == nil
+ end
+
+ it "returns nil when the second's Encoding is invalid and not ASCII only" do
+ Encoding.compatible?(@str, "\xff\xff".dup.force_encoding("utf-16be")).should == nil
+ end
+
+ it "returns the Encoding when the second's Encoding is invalid but the same as the first" do
+ Encoding.compatible?(@str, @str).should == Encoding::UTF_8
+ end
+ end
+
+ describe "when the first String is empty and the second is not" do
+ describe "and the first's Encoding is ASCII compatible" do
+ before :each do
+ @str = "".dup.force_encoding("utf-8")
+ end
+
+ it "returns the first's encoding when the second String is ASCII only" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == Encoding::UTF_8
+ end
+
+ it "returns the second's encoding when the second String is not ASCII only" do
+ Encoding.compatible?(@str, "def".encode("utf-32le")).should == Encoding::UTF_32LE
+ end
+ end
+
+ describe "when the first's Encoding is not ASCII compatible" do
+ before :each do
+ @str = "".dup.force_encoding Encoding::UTF_7
+ end
+
+ it "returns the second string's encoding" do
+ Encoding.compatible?(@str, "def".encode("us-ascii")).should == Encoding::US_ASCII
+ end
+ end
+ end
+
+ describe "when the second String is empty" do
+ before :each do
+ @str = "abc".dup.force_encoding("utf-7")
+ end
+
+ it "returns the first Encoding" do
+ Encoding.compatible?(@str, "").should == Encoding::UTF_7
+ end
+ end
+
+ # Encoding negotiation depends on whether encodings are ASCII-compatible, empty
+ # and contain only ASCII characters (that take 7 bits). Check US-ASCII, UTF-8 and
+ # BINARY encodings (as most common) as well as an ASCII-compatible, a non-ASCII-compatible and a dummy
+ # encodings in all possible combinations.
+ describe "compatibility matrix" do
+
+# Use the following script to regenerate the matrix:
+#
+# ```
+# # encoding: binary
+#
+# ENCODINGS = [
+# "US-ASCII",
+# "UTF-8",
+# "ASCII-8BIT",
+# "ISO-8859-1", # ASCII-compatible
+# "UTF-16BE", # non-ASCII-compatible
+# "ISO-2022-JP" # dummy
+# ]
+#
+# TYPES = [:empty, :"7bits", :non7bits]
+#
+# VALUES = {
+# empty: "",
+# :"7bits" => "\x01\x01",
+# non7bits: "\x01\x81"
+# }
+#
+# ENCODINGS.product(TYPES, ENCODINGS, TYPES).each do |encoding1, type1, encoding2, type2|
+# value1 = VALUES[type1].dup.force_encoding(encoding1)
+# value2 = VALUES[type2].dup.force_encoding(encoding2)
+#
+# result_encoding = Encoding.compatible?(value1, value2)
+#
+# puts "[#{encoding1.inspect}, #{value1.inspect}, #{encoding2.inspect}, #{value2.inspect}, #{result_encoding&.name.inspect}],"
+# end
+# ```
+
+ matrix = [
+ ["US-ASCII", "", "US-ASCII", "", "US-ASCII"],
+ ["US-ASCII", "", "US-ASCII", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["US-ASCII", "", "UTF-8", "", "US-ASCII"],
+ ["US-ASCII", "", "UTF-8", "\u0001\u0001", "US-ASCII"],
+ ["US-ASCII", "", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["US-ASCII", "", "ASCII-8BIT", "", "US-ASCII"],
+ ["US-ASCII", "", "ASCII-8BIT", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["US-ASCII", "", "ISO-8859-1", "", "US-ASCII"],
+ ["US-ASCII", "", "ISO-8859-1", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["US-ASCII", "", "UTF-16BE", "", "US-ASCII"],
+ ["US-ASCII", "", "UTF-16BE", "\u0101", "UTF-16BE"],
+ ["US-ASCII", "", "UTF-16BE", "\u0181", "UTF-16BE"],
+ ["US-ASCII", "", "ISO-2022-JP", "", "US-ASCII"],
+ ["US-ASCII", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"],
+ ["US-ASCII", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"],
+ ["US-ASCII", "\x01\x01", "US-ASCII", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "US-ASCII", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "UTF-8", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "UTF-8", "\u0001\u0001", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["US-ASCII", "\x01\x01", "ASCII-8BIT", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "ASCII-8BIT", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["US-ASCII", "\x01\x01", "ISO-8859-1", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "ISO-8859-1", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["US-ASCII", "\x01\x01", "UTF-16BE", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "UTF-16BE", "\u0101", nil],
+ ["US-ASCII", "\x01\x01", "UTF-16BE", "\u0181", nil],
+ ["US-ASCII", "\x01\x01", "ISO-2022-JP", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x01", "ISO-2022-JP", "\x01\x01", nil],
+ ["US-ASCII", "\x01\x01", "ISO-2022-JP", "\x01\x81", nil],
+ ["US-ASCII", "\x01\x81", "US-ASCII", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "US-ASCII", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "UTF-8", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "UTF-8", "\u0001\u0001", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "UTF-8", "\u0001\x81", nil],
+ ["US-ASCII", "\x01\x81", "ASCII-8BIT", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "ASCII-8BIT", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "ASCII-8BIT", "\x01\x81", nil],
+ ["US-ASCII", "\x01\x81", "ISO-8859-1", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "ISO-8859-1", "\x01\x01", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "ISO-8859-1", "\x01\x81", nil],
+ ["US-ASCII", "\x01\x81", "UTF-16BE", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "UTF-16BE", "\u0101", nil],
+ ["US-ASCII", "\x01\x81", "UTF-16BE", "\u0181", nil],
+ ["US-ASCII", "\x01\x81", "ISO-2022-JP", "", "US-ASCII"],
+ ["US-ASCII", "\x01\x81", "ISO-2022-JP", "\x01\x01", nil],
+ ["US-ASCII", "\x01\x81", "ISO-2022-JP", "\x01\x81", nil],
+ ["UTF-8", "", "US-ASCII", "", "UTF-8"],
+ ["UTF-8", "", "US-ASCII", "\x01\x01", "UTF-8"],
+ ["UTF-8", "", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["UTF-8", "", "UTF-8", "", "UTF-8"],
+ ["UTF-8", "", "UTF-8", "\u0001\u0001", "UTF-8"],
+ ["UTF-8", "", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["UTF-8", "", "ASCII-8BIT", "", "UTF-8"],
+ ["UTF-8", "", "ASCII-8BIT", "\x01\x01", "UTF-8"],
+ ["UTF-8", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["UTF-8", "", "ISO-8859-1", "", "UTF-8"],
+ ["UTF-8", "", "ISO-8859-1", "\x01\x01", "UTF-8"],
+ ["UTF-8", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["UTF-8", "", "UTF-16BE", "", "UTF-8"],
+ ["UTF-8", "", "UTF-16BE", "\u0101", "UTF-16BE"],
+ ["UTF-8", "", "UTF-16BE", "\u0181", "UTF-16BE"],
+ ["UTF-8", "", "ISO-2022-JP", "", "UTF-8"],
+ ["UTF-8", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"],
+ ["UTF-8", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"],
+ ["UTF-8", "\u0001\u0001", "US-ASCII", "", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "US-ASCII", "\x01\x01", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["UTF-8", "\u0001\u0001", "UTF-8", "", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "UTF-8", "\u0001\u0001", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "ASCII-8BIT", "", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "ASCII-8BIT", "\x01\x01", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["UTF-8", "\u0001\u0001", "ISO-8859-1", "", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "ISO-8859-1", "\x01\x01", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["UTF-8", "\u0001\u0001", "UTF-16BE", "", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "UTF-16BE", "\u0101", nil],
+ ["UTF-8", "\u0001\u0001", "UTF-16BE", "\u0181", nil],
+ ["UTF-8", "\u0001\u0001", "ISO-2022-JP", "", "UTF-8"],
+ ["UTF-8", "\u0001\u0001", "ISO-2022-JP", "\x01\x01", nil],
+ ["UTF-8", "\u0001\u0001", "ISO-2022-JP", "\x01\x81", nil],
+ ["UTF-8", "\u0001\x81", "US-ASCII", "", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "US-ASCII", "\x01\x01", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "US-ASCII", "\x01\x81", nil],
+ ["UTF-8", "\u0001\x81", "UTF-8", "", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "UTF-8", "\u0001\u0001", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "ASCII-8BIT", "", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "ASCII-8BIT", "\x01\x01", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "ASCII-8BIT", "\x01\x81", nil],
+ ["UTF-8", "\u0001\x81", "ISO-8859-1", "", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "ISO-8859-1", "\x01\x01", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "ISO-8859-1", "\x01\x81", nil],
+ ["UTF-8", "\u0001\x81", "UTF-16BE", "", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "UTF-16BE", "\u0101", nil],
+ ["UTF-8", "\u0001\x81", "UTF-16BE", "\u0181", nil],
+ ["UTF-8", "\u0001\x81", "ISO-2022-JP", "", "UTF-8"],
+ ["UTF-8", "\u0001\x81", "ISO-2022-JP", "\x01\x01", nil],
+ ["UTF-8", "\u0001\x81", "ISO-2022-JP", "\x01\x81", nil],
+ ["ASCII-8BIT", "", "US-ASCII", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "US-ASCII", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["ASCII-8BIT", "", "UTF-8", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "UTF-8", "\u0001\u0001", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["ASCII-8BIT", "", "ASCII-8BIT", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "ISO-8859-1", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "ISO-8859-1", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["ASCII-8BIT", "", "UTF-16BE", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "UTF-16BE", "\u0101", "UTF-16BE"],
+ ["ASCII-8BIT", "", "UTF-16BE", "\u0181", "UTF-16BE"],
+ ["ASCII-8BIT", "", "ISO-2022-JP", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"],
+ ["ASCII-8BIT", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"],
+ ["ASCII-8BIT", "\x01\x01", "US-ASCII", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "US-ASCII", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["ASCII-8BIT", "\x01\x01", "UTF-8", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "UTF-8", "\u0001\u0001", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["ASCII-8BIT", "\x01\x01", "ASCII-8BIT", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "ISO-8859-1", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "ISO-8859-1", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["ASCII-8BIT", "\x01\x01", "UTF-16BE", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "UTF-16BE", "\u0101", nil],
+ ["ASCII-8BIT", "\x01\x01", "UTF-16BE", "\u0181", nil],
+ ["ASCII-8BIT", "\x01\x01", "ISO-2022-JP", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x01", "ISO-2022-JP", "\x01\x01", nil],
+ ["ASCII-8BIT", "\x01\x01", "ISO-2022-JP", "\x01\x81", nil],
+ ["ASCII-8BIT", "\x01\x81", "US-ASCII", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "US-ASCII", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "US-ASCII", "\x01\x81", nil],
+ ["ASCII-8BIT", "\x01\x81", "UTF-8", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "UTF-8", "\u0001\u0001", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "UTF-8", "\u0001\x81", nil],
+ ["ASCII-8BIT", "\x01\x81", "ASCII-8BIT", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "ISO-8859-1", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "ISO-8859-1", "\x01\x01", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "ISO-8859-1", "\x01\x81", nil],
+ ["ASCII-8BIT", "\x01\x81", "UTF-16BE", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "UTF-16BE", "\u0101", nil],
+ ["ASCII-8BIT", "\x01\x81", "UTF-16BE", "\u0181", nil],
+ ["ASCII-8BIT", "\x01\x81", "ISO-2022-JP", "", "ASCII-8BIT"],
+ ["ASCII-8BIT", "\x01\x81", "ISO-2022-JP", "\x01\x01", nil],
+ ["ASCII-8BIT", "\x01\x81", "ISO-2022-JP", "\x01\x81", nil],
+ ["ISO-8859-1", "", "US-ASCII", "", "ISO-8859-1"],
+ ["ISO-8859-1", "", "US-ASCII", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["ISO-8859-1", "", "UTF-8", "", "ISO-8859-1"],
+ ["ISO-8859-1", "", "UTF-8", "\u0001\u0001", "ISO-8859-1"],
+ ["ISO-8859-1", "", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["ISO-8859-1", "", "ASCII-8BIT", "", "ISO-8859-1"],
+ ["ISO-8859-1", "", "ASCII-8BIT", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["ISO-8859-1", "", "ISO-8859-1", "", "ISO-8859-1"],
+ ["ISO-8859-1", "", "ISO-8859-1", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["ISO-8859-1", "", "UTF-16BE", "", "ISO-8859-1"],
+ ["ISO-8859-1", "", "UTF-16BE", "\u0101", "UTF-16BE"],
+ ["ISO-8859-1", "", "UTF-16BE", "\u0181", "UTF-16BE"],
+ ["ISO-8859-1", "", "ISO-2022-JP", "", "ISO-8859-1"],
+ ["ISO-8859-1", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"],
+ ["ISO-8859-1", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"],
+ ["ISO-8859-1", "\x01\x01", "US-ASCII", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "US-ASCII", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["ISO-8859-1", "\x01\x01", "UTF-8", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "UTF-8", "\u0001\u0001", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["ISO-8859-1", "\x01\x01", "ASCII-8BIT", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "ASCII-8BIT", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["ISO-8859-1", "\x01\x01", "ISO-8859-1", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "ISO-8859-1", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "UTF-16BE", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "UTF-16BE", "\u0101", nil],
+ ["ISO-8859-1", "\x01\x01", "UTF-16BE", "\u0181", nil],
+ ["ISO-8859-1", "\x01\x01", "ISO-2022-JP", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x01", "ISO-2022-JP", "\x01\x01", nil],
+ ["ISO-8859-1", "\x01\x01", "ISO-2022-JP", "\x01\x81", nil],
+ ["ISO-8859-1", "\x01\x81", "US-ASCII", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "US-ASCII", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "US-ASCII", "\x01\x81", nil],
+ ["ISO-8859-1", "\x01\x81", "UTF-8", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "UTF-8", "\u0001\u0001", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "UTF-8", "\u0001\x81", nil],
+ ["ISO-8859-1", "\x01\x81", "ASCII-8BIT", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "ASCII-8BIT", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "ASCII-8BIT", "\x01\x81", nil],
+ ["ISO-8859-1", "\x01\x81", "ISO-8859-1", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "ISO-8859-1", "\x01\x01", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "UTF-16BE", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "UTF-16BE", "\u0101", nil],
+ ["ISO-8859-1", "\x01\x81", "UTF-16BE", "\u0181", nil],
+ ["ISO-8859-1", "\x01\x81", "ISO-2022-JP", "", "ISO-8859-1"],
+ ["ISO-8859-1", "\x01\x81", "ISO-2022-JP", "\x01\x01", nil],
+ ["ISO-8859-1", "\x01\x81", "ISO-2022-JP", "\x01\x81", nil],
+ ["UTF-16BE", "", "US-ASCII", "", "UTF-16BE"],
+ ["UTF-16BE", "", "US-ASCII", "\x01\x01", "US-ASCII"],
+ ["UTF-16BE", "", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["UTF-16BE", "", "UTF-8", "", "UTF-16BE"],
+ ["UTF-16BE", "", "UTF-8", "\u0001\u0001", "UTF-8"],
+ ["UTF-16BE", "", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["UTF-16BE", "", "ASCII-8BIT", "", "UTF-16BE"],
+ ["UTF-16BE", "", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"],
+ ["UTF-16BE", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["UTF-16BE", "", "ISO-8859-1", "", "UTF-16BE"],
+ ["UTF-16BE", "", "ISO-8859-1", "\x01\x01", "ISO-8859-1"],
+ ["UTF-16BE", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["UTF-16BE", "", "UTF-16BE", "", "UTF-16BE"],
+ ["UTF-16BE", "", "UTF-16BE", "\u0101", "UTF-16BE"],
+ ["UTF-16BE", "", "UTF-16BE", "\u0181", "UTF-16BE"],
+ ["UTF-16BE", "", "ISO-2022-JP", "", "UTF-16BE"],
+ ["UTF-16BE", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"],
+ ["UTF-16BE", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"],
+ ["UTF-16BE", "\u0101", "US-ASCII", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0101", "US-ASCII", "\x01\x01", nil],
+ ["UTF-16BE", "\u0101", "US-ASCII", "\x01\x81", nil],
+ ["UTF-16BE", "\u0101", "UTF-8", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0101", "UTF-8", "\u0001\u0001", nil],
+ ["UTF-16BE", "\u0101", "UTF-8", "\u0001\x81", nil],
+ ["UTF-16BE", "\u0101", "ASCII-8BIT", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0101", "ASCII-8BIT", "\x01\x01", nil],
+ ["UTF-16BE", "\u0101", "ASCII-8BIT", "\x01\x81", nil],
+ ["UTF-16BE", "\u0101", "ISO-8859-1", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0101", "ISO-8859-1", "\x01\x01", nil],
+ ["UTF-16BE", "\u0101", "ISO-8859-1", "\x01\x81", nil],
+ ["UTF-16BE", "\u0101", "UTF-16BE", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0101", "UTF-16BE", "\u0101", "UTF-16BE"],
+ ["UTF-16BE", "\u0101", "UTF-16BE", "\u0181", "UTF-16BE"],
+ ["UTF-16BE", "\u0101", "ISO-2022-JP", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0101", "ISO-2022-JP", "\x01\x01", nil],
+ ["UTF-16BE", "\u0101", "ISO-2022-JP", "\x01\x81", nil],
+ ["UTF-16BE", "\u0181", "US-ASCII", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0181", "US-ASCII", "\x01\x01", nil],
+ ["UTF-16BE", "\u0181", "US-ASCII", "\x01\x81", nil],
+ ["UTF-16BE", "\u0181", "UTF-8", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0181", "UTF-8", "\u0001\u0001", nil],
+ ["UTF-16BE", "\u0181", "UTF-8", "\u0001\x81", nil],
+ ["UTF-16BE", "\u0181", "ASCII-8BIT", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0181", "ASCII-8BIT", "\x01\x01", nil],
+ ["UTF-16BE", "\u0181", "ASCII-8BIT", "\x01\x81", nil],
+ ["UTF-16BE", "\u0181", "ISO-8859-1", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0181", "ISO-8859-1", "\x01\x01", nil],
+ ["UTF-16BE", "\u0181", "ISO-8859-1", "\x01\x81", nil],
+ ["UTF-16BE", "\u0181", "UTF-16BE", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0181", "UTF-16BE", "\u0101", "UTF-16BE"],
+ ["UTF-16BE", "\u0181", "UTF-16BE", "\u0181", "UTF-16BE"],
+ ["UTF-16BE", "\u0181", "ISO-2022-JP", "", "UTF-16BE"],
+ ["UTF-16BE", "\u0181", "ISO-2022-JP", "\x01\x01", nil],
+ ["UTF-16BE", "\u0181", "ISO-2022-JP", "\x01\x81", nil],
+ ["ISO-2022-JP", "", "US-ASCII", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "", "US-ASCII", "\x01\x01", "US-ASCII"],
+ ["ISO-2022-JP", "", "US-ASCII", "\x01\x81", "US-ASCII"],
+ ["ISO-2022-JP", "", "UTF-8", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "", "UTF-8", "\u0001\u0001", "UTF-8"],
+ ["ISO-2022-JP", "", "UTF-8", "\u0001\x81", "UTF-8"],
+ ["ISO-2022-JP", "", "ASCII-8BIT", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "", "ASCII-8BIT", "\x01\x01", "ASCII-8BIT"],
+ ["ISO-2022-JP", "", "ASCII-8BIT", "\x01\x81", "ASCII-8BIT"],
+ ["ISO-2022-JP", "", "ISO-8859-1", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "", "ISO-8859-1", "\x01\x01", "ISO-8859-1"],
+ ["ISO-2022-JP", "", "ISO-8859-1", "\x01\x81", "ISO-8859-1"],
+ ["ISO-2022-JP", "", "UTF-16BE", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "", "UTF-16BE", "\u0101", "UTF-16BE"],
+ ["ISO-2022-JP", "", "UTF-16BE", "\u0181", "UTF-16BE"],
+ ["ISO-2022-JP", "", "ISO-2022-JP", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"],
+ ["ISO-2022-JP", "", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x01", "US-ASCII", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x01", "US-ASCII", "\x01\x01", nil],
+ ["ISO-2022-JP", "\x01\x01", "US-ASCII", "\x01\x81", nil],
+ ["ISO-2022-JP", "\x01\x01", "UTF-8", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x01", "UTF-8", "\u0001\u0001", nil],
+ ["ISO-2022-JP", "\x01\x01", "UTF-8", "\u0001\x81", nil],
+ ["ISO-2022-JP", "\x01\x01", "ASCII-8BIT", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x01", "ASCII-8BIT", "\x01\x01", nil],
+ ["ISO-2022-JP", "\x01\x01", "ASCII-8BIT", "\x01\x81", nil],
+ ["ISO-2022-JP", "\x01\x01", "ISO-8859-1", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x01", "ISO-8859-1", "\x01\x01", nil],
+ ["ISO-2022-JP", "\x01\x01", "ISO-8859-1", "\x01\x81", nil],
+ ["ISO-2022-JP", "\x01\x01", "UTF-16BE", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x01", "UTF-16BE", "\u0101", nil],
+ ["ISO-2022-JP", "\x01\x01", "UTF-16BE", "\u0181", nil],
+ ["ISO-2022-JP", "\x01\x01", "ISO-2022-JP", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x01", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x01", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x81", "US-ASCII", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x81", "US-ASCII", "\x01\x01", nil],
+ ["ISO-2022-JP", "\x01\x81", "US-ASCII", "\x01\x81", nil],
+ ["ISO-2022-JP", "\x01\x81", "UTF-8", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x81", "UTF-8", "\u0001\u0001", nil],
+ ["ISO-2022-JP", "\x01\x81", "UTF-8", "\u0001\x81", nil],
+ ["ISO-2022-JP", "\x01\x81", "ASCII-8BIT", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x81", "ASCII-8BIT", "\x01\x01", nil],
+ ["ISO-2022-JP", "\x01\x81", "ASCII-8BIT", "\x01\x81", nil],
+ ["ISO-2022-JP", "\x01\x81", "ISO-8859-1", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x81", "ISO-8859-1", "\x01\x01", nil],
+ ["ISO-2022-JP", "\x01\x81", "ISO-8859-1", "\x01\x81", nil],
+ ["ISO-2022-JP", "\x01\x81", "UTF-16BE", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x81", "UTF-16BE", "\u0101", nil],
+ ["ISO-2022-JP", "\x01\x81", "UTF-16BE", "\u0181", nil],
+ ["ISO-2022-JP", "\x01\x81", "ISO-2022-JP", "", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x81", "ISO-2022-JP", "\x01\x01", "ISO-2022-JP"],
+ ["ISO-2022-JP", "\x01\x81", "ISO-2022-JP", "\x01\x81", "ISO-2022-JP"],
+ ]
+
+ matrix.each do |encoding1, value1, encoding2, value2, compatible_encoding|
+ it "returns #{compatible_encoding} for #{value1.inspect} in #{encoding1} and #{value2.inspect} in #{encoding2}" do
+ actual_encoding = Encoding.compatible?(value1.dup.force_encoding(encoding1), value2.dup.force_encoding(encoding2))
+ actual_encoding&.name.should == compatible_encoding
+ end
+ end
+ end
+end
+
+describe "Encoding.compatible? String, Regexp" do
+ it "returns US-ASCII if both are US-ASCII" do
+ str = "abc".dup.force_encoding("us-ascii")
+ Encoding.compatible?(str, /abc/).should == Encoding::US_ASCII
+ end
+
+ it "returns the String's Encoding if it is not US-ASCII but both are ASCII only" do
+ [ [Encoding, "abc", Encoding::BINARY],
+ [Encoding, "abc".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "abc".encode("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "abc".encode("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, /abc/)
+ end
+
+ it "returns the String's Encoding if the String is not ASCII only" do
+ [ [Encoding, "\xff", Encoding::BINARY],
+ [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, /abc/)
+ end
+
+ it "returns the Regexp's Encoding if the String is ASCII only and the Regexp is not" do
+ r = Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp"))
+ Encoding.compatible?("hello".dup.force_encoding("utf-8"), r).should == Encoding::EUC_JP
+ end
+end
+
+describe "Encoding.compatible? String, Symbol" do
+ it "returns US-ASCII if both are ASCII only" do
+ str = "abc".dup.force_encoding("us-ascii")
+ Encoding.compatible?(str, :abc).should == Encoding::US_ASCII
+ end
+
+ it "returns the String's Encoding if it is not US-ASCII but both are ASCII only" do
+ [ [Encoding, "abc", Encoding::BINARY],
+ [Encoding, "abc".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "abc".encode("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "abc".encode("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, :abc)
+ end
+
+ it "returns the String's Encoding if the String is not ASCII only" do
+ [ [Encoding, "\xff", Encoding::BINARY],
+ [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, :abc)
+ end
+end
+
+describe "Encoding.compatible? String, Encoding" do
+ it "returns nil if the String's encoding is not ASCII compatible" do
+ Encoding.compatible?("abc".encode("utf-32le"), Encoding::US_ASCII).should == nil
+ end
+
+ it "returns nil if the Encoding is not ASCII compatible" do
+ Encoding.compatible?("abc".encode("us-ascii"), Encoding::UTF_32LE).should == nil
+ end
+
+ it "returns the String's encoding if the Encoding is US-ASCII" do
+ [ [Encoding, "\xff", Encoding::BINARY],
+ [Encoding, "\u3042".encode("utf-8"), Encoding::UTF_8],
+ [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp"), Encoding::EUC_JP],
+ [Encoding, "\x82\xa0".dup.force_encoding("shift_jis"), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, Encoding::US_ASCII)
+ end
+
+ it "returns the Encoding if the String's encoding is ASCII compatible and the String is ASCII only" do
+ str = "abc".encode("utf-8")
+
+ Encoding.compatible?(str, Encoding::BINARY).should == Encoding::BINARY
+ Encoding.compatible?(str, Encoding::UTF_8).should == Encoding::UTF_8
+ Encoding.compatible?(str, Encoding::EUC_JP).should == Encoding::EUC_JP
+ Encoding.compatible?(str, Encoding::Shift_JIS).should == Encoding::Shift_JIS
+ end
+
+ it "returns nil if the String's encoding is ASCII compatible but the string is not ASCII only" do
+ Encoding.compatible?("\u3042".encode("utf-8"), Encoding::BINARY).should == nil
+ end
+end
+
+describe "Encoding.compatible? Regexp, String" do
+ it "returns US-ASCII if both are US-ASCII" do
+ str = "abc".dup.force_encoding("us-ascii")
+ Encoding.compatible?(/abc/, str).should == Encoding::US_ASCII
+ end
+
+ it "returns the String's Encoding when the String is ASCII only with a different encoding" do
+ r = Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp"))
+ Encoding.compatible?(r, "hello".dup.force_encoding("utf-8")).should == Encoding::UTF_8
+ end
+
+ it "returns the Regexp's Encoding if the String has the same non-ASCII encoding" do
+ r = Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp"))
+ Encoding.compatible?(r, "hello".dup.force_encoding("euc-jp")).should == Encoding::EUC_JP
+ end
+end
+
+describe "Encoding.compatible? Regexp, Regexp" do
+ it "returns US-ASCII if both are US-ASCII" do
+ Encoding.compatible?(/abc/, /def/).should == Encoding::US_ASCII
+ end
+
+ it "returns the first's Encoding if it is not US-ASCII and not ASCII only" do
+ [ [Encoding, Regexp.new("\xff"), Encoding::BINARY],
+ [Encoding, Regexp.new("\u3042".encode("utf-8")), Encoding::UTF_8],
+ [Encoding, Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp")), Encoding::EUC_JP],
+ [Encoding, Regexp.new("\x82\xa0".dup.force_encoding("shift_jis")), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, /abc/)
+ end
+end
+
+describe "Encoding.compatible? Regexp, Symbol" do
+ it "returns US-ASCII if both are US-ASCII" do
+ Encoding.compatible?(/abc/, :def).should == Encoding::US_ASCII
+ end
+
+ it "returns the first's Encoding if it is not US-ASCII and not ASCII only" do
+ [ [Encoding, Regexp.new("\xff"), Encoding::BINARY],
+ [Encoding, Regexp.new("\u3042".encode("utf-8")), Encoding::UTF_8],
+ [Encoding, Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp")), Encoding::EUC_JP],
+ [Encoding, Regexp.new("\x82\xa0".dup.force_encoding("shift_jis")), Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, /abc/)
+ end
+end
+
+describe "Encoding.compatible? Symbol, String" do
+ it "returns US-ASCII if both are ASCII only" do
+ str = "abc".dup.force_encoding("us-ascii")
+ Encoding.compatible?(str, :abc).should == Encoding::US_ASCII
+ end
+end
+
+describe "Encoding.compatible? Symbol, Regexp" do
+ it "returns US-ASCII if both are US-ASCII" do
+ Encoding.compatible?(:abc, /def/).should == Encoding::US_ASCII
+ end
+
+ it "returns the Regexp's Encoding if it is not US-ASCII and not ASCII only" do
+ a = Regexp.new("\xff")
+ b = Regexp.new("\u3042".encode("utf-8"))
+ c = Regexp.new("\xa4\xa2".dup.force_encoding("euc-jp"))
+ d = Regexp.new("\x82\xa0".dup.force_encoding("shift_jis"))
+
+ [ [Encoding, :abc, a, Encoding::BINARY],
+ [Encoding, :abc, b, Encoding::UTF_8],
+ [Encoding, :abc, c, Encoding::EUC_JP],
+ [Encoding, :abc, d, Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?)
+ end
+end
+
+describe "Encoding.compatible? Symbol, Symbol" do
+ it "returns US-ASCII if both are US-ASCII" do
+ Encoding.compatible?(:abc, :def).should == Encoding::US_ASCII
+ end
+
+ it "returns the first's Encoding if it is not ASCII only" do
+ [ [Encoding, "\xff".to_sym, Encoding::BINARY],
+ [Encoding, "\u3042".encode("utf-8").to_sym, Encoding::UTF_8],
+ [Encoding, "\xa4\xa2".dup.force_encoding("euc-jp").to_sym, Encoding::EUC_JP],
+ [Encoding, "\x82\xa0".dup.force_encoding("shift_jis").to_sym, Encoding::Shift_JIS],
+ ].should be_computed_by(:compatible?, :abc)
+ end
+end
+
+describe "Encoding.compatible? Encoding, Encoding" do
+ it "returns nil if one of the encodings is a dummy encoding" do
+ [ [Encoding, Encoding::UTF_7, Encoding::US_ASCII, nil],
+ [Encoding, Encoding::US_ASCII, Encoding::UTF_7, nil],
+ [Encoding, Encoding::EUC_JP, Encoding::UTF_7, nil],
+ [Encoding, Encoding::UTF_7, Encoding::EUC_JP, nil],
+ [Encoding, Encoding::UTF_7, Encoding::BINARY, nil],
+ [Encoding, Encoding::BINARY, Encoding::UTF_7, nil],
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns nil if one of the encodings is not US-ASCII" do
+ [ [Encoding, Encoding::UTF_8, Encoding::BINARY, nil],
+ [Encoding, Encoding::BINARY, Encoding::UTF_8, nil],
+ [Encoding, Encoding::BINARY, Encoding::EUC_JP, nil],
+ [Encoding, Encoding::Shift_JIS, Encoding::EUC_JP, nil],
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns the first if the second is US-ASCII" do
+ [ [Encoding, Encoding::UTF_8, Encoding::US_ASCII, Encoding::UTF_8],
+ [Encoding, Encoding::EUC_JP, Encoding::US_ASCII, Encoding::EUC_JP],
+ [Encoding, Encoding::Shift_JIS, Encoding::US_ASCII, Encoding::Shift_JIS],
+ [Encoding, Encoding::BINARY, Encoding::US_ASCII, Encoding::BINARY],
+ ].should be_computed_by(:compatible?)
+ end
+
+ it "returns the Encoding if both are the same" do
+ [ [Encoding, Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8],
+ [Encoding, Encoding::US_ASCII, Encoding::US_ASCII, Encoding::US_ASCII],
+ [Encoding, Encoding::BINARY, Encoding::BINARY, Encoding::BINARY],
+ [Encoding, Encoding::UTF_7, Encoding::UTF_7, Encoding::UTF_7],
+ ].should be_computed_by(:compatible?)
+ end
+end
+
+describe "Encoding.compatible? Object, Object" do
+ it "returns nil for Object, String" do
+ Encoding.compatible?(Object.new, "abc").should == nil
+ end
+
+ it "returns nil for Object, Regexp" do
+ Encoding.compatible?(Object.new, /./).should == nil
+ end
+
+ it "returns nil for Object, Symbol" do
+ Encoding.compatible?(Object.new, :sym).should == nil
+ end
+
+ it "returns nil for String, Object" do
+ Encoding.compatible?("abc", Object.new).should == nil
+ end
+
+ it "returns nil for Regexp, Object" do
+ Encoding.compatible?(/./, Object.new).should == nil
+ end
+
+ it "returns nil for Symbol, Object" do
+ Encoding.compatible?(:sym, Object.new).should == nil
+ end
+end
+
+describe "Encoding.compatible? nil, nil" do
+ it "returns nil" do
+ Encoding.compatible?(nil, nil).should == nil
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/asciicompat_encoding_spec.rb b/spec/ruby/core/encoding/converter/asciicompat_encoding_spec.rb
new file mode 100644
index 0000000000..07c7a88356
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/asciicompat_encoding_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter.asciicompat_encoding" do
+ it "accepts an encoding name as a String argument" do
+ -> { Encoding::Converter.asciicompat_encoding('UTF-8') }.
+ should_not raise_error
+ end
+
+ it "coerces non-String/Encoding objects with #to_str" do
+ str = mock('string')
+ str.should_receive(:to_str).at_least(1).times.and_return('string')
+ Encoding::Converter.asciicompat_encoding(str)
+ end
+
+ it "accepts an Encoding object as an argument" do
+ Encoding::Converter.
+ asciicompat_encoding(Encoding.find("ISO-2022-JP")).
+ should == Encoding::Converter.asciicompat_encoding("ISO-2022-JP")
+ end
+
+ it "returns a corresponding ASCII compatible encoding for ASCII-incompatible encodings" do
+ Encoding::Converter.asciicompat_encoding('UTF-16BE').should == Encoding::UTF_8
+ Encoding::Converter.asciicompat_encoding("ISO-2022-JP").should == Encoding.find("stateless-ISO-2022-JP")
+ end
+
+ it "returns nil when the given encoding is ASCII compatible" do
+ Encoding::Converter.asciicompat_encoding('ASCII').should == nil
+ Encoding::Converter.asciicompat_encoding('UTF-8').should == nil
+ end
+
+ it "handles encoding names who resolve to nil encodings" do
+ internal = Encoding.default_internal
+ Encoding.default_internal = nil
+ Encoding::Converter.asciicompat_encoding('internal').should == nil
+ Encoding.default_internal = internal
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/constants_spec.rb b/spec/ruby/core/encoding/converter/constants_spec.rb
new file mode 100644
index 0000000000..e2d21b5429
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/constants_spec.rb
@@ -0,0 +1,131 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter::INVALID_MASK" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:INVALID_MASK, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::INVALID_MASK.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::INVALID_REPLACE" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:INVALID_REPLACE, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::INVALID_REPLACE.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::UNDEF_MASK" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:UNDEF_MASK, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::UNDEF_MASK.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::UNDEF_REPLACE" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:UNDEF_REPLACE, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::UNDEF_REPLACE.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::UNDEF_HEX_CHARREF" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:UNDEF_HEX_CHARREF, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::UNDEF_HEX_CHARREF.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::PARTIAL_INPUT" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:PARTIAL_INPUT, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::PARTIAL_INPUT.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::AFTER_OUTPUT" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:AFTER_OUTPUT, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::AFTER_OUTPUT.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::UNIVERSAL_NEWLINE_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:UNIVERSAL_NEWLINE_DECORATOR, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::UNIVERSAL_NEWLINE_DECORATOR.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::CRLF_NEWLINE_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:CRLF_NEWLINE_DECORATOR, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::CRLF_NEWLINE_DECORATOR.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::CR_NEWLINE_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:CR_NEWLINE_DECORATOR, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::CR_NEWLINE_DECORATOR.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::XML_TEXT_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:XML_TEXT_DECORATOR, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::XML_TEXT_DECORATOR.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::XML_ATTR_CONTENT_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:XML_ATTR_CONTENT_DECORATOR, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::XML_ATTR_CONTENT_DECORATOR.should.instance_of?(Integer)
+ end
+end
+
+describe "Encoding::Converter::XML_ATTR_QUOTE_DECORATOR" do
+ it "exists" do
+ Encoding::Converter.should.const_defined?(:XML_ATTR_QUOTE_DECORATOR, false)
+ end
+
+ it "has an Integer value" do
+ Encoding::Converter::XML_ATTR_QUOTE_DECORATOR.should.instance_of?(Integer)
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/convert_spec.rb b/spec/ruby/core/encoding/converter/convert_spec.rb
new file mode 100644
index 0000000000..c95e88a491
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/convert_spec.rb
@@ -0,0 +1,45 @@
+# encoding: binary
+# frozen_string_literal: true
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#convert" do
+ it "returns a String" do
+ ec = Encoding::Converter.new('ascii', 'utf-8')
+ ec.convert('glark').should.instance_of?(String)
+ end
+
+ it "sets the encoding of the result to the target encoding" do
+ ec = Encoding::Converter.new('ascii', 'utf-8')
+ str = 'glark'.dup.force_encoding('ascii')
+ ec.convert(str).encoding.should == Encoding::UTF_8
+ end
+
+ it "transcodes the given String to the target encoding" do
+ ec = Encoding::Converter.new("utf-8", "euc-jp")
+ ec.convert("\u3042".dup.force_encoding('UTF-8')).should == \
+ "\xA4\xA2".dup.force_encoding('EUC-JP')
+ end
+
+ it "allows Strings of different encodings to the source encoding" do
+ ec = Encoding::Converter.new('ascii', 'utf-8')
+ str = 'glark'.dup.force_encoding('SJIS')
+ ec.convert(str).encoding.should == Encoding::UTF_8
+ end
+
+ it "reuses the given encoding pair if called multiple times" do
+ ec = Encoding::Converter.new('ascii', 'SJIS')
+ ec.convert('a'.dup.force_encoding('ASCII')).should == 'a'.dup.force_encoding('SJIS')
+ ec.convert('b'.dup.force_encoding('ASCII')).should == 'b'.dup.force_encoding('SJIS')
+ end
+
+ it "raises UndefinedConversionError if the String contains characters invalid for the target encoding" do
+ ec = Encoding::Converter.new('UTF-8', Encoding.find('macCyrillic'))
+ -> { ec.convert("\u{6543}".dup.force_encoding('UTF-8')) }.should.raise(Encoding::UndefinedConversionError)
+ end
+
+ it "raises an ArgumentError if called on a finished stream" do
+ ec = Encoding::Converter.new('UTF-8', Encoding.find('macCyrillic'))
+ ec.finish
+ -> { ec.convert("\u{65}") }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/convpath_spec.rb b/spec/ruby/core/encoding/converter/convpath_spec.rb
new file mode 100644
index 0000000000..23f1e5dc33
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/convpath_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#convpath" do
+ it "returns an Array with a single element if there is a direct converter" do
+ cp = Encoding::Converter.new('ASCII', 'UTF-8').convpath
+ cp.should == [[Encoding::US_ASCII, Encoding::UTF_8]]
+ end
+
+ it "returns multiple encoding pairs when direct conversion is impossible" do
+ cp = Encoding::Converter.new('ascii','Big5').convpath
+ cp.should == [
+ [Encoding::US_ASCII, Encoding::UTF_8],
+ [Encoding::UTF_8, Encoding::Big5]
+ ]
+ end
+
+ it "indicates if crlf_newline conversion would occur" do
+ ec = Encoding::Converter.new("ISo-8859-1", "EUC-JP", crlf_newline: true)
+ ec.convpath.last.should == "crlf_newline"
+
+ ec = Encoding::Converter.new("ASCII", "UTF-8", crlf_newline: false)
+ ec.convpath.last.should_not == "crlf_newline"
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/destination_encoding_spec.rb b/spec/ruby/core/encoding/converter/destination_encoding_spec.rb
new file mode 100644
index 0000000000..481a857909
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/destination_encoding_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#destination_encoding" do
+ it "returns the destination encoding as an Encoding object" do
+ ec = Encoding::Converter.new('ASCII','Big5')
+ ec.destination_encoding.should == Encoding::BIG5
+
+ ec = Encoding::Converter.new('SJIS','EUC-JP')
+ ec.destination_encoding.should == Encoding::EUC_JP
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/finish_spec.rb b/spec/ruby/core/encoding/converter/finish_spec.rb
new file mode 100644
index 0000000000..e13b7415f8
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/finish_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#finish" do
+ before :each do
+ @ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ end
+
+ it "returns a String" do
+ @ec.convert('foo')
+ @ec.finish.should.instance_of?(String)
+ end
+
+ it "returns an empty String if there is nothing more to convert" do
+ @ec.convert("glark")
+ @ec.finish.should == ""
+ 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')
+ end
+
+ it "returns a String in the destination encoding" do
+ @ec.convert("glark")
+ @ec.finish.encoding.should == Encoding::ISO2022_JP
+ end
+
+ it "returns an empty String if self was not given anything to convert" do
+ @ec.finish.should == ""
+ end
+
+ it "returns an empty String on subsequent invocations" do
+ @ec.finish.should == ""
+ @ec.finish.should == ""
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/insert_output_spec.rb b/spec/ruby/core/encoding/converter/insert_output_spec.rb
new file mode 100644
index 0000000000..1346adde1e
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/insert_output_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#insert_output" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/encoding/converter/inspect_spec.rb b/spec/ruby/core/encoding/converter/inspect_spec.rb
new file mode 100644
index 0000000000..3170ee451f
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/inspect_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#inspect" do
+ it "includes the source and destination encodings in the return value" do
+ source = Encoding::UTF_8
+ destination = Encoding::UTF_16LE
+
+ output = "#<Encoding::Converter: #{source.name} to #{destination.name}>"
+
+ x = Encoding::Converter.new(source, destination)
+ x.inspect.should == output
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/last_error_spec.rb b/spec/ruby/core/encoding/converter/last_error_spec.rb
new file mode 100644
index 0000000000..3984a628f5
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/last_error_spec.rb
@@ -0,0 +1,91 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#last_error" do
+ it "returns nil when the no conversion has been attempted" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.last_error.should == nil
+ end
+
+ it "returns nil when the last conversion did not produce an error" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.convert('a'.dup.force_encoding('ascii'))
+ ec.last_error.should == nil
+ end
+
+ it "returns nil when #primitive_convert last returned :destination_buffer_full" do
+ ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ ec.primitive_convert(+"\u{9999}", +"", 0, 0, partial_input: false) \
+ .should == :destination_buffer_full
+ ec.last_error.should == nil
+ end
+
+ it "returns nil when #primitive_convert last returned :finished" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("glark".dup.force_encoding('utf-8'), +"").should == :finished
+ ec.last_error.should == nil
+ end
+
+ it "returns nil if the last conversion succeeded but the penultimate failed" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert(+"\xf1abcd", +"").should == :invalid_byte_sequence
+ ec.primitive_convert("glark".dup.force_encoding('utf-8'), +"").should == :finished
+ ec.last_error.should == nil
+ end
+
+ it "returns an Encoding::InvalidByteSequenceError when #primitive_convert last returned :invalid_byte_sequence" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert(+"\xf1abcd", +"").should == :invalid_byte_sequence
+ ec.last_error.should.instance_of?(Encoding::InvalidByteSequenceError)
+ end
+
+ it "returns an Encoding::UndefinedConversionError when #primitive_convert last returned :undefined_conversion" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert(+"\u{9876}", +"").should == :undefined_conversion
+ ec.last_error.should.instance_of?(Encoding::UndefinedConversionError)
+ end
+
+ it "returns an Encoding::InvalidByteSequenceError when #primitive_convert last returned :incomplete_input" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert(+"\xa4", +"", nil, 10).should == :incomplete_input
+ ec.last_error.should.instance_of?(Encoding::InvalidByteSequenceError)
+ end
+
+ it "returns an Encoding::InvalidByteSequenceError when the last call to #convert produced one" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ exception = nil
+ -> {
+ ec.convert("\xf1abcd")
+ }.should.raise(Encoding::InvalidByteSequenceError) { |e|
+ exception = e
+ }
+ ec.last_error.should.instance_of?(Encoding::InvalidByteSequenceError)
+ ec.last_error.message.should == exception.message
+ end
+
+ it "returns an Encoding::UndefinedConversionError when the last call to #convert produced one" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ exception = nil
+ -> {
+ ec.convert("\u{9899}")
+ }.should.raise(Encoding::UndefinedConversionError) { |e|
+ exception = e
+ }
+ ec.last_error.should.instance_of?(Encoding::UndefinedConversionError)
+ ec.last_error.message.should == exception.message
+ ec.last_error.message.should.include? "from UTF-8 to ISO-8859-1"
+ end
+
+ it "returns the last error of #convert with a message showing the transcoding path" do
+ ec = Encoding::Converter.new("iso-8859-1", "Big5")
+ exception = nil
+ -> {
+ ec.convert("\xE9") # é in ISO-8859-1
+ }.should.raise(Encoding::UndefinedConversionError) { |e|
+ exception = e
+ }
+ ec.last_error.should.instance_of?(Encoding::UndefinedConversionError)
+ ec.last_error.message.should == exception.message
+ ec.last_error.message.should.include? "from ISO-8859-1 to UTF-8 to Big5"
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/new_spec.rb b/spec/ruby/core/encoding/converter/new_spec.rb
new file mode 100644
index 0000000000..bbdfb48bce
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/new_spec.rb
@@ -0,0 +1,119 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter.new" do
+ it "accepts a String for the source encoding" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8")
+ conv.source_encoding.should == Encoding::US_ASCII
+ end
+
+ it "accepts a String for the destination encoding" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8")
+ conv.destination_encoding.should == Encoding::UTF_8
+ end
+
+ it "accepts an Encoding object for the source encoding" do
+ conv = Encoding::Converter.new(Encoding::US_ASCII, "utf-8")
+ conv.source_encoding.should == Encoding::US_ASCII
+ end
+
+ it "accepts an Encoding object for the destination encoding" do
+ conv = Encoding::Converter.new("us-ascii", Encoding::UTF_8)
+ conv.destination_encoding.should == Encoding::UTF_8
+ end
+
+ it "raises an Encoding::ConverterNotFoundError if both encodings are the same" do
+ -> do
+ Encoding::Converter.new "utf-8", "utf-8"
+ end.should.raise(Encoding::ConverterNotFoundError)
+ end
+
+ it "calls #to_str to convert the source encoding argument to an encoding name" do
+ enc = mock("us-ascii")
+ enc.should_receive(:to_str).and_return("us-ascii")
+ conv = Encoding::Converter.new(enc, "utf-8")
+ conv.source_encoding.should == Encoding::US_ASCII
+ end
+
+ it "calls #to_str to convert the destination encoding argument to an encoding name" do
+ enc = mock("utf-8")
+ enc.should_receive(:to_str).and_return("utf-8")
+ conv = Encoding::Converter.new("us-ascii", enc)
+ conv.destination_encoding.should == Encoding::UTF_8
+ end
+
+ it "sets replacement from the options Hash" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: "fubar")
+ conv.replacement.should == "fubar"
+ end
+
+ it "calls #to_hash to convert the options argument to a Hash if not an Integer" do
+ opts = mock("encoding converter options")
+ opts.should_receive(:to_hash).and_return({ replace: "fubar" })
+ conv = Encoding::Converter.new("us-ascii", "utf-8", **opts)
+ conv.replacement.should == "fubar"
+ end
+
+ it "calls #to_str to convert the replacement object to a String" do
+ obj = mock("encoding converter replacement")
+ obj.should_receive(:to_str).and_return("fubar")
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: obj)
+ conv.replacement.should == "fubar"
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock("encoding converter replacement")
+ obj.should_receive(:to_str).and_return(1)
+
+ -> do
+ Encoding::Converter.new("us-ascii", "utf-8", replace: obj)
+ end.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed true for the replacement object" do
+ -> do
+ Encoding::Converter.new("us-ascii", "utf-8", replace: true)
+ end.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed false for the replacement object" do
+ -> do
+ Encoding::Converter.new("us-ascii", "utf-8", replace: false)
+ end.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed an Integer for the replacement object" do
+ -> do
+ Encoding::Converter.new("us-ascii", "utf-8", replace: 1)
+ end.should.raise(TypeError)
+ end
+
+ it "accepts an empty String for the replacement object" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: "")
+ conv.replacement.should == ""
+ end
+
+ describe "when passed nil for the replacement object" do
+ describe "when the destination encoding is not UTF-8" do
+ it "sets the replacement String to '?'" do
+ conv = Encoding::Converter.new("us-ascii", "binary", replace: nil)
+ conv.replacement.should == "?"
+ end
+
+ it "sets the replacement String encoding to US-ASCII" do
+ conv = Encoding::Converter.new("us-ascii", "binary", replace: nil)
+ conv.replacement.encoding.should == Encoding::US_ASCII
+ end
+
+ it "sets the replacement String to '\\uFFFD'" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: nil)
+ conv.replacement.should == "\u{fffd}".dup.force_encoding("utf-8")
+ end
+
+ it "sets the replacement String encoding to UTF-8" do
+ conv = Encoding::Converter.new("us-ascii", "utf-8", replace: nil)
+ conv.replacement.encoding.should == Encoding::UTF_8
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/primitive_convert_spec.rb b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb
new file mode 100644
index 0000000000..ab9ce6a992
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/primitive_convert_spec.rb
@@ -0,0 +1,216 @@
+# encoding: binary
+# frozen_string_literal: false
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#primitive_convert" do
+ before :each do
+ @ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ end
+
+ it "accepts a nil source buffer" do
+ -> { @ec.primitive_convert(nil,"") }.should_not.raise
+ end
+
+ it "accepts a String as the source buffer" do
+ -> { @ec.primitive_convert("","") }.should_not.raise
+ end
+
+ it "raises FrozenError when the destination buffer is a frozen String" do
+ -> { @ec.primitive_convert("", "".freeze) }.should.raise(FrozenError)
+ end
+
+ it "accepts nil for the destination byte offset" do
+ -> { @ec.primitive_convert("","", nil) }.should_not.raise
+ end
+
+ it "accepts an integer for the destination byte offset" do
+ -> { @ec.primitive_convert("","a", 1) }.should_not.raise
+ end
+
+ it "calls #to_int to convert the destination byte offset" do
+ offset = mock("encoding primitive_convert destination byte offset")
+ offset.should_receive(:to_int).and_return(2)
+ @ec.primitive_convert("abc", result = " ", offset).should == :finished
+ result.should == " abc"
+ end
+
+ it "raises an ArgumentError if the destination byte offset is greater than the bytesize of the destination buffer" do
+ -> { @ec.primitive_convert("","am", 0) }.should_not.raise
+ -> { @ec.primitive_convert("","am", 1) }.should_not.raise
+ -> { @ec.primitive_convert("","am", 2) }.should_not.raise
+ -> { @ec.primitive_convert("","am", 3) }.should.raise(ArgumentError)
+ end
+
+ it "uses the destination byte offset to determine where to write the result in the destination buffer" do
+ dest = "aa"
+ @ec.primitive_convert("b",dest, nil, 0)
+ dest.should == "aa"
+
+ @ec.primitive_convert("b",dest, nil, 1)
+ dest.should == "aab"
+
+ @ec.primitive_convert("b",dest, nil, 2)
+ dest.should == "aabbb"
+ end
+
+ it "accepts nil for the destination bytesize" do
+ -> { @ec.primitive_convert("","", nil, nil) }.should_not.raise
+ end
+
+ it "accepts an integer for the destination bytesize" do
+ -> { @ec.primitive_convert("","", nil, 0) }.should_not.raise
+ end
+
+ it "allows a destination bytesize value greater than the bytesize of the source buffer" do
+ -> { @ec.primitive_convert("am","", nil, 3) }.should_not.raise
+ end
+
+ it "allows a destination bytesize value less than the bytesize of the source buffer" do
+ -> { @ec.primitive_convert("am","", nil, 1) }.should_not.raise
+ end
+
+ it "calls #to_int to convert the destination byte size" do
+ size = mock("encoding primitive_convert destination byte size")
+ size.should_receive(:to_int).and_return(2)
+ @ec.primitive_convert("abc", result = " ", 0, size).should == :destination_buffer_full
+ result.should == "ab"
+ end
+
+ it "uses destination bytesize as the maximum bytesize of the destination buffer" do
+ dest = ""
+ @ec.primitive_convert("glark", dest, nil, 1)
+ dest.bytesize.should == 1
+ end
+
+ it "allows a destination buffer of unlimited size if destination bytesize is nil" do
+ source = "glark".force_encoding('utf-8')
+ dest = ""
+ @ec.primitive_convert("glark", dest, nil, nil)
+ dest.bytesize.should == source.bytesize
+ end
+
+ it "accepts an options hash" do
+ @ec.primitive_convert("","",nil,nil, after_output: true).should == :finished
+ end
+
+ it "sets the destination buffer's encoding to the destination encoding if the conversion succeeded" do
+ dest = "".force_encoding('utf-8')
+ dest.encoding.should == Encoding::UTF_8
+ @ec.primitive_convert("\u{98}",dest).should == :finished
+ dest.encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "sets the destination buffer's encoding to the destination encoding if the conversion failed" do
+ dest = "".force_encoding('utf-8')
+ dest.encoding.should == Encoding::UTF_8
+ @ec.primitive_convert("\u{9878}",dest).should == :undefined_conversion
+ dest.encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "removes the undefined part from the source buffer when returning :undefined_conversion" do
+ dest = "".force_encoding('utf-8')
+ s = "\u{9878}abcd"
+ @ec.primitive_convert(s, dest).should == :undefined_conversion
+
+ s.should == "abcd"
+ end
+
+ it "returns :incomplete_input when source buffer ends unexpectedly and :partial_input isn't specified" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert("\xa4", "", nil, nil, partial_input: false).should == :incomplete_input
+ end
+
+ it "clears the source buffer when returning :incomplete_input" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ s = "\xa4"
+ ec.primitive_convert(s, "").should == :incomplete_input
+
+ s.should == ""
+ end
+
+ it "returns :source_buffer_empty when source buffer ends unexpectedly and :partial_input is true" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert("\xa4", "", nil, nil, partial_input: true).should == :source_buffer_empty
+ end
+
+ it "clears the source buffer when returning :source_buffer_empty" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ s = "\xa4"
+ ec.primitive_convert(s, "", nil, nil, partial_input: true).should == :source_buffer_empty
+
+ s.should == ""
+ end
+
+ it "returns :undefined_conversion when a character in the source buffer is not representable in the output encoding" do
+ @ec.primitive_convert("\u{9876}","").should == :undefined_conversion
+ end
+
+ it "returns :invalid_byte_sequence when an invalid byte sequence was found in the source buffer" do
+ @ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence
+ end
+
+ it "removes consumed and erroneous bytes from the source buffer when returning :invalid_byte_sequence" do
+ ec = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC)
+ s = "\xC3\xA1\x80\x80\xC3\xA1".force_encoding("utf-8")
+ dest = "".force_encoding("utf-8")
+ ec.primitive_convert(s, dest)
+
+ s.should == "\x80\xC3\xA1".force_encoding("utf-8")
+ end
+
+ it "returns :finished when the conversion succeeded" do
+ @ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished
+ end
+
+ it "clears the source buffer when returning :finished" do
+ s = "glark".force_encoding('utf-8')
+ @ec.primitive_convert(s, "").should == :finished
+
+ s.should == ""
+ end
+
+ it "returns :destination_buffer_full when the destination buffer is too small" do
+ ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ source = "\u{9999}"
+ destination_bytesize = source.bytesize - 1
+ ec.primitive_convert(source, "", 0, destination_bytesize) \
+ .should == :destination_buffer_full
+ source.should == ""
+ end
+
+ it "clears the source buffer when returning :destination_buffer_full" do
+ ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ s = "\u{9999}"
+ destination_bytesize = s.bytesize - 1
+ ec.primitive_convert(s, "", 0, destination_bytesize).should == :destination_buffer_full
+
+ s.should == ""
+ end
+
+ it "keeps removing invalid bytes from the source buffer" do
+ ec = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_8_MAC)
+ s = "\x80\x80\x80"
+ dest = "".force_encoding(Encoding::UTF_8_MAC)
+
+ ec.primitive_convert(s, dest)
+ s.should == "\x80\x80"
+ ec.primitive_convert(s, dest)
+ s.should == "\x80"
+ ec.primitive_convert(s, dest)
+ s.should == ""
+ end
+
+ it "reuses read-again bytes after the first error" do
+ s = "\xf1abcd"
+ dest = ""
+
+ @ec.primitive_convert(s, dest).should == :invalid_byte_sequence
+ s.should == "bcd"
+ @ec.primitive_errinfo[4].should == "a"
+
+ @ec.primitive_convert(s, dest).should == :finished
+ s.should == ""
+
+ dest.should == "abcd"
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb
new file mode 100644
index 0000000000..580e2e37e1
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/primitive_errinfo_spec.rb
@@ -0,0 +1,69 @@
+# encoding: binary
+# frozen_string_literal: false
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#primitive_errinfo" do
+ it "returns [:source_buffer_empty,nil,nil,nil,nil] when no conversion has been attempted" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.primitive_errinfo.should == [:source_buffer_empty, nil, nil, nil, nil]
+ end
+
+ it "returns [:finished,nil,nil,nil,nil] when #primitive_convert last returned :finished" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.primitive_convert("a","").should == :finished
+ ec.primitive_errinfo.should == [:finished, nil, nil, nil, nil]
+ end
+
+ it "returns [:source_buffer_empty,nil,nil,nil, nil] when #convert last succeeded" do
+ ec = Encoding::Converter.new('ascii','utf-8')
+ ec.convert("a".force_encoding('ascii')).should == "a".force_encoding('utf-8')
+ ec.primitive_errinfo.should == [:source_buffer_empty, nil, nil, nil, nil]
+ end
+
+ it "returns [:destination_buffer_full,nil,nil,nil,nil] when #primitive_convert last returned :destination_buffer_full" do
+ ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
+ ec.primitive_convert("\u{9999}", "", 0, 0, partial_input: false).should == :destination_buffer_full
+ ec.primitive_errinfo.should == [:destination_buffer_full, nil, nil, nil, nil]
+ end
+
+ it "returns the status of the last primitive conversion, even if it was successful and the previous one wasn't" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence
+ ec.primitive_convert("glark".force_encoding('utf-8'),"").should == :finished
+ ec.primitive_errinfo.should == [:finished, nil, nil, nil, nil]
+ end
+
+ it "returns the state, source encoding, target encoding, and the erroneous bytes when #primitive_convert last returned :undefined_conversion" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\u{9876}","").should == :undefined_conversion
+ ec.primitive_errinfo.should ==
+ [:undefined_conversion, "UTF-8", "ISO-8859-1", "\xE9\xA1\xB6", ""]
+ end
+
+ it "returns the state, source encoding, target encoding, and erroneous bytes when #primitive_convert last returned :incomplete_input" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert("\xa4", "", nil, 10).should == :incomplete_input
+ ec.primitive_errinfo.should == [:incomplete_input, "EUC-JP", "UTF-8", "\xA4", ""]
+ end
+
+ it "returns the state, source encoding, target encoding, erroneous bytes, and the read-again bytes when #primitive_convert last returned :invalid_byte_sequence" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ ec.primitive_convert("\xf1abcd","").should == :invalid_byte_sequence
+ ec.primitive_errinfo.should ==
+ [:invalid_byte_sequence, "UTF-8", "ISO-8859-1", "\xF1", "a"]
+ end
+
+ it "returns the state, source encoding, target encoding, erroneous bytes, and the read-again bytes when #convert last raised InvalidByteSequenceError" do
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ -> { ec.convert("\xf1abcd") }.should.raise(Encoding::InvalidByteSequenceError)
+ ec.primitive_errinfo.should ==
+ [:invalid_byte_sequence, "UTF-8", "ISO-8859-1", "\xF1", "a"]
+ end
+
+ it "returns the state, source encoding, target encoding, erroneous bytes, and the read-again bytes when #finish last raised InvalidByteSequenceError" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.convert("\xa4")
+ -> { ec.finish }.should.raise(Encoding::InvalidByteSequenceError)
+ ec.primitive_errinfo.should == [:incomplete_input, "EUC-JP", "UTF-8", "\xA4", ""]
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/putback_spec.rb b/spec/ruby/core/encoding/converter/putback_spec.rb
new file mode 100644
index 0000000000..a85cec5145
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/putback_spec.rb
@@ -0,0 +1,56 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#putback" do
+ before :each do
+ @ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ @ret = @ec.primitive_convert(@src=+"abc\xa1def", @dst=+"", nil, 10)
+ end
+
+ it "returns a String" do
+ @ec.putback.should.instance_of?(String)
+ end
+
+ it "returns a String in the source encoding" do
+ @ec.putback.encoding.should == Encoding::EUC_JP
+ end
+
+ it "returns the bytes buffered due to an :invalid_byte_sequence error" do
+ @ret.should == :invalid_byte_sequence
+ @ec.putback.should == 'd'
+ @ec.primitive_errinfo.last.should == 'd'
+ end
+
+ it "allows conversion to be resumed after an :invalid_byte_sequence" do
+ @src = @ec.putback + @src
+ @ret = @ec.primitive_convert(@src, @dst, nil, 10)
+ @ret.should == :finished
+ @dst.should == "abcdef"
+ @src.should == ""
+ end
+
+ it "returns an empty String when there are no more bytes to put back" do
+ @ec.putback
+ @ec.putback.should == ""
+ end
+
+ it "returns the problematic bytes for UTF-16LE" do
+ ec = Encoding::Converter.new("utf-16le", "iso-8859-1")
+ src = +"\x00\xd8\x61\x00"
+ dst = +""
+ ec.primitive_convert(src, dst).should == :invalid_byte_sequence
+ ec.primitive_errinfo.should == [:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "a\x00"]
+ ec.putback.should == "a\x00".dup.force_encoding("utf-16le")
+ ec.putback.should == ""
+ end
+
+ it "accepts an integer argument corresponding to the number of bytes to be put back" do
+ ec = Encoding::Converter.new("utf-16le", "iso-8859-1")
+ src = +"\x00\xd8\x61\x00"
+ dst = +""
+ ec.primitive_convert(src, dst).should == :invalid_byte_sequence
+ ec.primitive_errinfo.should == [:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "a\x00"]
+ ec.putback(2).should == "a\x00".dup.force_encoding("utf-16le")
+ ec.putback.should == ""
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/replacement_spec.rb b/spec/ruby/core/encoding/converter/replacement_spec.rb
new file mode 100644
index 0000000000..c25ec36517
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/replacement_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#replacement" do
+ it "returns '?' in US-ASCII when the destination encoding is not UTF-8" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii")
+ ec.replacement.should == "?"
+ ec.replacement.encoding.should == Encoding::US_ASCII
+
+ ec = Encoding::Converter.new("utf-8", "sjis")
+ ec.replacement.should == "?"
+ ec.replacement.encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns \\uFFFD when the destination encoding is UTF-8" do
+ ec = Encoding::Converter.new("us-ascii", "utf-8")
+ ec.replacement.should == "\u{fffd}".dup.force_encoding('utf-8')
+ ec.replacement.encoding.should == Encoding::UTF_8
+ end
+end
+
+describe "Encoding::Converter#replacement=" do
+ it "accepts a String argument" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii")
+ ec.replacement = "!"
+ ec.replacement.should == "!"
+ end
+
+ it "accepts a String argument of arbitrary length" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii")
+ ec.replacement = "?!?" * 9999
+ ec.replacement.should == "?!?" * 9999
+ end
+
+ it "raises a TypeError if assigned a non-String argument" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii")
+ -> { ec.replacement = nil }.should.raise(TypeError)
+ end
+
+ it "sets #replacement" do
+ ec = Encoding::Converter.new("us-ascii", "utf-8")
+ ec.replacement.should == "\u{fffd}".dup.force_encoding('utf-8')
+ ec.replacement = '?'.encode('utf-8')
+ ec.replacement.should == '?'.dup.force_encoding('utf-8')
+ end
+
+ it "raises an UndefinedConversionError is the argument cannot be converted into the destination encoding" do
+ ec = Encoding::Converter.new("sjis", "ascii")
+ utf8_q = "\u{986}".dup.force_encoding('utf-8')
+ ec.primitive_convert(utf8_q.dup, +"").should == :undefined_conversion
+ -> { ec.replacement = utf8_q }.should.raise(Encoding::UndefinedConversionError)
+ end
+
+ it "does not change the replacement character if the argument cannot be converted into the destination encoding" do
+ ec = Encoding::Converter.new("sjis", "ascii")
+ utf8_q = "\u{986}".dup.force_encoding('utf-8')
+ ec.primitive_convert(utf8_q.dup, +"").should == :undefined_conversion
+ -> { ec.replacement = utf8_q }.should.raise(Encoding::UndefinedConversionError)
+ ec.replacement.should == "?".dup.force_encoding('us-ascii')
+ end
+
+ it "uses the replacement character" do
+ ec = Encoding::Converter.new("utf-8", "us-ascii", :invalid => :replace, :undef => :replace)
+ ec.replacement = "!"
+ dest = +""
+ status = ec.primitive_convert(+"中文123", dest)
+
+ status.should == :finished
+ dest.should == "!!123"
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/search_convpath_spec.rb b/spec/ruby/core/encoding/converter/search_convpath_spec.rb
new file mode 100644
index 0000000000..cac44765f8
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/search_convpath_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter.search_convpath" do
+ it "returns an Array with a single element if there is a direct converter" do
+ cp = Encoding::Converter.search_convpath('ASCII', 'UTF-8')
+ cp.should == [[Encoding::US_ASCII, Encoding::UTF_8]]
+ end
+
+ it "returns multiple encoding pairs when direct conversion is impossible" do
+ cp = Encoding::Converter.search_convpath('ascii','Big5')
+ cp.should == [
+ [Encoding::US_ASCII, Encoding::UTF_8],
+ [Encoding::UTF_8, Encoding::Big5]
+ ]
+ end
+
+ it "indicates if crlf_newline conversion would occur" do
+ cp = Encoding::Converter.search_convpath("ISO-8859-1", "EUC-JP", crlf_newline: true)
+ cp.last.should == "crlf_newline"
+
+ cp = Encoding::Converter.search_convpath("ASCII", "UTF-8", crlf_newline: false)
+ cp.last.should_not == "crlf_newline"
+ 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(Encoding::ConverterNotFoundError)
+ end
+end
diff --git a/spec/ruby/core/encoding/converter/source_encoding_spec.rb b/spec/ruby/core/encoding/converter/source_encoding_spec.rb
new file mode 100644
index 0000000000..6196f717bd
--- /dev/null
+++ b/spec/ruby/core/encoding/converter/source_encoding_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+describe "Encoding::Converter#source_encoding" do
+ it "returns the source encoding as an Encoding object" do
+ ec = Encoding::Converter.new('ASCII','Big5')
+ ec.source_encoding.should == Encoding::US_ASCII
+
+ ec = Encoding::Converter.new('Shift_JIS','EUC-JP')
+ ec.source_encoding.should == Encoding::SHIFT_JIS
+ end
+end
diff --git a/spec/ruby/core/encoding/default_external_spec.rb b/spec/ruby/core/encoding/default_external_spec.rb
new file mode 100644
index 0000000000..2a2bd7f6ae
--- /dev/null
+++ b/spec/ruby/core/encoding/default_external_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.default_external" do
+ before :each do
+ @original_encoding = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @original_encoding
+ end
+
+ it "returns an Encoding object" do
+ Encoding.default_external.should.instance_of?(Encoding)
+ end
+
+ it "returns the default external encoding" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+ Encoding.default_external.should == Encoding::SHIFT_JIS
+ end
+
+ platform_is :windows do
+ it 'is UTF-8 by default on Windows' do
+ Encoding.default_external.should == Encoding::UTF_8
+ end
+ end
+end
+
+describe "Encoding.default_external=" do
+ before :each do
+ @original_encoding = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @original_encoding
+ end
+
+ it "sets the default external encoding" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+ Encoding.default_external.should == Encoding::SHIFT_JIS
+ Encoding.find('external').should == Encoding::SHIFT_JIS
+ end
+
+ platform_is_not :windows do
+ it "also sets the filesystem encoding" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+ Encoding.find('filesystem').should == Encoding::SHIFT_JIS
+ end
+ end
+
+ it "can accept a name of an encoding as a String" do
+ Encoding.default_external = 'Shift_JIS'
+ Encoding.default_external.should == Encoding::SHIFT_JIS
+ end
+
+ it "calls #to_s on arguments that are neither Strings nor Encodings" do
+ string = mock('string')
+ string.should_receive(:to_str).at_least(1).and_return('US-ASCII')
+ Encoding.default_external = string
+ Encoding.default_external.should == Encoding::ASCII
+ end
+
+ it "raises a TypeError unless the argument is an Encoding or convertible to a String" do
+ -> { Encoding.default_external = [] }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError if the argument is nil" do
+ -> { Encoding.default_external = nil }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/encoding/default_internal_spec.rb b/spec/ruby/core/encoding/default_internal_spec.rb
new file mode 100644
index 0000000000..38aef9dce9
--- /dev/null
+++ b/spec/ruby/core/encoding/default_internal_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.default_internal" do
+ before :each do
+ @original_encoding = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @original_encoding
+ end
+
+ it "is nil by default" do
+ Encoding.default_internal.should == nil
+ end
+
+ it "returns an Encoding object if a default internal encoding is set" do
+ Encoding.default_internal = Encoding::ASCII
+ Encoding.default_internal.should.instance_of?(Encoding)
+ end
+
+ it "returns nil if no default internal encoding is set" do
+ Encoding.default_internal = nil
+ Encoding.default_internal.should == nil
+ end
+
+ it "returns the default internal encoding" do
+ Encoding.default_internal = Encoding::BINARY
+ Encoding.default_internal.should == Encoding::BINARY
+ end
+end
+
+describe "Encoding.default_internal=" do
+ before :each do
+ @original_encoding = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @original_encoding
+ end
+
+ it "sets the default internal encoding" do
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ Encoding.default_internal.should == Encoding::SHIFT_JIS
+ end
+
+ it "can accept a name of an encoding as a String" do
+ Encoding.default_internal = 'Shift_JIS'
+ Encoding.default_internal.should == Encoding::SHIFT_JIS
+ end
+
+ it "calls #to_str to convert an object to a String" do
+ obj = mock('string')
+ obj.should_receive(:to_str).at_least(1).times.and_return('ascii')
+
+ Encoding.default_internal = obj
+ Encoding.default_internal.should == Encoding::ASCII
+ end
+
+ it "raises a TypeError if #to_str does not return a String" do
+ obj = mock('string')
+ obj.should_receive(:to_str).at_least(1).times.and_return(1)
+
+ -> { Encoding.default_internal = obj }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed an object not providing #to_str" do
+ -> { Encoding.default_internal = mock("encoding") }.should.raise(TypeError)
+ end
+
+ it "accepts an argument of nil to unset the default internal encoding" do
+ Encoding.default_internal = nil
+ Encoding.default_internal.should == nil
+ end
+end
diff --git a/spec/ruby/core/encoding/dummy_spec.rb b/spec/ruby/core/encoding/dummy_spec.rb
new file mode 100644
index 0000000000..05530a8186
--- /dev/null
+++ b/spec/ruby/core/encoding/dummy_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#dummy?" do
+ it "returns false for proper encodings" do
+ Encoding::UTF_8.dummy?.should == false
+ Encoding::ASCII.dummy?.should == false
+ end
+
+ it "returns true for dummy encodings" do
+ Encoding::ISO_2022_JP.dummy?.should == true
+ Encoding::CP50221.dummy?.should == true
+ Encoding::UTF_7.dummy?.should == true
+ end
+
+ it "returns true for UTF_16 and UTF_32" do
+ Encoding::UTF_16.should.dummy?
+ Encoding::UTF_32.should.dummy?
+ end
+
+ it "implies not #ascii_compatible?" do
+ Encoding.list.select(&:dummy?).each do |encoding|
+ encoding.should_not.ascii_compatible?
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/find_spec.rb b/spec/ruby/core/encoding/find_spec.rb
new file mode 100644
index 0000000000..c5356560eb
--- /dev/null
+++ b/spec/ruby/core/encoding/find_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.find" do
+ before :all do
+ @encodings = Encoding.aliases.to_a.flatten.uniq
+ end
+
+ it "returns the corresponding Encoding object if given a valid encoding name" do
+ @encodings.each do |enc|
+ Encoding.find(enc).should.instance_of?(Encoding)
+ end
+ end
+
+ it "returns the corresponding Encoding object if given a valid alias name" do
+ Encoding.aliases.keys.each do |enc_alias|
+ Encoding.find(enc_alias).should.instance_of?(Encoding)
+ end
+ end
+
+ it "raises a TypeError if passed a Symbol" do
+ -> { Encoding.find(:"utf-8") }.should.raise(TypeError)
+ end
+
+ it "returns the passed Encoding object" do
+ Encoding.find(Encoding::UTF_8).should == Encoding::UTF_8
+ end
+
+ it "accepts encoding names as Strings" do
+ Encoding.list.each do |enc|
+ Encoding.find(enc.name).should == enc
+ end
+ end
+
+ it "accepts any object as encoding name, if it responds to #to_str" do
+ obj = Class.new do
+ attr_writer :encoding_name
+ def to_str; @encoding_name; end
+ end.new
+
+ Encoding.list.each do |enc|
+ obj.encoding_name = enc.name
+ Encoding.find(obj).should == enc
+ end
+ end
+
+ it "is case insensitive" do
+ @encodings.each do |enc|
+ Encoding.find(enc.upcase).should == Encoding.find(enc)
+ end
+ end
+
+ it "raises an ArgumentError if the given encoding does not exist" do
+ -> { Encoding.find('dh2dh278d') }.should.raise(ArgumentError, 'unknown encoding name - dh2dh278d')
+ end
+
+ # Not sure how to do a better test, since locale depends on weird platform-specific stuff
+ it "supports the 'locale' encoding alias" do
+ enc = Encoding.find('locale')
+ enc.should_not == nil
+ end
+
+ it "returns default external encoding for the 'external' encoding alias" do
+ enc = Encoding.find('external')
+ enc.should == Encoding.default_external
+ end
+
+ it "returns default internal encoding for the 'internal' encoding alias" do
+ enc = Encoding.find('internal')
+ enc.should == Encoding.default_internal
+ end
+
+ platform_is_not :windows do
+ it "uses default external encoding for the 'filesystem' encoding alias" do
+ enc = Encoding.find('filesystem')
+ enc.should == Encoding.default_external
+ end
+ end
+
+ platform_is :windows do
+ it "needs to be reviewed for spec completeness"
+ end
+end
diff --git a/spec/ruby/core/encoding/fixtures/classes.rb b/spec/ruby/core/encoding/fixtures/classes.rb
new file mode 100644
index 0000000000..943865e8d8
--- /dev/null
+++ b/spec/ruby/core/encoding/fixtures/classes.rb
@@ -0,0 +1,49 @@
+# encoding: binary
+module EncodingSpecs
+ class UndefinedConversionError
+ def self.exception
+ ec = Encoding::Converter.new('utf-8','ascii')
+ begin
+ ec.convert("\u{8765}")
+ rescue Encoding::UndefinedConversionError => e
+ e
+ end
+ end
+ end
+
+ class UndefinedConversionErrorIndirect
+ def self.exception
+ ec = Encoding::Converter.new("ISO-8859-1", "EUC-JP")
+ begin
+ ec.convert("\xA0")
+ rescue Encoding::UndefinedConversionError => e
+ e
+ end
+ end
+ end
+
+ class InvalidByteSequenceError
+ def self.exception
+ ec = Encoding::Converter.new("utf-8", "iso-8859-1")
+ begin
+ ec.convert("\xf1abcd")
+ rescue Encoding::InvalidByteSequenceError => e
+ # Return the exception object and the primitive_errinfo Array
+ [e, ec.primitive_errinfo]
+ end
+ end
+ end
+
+ class InvalidByteSequenceErrorIndirect
+ def self.exception
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ begin
+ ec.convert("abc\xA1\xFFdef")
+ rescue Encoding::InvalidByteSequenceError => e
+ # Return the exception object and the discarded bytes reported by
+ # #primitive_errinfo
+ [e, ec.primitive_errinfo]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/inspect_spec.rb b/spec/ruby/core/encoding/inspect_spec.rb
new file mode 100644
index 0000000000..ab7f8cf9fc
--- /dev/null
+++ b/spec/ruby/core/encoding/inspect_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#inspect" do
+ it "returns a String" do
+ Encoding::UTF_8.inspect.should.instance_of?(String)
+ end
+
+ ruby_version_is ""..."3.4" do
+ it "returns #<Encoding:name> for a non-dummy encoding named 'name'" do
+ Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc|
+ enc.inspect.should =~ /#<Encoding:#{enc.name}>/
+ end
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "returns #<Encoding:name> for a non-dummy encoding named 'name'" do
+ Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc|
+ if enc.name == "ASCII-8BIT"
+ enc.inspect.should == "#<Encoding:BINARY (ASCII-8BIT)>"
+ else
+ enc.inspect.should =~ /#<Encoding:#{enc.name}>/
+ end
+ end
+ end
+ end
+
+ it "returns #<Encoding:name (dummy)> for a dummy encoding named 'name'" do
+ Encoding.list.to_a.select {|e| e.dummy? }.each do |enc|
+ enc.inspect.should =~ /#<Encoding:#{enc.name} \(dummy\)>/
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_name_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_name_spec.rb
new file mode 100644
index 0000000000..7d3cc77c0b
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_name_spec.rb
@@ -0,0 +1,19 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#destination_encoding_name" do
+ before :each do
+ @exception, = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.destination_encoding_name.should.instance_of?(String)
+ @exception2.destination_encoding_name.should.instance_of?(String)
+ end
+
+ it "is equal to the destination encoding name of the object that raised it" do
+ @exception.destination_encoding_name.should == "ISO-8859-1"
+ @exception2.destination_encoding_name.should == "UTF-8"
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_spec.rb
new file mode 100644
index 0000000000..264c409b1c
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/destination_encoding_spec.rb
@@ -0,0 +1,19 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#destination_encoding" do
+ before :each do
+ @exception, = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns an Encoding object" do
+ @exception.destination_encoding.should.instance_of?(Encoding)
+ @exception2.destination_encoding.should.instance_of?(Encoding)
+ end
+
+ it "is equal to the destination encoding of the object that raised it" do
+ @exception.destination_encoding.should == Encoding::ISO_8859_1
+ @exception2.destination_encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb
new file mode 100644
index 0000000000..40a9a35caf
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/error_bytes_spec.rb
@@ -0,0 +1,31 @@
+# encoding: binary
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#error_bytes" do
+ before :each do
+ @exception, @errinfo = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, @errinfo2 = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.error_bytes.should.instance_of?(String)
+ @exception2.error_bytes.should.instance_of?(String)
+ end
+
+ it "returns the bytes that caused the exception" do
+ @exception.error_bytes.size.should == 1
+ @exception.error_bytes.should == "\xF1"
+ @exception.error_bytes.should == @errinfo[-2]
+
+ @exception2.error_bytes.size.should == 1
+ @exception2.error_bytes.should == "\xA1"
+ @exception2.error_bytes.should == @errinfo2[-2]
+ end
+
+ it "uses BINARY as the encoding" do
+ @exception.error_bytes.encoding.should == Encoding::BINARY
+
+ @exception2.error_bytes.encoding.should == Encoding::BINARY
+ end
+end
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
new file mode 100644
index 0000000000..143db7b6da
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/incomplete_input_spec.rb
@@ -0,0 +1,28 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+
+describe "Encoding::InvalidByteSequenceError#incomplete_input?" do
+ it "returns nil by default" do
+ Encoding::InvalidByteSequenceError.new.incomplete_input?.should == nil
+ end
+
+ it "returns true if #primitive_convert returned :incomplete_input for the same data" do
+ ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1")
+ ec.primitive_convert(+"\xA1", +'').should == :incomplete_input
+ begin
+ ec.convert("\xA1")
+ rescue Encoding::InvalidByteSequenceError => e
+ e.incomplete_input?.should == true
+ end
+ end
+
+ it "returns false if #primitive_convert returned :invalid_byte_sequence for the same data" do
+ ec = Encoding::Converter.new("ascii", "utf-8")
+ ec.primitive_convert(+"\xfffffffff", +'').should == :invalid_byte_sequence
+ begin
+ ec.convert("\xfffffffff")
+ rescue Encoding::InvalidByteSequenceError => e
+ e.incomplete_input?.should == false
+ end
+ end
+end
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
new file mode 100644
index 0000000000..e4fc81aac6
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/readagain_bytes_spec.rb
@@ -0,0 +1,31 @@
+# encoding: binary
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#readagain_bytes" do
+ before :each do
+ @exception, @errinfo = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, @errinfo2 = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.readagain_bytes.should.instance_of?(String)
+ @exception2.readagain_bytes.should.instance_of?(String)
+ end
+
+ it "returns the bytes to be read again" do
+ @exception.readagain_bytes.size.should == 1
+ @exception.readagain_bytes.should == "a".dup.force_encoding('binary')
+ @exception.readagain_bytes.should == @errinfo[-1]
+
+ @exception2.readagain_bytes.size.should == 1
+ @exception2.readagain_bytes.should == "\xFF".dup.force_encoding('binary')
+ @exception2.readagain_bytes.should == @errinfo2[-1]
+ end
+
+ it "uses BINARY as the encoding" do
+ @exception.readagain_bytes.encoding.should == Encoding::BINARY
+
+ @exception2.readagain_bytes.encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_name_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_name_spec.rb
new file mode 100644
index 0000000000..b00e1ad4e8
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_name_spec.rb
@@ -0,0 +1,29 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#source_encoding_name" do
+ before :each do
+ @exception, = EncodingSpecs::UndefinedConversionError.exception
+ @exception2, = EncodingSpecs::UndefinedConversionErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.source_encoding_name.should.instance_of?(String)
+ end
+
+ it "is equal to the source encoding name of the object that raised it" do
+ @exception.source_encoding_name.should == "UTF-8"
+ end
+
+ # The source encoding specified in the Encoding::Converter constructor may
+ # differ from the source encoding returned here. What seems to happen is
+ # that when transcoding along a path with multiple pairs of encodings, the
+ # last one encountered when the error occurred is returned. So in this
+ # case, the conversion path is ISO-8859-1 -> UTF-8 -> EUC-JP. The
+ # conversion from ISO-8859-1 -> UTF-8 succeeded, but the conversion from
+ # UTF-8 to EUC-JP failed. IOW, it failed when the source encoding was
+ # UTF-8, so UTF-8 is regarded as the source encoding.
+ it "is equal to the source encoding at the stage of the conversion path where the error occurred" do
+ @exception2.source_encoding_name.should == 'UTF-8'
+ end
+end
diff --git a/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_spec.rb b/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_spec.rb
new file mode 100644
index 0000000000..32ad25dbb5
--- /dev/null
+++ b/spec/ruby/core/encoding/invalid_byte_sequence_error/source_encoding_spec.rb
@@ -0,0 +1,34 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::InvalidByteSequenceError#source_encoding" do
+ before :each do
+ @exception, = EncodingSpecs::InvalidByteSequenceError.exception
+ @exception2, = EncodingSpecs::InvalidByteSequenceErrorIndirect.exception
+ end
+
+ it "returns an Encoding object" do
+ @exception.source_encoding.should.instance_of?(Encoding)
+ @exception2.source_encoding.should.instance_of?(Encoding)
+ end
+
+ it "is equal to the source encoding of the object that raised it" do
+ @exception.source_encoding.should == Encoding::UTF_8
+ end
+
+ # The source encoding specified in the Encoding::Converter constructor may
+ # differ from the source encoding returned here. What seems to happen is
+ # that when transcoding along a path with multiple pairs of encodings, the
+ # last one encountered when the error occurred is returned. So in this
+ # case, the conversion path is EUC-JP -> UTF-8 -> ISO-8859-1. The
+ # conversions failed with the first pair of encodings (i.e. transcoding
+ # from EUC-JP to UTF-8, so UTF-8 is regarded as the source encoding; if
+ # the error had occurred when converting from UTF-8 to ISO-8859-1, UTF-8
+ # would have been the source encoding.
+
+ # FIXME: Derive example where the failure occurs at the UTF-8 ->
+ # ISO-8859-1 case so as to better illustrate the issue
+ it "is equal to the source encoding at the stage of the conversion path where the error occurred" do
+ @exception2.source_encoding.should == Encoding::EUC_JP
+ end
+end
diff --git a/spec/ruby/core/encoding/list_spec.rb b/spec/ruby/core/encoding/list_spec.rb
new file mode 100644
index 0000000000..9fe336c608
--- /dev/null
+++ b/spec/ruby/core/encoding/list_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.list" do
+ it "returns an Array" do
+ Encoding.list.should.instance_of?(Array)
+ end
+
+ it "returns an Array of Encoding objects" do
+ Encoding.list.each do |enc|
+ enc.should.instance_of?(Encoding)
+ end
+ end
+
+ it "returns each encoding only once" do
+ orig = Encoding.list.map { |e| e.name }
+ orig.should == orig.uniq
+ end
+
+ it "includes the default external encoding" do
+ Encoding.list.include?(Encoding.default_external).should == true
+ end
+
+ it "does not include any alias names" do
+ Encoding.aliases.keys.each do |enc_alias|
+ Encoding.list.include?(enc_alias).should == false
+ end
+ end
+
+ it "includes all aliased encodings" do
+ Encoding.aliases.values.each do |enc_alias|
+ Encoding.list.include?(Encoding.find(enc_alias)).should == true
+ end
+ end
+
+ it "includes dummy encodings" do
+ Encoding.list.select { |e| e.dummy? }.should_not == []
+ end
+
+ it 'includes UTF-8 encoding' do
+ Encoding.list.should.include?(Encoding::UTF_8)
+ end
+
+ it 'includes CESU-8 encoding' do
+ Encoding.list.should.include?(Encoding::CESU_8)
+ end
+
+ # TODO: Find example that illustrates this
+ it "updates the list when #find is used to load a new encoding"
+end
diff --git a/spec/ruby/core/encoding/locale_charmap_spec.rb b/spec/ruby/core/encoding/locale_charmap_spec.rb
new file mode 100644
index 0000000000..0d77bf227b
--- /dev/null
+++ b/spec/ruby/core/encoding/locale_charmap_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.locale_charmap" do
+ it "returns a String" do
+ Encoding.locale_charmap.should.instance_of?(String)
+ end
+
+ describe "when setting LC_ALL=C" do
+ before :each do
+ @old_lc_all = ENV['LC_ALL']
+ end
+
+ after :each do
+ ENV['LC_ALL'] = @old_lc_all
+ end
+
+ # FIXME: Get this working on Windows
+ platform_is :linux do
+ platform_is_not :android do
+ it "returns a value based on the LC_ALL environment variable" do
+ ENV['LC_ALL'] = 'C'
+ ruby_exe("print Encoding.locale_charmap").should == 'ANSI_X3.4-1968'
+ end
+ end
+ end
+
+ platform_is :freebsd, :openbsd, :darwin do
+ it "returns a value based on the LC_ALL environment variable" do
+ ENV['LC_ALL'] = 'C'
+ ruby_exe("print Encoding.locale_charmap").should == 'US-ASCII'
+ end
+ end
+
+ platform_is :netbsd do
+ it "returns a value based on the LC_ALL environment variable" do
+ ENV['LC_ALL'] = 'C'
+ ruby_exe("print Encoding.locale_charmap").should == '646'
+ end
+ end
+
+ platform_is :android do
+ it "always returns UTF-8" do
+ ENV['LC_ALL'] = 'C'
+ ruby_exe("print Encoding.locale_charmap").should == 'UTF-8'
+ end
+ end
+
+ platform_is :bsd, :darwin, :linux do
+ it "is unaffected by assigning to ENV['LC_ALL'] in the same process" do
+ old_charmap = Encoding.locale_charmap
+ ENV['LC_ALL'] = 'C'
+ Encoding.locale_charmap.should == old_charmap
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/name_list_spec.rb b/spec/ruby/core/encoding/name_list_spec.rb
new file mode 100644
index 0000000000..1ba8d383bc
--- /dev/null
+++ b/spec/ruby/core/encoding/name_list_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Encoding.name_list" do
+ it "returns an Array" do
+ Encoding.name_list.should.instance_of?(Array)
+ end
+
+ it "returns encoding names as Strings" do
+ Encoding.name_list.each {|e| e.should.instance_of?(String) }
+ end
+
+ it "includes all aliases" do
+ Encoding.aliases.keys.each do |enc_alias|
+ Encoding.name_list.include?(enc_alias).should == true
+ end
+ end
+
+ it "includes all non-dummy encodings" do
+ Encoding.list.each do |enc|
+ Encoding.name_list.include?(enc.name).should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/name_spec.rb b/spec/ruby/core/encoding/name_spec.rb
new file mode 100644
index 0000000000..1d625c9379
--- /dev/null
+++ b/spec/ruby/core/encoding/name_spec.rb
@@ -0,0 +1,15 @@
+require_relative "../../spec_helper"
+
+describe "Encoding#name" do
+ it "returns a String" do
+ Encoding.list.each do |e|
+ e.name.should.instance_of?(String)
+ end
+ end
+
+ it "uniquely identifies an encoding" do
+ Encoding.list.each do |e|
+ e.should == Encoding.find(e.name)
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/names_spec.rb b/spec/ruby/core/encoding/names_spec.rb
new file mode 100644
index 0000000000..e6bcbf474a
--- /dev/null
+++ b/spec/ruby/core/encoding/names_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Encoding#names" do
+ it "returns an Array" do
+ Encoding.name_list.each do |name|
+ e = Encoding.find(name) or next
+ e.names.should.instance_of?(Array)
+ end
+ end
+
+ it "returns names as Strings" do
+ Encoding.name_list.each do |name|
+ e = Encoding.find(name) or next
+ e.names.each do |this_name|
+ this_name.should.instance_of?(String)
+ end
+ end
+ end
+
+ it "returns #name as the first value" do
+ Encoding.name_list.each do |name|
+ e = Encoding.find(name) or next
+ e.names.first.should == e.name
+ end
+ end
+
+ it "includes any aliases the encoding has" do
+ Encoding.name_list.each do |name|
+ e = Encoding.find(name) or next
+ aliases = Encoding.aliases.select{|a,n| n == name}.keys
+ names = e.names
+ aliases.each {|a| names.include?(a).should == true}
+ end
+ end
+end
diff --git a/spec/ruby/core/encoding/replicate_spec.rb b/spec/ruby/core/encoding/replicate_spec.rb
new file mode 100644
index 0000000000..9fe0ba8747
--- /dev/null
+++ b/spec/ruby/core/encoding/replicate_spec.rb
@@ -0,0 +1,8 @@
+# encoding: binary
+require_relative '../../spec_helper'
+
+describe "Encoding#replicate" do
+ it "has been removed" do
+ Encoding::US_ASCII.should_not.respond_to?(:replicate, true)
+ end
+end
diff --git a/spec/ruby/core/encoding/to_s_spec.rb b/spec/ruby/core/encoding/to_s_spec.rb
new file mode 100644
index 0000000000..ee5e3b4abe
--- /dev/null
+++ b/spec/ruby/core/encoding/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative "../../spec_helper"
+
+describe "Encoding#to_s" do
+ it "is an alias of Encoding#name" do
+ Encoding.instance_method(:to_s).should == Encoding.instance_method(:name)
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_name_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_name_spec.rb
new file mode 100644
index 0000000000..bc36695ca7
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_name_spec.rb
@@ -0,0 +1,16 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#destination_encoding_name" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ end
+
+ it "returns a String" do
+ @exception.destination_encoding_name.should.instance_of?(String)
+ end
+
+ it "is equal to the destination encoding name of the object that raised it" do
+ @exception.destination_encoding_name.should == "US-ASCII"
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_spec.rb
new file mode 100644
index 0000000000..c0fcf8de58
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/destination_encoding_spec.rb
@@ -0,0 +1,16 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#destination_encoding" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ end
+
+ it "returns an Encoding object" do
+ @exception.destination_encoding.should.instance_of?(Encoding)
+ end
+
+ it "is equal to the destination encoding of the object that raised it" do
+ @exception.destination_encoding.should == Encoding::US_ASCII
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/error_char_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/error_char_spec.rb
new file mode 100644
index 0000000000..333acf5ee6
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/error_char_spec.rb
@@ -0,0 +1,28 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#error_char" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ @exception2 = EncodingSpecs::UndefinedConversionErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.error_char.should.instance_of?(String)
+ @exception2.error_char.should.instance_of?(String)
+ end
+
+ it "returns the one-character String that caused the exception" do
+ @exception.error_char.size.should == 1
+ @exception.error_char.should == "\u{8765}"
+
+ @exception2.error_char.size.should == 1
+ @exception2.error_char.should == "\u{A0}"
+ end
+
+ it "uses the source encoding" do
+ @exception.error_char.encoding.should == @exception.source_encoding
+
+ @exception2.error_char.encoding.should == @exception2.source_encoding
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_name_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_name_spec.rb
new file mode 100644
index 0000000000..4932a25ed7
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_name_spec.rb
@@ -0,0 +1,29 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#source_encoding_name" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ @exception2 = EncodingSpecs::UndefinedConversionErrorIndirect.exception
+ end
+
+ it "returns a String" do
+ @exception.source_encoding_name.should.instance_of?(String)
+ end
+
+ it "is equal to the source encoding name of the object that raised it" do
+ @exception.source_encoding_name.should == "UTF-8"
+ end
+
+ # The source encoding specified in the Encoding::Converter constructor may
+ # differ from the source encoding returned here. What seems to happen is
+ # that when transcoding along a path with multiple pairs of encodings, the
+ # last one encountered when the error occurred is returned. So in this
+ # case, the conversion path is ISO-8859-1 -> UTF-8 -> EUC-JP. The
+ # conversion from ISO-8859-1 -> UTF-8 succeeded, but the conversion from
+ # UTF-8 to EUC-JP failed. IOW, it failed when the source encoding was
+ # UTF-8, so UTF-8 is regarded as the source encoding.
+ it "is equal to the source encoding at the stage of the conversion path where the error occurred" do
+ @exception2.source_encoding_name.should == 'UTF-8'
+ end
+end
diff --git a/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_spec.rb b/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_spec.rb
new file mode 100644
index 0000000000..cf12020ad2
--- /dev/null
+++ b/spec/ruby/core/encoding/undefined_conversion_error/source_encoding_spec.rb
@@ -0,0 +1,30 @@
+require_relative "../../../spec_helper"
+require_relative '../fixtures/classes'
+
+describe "Encoding::UndefinedConversionError#source_encoding" do
+ before :each do
+ @exception = EncodingSpecs::UndefinedConversionError.exception
+ @exception2 = EncodingSpecs::UndefinedConversionErrorIndirect.exception
+ end
+
+ it "returns an Encoding object" do
+ @exception.source_encoding.should.instance_of?(Encoding)
+ @exception2.source_encoding.should.instance_of?(Encoding)
+ end
+
+ it "is equal to the source encoding of the object that raised it" do
+ @exception.source_encoding.should == Encoding::UTF_8
+ end
+
+ # The source encoding specified in the Encoding::Converter constructor may
+ # differ from the source encoding returned here. What seems to happen is
+ # that when transcoding along a path with multiple pairs of encodings, the
+ # last one encountered when the error occurred is returned. So in this
+ # case, the conversion path is ISO-8859-1 -> UTF-8 -> EUC-JP. The
+ # conversion from ISO-8859-1 -> UTF-8 succeeded, but the conversion from
+ # UTF-8 to EUC-JP failed. IOW, it failed when the source encoding was
+ # UTF-8, so UTF-8 is regarded as the source encoding.
+ it "is equal to the source encoding at the stage of the conversion path where the error occurred" do
+ @exception2.source_encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/enumerable/all_spec.rb b/spec/ruby/core/enumerable/all_spec.rb
new file mode 100644
index 0000000000..cbdd63f86a
--- /dev/null
+++ b/spec/ruby/core/enumerable/all_spec.rb
@@ -0,0 +1,187 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#all?" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new
+ @empty = EnumerableSpecs::Empty.new()
+ @enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
+ @enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
+ end
+
+ it "always returns true on empty enumeration" do
+ @empty.should.all?
+ @empty.all? { nil }.should == true
+
+ [].should.all?
+ [].all? { false }.should == true
+
+ {}.should.all?
+ {}.all? { nil }.should == true
+ end
+
+ it "raises an ArgumentError when more than 1 argument is provided" do
+ -> { @enum.all?(1, 2, 3) }.should.raise(ArgumentError)
+ -> { [].all?(1, 2, 3) }.should.raise(ArgumentError)
+ -> { {}.all?(1, 2, 3) }.should.raise(ArgumentError)
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.all?
+ }.should.raise(RuntimeError)
+
+ -> {
+ EnumerableSpecs::ThrowingEach.new.all? { false }
+ }.should.raise(RuntimeError)
+ end
+
+ describe "with no block" do
+ it "returns true if no elements are false or nil" do
+ @enum.should.all?
+ @enum1.should.all?
+ @enum2.should_not.all?
+
+ EnumerableSpecs::Numerous.new('a','b','c').should.all?
+ EnumerableSpecs::Numerous.new(0, "x", true).should.all?
+ end
+
+ it "returns false if there are false or nil elements" do
+ EnumerableSpecs::Numerous.new(false).should_not.all?
+ EnumerableSpecs::Numerous.new(false, false).should_not.all?
+
+ EnumerableSpecs::Numerous.new(nil).should_not.all?
+ EnumerableSpecs::Numerous.new(nil, nil).should_not.all?
+
+ EnumerableSpecs::Numerous.new(1, nil, 2).should_not.all?
+ EnumerableSpecs::Numerous.new(0, "x", false, true).should_not.all?
+ @enum2.should_not.all?
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMultiWithFalse.new
+ multi.all?.should == true
+ end
+ end
+
+ describe "with block" do
+ it "returns true if the block never returns false or nil" do
+ @enum.all? { true }.should == true
+ @enum1.all?{ |o| o < 5 }.should == true
+ @enum1.all?{ |o| 5 }.should == true
+ end
+
+ it "returns false if the block ever returns false or nil" do
+ @enum.all? { false }.should == false
+ @enum.all? { nil }.should == false
+ @enum1.all?{ |o| o > 2 }.should == false
+
+ EnumerableSpecs::Numerous.new.all? { |i| i > 5 }.should == false
+ EnumerableSpecs::Numerous.new.all? { |i| i == 3 ? nil : true }.should == false
+ end
+
+ it "stops iterating once the return value is determined" do
+ yielded = []
+ EnumerableSpecs::Numerous.new(:one, :two, :three).all? do |e|
+ yielded << e
+ false
+ end.should == false
+ yielded.should == [:one]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(true, true, false, true).all? do |e|
+ yielded << e
+ e
+ end.should == false
+ yielded.should == [true, true, false]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(1, 2, 3, 4, 5).all? do |e|
+ yielded << e
+ e
+ end.should == true
+ yielded.should == [1, 2, 3, 4, 5]
+ end
+
+ it "does not hide exceptions out of the block" do
+ -> {
+ @enum.all? { raise "from block" }
+ }.should.raise(RuntimeError)
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.all? { |e| yielded << e }.should == true
+ yielded.should == [1, 3, 6]
+ end
+
+ it "yields multiple arguments when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.all? { |*args| yielded << args }.should == true
+ yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe 'when given a pattern argument' do
+ it "calls `===` on the pattern the return value" do
+ pattern = EnumerableSpecs::Pattern.new { |x| x >= 0 }
+ @enum1.all?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+ end
+
+ it "always returns true on empty enumeration" do
+ @empty.all?(Integer).should == true
+ [].all?(Integer).should == true
+ {}.all?(NilClass).should == true
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.all?(Integer)
+ }.should.raise(RuntimeError)
+ end
+
+ it "returns true if the pattern never returns false or nil" do
+ pattern = EnumerableSpecs::Pattern.new { |x| 42 }
+ @enum.all?(pattern).should == true
+
+ [1, 42, 3].all?(pattern).should == true
+
+ pattern = EnumerableSpecs::Pattern.new { |x| Array === x }
+ {a: 1, b: 2}.all?(pattern).should == true
+ end
+
+ it "returns false if the pattern ever returns false or nil" do
+ pattern = EnumerableSpecs::Pattern.new { |x| x >= 0 }
+ @enum1.all?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+
+ [1, 2, 3, -1].all?(pattern).should == false
+
+ pattern = EnumerableSpecs::Pattern.new { |x| x[1] >= 0 }
+ {a: 1, b: -1}.all?(pattern).should == false
+ end
+
+ it "does not hide exceptions out of pattern#===" do
+ pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
+ -> {
+ @enum.all?(pattern)
+ }.should.raise(RuntimeError)
+ end
+
+ it "calls the pattern with gathered array when yielded with multiple arguments" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ pattern = EnumerableSpecs::Pattern.new { true }
+ multi.all?(pattern).should == true
+ pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ EnumerableSpecs::Numerous.new(1, 2, 3, 4, 5).all?(String) { true }.should == false
+ }.should complain(/given block not used/)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/any_spec.rb b/spec/ruby/core/enumerable/any_spec.rb
new file mode 100644
index 0000000000..4405d4740a
--- /dev/null
+++ b/spec/ruby/core/enumerable/any_spec.rb
@@ -0,0 +1,200 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#any?" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new
+ @empty = EnumerableSpecs::Empty.new
+ @enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
+ @enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
+ end
+
+ it "always returns false on empty enumeration" do
+ @empty.should_not.any?
+ @empty.any? { nil }.should == false
+
+ [].should_not.any?
+ [].any? { false }.should == false
+
+ {}.should_not.any?
+ {}.any? { nil }.should == false
+ end
+
+ it "raises an ArgumentError when more than 1 argument is provided" do
+ -> { @enum.any?(1, 2, 3) }.should.raise(ArgumentError)
+ -> { [].any?(1, 2, 3) }.should.raise(ArgumentError)
+ -> { {}.any?(1, 2, 3) }.should.raise(ArgumentError)
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.any?
+ }.should.raise(RuntimeError)
+
+ -> {
+ EnumerableSpecs::ThrowingEach.new.any? { false }
+ }.should.raise(RuntimeError)
+ end
+
+ describe "with no block" do
+ it "returns true if any element is not false or nil" do
+ @enum.should.any?
+ @enum1.should.any?
+ @enum2.should.any?
+ EnumerableSpecs::Numerous.new(true).should.any?
+ EnumerableSpecs::Numerous.new('a','b','c').should.any?
+ EnumerableSpecs::Numerous.new('a','b','c', nil).should.any?
+ EnumerableSpecs::Numerous.new(1, nil, 2).should.any?
+ EnumerableSpecs::Numerous.new(1, false).should.any?
+ EnumerableSpecs::Numerous.new(false, nil, 1, false).should.any?
+ EnumerableSpecs::Numerous.new(false, 0, nil).should.any?
+ end
+
+ it "returns false if all elements are false or nil" do
+ EnumerableSpecs::Numerous.new(false).should_not.any?
+ EnumerableSpecs::Numerous.new(false, false).should_not.any?
+ EnumerableSpecs::Numerous.new(nil).should_not.any?
+ EnumerableSpecs::Numerous.new(nil, nil).should_not.any?
+ EnumerableSpecs::Numerous.new(nil, false, nil).should_not.any?
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMultiWithFalse.new
+ multi.any?.should == true
+ end
+ end
+
+ describe "with block" do
+ it "returns true if the block ever returns other than false or nil" do
+ @enum.any? { true }.should == true
+ @enum.any? { 0 }.should == true
+ @enum.any? { 1 }.should == true
+
+ @enum1.any? { Object.new }.should == true
+ @enum1.any?{ |o| o < 1 }.should == true
+ @enum1.any?{ |o| 5 }.should == true
+
+ @enum2.any? { |i| i == nil }.should == true
+ end
+
+ it "returns false if the block never returns other than false or nil" do
+ @enum.any? { false }.should == false
+ @enum.any? { nil }.should == false
+
+ @enum1.any?{ |o| o < -10 }.should == false
+ @enum1.any?{ |o| nil }.should == false
+
+ @enum2.any? { |i| i == :stuff }.should == false
+ end
+
+ it "stops iterating once the return value is determined" do
+ yielded = []
+ EnumerableSpecs::Numerous.new(:one, :two, :three).any? do |e|
+ yielded << e
+ false
+ end.should == false
+ yielded.should == [:one, :two, :three]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(true, true, false, true).any? do |e|
+ yielded << e
+ e
+ end.should == true
+ yielded.should == [true]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(false, nil, false, true, false).any? do |e|
+ yielded << e
+ e
+ end.should == true
+ yielded.should == [false, nil, false, true]
+
+ yielded = []
+ EnumerableSpecs::Numerous.new(1, 2, 3, 4, 5).any? do |e|
+ yielded << e
+ e
+ end.should == true
+ yielded.should == [1]
+ end
+
+ it "does not hide exceptions out of the block" do
+ -> {
+ @enum.any? { raise "from block" }
+ }.should.raise(RuntimeError)
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.any? { |e| yielded << e; false }.should == false
+ yielded.should == [1, 3, 6]
+ end
+
+ it "yields multiple arguments when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.any? { |*args| yielded << args; false }.should == false
+ yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe 'when given a pattern argument' do
+ it "calls `===` on the pattern the return value" do
+ pattern = EnumerableSpecs::Pattern.new { |x| x == 2 }
+ @enum1.any?(pattern).should == true
+ pattern.yielded.should == [[0], [1], [2]]
+ end
+
+ it "always returns false on empty enumeration" do
+ @empty.any?(Integer).should == false
+ [].any?(Integer).should == false
+ {}.any?(NilClass).should == false
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.any?(Integer)
+ }.should.raise(RuntimeError)
+ end
+
+ it "returns true if the pattern ever returns a truthy value" do
+ @enum2.any?(NilClass).should == true
+ pattern = EnumerableSpecs::Pattern.new { |x| 42 }
+ @enum.any?(pattern).should == true
+
+ [1, 42, 3].any?(pattern).should == true
+
+ pattern = EnumerableSpecs::Pattern.new { |x| x == [:b, 2] }
+ {a: 1, b: 2}.any?(pattern).should == true
+ end
+
+ it "returns false if the block never returns other than false or nil" do
+ pattern = EnumerableSpecs::Pattern.new { |x| nil }
+ @enum1.any?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+
+ [1, 2, 3].any?(pattern).should == false
+ {a: 1}.any?(pattern).should == false
+ end
+
+ it "does not hide exceptions out of pattern#===" do
+ pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
+ -> {
+ @enum.any?(pattern)
+ }.should.raise(RuntimeError)
+ end
+
+ it "calls the pattern with gathered array when yielded with multiple arguments" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ pattern = EnumerableSpecs::Pattern.new { false }
+ multi.any?(pattern).should == false
+ pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ EnumerableSpecs::Numerous.new(1, 2, 3, 4, 5).any?(String) { true }.should == false
+ }.should complain(/given block not used/)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/chain_spec.rb b/spec/ruby/core/enumerable/chain_spec.rb
new file mode 100644
index 0000000000..a0597e46a1
--- /dev/null
+++ b/spec/ruby/core/enumerable/chain_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#chain" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns a chain of self and provided enumerables" do
+ one = EnumerableSpecs::Numerous.new(1)
+ two = EnumerableSpecs::Numerous.new(2, 3)
+ three = EnumerableSpecs::Numerous.new(4, 5, 6)
+
+ chain = one.chain(two, three)
+
+ chain.each { |item| ScratchPad << item }
+ ScratchPad.recorded.should == [1, 2, 3, 4, 5, 6]
+ end
+
+ it "returns an Enumerator::Chain if given a block" do
+ EnumerableSpecs::Numerous.new.chain.should.instance_of?(Enumerator::Chain)
+ end
+end
diff --git a/spec/ruby/core/enumerable/chunk_spec.rb b/spec/ruby/core/enumerable/chunk_spec.rb
new file mode 100644
index 0000000000..7c9b31c991
--- /dev/null
+++ b/spec/ruby/core/enumerable/chunk_spec.rb
@@ -0,0 +1,77 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#chunk" do
+ before do
+ ScratchPad.record []
+ end
+
+ it "returns an Enumerator if called without a block" do
+ chunk = EnumerableSpecs::Numerous.new(1, 2, 3, 1, 2).chunk
+ chunk.should.instance_of?(Enumerator)
+ result = chunk.with_index {|elt, i| elt - i }.to_a
+ result.should == [[1, [1, 2, 3]], [-2, [1, 2]]]
+ end
+
+ it "returns an Enumerator if given a block" do
+ EnumerableSpecs::Numerous.new.chunk {}.should.instance_of?(Enumerator)
+ end
+
+ it "yields the current element and the current chunk to the block" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3)
+ e.chunk { |x| ScratchPad << x }.to_a
+ ScratchPad.recorded.should == [1, 2, 3]
+ end
+
+ it "returns elements of the Enumerable in an Array of Arrays, [v, ary], where 'ary' contains the consecutive elements for which the block returned the value 'v'" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 2, 3, 2, 1)
+ result = e.chunk { |x| x < 3 && 1 || 0 }.to_a
+ result.should == [[1, [1, 2]], [0, [3]], [1, [2]], [0, [3]], [1, [2, 1]]]
+ end
+
+ it "returns a partitioned Array of values" do
+ e = EnumerableSpecs::Numerous.new(1,2,3)
+ e.chunk { |x| x > 2 }.map(&:last).should == [[1, 2], [3]]
+ end
+
+ it "returns elements for which the block returns :_alone in separate Arrays" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 2, 1)
+ result = e.chunk { |x| x < 2 && :_alone }.to_a
+ result.should == [[:_alone, [1]], [false, [2, 3, 2]], [:_alone, [1]]]
+ end
+
+ it "yields Arrays as a single argument to a rest argument" do
+ e = EnumerableSpecs::Numerous.new([1, 2])
+ result = e.chunk { |*x| x.should == [[1,2]] }.to_a
+ end
+
+ it "does not return elements for which the block returns :_separator" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 3, 2, 1)
+ result = e.chunk { |x| x == 2 ? :_separator : 1 }.to_a
+ result.should == [[1, [1]], [1, [3, 3]], [1, [1]]]
+ end
+
+ it "does not return elements for which the block returns nil" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 2, 1)
+ result = e.chunk { |x| x == 2 ? nil : 1 }.to_a
+ result.should == [[1, [1]], [1, [3]], [1, [1]]]
+ end
+
+ it "raises a RuntimeError if the block returns a Symbol starting with an underscore other than :_alone or :_separator" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3, 2, 1)
+ -> { e.chunk { |x| :_arbitrary }.to_a }.should.raise(RuntimeError)
+ end
+
+ it "does not accept arguments" do
+ e = EnumerableSpecs::Numerous.new(1, 2, 3)
+ -> {
+ e.chunk(1) {}
+ }.should.raise(ArgumentError)
+ end
+
+ it 'returned Enumerator size returns nil' do
+ e = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 2, 1)
+ enum = e.chunk { |x| true }
+ enum.size.should == nil
+ end
+end
diff --git a/spec/ruby/core/enumerable/chunk_while_spec.rb b/spec/ruby/core/enumerable/chunk_while_spec.rb
new file mode 100644
index 0000000000..286f717442
--- /dev/null
+++ b/spec/ruby/core/enumerable/chunk_while_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#chunk_while" do
+ before :each do
+ ary = [10, 9, 7, 6, 4, 3, 2, 1]
+ @enum = EnumerableSpecs::Numerous.new(*ary)
+ @result = @enum.chunk_while { |i, j| i - 1 == j }
+ @enum_length = ary.length
+ end
+
+ context "when given a block" do
+ it "returns an enumerator" do
+ @result.should.instance_of?(Enumerator)
+ end
+
+ it "splits chunks between adjacent elements i and j where the block returns false" do
+ @result.to_a.should == [[10, 9], [7, 6], [4, 3, 2, 1]]
+ end
+
+ it "calls the block for length of the receiver enumerable minus one times" do
+ times_called = 0
+ @enum.chunk_while do |i, j|
+ times_called += 1
+ i - 1 == j
+ end.to_a
+ times_called.should == (@enum_length - 1)
+ end
+ end
+
+ context "when not given a block" do
+ it "raises an ArgumentError" do
+ -> { @enum.chunk_while }.should.raise(ArgumentError)
+ end
+ end
+
+ context "on a single-element array" do
+ it "ignores the block and returns an enumerator that yields [element]" do
+ [1].chunk_while {|x| x.even?}.to_a.should == [[1]]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/collect_concat_spec.rb b/spec/ruby/core/enumerable/collect_concat_spec.rb
new file mode 100644
index 0000000000..59317cfe34
--- /dev/null
+++ b/spec/ruby/core/enumerable/collect_concat_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect_concat'
+
+describe "Enumerable#collect_concat" do
+ 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
new file mode 100644
index 0000000000..cfa2895cce
--- /dev/null
+++ b/spec/ruby/core/enumerable/collect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect'
+
+describe "Enumerable#collect" do
+ 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
new file mode 100644
index 0000000000..1895430c4e
--- /dev/null
+++ b/spec/ruby/core/enumerable/compact_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+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/count_spec.rb b/spec/ruby/core/enumerable/count_spec.rb
new file mode 100644
index 0000000000..50a1c8e1a4
--- /dev/null
+++ b/spec/ruby/core/enumerable/count_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#count" do
+ before :each do
+ @elements = [1, 2, 4, 2]
+ @numerous = EnumerableSpecs::Numerous.new(*@elements)
+ end
+
+ describe "when no argument or a block" do
+ it "returns size" do
+ @numerous.count.should == 4
+ end
+
+ describe "with a custom size method" do
+ before :each do
+ class << @numerous
+ def size
+ :any_object
+ end
+ end
+ end
+
+ it "ignores the custom size method" do
+ @numerous.count.should == 4
+ end
+ end
+ end
+
+ it "counts nils if given nil as an argument" do
+ EnumerableSpecs::Numerous.new(nil, nil, nil, false).count(nil).should == 3
+ end
+
+ it "accepts an argument for comparison using ==" do
+ @numerous.count(2).should == 2
+ end
+
+ it "uses a block for comparison" do
+ @numerous.count{|x| x%2==0 }.should == 3
+ end
+
+ it "ignores the block when given an argument" do
+ -> {
+ @numerous.count(4){|x| x%2==0 }.should == 1
+ }.should complain(/given block not used/)
+ end
+
+ describe "when each yields multiple values" do
+ it "gathers initial args as elements" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.count {|e| e == 1 }.should == 1
+ end
+
+ it "accepts an argument for comparison using ==" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.count([1, 2]).should == 1
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/cycle_spec.rb b/spec/ruby/core/enumerable/cycle_spec.rb
new file mode 100644
index 0000000000..1fb3cc3d41
--- /dev/null
+++ b/spec/ruby/core/enumerable/cycle_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorized'
+
+describe "Enumerable#cycle" do
+ describe "passed no argument or nil" do
+ it "loops indefinitely" do
+ [[],[nil]].each do |args|
+ bomb = 10
+ EnumerableSpecs::Numerous.new.cycle(*args) do
+ bomb -= 1
+ break 42 if bomb <= 0
+ end.should == 42
+ bomb.should == 0
+ end
+ end
+
+ it "returns nil if there are no elements" do
+ out = EnumerableSpecs::Empty.new.cycle { break :nope }
+ out.should == nil
+ end
+
+ it "yields successive elements of the array repeatedly" do
+ b = []
+ EnumerableSpecs::Numerous.new(1,2,3).cycle do |elem|
+ b << elem
+ break if b.size == 7
+ end
+ b.should == [1,2,3,1,2,3,1]
+ end
+
+ it "calls each at most once" do
+ enum = EnumerableSpecs::EachCounter.new(1, 2)
+ enum.cycle.first(6).should == [1,2,1,2,1,2]
+ enum.times_called.should == 1
+ end
+
+ it "yields only when necessary" do
+ enum = EnumerableSpecs::EachCounter.new(10, 20, 30)
+ enum.cycle { |x| break if x == 20}
+ enum.times_yielded.should == 2
+ end
+ end
+
+ describe "passed a number n as an argument" do
+ it "returns nil and does nothing for non positive n" do
+ EnumerableSpecs::ThrowingEach.new.cycle(0) {}.should == nil
+ EnumerableSpecs::NoEach.new.cycle(-22) {}.should == nil
+ end
+
+ it "calls each at most once" do
+ enum = EnumerableSpecs::EachCounter.new(1, 2)
+ enum.cycle(3).to_a.should == [1,2,1,2,1,2]
+ enum.times_called.should == 1
+ end
+
+ it "yields only when necessary" do
+ enum = EnumerableSpecs::EachCounter.new(10, 20, 30)
+ enum.cycle(3) { |x| break if x == 20}
+ enum.times_yielded.should == 2
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ enum = EnumerableSpecs::Numerous.new(3, 2, 1)
+ enum.cycle(2.3).to_a.should == [3, 2, 1, 3, 2, 1]
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ enum.cycle(obj).to_a.should == [3, 2, 1, 3, 2, 1]
+ end
+
+ it "raises a TypeError when the passed n cannot be coerced to Integer" do
+ enum = EnumerableSpecs::Numerous.new
+ ->{ enum.cycle("cat"){} }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError if more arguments are passed" do
+ enum = EnumerableSpecs::Numerous.new
+ ->{ enum.cycle(1, 2) {} }.should.raise(ArgumentError)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.cycle(2).to_a.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9], [1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe "Enumerable with size" do
+ before :all do
+ @object = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4)
+ @empty_object = EnumerableSpecs::EmptyWithSize.new
+ end
+ it_should_behave_like :enumeratorized_with_cycle_size
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ @method = :cycle
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+
+end
diff --git a/spec/ruby/core/enumerable/detect_spec.rb b/spec/ruby/core/enumerable/detect_spec.rb
new file mode 100644
index 0000000000..6959aadc44
--- /dev/null
+++ b/spec/ruby/core/enumerable/detect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find'
+
+describe "Enumerable#detect" do
+ it_behaves_like :enumerable_find, :detect
+end
diff --git a/spec/ruby/core/enumerable/drop_spec.rb b/spec/ruby/core/enumerable/drop_spec.rb
new file mode 100644
index 0000000000..8d95f464b3
--- /dev/null
+++ b/spec/ruby/core/enumerable/drop_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#drop" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(3, 2, 1, :go)
+ end
+
+ it "requires exactly one argument" do
+ ->{ @enum.drop{} }.should.raise(ArgumentError)
+ ->{ @enum.drop(1, 2){} }.should.raise(ArgumentError)
+ end
+
+ describe "passed a number n as an argument" do
+ it "raises ArgumentError if n < 0" do
+ ->{ @enum.drop(-1) }.should.raise(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ @enum.drop(2.3).should == [1, :go]
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(2)
+ @enum.drop(obj).should == [1, :go]
+ end
+
+ it "returns [] for empty enumerables" do
+ EnumerableSpecs::Empty.new.drop(0).should == []
+ EnumerableSpecs::Empty.new.drop(2).should == []
+ end
+
+ it "returns [] if dropping all" do
+ @enum.drop(5).should == []
+ EnumerableSpecs::Numerous.new(3, 2, 1, :go).drop(4).should == []
+ end
+
+ it "raises a TypeError when the passed n cannot be coerced to Integer" do
+ ->{ @enum.drop("hat") }.should.raise(TypeError)
+ ->{ @enum.drop(nil) }.should.raise(TypeError)
+ end
+
+ end
+end
diff --git a/spec/ruby/core/enumerable/drop_while_spec.rb b/spec/ruby/core/enumerable/drop_while_spec.rb
new file mode 100644
index 0000000000..4b4fdf2d4f
--- /dev/null
+++ b/spec/ruby/core/enumerable/drop_while_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#drop_while" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(3, 2, 1, :go)
+ end
+
+ it "returns an Enumerator if no block given" do
+ @enum.drop_while.should.instance_of?(Enumerator)
+ end
+
+ it "returns no/all elements for {true/false} block" do
+ @enum.drop_while{true}.should == []
+ @enum.drop_while{false}.should == @enum.to_a
+ end
+
+ it "accepts returns other than true/false" do
+ @enum.drop_while{1}.should == []
+ @enum.drop_while{nil}.should == @enum.to_a
+ end
+
+ it "passes elements to the block until the first false" do
+ a = []
+ @enum.drop_while{|obj| (a << obj).size < 3}.should == [1, :go]
+ a.should == [3, 2, 1]
+ end
+
+ it "will only go through what's needed" do
+ enum = EnumerableSpecs::EachCounter.new(1,2,3,4)
+ enum.drop_while { |x|
+ break 42 if x == 3
+ true
+ }.should == 42
+ enum.times_yielded.should == 3
+ end
+
+ it "doesn't return self when it could" do
+ a = [1,2,3]
+ a.drop_while{false}.should_not.equal?(a)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.drop_while {|e| e != [6, 7, 8, 9] }.should == [[6, 7, 8, 9]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_unknown_size, :drop_while
+end
diff --git a/spec/ruby/core/enumerable/each_cons_spec.rb b/spec/ruby/core/enumerable/each_cons_spec.rb
new file mode 100644
index 0000000000..c5e299fd00
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_cons_spec.rb
@@ -0,0 +1,103 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorized'
+
+describe "Enumerable#each_cons" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(4,3,2,1)
+ @in_threes = [[4,3,2],[3,2,1]]
+ end
+
+ it "passes element groups to the block" do
+ acc = []
+ @enum.each_cons(3){|g| acc << g}
+ acc.should == @in_threes
+ end
+
+ it "raises an ArgumentError if there is not a single parameter > 0" do
+ ->{ @enum.each_cons(0){} }.should.raise(ArgumentError)
+ ->{ @enum.each_cons(-2){} }.should.raise(ArgumentError)
+ ->{ @enum.each_cons{} }.should.raise(ArgumentError)
+ ->{ @enum.each_cons(2,2){} }.should.raise(ArgumentError)
+ ->{ @enum.each_cons(0) }.should.raise(ArgumentError)
+ ->{ @enum.each_cons(-2) }.should.raise(ArgumentError)
+ ->{ @enum.each_cons }.should.raise(ArgumentError)
+ ->{ @enum.each_cons(2,2) }.should.raise(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ acc = []
+ @enum.each_cons(3.3){|g| acc << g}
+ acc.should == @in_threes
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(3)
+ @enum.each_cons(obj){|g| break g.length}.should == 3
+ end
+
+ it "works when n is >= full length" do
+ full = @enum.to_a
+ acc = []
+ @enum.each_cons(full.length){|g| acc << g}
+ acc.should == [full]
+ acc = []
+ @enum.each_cons(full.length+1){|g| acc << g}
+ acc.should == []
+ end
+
+ it "yields only as much as needed" do
+ cnt = EnumerableSpecs::EachCounter.new(1, 2, :stop, "I said stop!", :got_it)
+ cnt.each_cons(2) {|g| break 42 if g[-1] == :stop }.should == 42
+ cnt.times_yielded.should == 3
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.each_cons(2).to_a.should == [[[1, 2], [3, 4, 5]], [[3, 4, 5], [6, 7, 8, 9]]]
+ end
+
+ it "returns self when a block is given" do
+ @enum.each_cons(3){}.should == @enum
+ end
+
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ e = @enum.each_cons(3)
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == @in_threes
+ end
+
+ describe "Enumerable with size" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns enum size - each_cons argument + 1" do
+ enum = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ enum.each_cons(10).size.should == 1
+ enum.each_cons(9).size.should == 2
+ enum.each_cons(3).size.should == 8
+ enum.each_cons(2).size.should == 9
+ enum.each_cons(1).size.should == 10
+ end
+
+ it "returns 0 when the argument is larger than self" do
+ enum = EnumerableSpecs::NumerousWithSize.new(1, 2, 3)
+ enum.each_cons(20).size.should == 0
+ end
+
+ it "returns 0 when the enum is empty" do
+ enum = EnumerableSpecs::EmptyWithSize.new
+ enum.each_cons(10).size.should == 0
+ end
+ end
+ end
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ @method = [:each_cons, 8]
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/each_entry_spec.rb b/spec/ruby/core/enumerable/each_entry_spec.rb
new file mode 100644
index 0000000000..9dc89ec28e
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_entry_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#each_entry" do
+ before :each do
+ ScratchPad.record []
+ @enum = EnumerableSpecs::YieldsMixed.new
+ @entries = [1, [2], [3,4], [5,6,7], [8,9], nil, []]
+ end
+
+ it "yields multiple arguments as an array" do
+ acc = []
+ @enum.each_entry {|g| acc << g}.should.equal?(@enum)
+ acc.should == @entries
+ end
+
+ it "returns an enumerator if no block" do
+ e = @enum.each_entry
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == @entries
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.each_entry { |x, i| ScratchPad << [x, i] }
+ ScratchPad.recorded.should == [[:a, 0], [:b, 1]]
+ end
+
+ it "raises an ArgumentError when extra arguments" do
+ -> { @enum.each_entry("one").to_a }.should.raise(ArgumentError)
+ -> { @enum.each_entry("one"){}.to_a }.should.raise(ArgumentError)
+ end
+
+ it "passes extra arguments to #each" do
+ enum = EnumerableSpecs::EachCounter.new(1, 2)
+ enum.each_entry(:foo, "bar").to_a.should == [1,2]
+ enum.arguments_passed.should == [:foo, "bar"]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :each_entry
+end
diff --git a/spec/ruby/core/enumerable/each_slice_spec.rb b/spec/ruby/core/enumerable/each_slice_spec.rb
new file mode 100644
index 0000000000..d05abad1e9
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_slice_spec.rb
@@ -0,0 +1,105 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumeratorized'
+
+describe "Enumerable#each_slice" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(7,6,5,4,3,2,1)
+ @sliced = [[7,6,5],[4,3,2],[1]]
+ end
+
+ it "passes element groups to the block" do
+ acc = []
+ @enum.each_slice(3){|g| acc << g}
+ acc.should == @sliced
+ end
+
+ it "raises an ArgumentError if there is not a single parameter > 0" do
+ ->{ @enum.each_slice(0){} }.should.raise(ArgumentError)
+ ->{ @enum.each_slice(-2){} }.should.raise(ArgumentError)
+ ->{ @enum.each_slice{} }.should.raise(ArgumentError)
+ ->{ @enum.each_slice(2,2){} }.should.raise(ArgumentError)
+ ->{ @enum.each_slice(0) }.should.raise(ArgumentError)
+ ->{ @enum.each_slice(-2) }.should.raise(ArgumentError)
+ ->{ @enum.each_slice }.should.raise(ArgumentError)
+ ->{ @enum.each_slice(2,2) }.should.raise(ArgumentError)
+ end
+
+ it "tries to convert n to an Integer using #to_int" do
+ acc = []
+ @enum.each_slice(3.3){|g| acc << g}
+ acc.should == @sliced
+
+ obj = mock('to_int')
+ obj.should_receive(:to_int).and_return(3)
+ @enum.each_slice(obj){|g| break g.length}.should == 3
+ end
+
+ it "works when n is >= full length" do
+ full = @enum.to_a
+ acc = []
+ @enum.each_slice(full.length){|g| acc << g}
+ acc.should == [full]
+ acc = []
+ @enum.each_slice(full.length+1){|g| acc << g}
+ acc.should == [full]
+ end
+
+ it "yields only as much as needed" do
+ cnt = EnumerableSpecs::EachCounter.new(1, 2, :stop, "I said stop!", :got_it)
+ cnt.each_slice(2) {|g| break 42 if g[0] == :stop }.should == 42
+ cnt.times_yielded.should == 4
+ end
+
+ it "returns an enumerator if no block" do
+ e = @enum.each_slice(3)
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == @sliced
+ 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
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.each_slice(2).to_a.should == [[[1, 2], [3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ e = @enum.each_slice(3)
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == @sliced
+ end
+
+ describe "Enumerable with size" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns the ceil of Enumerable size divided by the argument value" do
+ enum = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+ enum.each_slice(10).size.should == 1
+ enum.each_slice(9).size.should == 2
+ enum.each_slice(3).size.should == 4
+ enum.each_slice(2).size.should == 5
+ enum.each_slice(1).size.should == 10
+ end
+
+ it "returns 0 when the Enumerable is empty" do
+ enum = EnumerableSpecs::EmptyWithSize.new
+ enum.each_slice(10).size.should == 0
+ end
+ end
+ end
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ @method = [:each_slice, 8]
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+ end
+
+end
diff --git a/spec/ruby/core/enumerable/each_with_index_spec.rb b/spec/ruby/core/enumerable/each_with_index_spec.rb
new file mode 100644
index 0000000000..fcb2f82f84
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_with_index_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#each_with_index" do
+
+ before :each do
+ @b = EnumerableSpecs::Numerous.new(2, 5, 3, 6, 1, 4)
+ end
+
+ it "passes each element and its index to block" do
+ @a = []
+ @b.each_with_index { |o, i| @a << [o, i] }
+ @a.should == [[2, 0], [5, 1], [3, 2], [6, 3], [1, 4], [4, 5]]
+ end
+
+ it "provides each element to the block" do
+ acc = []
+ obj = EnumerableSpecs::EachDefiner.new()
+ res = obj.each_with_index {|a,i| acc << [a,i]}
+ acc.should == []
+ obj.should == res
+ end
+
+ it "provides each element to the block and its index" do
+ acc = []
+ res = @b.each_with_index {|a,i| acc << [a,i]}
+ [[2, 0], [5, 1], [3, 2], [6, 3], [1, 4], [4, 5]].should == acc
+ res.should.eql?(@b)
+ end
+
+ it "binds splat arguments properly" do
+ acc = []
+ res = @b.each_with_index { |*b| c,d = b; acc << c; acc << d }
+ [2, 0, 5, 1, 3, 2, 6, 3, 1, 4, 4, 5].should == acc
+ res.should.eql?(@b)
+ end
+
+ it "returns an enumerator if no block" do
+ e = @b.each_with_index
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == [[2, 0], [5, 1], [3, 2], [6, 3], [1, 4], [4, 5]]
+ end
+
+ it "passes extra parameters to each" do
+ count = EnumerableSpecs::EachCounter.new(:apple)
+ e = count.each_with_index(:foo, :bar)
+ e.to_a.should == [[:apple, 0]]
+ count.arguments_passed.should == [:foo, :bar]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :each_with_index
+end
diff --git a/spec/ruby/core/enumerable/each_with_object_spec.rb b/spec/ruby/core/enumerable/each_with_object_spec.rb
new file mode 100644
index 0000000000..1760d3b267
--- /dev/null
+++ b/spec/ruby/core/enumerable/each_with_object_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#each_with_object" do
+ before :each do
+ @values = [2, 5, 3, 6, 1, 4]
+ @enum = EnumerableSpecs::Numerous.new(*@values)
+ @initial = "memo"
+ end
+
+ it "passes each element and its argument to the block" do
+ acc = []
+ @enum.each_with_object(@initial) do |elem, obj|
+ obj.should.equal?(@initial)
+ obj = 42
+ acc << elem
+ end.should.equal?(@initial)
+ acc.should == @values
+ end
+
+ it "returns an enumerator if no block" do
+ acc = []
+ e = @enum.each_with_object(@initial)
+ e.each do |elem, obj|
+ obj.should.equal?(@initial)
+ obj = 42
+ acc << elem
+ end.should.equal?(@initial)
+ acc.should == @values
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ array = []
+ multi.each_with_object(array) { |elem, obj| obj << elem }
+ array.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, [:each_with_object, []]
+end
diff --git a/spec/ruby/core/enumerable/entries_spec.rb b/spec/ruby/core/enumerable/entries_spec.rb
new file mode 100644
index 0000000000..2de4fc756a
--- /dev/null
+++ b/spec/ruby/core/enumerable/entries_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/entries'
+
+describe "Enumerable#entries" do
+ it_behaves_like :enumerable_entries, :entries
+end
diff --git a/spec/ruby/core/enumerable/filter_map_spec.rb b/spec/ruby/core/enumerable/filter_map_spec.rb
new file mode 100644
index 0000000000..1ed131a960
--- /dev/null
+++ b/spec/ruby/core/enumerable/filter_map_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Enumerable#filter_map' do
+ before :each do
+ @numerous = EnumerableSpecs::Numerous.new(*(1..8).to_a)
+ end
+
+ it 'returns an empty array if there are no elements' do
+ EnumerableSpecs::Empty.new.filter_map { true }.should == []
+ end
+
+ it 'returns an array with truthy results of passing each element to block' do
+ @numerous.filter_map { |i| i * 2 if i.even? }.should == [4, 8, 12, 16]
+ @numerous.filter_map { |i| i * 2 }.should == [2, 4, 6, 8, 10, 12, 14, 16]
+ @numerous.filter_map { 0 }.should == [0, 0, 0, 0, 0, 0, 0, 0]
+ @numerous.filter_map { false }.should == []
+ @numerous.filter_map { nil }.should == []
+ end
+
+ it 'returns an enumerator when no block given' do
+ @numerous.filter_map.should.instance_of?(Enumerator)
+ end
+end
diff --git a/spec/ruby/core/enumerable/filter_spec.rb b/spec/ruby/core/enumerable/filter_spec.rb
new file mode 100644
index 0000000000..1c3a7e9ff5
--- /dev/null
+++ b/spec/ruby/core/enumerable/filter_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find_all'
+
+describe "Enumerable#filter" do
+ 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
new file mode 100644
index 0000000000..9cd635f247
--- /dev/null
+++ b/spec/ruby/core/enumerable/find_all_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find_all'
+
+describe "Enumerable#find_all" do
+ it_behaves_like :enumerable_find_all, :find_all
+end
diff --git a/spec/ruby/core/enumerable/find_index_spec.rb b/spec/ruby/core/enumerable/find_index_spec.rb
new file mode 100644
index 0000000000..2e714367ba
--- /dev/null
+++ b/spec/ruby/core/enumerable/find_index_spec.rb
@@ -0,0 +1,89 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#find_index" do
+ before :each do
+ @elements = [2, 4, 6, 8, 10]
+ @numerous = EnumerableSpecs::Numerous.new(*@elements)
+ @yieldsmixed = EnumerableSpecs::YieldsMixed2.new
+ end
+
+ it "passes each entry in enum to block while block when block is false" do
+ visited_elements = []
+ @numerous.find_index do |element|
+ visited_elements << element
+ false
+ end
+ visited_elements.should == @elements
+ end
+
+ it "returns nil when the block is false" do
+ @numerous.find_index {|e| false }.should == nil
+ end
+
+ it "returns the first index for which the block is not false" do
+ @elements.each_with_index do |element, index|
+ @numerous.find_index {|e| e > element - 1 }.should == index
+ end
+ end
+
+ it "returns the first index found" do
+ repeated = [10, 11, 11, 13, 11, 13, 10, 10, 13, 11]
+ numerous_repeat = EnumerableSpecs::Numerous.new(*repeated)
+ repeated.each do |element|
+ numerous_repeat.find_index(element).should == element - 10
+ end
+ end
+
+ it "returns nil when the element not found" do
+ @numerous.find_index(-1).should == nil
+ end
+
+ it "ignores the block if an argument is given" do
+ -> {
+ @numerous.find_index(-1) {|e| true }.should == nil
+ }.should complain(/given block not used/)
+ end
+
+ it "returns an Enumerator if no block given" do
+ @numerous.find_index.should.instance_of?(Enumerator)
+ end
+
+ it "uses #== for testing equality" do
+ [2].to_enum.find_index(2.0).should == 0
+ [2.0].to_enum.find_index(2).should == 0
+ end
+
+ describe "without block" do
+ it "gathers whole arrays as elements when each yields multiple" do
+ @yieldsmixed.find_index([0, 1, 2]).should == 3
+ end
+ end
+
+ describe "with block" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ describe "given a single yield parameter" do
+ it "passes first element to the parameter" do
+ @yieldsmixed.find_index {|a| ScratchPad << a; false }
+ ScratchPad.recorded.should == EnumerableSpecs::YieldsMixed2.first_yields
+ end
+ end
+
+ describe "given a greedy yield parameter" do
+ it "passes a gathered array to the parameter" do
+ @yieldsmixed.find_index {|*args| ScratchPad << args; false }
+ ScratchPad.recorded.should == EnumerableSpecs::YieldsMixed2.greedy_yields
+ end
+ end
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_unknown_size, :find_index
+end
diff --git a/spec/ruby/core/enumerable/find_spec.rb b/spec/ruby/core/enumerable/find_spec.rb
new file mode 100644
index 0000000000..5ddebc05f8
--- /dev/null
+++ b/spec/ruby/core/enumerable/find_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find'
+
+describe "Enumerable#find" do
+ it_behaves_like :enumerable_find, :find
+end
diff --git a/spec/ruby/core/enumerable/first_spec.rb b/spec/ruby/core/enumerable/first_spec.rb
new file mode 100644
index 0000000000..592dff1ebc
--- /dev/null
+++ b/spec/ruby/core/enumerable/first_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/take'
+
+describe "Enumerable#first" do
+ it "returns the first element" do
+ EnumerableSpecs::Numerous.new.first.should == 2
+ EnumerableSpecs::Empty.new.first.should == nil
+ end
+
+ it "returns nil if self is empty" do
+ EnumerableSpecs::Empty.new.first.should == nil
+ end
+
+ it 'returns a gathered array from yield parameters' do
+ EnumerableSpecs::YieldsMulti.new.to_enum.first.should == [1, 2]
+ EnumerableSpecs::YieldsMixed2.new.to_enum.first.should == nil
+ end
+
+ it "raises a RangeError when passed a Bignum" do
+ enum = EnumerableSpecs::Empty.new
+ -> { enum.first(bignum_value) }.should.raise(RangeError)
+ end
+
+ describe "when passed an argument" do
+ it_behaves_like :enumerable_take, :first
+ end
+end
diff --git a/spec/ruby/core/enumerable/fixtures/classes.rb b/spec/ruby/core/enumerable/fixtures/classes.rb
new file mode 100644
index 0000000000..b5feafcfb7
--- /dev/null
+++ b/spec/ruby/core/enumerable/fixtures/classes.rb
@@ -0,0 +1,350 @@
+module EnumerableSpecs
+
+ 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
+
+ class NumerousWithSize < Numerous
+ def size
+ @list.size
+ end
+ end
+
+ class EachCounter < Numerous
+ attr_reader :times_called, :times_yielded, :arguments_passed
+ def initialize(*list)
+ super(*list)
+ @times_yielded = @times_called = 0
+ end
+
+ def each(*arg)
+ @times_called += 1
+ @times_yielded = 0
+ @arguments_passed = arg
+ @list.each do |i|
+ @times_yielded +=1
+ yield i
+ end
+ end
+ end
+
+ class Empty
+ include Enumerable
+ def each
+ self
+ end
+ end
+
+ class EmptyWithSize
+ include Enumerable
+ def each
+ self
+ end
+ def size
+ 0
+ end
+ end
+
+ class ThrowingEach
+ include Enumerable
+ def each
+ raise "from each"
+ end
+ end
+
+ class NoEach
+ include Enumerable
+ end
+
+ # (Legacy form rubycon)
+ class EachDefiner
+
+ include Enumerable
+
+ attr_reader :arr
+
+ def initialize(*arr)
+ @arr = arr
+ end
+
+ def each
+ i = 0
+ loop do
+ break if i == @arr.size
+ yield @arr[i]
+ i += 1
+ end
+ end
+
+ end
+
+ class SortByDummy
+ def initialize(s)
+ @s = s
+ end
+
+ def s
+ @s
+ end
+ end
+
+ class ComparesByVowelCount
+
+ attr_accessor :value, :vowels
+
+ def self.wrap(*args)
+ args.map {|element| ComparesByVowelCount.new(element)}
+ end
+
+ def initialize(string)
+ self.value = string
+ self.vowels = string.gsub(/[^aeiou]/, '').size
+ end
+
+ def <=>(other)
+ self.vowels <=> other.vowels
+ end
+
+ end
+
+ class InvalidComparable
+ def <=>(other)
+ "Not Valid"
+ end
+ end
+
+ class ArrayConvertible
+ attr_accessor :called
+ def initialize(*values)
+ @values = values
+ end
+
+ def to_a
+ self.called = :to_a
+ @values
+ end
+
+ def to_ary
+ self.called = :to_ary
+ @values
+ end
+ end
+
+ class EnumConvertible
+ attr_accessor :called
+ attr_accessor :sym
+ def initialize(delegate)
+ @delegate = delegate
+ end
+
+ def to_enum(sym)
+ self.called = :to_enum
+ self.sym = sym
+ @delegate.to_enum(sym)
+ end
+
+ def respond_to_missing?(*args)
+ @delegate.respond_to?(*args)
+ end
+ end
+
+ class Equals
+ def initialize(obj)
+ @obj = obj
+ end
+ def ==(other)
+ @obj == other
+ end
+ end
+
+ class YieldsMulti
+ include Enumerable
+ def each
+ yield 1,2
+ yield 3,4,5
+ yield 6,7,8,9
+ end
+ end
+
+ class YieldsMultiWithFalse
+ include Enumerable
+ def each
+ yield false,2
+ yield false,4,5
+ yield false,7,8,9
+ end
+ end
+
+ class YieldsMultiWithSingleTrue
+ include Enumerable
+ def each
+ yield false,2
+ yield true,4,5
+ yield false,7,8,9
+ end
+ end
+
+ class YieldsMixed
+ include Enumerable
+ def each
+ yield 1
+ yield [2]
+ yield 3,4
+ yield 5,6,7
+ yield [8,9]
+ yield nil
+ yield []
+ end
+ end
+
+ class YieldsMixed2
+ include Enumerable
+
+ def self.first_yields
+ [nil, 0, 0, 0, 0, nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.gathered_yields
+ [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.gathered_yields_with_args(arg, *args)
+ [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, arg, args, [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.greedy_yields
+ [[], [0], [0, 1], [0, 1, 2], [0, 1, 2], [nil], [:default_arg], [[]], [[]], [[0]], [[0, 1]], [[0, 1, 2]]]
+ end
+
+ def each(arg=:default_arg, *args)
+ yield
+ yield 0
+ yield 0, 1
+ yield 0, 1, 2
+ yield(*[0, 1, 2])
+ yield nil
+ yield arg
+ yield args
+ yield []
+ yield [0]
+ yield [0, 1]
+ yield [0, 1, 2]
+ end
+ end
+
+ class ReverseComparable
+ include Comparable
+ def initialize(num)
+ @num = num
+ end
+
+ attr_accessor :num
+
+ # Reverse comparison
+ def <=>(other)
+ other.num <=> @num
+ end
+ end
+
+ class ComparableWithInteger
+ include Comparable
+ def initialize(num)
+ @num = num
+ end
+
+ def <=>(fixnum)
+ @num <=> fixnum
+ end
+ end
+
+ class Uncomparable
+ def <=>(obj)
+ nil
+ end
+ end
+
+ class Undupable
+ attr_reader :initialize_called, :initialize_dup_called
+ def dup
+ raise "Can't, sorry"
+ end
+
+ def clone
+ raise "Can't, either, sorry"
+ end
+
+ def initialize
+ @initialize_dup = true
+ end
+
+ def initialize_dup(arg)
+ @initialize_dup_called = true
+ end
+ end
+
+ class Freezy
+ include Enumerable
+
+ def each
+ yield 1
+ yield 2
+ end
+
+ def to_a
+ super.freeze
+ end
+ end
+
+ class MapReturnsEnumerable
+ include Enumerable
+
+ class EnumerableMapping
+ include Enumerable
+
+ def initialize(items, block)
+ @items = items
+ @block = block
+ end
+
+ def each
+ @items.each do |i|
+ yield @block.call(i)
+ end
+ end
+ end
+
+ def each
+ yield 1
+ yield 2
+ yield 3
+ end
+
+ def map(&block)
+ EnumerableMapping.new(self, block)
+ end
+ end
+
+ class Pattern
+ attr_reader :yielded
+
+ def initialize(&block)
+ @block = block
+ @yielded = []
+ end
+
+ def ===(*args)
+ @yielded << args
+ @block.call(*args)
+ end
+ end
+
+ class SetSubclass < Set
+ end
+end # EnumerableSpecs utility classes
diff --git a/spec/ruby/core/enumerable/flat_map_spec.rb b/spec/ruby/core/enumerable/flat_map_spec.rb
new file mode 100644
index 0000000000..bd07eab6c5
--- /dev/null
+++ b/spec/ruby/core/enumerable/flat_map_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect_concat'
+
+describe "Enumerable#flat_map" do
+ it_behaves_like :enumerable_collect_concat, :flat_map
+end
diff --git a/spec/ruby/core/enumerable/grep_spec.rb b/spec/ruby/core/enumerable/grep_spec.rb
new file mode 100644
index 0000000000..965e183766
--- /dev/null
+++ b/spec/ruby/core/enumerable/grep_spec.rb
@@ -0,0 +1,87 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#grep" do
+ before :each do
+ @a = EnumerableSpecs::EachDefiner.new( 2, 4, 6, 8, 10)
+ end
+
+ it "grep without a block should return an array of all elements === pattern" do
+ class EnumerableSpecGrep; def ===(obj); obj == '2'; end; end
+
+ EnumerableSpecs::Numerous.new('2', 'a', 'nil', '3', false).grep(EnumerableSpecGrep.new).should == ['2']
+ end
+
+ it "grep with a block should return an array of elements === pattern passed through block" do
+ class EnumerableSpecGrep2; def ===(obj); /^ca/ =~ obj; end; end
+
+ EnumerableSpecs::Numerous.new("cat", "coat", "car", "cadr", "cost").grep(EnumerableSpecGrep2.new) { |i| i.upcase }.should == ["CAT", "CAR", "CADR"]
+ end
+
+ it "grep the enumerable (rubycon legacy)" do
+ EnumerableSpecs::EachDefiner.new().grep(1).should == []
+ @a.grep(3..7).should == [4,6]
+ @a.grep(3..7) {|a| a+1}.should == [5,7]
+ end
+
+ it "can use $~ in the block when used with a Regexp" do
+ ary = ["aba", "aba"]
+ ary.grep(/a(b)a/) { $1 }.should == ["b", "b"]
+ end
+
+ it "sets $~ in the block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep(/b/) { |e|
+ e.should == "abc"
+ $&.should == "b"
+ }
+
+ # Set by the failed match of "def"
+ $~.should == nil
+ end
+
+ it "does not set $~ when given no block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep(/b/).should == ["abc"]
+ $&.should == "z"
+ end
+
+ it "does not modify Regexp.last_match without block" do
+ "z" =~ /z/ # Reset last match
+ ["abc", "def"].grep(/b/).should == ["abc"]
+ Regexp.last_match[0].should == "z"
+ end
+
+ it "correctly handles non-string elements" do
+ 'set last match' =~ /set last (.*)/
+ [:a, 'b', 'z', :c, 42, nil].grep(/[a-d]/).should == [:a, 'b', :c]
+ $1.should == 'match'
+
+ o = Object.new
+ def o.to_str
+ 'hello'
+ end
+ [o].grep(/ll/).first.should.equal?(o)
+ end
+
+ describe "with a block" do
+ before :each do
+ @numerous = EnumerableSpecs::Numerous.new(*(0..9).to_a)
+ def (@odd_matcher = BasicObject.new).===(obj)
+ obj.odd?
+ end
+ end
+
+ it "returns an Array of matched elements that mapped by the block" do
+ @numerous.grep(@odd_matcher) { |n| n * 2 }.should == [2, 6, 10, 14, 18]
+ end
+
+ it "calls the block with gathered array when yielded with multiple arguments" do
+ EnumerableSpecs::YieldsMixed2.new.grep(Object){ |e| e }.should == EnumerableSpecs::YieldsMixed2.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a pattern" do
+ -> { @numerous.grep { |e| e } }.should.raise(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/grep_v_spec.rb b/spec/ruby/core/enumerable/grep_v_spec.rb
new file mode 100644
index 0000000000..ee99a77ac1
--- /dev/null
+++ b/spec/ruby/core/enumerable/grep_v_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#grep_v" do
+ before :each do
+ @numerous = EnumerableSpecs::Numerous.new(*(0..9).to_a)
+ def (@odd_matcher = BasicObject.new).===(obj)
+ obj.odd?
+ end
+ end
+
+ it "sets $~ in the block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep_v(/e/) { |e|
+ e.should == "abc"
+ $~.should == nil
+ }
+
+ # Set by the match of "def"
+ $&.should == "e"
+ end
+
+ it "does not set $~ when given no block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].grep_v(/e/).should == ["abc"]
+ $&.should == "z"
+ end
+
+ it "does not modify Regexp.last_match without block" do
+ "z" =~ /z/ # Reset last match
+ ["abc", "def"].grep_v(/e/).should == ["abc"]
+ Regexp.last_match[0].should == "z"
+ end
+
+ it "correctly handles non-string elements" do
+ 'set last match' =~ /set last (.*)/
+ [:a, 'b', 'z', :c, 42, nil].grep_v(/[a-d]/).should == ['z', 42, nil]
+ $1.should == 'match'
+
+ o = Object.new
+ def o.to_str
+ 'hello'
+ end
+ [o].grep_v(/mm/).first.should.equal?(o)
+ end
+
+ describe "without block" do
+ it "returns an Array of matched elements" do
+ @numerous.grep_v(@odd_matcher).should == [0, 2, 4, 6, 8]
+ end
+
+ it "compares pattern with gathered array when yielded with multiple arguments" do
+ (unmatcher = Object.new).stub!(:===).and_return(false)
+ EnumerableSpecs::YieldsMixed2.new.grep_v(unmatcher).should == EnumerableSpecs::YieldsMixed2.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a pattern" do
+ -> { @numerous.grep_v }.should.raise(ArgumentError)
+ end
+ end
+
+ describe "with block" do
+ it "returns an Array of matched elements that mapped by the block" do
+ @numerous.grep_v(@odd_matcher) { |n| n * 2 }.should == [0, 4, 8, 12, 16]
+ end
+
+ it "calls the block with gathered array when yielded with multiple arguments" do
+ (unmatcher = Object.new).stub!(:===).and_return(false)
+ EnumerableSpecs::YieldsMixed2.new.grep_v(unmatcher){ |e| e }.should == EnumerableSpecs::YieldsMixed2.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a pattern" do
+ -> { @numerous.grep_v { |e| e } }.should.raise(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/group_by_spec.rb b/spec/ruby/core/enumerable/group_by_spec.rb
new file mode 100644
index 0000000000..904e5d6c68
--- /dev/null
+++ b/spec/ruby/core/enumerable/group_by_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#group_by" do
+ it "returns a hash with values grouped according to the block" do
+ e = EnumerableSpecs::Numerous.new("foo", "bar", "baz")
+ h = e.group_by { |word| word[0..0].to_sym }
+ h.should == { f: ["foo"], b: ["bar", "baz"]}
+ end
+
+ it "returns an empty hash for empty enumerables" do
+ EnumerableSpecs::Empty.new.group_by { |x| x}.should == {}
+ end
+
+ it "returns a hash without default_proc" do
+ e = EnumerableSpecs::Numerous.new("foo", "bar", "baz")
+ h = e.group_by { |word| word[0..0].to_sym }
+ h[:some].should == nil
+ h.default_proc.should == nil
+ h.default.should == nil
+ end
+
+ it "returns an Enumerator if called without a block" do
+ EnumerableSpecs::Numerous.new.group_by.should.instance_of?(Enumerator)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ e = EnumerableSpecs::YieldsMulti.new
+ h = e.group_by { |i| i }
+ h.should == { [1, 2] => [[1, 2]],
+ [6, 7, 8, 9] => [[6, 7, 8, 9]],
+ [3, 4, 5] => [[3, 4, 5]] }
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :group_by
+end
diff --git a/spec/ruby/core/enumerable/include_spec.rb b/spec/ruby/core/enumerable/include_spec.rb
new file mode 100644
index 0000000000..dab1b04451
--- /dev/null
+++ b/spec/ruby/core/enumerable/include_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/include'
+
+describe "Enumerable#include?" do
+ it_behaves_like :enumerable_include, :include?
+end
diff --git a/spec/ruby/core/enumerable/inject_spec.rb b/spec/ruby/core/enumerable/inject_spec.rb
new file mode 100644
index 0000000000..e1fe216144
--- /dev/null
+++ b/spec/ruby/core/enumerable/inject_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/inject'
+
+describe "Enumerable#inject" do
+ it_behaves_like :enumerable_inject, :inject
+end
diff --git a/spec/ruby/core/enumerable/lazy_spec.rb b/spec/ruby/core/enumerable/lazy_spec.rb
new file mode 100644
index 0000000000..935e574067
--- /dev/null
+++ b/spec/ruby/core/enumerable/lazy_spec.rb
@@ -0,0 +1,10 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#lazy" do
+ it "returns an instance of Enumerator::Lazy" do
+ EnumerableSpecs::Numerous.new.lazy.should.instance_of?(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerable/map_spec.rb b/spec/ruby/core/enumerable/map_spec.rb
new file mode 100644
index 0000000000..98a70781cf
--- /dev/null
+++ b/spec/ruby/core/enumerable/map_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/collect'
+
+describe "Enumerable#map" do
+ it_behaves_like :enumerable_collect, :map
+end
diff --git a/spec/ruby/core/enumerable/max_by_spec.rb b/spec/ruby/core/enumerable/max_by_spec.rb
new file mode 100644
index 0000000000..f67b5d15ea
--- /dev/null
+++ b/spec/ruby/core/enumerable/max_by_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#max_by" do
+ it "returns an enumerator if no block" do
+ EnumerableSpecs::Numerous.new(42).max_by.should.instance_of?(Enumerator)
+ end
+
+ it "returns nil if #each yields no objects" do
+ EnumerableSpecs::Empty.new.max_by {|o| o.nonesuch }.should == nil
+ end
+
+ it "returns the object for whom the value returned by block is the largest" do
+ EnumerableSpecs::Numerous.new(*%w[1 2 3]).max_by {|obj| obj.to_i }.should == '3'
+ EnumerableSpecs::Numerous.new(*%w[three five]).max_by {|obj| obj.length }.should == 'three'
+ end
+
+ it "returns the object that appears first in #each in case of a tie" do
+ a, b, c = '1', '2', '2'
+ EnumerableSpecs::Numerous.new(a, b, c).max_by {|obj| obj.to_i }.should.equal?(b)
+ end
+
+ it "uses max.<=>(current) to determine order" do
+ a, b, c = (1..3).map{|n| EnumerableSpecs::ReverseComparable.new(n)}
+
+ # Just using self here to avoid additional complexity
+ EnumerableSpecs::Numerous.new(a, b, c).max_by {|obj| obj }.should == a
+ end
+
+ it "is able to return the maximum for enums that contain nils" do
+ enum = EnumerableSpecs::Numerous.new(nil, nil, true)
+ enum.max_by {|o| o.nil? ? 0 : 1 }.should == true
+ enum.max_by {|o| o.nil? ? 1 : 0 }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.max_by {|e| e.size}.should == [6, 7, 8, 9]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :max_by
+
+ context "when called with an argument n" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(101, 55, 1, 20, 33, 500, 60)
+ end
+
+ context "without a block" do
+ it "returns an enumerator" do
+ @enum.max_by(2).should.instance_of?(Enumerator)
+ end
+ end
+
+ context "with a block" do
+ it "returns an array containing the maximum n elements based on the block's value" do
+ result = @enum.max_by(3) { |i| i.to_s }
+ result.should == [60, 55, 500]
+ end
+
+ context "on a enumerable of length x where x < n" do
+ it "returns an array containing the maximum n elements of length n" do
+ result = @enum.max_by(500) { |i| i.to_s }
+ result.length.should == 7
+ end
+ end
+
+ context "when n is negative" do
+ it "raises an ArgumentError" do
+ -> { @enum.max_by(-1) { |i| i.to_s } }.should.raise(ArgumentError)
+ end
+ end
+ end
+
+ context "when n is nil" do
+ it "returns the maximum element" do
+ @enum.max_by(nil) { |i| i.to_s }.should == 60
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/max_spec.rb b/spec/ruby/core/enumerable/max_spec.rb
new file mode 100644
index 0000000000..d92700258b
--- /dev/null
+++ b/spec/ruby/core/enumerable/max_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#max" do
+ before :each do
+ @e_strs = EnumerableSpecs::EachDefiner.new("333", "22", "666666", "1", "55555", "1010101010")
+ @e_ints = EnumerableSpecs::EachDefiner.new( 333, 22, 666666, 55555, 1010101010)
+ end
+
+ it "returns the maximum element" do
+ EnumerableSpecs::Numerous.new.max.should == 6
+ end
+
+ it "returns the maximum element (basics cases)" do
+ EnumerableSpecs::EachDefiner.new(55).max.should == 55
+
+ EnumerableSpecs::EachDefiner.new(11,99).max.should == 99
+ EnumerableSpecs::EachDefiner.new(99,11).max.should == 99
+ EnumerableSpecs::EachDefiner.new(2, 33, 4, 11).max.should == 33
+
+ EnumerableSpecs::EachDefiner.new(1,2,3,4,5).max.should == 5
+ EnumerableSpecs::EachDefiner.new(5,4,3,2,1).max.should == 5
+ EnumerableSpecs::EachDefiner.new(1,4,3,5,2).max.should == 5
+ EnumerableSpecs::EachDefiner.new(5,5,5,5,5).max.should == 5
+
+ EnumerableSpecs::EachDefiner.new("aa","tt").max.should == "tt"
+ EnumerableSpecs::EachDefiner.new("tt","aa").max.should == "tt"
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").max.should == "4"
+
+ @e_strs.max.should == "666666"
+ @e_ints.max.should == 1010101010
+ end
+
+ it "returns nil for an empty Enumerable" do
+ EnumerableSpecs::EachDefiner.new.max.should == nil
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> do
+ EnumerableSpecs::EachDefiner.new(BasicObject.new, BasicObject.new).max
+ end.should.raise(NoMethodError)
+ end
+
+ it "raises an ArgumentError for incomparable elements" do
+ -> do
+ EnumerableSpecs::EachDefiner.new(11,"22").max
+ end.should.raise(ArgumentError)
+ -> do
+ EnumerableSpecs::EachDefiner.new(11,12,22,33).max{|a, b| nil}
+ end.should.raise(ArgumentError)
+ end
+
+ context "when passed a block" do
+ it "returns the maximum element" do
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").max {|a,b| a <=> b }.should == "4"
+ EnumerableSpecs::EachDefiner.new( 2 , 33 , 4 , 11 ).max {|a,b| a <=> b }.should == 33
+
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").max {|a,b| b <=> a }.should == "11"
+ EnumerableSpecs::EachDefiner.new( 2 , 33 , 4 , 11 ).max {|a,b| b <=> a }.should == 2
+
+ @e_strs.max {|a,b| a.length <=> b.length }.should == "1010101010"
+
+ @e_strs.max {|a,b| a <=> b }.should == "666666"
+ @e_strs.max {|a,b| a.to_i <=> b.to_i }.should == "1010101010"
+
+ @e_ints.max {|a,b| a <=> b }.should == 1010101010
+ @e_ints.max {|a,b| a.to_s <=> b.to_s }.should == 666666
+ end
+ end
+
+ it "returns the maximum for enumerables that contain nils" do
+ arr = EnumerableSpecs::Numerous.new(nil, nil, true)
+ arr.max { |a, b|
+ x = a.nil? ? 1 : a ? 0 : -1
+ y = b.nil? ? 1 : b ? 0 : -1
+ x <=> y
+ }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.max.should == [6, 7, 8, 9]
+ end
+
+ context "when called with an argument n" do
+ context "without a block" do
+ it "returns an array containing the maximum n elements" do
+ result = @e_ints.max(2)
+ result.should == [1010101010, 666666]
+ end
+ end
+
+ context "with a block" do
+ it "returns an array containing the maximum n elements" do
+ result = @e_ints.max(2) { |a, b| a * 2 <=> b * 2 }
+ result.should == [1010101010, 666666]
+ end
+ end
+
+ context "on a enumerable of length x where x < n" do
+ it "returns an array containing the maximum n elements of length x" do
+ result = @e_ints.max(500)
+ result.length.should == 5
+ end
+ end
+
+ context "that is negative" do
+ it "raises an ArgumentError" do
+ -> { @e_ints.max(-1) }.should.raise(ArgumentError)
+ end
+ end
+ end
+
+ context "that is nil" do
+ it "returns the maximum element" do
+ @e_ints.max(nil).should == 1010101010
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/member_spec.rb b/spec/ruby/core/enumerable/member_spec.rb
new file mode 100644
index 0000000000..1fe3cebd28
--- /dev/null
+++ b/spec/ruby/core/enumerable/member_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/include'
+
+describe "Enumerable#member?" do
+ it_behaves_like :enumerable_include, :member?
+end
diff --git a/spec/ruby/core/enumerable/min_by_spec.rb b/spec/ruby/core/enumerable/min_by_spec.rb
new file mode 100644
index 0000000000..4f949e2130
--- /dev/null
+++ b/spec/ruby/core/enumerable/min_by_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#min_by" do
+ it "returns an enumerator if no block" do
+ EnumerableSpecs::Numerous.new(42).min_by.should.instance_of?(Enumerator)
+ end
+
+ it "returns nil if #each yields no objects" do
+ EnumerableSpecs::Empty.new.min_by {|o| o.nonesuch }.should == nil
+ end
+
+ it "returns the object for whom the value returned by block is the smallest" do
+ EnumerableSpecs::Numerous.new(*%w[3 2 1]).min_by {|obj| obj.to_i }.should == '1'
+ EnumerableSpecs::Numerous.new(*%w[five three]).min_by {|obj| obj.length }.should == 'five'
+ end
+
+ it "returns the object that appears first in #each in case of a tie" do
+ a, b, c = '2', '1', '1'
+ EnumerableSpecs::Numerous.new(a, b, c).min_by {|obj| obj.to_i }.should.equal?(b)
+ end
+
+ it "uses min.<=>(current) to determine order" do
+ a, b, c = (1..3).map{|n| EnumerableSpecs::ReverseComparable.new(n)}
+
+ # Just using self here to avoid additional complexity
+ EnumerableSpecs::Numerous.new(a, b, c).min_by {|obj| obj }.should == c
+ end
+
+ it "is able to return the minimum for enums that contain nils" do
+ enum = EnumerableSpecs::Numerous.new(nil, nil, true)
+ enum.min_by {|o| o.nil? ? 0 : 1 }.should == nil
+ enum.min_by {|o| o.nil? ? 1 : 0 }.should == true
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.min_by {|e| e.size}.should == [1, 2]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :min_by
+
+ context "when called with an argument n" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(101, 55, 1, 20, 33, 500, 60)
+ end
+
+ context "without a block" do
+ it "returns an enumerator" do
+ @enum.min_by(2).should.instance_of?(Enumerator)
+ end
+ end
+
+ context "with a block" do
+ it "returns an array containing the minimum n elements based on the block's value" do
+ result = @enum.min_by(3) { |i| i.to_s }
+ result.should == [1, 101, 20]
+ end
+
+ context "on a enumerable of length x where x < n" do
+ it "returns an array containing the minimum n elements of length n" do
+ result = @enum.min_by(500) { |i| i.to_s }
+ result.length.should == 7
+ end
+ end
+
+ context "when n is negative" do
+ it "raises an ArgumentError" do
+ -> { @enum.min_by(-1) { |i| i.to_s } }.should.raise(ArgumentError)
+ end
+ end
+ end
+
+ context "when n is nil" do
+ it "returns the minimum element" do
+ @enum.min_by(nil) { |i| i.to_s }.should == 1
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/min_spec.rb b/spec/ruby/core/enumerable/min_spec.rb
new file mode 100644
index 0000000000..f05d59c2c9
--- /dev/null
+++ b/spec/ruby/core/enumerable/min_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#min" do
+ before :each do
+ @e_strs = EnumerableSpecs::EachDefiner.new("333", "22", "666666", "1", "55555", "1010101010")
+ @e_ints = EnumerableSpecs::EachDefiner.new( 333, 22, 666666, 55555, 1010101010)
+ end
+
+ it "min should return the minimum element" do
+ EnumerableSpecs::Numerous.new.min.should == 1
+ end
+
+ it "returns the minimum (basic cases)" do
+ EnumerableSpecs::EachDefiner.new(55).min.should == 55
+
+ EnumerableSpecs::EachDefiner.new(11,99).min.should == 11
+ EnumerableSpecs::EachDefiner.new(99,11).min.should == 11
+ EnumerableSpecs::EachDefiner.new(2, 33, 4, 11).min.should == 2
+
+ EnumerableSpecs::EachDefiner.new(1,2,3,4,5).min.should == 1
+ EnumerableSpecs::EachDefiner.new(5,4,3,2,1).min.should == 1
+ EnumerableSpecs::EachDefiner.new(4,1,3,5,2).min.should == 1
+ EnumerableSpecs::EachDefiner.new(5,5,5,5,5).min.should == 5
+
+ EnumerableSpecs::EachDefiner.new("aa","tt").min.should == "aa"
+ EnumerableSpecs::EachDefiner.new("tt","aa").min.should == "aa"
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").min.should == "11"
+
+ @e_strs.min.should == "1"
+ @e_ints.min.should == 22
+ end
+
+ it "returns nil for an empty Enumerable" do
+ EnumerableSpecs::EachDefiner.new.min.should == nil
+ end
+
+ it "raises a NoMethodError for elements without #<=>" do
+ -> do
+ EnumerableSpecs::EachDefiner.new(BasicObject.new, BasicObject.new).min
+ end.should.raise(NoMethodError)
+ end
+
+ it "raises an ArgumentError for incomparable elements" do
+ -> do
+ EnumerableSpecs::EachDefiner.new(11,"22").min
+ end.should.raise(ArgumentError)
+ -> do
+ EnumerableSpecs::EachDefiner.new(11,12,22,33).min{|a, b| nil}
+ end.should.raise(ArgumentError)
+ end
+
+ it "returns the minimum when using a block rule" do
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").min {|a,b| a <=> b }.should == "11"
+ EnumerableSpecs::EachDefiner.new( 2 , 33 , 4 , 11 ).min {|a,b| a <=> b }.should == 2
+
+ EnumerableSpecs::EachDefiner.new("2","33","4","11").min {|a,b| b <=> a }.should == "4"
+ EnumerableSpecs::EachDefiner.new( 2 , 33 , 4 , 11 ).min {|a,b| b <=> a }.should == 33
+
+ EnumerableSpecs::EachDefiner.new( 1, 2, 3, 4 ).min {|a,b| 15 }.should == 1
+
+ EnumerableSpecs::EachDefiner.new(11,12,22,33).min{|a, b| 2 }.should == 11
+ @i = -2
+ EnumerableSpecs::EachDefiner.new(11,12,22,33).min{|a, b| @i += 1 }.should == 12
+
+ @e_strs.min {|a,b| a.length <=> b.length }.should == "1"
+
+ @e_strs.min {|a,b| a <=> b }.should == "1"
+ @e_strs.min {|a,b| a.to_i <=> b.to_i }.should == "1"
+
+ @e_ints.min {|a,b| a <=> b }.should == 22
+ @e_ints.min {|a,b| a.to_s <=> b.to_s }.should == 1010101010
+ end
+
+ it "returns the minimum for enumerables that contain nils" do
+ arr = EnumerableSpecs::Numerous.new(nil, nil, true)
+ arr.min { |a, b|
+ x = a.nil? ? -1 : a ? 0 : 1
+ y = b.nil? ? -1 : b ? 0 : 1
+ x <=> y
+ }.should == nil
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.min.should == [1, 2]
+ end
+
+ context "when called with an argument n" do
+ context "without a block" do
+ it "returns an array containing the minimum n elements" do
+ result = @e_ints.min(2)
+ result.should == [22, 333]
+ end
+ end
+
+ context "with a block" do
+ it "returns an array containing the minimum n elements" do
+ result = @e_ints.min(2) { |a, b| a * 2 <=> b * 2 }
+ result.should == [22, 333]
+ end
+ end
+
+ context "on a enumerable of length x where x < n" do
+ it "returns an array containing the minimum n elements of length x" do
+ result = @e_ints.min(500)
+ result.length.should == 5
+ end
+ end
+
+ context "that is negative" do
+ it "raises an ArgumentError" do
+ -> { @e_ints.min(-1) }.should.raise(ArgumentError)
+ end
+ end
+ end
+
+ context "that is nil" do
+ it "returns the minimum element" do
+ @e_ints.min(nil).should == 22
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/minmax_by_spec.rb b/spec/ruby/core/enumerable/minmax_by_spec.rb
new file mode 100644
index 0000000000..30c88cfcfb
--- /dev/null
+++ b/spec/ruby/core/enumerable/minmax_by_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#minmax_by" do
+ it "returns an enumerator if no block" do
+ EnumerableSpecs::Numerous.new(42).minmax_by.should.instance_of?(Enumerator)
+ end
+
+ it "returns nil if #each yields no objects" do
+ EnumerableSpecs::Empty.new.minmax_by {|o| o.nonesuch }.should == [nil, nil]
+ end
+
+ it "returns the object for whom the value returned by block is the largest" do
+ EnumerableSpecs::Numerous.new(*%w[1 2 3]).minmax_by {|obj| obj.to_i }.should == ['1', '3']
+ EnumerableSpecs::Numerous.new(*%w[three five]).minmax_by {|obj| obj.length }.should == ['five', 'three']
+ end
+
+ it "returns the object that appears first in #each in case of a tie" do
+ a, b, c, d = '1', '1', '2', '2'
+ mm = EnumerableSpecs::Numerous.new(a, b, c, d).minmax_by {|obj| obj.to_i }
+ mm[0].should.equal?(a)
+ mm[1].should.equal?(c)
+ end
+
+ it "uses min/max.<=>(current) to determine order" do
+ a, b, c = (1..3).map{|n| EnumerableSpecs::ReverseComparable.new(n)}
+
+ # Just using self here to avoid additional complexity
+ EnumerableSpecs::Numerous.new(a, b, c).minmax_by {|obj| obj }.should == [c, a]
+ end
+
+ it "is able to return the maximum for enums that contain nils" do
+ enum = EnumerableSpecs::Numerous.new(nil, nil, true)
+ enum.minmax_by {|o| o.nil? ? 0 : 1 }.should == [nil, true]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.minmax_by {|e| e.size}.should == [[1, 2], [6, 7, 8, 9]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :minmax_by
+end
diff --git a/spec/ruby/core/enumerable/minmax_spec.rb b/spec/ruby/core/enumerable/minmax_spec.rb
new file mode 100644
index 0000000000..f5f17ef079
--- /dev/null
+++ b/spec/ruby/core/enumerable/minmax_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/enumerable/minmax'
+
+describe "Enumerable#minmax" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(6, 4, 5, 10, 8)
+ @empty_enum = EnumerableSpecs::Empty.new
+ @incomparable_enum = EnumerableSpecs::Numerous.new(BasicObject.new, BasicObject.new)
+ @incompatible_enum = EnumerableSpecs::Numerous.new(11,"22")
+ @strs = EnumerableSpecs::Numerous.new("333", "2", "60", "55555", "1010", "111")
+ end
+
+ it_behaves_like :enumerable_minmax, :minmax
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.minmax.should == [[1, 2], [6, 7, 8, 9]]
+ end
+end
diff --git a/spec/ruby/core/enumerable/none_spec.rb b/spec/ruby/core/enumerable/none_spec.rb
new file mode 100644
index 0000000000..d9ee0b441e
--- /dev/null
+++ b/spec/ruby/core/enumerable/none_spec.rb
@@ -0,0 +1,153 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#none?" do
+ before :each do
+ @empty = EnumerableSpecs::Empty.new
+ @enum = EnumerableSpecs::Numerous.new
+ @enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
+ @enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
+ end
+
+ it "always returns true on empty enumeration" do
+ @empty.should.none?
+ @empty.none? { true }.should == true
+ end
+
+ it "raises an ArgumentError when more than 1 argument is provided" do
+ -> { @enum.none?(1, 2, 3) }.should.raise(ArgumentError)
+ -> { [].none?(1, 2, 3) }.should.raise(ArgumentError)
+ -> { {}.none?(1, 2, 3) }.should.raise(ArgumentError)
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.none?
+ }.should.raise(RuntimeError)
+
+ -> {
+ EnumerableSpecs::ThrowingEach.new.none? { false }
+ }.should.raise(RuntimeError)
+ end
+
+ describe "with no block" do
+ it "returns true if none of the elements in self are true" do
+ e = EnumerableSpecs::Numerous.new(false, nil, false)
+ e.none?.should == true
+ end
+
+ it "returns false if at least one of the elements in self are true" do
+ e = EnumerableSpecs::Numerous.new(false, nil, true, false)
+ e.none?.should == false
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMultiWithFalse.new
+ multi.none?.should == false
+ end
+ end
+
+ describe "with a block" do
+ before :each do
+ @e = EnumerableSpecs::Numerous.new(1,1,2,3,4)
+ end
+
+ it "passes each element to the block in turn until it returns true" do
+ acc = []
+ @e.none? {|e| acc << e; false }
+ acc.should == [1,1,2,3,4]
+ end
+
+ it "stops passing elements to the block when it returns true" do
+ acc = []
+ @e.none? {|e| acc << e; e == 3 ? true : false }
+ acc.should == [1,1,2,3]
+ end
+
+ it "returns true if the block never returns true" do
+ @e.none? {|e| false }.should == true
+ end
+
+ it "returns false if the block ever returns true" do
+ @e.none? {|e| e == 3 ? true : false }.should == false
+ end
+
+ it "does not hide exceptions out of the block" do
+ -> {
+ @enum.none? { raise "from block" }
+ }.should.raise(RuntimeError)
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.none? { |e| yielded << e; false }
+ yielded.should == [1, 3, 6]
+ end
+
+ it "yields multiple arguments when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.none? { |*args| yielded << args; false }
+ yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe 'when given a pattern argument' do
+ it "calls `===` on the pattern the return value" do
+ pattern = EnumerableSpecs::Pattern.new { |x| x == 3 }
+ @enum1.none?(pattern).should == true
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+ end
+
+ it "always returns true on empty enumeration" do
+ @empty.none?(Integer).should == true
+ [].none?(Integer).should == true
+ {}.none?(NilClass).should == true
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.none?(Integer)
+ }.should.raise(RuntimeError)
+ end
+
+ it "returns true if the pattern never returns a truthy value" do
+ @enum2.none?(Integer).should == true
+ pattern = EnumerableSpecs::Pattern.new { |x| nil }
+ @enum.none?(pattern).should == true
+
+ [1, 42, 3].none?(pattern).should == true
+ {a: 1, b: 2}.none?(pattern).should == true
+ end
+
+ it "returns false if the pattern ever returns other than false or nil" do
+ pattern = EnumerableSpecs::Pattern.new { |x| x < 0 }
+ @enum1.none?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+
+ [1, 2, 3, -1].none?(pattern).should == false
+ {a: 1}.none?(Array).should == false
+ end
+
+ it "does not hide exceptions out of pattern#===" do
+ pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
+ -> {
+ @enum.none?(pattern)
+ }.should.raise(RuntimeError)
+ end
+
+ it "calls the pattern with gathered array when yielded with multiple arguments" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ pattern = EnumerableSpecs::Pattern.new { false }
+ multi.none?(pattern).should == true
+ pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ EnumerableSpecs::Numerous.new(1, 2, 3, 4, 5).none?(String) { true }.should == true
+ }.should complain(/given block not used/)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/one_spec.rb b/spec/ruby/core/enumerable/one_spec.rb
new file mode 100644
index 0000000000..cf966d4a9b
--- /dev/null
+++ b/spec/ruby/core/enumerable/one_spec.rb
@@ -0,0 +1,154 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#one?" do
+ before :each do
+ @empty = EnumerableSpecs::Empty.new
+ @enum = EnumerableSpecs::Numerous.new
+ @enum1 = EnumerableSpecs::Numerous.new(0, 1, 2, -1)
+ @enum2 = EnumerableSpecs::Numerous.new(nil, false, true)
+ end
+
+ it "always returns false on empty enumeration" do
+ @empty.should_not.one?
+ @empty.one? { true }.should == false
+ end
+
+ it "raises an ArgumentError when more than 1 argument is provided" do
+ -> { @enum.one?(1, 2, 3) }.should.raise(ArgumentError)
+ -> { [].one?(1, 2, 3) }.should.raise(ArgumentError)
+ -> { {}.one?(1, 2, 3) }.should.raise(ArgumentError)
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.one?
+ }.should.raise(RuntimeError)
+
+ -> {
+ EnumerableSpecs::ThrowingEach.new.one? { false }
+ }.should.raise(RuntimeError)
+ end
+
+ describe "with no block" do
+ it "returns true if only one element evaluates to true" do
+ [false, nil, true].one?.should == true
+ end
+
+ it "returns false if two elements evaluate to true" do
+ [false, :value, nil, true].one?.should == false
+ end
+
+ it "returns false if all elements evaluate to false" do
+ [false, nil, false].one?.should == false
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMultiWithSingleTrue.new
+ multi.one?.should == false
+ end
+ end
+
+ describe "with a block" do
+ it "returns true if block returns true once" do
+ [:a, :b, :c].one? { |s| s == :a }.should == true
+ end
+
+ it "returns false if the block returns true more than once" do
+ [:a, :b, :c].one? { |s| s == :a || s == :b }.should == false
+ end
+
+ it "returns false if the block only returns false" do
+ [:a, :b, :c].one? { |s| s == :d }.should == false
+ end
+
+ it "does not hide exceptions out of the block" do
+ -> {
+ @enum.one? { raise "from block" }
+ }.should.raise(RuntimeError)
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.one? { |e| yielded << e; false }.should == false
+ yielded.should == [1, 3, 6]
+ end
+
+ it "yields multiple arguments when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.one? { |*args| yielded << args; false }.should == false
+ yielded.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+ end
+
+ describe 'when given a pattern argument' do
+ it "calls `===` on the pattern the return value" do
+ pattern = EnumerableSpecs::Pattern.new { |x| x == 1 }
+ @enum1.one?(pattern).should == true
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+ end
+
+ it "always returns false on empty enumeration" do
+ @empty.one?(Integer).should == false
+ [].one?(Integer).should == false
+ {}.one?(NilClass).should == false
+ end
+
+ it "does not hide exceptions out of #each" do
+ -> {
+ EnumerableSpecs::ThrowingEach.new.one?(Integer)
+ }.should.raise(RuntimeError)
+ end
+
+ it "returns true if the pattern returns a truthy value only once" do
+ @enum2.one?(NilClass).should == true
+ pattern = EnumerableSpecs::Pattern.new { |x| x == 2 }
+ @enum1.one?(pattern).should == true
+
+ [1, 2, 42, 3].one?(pattern).should == true
+
+ pattern = EnumerableSpecs::Pattern.new { |x| x == [:b, 2] }
+ {a: 1, b: 2}.one?(pattern).should == true
+ end
+
+ it "returns false if the pattern returns a truthy value more than once" do
+ pattern = EnumerableSpecs::Pattern.new { |x| !x }
+ @enum2.one?(pattern).should == false
+ pattern.yielded.should == [[nil], [false]]
+
+ [1, 2, 3].one?(Integer).should == false
+ {a: 1, b: 2}.one?(Array).should == false
+ end
+
+ it "returns false if the pattern never returns a truthy value" do
+ pattern = EnumerableSpecs::Pattern.new { |x| nil }
+ @enum1.one?(pattern).should == false
+ pattern.yielded.should == [[0], [1], [2], [-1]]
+
+ [1, 2, 3].one?(pattern).should == false
+ {a: 1}.one?(pattern).should == false
+ end
+
+ it "does not hide exceptions out of pattern#===" do
+ pattern = EnumerableSpecs::Pattern.new { raise "from pattern" }
+ -> {
+ @enum.one?(pattern)
+ }.should.raise(RuntimeError)
+ end
+
+ it "calls the pattern with gathered array when yielded with multiple arguments" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ pattern = EnumerableSpecs::Pattern.new { false }
+ multi.one?(pattern).should == false
+ pattern.yielded.should == [[[1, 2]], [[3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+
+ it "ignores the block if there is an argument" do
+ -> {
+ EnumerableSpecs::Numerous.new(1, 2, 3, 4, "5").one?(String) { false }.should == true
+ }.should complain(/given block not used/)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/partition_spec.rb b/spec/ruby/core/enumerable/partition_spec.rb
new file mode 100644
index 0000000000..8272ee189e
--- /dev/null
+++ b/spec/ruby/core/enumerable/partition_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#partition" do
+ it "returns two arrays, the first containing elements for which the block is true, the second containing the rest" do
+ EnumerableSpecs::Numerous.new.partition { |i| i % 2 == 0 }.should == [[2, 6, 4], [5, 3, 1]]
+ end
+
+ it "returns an Enumerator if called without a block" do
+ EnumerableSpecs::Numerous.new.partition.should.instance_of?(Enumerator)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.partition {|e| e == [3, 4, 5] }.should == [[[3, 4, 5]], [[1, 2], [6, 7, 8, 9]]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :partition
+end
diff --git a/spec/ruby/core/enumerable/reduce_spec.rb b/spec/ruby/core/enumerable/reduce_spec.rb
new file mode 100644
index 0000000000..bc8691c1b0
--- /dev/null
+++ b/spec/ruby/core/enumerable/reduce_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/inject'
+
+describe "Enumerable#reduce" do
+ it_behaves_like :enumerable_inject, :reduce
+end
diff --git a/spec/ruby/core/enumerable/reject_spec.rb b/spec/ruby/core/enumerable/reject_spec.rb
new file mode 100644
index 0000000000..31e89f5b0e
--- /dev/null
+++ b/spec/ruby/core/enumerable/reject_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#reject" do
+ it "returns an array of the elements for which block is false" do
+ EnumerableSpecs::Numerous.new.reject { |i| i > 3 }.should == [2, 3, 1]
+ entries = (1..10).to_a
+ numerous = EnumerableSpecs::Numerous.new(*entries)
+ numerous.reject {|i| i % 2 == 0 }.should == [1,3,5,7,9]
+ numerous.reject {|i| true }.should == []
+ numerous.reject {|i| false }.should == entries
+ end
+
+ it "returns an Enumerator if called without a block" do
+ EnumerableSpecs::Numerous.new.reject.should.instance_of?(Enumerator)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.reject {|e| e == [3, 4, 5] }.should == [[1, 2], [6, 7, 8, 9]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :reject
+end
diff --git a/spec/ruby/core/enumerable/reverse_each_spec.rb b/spec/ruby/core/enumerable/reverse_each_spec.rb
new file mode 100644
index 0000000000..4753956724
--- /dev/null
+++ b/spec/ruby/core/enumerable/reverse_each_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#reverse_each" do
+ it "traverses enum in reverse order and pass each element to block" do
+ a=[]
+ EnumerableSpecs::Numerous.new.reverse_each { |i| a << i }
+ a.should == [4, 1, 6, 3, 5, 2]
+ end
+
+ it "returns an Enumerator if no block given" do
+ enum = EnumerableSpecs::Numerous.new.reverse_each
+ enum.should.instance_of?(Enumerator)
+ enum.to_a.should == [4, 1, 6, 3, 5, 2]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ yielded = []
+ multi.reverse_each {|e| yielded << e }
+ yielded.should == [[6, 7, 8, 9], [3, 4, 5], [1, 2]]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :reverse_each
+end
diff --git a/spec/ruby/core/enumerable/select_spec.rb b/spec/ruby/core/enumerable/select_spec.rb
new file mode 100644
index 0000000000..7fc61926f9
--- /dev/null
+++ b/spec/ruby/core/enumerable/select_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/find_all'
+
+describe "Enumerable#select" do
+ it_behaves_like :enumerable_find_all, :select
+end
diff --git a/spec/ruby/core/enumerable/shared/collect.rb b/spec/ruby/core/enumerable/shared/collect.rb
new file mode 100644
index 0000000000..4696d32454
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/collect.rb
@@ -0,0 +1,107 @@
+require_relative 'enumerable_enumeratorized'
+
+describe :enumerable_collect, shared: true do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns a new array with the results of passing each element to block" do
+ entries = [0, 1, 3, 4, 5, 6]
+ numerous = EnumerableSpecs::Numerous.new(*entries)
+ numerous.send(@method) { |i| i % 2 }.should == [0, 1, 1, 0, 1, 0]
+ numerous.send(@method) { |i| i }.should == entries
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i]; nil }
+ ScratchPad.recorded.should == [[:a, 0], [:b, 1]]
+ end
+
+ it "gathers initial args as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method) {|e| e}.should == [1,3,6]
+ end
+
+ it "only yields increasing values for a Range" do
+ (1..0).send(@method) { |x| x }.should == []
+ (1..1).send(@method) { |x| x }.should == [1]
+ (1..2).send(@method) { |x| x }.should == [1, 2]
+ end
+
+ it "returns an enumerator when no block given" do
+ enum = EnumerableSpecs::Numerous.new.send(@method)
+ enum.should.instance_of?(Enumerator)
+ enum.each { |i| -i }.should == [-2, -5, -3, -6, -1, -4]
+ end
+
+ it "reports the same arity as the given block" do
+ entries = [0, 1, 3, 4, 5, 6]
+ numerous = EnumerableSpecs::Numerous.new(*entries)
+
+ def numerous.each(&block)
+ ScratchPad << block.arity
+ super
+ end
+
+ numerous.send(@method) { |a, b| a % 2 }.should == [0, 1, 1, 0, 1, 0]
+ ScratchPad.recorded.should == [2]
+ ScratchPad.clear
+ ScratchPad.record []
+ numerous.send(@method) { |i| i }.should == entries
+ ScratchPad.recorded.should == [1]
+ end
+
+ it "yields an Array of 2 elements for a Hash when block arity is 1" do
+ c = Class.new do
+ def register(a)
+ ScratchPad << a
+ end
+ end
+ m = c.new.method(:register)
+
+ ScratchPad.record []
+ { 1 => 'a', 2 => 'b' }.map(&m)
+ ScratchPad.recorded.should == [[1, 'a'], [2, 'b']]
+ end
+
+ it "yields 2 arguments for a Hash when block arity is 2" do
+ c = Class.new do
+ def register(a, b)
+ ScratchPad << [a, b]
+ end
+ end
+ m = c.new.method(:register)
+
+ ScratchPad.record []
+ { 1 => 'a', 2 => 'b' }.map(&m)
+ ScratchPad.recorded.should == [[1, 'a'], [2, 'b']]
+ end
+
+ it "raises an error for a Hash when an arity enforcing block of arity >2 is passed in" do
+ c = Class.new do
+ def register(a, b, c)
+ end
+ end
+ m = c.new.method(:register)
+
+ -> do
+ { 1 => 'a', 2 => 'b' }.map(&m)
+ end.should.raise(ArgumentError)
+ end
+
+ it "calls the each method on sub-classes" do
+ c = Class.new(Hash) do
+ def each
+ ScratchPad << 'in each'
+ super
+ end
+ end
+ h = c.new
+ h[1] = 'a'
+ ScratchPad.record []
+ h.send(@method) { |k,v| v }
+ ScratchPad.recorded.should == ['in each']
+ end
+
+ it_should_behave_like :enumerable_enumeratorized_with_origin_size
+end
diff --git a/spec/ruby/core/enumerable/shared/collect_concat.rb b/spec/ruby/core/enumerable/shared/collect_concat.rb
new file mode 100644
index 0000000000..1694e3fdce
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/collect_concat.rb
@@ -0,0 +1,54 @@
+require_relative 'enumerable_enumeratorized'
+
+describe :enumerable_collect_concat, shared: true do
+ it "yields elements to the block and flattens one level" do
+ numerous = EnumerableSpecs::Numerous.new(1, [2, 3], [4, [5, 6]], {foo: :bar})
+ numerous.send(@method) { |i| i }.should == [1, 2, 3, 4, [5, 6], {foo: :bar}]
+ end
+
+ it "appends non-Array elements that do not define #to_ary" do
+ obj = mock("to_ary undefined")
+
+ numerous = EnumerableSpecs::Numerous.new(1, obj, 2)
+ numerous.send(@method) { |i| i }.should == [1, obj, 2]
+ end
+
+ it "concatenates the result of calling #to_ary if it returns an Array" do
+ obj = mock("to_ary defined")
+ obj.should_receive(:to_ary).and_return([:a, :b])
+
+ numerous = EnumerableSpecs::Numerous.new(1, obj, 2)
+ numerous.send(@method) { |i| i }.should == [1, :a, :b, 2]
+ end
+
+ it "does not call #to_a" do
+ obj = mock("to_ary undefined")
+ obj.should_not_receive(:to_a)
+
+ numerous = EnumerableSpecs::Numerous.new(1, obj, 2)
+ numerous.send(@method) { |i| i }.should == [1, obj, 2]
+ end
+
+ it "appends an element that defines #to_ary that returns nil" do
+ obj = mock("to_ary defined")
+ obj.should_receive(:to_ary).and_return(nil)
+
+ numerous = EnumerableSpecs::Numerous.new(1, obj, 2)
+ numerous.send(@method) { |i| i }.should == [1, obj, 2]
+ end
+
+ it "raises a TypeError if an element defining #to_ary does not return an Array or nil" do
+ obj = mock("to_ary defined")
+ obj.should_receive(:to_ary).and_return("array")
+
+ -> { [1, obj, 3].send(@method) { |i| i } }.should.raise(TypeError)
+ end
+
+ it "returns an enumerator when no block given" do
+ enum = EnumerableSpecs::Numerous.new(1, 2).send(@method)
+ enum.should.instance_of?(Enumerator)
+ enum.each{ |i| [i] * i }.should == [1, 2, 2]
+ end
+
+ it_should_behave_like :enumerable_enumeratorized_with_origin_size
+end
diff --git a/spec/ruby/core/enumerable/shared/entries.rb b/spec/ruby/core/enumerable/shared/entries.rb
new file mode 100644
index 0000000000..e32eb23d2a
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/entries.rb
@@ -0,0 +1,16 @@
+describe :enumerable_entries, shared: true do
+ it "returns an array containing the elements" do
+ numerous = EnumerableSpecs::Numerous.new(1, nil, 'a', 2, false, true)
+ numerous.send(@method).should == [1, nil, "a", 2, false, true]
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.send(@method).should == [[:a, 0], [:b, 1]]
+ end
+
+ it "passes arguments to each" do
+ count = EnumerableSpecs::EachCounter.new(1, 2, 3)
+ count.send(@method, :hello, "world").should == [1, 2, 3]
+ count.arguments_passed.should == [:hello, "world"]
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/enumerable_enumeratorized.rb b/spec/ruby/core/enumerable/shared/enumerable_enumeratorized.rb
new file mode 100644
index 0000000000..e2bbe18eda
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/enumerable_enumeratorized.rb
@@ -0,0 +1,33 @@
+require_relative 'enumeratorized'
+
+describe :enumerable_enumeratorized_with_unknown_size, shared: true do
+ describe "Enumerable with size" do
+ before :all do
+ @object = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4)
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+end
+
+describe :enumerable_enumeratorized_with_origin_size, shared: true do
+ describe "Enumerable with size" do
+ before :all do
+ @object = EnumerableSpecs::NumerousWithSize.new(1, 2, 3, 4)
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+ end
+
+ describe "Enumerable with no size" do
+ before :all do
+ @object = EnumerableSpecs::Numerous.new(1, 2, 3, 4)
+ end
+ it_should_behave_like :enumeratorized_with_unknown_size
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/enumeratorized.rb b/spec/ruby/core/enumerable/shared/enumeratorized.rb
new file mode 100644
index 0000000000..05d27b5783
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/enumeratorized.rb
@@ -0,0 +1,42 @@
+describe :enumeratorized_with_unknown_size, shared: true do
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ it "size returns nil" do
+ @object.send(*@method).size.should == nil
+ end
+ end
+ end
+end
+
+describe :enumeratorized_with_origin_size, shared: true do
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ it "size returns the enumerable size" do
+ @object.send(*@method).size.should == @object.size
+ end
+ end
+ end
+end
+
+describe :enumeratorized_with_cycle_size, shared: true do
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should be the result of multiplying the enumerable size by the argument passed" do
+ @object.cycle(2).size.should == @object.size * 2
+ @object.cycle(7).size.should == @object.size * 7
+ @object.cycle(0).size.should == 0
+ @empty_object.cycle(2).size.should == 0
+ end
+
+ it "should be zero when the argument passed is 0 or less" do
+ @object.cycle(-1).size.should == 0
+ end
+
+ it "should be Float::INFINITY when no argument is passed" do
+ @object.cycle.size.should == Float::INFINITY
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/find.rb b/spec/ruby/core/enumerable/shared/find.rb
new file mode 100644
index 0000000000..cdff640415
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/find.rb
@@ -0,0 +1,77 @@
+require_relative 'enumerable_enumeratorized'
+
+describe :enumerable_find, shared: true do
+ # #detect and #find are aliases, so we only need one function
+ before :each do
+ ScratchPad.record []
+ @elements = [2, 4, 6, 8, 10]
+ @numerous = EnumerableSpecs::Numerous.new(*@elements)
+ @empty = []
+ end
+
+ it "passes each entry in enum to block while block when block is false" do
+ visited_elements = []
+ @numerous.send(@method) do |element|
+ visited_elements << element
+ false
+ end
+ visited_elements.should == @elements
+ end
+
+ it "returns nil when the block is false and there is no ifnone proc given" do
+ @numerous.send(@method) {|e| false }.should == nil
+ end
+
+ it "returns the first element for which the block is not false" do
+ @elements.each do |element|
+ @numerous.send(@method) {|e| e > element - 1 }.should == element
+ end
+ end
+
+ it "returns the value of the ifnone proc if the block is false" do
+ fail_proc = -> { "cheeseburgers" }
+ @numerous.send(@method, fail_proc) {|e| false }.should == "cheeseburgers"
+ end
+
+ it "doesn't call the ifnone proc if an element is found" do
+ fail_proc = -> { raise "This shouldn't have been called" }
+ @numerous.send(@method, fail_proc) {|e| e == @elements.first }.should == 2
+ end
+
+ it "calls the ifnone proc only once when the block is false" do
+ times = 0
+ fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" }
+ @numerous.send(@method, fail_proc) {|e| false }.should == "cheeseburgers"
+ end
+
+ it "calls the ifnone proc when there are no elements" do
+ fail_proc = -> { "yay" }
+ @empty.send(@method, fail_proc) {|e| true}.should == "yay"
+ end
+
+ it "ignores the ifnone argument when nil" do
+ @numerous.send(@method, nil) {|e| false }.should == nil
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i]; nil }
+ ScratchPad.recorded.should == [[:a, 0], [:b, 1]]
+ end
+
+ it "returns an enumerator when no block given" do
+ @numerous.send(@method).should.instance_of?(Enumerator)
+ end
+
+ it "passes the ifnone proc to the enumerator" do
+ times = 0
+ fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" }
+ @numerous.send(@method, fail_proc).each {|e| false }.should == "cheeseburgers"
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method) {|e| e == [1, 2] }.should == [1, 2]
+ end
+
+ it_should_behave_like :enumerable_enumeratorized_with_unknown_size
+end
diff --git a/spec/ruby/core/enumerable/shared/find_all.rb b/spec/ruby/core/enumerable/shared/find_all.rb
new file mode 100644
index 0000000000..27f01de6e0
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/find_all.rb
@@ -0,0 +1,31 @@
+require_relative 'enumerable_enumeratorized'
+
+describe :enumerable_find_all, shared: true do
+ before :each do
+ ScratchPad.record []
+ @elements = (1..10).to_a
+ @numerous = EnumerableSpecs::Numerous.new(*@elements)
+ end
+
+ it "returns all elements for which the block is not false" do
+ @numerous.send(@method) {|i| i % 3 == 0 }.should == [3, 6, 9]
+ @numerous.send(@method) {|i| true }.should == @elements
+ @numerous.send(@method) {|i| false }.should == []
+ end
+
+ it "returns an enumerator when no block given" do
+ @numerous.send(@method).should.instance_of?(Enumerator)
+ end
+
+ it "passes through the values yielded by #each_with_index" do
+ [:a, :b].each_with_index.send(@method) { |x, i| ScratchPad << [x, i] }
+ ScratchPad.recorded.should == [[:a, 0], [:b, 1]]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method) {|e| e == [3, 4, 5] }.should == [[3, 4, 5]]
+ end
+
+ it_should_behave_like :enumerable_enumeratorized_with_origin_size
+end
diff --git a/spec/ruby/core/enumerable/shared/include.rb b/spec/ruby/core/enumerable/shared/include.rb
new file mode 100644
index 0000000000..ea250f032b
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/include.rb
@@ -0,0 +1,34 @@
+describe :enumerable_include, shared: true do
+ it "returns true if any element == argument for numbers" do
+ class EnumerableSpecIncludeP; def ==(obj) obj == 5; end; end
+
+ elements = (0..5).to_a
+ EnumerableSpecs::Numerous.new(*elements).send(@method,5).should == true
+ EnumerableSpecs::Numerous.new(*elements).send(@method,10).should == false
+ EnumerableSpecs::Numerous.new(*elements).send(@method,EnumerableSpecIncludeP.new).should == true
+ end
+
+ it "returns true if any element == argument for other objects" do
+ class EnumerableSpecIncludeP11; def ==(obj); obj == '11'; end; end
+
+ elements = ('0'..'5').to_a + [EnumerableSpecIncludeP11.new]
+ EnumerableSpecs::Numerous.new(*elements).send(@method,'5').should == true
+ EnumerableSpecs::Numerous.new(*elements).send(@method,'10').should == false
+ EnumerableSpecs::Numerous.new(*elements).send(@method,EnumerableSpecIncludeP11.new).should == true
+ EnumerableSpecs::Numerous.new(*elements).send(@method,'11').should == true
+ end
+
+
+ it "returns true if any member of enum equals obj when == compare different classes (legacy rubycon)" do
+ # equality is tested with ==
+ EnumerableSpecs::Numerous.new(2,4,6,8,10).send(@method, 2.0).should == true
+ EnumerableSpecs::Numerous.new(2,4,[6,8],10).send(@method, [6, 8]).should == true
+ EnumerableSpecs::Numerous.new(2,4,[6,8],10).send(@method, [6.0, 8.0]).should == true
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method, [1,2]).should == true
+ end
+
+end
diff --git a/spec/ruby/core/enumerable/shared/inject.rb b/spec/ruby/core/enumerable/shared/inject.rb
new file mode 100644
index 0000000000..7da4f0ca99
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/inject.rb
@@ -0,0 +1,142 @@
+require_relative '../../array/shared/iterable_and_tolerating_size_increasing'
+
+describe :enumerable_inject, shared: true do
+ it "with argument takes a block with an accumulator (with argument as initial value) and the current element. Value of block becomes new accumulator" do
+ a = []
+ EnumerableSpecs::Numerous.new.send(@method, 0) { |memo, i| a << [memo, i]; i }
+ a.should == [[0, 2], [2, 5], [5, 3], [3, 6], [6, 1], [1, 4]]
+ EnumerableSpecs::EachDefiner.new(true, true, true).send(@method, nil) {|result, i| i && result}.should == nil
+ end
+
+ it "produces an array of the accumulator and the argument when given a block with a *arg" do
+ a = []
+ [1,2].send(@method, 0) {|*args| a << args; args[0] + args[1]}
+ a.should == [[0, 1], [1, 2]]
+ end
+
+ 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(TypeError, /is not a symbol nor a string/)
+ -> { [1, 2, 3].send(@method, 10, Object.new) }.should.raise(TypeError, /is not a symbol nor a string/)
+ end
+
+ it "ignores the block if two arguments" do
+ -> {
+ EnumerableSpecs::Numerous.new(1, 2, 3).send(@method, 10, :-) { raise "we never get here"}.should == 4
+ }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true)
+
+ -> {
+ [1, 2, 3].send(@method, 10, :-) { raise "we never get here"}.should == 4
+ }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true)
+ end
+
+ it "does not warn when given a Symbol with $VERBOSE true" do
+ -> {
+ [1, 2].send(@method, 0, :+)
+ [1, 2].send(@method, :+)
+ EnumerableSpecs::Numerous.new(1, 2).send(@method, 0, :+)
+ EnumerableSpecs::Numerous.new(1, 2).send(@method, :+)
+ }.should_not complain(verbose: true)
+ end
+
+ 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(TypeError, /is not a symbol nor a string/)
+ -> { [10, 1, 2, 3].send(@method, Object.new) }.should.raise(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
+ a = []
+ EnumerableSpecs::Numerous.new.send(@method) { |memo, i| a << [memo, i]; i }
+ a.should == [[2, 5], [5, 3], [3, 6], [6, 1], [1, 4]]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method, []) {|acc, e| acc << e }.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+
+ it "with inject arguments(legacy rubycon)" do
+ # with inject argument
+ EnumerableSpecs::EachDefiner.new().send(@method, 1) {|acc,x| 999 }.should == 1
+ EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| 999 }.should == 999
+ EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| acc }.should == 1
+ EnumerableSpecs::EachDefiner.new(2).send(@method, 1) {|acc,x| x }.should == 2
+
+ EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method, 100) {|acc,x| acc + x }.should == 110
+ EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method, 100) {|acc,x| acc * x }.should == 2400
+
+ EnumerableSpecs::EachDefiner.new('a','b','c').send(@method, "z") {|result, i| i+result}.should == "cbaz"
+ end
+
+ 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| acc }.should == 2
+ EnumerableSpecs::EachDefiner.new(2).send(@method) {|acc,x| x }.should == 2
+
+ EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method) {|acc,x| acc + x }.should == 10
+ EnumerableSpecs::EachDefiner.new(1,2,3,4).send(@method) {|acc,x| acc * x }.should == 24
+
+ 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
+ EnumerableSpecs::EachDefiner.new().send(@method) {|acc,x| 999 }.should == nil
+ end
+
+ it "tolerates increasing a collection size during iterating Array" do
+ array = [:a, :b, :c]
+ ScratchPad.record []
+ i = 0
+
+ array.send(@method, nil) do |_, e|
+ ScratchPad << e
+ array << i if i < 100
+ i += 1
+ end
+
+ actual = ScratchPad.recorded
+ expected = [:a, :b, :c] + (0..99).to_a
+ actual.sort_by(&:to_s).should == expected.sort_by(&:to_s)
+ end
+
+ it "raises an ArgumentError when no parameters or block is given" do
+ -> { [1,2].send(@method) }.should.raise(ArgumentError)
+ -> { {one: 1, two: 2}.send(@method) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/take.rb b/spec/ruby/core/enumerable/shared/take.rb
new file mode 100644
index 0000000000..a6da06325f
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/take.rb
@@ -0,0 +1,63 @@
+describe :enumerable_take, shared: true do
+ before :each do
+ @values = [4,3,2,1,0,-1]
+ @enum = EnumerableSpecs::Numerous.new(*@values)
+ end
+
+ it "returns the first count elements if given a count" do
+ @enum.send(@method, 2).should == [4, 3]
+ @enum.send(@method, 4).should == [4, 3, 2, 1] # See redmine #1686 !
+ end
+
+ it "returns an empty array when passed count on an empty array" do
+ empty = EnumerableSpecs::Empty.new
+ empty.send(@method, 0).should == []
+ empty.send(@method, 1).should == []
+ empty.send(@method, 2).should == []
+ end
+
+ it "returns an empty array when passed count == 0" do
+ @enum.send(@method, 0).should == []
+ end
+
+ it "returns an array containing the first element when passed count == 1" do
+ @enum.send(@method, 1).should == [4]
+ end
+
+ it "raises an ArgumentError when count is negative" do
+ -> { @enum.send(@method, -1) }.should.raise(ArgumentError)
+ end
+
+ it "returns the entire array when count > length" do
+ @enum.send(@method, 100).should == @values
+ @enum.send(@method, 8).should == @values # See redmine #1686 !
+ 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(3).at_most(:twice) # called twice, no apparent reason. See redmine #1554
+ @enum.send(@method, obj).should == [4, 3, 2]
+ end
+
+ it "raises a TypeError if the passed argument is not numeric" do
+ -> { @enum.send(@method, nil) }.should.raise(TypeError)
+ -> { @enum.send(@method, "a") }.should.raise(TypeError)
+
+ obj = mock("nonnumeric")
+ -> { @enum.send(@method, obj) }.should.raise(TypeError)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.send(@method, 1).should == [[1, 2]]
+ end
+
+ it "consumes only what is needed" do
+ thrower = EnumerableSpecs::ThrowingEach.new
+ thrower.send(@method, 0).should == []
+ counter = EnumerableSpecs::EachCounter.new(1,2,3,4)
+ counter.send(@method, 2).should == [1,2]
+ counter.times_called.should == 1
+ counter.times_yielded.should == 2
+ end
+end
diff --git a/spec/ruby/core/enumerable/shared/value_packing.rb b/spec/ruby/core/enumerable/shared/value_packing.rb
new file mode 100644
index 0000000000..ff77f45cdf
--- /dev/null
+++ b/spec/ruby/core/enumerable/shared/value_packing.rb
@@ -0,0 +1,26 @@
+# This is the behavior of rb_enum_values_pack() in CRuby
+describe :enumerable_value_packing, shared: true do
+ # @take must be set to a Proc that returns the take-result whose #each
+ # yields packed values (e.g. -> e { e.take(1) } or -> e { e.lazy.take(1) }).
+
+ it "yields a single nil for a zero-argument source yield" do
+ e = Enumerator.new { |y| y.yield }
+ args = nil
+ @take.call(e).each { |*a| args = a }
+ args.should == [nil]
+ end
+
+ it "yields the value for a single-argument source yield" do
+ e = Enumerator.new { |y| y.yield :v }
+ args = nil
+ @take.call(e).each { |*a| args = a }
+ args.should == [:v]
+ end
+
+ it "yields a packed Array for a multi-argument source yield" do
+ e = Enumerator.new { |y| y.yield 1, 2 }
+ args = nil
+ @take.call(e).each { |*a| args = a }
+ args.should == [[1, 2]]
+ end
+end
diff --git a/spec/ruby/core/enumerable/slice_after_spec.rb b/spec/ruby/core/enumerable/slice_after_spec.rb
new file mode 100644
index 0000000000..1ef37195e5
--- /dev/null
+++ b/spec/ruby/core/enumerable/slice_after_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#slice_after" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(7, 6, 5, 4, 3, 2, 1)
+ end
+
+ describe "when given an argument and no block" do
+ it "calls === on the argument to determine when to yield" do
+ arg = mock("filter")
+ arg.should_receive(:===).and_return(false, true, false, false, false, true, false)
+ e = @enum.slice_after(arg)
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == [[7, 6], [5, 4, 3, 2], [1]]
+ end
+
+ it "doesn't yield an empty array if the filter matches the first entry or the last entry" do
+ arg = mock("filter")
+ arg.should_receive(:===).and_return(true).exactly(7)
+ e = @enum.slice_after(arg)
+ e.to_a.should == [[7], [6], [5], [4], [3], [2], [1]]
+ end
+
+ it "uses standard boolean as a test" do
+ arg = mock("filter")
+ arg.should_receive(:===).and_return(false, :foo, nil, false, false, 42, false)
+ e = @enum.slice_after(arg)
+ e.to_a.should == [[7, 6], [5, 4, 3, 2], [1]]
+ end
+ end
+
+ describe "when given a block" do
+ describe "and no argument" do
+ it "calls the block to determine when to yield" do
+ e = @enum.slice_after{ |i| i == 6 || i == 2 }
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == [[7, 6], [5, 4, 3, 2], [1]]
+ end
+ end
+
+ describe "and an argument" do
+ it "raises an ArgumentError" do
+ -> { @enum.slice_after(42) { |i| i == 6 } }.should.raise(ArgumentError)
+ end
+ end
+ end
+
+ it "raises an ArgumentError when given an incorrect number of arguments" do
+ -> { @enum.slice_after("one", "two") }.should.raise(ArgumentError)
+ -> { @enum.slice_after }.should.raise(ArgumentError)
+ end
+end
+
+describe "when an iterator method yields more than one value" do
+ it "processes all yielded values" do
+ enum = EnumerableSpecs::YieldsMulti.new
+ result = enum.slice_after { |i| i == [3, 4, 5] }.to_a
+ result.should == [[[1, 2], [3, 4, 5]], [[6, 7, 8, 9]]]
+ end
+end
diff --git a/spec/ruby/core/enumerable/slice_before_spec.rb b/spec/ruby/core/enumerable/slice_before_spec.rb
new file mode 100644
index 0000000000..7eb4410a25
--- /dev/null
+++ b/spec/ruby/core/enumerable/slice_before_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#slice_before" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(7,6,5,4,3,2,1)
+ end
+
+ describe "when given an argument and no block" do
+ it "calls === on the argument to determine when to yield" do
+ arg = mock "filter"
+ arg.should_receive(:===).and_return(false, true, false, false, false, true, false)
+ e = @enum.slice_before(arg)
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == [[7], [6, 5, 4, 3], [2, 1]]
+ end
+
+ it "doesn't yield an empty array if the filter matches the first entry or the last entry" do
+ arg = mock "filter"
+ arg.should_receive(:===).and_return(true).exactly(7)
+ e = @enum.slice_before(arg)
+ e.to_a.should == [[7], [6], [5], [4], [3], [2], [1]]
+ end
+
+ it "uses standard boolean as a test" do
+ arg = mock "filter"
+ arg.should_receive(:===).and_return(false, :foo, nil, false, false, 42, false)
+ e = @enum.slice_before(arg)
+ e.to_a.should == [[7], [6, 5, 4, 3], [2, 1]]
+ end
+ end
+
+ describe "when given a block" do
+ describe "and no argument" do
+ it "calls the block to determine when to yield" do
+ e = @enum.slice_before{|i| i == 6 || i == 2}
+ e.should.instance_of?(Enumerator)
+ e.to_a.should == [[7], [6, 5, 4, 3], [2, 1]]
+ end
+ end
+
+ it "does not accept arguments" do
+ -> {
+ @enum.slice_before(1) {}
+ }.should.raise(ArgumentError)
+ end
+ end
+
+ it "raises an ArgumentError when given an incorrect number of arguments" do
+ -> { @enum.slice_before("one", "two") }.should.raise(ArgumentError)
+ -> { @enum.slice_before }.should.raise(ArgumentError)
+ end
+
+ describe "when an iterator method yields more than one value" do
+ it "processes all yielded values" do
+ enum = EnumerableSpecs::YieldsMulti.new
+ result = enum.slice_before { |i| i == [3, 4, 5] }.to_a
+ result.should == [[[1, 2]], [[3, 4, 5], [6, 7, 8, 9]]]
+ end
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_unknown_size, [:slice_before, 3]
+end
diff --git a/spec/ruby/core/enumerable/slice_when_spec.rb b/spec/ruby/core/enumerable/slice_when_spec.rb
new file mode 100644
index 0000000000..fe1ecd31e2
--- /dev/null
+++ b/spec/ruby/core/enumerable/slice_when_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#slice_when" do
+ before :each do
+ ary = [10, 9, 7, 6, 4, 3, 2, 1]
+ @enum = EnumerableSpecs::Numerous.new(*ary)
+ @result = @enum.slice_when { |i, j| i - 1 != j }
+ @enum_length = ary.length
+ end
+
+ context "when given a block" do
+ it "returns an enumerator" do
+ @result.should.instance_of?(Enumerator)
+ end
+
+ it "splits chunks between adjacent elements i and j where the block returns true" do
+ @result.to_a.should == [[10, 9], [7, 6], [4, 3, 2, 1]]
+ end
+
+ it "calls the block for length of the receiver enumerable minus one times" do
+ times_called = 0
+ @enum.slice_when do |i, j|
+ times_called += 1
+ i - 1 != j
+ end.to_a
+ times_called.should == (@enum_length - 1)
+ end
+
+ it "doesn't yield an empty array if the block matches the first or the last time" do
+ @enum.slice_when { true }.to_a.should == [[10], [9], [7], [6], [4], [3], [2], [1]]
+ end
+
+ it "doesn't yield an empty array on a small enumerable" do
+ EnumerableSpecs::Empty.new.slice_when { raise }.to_a.should == []
+ EnumerableSpecs::Numerous.new(42).slice_when { raise }.to_a.should == [[42]]
+ end
+ end
+
+ context "when not given a block" do
+ it "raises an ArgumentError" do
+ -> { @enum.slice_when }.should.raise(ArgumentError)
+ end
+ end
+
+ describe "when an iterator method yields more than one value" do
+ it "processes all yielded values" do
+ def foo
+ yield 1, 2
+ end
+ to_enum(:foo).slice_when { true }.to_a.should == [[[1, 2]]]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/sort_by_spec.rb b/spec/ruby/core/enumerable/sort_by_spec.rb
new file mode 100644
index 0000000000..62cf38ce3e
--- /dev/null
+++ b/spec/ruby/core/enumerable/sort_by_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#sort_by" do
+ it "returns an array of elements ordered by the result of block" do
+ a = EnumerableSpecs::Numerous.new("once", "upon", "a", "time")
+ a.sort_by { |i| i[0] }.should == ["a", "once", "time", "upon"]
+ end
+
+ it "sorts the object by the given attribute" do
+ a = EnumerableSpecs::SortByDummy.new("fooo")
+ b = EnumerableSpecs::SortByDummy.new("bar")
+
+ ar = [a, b].sort_by { |d| d.s }
+ ar.should == [b, a]
+ end
+
+ it "returns an Enumerator when a block is not supplied" do
+ a = EnumerableSpecs::Numerous.new("a","b")
+ a.sort_by.should.instance_of?(Enumerator)
+ a.to_a.should == ["a", "b"]
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.sort_by {|e| e.size}.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+
+ it "returns an array of elements when a block is supplied and #map returns an enumerable" do
+ b = EnumerableSpecs::MapReturnsEnumerable.new
+ b.sort_by{ |x| -x }.should == [3, 2, 1]
+ end
+
+ it "calls #each to iterate over the elements to be sorted" do
+ b = EnumerableSpecs::Numerous.new( 1, 2, 3 )
+ b.should_receive(:each).once.and_yield(1).and_yield(2).and_yield(3)
+ b.should_not_receive :map
+ b.sort_by { |x| -x }.should == [3, 2, 1]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_origin_size, :sort_by
+end
diff --git a/spec/ruby/core/enumerable/sort_spec.rb b/spec/ruby/core/enumerable/sort_spec.rb
new file mode 100644
index 0000000000..427b1cd8f1
--- /dev/null
+++ b/spec/ruby/core/enumerable/sort_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#sort" do
+ it "sorts by the natural order as defined by <=>" do
+ EnumerableSpecs::Numerous.new.sort.should == [1, 2, 3, 4, 5, 6]
+ sorted = EnumerableSpecs::ComparesByVowelCount.wrap("a" * 1, "a" * 2, "a"*3, "a"*4, "a"*5)
+ EnumerableSpecs::Numerous.new(sorted[2],sorted[0],sorted[1],sorted[3],sorted[4]).sort.should == sorted
+ end
+
+ it "yields elements to the provided block" do
+ EnumerableSpecs::Numerous.new.sort { |a, b| b <=> a }.should == [6, 5, 4, 3, 2, 1]
+ EnumerableSpecs::Numerous.new(2,0,1,3,4).sort { |n, m| -(n <=> m) }.should == [4,3,2,1,0]
+ end
+
+ it "raises a NoMethodError if elements do not define <=>" do
+ -> do
+ EnumerableSpecs::Numerous.new(BasicObject.new, BasicObject.new, BasicObject.new).sort
+ end.should.raise(NoMethodError)
+ end
+
+ it "sorts enumerables that contain nils" do
+ arr = EnumerableSpecs::Numerous.new(nil, true, nil, false, nil, true, nil, false, nil)
+ arr.sort { |a, b|
+ x = a ? -1 : a.nil? ? 0 : 1
+ y = b ? -1 : b.nil? ? 0 : 1
+ x <=> y
+ }.should == [true, true, nil, nil, nil, nil, nil, false, false]
+ end
+
+ it "compare values returned by block with 0" do
+ EnumerableSpecs::Numerous.new.sort { |n, m| -(n+m) * (n <=> m) }.should == [6, 5, 4, 3, 2, 1]
+ EnumerableSpecs::Numerous.new.sort { |n, m|
+ EnumerableSpecs::ComparableWithInteger.new(-(n+m) * (n <=> m))
+ }.should == [6, 5, 4, 3, 2, 1]
+ -> {
+ EnumerableSpecs::Numerous.new.sort { |n, m| (n <=> m).to_s }
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises an error if objects can't be compared" do
+ a=EnumerableSpecs::Numerous.new(EnumerableSpecs::Uncomparable.new, EnumerableSpecs::Uncomparable.new)
+ -> {a.sort}.should.raise(ArgumentError)
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.sort {|a, b| a.first <=> b.first}.should == [[1, 2], [3, 4, 5], [6, 7, 8, 9]]
+ end
+
+ it "doesn't raise an error if #to_a returns a frozen Array" do
+ EnumerableSpecs::Freezy.new.sort.should == [1,2]
+ end
+end
diff --git a/spec/ruby/core/enumerable/sum_spec.rb b/spec/ruby/core/enumerable/sum_spec.rb
new file mode 100644
index 0000000000..2eb74db6ac
--- /dev/null
+++ b/spec/ruby/core/enumerable/sum_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Enumerable#sum' do
+ before :each do
+ @enum = Object.new.to_enum
+ class << @enum
+ def each
+ yield 0
+ yield(-1)
+ yield 2
+ yield 2/3r
+ end
+ end
+ end
+
+ it 'returns amount of the elements with taking an argument as the initial value' do
+ @enum.sum(10).should == 35/3r
+ end
+
+ it 'gives 0 as a default argument' do
+ @enum.sum.should == 5/3r
+ end
+
+ context 'with a block' do
+ it 'transforms the elements' do
+ @enum.sum { |element| element * 2 }.should == 10/3r
+ end
+
+ it 'does not destructure array elements' do
+ class << @enum
+ def each
+ yield [1,2]
+ yield [3]
+ end
+ end
+
+ @enum.sum(&:last).should == 5
+ end
+ end
+
+ # https://bugs.ruby-lang.org/issues/12217
+ # https://github.com/ruby/ruby/blob/master/doc/ChangeLog/ChangeLog-2.4.0#L6208-L6214
+ it "uses Kahan's compensated summation algorithm for precise sum of float numbers" do
+ floats = [2.7800000000000002, 5.0, 2.5, 4.44, 3.89, 3.89, 4.44, 7.78, 5.0, 2.7800000000000002, 5.0, 2.5].to_enum
+ naive_sum = floats.reduce { |sum, e| sum + e }
+ naive_sum.should == 50.00000000000001
+ floats.sum.should == 50.0
+ end
+end
diff --git a/spec/ruby/core/enumerable/take_spec.rb b/spec/ruby/core/enumerable/take_spec.rb
new file mode 100644
index 0000000000..ca439b750d
--- /dev/null
+++ b/spec/ruby/core/enumerable/take_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/take'
+require_relative 'shared/value_packing'
+
+describe "Enumerable#take" do
+ it "requires an argument" do
+ ->{ EnumerableSpecs::Numerous.new.take}.should.raise(ArgumentError)
+ end
+
+ describe "when passed an argument" do
+ it_behaves_like :enumerable_take, :take
+ end
+
+ describe "value packing of source yields" do
+ before :each do
+ @take = -> e { e.take(1) }
+ end
+ it_behaves_like :enumerable_value_packing, nil
+ end
+end
diff --git a/spec/ruby/core/enumerable/take_while_spec.rb b/spec/ruby/core/enumerable/take_while_spec.rb
new file mode 100644
index 0000000000..918bfc897d
--- /dev/null
+++ b/spec/ruby/core/enumerable/take_while_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/enumerable_enumeratorized'
+
+describe "Enumerable#take_while" do
+ before :each do
+ @enum = EnumerableSpecs::Numerous.new(3, 2, 1, :go)
+ end
+
+ it "returns an Enumerator if no block given" do
+ @enum.take_while.should.instance_of?(Enumerator)
+ end
+
+ it "returns no/all elements for {true/false} block" do
+ @enum.take_while{true}.should == @enum.to_a
+ @enum.take_while{false}.should == []
+ end
+
+ it "accepts returns other than true/false" do
+ @enum.take_while{1}.should == @enum.to_a
+ @enum.take_while{nil}.should == []
+ end
+
+ it "passes elements to the block until the first false" do
+ a = []
+ @enum.take_while{|obj| (a << obj).size < 3}.should == [3, 2]
+ a.should == [3, 2, 1]
+ end
+
+ it "will only go through what's needed" do
+ enum = EnumerableSpecs::EachCounter.new(4, 3, 2, 1, :stop)
+ enum.take_while { |x|
+ break 42 if x == 3
+ true
+ }.should == 42
+ enum.times_yielded.should == 2
+ end
+
+ it "doesn't return self when it could" do
+ a = [1,2,3]
+ a.take_while{true}.should_not.equal?(a)
+ end
+
+ it "calls the block with initial args when yielded with multiple arguments" do
+ yields = []
+ EnumerableSpecs::YieldsMixed.new.take_while{ |v| yields << v }
+ yields.should == [1, [2], 3, 5, [8, 9], nil, []]
+ end
+
+ it_behaves_like :enumerable_enumeratorized_with_unknown_size, :take_while
+end
diff --git a/spec/ruby/core/enumerable/tally_spec.rb b/spec/ruby/core/enumerable/tally_spec.rb
new file mode 100644
index 0000000000..deef741407
--- /dev/null
+++ b/spec/ruby/core/enumerable/tally_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#tally" 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.should == { 'foo' => 2, 'bar' => 1, 'baz' => 1}
+ end
+
+ it "returns a hash without default" do
+ hash = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz').tally
+ hash.default_proc.should == nil
+ hash.default.should == nil
+ end
+
+ it "returns an empty hash for empty enumerables" do
+ EnumerableSpecs::Empty.new.tally.should == {}
+ end
+
+ it "counts values as gathered array when yielded with multiple arguments" do
+ EnumerableSpecs::YieldsMixed2.new.tally.should == EnumerableSpecs::YieldsMixed2.gathered_yields.group_by(&:itself).transform_values(&:size)
+ end
+
+ it "does not call given block" do
+ enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz')
+ enum.tally { |v| ScratchPad << v }
+ ScratchPad.recorded.should == []
+ end
+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(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(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(TypeError)
+ end
+end
diff --git a/spec/ruby/core/enumerable/to_a_spec.rb b/spec/ruby/core/enumerable/to_a_spec.rb
new file mode 100644
index 0000000000..723f922574
--- /dev/null
+++ b/spec/ruby/core/enumerable/to_a_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/entries'
+
+describe "Enumerable#to_a" do
+ 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
new file mode 100644
index 0000000000..38847eccfb
--- /dev/null
+++ b/spec/ruby/core/enumerable/to_h_spec.rb
@@ -0,0 +1,96 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#to_h" do
+ it "converts empty enumerable to empty hash" do
+ enum = EnumerableSpecs::EachDefiner.new
+ enum.to_h.should == {}
+ end
+
+ it "converts yielded [key, value] pairs to a hash" do
+ enum = EnumerableSpecs::EachDefiner.new([:a, 1], [:b, 2])
+ enum.to_h.should == { a: 1, b: 2 }
+ end
+
+ it "uses the last value of a duplicated key" do
+ enum = EnumerableSpecs::EachDefiner.new([:a, 1], [:b, 2], [:a, 3])
+ enum.to_h.should == { a: 3, b: 2 }
+ end
+
+ it "calls #to_ary on contents" do
+ pair = mock('to_ary')
+ pair.should_receive(:to_ary).and_return([:b, 2])
+ enum = EnumerableSpecs::EachDefiner.new([:a, 1], pair)
+ enum.to_h.should == { a: 1, b: 2 }
+ end
+
+ it "forwards arguments to #each" do
+ enum = Object.new
+ def enum.each(*args)
+ yield(*args)
+ yield([:b, 2])
+ end
+ enum.extend Enumerable
+ enum.to_h(:a, 1).should == { a: 1, b: 2 }
+ end
+
+ it "raises TypeError if an element is not an array" do
+ enum = EnumerableSpecs::EachDefiner.new(:x)
+ -> { enum.to_h }.should.raise(TypeError)
+ end
+
+ it "raises ArgumentError if an element is not a [key, value] pair" do
+ enum = EnumerableSpecs::EachDefiner.new([:x])
+ -> { enum.to_h }.should.raise(ArgumentError)
+ end
+
+ context "with block" do
+ before do
+ @enum = EnumerableSpecs::EachDefiner.new(:a, :b)
+ end
+
+ it "converts [key, value] pairs returned by the block to a hash" 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] }
+ end.should.raise(ArgumentError, /element has wrong array length/)
+
+ -> do
+ @enum.to_h { |k| [k] }
+ end.should.raise(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ @enum.to_h { |k| "not-array" }
+ end.should.raise(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'])
+
+ @enum.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'])
+
+ -> do
+ @enum.to_h { |k| x }
+ end.should.raise(TypeError, /wrong element type MockObject/)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/to_set_spec.rb b/spec/ruby/core/enumerable/to_set_spec.rb
new file mode 100644
index 0000000000..7b04c72bce
--- /dev/null
+++ b/spec/ruby/core/enumerable/to_set_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#to_set" do
+ it "returns a new Set created from self" do
+ [1, 2, 3].to_set.should == Set[1, 2, 3]
+ {a: 1, b: 2}.to_set.should == Set[[:b, 2], [:a, 1]]
+ end
+
+ it "passes down passed blocks" do
+ [1, 2, 3].to_set { |x| x * x }.should == Set[1, 4, 9]
+ end
+
+ ruby_version_is "4.0"..."4.1" do
+ it "instantiates an object of provided as the first argument set class" do
+ set = nil
+ proc{set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass)}.should complain(/Enumerable#to_set/)
+ set.should.is_a?(EnumerableSpecs::SetSubclass)
+ set.to_a.sort.should == [1, 2, 3]
+ end
+ end
+
+ ruby_version_is ""..."4.0" do
+ it "instantiates an object of provided as the first argument set class" do
+ set = [1, 2, 3].to_set(EnumerableSpecs::SetSubclass)
+ set.should.is_a?(EnumerableSpecs::SetSubclass)
+ set.to_a.sort.should == [1, 2, 3]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/uniq_spec.rb b/spec/ruby/core/enumerable/uniq_spec.rb
new file mode 100644
index 0000000000..a1ed44796f
--- /dev/null
+++ b/spec/ruby/core/enumerable/uniq_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Enumerable#uniq' do
+ it 'returns an array that contains only unique elements' do
+ [0, 1, 2, 3].to_enum.uniq { |n| n.even? }.should == [0, 1]
+ end
+
+ it "uses eql? semantics" do
+ [1.0, 1].to_enum.uniq.should == [1.0, 1]
+ end
+
+ it "compares elements first with hash" do
+ x = mock('0')
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y = mock('0')
+ y.should_receive(:hash).at_least(1).and_return(0)
+
+ [x, y].to_enum.uniq.should == [x, y]
+ end
+
+ it "does not compare elements with different hash codes via eql?" do
+ x = mock('0')
+ x.should_not_receive(:eql?)
+ y = mock('1')
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).at_least(1).and_return(0)
+ y.should_receive(:hash).at_least(1).and_return(1)
+
+ [x, y].to_enum.uniq.should == [x, y]
+ end
+
+ it "compares elements with matching hash codes with #eql?" do
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ false
+ end
+
+ obj
+ end
+
+ a.uniq.should == a
+
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ true
+ end
+
+ obj
+ end
+
+ a.to_enum.uniq.size.should == 1
+ end
+
+ context 'when yielded with multiple arguments' do
+ before :each do
+ @enum = Object.new.to_enum
+ class << @enum
+ def each
+ yield 0, 'foo'
+ yield 1, 'FOO'
+ yield 2, 'bar'
+ end
+ end
+ end
+
+ it 'returns all yield arguments as an array' do
+ @enum.uniq { |_, label| label.downcase }.should == [[0, 'foo'], [2, 'bar']]
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerable/zip_spec.rb b/spec/ruby/core/enumerable/zip_spec.rb
new file mode 100644
index 0000000000..c5f9a3e4d4
--- /dev/null
+++ b/spec/ruby/core/enumerable/zip_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerable#zip" do
+
+ it "combines each element of the receiver with the element of the same index in arrays given as arguments" do
+ EnumerableSpecs::Numerous.new(1,2,3).zip([4,5,6],[7,8,9]).should == [[1,4,7],[2,5,8],[3,6,9]]
+ EnumerableSpecs::Numerous.new(1,2,3).zip.should == [[1],[2],[3]]
+ end
+
+ it "passes each element of the result array to a block and return nil if a block is given" do
+ expected = [[1,4,7],[2,5,8],[3,6,9]]
+ EnumerableSpecs::Numerous.new(1,2,3).zip([4,5,6],[7,8,9]) do |result_component|
+ result_component.should == expected.shift
+ end.should == nil
+ expected.size.should == 0
+ end
+
+ it "fills resulting array with nils if an argument array is too short" do
+ EnumerableSpecs::Numerous.new(1,2,3).zip([4,5,6], [7,8]).should == [[1,4,7],[2,5,8],[3,6,nil]]
+ end
+
+ it "converts arguments to arrays using #to_ary" do
+ convertible = EnumerableSpecs::ArrayConvertible.new(4,5,6)
+ EnumerableSpecs::Numerous.new(1,2,3).zip(convertible).should == [[1,4],[2,5],[3,6]]
+ convertible.called.should == :to_ary
+ end
+
+ it "converts arguments to enums using #to_enum" do
+ convertible = EnumerableSpecs::EnumConvertible.new(4..6)
+ EnumerableSpecs::Numerous.new(1,2,3).zip(convertible).should == [[1,4],[2,5],[3,6]]
+ convertible.called.should == :to_enum
+ convertible.sym.should == :each
+ end
+
+ it "gathers whole arrays as elements when each yields multiple" do
+ multi = EnumerableSpecs::YieldsMulti.new
+ multi.zip(multi).should == [[[1, 2], [1, 2]], [[3, 4, 5], [3, 4, 5]], [[6, 7, 8, 9], [6, 7, 8, 9]]]
+ end
+
+ it "raises TypeError when some argument isn't Array and doesn't respond to #to_ary and #to_enum" do
+ -> { EnumerableSpecs::Numerous.new(1,2,3).zip(Object.new) }.should.raise(TypeError, "wrong argument type Object (must respond to :each)")
+ -> { EnumerableSpecs::Numerous.new(1,2,3).zip(1) }.should.raise(TypeError, "wrong argument type Integer (must respond to :each)")
+ -> { EnumerableSpecs::Numerous.new(1,2,3).zip(true) }.should.raise(TypeError, "wrong argument type TrueClass (must respond to :each)")
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb
new file mode 100644
index 0000000000..bd243fa0b5
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/begin_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#begin" do
+ it "returns the begin of the sequence" do
+ 1.step(10).begin.should == 1
+ (1..10).step.begin.should == 1
+ (1...10).step.begin.should == 1
+ end
+
+ context "with beginless" do
+ it "returns nil as begin of the sequence" do
+ (..10).step(1).begin.should == nil
+ (...10).step(1).begin.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb
new file mode 100644
index 0000000000..0a83019d49
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/each_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#each" do
+ before :each do
+ ScratchPad.record []
+ @seq = 1.step(10, 4)
+ end
+
+ it "calls given block on each item of the sequence" do
+ @seq.each { |item| ScratchPad << item }
+ ScratchPad.recorded.should == [1, 5, 9]
+ end
+
+ it "returns self" do
+ @seq.each { |item| }.should.equal?(@seq)
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb
new file mode 100644
index 0000000000..05429cac3e
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/end_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#end" do
+ it "returns the end of the sequence" do
+ 1.step(10).end.should == 10
+ (1..10).step.end.should == 10
+ (1...10).step(17).end.should == 10
+ end
+
+ context "with endless" do
+ it "returns nil as end of the sequence" do
+ (1..).step(1).end.should == nil
+ (1...).step(1).end.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb
new file mode 100644
index 0000000000..77eed02d8b
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/eq_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#==" do
+ it "returns true if begin, end, step and exclude_end? are equal" do
+ 1.step(10).should == 1.step(10)
+ 1.step(10, 5).should == 1.step(10, 5)
+
+ (1..10).step.should == (1..10).step
+ (1...10).step(8).should == (1...10).step(8)
+
+ # both have exclude_end? == false
+ (1..10).step(100).should == 1.step(10, 100)
+
+ ((1..10).step == (1..11).step).should == false
+ ((1..10).step == (1...10).step).should == false
+ ((1..10).step == (1..10).step(2)).should == false
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb
new file mode 100644
index 0000000000..021fe7d90f
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/exclude_end_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#exclude_end?" do
+ context "when created using Numeric#step" do
+ it "always returns false" do
+ 1.step(10).should_not.exclude_end?
+ 10.step(1).should_not.exclude_end?
+ end
+ end
+
+ context "when created using Range#step" do
+ it "mirrors range.exclude_end?" do
+ (1...10).step.should.exclude_end?
+ (1..10).step.should_not.exclude_end?
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb
new file mode 100644
index 0000000000..ccd02be020
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/first_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#first" do
+ it "returns the first element of the sequence" do
+ 1.step(10).first.should == 1
+ (1..10).step.first.should == 1
+ (1...10).step.first.should == 1
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb
new file mode 100644
index 0000000000..a18c554fb3
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/hash_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#hash" do
+ it "is based on begin, end, step and exclude_end?" do
+ 1.step(10).hash.should.instance_of?(Integer)
+
+ 1.step(10).hash.should == 1.step(10).hash
+ 1.step(10, 5).hash.should == 1.step(10, 5).hash
+
+ (1..10).step.hash.should == (1..10).step.hash
+ (1...10).step(8).hash.should == (1...10).step(8).hash
+
+ # both have exclude_end? == false
+ (1..10).step(100).hash.should == 1.step(10, 100).hash
+
+ ((1..10).step.hash == (1..11).step.hash).should == false
+ ((1..10).step.hash == (1...10).step.hash).should == false
+ ((1..10).step.hash == (1..10).step(2).hash).should == false
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb
new file mode 100644
index 0000000000..b73b49d272
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/inspect_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#inspect" do
+ context 'when Numeric#step is used' do
+ it "returns '(begin.step(end{, step}))'" do
+ 1.step(10).inspect.should == "(1.step(10))"
+ 1.step(10, 3).inspect.should == "(1.step(10, 3))"
+ end
+ end
+
+ context 'when Range#step is used' do
+ it "returns '((range).step{(step)})'" do
+ (1..10).step.inspect.should == "((1..10).step)"
+ (1..10).step(3).inspect.should == "((1..10).step(3))"
+
+ (1...10).step.inspect.should == "((1...10).step)"
+ (1...10).step(3).inspect.should == "((1...10).step(3))"
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb
new file mode 100644
index 0000000000..31f982b7c4
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/last_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#last" do
+ it "returns the last element of the sequence" do
+ 1.step(10).last.should == 10
+ (1..10).step.last.should == 10
+ (1...10).step(4).last.should == 9
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb
new file mode 100644
index 0000000000..1bd2f7f0f7
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/new_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence.new" do
+ it "is not defined" do
+ -> {
+ Enumerator::ArithmeticSequence.new
+ }.should.raise(NoMethodError)
+ end
+end
+
+describe "Enumerator::ArithmeticSequence.allocate" do
+ it "is not defined" do
+ -> {
+ Enumerator::ArithmeticSequence.allocate
+ }.should.raise(TypeError, 'allocator undefined for Enumerator::ArithmeticSequence')
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb
new file mode 100644
index 0000000000..7e03edd961
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/size_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#size" do
+ context "for finite sequence" do
+ it "returns the number of elements in this arithmetic sequence" do
+ 1.step(10).size.should == 10
+ (1...10).step.size.should == 9
+ end
+ end
+
+ context "for infinite sequence" do
+ it "returns Infinity" do
+ 1.step(Float::INFINITY).size.should == Float::INFINITY
+ (1..Float::INFINITY).step.size.should == Float::INFINITY
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb b/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb
new file mode 100644
index 0000000000..c1f2d9173f
--- /dev/null
+++ b/spec/ruby/core/enumerator/arithmetic_sequence/step_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::ArithmeticSequence#step" do
+ it "returns the original value given to step method" do
+ (1..10).step.step.should == 1
+ (1..10).step(3).step.should == 3
+
+ 1.step(10).step.should == 1
+ 1.step(10, 3).step.should == 3
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/each_spec.rb b/spec/ruby/core/enumerator/chain/each_spec.rb
new file mode 100644
index 0000000000..cc93cbac60
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/each_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+require_relative '../../enumerable/fixtures/classes'
+
+describe "Enumerator::Chain#each" do
+ it "calls each on its constituents as needed" do
+ a = EnumerableSpecs::EachCounter.new(:a, :b)
+ b = EnumerableSpecs::EachCounter.new(:c, :d)
+
+ ScratchPad.record []
+ Enumerator::Chain.new(a, b).each do |elem|
+ ScratchPad << elem << b.times_yielded
+ end
+ ScratchPad.recorded.should == [:a, 0, :b, 0, :c, 1, :d, 2]
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/initialize_spec.rb b/spec/ruby/core/enumerator/chain/initialize_spec.rb
new file mode 100644
index 0000000000..1df1dec5f8
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/initialize_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Chain#initialize" do
+ before :each do
+ @uninitialized = Enumerator::Chain.allocate
+ end
+
+ it "is a private method" do
+ Enumerator::Chain.private_instance_methods(false).should.include?(:initialize)
+ end
+
+ it "returns self" do
+ @uninitialized.send(:initialize).should.equal?(@uninitialized)
+ end
+
+ it "accepts many arguments" do
+ @uninitialized.send(:initialize, 0..1, 2..3, 4..5).should.equal?(@uninitialized)
+ end
+
+ it "accepts arguments that are not Enumerable nor responding to :each" do
+ @uninitialized.send(:initialize, Object.new).should.equal?(@uninitialized)
+ end
+
+ describe "on frozen instance" do
+ it "raises a FrozenError" do
+ -> {
+ @uninitialized.freeze.send(:initialize)
+ }.should.raise(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/inspect_spec.rb b/spec/ruby/core/enumerator/chain/inspect_spec.rb
new file mode 100644
index 0000000000..9b5a442b75
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/inspect_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Chain#inspect" do
+ it "shows a representation of the Enumerator" do
+ Enumerator::Chain.new.inspect.should == "#<Enumerator::Chain: []>"
+ Enumerator::Chain.new(1..2, 3..4).inspect.should == "#<Enumerator::Chain: [1..2, 3..4]>"
+ end
+
+ it "calls inspect on its chain elements" do
+ obj = mock('inspect')
+ obj.should_receive(:inspect).and_return('some desc')
+ Enumerator::Chain.new(obj).inspect.should == "#<Enumerator::Chain: [some desc]>"
+ end
+
+ it "returns a not initialized representation if #initialized is not called yet" do
+ Enumerator::Chain.allocate.inspect.should == "#<Enumerator::Chain: uninitialized>"
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/rewind_spec.rb b/spec/ruby/core/enumerator/chain/rewind_spec.rb
new file mode 100644
index 0000000000..4525b82f7b
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/rewind_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Chain#rewind" do
+ before(:each) do
+ @obj = mock('obj')
+ @obj.should_receive(:each).any_number_of_times.and_yield
+ @second = mock('obj')
+ @second.should_receive(:each).any_number_of_times.and_yield
+ @enum = Enumerator::Chain.new(@obj, @second)
+ end
+
+ it "returns self" do
+ @enum.rewind.should.equal? @enum
+ end
+
+ it "does nothing if receiver has not been iterated" do
+ @obj.should_not_receive(:rewind)
+ @obj.respond_to?(:rewind).should == true # sanity check
+ @enum.rewind
+ end
+
+ it "does nothing on objects that don't respond_to rewind" do
+ @obj.respond_to?(:rewind).should == false # sanity check
+ @enum.each {}
+ @enum.rewind
+ end
+
+ it "calls_rewind its objects" do
+ @obj.should_receive(:rewind)
+ @enum.each {}
+ @enum.rewind
+ end
+
+ it "calls_rewind in reverse order" do
+ @obj.should_not_receive(:rewind)
+ @second.should_receive(:rewind).and_raise(RuntimeError)
+ @enum.each {}
+ -> { @enum.rewind }.should.raise(RuntimeError)
+ end
+
+ it "calls rewind only for objects that have actually been iterated on" do
+ @obj = mock('obj')
+ @obj.should_receive(:each).any_number_of_times.and_raise(RuntimeError)
+ @enum = Enumerator::Chain.new(@obj, @second)
+
+ @obj.should_receive(:rewind)
+ @second.should_not_receive(:rewind)
+ -> { @enum.each {} }.should.raise(RuntimeError)
+ @enum.rewind
+ end
+end
diff --git a/spec/ruby/core/enumerator/chain/size_spec.rb b/spec/ruby/core/enumerator/chain/size_spec.rb
new file mode 100644
index 0000000000..d85b88ee8b
--- /dev/null
+++ b/spec/ruby/core/enumerator/chain/size_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../../spec_helper'
+require_relative '../../enumerable/fixtures/classes'
+
+describe "Enumerator::Chain#size" do
+ it "returns the sum of the sizes of the elements" do
+ a = mock('size')
+ a.should_receive(:size).and_return(40)
+ Enumerator::Chain.new(a, [:a, :b]).size.should == 42
+ end
+
+ it "returns nil or Infinity for the first element of such a size" do
+ [nil, Float::INFINITY].each do |special|
+ a = mock('size')
+ a.should_receive(:size).and_return(40)
+ b = mock('special')
+ b.should_receive(:size).and_return(special)
+ c = mock('not called')
+ c.should_not_receive(:size)
+ Enumerator::Chain.new(a, b, c).size.should == special
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/each_spec.rb b/spec/ruby/core/enumerator/each_spec.rb
new file mode 100644
index 0000000000..03be53fe05
--- /dev/null
+++ b/spec/ruby/core/enumerator/each_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#each" do
+ before :each do
+ object_each_with_arguments = Object.new
+ def object_each_with_arguments.each_with_arguments(arg, *args)
+ yield arg, *args
+ :method_returned
+ end
+
+ @enum_with_arguments = object_each_with_arguments.to_enum(:each_with_arguments, :arg0, :arg1, :arg2)
+
+ @enum_with_yielder = Enumerator.new { |y| y.yield :ok }
+ end
+
+ it "yields each element of self to the given block" do
+ acc = []
+ [1, 2, 3].to_enum.each { |e| acc << e }
+ acc.should == [1, 2, 3]
+ end
+
+ it "calls #each on the object given in the constructor by default" do
+ each = mock('each')
+ each.should_receive(:each)
+ each.to_enum.each { |e| e }
+ end
+
+ it "calls #each on the underlying object until it's exhausted" do
+ each = mock('each')
+ each.should_receive(:each).and_yield(1).and_yield(2).and_yield(3)
+ acc = []
+ each.to_enum.each { |e| acc << e }
+ acc.should == [1, 2, 3]
+ end
+
+ it "calls the method given in the constructor instead of #each" do
+ each = mock('peach')
+ each.should_receive(:peach)
+ each.to_enum(:peach).each { |e| e }
+ end
+
+ it "calls the method given in the constructor until it's exhausted" do
+ each = mock('peach')
+ each.should_receive(:peach).and_yield(1).and_yield(2).and_yield(3)
+ acc = []
+ each.to_enum(:peach).each { |e| acc << e }
+ acc.should == [1, 2, 3]
+ end
+
+ it "raises a NoMethodError if the object doesn't respond to #each" do
+ enum = Object.new.to_enum
+ -> do
+ enum.each { |e| e }
+ end.should.raise(NoMethodError)
+ end
+
+ it "returns self if not given arguments and not given a block" do
+ @enum_with_arguments.each.should.equal?(@enum_with_arguments)
+
+ @enum_with_yielder.each.should.equal?(@enum_with_yielder)
+ end
+
+ it "returns the same value from receiver.each if block is given" do
+ @enum_with_arguments.each {}.should.equal?(:method_returned)
+ end
+
+ it "passes given arguments at initialized to receiver.each" do
+ @enum_with_arguments.each.to_a.should == [[:arg0, :arg1, :arg2]]
+ end
+
+ it "requires multiple arguments" do
+ Enumerator.instance_method(:each).arity.should < 0
+ end
+
+ it "appends given arguments to receiver.each" do
+ @enum_with_arguments.each(:each0, :each1).to_a.should == [[:arg0, :arg1, :arg2, :each0, :each1]]
+ @enum_with_arguments.each(:each2, :each3).to_a.should == [[:arg0, :arg1, :arg2, :each2, :each3]]
+ end
+
+ it "returns the same value from receiver.each if block and arguments are given" do
+ @enum_with_arguments.each(:each1, :each2) {}.should.equal?(:method_returned)
+ end
+
+ it "returns new Enumerator if given arguments but not given a block" do
+ ret = @enum_with_arguments.each 1
+ ret.should.instance_of?(Enumerator)
+ ret.should_not.equal?(@enum_with_arguments)
+ end
+
+ it "does not destructure yielded array values when chaining each.map" do
+ result = [[[1]]].each.map { |a, b| [a, b] }
+ result.should == [[[1], nil]]
+ end
+
+ it "preserves array values yielded from the enumerator" do
+ result = [[1, 2]].each.map { |a| a }
+ result.should == [[1, 2]]
+ end
+
+ it "allows destructuring to occur in the block, not the enumerator" do
+ result = [[1, 2]].each.map { |a, b| a }
+ result.should == [1]
+ end
+end
diff --git a/spec/ruby/core/enumerator/each_with_index_spec.rb b/spec/ruby/core/enumerator/each_with_index_spec.rb
new file mode 100644
index 0000000000..0630b7045e
--- /dev/null
+++ b/spec/ruby/core/enumerator/each_with_index_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative 'shared/with_index'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Enumerator#each_with_index" do
+ it_behaves_like :enum_with_index, :each_with_index
+ it_behaves_like :enumeratorized_with_origin_size, :each_with_index, [1,2,3].select
+
+ it "returns a new Enumerator when no block is given" do
+ enum1 = [1,2,3].select
+ enum2 = enum1.each_with_index
+ enum2.should.instance_of?(Enumerator)
+ enum1.should_not == enum2
+ end
+
+ it "raises an ArgumentError if passed extra arguments" do
+ -> do
+ [1].to_enum.each_with_index(:glark)
+ end.should.raise(ArgumentError)
+ end
+
+ it "passes on the given block's return value" do
+ arr = [1,2,3]
+ 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.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]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/each_with_object_spec.rb b/spec/ruby/core/enumerator/each_with_object_spec.rb
new file mode 100644
index 0000000000..84a45ae89d
--- /dev/null
+++ b/spec/ruby/core/enumerator/each_with_object_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/with_object'
+
+describe "Enumerator#each_with_object" do
+ it_behaves_like :enum_with_object, :each_with_object
+end
diff --git a/spec/ruby/core/enumerator/enum_for_spec.rb b/spec/ruby/core/enumerator/enum_for_spec.rb
new file mode 100644
index 0000000000..fbdf98545a
--- /dev/null
+++ b/spec/ruby/core/enumerator/enum_for_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/enum_for'
+
+describe "Enumerator#enum_for" do
+ it_behaves_like :enum_for, :enum_for
+end
diff --git a/spec/ruby/core/enumerator/enumerator_spec.rb b/spec/ruby/core/enumerator/enumerator_spec.rb
new file mode 100644
index 0000000000..7a263336cb
--- /dev/null
+++ b/spec/ruby/core/enumerator/enumerator_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator" do
+ it "includes Enumerable" do
+ Enumerator.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/enumerator/feed_spec.rb b/spec/ruby/core/enumerator/feed_spec.rb
new file mode 100644
index 0000000000..781947a8c7
--- /dev/null
+++ b/spec/ruby/core/enumerator/feed_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Enumerator#feed" do
+ before :each do
+ ScratchPad.record []
+ @enum = EnumeratorSpecs::Feed.new.to_enum(:each)
+ end
+
+ it "sets the future return value of yield if called before advancing the iterator" do
+ @enum.feed :a
+ @enum.next
+ @enum.next
+ @enum.next
+ ScratchPad.recorded.should == [:a, nil]
+ end
+
+ it "causes yield to return the value if called during iteration" do
+ @enum.next
+ @enum.feed :a
+ @enum.next
+ @enum.next
+ ScratchPad.recorded.should == [:a, nil]
+ end
+
+ it "can be called for each iteration" do
+ @enum.next
+ @enum.feed :a
+ @enum.next
+ @enum.feed :b
+ @enum.next
+ ScratchPad.recorded.should == [:a, :b]
+ end
+
+ it "returns nil" do
+ @enum.feed(:a).should == nil
+ end
+
+ it "raises a TypeError if called more than once without advancing the enumerator" do
+ @enum.feed :a
+ @enum.next
+ -> { @enum.feed :b }.should.raise(TypeError)
+ end
+
+ it "sets the return value of Yielder#yield" do
+ enum = Enumerator.new { |y| ScratchPad << y.yield }
+ enum.next
+ enum.feed :a
+ -> { enum.next }.should.raise(StopIteration)
+ ScratchPad.recorded.should == [:a]
+ end
+end
diff --git a/spec/ruby/core/enumerator/first_spec.rb b/spec/ruby/core/enumerator/first_spec.rb
new file mode 100644
index 0000000000..458080bb31
--- /dev/null
+++ b/spec/ruby/core/enumerator/first_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#first" do
+ it "returns arrays correctly when calling #first (2376)" do
+ Enumerator.new {|y| y << [42] }.first.should == [42]
+ end
+end
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/fixtures/common.rb b/spec/ruby/core/enumerator/fixtures/common.rb
new file mode 100644
index 0000000000..e332e3195b
--- /dev/null
+++ b/spec/ruby/core/enumerator/fixtures/common.rb
@@ -0,0 +1,9 @@
+module EnumeratorSpecs
+ class Feed
+ def each
+ ScratchPad << yield
+ ScratchPad << yield
+ ScratchPad << yield
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/initialize_spec.rb b/spec/ruby/core/enumerator/initialize_spec.rb
new file mode 100644
index 0000000000..9929494b5a
--- /dev/null
+++ b/spec/ruby/core/enumerator/initialize_spec.rb
@@ -0,0 +1,57 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../spec_helper'
+
+describe "Enumerator#initialize" do
+ before :each do
+ @uninitialized = Enumerator.allocate
+ end
+
+ it "is a private method" do
+ Enumerator.private_instance_methods(false).should.include?(:initialize)
+ end
+
+ it "returns self when given a block" do
+ @uninitialized.send(:initialize) {}.should.equal?(@uninitialized)
+ end
+
+ # Maybe spec should be broken up?
+ it "accepts a block" do
+ @uninitialized.send(:initialize) do |yielder|
+ r = yielder.yield 3
+ yielder << r << 2 << 1
+ end
+ @uninitialized.should.instance_of?(Enumerator)
+ r = []
+ @uninitialized.each{|x| r << x; x * 2}
+ r.should == [3, 6, 2, 1]
+ end
+
+ it "sets size to nil if size is not given" do
+ @uninitialized.send(:initialize) {}.size.should == nil
+ end
+
+ it "sets size to nil if the given size is nil" do
+ @uninitialized.send(:initialize, nil) {}.size.should == nil
+ end
+
+ it "sets size to the given size if the given size is Float::INFINITY" do
+ @uninitialized.send(:initialize, Float::INFINITY) {}.size.should.equal?(Float::INFINITY)
+ end
+
+ it "sets size to the given size if the given size is an Integer" do
+ @uninitialized.send(:initialize, 100) {}.size.should == 100
+ end
+
+ it "sets size to the given size if the given size is a Proc" do
+ @uninitialized.send(:initialize, -> { 200 }) {}.size.should == 200
+ end
+
+ describe "on frozen instance" do
+ it "raises a FrozenError" do
+ -> {
+ @uninitialized.freeze.send(:initialize) {}
+ }.should.raise(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/inspect_spec.rb b/spec/ruby/core/enumerator/inspect_spec.rb
new file mode 100644
index 0000000000..7e97864246
--- /dev/null
+++ b/spec/ruby/core/enumerator/inspect_spec.rb
@@ -0,0 +1,22 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#inspect" do
+ describe "shows a representation of the Enumerator" do
+ it "including receiver and method" do
+ (1..3).each.inspect.should == "#<Enumerator: 1..3:each>"
+ end
+
+ it "including receiver and method and arguments" do
+ (1..3).each_slice(2).inspect.should == "#<Enumerator: 1..3:each_slice(2)>"
+ end
+
+ it "including the nested Enumerator" do
+ (1..3).each.each_slice(2).inspect.should == "#<Enumerator: #<Enumerator: 1..3:each>:each_slice(2)>"
+ end
+ end
+
+ it "returns a not initialized representation if #initialized is not called yet" do
+ Enumerator.allocate.inspect.should == "#<Enumerator: uninitialized>"
+ Enumerator::Lazy.allocate.inspect.should == "#<Enumerator::Lazy: uninitialized>"
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/chunk_spec.rb b/spec/ruby/core/enumerator/lazy/chunk_spec.rb
new file mode 100644
index 0000000000..d0179d32b6
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/chunk_spec.rb
@@ -0,0 +1,67 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#chunk" do
+
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.chunk {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.chunk { |v| v }.size.should == nil
+ end
+
+ it "returns an Enumerator if called without a block" do
+ chunk = @yieldsmixed.chunk
+ chunk.should.instance_of?(Enumerator::Lazy)
+
+ res = chunk.each { |v| true }.force
+ res.should == [[true, EnumeratorLazySpecs::YieldsMixed.gathered_yields]]
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ first_two = (0..Float::INFINITY).lazy.chunk { |n| n.even? }.first(2)
+ first_two.should == [[true, [0]], [false, [1]]]
+ end
+ end
+
+ it "calls the block with gathered values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.chunk { |v| yields << v; true }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).chunk { |v| v }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ remains_lazy = (0..Float::INFINITY).lazy.chunk { |n| n }
+ remains_lazy.chunk { |n| n }.first(2).size.should == 2
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.chunk { |n| n.even? }.first(100).should ==
+ s.first(100).chunk { |n| n.even? }.to_a
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb b/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb
new file mode 100644
index 0000000000..edba8e1363
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#chunk_while" do
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.chunk_while { |a, b| false }.first(100).should ==
+ s.first(100).chunk_while { |a, b| false }.to_a
+ end
+
+ it "should return a lazy enumerator" do
+ s = 0..Float::INFINITY
+ s.lazy.chunk_while { |a, b| false }.should.is_a?(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb b/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb
new file mode 100644
index 0000000000..8765bb2190
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/collect_concat_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/collect_concat'
+
+describe "Enumerator::Lazy#collect_concat" do
+ it_behaves_like :enumerator_lazy_collect_concat, :collect_concat
+end
diff --git a/spec/ruby/core/enumerator/lazy/collect_spec.rb b/spec/ruby/core/enumerator/lazy/collect_spec.rb
new file mode 100644
index 0000000000..14b79ce16d
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/collect_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/collect'
+
+describe "Enumerator::Lazy#collect" do
+ it_behaves_like :enumerator_lazy_collect, :collect
+end
diff --git a/spec/ruby/core/enumerator/lazy/compact_spec.rb b/spec/ruby/core/enumerator/lazy/compact_spec.rb
new file mode 100644
index 0000000000..7305e1c9c4
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/compact_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#compact" do
+ it 'returns array without nil elements' do
+ arr = [1, nil, 3, false, 5].to_enum.lazy.compact
+ arr.should.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
+end
diff --git a/spec/ruby/core/enumerator/lazy/drop_spec.rb b/spec/ruby/core/enumerator/lazy/drop_spec.rb
new file mode 100644
index 0000000000..95ac7e9ecc
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/drop_spec.rb
@@ -0,0 +1,58 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#drop" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.drop(1)
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets difference of given count with old size to new size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.drop(20).size.should == 80
+ Enumerator::Lazy.new(Object.new, 100) {}.drop(200).size.should == 0
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.drop(2).first(2).should == [2, 3]
+
+ @eventsmixed.drop(0).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ describe "on a nested Lazy" do
+ it "sets difference of given count with old size to new size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.drop(20).drop(50).size.should == 30
+ Enumerator::Lazy.new(Object.new, 100) {}.drop(50).drop(20).size.should == 30
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.drop(2).drop(2).first(2).should == [4, 5]
+
+ @eventsmixed.drop(0).drop(0).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.drop(100).first(100).should ==
+ s.first(200).drop(100)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/drop_while_spec.rb b/spec/ruby/core/enumerator/lazy/drop_while_spec.rb
new file mode 100644
index 0000000000..65f3007dec
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/drop_while_spec.rb
@@ -0,0 +1,66 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#drop_while" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.drop_while {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.drop_while { |v| v }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.drop_while { |n| n < 5 }.first(2).should == [5, 6]
+
+ @eventsmixed.drop_while { false }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with initial values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.drop_while { |v| yields << v; true }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.drop_while }.should.raise(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).drop_while { |v| v }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.drop_while { |n| n < 5 }.drop_while { |n| n.odd? }.first(2).should == [6, 7]
+
+ @eventsmixed.drop_while { false }.drop_while { false }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.drop_while { |n| n < 100 }.first(100).should ==
+ s.first(200).drop_while { |n| n < 100 }
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/eager_spec.rb b/spec/ruby/core/enumerator/lazy/eager_spec.rb
new file mode 100644
index 0000000000..592da4fd8c
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/eager_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#eager" do
+ it "returns a non-lazy Enumerator converted from the lazy enumerator" do
+ enum = [1, 2, 3].lazy
+
+ enum.class.should == Enumerator::Lazy
+ enum.eager.class.should == Enumerator
+ end
+
+ it "does not enumerate an enumerator" do
+ ScratchPad.record []
+
+ sequence = [1, 2, 3]
+ enum_lazy = Enumerator::Lazy.new(sequence) do |yielder, value|
+ yielder << value
+ ScratchPad << value
+ end
+
+ ScratchPad.recorded.should == []
+ enum = enum_lazy.eager
+ ScratchPad.recorded.should == []
+
+ enum.map { |i| i }.should == [1, 2, 3]
+ ScratchPad.recorded.should == [1, 2, 3]
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/enum_for_spec.rb b/spec/ruby/core/enumerator/lazy/enum_for_spec.rb
new file mode 100644
index 0000000000..7e7783f6f1
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/enum_for_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/to_enum'
+
+describe "Enumerator::Lazy#enum_for" do
+ it_behaves_like :enumerator_lazy_to_enum, :enum_for
+end
diff --git a/spec/ruby/core/enumerator/lazy/filter_map_spec.rb b/spec/ruby/core/enumerator/lazy/filter_map_spec.rb
new file mode 100644
index 0000000000..99dd59cebe
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/filter_map_spec.rb
@@ -0,0 +1,14 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#filter_map" do
+ it "maps only truthy results" do
+ (1..Float::INFINITY).lazy.filter_map { |i| i if i.odd? }.first(4).should == [1, 3, 5, 7]
+ end
+
+ it "does not map false results" do
+ (1..Float::INFINITY).lazy.filter_map { |i| i.odd? ? i : false }.first(4).should == [1, 3, 5, 7]
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/filter_spec.rb b/spec/ruby/core/enumerator/lazy/filter_spec.rb
new file mode 100644
index 0000000000..43128241e0
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/filter_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../../spec_helper'
+require_relative 'shared/select'
+
+describe "Enumerator::Lazy#filter" do
+ it_behaves_like :enumerator_lazy_select, :filter
+end
diff --git a/spec/ruby/core/enumerator/lazy/find_all_spec.rb b/spec/ruby/core/enumerator/lazy/find_all_spec.rb
new file mode 100644
index 0000000000..8b05c53803
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/find_all_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/select'
+
+describe "Enumerator::Lazy#find_all" do
+ it_behaves_like :enumerator_lazy_select, :find_all
+end
diff --git a/spec/ruby/core/enumerator/lazy/fixtures/classes.rb b/spec/ruby/core/enumerator/lazy/fixtures/classes.rb
new file mode 100644
index 0000000000..e35592ba1c
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/fixtures/classes.rb
@@ -0,0 +1,54 @@
+# -*- encoding: us-ascii -*-
+
+module EnumeratorLazySpecs
+ class SpecificError < Exception; end
+
+ class YieldsMixed
+ def self.initial_yields
+ [nil, 0, 0, 0, 0, nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.gathered_yields
+ [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, :default_arg, [], [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def self.gathered_non_array_yields
+ [nil, 0, nil, :default_arg]
+ end
+
+ def self.gathered_yields_with_args(arg, *args)
+ [nil, 0, [0, 1], [0, 1, 2], [0, 1, 2], nil, arg, args, [], [0], [0, 1], [0, 1, 2]]
+ end
+
+ def each(arg=:default_arg, *args)
+ yield
+ yield 0
+ yield 0, 1
+ yield 0, 1, 2
+ yield(*[0, 1, 2])
+ yield nil
+ yield arg
+ yield args
+ yield []
+ yield [0]
+ yield [0, 1]
+ yield [0, 1, 2]
+ end
+ end
+
+ class EventsMixed
+ def each
+ ScratchPad << :before_yield
+
+ yield 0
+
+ ScratchPad << :after_yield
+
+ raise SpecificError
+
+ ScratchPad << :after_error
+
+ :should_not_reach_here
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/flat_map_spec.rb b/spec/ruby/core/enumerator/lazy/flat_map_spec.rb
new file mode 100644
index 0000000000..5dcaa8bfa1
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/flat_map_spec.rb
@@ -0,0 +1,16 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/collect_concat'
+
+describe "Enumerator::Lazy#flat_map" do
+ it_behaves_like :enumerator_lazy_collect_concat, :flat_map
+
+ it "properly unwraps nested yields" do
+ s = Enumerator.new do |y| loop do y << [1, 2] end end
+
+ expected = s.take(3).flat_map { |x| x }.to_a
+ actual = s.lazy.take(3).flat_map{ |x| x }.force
+ actual.should == expected
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/force_spec.rb b/spec/ruby/core/enumerator/lazy/force_spec.rb
new file mode 100644
index 0000000000..a7fa029135
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/force_spec.rb
@@ -0,0 +1,36 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#force" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "passes given arguments to receiver.each" do
+ @yieldsmixed.force(:arg1, :arg2, :arg3).should ==
+ EnumeratorLazySpecs::YieldsMixed.gathered_yields_with_args(:arg1, :arg2, :arg3)
+ end
+
+ describe "on a nested Lazy" do
+ it "calls all block and returns an Array" do
+ (0..Float::INFINITY).lazy.map(&:succ).take(2).force.should == [1, 2]
+
+ @eventsmixed.take(1).map(&:succ).force.should == [1]
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.take(100).force.should ==
+ s.take(100)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/grep_spec.rb b/spec/ruby/core/enumerator/lazy/grep_spec.rb
new file mode 100644
index 0000000000..383f80a918
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/grep_spec.rb
@@ -0,0 +1,121 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#grep" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "requires an argument" do
+ Enumerator::Lazy.instance_method(:grep).arity.should == 1
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.grep(Object) {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+
+ ret = @yieldsmixed.grep(Object)
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.grep(Object) {}.size.should == nil
+ Enumerator::Lazy.new(Object.new, 100) {}.grep(Object).size.should == nil
+ end
+
+ it "sets $~ in the block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep(/b/) { |e|
+ e.should == "abc"
+ $&.should == "b"
+ }.force
+
+ # Set by the failed match of "def"
+ $~.should == nil
+ end
+
+ it "sets $~ in the next block with each" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep(/b/).each { |e|
+ e.should == "abc"
+ $&.should == "b"
+ }
+
+ # Set by the failed match of "def"
+ $~.should == nil
+ end
+
+ it "sets $~ in the next block with map" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep(/b/).map { |e|
+ e.should == "abc"
+ $&.should == "b"
+ }.force
+
+ # Set by the failed match of "def"
+ $~.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times when not given a block" do
+ (0..Float::INFINITY).lazy.grep(Integer).first(3).should == [0, 1, 2]
+
+ @eventsmixed.grep(BasicObject).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops after specified times when given a block" do
+ (0..Float::INFINITY).lazy.grep(Integer, &:succ).first(3).should == [1, 2, 3]
+
+ @eventsmixed.grep(BasicObject) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.grep(BasicObject) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields
+
+ @yieldsmixed.grep(BasicObject).force.should == yields
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.grep(Object) {}.size.should == nil
+ Enumerator::Lazy.new(Object.new, 100) {}.grep(Object).size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times when not given a block" do
+ (0..Float::INFINITY).lazy.grep(Integer).grep(Object).first(3).should == [0, 1, 2]
+
+ @eventsmixed.grep(BasicObject).grep(Object).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops after specified times when given a block" do
+ (0..Float::INFINITY).lazy.grep(Integer) { |n| n > 3 ? n : false }.grep(Integer) { |n| n.even? ? n : false }.first(3).should == [4, false, 6]
+
+ @eventsmixed.grep(BasicObject) {}.grep(Object) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.grep(Numeric).first(100).should ==
+ s.first(100).grep(Numeric)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/grep_v_spec.rb b/spec/ruby/core/enumerator/lazy/grep_v_spec.rb
new file mode 100644
index 0000000000..19c917f254
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/grep_v_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#grep_v" do
+ before(:each) do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after(:each) do
+ ScratchPad.clear
+ end
+
+ it "requires an argument" do
+ Enumerator::Lazy.instance_method(:grep_v).arity.should == 1
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.grep_v(Object) {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+
+ ret = @yieldsmixed.grep_v(Object)
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object) {}.size.should == nil
+ Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).size.should == nil
+ end
+
+ it "sets $~ in the block" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep_v(/e/) { |e|
+ e.should == "abc"
+ $~.should == nil
+ }.force
+
+ # Set by the match of "def"
+ $&.should == "e"
+ end
+
+ it "sets $~ in the next block with each" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep_v(/e/).each { |e|
+ e.should == "abc"
+ $~.should == nil
+ }
+
+ # Set by the match of "def"
+ $&.should == "e"
+ end
+
+ it "sets $~ in the next block with map" do
+ "z" =~ /z/ # Reset $~
+ ["abc", "def"].lazy.grep_v(/e/).map { |e|
+ e.should == "abc"
+ $~.should == nil
+ }.force
+
+ # Set by the match of "def"
+ $&.should == "e"
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times when not given a block" do
+ (0..Float::INFINITY).lazy.grep_v(3..5).first(3).should == [0, 1, 2]
+
+ @eventsmixed.grep_v(Symbol).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops after specified times when given a block" do
+ (0..Float::INFINITY).lazy.grep_v(4..8, &:succ).first(3).should == [1, 2, 3]
+
+ @eventsmixed.grep_v(Symbol) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.grep_v(Array) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_non_array_yields
+
+ @yieldsmixed.grep_v(Array).force.should == yields
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).grep_v(Object) {}.size.should == nil
+ Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).grep_v(Object).size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times when not given a block" do
+ (0..Float::INFINITY).lazy.grep_v(3..5).grep_v(6..10).first(3).should == [0, 1, 2]
+
+ @eventsmixed.grep_v(Symbol).grep_v(String).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops after specified times when given a block" do
+ (0..Float::INFINITY).lazy
+ .grep_v(1..2) { |n| n > 3 ? n : false }
+ .grep_v(false) { |n| n.even? ? n : false }
+ .first(3)
+ .should == [4, false, 6]
+
+ @eventsmixed.grep_v(Symbol) {}.grep_v(String) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.grep_v(String).first(100).should ==
+ s.first(100).grep_v(String)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/initialize_spec.rb b/spec/ruby/core/enumerator/lazy/initialize_spec.rb
new file mode 100644
index 0000000000..47765d32c3
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/initialize_spec.rb
@@ -0,0 +1,63 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#initialize" do
+ before :each do
+ @receiver = receiver = Object.new
+
+ def receiver.each
+ yield 0
+ yield 1
+ yield 2
+ end
+
+ @uninitialized = Enumerator::Lazy.allocate
+ end
+
+ it "is a private method" do
+ Enumerator::Lazy.private_instance_methods(false).should.include?(:initialize)
+ end
+
+ it "returns self" do
+ @uninitialized.send(:initialize, @receiver) {}.should.equal?(@uninitialized)
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ @uninitialized.send(:initialize, @receiver) do |yielder, *values|
+ yielder.<<(*values)
+ end.first(2).should == [0, 1]
+ end
+ end
+
+ it "sets #size to nil if not given a size" do
+ @uninitialized.send(:initialize, @receiver) {}.size.should == nil
+ end
+
+ it "sets #size to nil if given size is nil" do
+ @uninitialized.send(:initialize, @receiver, nil) {}.size.should == nil
+ end
+
+ it "sets given size to own size if the given size is Float::INFINITY" do
+ @uninitialized.send(:initialize, @receiver, Float::INFINITY) {}.size.should.equal?(Float::INFINITY)
+ end
+
+ it "sets given size to own size if the given size is an Integer" do
+ @uninitialized.send(:initialize, @receiver, 100) {}.size.should == 100
+ end
+
+ it "sets given size to own size if the given size is a Proc" do
+ @uninitialized.send(:initialize, @receiver, -> { 200 }) {}.size.should == 200
+ end
+
+ it "raises an ArgumentError when block is not given" do
+ -> { @uninitialized.send :initialize, @receiver }.should.raise(ArgumentError)
+ end
+
+ describe "on frozen instance" do
+ it "raises a FrozenError" do
+ -> { @uninitialized.freeze.send(:initialize, @receiver) {} }.should.raise(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/lazy_spec.rb b/spec/ruby/core/enumerator/lazy/lazy_spec.rb
new file mode 100644
index 0000000000..12107383d6
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/lazy_spec.rb
@@ -0,0 +1,27 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy" do
+ it "is a subclass of Enumerator" do
+ Enumerator::Lazy.superclass.should.equal?(Enumerator)
+ end
+
+ it "defines lazy versions of a whitelist of Enumerator methods" do
+ lazy_methods = Set[
+ :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, :uniq, :zip
+ ]
+
+ Enumerator::Lazy.instance_methods(false).to_set.should >= lazy_methods
+ end
+end
+
+describe "Enumerator::Lazy#lazy" do
+ it "returns self" do
+ lazy = (1..3).to_enum.lazy
+ lazy.lazy.should.equal?(lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/map_spec.rb b/spec/ruby/core/enumerator/lazy/map_spec.rb
new file mode 100644
index 0000000000..5cb998f5f7
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/map_spec.rb
@@ -0,0 +1,12 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/collect'
+
+describe "Enumerator::Lazy#map" do
+ it_behaves_like :enumerator_lazy_collect, :map
+
+ it "doesn't unwrap Arrays" do
+ Enumerator.new {|y| y.yield([1])}.lazy.to_a.should == [[1]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/reject_spec.rb b/spec/ruby/core/enumerator/lazy/reject_spec.rb
new file mode 100644
index 0000000000..374d4df14e
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/reject_spec.rb
@@ -0,0 +1,78 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#reject" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.reject {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.reject {}.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.reject(&:even?).first(3).should == [1, 3, 5]
+
+ @eventsmixed.reject { false }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "lets exceptions raised in the block go through" do
+ lazy = 10.times.lazy.map do |i|
+ raise "foo"
+ end
+
+ lazy = lazy.reject(&:nil?)
+
+ -> {
+ lazy.first
+ }.should.raise(RuntimeError, "foo")
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.reject { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.reject }.should.raise(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).reject {}.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.reject { |n| n < 4 }.reject(&:even?).first(3).should == [5, 7, 9]
+
+ @eventsmixed.reject { false }.reject { false }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.reject { |n| false }.first(100).should ==
+ s.first(100).reject { |n| false }
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/select_spec.rb b/spec/ruby/core/enumerator/lazy/select_spec.rb
new file mode 100644
index 0000000000..3773d8f0a8
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/select_spec.rb
@@ -0,0 +1,47 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/select'
+
+describe "Enumerator::Lazy#select" do
+ it_behaves_like :enumerator_lazy_select, :select
+
+ it "doesn't pre-evaluate the next element" do
+ eval_count = 0
+ enum = %w[Text1 Text2 Text3].lazy.select do
+ eval_count += 1
+ true
+ end
+
+ eval_count.should == 0
+ enum.next
+ eval_count.should == 1
+ end
+
+ it "doesn't over-evaluate when peeked" do
+ eval_count = 0
+ enum = %w[Text1 Text2 Text3].lazy.select do
+ eval_count += 1
+ true
+ end
+
+ eval_count.should == 0
+ enum.peek
+ enum.peek
+ eval_count.should == 1
+ end
+
+ it "doesn't re-evaluate after peek" do
+ eval_count = 0
+ enum = %w[Text1 Text2 Text3].lazy.select do
+ eval_count += 1
+ true
+ end
+
+ eval_count.should == 0
+ enum.peek
+ eval_count.should == 1
+ enum.next
+ eval_count.should == 1
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/shared/collect.rb b/spec/ruby/core/enumerator/lazy/shared/collect.rb
new file mode 100644
index 0000000000..0ed04c8e02
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/shared/collect.rb
@@ -0,0 +1,62 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :enumerator_lazy_collect, shared: true do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.send(@method) {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "keeps size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) {}.size.should == 100
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method, &:succ).first(3).should == [1, 2, 3]
+
+ @eventsmixed.send(@method) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with initial values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.send(@method) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields
+ end
+
+ describe "on a nested Lazy" do
+ it "keeps size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) {}.send(@method) {}.size.should == 100
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method, &:succ).send(@method, &:succ).first(3).should == [2, 3, 4]
+
+ @eventsmixed.send(@method) {}.send(@method) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.send(@method) { |n| n }.first(100).should ==
+ s.first(100).send(@method) { |n| n }.to_a
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb b/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb
new file mode 100644
index 0000000000..685e6d0594
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/shared/collect_concat.rb
@@ -0,0 +1,78 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :enumerator_lazy_collect_concat, shared: true do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.send(@method) {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s }.first(6).should == %w[0 10 20 30 40 50]
+
+ @eventsmixed.send(@method) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "flattens elements when the given block returned an array or responding to .each and .force" do
+ (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.chars }.first(6).should == %w[0 1 0 2 0 3]
+ (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should == true
+ (0..Float::INFINITY).lazy.send(@method) { |n| (n * 10).to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3]
+ end
+ end
+
+ it "calls the block with initial values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.send(@method) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.send(@method) }.should.raise(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.send(@method) {}.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s }.first(6).should == %w[0 10 20 30 40 50]
+
+ @eventsmixed.send(@method) {}.send(@method) {}.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "flattens elements when the given block returned an array or responding to .each and .force" do
+ (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.chars }.first(6).should == %w[0 1 0 2 0 3]
+ (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.each_char }.first(6).all? { |o| o.instance_of? Enumerator }.should == true
+ (0..Float::INFINITY).lazy.map {|n| n * 10 }.send(@method) { |n| n.to_s.each_char.lazy }.first(6).should == %w[0 1 0 2 0 3]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.send(@method) { |n| [-n, +n] }.first(200).should ==
+ s.first(100).send(@method) { |n| [-n, +n] }.to_a
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/shared/select.rb b/spec/ruby/core/enumerator/lazy/shared/select.rb
new file mode 100644
index 0000000000..2d15176620
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/shared/select.rb
@@ -0,0 +1,66 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :enumerator_lazy_select, shared: true do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.send(@method) {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method, &:even?).first(3).should == [0, 2, 4]
+
+ @eventsmixed.send(@method) { true }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.send(@method) { |v| yields << v }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.gathered_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.send(@method) }.should.raise(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(50) {}.send(@method) { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.send(@method) { |n| n > 5 }.send(@method, &:even?).first(3).should == [6, 8, 10]
+
+ @eventsmixed.send(@method) { true }.send(@method) { true }.first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.send(@method) { |n| true }.first(100).should ==
+ s.first(100).send(@method) { |n| true }
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/shared/to_enum.rb b/spec/ruby/core/enumerator/lazy/shared/to_enum.rb
new file mode 100644
index 0000000000..75b9aefe8c
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/shared/to_enum.rb
@@ -0,0 +1,55 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../../spec_helper'
+
+describe :enumerator_lazy_to_enum, shared: true do
+ before :each do
+ @infinite = (0..Float::INFINITY).lazy
+ end
+
+ it "requires multiple arguments" do
+ Enumerator::Lazy.instance_method(@method).arity.should < 0
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @infinite.send @method
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@infinite)
+ end
+
+ it "sets #size to nil when not given a block" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method).size.should == nil
+ end
+
+ it "sets given block to size when given a block" do
+ Enumerator::Lazy.new(Object.new, 100) {}.send(@method) { 30 }.size.should == 30
+ end
+
+ it "generates a lazy enumerator from the given name" do
+ @infinite.send(@method, :with_index, 10).first(3).should == [[0, 10], [1, 11], [2, 12]]
+ end
+
+ it "passes given arguments to wrapped method" do
+ @infinite.send(@method, :each_slice, 2).map { |assoc| assoc.first * assoc.last }.first(4).should == [0, 6, 20, 42]
+ end
+
+ it "used by some parent's methods though returning Lazy" do
+ { each_with_index: [],
+ with_index: [],
+ cycle: [1],
+ each_with_object: [Object.new],
+ with_object: [Object.new],
+ each_slice: [2],
+ each_entry: [],
+ each_cons: [2]
+ }.each_pair do |method, args|
+ @infinite.send(method, *args).should.instance_of?(Enumerator::Lazy)
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.send(@method, :with_index).first(100).should ==
+ s.first(100).to_enum.send(@method, :with_index).to_a
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/slice_after_spec.rb b/spec/ruby/core/enumerator/lazy/slice_after_spec.rb
new file mode 100644
index 0000000000..f8cd97178f
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/slice_after_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#slice_after" do
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_after { |n| true }.first(100).should ==
+ s.first(100).slice_after { |n| true }.to_a
+ end
+
+ it "should return a lazy enumerator" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_after { |n| true }.should.is_a?(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/slice_before_spec.rb b/spec/ruby/core/enumerator/lazy/slice_before_spec.rb
new file mode 100644
index 0000000000..192e853343
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/slice_before_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#slice_before" do
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_before { |n| true }.first(100).should ==
+ s.first(100).slice_before { |n| true }.to_a
+ end
+
+ it "should return a lazy enumerator" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_before { |n| true }.should.is_a?(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/slice_when_spec.rb b/spec/ruby/core/enumerator/lazy/slice_when_spec.rb
new file mode 100644
index 0000000000..fc9d5f5069
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/slice_when_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Lazy#slice_when" do
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_when { |a, b| true }.first(100).should ==
+ s.first(100).slice_when { |a, b| true }.to_a
+ end
+
+ it "should return a lazy enumerator" do
+ s = 0..Float::INFINITY
+ s.lazy.slice_when { |a, b| true }.should.is_a?(Enumerator::Lazy)
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/take_spec.rb b/spec/ruby/core/enumerator/lazy/take_spec.rb
new file mode 100644
index 0000000000..2dd5b939e2
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/take_spec.rb
@@ -0,0 +1,74 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../enumerable/shared/value_packing'
+
+describe "Enumerator::Lazy#take" do
+ describe "value packing of source yields (matches Enumerable#take)" do
+ before :each do
+ @take = -> e { e.lazy.take(1) }
+ end
+ it_behaves_like :enumerable_value_packing, nil
+ end
+
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.take(1)
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets given count to size if the given count is less than old size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).size.should == 20
+ Enumerator::Lazy.new(Object.new, 100) {}.take(200).size.should == 100
+ end
+
+ it "sets given count to size if the old size is Infinity" do
+ loop.lazy.take(20).size.should == 20
+ end
+
+ describe "when the returned lazy enumerator is evaluated by .force" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.take(2).force.should == [0, 1]
+
+ @eventsmixed.take(1).force
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops without iterations if the given argument is 0" do
+ @eventsmixed.take(0).force
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ describe "on a nested Lazy" do
+ it "sets given count to size if the given count is less than old size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).take(50).size.should == 20
+ Enumerator::Lazy.new(Object.new, 100) {}.take(50).take(20).size.should == 20
+ end
+
+ describe "when the returned lazy enumerator is evaluated by .force" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.map(&:succ).take(2).force.should == [1, 2]
+
+ @eventsmixed.take(10).take(1).force
+ ScratchPad.recorded.should == [:before_yield]
+ end
+
+ it "stops without iterations if the given argument is 0" do
+ @eventsmixed.take(10).take(0).force
+ ScratchPad.recorded.should == []
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/take_while_spec.rb b/spec/ruby/core/enumerator/lazy/take_while_spec.rb
new file mode 100644
index 0000000000..c369712c56
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/take_while_spec.rb
@@ -0,0 +1,60 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#take_while" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.take_while {}
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take_while { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by .force" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.take_while { |n| n < 3 }.force.should == [0, 1, 2]
+
+ @eventsmixed.take_while { false }.force
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with initial values when yield with multiple arguments" do
+ yields = []
+ @yieldsmixed.take_while { |v| yields << v; true }.force
+ yields.should == EnumeratorLazySpecs::YieldsMixed.initial_yields
+ end
+
+ it "raises an ArgumentError when not given a block" do
+ -> { @yieldsmixed.take_while }.should.raise(ArgumentError)
+ end
+
+ describe "on a nested Lazy" do
+ it "sets #size to nil" do
+ Enumerator::Lazy.new(Object.new, 100) {}.take(20).take_while { true }.size.should == nil
+ end
+
+ describe "when the returned lazy enumerator is evaluated by .force" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.take_while { |n| n < 3 }.take_while(&:even?).force.should == [0]
+
+ @eventsmixed.take_while { true }.take_while { false }.force
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/to_enum_spec.rb b/spec/ruby/core/enumerator/lazy/to_enum_spec.rb
new file mode 100644
index 0000000000..210e5294b7
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/to_enum_spec.rb
@@ -0,0 +1,8 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'shared/to_enum'
+
+describe "Enumerator::Lazy#to_enum" do
+ it_behaves_like :enumerator_lazy_to_enum, :to_enum
+end
diff --git a/spec/ruby/core/enumerator/lazy/uniq_spec.rb b/spec/ruby/core/enumerator/lazy/uniq_spec.rb
new file mode 100644
index 0000000000..d30ed8df2f
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/uniq_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe 'Enumerator::Lazy#uniq' do
+ context 'without block' do
+ before :each do
+ @lazy = [0, 1, 0, 1].to_enum.lazy.uniq
+ end
+
+ it 'returns a lazy enumerator' do
+ @lazy.should.instance_of?(Enumerator::Lazy)
+ @lazy.force.should == [0, 1]
+ end
+
+ it 'return same value after rewind' do
+ @lazy.force.should == [0, 1]
+ @lazy.force.should == [0, 1]
+ end
+
+ it 'sets the size to nil' do
+ @lazy.size.should == nil
+ end
+ end
+
+ context 'when yielded with an argument' do
+ before :each do
+ @lazy = [0, 1, 2, 3].to_enum.lazy.uniq(&:even?)
+ end
+
+ it 'returns a lazy enumerator' do
+ @lazy.should.instance_of?(Enumerator::Lazy)
+ @lazy.force.should == [0, 1]
+ end
+
+ it 'return same value after rewind' do
+ @lazy.force.should == [0, 1]
+ @lazy.force.should == [0, 1]
+ end
+
+ it 'sets the size to nil' do
+ @lazy.size.should == nil
+ end
+ end
+
+ context 'when yielded with multiple arguments' do
+ before :each do
+ enum = Object.new.to_enum
+ class << enum
+ def each
+ yield 0, 'foo'
+ yield 1, 'FOO'
+ yield 2, 'bar'
+ end
+ end
+ @lazy = enum.lazy
+ end
+
+ it 'return same value after rewind' do
+ enum = @lazy.uniq { |_, label| label.downcase }
+ enum.force.should == [[0, 'foo'], [2, 'bar']]
+ enum.force.should == [[0, 'foo'], [2, 'bar']]
+ end
+
+ it 'returns all yield arguments as an array' do
+ @lazy.uniq { |_, label| label.downcase }.force.should == [[0, 'foo'], [2, 'bar']]
+ end
+ end
+
+ it "works with an infinite enumerable" do
+ s = 0..Float::INFINITY
+ s.lazy.uniq.first(100).should ==
+ s.first(100).uniq
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/with_index_spec.rb b/spec/ruby/core/enumerator/lazy/with_index_spec.rb
new file mode 100644
index 0000000000..2e983fd3b1
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/with_index_spec.rb
@@ -0,0 +1,36 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#with_index" do
+ it "enumerates with an index" do
+ (0..Float::INFINITY).lazy.with_index.map { |i, idx| [i, idx] }.first(3).should == [[0, 0], [1, 1], [2, 2]]
+ end
+
+ it "enumerates with an index starting at a given offset" do
+ (0..Float::INFINITY).lazy.with_index(3).map { |i, idx| [i, idx] }.first(3).should == [[0, 3], [1, 4], [2, 5]]
+ end
+
+ it "enumerates with an index starting at 0 when offset is nil" do
+ (0..Float::INFINITY).lazy.with_index(nil).map { |i, idx| [i, idx] }.first(3).should == [[0, 0], [1, 1], [2, 2]]
+ end
+
+ it "raises TypeError when offset does not convert to Integer" do
+ -> { (0..Float::INFINITY).lazy.with_index(false).map { |i, idx| i }.first(3) }.should.raise(TypeError)
+ end
+
+ it "enumerates with a given block" do
+ result = []
+ (0..Float::INFINITY).lazy.with_index { |i, idx| result << [i * 2, idx] }.first(3)
+ result.should == [[0,0],[2,1],[4,2]]
+ end
+
+ it "resets after a new call to each" do
+ enum = (0..2).lazy.with_index.map { |i, idx| [i, idx] }
+ result = []
+ enum.each { |i, idx| result << [i, idx] }
+ enum.each { |i, idx| result << [i, idx] }
+ result.should == [[0,0], [1,1], [2,2], [0,0], [1,1], [2,2]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/lazy/zip_spec.rb b/spec/ruby/core/enumerator/lazy/zip_spec.rb
new file mode 100644
index 0000000000..9f612542d7
--- /dev/null
+++ b/spec/ruby/core/enumerator/lazy/zip_spec.rb
@@ -0,0 +1,86 @@
+# -*- encoding: us-ascii -*-
+
+require_relative '../../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Enumerator::Lazy#zip" do
+ before :each do
+ @yieldsmixed = EnumeratorLazySpecs::YieldsMixed.new.to_enum.lazy
+ @eventsmixed = EnumeratorLazySpecs::EventsMixed.new.to_enum.lazy
+ ScratchPad.record []
+ end
+
+ after :each do
+ ScratchPad.clear
+ end
+
+ it "returns a new instance of Enumerator::Lazy" do
+ ret = @yieldsmixed.zip []
+ ret.should.instance_of?(Enumerator::Lazy)
+ ret.should_not.equal?(@yieldsmixed)
+ end
+
+ it "keeps size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.zip([], []).size.should == 100
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.zip([4, 5], [8]).first(2).should == [[0, 4, 8], [1, 5, nil]]
+
+ @eventsmixed.zip([0, 1]).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+
+ it "calls the block with a gathered array when yield with multiple arguments" do
+ yields = @yieldsmixed.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum).force
+ yields.should == [EnumeratorLazySpecs::YieldsMixed.gathered_yields,
+ EnumeratorLazySpecs::YieldsMixed.gathered_yields].transpose
+ end
+
+ it "returns a Lazy when no arguments given" do
+ @yieldsmixed.zip.should.instance_of?(Enumerator::Lazy)
+ end
+
+ it "raises a TypeError if arguments contain non-list object" do
+ -> { @yieldsmixed.zip [], Object.new, [] }.should.raise(TypeError)
+ end
+
+ describe "on a nested Lazy" do
+ it "keeps size" do
+ Enumerator::Lazy.new(Object.new, 100) {}.map {}.zip([], []).size.should == 100
+ end
+
+ it "behaves as Enumerable#zip when given a block" do
+ lazy_yields = []
+ lazy_ret = @yieldsmixed.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum) { |lists| lazy_yields << lists }
+ enum_yields = []
+ enum_ret = EnumeratorLazySpecs::YieldsMixed.new.to_enum.zip(EnumeratorLazySpecs::YieldsMixed.new.to_enum) { |lists| enum_yields << lists }
+
+ lazy_yields.should == enum_yields
+ lazy_ret.should == enum_ret
+ end
+
+ describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
+ it "stops after specified times" do
+ (0..Float::INFINITY).lazy.map(&:succ).zip([4, 5], [8]).first(2).should == [[1, 4, 8], [2, 5, nil]]
+
+ @eventsmixed.zip([0, 1]).zip([0, 1]).first(1)
+ ScratchPad.recorded.should == [:before_yield]
+ end
+ end
+ end
+
+ it "works with an infinite enumerable and an array" do
+ s = 0..Float::INFINITY
+ s.lazy.zip(0..1000).first(100).should ==
+ s.first(100).zip(0..100)
+ end
+
+ it "works with two infinite enumerables" do
+ s = 0..Float::INFINITY
+ s.lazy.zip(s).first(100).should ==
+ s.first(100).zip(s)
+ end
+end
diff --git a/spec/ruby/core/enumerator/new_spec.rb b/spec/ruby/core/enumerator/new_spec.rb
new file mode 100644
index 0000000000..eb6c13759e
--- /dev/null
+++ b/spec/ruby/core/enumerator/new_spec.rb
@@ -0,0 +1,115 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator.new" do
+ context "no block given" do
+ it "raises" do
+ -> { Enumerator.new(1, :upto, 3) }.should.raise(ArgumentError)
+ end
+ end
+
+ context "when passed a block" do
+ it "defines iteration with block, yielder argument and calling << method" do
+ enum = Enumerator.new do |yielder|
+ a = 1
+
+ loop do
+ yielder << a
+ a = a + 1
+ end
+ end
+
+ enum.take(3).should == [1, 2, 3]
+ end
+
+ it "defines iteration with block, yielder argument and calling yield method" do
+ enum = Enumerator.new do |yielder|
+ a = 1
+
+ loop do
+ yielder.yield(a)
+ a = a + 1
+ end
+ end
+
+ enum.take(3).should == [1, 2, 3]
+ end
+
+ it "defines iteration with block, yielder argument and treating it as a proc" do
+ enum = Enumerator.new do |yielder|
+ "a\nb\nc".each_line(&yielder)
+ end
+
+ enum.to_a.should == ["a\n", "b\n", "c"]
+ end
+
+ describe '#yield' do
+ it 'accepts a single argument' do
+ Enumerator.new { |y| y.yield(1) }.to_a.should == [1]
+ Enumerator.new { |y| y.yield(1) }.first.should == 1
+ end
+
+ it 'accepts multiple arguments' do
+ Enumerator.new { |y| y.yield(1, 2) }.to_a.should == [[1, 2]]
+ Enumerator.new { |y| y.yield(1, 2) }.first.should == [1, 2]
+ end
+
+ it "doesn't double-wrap arrays" do
+ Enumerator.new { |y| y.yield([1]) }.to_a.should == [[1]]
+ Enumerator.new { |y| y.yield([1]) }.first.should == [1]
+
+ Enumerator.new { |y| y.yield([1, 2]) }.to_a.should == [[1, 2]]
+ Enumerator.new { |y| y.yield([1, 2]) }.first.should == [1, 2]
+ end
+
+ it 'returns nil' do
+ ScratchPad.record []
+ Enumerator.new do |y|
+ ScratchPad << y.yield(1)
+ end.to_a
+
+ ScratchPad.recorded.should == [nil]
+ end
+
+ it 'accepts keyword arguments and treats them as a positional hash' do
+ Enumerator.new { |y| y.yield(foo: 42) }.to_a.should == [{ foo: 42 }]
+ Enumerator.new { |y| y.yield(foo: 42) }.first.should == { foo: 42 }
+
+ Enumerator.new { |y| y.yield(123, foo: 42) }.to_a.should == [[123, { foo: 42 }]]
+ Enumerator.new { |y| y.yield(123, foo: 42) }.first.should == [123, { foo: 42 }]
+ end
+ end
+
+ describe '#<<' do
+ it 'accepts a single argument' do
+ Enumerator.new { |y| y.<<(1) }.to_a.should == [1]
+ Enumerator.new { |y| y.<<(1) }.first.should == 1
+ end
+
+ it "doesn't double-wrap arrays" do
+ Enumerator.new { |y| y.<<([1]) }.to_a.should == [[1]]
+ Enumerator.new { |y| y.<<([1]) }.first.should == [1]
+
+ Enumerator.new { |y| y.<<([1, 2]) }.to_a.should == [[1, 2]]
+ Enumerator.new { |y| y.<<([1, 2]) }.first.should == [1, 2]
+ end
+
+ it 'accepts keyword arguments and treats them as a positional hash' do
+ Enumerator.new { |y| y.<<(foo: 42) }.to_a.should == [{ foo: 42 }]
+ Enumerator.new { |y| y.<<(foo: 42) }.first.should == { foo: 42 }
+ end
+
+ it 'can be chained' do
+ enum = Enumerator.new do |y|
+ y << 1 << 2
+ end
+ enum.to_a.should == [1, 2]
+ end
+
+ it 'raises ArgumentError when given more than one argument' do
+ -> {
+ Enumerator.new { |y| y.<<(1, 2) }.to_a
+ }.should.raise(ArgumentError, "wrong number of arguments (given 2, expected 1)")
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/next_spec.rb b/spec/ruby/core/enumerator/next_spec.rb
new file mode 100644
index 0000000000..77e79185a9
--- /dev/null
+++ b/spec/ruby/core/enumerator/next_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#next" do
+ before :each do
+ @enum = 1.upto(3)
+ end
+
+ it "returns the next element of the enumeration" do
+ @enum.next.should == 1
+ @enum.next.should == 2
+ @enum.next.should == 3
+ end
+
+ it "raises a StopIteration exception at the end of the stream" do
+ 3.times { @enum.next }
+ -> { @enum.next }.should.raise(StopIteration)
+ end
+
+ it "cannot be called again until the enumerator is rewound" do
+ 3.times { @enum.next }
+ -> { @enum.next }.should.raise(StopIteration)
+ -> { @enum.next }.should.raise(StopIteration)
+ -> { @enum.next }.should.raise(StopIteration)
+ @enum.rewind
+ @enum.next.should == 1
+ end
+
+ it "restarts the enumerator if an exception terminated a previous iteration" do
+ exception = StandardError.new
+ enum = Enumerator.new do
+ raise exception
+ end
+
+ result = 2.times.map { enum.next rescue $! }
+
+ result.should == [exception, exception]
+ end
+end
diff --git a/spec/ruby/core/enumerator/next_values_spec.rb b/spec/ruby/core/enumerator/next_values_spec.rb
new file mode 100644
index 0000000000..63e024e2b4
--- /dev/null
+++ b/spec/ruby/core/enumerator/next_values_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#next_values" do
+ before :each do
+ o = Object.new
+ def o.each
+ yield :a
+ yield :b1, :b2
+ yield :c
+ yield :d1, :d2
+ yield :e1, :e2, :e3
+ yield nil
+ yield
+ yield [:f1, :f2]
+ end
+
+ @e = o.to_enum
+ end
+
+ it "returns the next element in self" do
+ @e.next_values.should == [:a]
+ end
+
+ it "advances the position of the current element" do
+ @e.next.should == :a
+ @e.next_values.should == [:b1, :b2]
+ @e.next.should == :c
+ end
+
+ it "advances the position of the enumerator each time when called multiple times" do
+ 2.times { @e.next_values }
+ @e.next_values.should == [:c]
+ @e.next.should == [:d1, :d2]
+ end
+
+ it "works in concert with #rewind" do
+ 2.times { @e.next }
+ @e.rewind
+ @e.next_values.should == [:a]
+ end
+
+ it "returns an array with only nil if yield is called with nil" do
+ 5.times { @e.next }
+ @e.next_values.should == [nil]
+ end
+
+ it "returns an empty array if yield is called without arguments" do
+ 6.times { @e.next }
+ @e.next_values.should == []
+ end
+
+ it "returns an array of array if yield is called with an array" do
+ 7.times { @e.next }
+ @e.next_values.should == [[:f1, :f2]]
+ end
+
+ it "raises StopIteration if called on a finished enumerator" do
+ 8.times { @e.next }
+ -> { @e.next_values }.should.raise(StopIteration)
+ end
+end
diff --git a/spec/ruby/core/enumerator/peek_spec.rb b/spec/ruby/core/enumerator/peek_spec.rb
new file mode 100644
index 0000000000..096fd2b10c
--- /dev/null
+++ b/spec/ruby/core/enumerator/peek_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#peek" do
+ before :each do
+ @e = (1..5).to_a.to_enum
+ end
+
+ it "returns the next element in self" do
+ @e.peek.should == 1
+ end
+
+ it "does not advance the position of the current element" do
+ @e.next.should == 1
+ @e.peek.should == 2
+ @e.next.should == 2
+ end
+
+ it "can be called repeatedly without advancing the position of the current element" do
+ @e.peek
+ @e.peek
+ @e.peek.should == 1
+ @e.next.should == 1
+ end
+
+ it "works in concert with #rewind" do
+ @e.next
+ @e.next
+ @e.rewind
+ @e.peek.should == 1
+ end
+
+ it "raises StopIteration if called on a finished enumerator" do
+ 5.times { @e.next }
+ -> { @e.peek }.should.raise(StopIteration)
+ end
+end
diff --git a/spec/ruby/core/enumerator/peek_values_spec.rb b/spec/ruby/core/enumerator/peek_values_spec.rb
new file mode 100644
index 0000000000..63f7988bcd
--- /dev/null
+++ b/spec/ruby/core/enumerator/peek_values_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#peek_values" do
+ before :each do
+ o = Object.new
+ def o.each
+ yield :a
+ yield :b1, :b2
+ yield :c
+ yield :d1, :d2
+ yield :e1, :e2, :e3
+ yield nil
+ yield
+ yield [:f1, :f2]
+ end
+
+ @e = o.to_enum
+ end
+
+ it "returns the next element in self" do
+ @e.peek_values.should == [:a]
+ end
+
+ it "does not advance the position of the current element" do
+ @e.next.should == :a
+ @e.peek_values.should == [:b1, :b2]
+ @e.next.should == [:b1, :b2]
+ end
+
+ it "can be called repeatedly without advancing the position of the current element" do
+ @e.peek_values
+ @e.peek_values
+ @e.peek_values.should == [:a]
+ @e.next.should == :a
+ end
+
+ it "works in concert with #rewind" do
+ @e.next
+ @e.next
+ @e.rewind
+ @e.peek_values.should == [:a]
+ end
+
+ it "returns an array with only nil if yield is called with nil" do
+ 5.times { @e.next }
+ @e.peek_values.should == [nil]
+ end
+
+ it "returns an empty array if yield is called without arguments" do
+ 6.times { @e.next }
+ @e.peek_values.should == []
+ end
+
+ it "returns an array of array if yield is called with an array" do
+ 7.times { @e.next }
+ @e.peek_values.should == [[:f1, :f2]]
+ end
+
+ it "raises StopIteration if called on a finished enumerator" do
+ 8.times { @e.next }
+ -> { @e.peek_values }.should.raise(StopIteration)
+ end
+end
diff --git a/spec/ruby/core/enumerator/plus_spec.rb b/spec/ruby/core/enumerator/plus_spec.rb
new file mode 100644
index 0000000000..d6c0fa93ac
--- /dev/null
+++ b/spec/ruby/core/enumerator/plus_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#+" do
+ before :each do
+ ScratchPad.record []
+ end
+
+ it "returns a chain of self and provided enumerators" do
+ one = Enumerator.new { |y| y << 1 }
+ two = Enumerator.new { |y| y << 2 }
+ three = Enumerator.new { |y| y << 3 }
+
+ chain = one + two + three
+
+ chain.should.instance_of?(Enumerator::Chain)
+ chain.each { |item| ScratchPad << item }
+ ScratchPad.recorded.should == [1, 2, 3]
+ end
+
+ it "calls #each on each argument" do
+ enum = Enumerator.new { |y| y << "one" }
+
+ obj1 = mock("obj1")
+ obj1.should_receive(:each).once.and_yield("two")
+
+ obj2 = mock("obj2")
+ obj2.should_receive(:each).once.and_yield("three")
+
+ chain = enum + obj1 + obj2
+ chain.each { |item| ScratchPad << item }
+ ScratchPad.recorded.should == ["one", "two", "three"]
+ end
+end
diff --git a/spec/ruby/core/enumerator/produce_spec.rb b/spec/ruby/core/enumerator/produce_spec.rb
new file mode 100644
index 0000000000..eb1b09294e
--- /dev/null
+++ b/spec/ruby/core/enumerator/produce_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator.produce" do
+ it "creates an infinite enumerator" do
+ enum = Enumerator.produce(0) { |prev| prev + 1 }
+
+ enum.size.should == Float::INFINITY
+ enum.take(5).should == [0, 1, 2, 3, 4]
+ end
+
+ it "terminates iteration when block raises StopIteration exception" do
+ enum = Enumerator.produce(0) do | prev|
+ raise StopIteration if prev >= 2
+ prev + 1
+ end
+
+ enum.to_a.should == [0, 1, 2]
+ end
+
+ context "when initial value skipped" do
+ it "uses nil instead" do
+ ScratchPad.record []
+ enum = Enumerator.produce { |prev| ScratchPad << prev; (prev || 0) + 1 }
+
+ enum.take(3).should == [1, 2, 3]
+ ScratchPad.recorded.should == [nil, 1, 2]
+ end
+
+ it "starts enumerable from result of first block call" do
+ array = "a\nb\nc\nd".lines
+ lines = Enumerator.produce { array.shift }.take_while { |s| s }
+
+ lines.should == ["a\n", "b\n", "c\n", "d"]
+ end
+ end
+
+ it "raises ArgumentError when no block is given" do
+ -> { Enumerator.produce }.should.raise(ArgumentError, "no block given")
+ end
+
+ ruby_version_is ""..."4.0" do
+ it "accepts keyword arguments as the initial value" do
+ enum = Enumerator.produce(a: 1, b: 1) {}
+ enum.take(1).should == [{a: 1, b: 1}]
+ end
+ end
+
+ ruby_version_is "4.0" do
+ it "raises ArgumentError for unknown keyword arguments" do
+ -> { Enumerator.produce(a: 1, b: 1) {} }.should.raise(ArgumentError, /unknown keywords/)
+ end
+ end
+
+ ruby_version_is "4.0" do
+ context "with size keyword argument" do
+ it "sets the size of the enumerator" do
+ enum = Enumerator.produce(0, size: 10) { |n| n + 1 }
+
+ enum.size.should == 10
+ enum.take(5).should == [0, 1, 2, 3, 4]
+ end
+
+ it "accepts a callable" do
+ enum = Enumerator.produce(0, size: -> { 5 * 5 }) { |n| n + 1 }
+
+ enum.size.should == 25
+ enum.take(5).should == [0, 1, 2, 3, 4]
+ end
+
+ it "accepts nil" do
+ enum = Enumerator.produce(0, size: nil) { |n| n + 1 }
+
+ enum.size.should == nil
+ enum.take(5).should == [0, 1, 2, 3, 4]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/product/each_spec.rb b/spec/ruby/core/enumerator/product/each_spec.rb
new file mode 100644
index 0000000000..a5dced4db1
--- /dev/null
+++ b/spec/ruby/core/enumerator/product/each_spec.rb
@@ -0,0 +1,85 @@
+require_relative '../../../spec_helper'
+require_relative '../../enumerable/shared/enumeratorized'
+
+describe "Enumerator::Product#each" do
+ it_behaves_like :enumeratorized_with_origin_size, :each, Enumerator::Product.new([1, 2], [:a, :b])
+
+ it "yields each element of Cartesian product of enumerators" do
+ enum = Enumerator::Product.new([1, 2], [:a, :b])
+ acc = []
+ enum.each { |e| acc << e }
+ acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ end
+
+ it "calls #each_entry method on enumerators" do
+ object1 = Object.new
+ def object1.each_entry
+ yield 1
+ yield 2
+ end
+
+ object2 = Object.new
+ def object2.each_entry
+ yield :a
+ yield :b
+ end
+
+ enum = Enumerator::Product.new(object1, object2)
+ acc = []
+ enum.each { |e| acc << e }
+ acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ end
+
+ it "raises a NoMethodError if the object doesn't respond to #each_entry" do
+ -> {
+ Enumerator::Product.new(Object.new).each {}
+ }.should.raise(NoMethodError, /undefined method [`']each_entry' for/)
+ end
+
+ it "returns enumerator if not given a block" do
+ enum = Enumerator::Product.new([1, 2], [:a, :b])
+ enum.each.should.kind_of?(Enumerator)
+
+ enum = Enumerator::Product.new([1, 2], [:a, :b])
+ enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ end
+
+ it "returns self if given a block" do
+ enum = Enumerator::Product.new([1, 2], [:a, :b])
+ enum.each {}.should.equal?(enum)
+ end
+
+ it "doesn't accept arguments" do
+ Enumerator::Product.instance_method(:each).arity.should == 0
+ end
+
+ it "yields each element to a block that takes multiple arguments" do
+ enum = Enumerator::Product.new([1, 2], [:a, :b])
+
+ acc = []
+ enum.each { |x, y| acc << x }
+ acc.should == [1, 1, 2, 2]
+
+ acc = []
+ enum.each { |x, y| acc << y }
+ acc.should == [:a, :b, :a, :b]
+
+ acc = []
+ enum.each { |x, y, z| acc << z }
+ acc.should == [nil, nil, nil, nil]
+ end
+
+ it "yields no element when any enumerable is empty" do
+ enum = Enumerator::Product.new([], [1])
+
+ acc = []
+ enum.each { |x| acc << x }
+ acc.should == []
+
+ enum = Enumerator::Product.new([1], [])
+
+ acc = []
+ enum.each { |x| acc << x }
+ acc.should == []
+ end
+end
diff --git a/spec/ruby/core/enumerator/product/initialize_copy_spec.rb b/spec/ruby/core/enumerator/product/initialize_copy_spec.rb
new file mode 100644
index 0000000000..b5d2b345a9
--- /dev/null
+++ b/spec/ruby/core/enumerator/product/initialize_copy_spec.rb
@@ -0,0 +1,52 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Product#initialize_copy" do
+ it "replaces content of the receiver with content of the other object" do
+ enum = Enumerator::Product.new([true, false])
+ enum2 = Enumerator::Product.new([1, 2], [:a, :b])
+
+ enum.send(:initialize_copy, enum2)
+ enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ end
+
+ it "returns self" do
+ enum = Enumerator::Product.new([true, false])
+ enum2 = Enumerator::Product.new([1, 2], [:a, :b])
+
+ enum.send(:initialize_copy, enum2).should.equal?(enum)
+ end
+
+ it "is a private method" do
+ Enumerator::Product.private_instance_methods(false).should.include?(:initialize_copy)
+ end
+
+ it "does nothing if the argument is the same as the receiver" do
+ enum = Enumerator::Product.new(1..2)
+ enum.send(:initialize_copy, enum).should.equal?(enum)
+
+ enum.freeze
+ enum.send(:initialize_copy, enum).should.equal?(enum)
+ end
+
+ it "raises FrozenError if the receiver is frozen" do
+ enum = Enumerator::Product.new(1..2)
+ enum2 = Enumerator::Product.new(3..4)
+
+ -> { enum.freeze.send(:initialize_copy, enum2) }.should.raise(FrozenError)
+ end
+
+ it "raises TypeError if the objects are of different class" do
+ enum = Enumerator::Product.new(1..2)
+ enum2 = Class.new(Enumerator::Product).new(3..4)
+
+ -> { enum.send(:initialize_copy, enum2) }.should.raise(TypeError, 'initialize_copy should take same class object')
+ -> { enum2.send(:initialize_copy, enum) }.should.raise(TypeError, 'initialize_copy should take same class object')
+ end
+
+ it "raises ArgumentError if the argument is not initialized yet" do
+ enum = Enumerator::Product.new(1..2)
+ enum2 = Enumerator::Product.allocate
+
+ -> { enum.send(:initialize_copy, enum2) }.should.raise(ArgumentError, 'uninitialized product')
+ end
+end
diff --git a/spec/ruby/core/enumerator/product/initialize_spec.rb b/spec/ruby/core/enumerator/product/initialize_spec.rb
new file mode 100644
index 0000000000..8814f9d3c7
--- /dev/null
+++ b/spec/ruby/core/enumerator/product/initialize_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Product#initialize" do
+ before :each do
+ @uninitialized = Enumerator::Product.allocate
+ end
+
+ it "is a private method" do
+ Enumerator::Product.private_instance_methods(false).should.include?(:initialize)
+ end
+
+ it "returns self" do
+ @uninitialized.send(:initialize).should.equal?(@uninitialized)
+ end
+
+ it "accepts many arguments" do
+ @uninitialized.send(:initialize, 0..1, 2..3, 4..5).should.equal?(@uninitialized)
+ end
+
+ it "accepts arguments that are not Enumerable nor responding to :each_entry" do
+ @uninitialized.send(:initialize, Object.new).should.equal?(@uninitialized)
+ end
+
+ describe "on frozen instance" do
+ it "raises a FrozenError" do
+ -> {
+ @uninitialized.freeze.send(:initialize, 0..1)
+ }.should.raise(FrozenError)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/product/inspect_spec.rb b/spec/ruby/core/enumerator/product/inspect_spec.rb
new file mode 100644
index 0000000000..e0d7441f26
--- /dev/null
+++ b/spec/ruby/core/enumerator/product/inspect_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Product#inspect" do
+ it "returns a String including enumerators" do
+ enum = Enumerator::Product.new([1, 2], [:a, :b])
+ enum.inspect.should == "#<Enumerator::Product: [[1, 2], [:a, :b]]>"
+ end
+
+ it "represents a recursive element with '[...]'" do
+ enum = [1, 2]
+ enum_recursive = Enumerator::Product.new(enum)
+
+ enum << enum_recursive
+ enum_recursive.inspect.should == "#<Enumerator::Product: [[1, 2, #<Enumerator::Product: ...>]]>"
+ end
+
+ it "returns a not initialized representation if #initialized is not called yet" do
+ Enumerator::Product.allocate.inspect.should == "#<Enumerator::Product: uninitialized>"
+ end
+end
diff --git a/spec/ruby/core/enumerator/product/rewind_spec.rb b/spec/ruby/core/enumerator/product/rewind_spec.rb
new file mode 100644
index 0000000000..2beffaf5c1
--- /dev/null
+++ b/spec/ruby/core/enumerator/product/rewind_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Product#rewind" do
+ before :each do
+ @enum = Enumerator::Product.new([1, 2].each.to_enum, [:a, :b].each.to_enum)
+ end
+
+ it "resets the enumerator to its initial state" do
+ @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ @enum.rewind
+ @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ end
+
+ it "returns self" do
+ @enum.rewind.should.equal? @enum
+ end
+
+ it "has no effect on a new enumerator" do
+ @enum.rewind
+ @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ end
+
+ it "has no effect if called multiple, consecutive times" do
+ @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ @enum.rewind
+ @enum.rewind
+ @enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
+ end
+
+ it "calls the enclosed object's rewind method if one exists" do
+ obj = mock('rewinder')
+ enum = Enumerator::Product.new(obj.to_enum)
+
+ obj.should_receive(:rewind)
+ enum.rewind
+ end
+
+ it "does nothing if the object doesn't have a #rewind method" do
+ obj = mock('rewinder')
+ enum = Enumerator::Product.new(obj.to_enum)
+
+ enum.rewind.should == enum
+ end
+
+ it "calls a rewind method on each enumerable in direct order" do
+ ScratchPad.record []
+
+ object1 = Object.new
+ def object1.rewind; ScratchPad << :object1; end
+
+ object2 = Object.new
+ def object2.rewind; ScratchPad << :object2; end
+
+ object3 = Object.new
+ def object3.rewind; ScratchPad << :object3; end
+
+ enum = Enumerator::Product.new(object1, object2, object3)
+ enum.rewind
+
+ ScratchPad.recorded.should == [:object1, :object2, :object3]
+ end
+end
diff --git a/spec/ruby/core/enumerator/product/size_spec.rb b/spec/ruby/core/enumerator/product/size_spec.rb
new file mode 100644
index 0000000000..0ba427af9a
--- /dev/null
+++ b/spec/ruby/core/enumerator/product/size_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../../spec_helper'
+
+describe "Enumerator::Product#size" do
+ it "returns the total size of the enumerator product calculated by multiplying the sizes of enumerables in the product" do
+ product = Enumerator::Product.new(1..2, 1..3, 1..4)
+ product.size.should == 24 # 2 * 3 * 4
+ end
+
+ it "returns nil if any enumerable reports its size as nil" do
+ enum = Object.new
+ def enum.size; nil; end
+
+ product = Enumerator::Product.new(1..2, enum)
+ product.size.should == nil
+ end
+
+ it "returns Float::INFINITY if any enumerable reports its size as Float::INFINITY" do
+ enum = Object.new
+ def enum.size; Float::INFINITY; end
+
+ product = Enumerator::Product.new(1..2, enum)
+ product.size.should == Float::INFINITY
+ end
+
+ it "returns nil if any enumerable reports its size as Float::NAN" do
+ enum = Object.new
+ def enum.size; Float::NAN; end
+
+ product = Enumerator::Product.new(1..2, enum)
+ product.size.should == nil
+ end
+
+ it "returns nil if any enumerable doesn't respond to #size" do
+ enum = Object.new
+ product = Enumerator::Product.new(1..2, enum)
+ product.size.should == nil
+ end
+
+ it "returns nil if any enumerable reports a not-convertible to Integer" do
+ enum = Object.new
+ def enum.size; :symbol; end
+
+ product = Enumerator::Product.new(1..2, enum)
+ product.size.should == nil
+ end
+
+ it "returns nil if any enumerable reports a non-Integer but convertible to Integer size" do
+ enum = Object.new
+ def enum.size; 1.0; end
+
+ product = Enumerator::Product.new(1..2, enum)
+ product.size.should == nil
+ end
+
+ ruby_version_is "3.4" do
+ it "returns zero when any enumerable reports zero" do
+ enum = Enumerator::Product.new(1...1, ["A", "B"])
+ enum.size.should == 0
+
+ enum = Enumerator::Product.new(["A", "B"], 1...1)
+ enum.size.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/product_spec.rb b/spec/ruby/core/enumerator/product_spec.rb
new file mode 100644
index 0000000000..0f37ef76e7
--- /dev/null
+++ b/spec/ruby/core/enumerator/product_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+
+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]]
+
+ enum = Enumerator.product(1..2, ["A"])
+ enum.to_a.should == [[1, "A"], [2, "A"]]
+
+ enum = Enumerator.product(1..2, ["A"], ["B"])
+ enum.to_a.should == [[1, "A", "B"], [2, "A", "B"]]
+
+ enum = Enumerator.product(2..3, ["A"], ["B"], ["C"])
+ enum.to_a.should == [[2, "A", "B", "C"], [3, "A", "B", "C"]]
+ end
+
+ it "returns an enumerator with an empty array when no arguments passed" do
+ enum = Enumerator.product
+ enum.to_a.should == [[]]
+ end
+
+ it "returns an instance of Enumerator::Product" do
+ enum = Enumerator.product
+ enum.class.should == Enumerator::Product
+ end
+
+ it "accepts infinite enumerators and returns infinite enumerator" do
+ enum = Enumerator.product(1.., ["A", "B"])
+ enum.take(5).should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"]]
+ enum.size.should == Float::INFINITY
+ end
+
+ it "accepts a block" do
+ elems = []
+ enum = Enumerator.product(1..2, ["X", "Y"]) { elems << _1 }
+
+ elems.should == [[1, "X"], [1, "Y"], [2, "X"], [2, "Y"]]
+ end
+
+ it "returns nil when a block passed" do
+ Enumerator.product(1..2) {}.should == nil
+ end
+
+ # https://bugs.ruby-lang.org/issues/19829
+ it "reject keyword arguments" do
+ -> {
+ Enumerator.product(1..3, foo: 1, bar: 2)
+ }.should.raise(ArgumentError, "unknown keywords: :foo, :bar")
+ end
+
+ it "calls only #each_entry method on arguments" do
+ object = Object.new
+ def object.each_entry
+ yield 1
+ yield 2
+ end
+
+ enum = Enumerator.product(object, ["A", "B"])
+ enum.to_a.should == [[1, "A"], [1, "B"], [2, "A"], [2, "B"]]
+ end
+
+ it "raises NoMethodError when argument doesn't respond to #each_entry" do
+ -> {
+ Enumerator.product(Object.new).to_a
+ }.should.raise(NoMethodError, /undefined method [`']each_entry' for/)
+ end
+
+ it "calls #each_entry lazily" do
+ Enumerator.product(Object.new).should.is_a?(Enumerator)
+ end
+
+ 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
+ end
+
+ Enumerator.product(['a', 'b'], enum).to_a.should == [["a", 1], ["a", 2], ["a", 3]]
+ end
+end
diff --git a/spec/ruby/core/enumerator/rewind_spec.rb b/spec/ruby/core/enumerator/rewind_spec.rb
new file mode 100644
index 0000000000..6ba0edf174
--- /dev/null
+++ b/spec/ruby/core/enumerator/rewind_spec.rb
@@ -0,0 +1,70 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Enumerator#rewind" do
+ before :each do
+ @enum = 1.upto(3)
+ end
+
+ it "resets the enumerator to its initial state" do
+ @enum.next.should == 1
+ @enum.next.should == 2
+ @enum.rewind
+ @enum.next.should == 1
+ end
+
+ it "returns self" do
+ @enum.rewind.should.equal? @enum
+ end
+
+ it "has no effect on a new enumerator" do
+ @enum.rewind
+ @enum.next.should == 1
+ end
+
+ it "has no effect if called multiple, consecutive times" do
+ @enum.next.should == 1
+ @enum.rewind
+ @enum.rewind
+ @enum.next.should == 1
+ end
+
+ it "works with peek to reset the position" do
+ @enum.next
+ @enum.next
+ @enum.rewind
+ @enum.next
+ @enum.peek.should == 2
+ end
+
+ it "calls the enclosed object's rewind method if one exists" do
+ obj = mock('rewinder')
+ enum = obj.to_enum
+ obj.should_receive(:each).at_most(1)
+ obj.should_receive(:rewind)
+ enum.rewind
+ end
+
+ it "does nothing if the object doesn't have a #rewind method" do
+ obj = mock('rewinder')
+ enum = obj.to_enum
+ obj.should_receive(:each).at_most(1)
+ enum.rewind.should == enum
+ end
+end
+
+describe "Enumerator#rewind" do
+ before :each do
+ ScratchPad.record []
+ @enum = EnumeratorSpecs::Feed.new.to_enum(:each)
+ end
+
+ it "clears a pending #feed value" do
+ @enum.next
+ @enum.feed :a
+ @enum.rewind
+ @enum.next
+ @enum.next
+ ScratchPad.recorded.should == [nil]
+ 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..4388103ecf
--- /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 == true
+ end
+
+ it "returns a new enumerator" do
+ "abc".send(@method).should.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.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..0992397e95
--- /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.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..50d4f24eb3
--- /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.instance_of?(Enumerator)
+ ret.should_not.equal?(@enum)
+ end
+ end
+end
diff --git a/spec/ruby/core/enumerator/size_spec.rb b/spec/ruby/core/enumerator/size_spec.rb
new file mode 100644
index 0000000000..4b2beffbbe
--- /dev/null
+++ b/spec/ruby/core/enumerator/size_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+describe "Enumerator#size" do
+ it "returns same value if set size is an Integer" do
+ Enumerator.new(100) {}.size.should == 100
+ end
+
+ it "returns nil if set size is nil" do
+ Enumerator.new(nil) {}.size.should == nil
+ end
+
+ it "returns returning value from size.call if set size is a Proc" do
+ base_size = 100
+ enum = Enumerator.new(-> { base_size + 1 }) {}
+ base_size = 200
+ enum.size.should == 201
+ base_size = 300
+ enum.size.should == 301
+ end
+
+ it "returns the result from size.call if the size respond to call" do
+ obj = mock('call')
+ obj.should_receive(:call).and_return(42)
+ Enumerator.new(obj) {}.size.should == 42
+ end
+end
diff --git a/spec/ruby/core/enumerator/to_enum_spec.rb b/spec/ruby/core/enumerator/to_enum_spec.rb
new file mode 100644
index 0000000000..7fb73d0c3c
--- /dev/null
+++ b/spec/ruby/core/enumerator/to_enum_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/enum_for'
+
+describe "Enumerator#to_enum" do
+ 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
new file mode 100644
index 0000000000..ca90fd18f7
--- /dev/null
+++ b/spec/ruby/core/enumerator/with_index_spec.rb
@@ -0,0 +1,89 @@
+require_relative '../../spec_helper'
+require_relative 'shared/with_index'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Enumerator#with_index" do
+ it_behaves_like :enum_with_index, :with_index
+ it_behaves_like :enumeratorized_with_origin_size, :with_index, [1,2,3].select
+
+ it "returns a new Enumerator when no block is given" do
+ enum1 = [1,2,3].select
+ enum2 = enum1.with_index
+ enum2.should.instance_of?(Enumerator)
+ enum1.should_not === enum2
+ end
+
+ it "accepts an optional argument when given a block" do
+ -> do
+ @enum.with_index(1) { |f| f}
+ end.should_not.raise(ArgumentError)
+ end
+
+ it "accepts an optional argument when not given a block" do
+ -> do
+ @enum.with_index(1)
+ end.should_not.raise(ArgumentError)
+ end
+
+ it "numbers indices from the given index when given an offset but no block" do
+ @enum.with_index(1).to_a.should == [[1,1], [2,2], [3,3], [4,4]]
+ end
+
+ it "numbers indices from the given index when given an offset and block" do
+ acc = []
+ @enum.with_index(1) {|e,i| acc << [e,i] }
+ acc.should == [[1,1], [2,2], [3,3], [4,4]]
+ end
+
+ it "raises a TypeError when the argument cannot be converted to numeric" do
+ -> do
+ @enum.with_index('1') {|*i| i}
+ end.should.raise(TypeError)
+ end
+
+ it "converts non-numeric arguments to Integer via #to_int" do
+ (o = mock('1')).should_receive(:to_int).and_return(1)
+ @enum.with_index(o).to_a.should == [[1,1], [2,2], [3,3], [4,4]]
+ end
+
+ it "coerces the given numeric argument to an Integer" do
+ @enum.with_index(1.678).to_a.should == [[1,1], [2,2], [3,3], [4,4]]
+
+ res = []
+ @enum.with_index(1.001) { |*x| res << x}
+ res.should == [[1,1], [2,2], [3,3], [4,4]]
+ end
+
+ it "treats nil argument as no argument" do
+ @enum.with_index(nil).to_a.should == [[1,0], [2,1], [3,2], [4,3]]
+
+ res = []
+ @enum.with_index(nil) { |*x| res << x}
+ res.should == [[1,0], [2,1], [3,2], [4,3]]
+ end
+
+ it "accepts negative argument" do
+ @enum.with_index(-1).to_a.should == [[1,-1], [2,0], [3,1], [4,2]]
+
+ res = []
+ @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
new file mode 100644
index 0000000000..58031fd765
--- /dev/null
+++ b/spec/ruby/core/enumerator/with_object_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/with_object'
+
+describe "Enumerator#with_object" do
+ it_behaves_like :enum_with_object, :with_object
+end
diff --git a/spec/ruby/core/env/assoc_spec.rb b/spec/ruby/core/env/assoc_spec.rb
new file mode 100644
index 0000000000..b81be7ddf2
--- /dev/null
+++ b/spec/ruby/core/env/assoc_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "ENV.assoc" do
+ before :each do
+ @foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ end
+
+ it "returns an array of the key and value of the environment variable with the given key" do
+ ENV["foo"] = "bar"
+ ENV.assoc("foo").should == ["foo", "bar"]
+ end
+
+ it "returns nil if no environment variable with the given key exists" do
+ ENV.assoc("foo").should == nil
+ end
+
+ it "returns the key element coerced with #to_str" do
+ ENV["foo"] = "bar"
+ k = mock('key')
+ k.should_receive(:to_str).and_return("foo")
+ ENV.assoc(k).should == ["foo", "bar"]
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> { ENV.assoc(Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/clear_spec.rb b/spec/ruby/core/env/clear_spec.rb
new file mode 100644
index 0000000000..c0d20193ad
--- /dev/null
+++ b/spec/ruby/core/env/clear_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "ENV.clear" do
+ it "deletes all environment variables" do
+ orig = ENV.to_hash
+ begin
+ ENV.clear.should.equal?(ENV)
+
+ # This used 'env' the helper before. That shells out to 'env' which
+ # itself sets up certain environment variables before it runs, because
+ # the shell sets them up before it runs any command.
+ #
+ # Thusly, you can ONLY test this by asking through ENV itself.
+ ENV.size.should == 0
+ ensure
+ ENV.replace orig
+ end
+ end
+
+end
diff --git a/spec/ruby/core/env/clone_spec.rb b/spec/ruby/core/env/clone_spec.rb
new file mode 100644
index 0000000000..bb3c7ff2f8
--- /dev/null
+++ b/spec/ruby/core/env/clone_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "ENV#clone" do
+ it "raises ArgumentError when keyword argument 'freeze' is neither nil nor boolean" do
+ -> {
+ ENV.clone(freeze: 1)
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError when keyword argument is not 'freeze'" do
+ -> {
+ ENV.clone(foo: nil)
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises TypeError" do
+ -> {
+ ENV.clone
+ }.should.raise(TypeError, /Cannot clone ENV, use ENV.to_h to get a copy of ENV as a hash/)
+ end
+end
diff --git a/spec/ruby/core/env/delete_if_spec.rb b/spec/ruby/core/env/delete_if_spec.rb
new file mode 100644
index 0000000000..4b05064eba
--- /dev/null
+++ b/spec/ruby/core/env/delete_if_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.delete_if" do
+ before :each do
+ @foo = ENV["foo"]
+ @bar = ENV["bar"]
+
+ ENV["foo"] = "0"
+ ENV["bar"] = "1"
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ ENV["bar"] = @bar
+ end
+
+ it "deletes pairs if the block returns true" do
+ ENV.delete_if { |k, v| ["foo", "bar"].include?(k) }
+ ENV["foo"].should == nil
+ ENV["bar"].should == nil
+ end
+
+ it "returns ENV when block given" do
+ ENV.delete_if { |k, v| ["foo", "bar"].include?(k) }.should.equal?(ENV)
+ end
+
+ it "returns ENV even if nothing deleted" do
+ ENV.delete_if { false }.should.equal?(ENV)
+ end
+
+ it "returns an Enumerator if no block given" do
+ ENV.delete_if.should.instance_of?(Enumerator)
+ end
+
+ it "deletes pairs through enumerator" do
+ enum = ENV.delete_if
+ enum.each { |k, v| ["foo", "bar"].include?(k) }
+ ENV["foo"].should == nil
+ ENV["bar"].should == nil
+ end
+
+ it "returns ENV from enumerator" do
+ enum = ENV.delete_if
+ enum.each { |k, v| ["foo", "bar"].include?(k) }.should.equal?(ENV)
+ end
+
+ it "returns ENV from enumerator even if nothing deleted" do
+ enum = ENV.delete_if
+ enum.each { false }.should.equal?(ENV)
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :delete_if, ENV
+end
diff --git a/spec/ruby/core/env/delete_spec.rb b/spec/ruby/core/env/delete_spec.rb
new file mode 100644
index 0000000000..db6d07b057
--- /dev/null
+++ b/spec/ruby/core/env/delete_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+
+describe "ENV.delete" do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "removes the variable from the environment" do
+ ENV["foo"] = "bar"
+ ENV.delete("foo")
+ ENV["foo"].should == nil
+ end
+
+ it "returns the previous value" do
+ ENV["foo"] = "bar"
+ ENV.delete("foo").should == "bar"
+ end
+
+ it "returns nil if the named environment variable does not exist and no block given" do
+ ENV.delete("foo")
+ ENV.delete("foo").should == nil
+ end
+
+ it "yields the name to the given block if the named environment variable does not exist" do
+ ENV.delete("foo")
+ ENV.delete("foo") { |name| ScratchPad.record name }
+ ScratchPad.recorded.should == "foo"
+ end
+
+ it "returns the result of given block if the named environment variable does not exist" do
+ ENV.delete("foo")
+ ENV.delete("foo") { |name| "bar" }.should == "bar"
+ end
+
+ it "does not evaluate the block if the environment variable exists" do
+ ENV["foo"] = "bar"
+ ENV.delete("foo") { |name| fail "Should not happen" }
+ ENV["foo"].should == nil
+ end
+
+ it "removes the variable coerced with #to_str" do
+ ENV["foo"] = "bar"
+ k = mock('key')
+ k.should_receive(:to_str).and_return("foo")
+ ENV.delete(k)
+ ENV["foo"].should == nil
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> { ENV.delete(Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/dup_spec.rb b/spec/ruby/core/env/dup_spec.rb
new file mode 100644
index 0000000000..d8cfac891d
--- /dev/null
+++ b/spec/ruby/core/env/dup_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "ENV#dup" do
+ it "raises TypeError" do
+ -> {
+ ENV.dup
+ }.should.raise(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/each_key_spec.rb b/spec/ruby/core/env/each_key_spec.rb
new file mode 100644
index 0000000000..ad2cb560a0
--- /dev/null
+++ b/spec/ruby/core/env/each_key_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.each_key" do
+
+ it "returns each key" do
+ e = []
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ ENV["1"] = "3"
+ ENV["2"] = "4"
+ ENV.each_key { |k| e << k }.should.equal?(ENV)
+ e.should.include?("1")
+ e.should.include?("2")
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it "returns an Enumerator if called without a block" do
+ enum = ENV.each_key
+ enum.should.instance_of?(Enumerator)
+ enum.to_a.should == ENV.keys
+ end
+
+ it "returns keys in the locale encoding" do
+ ENV.each_key do |key|
+ key.encoding.should == Encoding.find('locale')
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :each_key, ENV
+end
diff --git a/spec/ruby/core/env/each_pair_spec.rb b/spec/ruby/core/env/each_pair_spec.rb
new file mode 100644
index 0000000000..2d7ed5faa0
--- /dev/null
+++ b/spec/ruby/core/env/each_pair_spec.rb
@@ -0,0 +1,6 @@
+require_relative 'spec_helper'
+require_relative 'shared/each'
+
+describe "ENV.each_pair" do
+ it_behaves_like :env_each, :each_pair
+end
diff --git a/spec/ruby/core/env/each_spec.rb b/spec/ruby/core/env/each_spec.rb
new file mode 100644
index 0000000000..d1e06f55b6
--- /dev/null
+++ b/spec/ruby/core/env/each_spec.rb
@@ -0,0 +1,6 @@
+require_relative 'spec_helper'
+require_relative 'shared/each'
+
+describe "ENV.each" do
+ it_behaves_like :env_each, :each
+end
diff --git a/spec/ruby/core/env/each_value_spec.rb b/spec/ruby/core/env/each_value_spec.rb
new file mode 100644
index 0000000000..6f65e923e6
--- /dev/null
+++ b/spec/ruby/core/env/each_value_spec.rb
@@ -0,0 +1,34 @@
+require_relative 'spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.each_value" do
+
+ it "returns each value" do
+ e = []
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ ENV["1"] = "3"
+ ENV["2"] = "4"
+ ENV.each_value { |v| e << v }.should.equal?(ENV)
+ e.should.include?("3")
+ e.should.include?("4")
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it "returns an Enumerator if called without a block" do
+ enum = ENV.each_value
+ enum.should.instance_of?(Enumerator)
+ enum.to_a.should == ENV.values
+ end
+
+ it "uses the locale encoding" do
+ ENV.each_value do |value|
+ value.should.be_locale_env
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :each_value, ENV
+end
diff --git a/spec/ruby/core/env/element_reference_spec.rb b/spec/ruby/core/env/element_reference_spec.rb
new file mode 100644
index 0000000000..e3ac979418
--- /dev/null
+++ b/spec/ruby/core/env/element_reference_spec.rb
@@ -0,0 +1,76 @@
+# encoding: binary
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "ENV.[]" do
+ before :each do
+ @variable = "returns_only_frozen_values"
+ end
+
+ after :each do
+ ENV.delete @variable
+ end
+
+ it "returns nil if the variable isn't found" do
+ ENV["this_var_is_never_set"].should == nil
+ end
+
+ it "returns only frozen values" do
+ ENV[@variable] = "a non-frozen string"
+ ENV[@variable].should.frozen?
+ end
+
+ it "coerces a non-string name with #to_str" do
+ ENV[@variable] = "bar"
+ k = mock('key')
+ k.should_receive(:to_str).and_return(@variable)
+ ENV[k].should == "bar"
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> { ENV[Object.new] }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+
+ platform_is :windows do
+ it "looks up values case-insensitively" do
+ ENV[@variable] = "bar"
+ ENV[@variable.upcase].should == "bar"
+ end
+ end
+end
+
+describe "ENV.[]" do
+ before :each do
+ @variable = "env_element_reference_encoding_specs"
+
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::BINARY
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+
+ ENV.delete @variable
+ end
+
+ it "uses the locale encoding if Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+
+ locale = ENVSpecs.encoding
+ locale = Encoding::BINARY if locale == Encoding::US_ASCII
+ ENV[@variable] = "\xC3\xB8"
+ ENV[@variable].encoding.should == locale
+ end
+
+ it "transcodes from the locale encoding to Encoding.default_internal if set" do
+ # We cannot reliably know the locale encoding, so we merely check that
+ # the result string has the expected encoding.
+ ENV[@variable] = ""
+ Encoding.default_internal = Encoding::IBM437
+
+ ENV[@variable].encoding.should.equal?(Encoding::IBM437)
+ end
+end
diff --git a/spec/ruby/core/env/element_set_spec.rb b/spec/ruby/core/env/element_set_spec.rb
new file mode 100644
index 0000000000..26dfee1ade
--- /dev/null
+++ b/spec/ruby/core/env/element_set_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/store'
+
+describe "ENV.[]=" do
+ it_behaves_like :env_store, :[]=
+end
diff --git a/spec/ruby/core/env/empty_spec.rb b/spec/ruby/core/env/empty_spec.rb
new file mode 100644
index 0000000000..afeb406a9e
--- /dev/null
+++ b/spec/ruby/core/env/empty_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "ENV.empty?" do
+
+ it "returns true if the Environment is empty" do
+ if ENV.keys.size > 0
+ ENV.should_not.empty?
+ end
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ ENV.should.empty?
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it "returns false if not empty" do
+ if ENV.keys.size > 0
+ ENV.should_not.empty?
+ end
+ end
+end
diff --git a/spec/ruby/core/env/except_spec.rb b/spec/ruby/core/env/except_spec.rb
new file mode 100644
index 0000000000..fb8f3b7536
--- /dev/null
+++ b/spec/ruby/core/env/except_spec.rb
@@ -0,0 +1,34 @@
+require_relative 'spec_helper'
+require_relative 'shared/to_hash'
+
+describe "ENV.except" do
+ before do
+ @orig_hash = ENV.to_hash
+ end
+
+ after do
+ ENV.replace @orig_hash
+ end
+
+ # Testing the method without arguments is covered via
+ it_behaves_like :env_to_hash, :except
+
+ it "returns a hash without the requested subset" do
+ ENV.clear
+
+ ENV['one'] = '1'
+ ENV['two'] = '2'
+ ENV['three'] = '3'
+
+ ENV.except('one', 'three').should == { 'two' => '2' }
+ end
+
+ it "ignores keys not present in the original hash" do
+ ENV.clear
+
+ ENV['one'] = '1'
+ ENV['two'] = '2'
+
+ ENV.except('one', 'three').should == { 'two' => '2' }
+ end
+end
diff --git a/spec/ruby/core/env/fetch_spec.rb b/spec/ruby/core/env/fetch_spec.rb
new file mode 100644
index 0000000000..a2ec79c62b
--- /dev/null
+++ b/spec/ruby/core/env/fetch_spec.rb
@@ -0,0 +1,63 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/hash/key_error'
+require_relative 'fixtures/common'
+
+describe "ENV.fetch" do
+ before :each do
+ @foo_saved = ENV.delete("foo")
+ end
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns a value" do
+ ENV["foo"] = "bar"
+ ENV.fetch("foo").should == "bar"
+ end
+
+ it "raises a TypeError if the key is not a String" do
+ -> { ENV.fetch Object.new }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+
+ context "when the key is not found" do
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, ENV
+
+ it "formats the object with #inspect in the KeyError message" do
+ -> {
+ ENV.fetch('foo')
+ }.should.raise(KeyError, 'key not found: "foo"')
+ end
+ end
+
+ it "provides the given default parameter" do
+ ENV.fetch("foo", "default").should == "default"
+ end
+
+ it "does not insist that the default be a String" do
+ ENV.fetch("foo", :default).should == :default
+ end
+
+ it "provides a default value from a block" do
+ ENV.fetch("foo") { |k| "wanted #{k}" }.should == "wanted foo"
+ end
+
+ it "does not insist that the block return a String" do
+ ENV.fetch("foo") { |k| k.to_sym }.should == :foo
+ end
+
+ it "warns on block and default parameter given" do
+ -> do
+ ENV.fetch("foo", "default") { "bar" }.should == "bar"
+ end.should complain(/block supersedes default value argument/)
+ end
+
+ it "does not evaluate the block when key found" do
+ ENV["foo"] = "bar"
+ ENV.fetch("foo") { fail "should not get here"}.should == "bar"
+ end
+
+ it "uses the locale encoding" do
+ ENV["foo"] = "bar"
+ ENV.fetch("foo").encoding.should == ENVSpecs.encoding
+ end
+end
diff --git a/spec/ruby/core/env/fetch_values_spec.rb b/spec/ruby/core/env/fetch_values_spec.rb
new file mode 100644
index 0000000000..302cde2fd1
--- /dev/null
+++ b/spec/ruby/core/env/fetch_values_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+ruby_version_is "4.1" do
+ describe "ENV.fetch_values" do
+ before :each do
+ @saved_foo = ENV["foo"]
+ @saved_bar = ENV["bar"]
+ ENV.delete("foo")
+ ENV.delete("bar")
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ ENV["bar"] = @saved_bar
+ end
+
+ it "returns an array of the values corresponding to the given keys" do
+ ENV["foo"] = "oof"
+ ENV["bar"] = "rab"
+ ENV.fetch_values("bar", "foo").should == ["rab", "oof"]
+ end
+
+ it "returns the default value from block" do
+ ENV["foo"] = "oof"
+ ENV.fetch_values("bar") { |key| "`#{key}' is not found" }.should == ["`bar' is not found"]
+ ENV.fetch_values("bar", "foo") { |key| "`#{key}' is not found" }.should == ["`bar' is not found", "oof"]
+ end
+
+ it "returns an empty array if no keys specified" do
+ ENV.fetch_values.should == []
+ end
+
+ it "raises KeyError when there is no matching key" do
+ ENV["foo"] = "oof"
+ ENV["bar"] = "rab"
+ -> {
+ ENV.fetch_values("bar", "y", "foo", "z")
+ }.should.raise(KeyError, 'key not found: "y"')
+ end
+
+ it "uses the locale encoding" do
+ ENV.fetch_values(ENV.keys.first).first.encoding.should == ENVSpecs.encoding
+ end
+
+ it "raises TypeError when a key is not coercible to String" do
+ ENV["foo"] = "oof"
+ -> { ENV.fetch_values("foo", Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+ end
+end
diff --git a/spec/ruby/core/env/filter_spec.rb b/spec/ruby/core/env/filter_spec.rb
new file mode 100644
index 0000000000..52f8b79a0b
--- /dev/null
+++ b/spec/ruby/core/env/filter_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+require_relative 'shared/select'
+
+describe "ENV.filter!" do
+ it_behaves_like :env_select!, :filter!
+ it_behaves_like :enumeratorized_with_origin_size, :filter!, ENV
+end
+
+describe "ENV.filter" do
+ it_behaves_like :env_select, :filter
+ it_behaves_like :enumeratorized_with_origin_size, :filter, ENV
+end
diff --git a/spec/ruby/core/env/fixtures/common.rb b/spec/ruby/core/env/fixtures/common.rb
new file mode 100644
index 0000000000..8d5057614d
--- /dev/null
+++ b/spec/ruby/core/env/fixtures/common.rb
@@ -0,0 +1,9 @@
+module ENVSpecs
+ def self.encoding
+ locale = Encoding.find('locale')
+ if ruby_version_is '3' and platform_is :windows
+ locale = Encoding::UTF_8
+ end
+ locale
+ end
+end
diff --git a/spec/ruby/core/env/has_key_spec.rb b/spec/ruby/core/env/has_key_spec.rb
new file mode 100644
index 0000000000..798668105d
--- /dev/null
+++ b/spec/ruby/core/env/has_key_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+
+describe "ENV.has_key?" do
+ it_behaves_like :env_include, :has_key?
+end
diff --git a/spec/ruby/core/env/has_value_spec.rb b/spec/ruby/core/env/has_value_spec.rb
new file mode 100644
index 0000000000..a2bf3eb877
--- /dev/null
+++ b/spec/ruby/core/env/has_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/value'
+
+describe "ENV.has_value?" do
+ it_behaves_like :env_value, :has_value?
+end
diff --git a/spec/ruby/core/env/include_spec.rb b/spec/ruby/core/env/include_spec.rb
new file mode 100644
index 0000000000..3975f095ac
--- /dev/null
+++ b/spec/ruby/core/env/include_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+
+describe "ENV.include?" do
+ it_behaves_like :env_include, :include?
+end
diff --git a/spec/ruby/core/env/inspect_spec.rb b/spec/ruby/core/env/inspect_spec.rb
new file mode 100644
index 0000000000..7dd92b120f
--- /dev/null
+++ b/spec/ruby/core/env/inspect_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+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.delete "foo"
+ end
+
+end
diff --git a/spec/ruby/core/env/invert_spec.rb b/spec/ruby/core/env/invert_spec.rb
new file mode 100644
index 0000000000..c095374d95
--- /dev/null
+++ b/spec/ruby/core/env/invert_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "ENV.invert" do
+ before :each do
+ ENV["foo"] = "bar"
+ end
+
+ after :each do
+ ENV.delete "foo"
+ end
+
+ it "returns a hash with ENV.keys as the values and vice versa" do
+ ENV.invert["bar"].should == "foo"
+ ENV["foo"].should == "bar"
+ end
+end
diff --git a/spec/ruby/core/env/keep_if_spec.rb b/spec/ruby/core/env/keep_if_spec.rb
new file mode 100644
index 0000000000..f24981e216
--- /dev/null
+++ b/spec/ruby/core/env/keep_if_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.keep_if" do
+ before :each do
+ @foo = ENV["foo"]
+ @bar = ENV["bar"]
+
+ ENV["foo"] = "0"
+ ENV["bar"] = "1"
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ ENV["bar"] = @bar
+ end
+
+ it "deletes pairs if the block returns false" do
+ ENV.keep_if { |k, v| !["foo", "bar"].include?(k) }
+ ENV["foo"].should == nil
+ ENV["bar"].should == nil
+ end
+
+ it "returns ENV when block given" do
+ ENV.keep_if { |k, v| !["foo", "bar"].include?(k) }.should.equal?(ENV)
+ end
+
+ it "returns ENV even if nothing deleted" do
+ ENV.keep_if { true }.should.equal?(ENV)
+ end
+
+ it "returns an Enumerator if no block given" do
+ ENV.keep_if.should.instance_of?(Enumerator)
+ end
+
+ it "deletes pairs through enumerator" do
+ enum = ENV.keep_if
+ enum.each { |k, v| !["foo", "bar"].include?(k) }
+ ENV["foo"].should == nil
+ ENV["bar"].should == nil
+ end
+
+ it "returns ENV from enumerator" do
+ enum = ENV.keep_if
+ enum.each { |k, v| !["foo", "bar"].include?(k) }.should.equal?(ENV)
+ end
+
+ it "returns ENV from enumerator even if nothing deleted" do
+ enum = ENV.keep_if
+ enum.each { true }.should.equal?(ENV)
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :keep_if, ENV
+end
diff --git a/spec/ruby/core/env/key_spec.rb b/spec/ruby/core/env/key_spec.rb
new file mode 100644
index 0000000000..677cf35216
--- /dev/null
+++ b/spec/ruby/core/env/key_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+
+describe "ENV.key?" do
+ it_behaves_like :env_include, :key?
+end
+
+describe "ENV.key" do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns the index associated with the passed value" do
+ ENV["foo"] = "bar"
+ ENV.key("bar").should == "foo"
+ end
+
+ it "returns nil if the passed value is not found" do
+ ENV.delete("foo")
+ ENV.key("foo").should == nil
+ end
+
+ it "coerces the key element with #to_str" do
+ ENV["foo"] = "bar"
+ k = mock('key')
+ k.should_receive(:to_str).and_return("bar")
+ ENV.key(k).should == "foo"
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> {
+ ENV.key(Object.new)
+ }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/keys_spec.rb b/spec/ruby/core/env/keys_spec.rb
new file mode 100644
index 0000000000..b074a8f7c7
--- /dev/null
+++ b/spec/ruby/core/env/keys_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "ENV.keys" do
+
+ it "returns an array of the keys" do
+ ENV.keys.should == ENV.to_hash.keys
+ end
+
+ it "returns the keys in the locale encoding" do
+ ENV.keys.each do |key|
+ key.encoding.should == Encoding.find('locale')
+ end
+ end
+end
diff --git a/spec/ruby/core/env/length_spec.rb b/spec/ruby/core/env/length_spec.rb
new file mode 100644
index 0000000000..c6f9062892
--- /dev/null
+++ b/spec/ruby/core/env/length_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+
+describe "ENV.length" do
+ it_behaves_like :env_length, :length
+end
diff --git a/spec/ruby/core/env/member_spec.rb b/spec/ruby/core/env/member_spec.rb
new file mode 100644
index 0000000000..9119022ae5
--- /dev/null
+++ b/spec/ruby/core/env/member_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/include'
+
+describe "ENV.member?" do
+ it_behaves_like :env_include, :member?
+end
diff --git a/spec/ruby/core/env/merge_spec.rb b/spec/ruby/core/env/merge_spec.rb
new file mode 100644
index 0000000000..f10662cf79
--- /dev/null
+++ b/spec/ruby/core/env/merge_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/update'
+
+describe "ENV.merge!" do
+ it_behaves_like :env_update, :merge!
+end
diff --git a/spec/ruby/core/env/rassoc_spec.rb b/spec/ruby/core/env/rassoc_spec.rb
new file mode 100644
index 0000000000..e4ac5ce5e2
--- /dev/null
+++ b/spec/ruby/core/env/rassoc_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+
+describe "ENV.rassoc" do
+ before :each do
+ @foo = ENV["foo"]
+ @baz = ENV["baz"]
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ ENV["baz"] = @baz
+ end
+
+ it "returns an array of the key and value of the environment variable with the given value" do
+ ENV["foo"] = "bar"
+ ENV.rassoc("bar").should == ["foo", "bar"]
+ end
+
+ it "returns a single array even if there are multiple such environment variables" do
+ ENV["foo"] = "bar"
+ ENV["baz"] = "bar"
+ [
+ ["foo", "bar"],
+ ["baz", "bar"],
+ ].should.include?(ENV.rassoc("bar"))
+ end
+
+ it "returns nil if no environment variable with the given value exists" do
+ ENV.rassoc("bar").should == nil
+ end
+
+ it "returns the value element coerced with #to_str" do
+ ENV["foo"] = "bar"
+ v = mock('value')
+ v.should_receive(:to_str).and_return("bar")
+ ENV.rassoc(v).should == ["foo", "bar"]
+ end
+
+ it "returns nil if the argument is not a String and does not respond to #to_str" do
+ ENV.rassoc(Object.new).should == nil
+ end
+end
diff --git a/spec/ruby/core/env/rehash_spec.rb b/spec/ruby/core/env/rehash_spec.rb
new file mode 100644
index 0000000000..3782e4b727
--- /dev/null
+++ b/spec/ruby/core/env/rehash_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ENV.rehash" do
+ it "returns nil" do
+ ENV.rehash.should == nil
+ end
+end
diff --git a/spec/ruby/core/env/reject_spec.rb b/spec/ruby/core/env/reject_spec.rb
new file mode 100644
index 0000000000..4df864493d
--- /dev/null
+++ b/spec/ruby/core/env/reject_spec.rb
@@ -0,0 +1,101 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "ENV.reject!" do
+ before :each do
+ @foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ end
+
+ it "rejects entries based on key" do
+ ENV["foo"] = "bar"
+ ENV.reject! { |k, v| k == "foo" }
+ ENV["foo"].should == nil
+ end
+
+ it "rejects entries based on value" do
+ ENV["foo"] = "bar"
+ ENV.reject! { |k, v| v == "bar" }
+ ENV["foo"].should == nil
+ end
+
+ it "returns itself or nil" do
+ ENV.reject! { false }.should == nil
+ ENV["foo"] = "bar"
+ ENV.reject! { |k, v| k == "foo" }.should.equal?(ENV)
+ ENV["foo"].should == nil
+ end
+
+ it "returns an Enumerator if called without a block" do
+ ENV["foo"] = "bar"
+ enum = ENV.reject!
+ enum.should.instance_of?(Enumerator)
+ enum.each { |k, v| k == "foo" }.should.equal?(ENV)
+ ENV["foo"].should == nil
+ end
+
+ it "doesn't raise if empty" do
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ -> { ENV.reject! }.should_not.raise(LocalJumpError)
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :reject!, ENV
+end
+
+describe "ENV.reject" do
+ before :each do
+ @foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @foo
+ end
+
+ it "rejects entries based on key" do
+ ENV["foo"] = "bar"
+ e = ENV.reject { |k, v| k == "foo" }
+ e["foo"].should == nil
+ ENV["foo"].should == "bar"
+ ENV["foo"] = nil
+ end
+
+ it "rejects entries based on value" do
+ ENV["foo"] = "bar"
+ e = ENV.reject { |k, v| v == "bar" }
+ e["foo"].should == nil
+ ENV["foo"].should == "bar"
+ ENV["foo"] = nil
+ end
+
+ it "returns a Hash" do
+ ENV.reject { false }.should.is_a?(Hash)
+ end
+
+ it "returns an Enumerator if called without a block" do
+ ENV["foo"] = "bar"
+ enum = ENV.reject
+ enum.should.instance_of?(Enumerator)
+ enum.each { |k, v| k == "foo"}
+ ENV["foo"] = nil
+ end
+
+ it "doesn't raise if empty" do
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ -> { ENV.reject }.should_not.raise(LocalJumpError)
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it_behaves_like :enumeratorized_with_origin_size, :reject, ENV
+end
diff --git a/spec/ruby/core/env/replace_spec.rb b/spec/ruby/core/env/replace_spec.rb
new file mode 100644
index 0000000000..27eb3e45dd
--- /dev/null
+++ b/spec/ruby/core/env/replace_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "ENV.replace" do
+ before :each do
+ @orig = ENV.to_hash
+ ENV.delete("foo")
+ end
+
+ after :each do
+ ENV.replace(@orig)
+ end
+
+ it "replaces ENV with a Hash" do
+ ENV.replace("foo" => "0", "bar" => "1").should.equal?(ENV)
+ ENV.size.should == 2
+ ENV["foo"].should == "0"
+ ENV["bar"].should == "1"
+ end
+
+ it "raises TypeError if the argument is not a Hash" do
+ -> { ENV.replace(Object.new) }.should.raise(TypeError, "no implicit conversion of Object into Hash")
+ ENV.to_hash.should == @orig
+ end
+
+ it "raises TypeError if a key is not a String" do
+ -> { ENV.replace(Object.new => "0") }.should.raise(TypeError, "no implicit conversion of Object into String")
+ ENV.to_hash.should == @orig
+ end
+
+ it "raises TypeError if a value is not a String" do
+ -> { ENV.replace("foo" => Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ ENV.to_hash.should == @orig
+ end
+
+ it "raises Errno::EINVAL when the key contains the '=' character" do
+ -> { ENV.replace("foo=" =>"bar") }.should.raise(Errno::EINVAL)
+ end
+
+ it "raises Errno::EINVAL when the key is an empty string" do
+ -> { ENV.replace("" => "bar") }.should.raise(Errno::EINVAL)
+ end
+
+ it "does not accept good data preceding an error" do
+ -> { ENV.replace("foo" => "1", Object.new => Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "does not accept good data following an error" do
+ -> { ENV.replace(Object.new => Object.new, "foo" => "0") }.should.raise(TypeError, "no implicit conversion of Object into String")
+ ENV.to_hash.should == @orig
+ end
+end
diff --git a/spec/ruby/core/env/select_spec.rb b/spec/ruby/core/env/select_spec.rb
new file mode 100644
index 0000000000..c3a76f4434
--- /dev/null
+++ b/spec/ruby/core/env/select_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+require_relative 'shared/select'
+
+describe "ENV.select!" do
+ it_behaves_like :env_select!, :select!
+ it_behaves_like :enumeratorized_with_origin_size, :select!, ENV
+end
+
+describe "ENV.select" do
+ it_behaves_like :env_select, :select
+ it_behaves_like :enumeratorized_with_origin_size, :select, ENV
+end
diff --git a/spec/ruby/core/env/shared/each.rb b/spec/ruby/core/env/shared/each.rb
new file mode 100644
index 0000000000..0661ca924c
--- /dev/null
+++ b/spec/ruby/core/env/shared/each.rb
@@ -0,0 +1,65 @@
+require_relative '../../enumerable/shared/enumeratorized'
+
+describe :env_each, shared: true do
+ it "returns each pair" do
+ orig = ENV.to_hash
+ e = []
+ begin
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "boo"
+ ENV.send(@method) { |k, v| e << [k, v] }.should.equal?(ENV)
+ e.should.include?(["foo", "bar"])
+ e.should.include?(["baz", "boo"])
+ ensure
+ ENV.replace orig
+ end
+ end
+
+ it "returns an Enumerator if called without a block" do
+ enum = ENV.send(@method)
+ enum.should.instance_of?(Enumerator)
+ enum.each do |name, value|
+ ENV[name].should == value
+ end
+ end
+
+ before :all do
+ @object = ENV
+ end
+ it_should_behave_like :enumeratorized_with_origin_size
+
+ describe "with encoding" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::BINARY
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "uses the locale encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+
+ ENV.send(@method) do |key, value|
+ key.should.be_locale_env
+ value.should.be_locale_env
+ end
+ end
+
+ it "transcodes from the locale encoding to Encoding.default_internal if set" do
+ Encoding.default_internal = internal = Encoding::IBM437
+
+ ENV.send(@method) do |key, value|
+ key.encoding.should.equal?(internal)
+ if value.ascii_only?
+ value.encoding.should.equal?(internal)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/env/shared/include.rb b/spec/ruby/core/env/shared/include.rb
new file mode 100644
index 0000000000..ceca02e3eb
--- /dev/null
+++ b/spec/ruby/core/env/shared/include.rb
@@ -0,0 +1,30 @@
+describe :env_include, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns true if ENV has the key" do
+ ENV["foo"] = "bar"
+ ENV.send(@method, "foo").should == true
+ end
+
+ it "returns false if ENV doesn't include the key" do
+ ENV.delete("foo")
+ ENV.send(@method, "foo").should == false
+ end
+
+ it "coerces the key with #to_str" do
+ ENV["foo"] = "bar"
+ k = mock('key')
+ k.should_receive(:to_str).and_return("foo")
+ ENV.send(@method, k).should == true
+ end
+
+ it "raises TypeError if the argument is not a String and does not respond to #to_str" do
+ -> { ENV.send(@method, Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/shared/length.rb b/spec/ruby/core/env/shared/length.rb
new file mode 100644
index 0000000000..6d788a3f4a
--- /dev/null
+++ b/spec/ruby/core/env/shared/length.rb
@@ -0,0 +1,13 @@
+describe :env_length, shared: true do
+ it "returns the number of ENV entries" do
+ orig = ENV.to_hash
+ begin
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "boo"
+ ENV.send(@method).should == 2
+ ensure
+ ENV.replace orig
+ end
+ end
+end
diff --git a/spec/ruby/core/env/shared/select.rb b/spec/ruby/core/env/shared/select.rb
new file mode 100644
index 0000000000..8ec648a637
--- /dev/null
+++ b/spec/ruby/core/env/shared/select.rb
@@ -0,0 +1,61 @@
+describe :env_select, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns a Hash of names and values for which block return true" do
+ ENV["foo"] = "bar"
+ (ENV.send(@method) { |k, v| k == "foo" }).should == { "foo" => "bar" }
+ end
+
+ it "returns an Enumerator when no block is given" do
+ enum = ENV.send(@method)
+ enum.should.instance_of?(Enumerator)
+ end
+
+ it "selects via the enumerator" do
+ enum = ENV.send(@method)
+ ENV["foo"] = "bar"
+ enum.each { |k, v| k == "foo" }.should == { "foo" => "bar"}
+ end
+end
+
+describe :env_select!, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "removes environment variables for which the block returns true" do
+ ENV["foo"] = "bar"
+ ENV.send(@method) { |k, v| k != "foo" }
+ ENV["foo"].should == nil
+ end
+
+ it "returns self if any changes were made" do
+ ENV["foo"] = "bar"
+ (ENV.send(@method) { |k, v| k != "foo" }).should == ENV
+ end
+
+ it "returns nil if no changes were made" do
+ (ENV.send(@method) { true }).should == nil
+ end
+
+ it "returns an Enumerator if called without a block" do
+ ENV.send(@method).should.instance_of?(Enumerator)
+ end
+
+ it "selects via the enumerator" do
+ enum = ENV.send(@method)
+ ENV["foo"] = "bar"
+ enum.each { |k, v| k != "foo" }
+ ENV["foo"].should == nil
+ end
+end
diff --git a/spec/ruby/core/env/shared/store.rb b/spec/ruby/core/env/shared/store.rb
new file mode 100644
index 0000000000..388208a8ac
--- /dev/null
+++ b/spec/ruby/core/env/shared/store.rb
@@ -0,0 +1,60 @@
+describe :env_store, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "sets the environment variable to the given value" do
+ ENV.send(@method, "foo", "bar")
+ ENV["foo"].should == "bar"
+ end
+
+ it "returns the value" do
+ value = "bar"
+ ENV.send(@method, "foo", value).should.equal?(value)
+ end
+
+ it "deletes the environment variable when the value is nil" do
+ ENV["foo"] = "bar"
+ ENV.send(@method, "foo", nil)
+ ENV.key?("foo").should == false
+ end
+
+ it "coerces the key argument with #to_str" do
+ k = mock("key")
+ k.should_receive(:to_str).and_return("foo")
+ ENV.send(@method, k, "bar")
+ ENV["foo"].should == "bar"
+ end
+
+ it "coerces the value argument with #to_str" do
+ v = mock("value")
+ v.should_receive(:to_str).and_return("bar")
+ ENV.send(@method, "foo", v)
+ ENV["foo"].should == "bar"
+ end
+
+ it "raises TypeError when the key is not coercible to String" do
+ -> { ENV.send(@method, Object.new, "bar") }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises TypeError when the value is not coercible to String" do
+ -> { ENV.send(@method, "foo", Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises Errno::EINVAL when the key contains the '=' character" do
+ -> { ENV.send(@method, "foo=", "bar") }.should.raise(Errno::EINVAL)
+ end
+
+ it "raises Errno::EINVAL when the key is an empty string" do
+ -> { ENV.send(@method, "", "bar") }.should.raise(Errno::EINVAL)
+ end
+
+ it "does nothing when the key is not a valid environment variable key and the value is nil" do
+ ENV.send(@method, "foo=", nil)
+ ENV.key?("foo=").should == false
+ end
+end
diff --git a/spec/ruby/core/env/shared/to_hash.rb b/spec/ruby/core/env/shared/to_hash.rb
new file mode 100644
index 0000000000..7868690c38
--- /dev/null
+++ b/spec/ruby/core/env/shared/to_hash.rb
@@ -0,0 +1,33 @@
+describe :env_to_hash, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"]= @saved_foo
+ end
+
+ it "returns the ENV as a hash" do
+ ENV["foo"] = "bar"
+ h = ENV.send(@method)
+ h.should.instance_of?(Hash)
+ h["foo"].should == "bar"
+ end
+
+ it "uses the locale encoding for keys" do
+ ENV.send(@method).keys.each {|k| k.should.be_locale_env }
+ end
+
+ it "uses the locale encoding for values" do
+ ENV.send(@method).values.each {|k| k.should.be_locale_env }
+ end
+
+ it "duplicates the ENV when converting to a Hash" do
+ h = ENV.send(@method)
+ h.should_not.equal? ENV
+ h.size.should == ENV.size
+ h.each_pair do |k, v|
+ ENV[k].should == v
+ end
+ end
+end
diff --git a/spec/ruby/core/env/shared/update.rb b/spec/ruby/core/env/shared/update.rb
new file mode 100644
index 0000000000..112cc2505d
--- /dev/null
+++ b/spec/ruby/core/env/shared/update.rb
@@ -0,0 +1,104 @@
+describe :env_update, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ @saved_bar = ENV["bar"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ ENV["bar"] = @saved_bar
+ end
+
+ it "adds the parameter hash to ENV, returning ENV" do
+ ENV.send(@method, "foo" => "0", "bar" => "1").should.equal?(ENV)
+ ENV["foo"].should == "0"
+ ENV["bar"].should == "1"
+ 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
+ ENV.send(@method, {"foo" => "0", "bar" => "1"}).should.equal?(ENV)
+ end
+
+ it "yields key, the old value and the new value when replacing an entry" do
+ ENV.send @method, {"foo" => "0", "bar" => "3"}
+ a = []
+ ENV.send @method, {"foo" => "1", "bar" => "4"} do |key, old, new|
+ a << [key, old, new]
+ new
+ end
+ a[0].should == ["foo", "0", "1"]
+ a[1].should == ["bar", "3", "4"]
+ end
+
+ it "yields key, the old value and the new value when replacing an entry" do
+ ENV.send @method, {"foo" => "0", "bar" => "3"}
+ ENV.send @method, {"foo" => "1", "bar" => "4"} do |key, old, new|
+ (new.to_i + 1).to_s
+ end
+ ENV["foo"].should == "2"
+ ENV["bar"].should == "5"
+ end
+
+ # BUG: https://bugs.ruby-lang.org/issues/16192
+ it "does not evaluate the block when the name is new" do
+ ENV.delete("bar")
+ ENV.send @method, {"foo" => "0"}
+ ENV.send(@method, "bar" => "1") { |key, old, new| fail "Should not get here" }
+ ENV["bar"].should == "1"
+ end
+
+ # BUG: https://bugs.ruby-lang.org/issues/16192
+ it "does not use the block's return value as the value when the name is new" do
+ ENV.delete("bar")
+ ENV.send @method, {"foo" => "0"}
+ ENV.send(@method, "bar" => "1") { |key, old, new| "Should not use this value" }
+ ENV["foo"].should == "0"
+ ENV["bar"].should == "1"
+ end
+
+ it "returns ENV when block given" do
+ ENV.send(@method, {"foo" => "0", "bar" => "1"}){}.should.equal?(ENV)
+ end
+
+ it "raises TypeError when a name is not coercible to String" do
+ -> { ENV.send @method, Object.new => "0" }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises TypeError when a value is not coercible to String" do
+ -> { ENV.send @method, "foo" => Object.new }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises Errno::EINVAL when a name contains the '=' character" do
+ -> { ENV.send(@method, "foo=" => "bar") }.should.raise(Errno::EINVAL)
+ end
+
+ it "raises Errno::EINVAL when a name is an empty string" do
+ -> { ENV.send(@method, "" => "bar") }.should.raise(Errno::EINVAL)
+ end
+
+ it "updates good data preceding an error" do
+ ENV["foo"] = "0"
+ begin
+ ENV.send @method, {"foo" => "2", Object.new => "1"}
+ rescue TypeError
+ ensure
+ ENV["foo"].should == "2"
+ end
+ end
+
+ it "does not update good data following an error" do
+ ENV["foo"] = "0"
+ begin
+ ENV.send @method, {Object.new => "1", "foo" => "2"}
+ rescue TypeError
+ ensure
+ ENV["foo"].should == "0"
+ end
+ end
+end
diff --git a/spec/ruby/core/env/shared/value.rb b/spec/ruby/core/env/shared/value.rb
new file mode 100644
index 0000000000..c2b5025465
--- /dev/null
+++ b/spec/ruby/core/env/shared/value.rb
@@ -0,0 +1,29 @@
+describe :env_value, shared: true do
+ before :each do
+ @saved_foo = ENV["foo"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ end
+
+ it "returns true if ENV has the value" do
+ ENV["foo"] = "bar"
+ ENV.send(@method, "bar").should == true
+ end
+
+ it "returns false if ENV doesn't have the value" do
+ ENV.send(@method, "foo").should == false
+ end
+
+ it "coerces the value element with #to_str" do
+ ENV["foo"] = "bar"
+ v = mock('value')
+ v.should_receive(:to_str).and_return("bar")
+ ENV.send(@method, v).should == true
+ end
+
+ it "returns nil if the argument is not a String and does not respond to #to_str" do
+ ENV.send(@method, Object.new).should == nil
+ end
+end
diff --git a/spec/ruby/core/env/shift_spec.rb b/spec/ruby/core/env/shift_spec.rb
new file mode 100644
index 0000000000..52bd0f9139
--- /dev/null
+++ b/spec/ruby/core/env/shift_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "ENV.shift" do
+ before :each do
+ @orig = ENV.to_hash
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::BINARY
+ ENV.replace({"FOO"=>"BAR"})
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ ENV.replace @orig
+ end
+
+ it "returns a pair and deletes it" do
+ ENV.should.has_key?("FOO")
+ pair = ENV.shift
+ pair.should == ["FOO", "BAR"]
+ ENV.should_not.has_key?("FOO")
+ end
+
+ it "returns nil if ENV.empty?" do
+ ENV.clear
+ ENV.shift.should == nil
+ end
+
+ it "uses the locale encoding if Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+
+ pair = ENV.shift
+ pair.first.encoding.should.equal?(ENVSpecs.encoding)
+ pair.last.encoding.should.equal?(ENVSpecs.encoding)
+ end
+
+ it "transcodes from the locale encoding to Encoding.default_internal if set" do
+ Encoding.default_internal = Encoding::IBM437
+
+ pair = ENV.shift
+ pair.first.encoding.should.equal?(Encoding::IBM437)
+ pair.last.encoding.should.equal?(Encoding::IBM437)
+ end
+end
diff --git a/spec/ruby/core/env/size_spec.rb b/spec/ruby/core/env/size_spec.rb
new file mode 100644
index 0000000000..7c8072481e
--- /dev/null
+++ b/spec/ruby/core/env/size_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/length'
+
+describe "ENV.size" do
+ it_behaves_like :env_length, :size
+end
diff --git a/spec/ruby/core/env/slice_spec.rb b/spec/ruby/core/env/slice_spec.rb
new file mode 100644
index 0000000000..4c0416547d
--- /dev/null
+++ b/spec/ruby/core/env/slice_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "ENV.slice" do
+ before :each do
+ @saved_foo = ENV["foo"]
+ @saved_bar = ENV["bar"]
+ ENV["foo"] = "0"
+ ENV["bar"] = "1"
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ ENV["bar"] = @saved_bar
+ end
+
+ it "returns a hash of the given environment variable names and their values" do
+ ENV.slice("foo", "bar").should == {"foo" => "0", "bar" => "1"}
+ end
+
+ it "ignores each String that is not an environment variable name" do
+ ENV.slice("foo", "boo", "bar").should == {"foo" => "0", "bar" => "1"}
+ end
+
+ it "returns the values for the keys coerced with #to_str, but keeps the original objects as result keys" do
+ foo = mock('key 1')
+ foo.should_receive(:to_str).and_return("foo")
+ boo = mock('key 2')
+ boo.should_receive(:to_str).and_return("boo")
+ bar = mock('key 3')
+ bar.should_receive(:to_str).and_return("bar")
+ ENV.slice(foo, boo, bar).should == {foo => "0", bar => "1"}
+ end
+
+ it "raises TypeError if any argument is not a String and does not respond to #to_str" do
+ -> { ENV.slice(Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/spec_helper.rb b/spec/ruby/core/env/spec_helper.rb
new file mode 100644
index 0000000000..470ffa58bc
--- /dev/null
+++ b/spec/ruby/core/env/spec_helper.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+locale_env_matcher = Class.new do
+ def initialize(name = 'locale')
+ encoding = Encoding.find(name)
+ @encodings = (encoding = Encoding::US_ASCII) ?
+ [encoding, Encoding::ASCII_8BIT] : [encoding]
+ end
+
+ def matches?(actual)
+ @actual = actual = actual.encoding
+ @encodings.include?(actual)
+ end
+
+ def failure_message
+ ["Expected #{@actual} to be #{@encodings.join(' or ')}"]
+ end
+
+ def negative_failure_message
+ ["Expected #{@actual} not to be #{@encodings.join(' or ')}"]
+ end
+end
+
+String.__send__(:define_method, :be_locale_env) do |expected = 'locale'|
+ locale_env_matcher.new(expected)
+end
diff --git a/spec/ruby/core/env/store_spec.rb b/spec/ruby/core/env/store_spec.rb
new file mode 100644
index 0000000000..b4700e0a96
--- /dev/null
+++ b/spec/ruby/core/env/store_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/store'
+
+describe "ENV.store" do
+ it_behaves_like :env_store, :store
+end
diff --git a/spec/ruby/core/env/to_a_spec.rb b/spec/ruby/core/env/to_a_spec.rb
new file mode 100644
index 0000000000..2b1649281f
--- /dev/null
+++ b/spec/ruby/core/env/to_a_spec.rb
@@ -0,0 +1,21 @@
+require_relative 'spec_helper'
+
+describe "ENV.to_a" do
+
+ it "returns the ENV as an array" do
+ a = ENV.to_a
+ a.is_a?(Array).should == true
+ a.size.should == ENV.size
+ a.each { |k,v| ENV[k].should == v }
+
+ a.first.should.is_a?(Array)
+ a.first.size.should == 2
+ end
+
+ it "returns the entries in the locale encoding" do
+ ENV.to_a.each do |key, value|
+ key.should.be_locale_env
+ value.should.be_locale_env
+ end
+ end
+end
diff --git a/spec/ruby/core/env/to_h_spec.rb b/spec/ruby/core/env/to_h_spec.rb
new file mode 100644
index 0000000000..7e0fef2120
--- /dev/null
+++ b/spec/ruby/core/env/to_h_spec.rb
@@ -0,0 +1,70 @@
+require_relative 'spec_helper'
+require_relative 'shared/to_hash'
+
+describe "ENV.to_h" do
+ it_behaves_like :env_to_hash, :to_h
+
+ context "with block" do
+ before do
+ @orig_hash = ENV.to_hash
+ end
+
+ after do
+ ENV.replace @orig_hash
+ end
+
+ it "converts [key, value] pairs returned by the block to a hash" do
+ ENV.replace("a" => "b", "c" => "d")
+ 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 }
+ end
+
+ it "raises ArgumentError if block returns longer or shorter array" do
+ -> do
+ ENV.to_h { |k, v| [k, v.upcase, 1] }
+ end.should.raise(ArgumentError, /element has wrong array length/)
+
+ -> do
+ ENV.to_h { |k, v| [k] }
+ end.should.raise(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ ENV.to_h { |k, v| "not-array" }
+ end.should.raise(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'])
+
+ ENV.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'])
+
+ -> do
+ ENV.to_h { |k| x }
+ end.should.raise(TypeError, /wrong element type MockObject/)
+ end
+ end
+end
diff --git a/spec/ruby/core/env/to_hash_spec.rb b/spec/ruby/core/env/to_hash_spec.rb
new file mode 100644
index 0000000000..306572c353
--- /dev/null
+++ b/spec/ruby/core/env/to_hash_spec.rb
@@ -0,0 +1,6 @@
+require_relative 'spec_helper'
+require_relative 'shared/to_hash'
+
+describe "ENV.to_hash" do
+ it_behaves_like :env_to_hash, :to_hash
+end
diff --git a/spec/ruby/core/env/to_s_spec.rb b/spec/ruby/core/env/to_s_spec.rb
new file mode 100644
index 0000000000..0bd92cf217
--- /dev/null
+++ b/spec/ruby/core/env/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "ENV.to_s" do
+ it "returns \"ENV\"" do
+ ENV.to_s.should == "ENV"
+ end
+end
diff --git a/spec/ruby/core/env/update_spec.rb b/spec/ruby/core/env/update_spec.rb
new file mode 100644
index 0000000000..95a8a2eb49
--- /dev/null
+++ b/spec/ruby/core/env/update_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/update'
+
+describe "ENV.update" do
+ it_behaves_like :env_update, :update
+end
diff --git a/spec/ruby/core/env/value_spec.rb b/spec/ruby/core/env/value_spec.rb
new file mode 100644
index 0000000000..906e86ab39
--- /dev/null
+++ b/spec/ruby/core/env/value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/value'
+
+describe "ENV.value?" do
+ it_behaves_like :env_value, :value?
+end
diff --git a/spec/ruby/core/env/values_at_spec.rb b/spec/ruby/core/env/values_at_spec.rb
new file mode 100644
index 0000000000..4a733f4ed5
--- /dev/null
+++ b/spec/ruby/core/env/values_at_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "ENV.values_at" do
+ before :each do
+ @saved_foo = ENV["foo"]
+ @saved_bar = ENV["bar"]
+ end
+
+ after :each do
+ ENV["foo"] = @saved_foo
+ ENV["bar"] = @saved_bar
+ end
+
+ it "returns an array of the values corresponding to the given keys" do
+ ENV["foo"] = "oof"
+ ENV["bar"] = "rab"
+ ENV.values_at("bar", "foo").should == ["rab", "oof"]
+ end
+
+ it "returns an empty array if no keys specified" do
+ ENV.values_at.should == []
+ end
+
+ it "returns nil for each key that is not a name" do
+ ENV["foo"] = "oof"
+ ENV["bar"] = "rab"
+ ENV.values_at("x", "bar", "y", "foo", "z").should == [nil, "rab", nil, "oof", nil]
+ end
+
+ it "uses the locale encoding" do
+ ENV.values_at(ENV.keys.first).first.encoding.should == ENVSpecs.encoding
+ end
+
+ it "raises TypeError when a key is not coercible to String" do
+ -> { ENV.values_at("foo", Object.new) }.should.raise(TypeError, "no implicit conversion of Object into String")
+ end
+end
diff --git a/spec/ruby/core/env/values_spec.rb b/spec/ruby/core/env/values_spec.rb
new file mode 100644
index 0000000000..71bc877d31
--- /dev/null
+++ b/spec/ruby/core/env/values_spec.rb
@@ -0,0 +1,14 @@
+require_relative 'spec_helper'
+
+describe "ENV.values" do
+
+ it "returns an array of the values" do
+ ENV.values.should == ENV.to_hash.values
+ end
+
+ it "uses the locale encoding" do
+ ENV.values.each do |value|
+ value.should.be_locale_env
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/backtrace_locations_spec.rb b/spec/ruby/core/exception/backtrace_locations_spec.rb
new file mode 100644
index 0000000000..62eab8a0f3
--- /dev/null
+++ b/spec/ruby/core/exception/backtrace_locations_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#backtrace_locations" do
+ before :each do
+ @backtrace = ExceptionSpecs::Backtrace.backtrace_locations
+ end
+
+ it "returns nil if no backtrace was set" do
+ Exception.new.backtrace_locations.should == nil
+ end
+
+ it "returns an Array" do
+ @backtrace.should.instance_of?(Array)
+ end
+
+ it "sets each element to a Thread::Backtrace::Location" do
+ @backtrace.each {|l| l.should.instance_of?(Thread::Backtrace::Location)}
+ end
+
+ it "produces a backtrace for an exception captured using $!" do
+ exception = begin
+ raise
+ rescue RuntimeError
+ $!
+ end
+
+ exception.backtrace_locations.first.path.should =~ /backtrace_locations_spec/
+ end
+
+ it "returns an Array that can be updated" do
+ begin
+ raise
+ rescue RuntimeError => e
+ e.backtrace_locations.unshift "backtrace first"
+ e.backtrace_locations[0].should == "backtrace first"
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/backtrace_spec.rb b/spec/ruby/core/exception/backtrace_spec.rb
new file mode 100644
index 0000000000..a4a8272030
--- /dev/null
+++ b/spec/ruby/core/exception/backtrace_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#backtrace" do
+ before :each do
+ @backtrace = ExceptionSpecs::Backtrace.backtrace
+ end
+
+ it "returns nil if no backtrace was set" do
+ Exception.new.backtrace.should == nil
+ end
+
+ it "returns an Array" do
+ @backtrace.should.instance_of?(Array)
+ end
+
+ it "sets each element to a String" do
+ @backtrace.each {|l| l.should.instance_of?(String)}
+ end
+
+ it "includes the filename of the location where self raised in the first element" do
+ @backtrace.first.should =~ /common\.rb/
+ end
+
+ it "includes the line number of the location where self raised in the first element" do
+ @backtrace.first.should =~ /:7:in /
+ end
+
+ it "includes the name of the method from where self raised in the first element" do
+ @backtrace.first.should =~ /in [`'](?:ExceptionSpecs::Backtrace\.)?backtrace'/
+ end
+
+ it "includes the filename of the location immediately prior to where self raised in the second element" do
+ @backtrace[1].should =~ /backtrace_spec\.rb/
+ end
+
+ it "includes the line number of the location immediately prior to where self raised in the second element" do
+ @backtrace[1].should =~ /:6(:in )?/
+ end
+
+ ruby_version_is ""..."3.4" do
+ it "contains lines of the same format for each prior position in the stack" do
+ @backtrace[2..-1].each do |line|
+ # This regexp is deliberately imprecise to account for the need to abstract out
+ # the paths of the included mspec files and the desire to avoid specifying in any
+ # detail what the in `...' portion looks like.
+ line.should =~ /^.+:\d+:in `[^`]+'$/
+ end
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "contains lines of the same format for each prior position in the stack" do
+ @backtrace[2..-1].each do |line|
+ # This regexp is deliberately imprecise to account for the need to abstract out
+ # the paths of the included mspec files and the desire to avoid specifying in any
+ # detail what the in '...' portion looks like.
+ line.should =~ /^.+:\d+:in '[^`]+'$/
+ end
+ end
+ end
+
+ it "captures the backtrace for an exception into $!" do
+ exception = begin
+ raise
+ rescue RuntimeError
+ $!
+ end
+
+ exception.backtrace.first.should =~ /backtrace_spec/
+ end
+
+ it "captures the backtrace for an exception into $@" do
+ backtrace = begin
+ raise
+ rescue RuntimeError
+ $@
+ end
+
+ backtrace.first.should =~ /backtrace_spec/
+ end
+
+ it "returns an Array that can be updated" do
+ begin
+ raise
+ rescue RuntimeError => e
+ e.backtrace.unshift "backtrace first"
+ e.backtrace[0].should == "backtrace first"
+ end
+ end
+
+ it "returns the same array after duping" do
+ begin
+ raise
+ rescue RuntimeError => err
+ bt = err.backtrace
+ err.dup.backtrace.should.equal?(bt)
+
+ new_bt = ['hi']
+ err.set_backtrace new_bt
+
+ err.backtrace.should == new_bt
+ err.dup.backtrace.should.equal?(new_bt)
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/case_compare_spec.rb b/spec/ruby/core/exception/case_compare_spec.rb
new file mode 100644
index 0000000000..5fd11ae741
--- /dev/null
+++ b/spec/ruby/core/exception/case_compare_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "SystemCallError.===" do
+ before :all do
+ @example_errno_class = Errno::EINVAL
+ @example_errno = @example_errno_class::Errno
+ end
+
+ it "returns true for an instance of the same class" do
+ Errno::EINVAL.should === Errno::EINVAL.new
+ end
+
+ it "returns true if errnos same" do
+ e = SystemCallError.new('foo', @example_errno)
+ @example_errno_class.===(e).should == true
+ end
+
+ it "returns false if errnos different" do
+ e = SystemCallError.new('foo', @example_errno + 1)
+ @example_errno_class.===(e).should == false
+ end
+
+ it "returns false if arg is not kind of SystemCallError" do
+ e = Object.new
+ @example_errno_class.===(e).should == false
+ end
+
+ it "returns true if receiver is generic and arg is kind of SystemCallError" do
+ e = SystemCallError.new('foo', @example_errno)
+ SystemCallError.===(e).should == true
+ end
+
+ it "returns false if receiver is generic and arg is not kind of SystemCallError" do
+ e = Object.new
+ SystemCallError.===(e).should == false
+ end
+end
diff --git a/spec/ruby/core/exception/cause_spec.rb b/spec/ruby/core/exception/cause_spec.rb
new file mode 100644
index 0000000000..cfc15bdda3
--- /dev/null
+++ b/spec/ruby/core/exception/cause_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+describe "Exception#cause" do
+ it "returns the active exception when an exception is raised" do
+ begin
+ raise Exception, "the cause"
+ rescue Exception => cause
+ -> {
+ raise RuntimeError, "the consequence"
+ }.should.raise(RuntimeError, "the consequence", cause:)
+ end
+ end
+
+ it "is set for user errors caused by internal errors" do
+ begin
+ 1 / 0
+ rescue => cause
+ -> { raise "foo" }.should.raise(RuntimeError, cause:)
+ end
+ end
+
+ it "is set for internal errors caused by user errors" do
+ cause = RuntimeError.new "cause"
+ begin
+ raise cause
+ rescue
+ -> { 1 / 0 }.should.raise(ZeroDivisionError, cause:)
+ end
+ end
+
+ it "is not set to the exception itself when it is re-raised" do
+ begin
+ raise RuntimeError
+ rescue RuntimeError => e
+ -> { raise e }.should.raise(RuntimeError, cause: nil)
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/detailed_message_spec.rb b/spec/ruby/core/exception/detailed_message_spec.rb
new file mode 100644
index 0000000000..9df164a1cf
--- /dev/null
+++ b/spec/ruby/core/exception/detailed_message_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#detailed_message" do
+ 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>"
+ end
+
+ it "returns just a message if exception class is anonymous" do
+ Class.new(RuntimeError).new("message").detailed_message.should == "message"
+ end
+
+ it "returns 'unhandled exception' for an instance of RuntimeError with empty message" do
+ RuntimeError.new("").detailed_message.should == "unhandled exception"
+ end
+
+ it "returns 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 a generated class name for an instance of RuntimeError anonymous subclass with empty message" do
+ klass = Class.new(RuntimeError)
+ klass.new("").detailed_message.should =~ /\A#<Class:0x\h+>\z/
+ end
+
+ it "accepts highlight keyword argument and adds escape control sequences" do
+ RuntimeError.new("new error").detailed_message(highlight: true).should == "\e[1mnew error (\e[1;4mRuntimeError\e[m\e[1m)\e[m"
+ end
+
+ it "accepts highlight keyword argument and adds escape control sequences for an instance of RuntimeError with empty message" do
+ RuntimeError.new("").detailed_message(highlight: true).should == "\e[1;4munhandled exception\e[m"
+ end
+
+ it "accepts highlight keyword argument and adds escape control sequences for an instance other than RuntimeError with empty message" do
+ StandardError.new("").detailed_message(highlight: true).should == "\e[1;4mStandardError\e[m"
+ end
+
+ it "allows and ignores other keyword arguments" do
+ RuntimeError.new("new error").detailed_message(foo: true).should == "new error (RuntimeError)"
+ end
+end
diff --git a/spec/ruby/core/exception/dup_spec.rb b/spec/ruby/core/exception/dup_spec.rb
new file mode 100644
index 0000000000..b53ad79bf3
--- /dev/null
+++ b/spec/ruby/core/exception/dup_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#dup" do
+ before :each do
+ @obj = ExceptionSpecs::InitializeException.new("my exception")
+ end
+
+ it "calls #initialize_copy on the new instance" do
+ dup = @obj.dup
+ ScratchPad.recorded.should_not == @obj.object_id
+ ScratchPad.recorded.should == dup.object_id
+ end
+
+ it "copies instance variables" do
+ dup = @obj.dup
+ dup.ivar.should == 1
+ end
+
+ it "does not copy singleton methods" do
+ def @obj.special() :the_one end
+ dup = @obj.dup
+ -> { dup.special }.should.raise(NameError)
+ end
+
+ it "does not copy modules included in the singleton class" do
+ class << @obj
+ include ExceptionSpecs::ExceptionModule
+ end
+
+ dup = @obj.dup
+ -> { dup.repr }.should.raise(NameError)
+ end
+
+ it "does not copy constants defined in the singleton class" do
+ class << @obj
+ CLONE = :clone
+ end
+
+ dup = @obj.dup
+ -> { class << dup; CLONE; end }.should.raise(NameError)
+ end
+
+ it "does copy the message" do
+ @obj.dup.message.should == @obj.message
+ end
+
+ it "does copy the backtrace" do
+ begin
+ # Explicitly raise so a backtrace is associated with the exception.
+ # It's tempting to call `set_backtrace` instead, but that complicates
+ # the test because it might affect other state (e.g., instance variables)
+ # on some implementations.
+ raise ExceptionSpecs::InitializeException.new("my exception")
+ rescue => e
+ @obj = e
+ end
+
+ @obj.dup.backtrace.should == @obj.backtrace
+ end
+
+ it "does copy the cause" do
+ begin
+ raise StandardError
+ rescue StandardError => cause
+ begin
+ raise RuntimeError
+ rescue RuntimeError => e
+ e.cause.should.equal?(cause)
+ e.dup.cause.should.equal?(cause)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/equal_value_spec.rb b/spec/ruby/core/exception/equal_value_spec.rb
new file mode 100644
index 0000000000..b76b3bcd4a
--- /dev/null
+++ b/spec/ruby/core/exception/equal_value_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#==" do
+ it "returns true if both exceptions are the same object" do
+ e = ArgumentError.new
+ e.should == e
+ end
+
+ it "returns true if one exception is the dup'd copy of the other" do
+ e = ArgumentError.new
+ e.should == e.dup
+ end
+
+ it "returns true if both exceptions have the same class, no message, and no backtrace" do
+ RuntimeError.new.should == RuntimeError.new
+ end
+
+ it "returns true if both exceptions have the same class, the same message, and no backtrace" do
+ TypeError.new("message").should == TypeError.new("message")
+ end
+
+ it "returns true if both exceptions have the same class, the same message, and the same backtrace" do
+ one = TypeError.new("message")
+ one.set_backtrace [__dir__]
+ two = TypeError.new("message")
+ two.set_backtrace [__dir__]
+ one.should == two
+ end
+
+ it "returns false if the two exceptions inherit from Exception but have different classes" do
+ one = RuntimeError.new("message")
+ one.set_backtrace [__dir__]
+ one.should.is_a?(Exception)
+ two = TypeError.new("message")
+ two.set_backtrace [__dir__]
+ two.should.is_a?(Exception)
+ one.should_not == two
+ end
+
+ it "returns true if the two objects subclass Exception and have the same message and backtrace" do
+ one = ExceptionSpecs::UnExceptional.new
+ two = ExceptionSpecs::UnExceptional.new
+ one.message.should == two.message
+ two.backtrace.should == two.backtrace
+ one.should == two
+ end
+
+ it "returns false if the argument is not an Exception" do
+ ArgumentError.new.should_not == String.new
+ end
+
+ it "returns false if the two exceptions differ only in their backtrace" do
+ one = RuntimeError.new("message")
+ one.set_backtrace [__dir__]
+ two = RuntimeError.new("message")
+ two.set_backtrace nil
+ one.should_not == two
+ end
+
+ it "returns false if the two exceptions differ only in their message" do
+ one = RuntimeError.new("message")
+ one.set_backtrace [__dir__]
+ two = RuntimeError.new("message2")
+ two.set_backtrace [__dir__]
+ one.should_not == two
+ end
+end
diff --git a/spec/ruby/core/exception/errno_spec.rb b/spec/ruby/core/exception/errno_spec.rb
new file mode 100644
index 0000000000..36beae9976
--- /dev/null
+++ b/spec/ruby/core/exception/errno_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Errno::EINVAL.new" do
+ it "can be called with no arguments" do
+ exc = Errno::EINVAL.new
+ exc.should.instance_of?(Errno::EINVAL)
+ exc.errno.should == Errno::EINVAL::Errno
+ exc.message.should == "Invalid argument"
+ end
+
+ it "accepts an optional custom message" do
+ exc = Errno::EINVAL.new('custom message')
+ exc.should.instance_of?(Errno::EINVAL)
+ exc.errno.should == Errno::EINVAL::Errno
+ exc.message.should == "Invalid argument - custom message"
+ end
+
+ it "accepts an optional custom message and location" do
+ exc = Errno::EINVAL.new('custom message', 'location')
+ exc.should.instance_of?(Errno::EINVAL)
+ exc.errno.should == Errno::EINVAL::Errno
+ exc.message.should == "Invalid argument @ location - custom message"
+ end
+end
+
+describe "Errno::EMFILE" do
+ it "can be subclassed" do
+ ExceptionSpecs::EMFILESub = Class.new(Errno::EMFILE)
+ exc = ExceptionSpecs::EMFILESub.new
+ exc.should.instance_of?(ExceptionSpecs::EMFILESub)
+ ensure
+ ExceptionSpecs.send(:remove_const, :EMFILESub)
+ end
+end
+
+describe "Errno::EAGAIN" do
+ # From http://jira.codehaus.org/browse/JRUBY-4747
+ it "is the same class as Errno::EWOULDBLOCK if they represent the same errno value" do
+ if Errno::EAGAIN::Errno == Errno::EWOULDBLOCK::Errno
+ Errno::EAGAIN.should == Errno::EWOULDBLOCK
+ else
+ Errno::EAGAIN.should_not == Errno::EWOULDBLOCK
+ end
+ end
+end
+
+describe "Errno::ENOTSUP" do
+ it "is defined" do
+ Errno.should.const_defined?(:ENOTSUP, false)
+ end
+
+ it "is the same class as Errno::EOPNOTSUPP if they represent the same errno value" do
+ if Errno::ENOTSUP::Errno == Errno::EOPNOTSUPP::Errno
+ Errno::ENOTSUP.should == Errno::EOPNOTSUPP
+ else
+ Errno::ENOTSUP.should_not == Errno::EOPNOTSUPP
+ end
+ end
+end
+
+describe "Errno::ENOENT" do
+ it "lets subclasses inherit the default error message" do
+ c = Class.new(Errno::ENOENT)
+ raise c, "custom message"
+ rescue => e
+ e.message.should == "No such file or directory - custom message"
+ end
+end
diff --git a/spec/ruby/core/exception/exception_spec.rb b/spec/ruby/core/exception/exception_spec.rb
new file mode 100644
index 0000000000..f5424cdabd
--- /dev/null
+++ b/spec/ruby/core/exception/exception_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/new'
+
+describe "Exception.exception" do
+ it_behaves_like :exception_new, :exception
+end
+
+describe "Exception#exception" do
+ it "returns self when passed no argument" do
+ e = RuntimeError.new
+ e.should == e.exception
+ end
+
+ it "returns self when passed self as an argument" do
+ e = RuntimeError.new
+ e.should == e.exception(e)
+ end
+
+ it "returns an exception of the same class as self with the message given as argument" do
+ e = RuntimeError.new
+ e2 = e.exception("message")
+ e2.should.instance_of?(RuntimeError)
+ e2.message.should == "message"
+ end
+
+ it "when raised will be rescued as the new exception" do
+ begin
+ begin
+ raised_first = StandardError.new('first')
+ raise raised_first
+ rescue => caught_first
+ raised_second = raised_first.exception('second')
+ raise raised_second
+ end
+ rescue => caught_second
+ end
+
+ raised_first.should == caught_first
+ raised_second.should == caught_second
+ end
+
+ it "captures an exception into $!" do
+ exception = begin
+ raise
+ rescue RuntimeError
+ $!
+ end
+
+ exception.class.should == RuntimeError
+ exception.message.should == ""
+ exception.backtrace.first.should =~ /exception_spec/
+ end
+
+ class CustomArgumentError < StandardError
+ attr_reader :val
+ def initialize(val)
+ @val = val
+ end
+ end
+
+ it "returns an exception of the same class as self with the message given as argument, but without reinitializing" do
+ e = CustomArgumentError.new(:boom)
+ e2 = e.exception("message")
+ e2.should.instance_of?(CustomArgumentError)
+ e2.val.should == :boom
+ e2.message.should == "message"
+ end
+end
diff --git a/spec/ruby/core/exception/exit_value_spec.rb b/spec/ruby/core/exception/exit_value_spec.rb
new file mode 100644
index 0000000000..bb6cff1831
--- /dev/null
+++ b/spec/ruby/core/exception/exit_value_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "LocalJumpError#exit_value" do
+ def get_me_a_return
+ Proc.new { return 42 }
+ end
+
+ it "returns the value given to return" do
+ -> { get_me_a_return.call }.should.raise(LocalJumpError) { |e|
+ e.exit_value.should == 42
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/fixtures/common.rb b/spec/ruby/core/exception/fixtures/common.rb
new file mode 100644
index 0000000000..3d8a3c3430
--- /dev/null
+++ b/spec/ruby/core/exception/fixtures/common.rb
@@ -0,0 +1,102 @@
+module ExceptionSpecs
+ class Exceptional < Exception; end
+
+ class Backtrace
+ def self.backtrace
+ begin
+ raise # If you move this line, update backtrace_spec.rb
+ rescue RuntimeError => e
+ e.backtrace
+ end
+ end
+
+ def self.backtrace_locations
+ begin
+ raise
+ rescue RuntimeError => e
+ e.backtrace_locations
+ end
+ end
+ end
+
+ class UnExceptional < Exception
+ def backtrace
+ nil
+ end
+ def message
+ nil
+ end
+ end
+
+ class ConstructorException < Exception
+
+ def initialize
+ end
+
+ end
+
+ class OverrideToS < RuntimeError
+ def to_s
+ "this is from #to_s"
+ end
+ end
+
+ class EmptyToS < RuntimeError
+ def to_s
+ ""
+ end
+ end
+
+ class InitializeException < StandardError
+ attr_reader :ivar
+
+ def initialize(message = nil)
+ super
+ @ivar = 1
+ end
+
+ def initialize_copy(other)
+ super
+ ScratchPad.record object_id
+ end
+ end
+
+ module ExceptionModule
+ def repr
+ 1
+ end
+ end
+end
+
+module NoMethodErrorSpecs
+ class NoMethodErrorA; end
+
+ class NoMethodErrorB; end
+
+ class NoMethodErrorC;
+ protected
+ def a_protected_method;end
+ private
+ def a_private_method; end
+ end
+
+ class NoMethodErrorD; end
+
+ class InstanceException < Exception
+ end
+
+ class AClass; end
+ module AModule; end
+end
+
+class NameErrorSpecs
+ class ReceiverClass
+ def call_undefined_class_variable
+ @@doesnt_exist
+ end
+ end
+end
+
+module DetailedMessageSpec
+ C = Class.new(RuntimeError)
+end
diff --git a/spec/ruby/core/exception/fixtures/syntax_error.rb b/spec/ruby/core/exception/fixtures/syntax_error.rb
new file mode 100644
index 0000000000..ccec62f7a1
--- /dev/null
+++ b/spec/ruby/core/exception/fixtures/syntax_error.rb
@@ -0,0 +1,3 @@
+# rubocop:disable Lint/Syntax
+1+1=2
+# rubocop:enable Lint/Syntax
diff --git a/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb b/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb
new file mode 100644
index 0000000000..c109ec6247
--- /dev/null
+++ b/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb
@@ -0,0 +1,22 @@
+ready = false
+t = Thread.new do
+ f = Fiber.new do
+ begin
+ Fiber.yield
+ ensure
+ STDERR.puts "suspended fiber ensure"
+ end
+ end
+ f.resume
+
+ begin
+ ready = true
+ sleep
+ ensure
+ STDERR.puts "current fiber ensure"
+ end
+end
+
+Thread.pass until ready && t.stop?
+
+# let the program end, it's the same as #exit or an exception for this behavior
diff --git a/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb b/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb
new file mode 100644
index 0000000000..3364ed06d0
--- /dev/null
+++ b/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb
@@ -0,0 +1,25 @@
+ready = false
+t = Thread.new do
+ f = Fiber.new do
+ begin
+ Fiber.yield
+ ensure
+ STDERR.puts "suspended fiber ensure"
+ end
+ end
+ f.resume
+
+ f2 = Fiber.new do
+ begin
+ ready = true
+ sleep
+ ensure
+ STDERR.puts "current fiber ensure"
+ end
+ end
+ f2.resume
+end
+
+Thread.pass until ready && t.stop?
+
+# let the program end, it's the same as #exit or an exception for this behavior
diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb
new file mode 100644
index 0000000000..a28f524b54
--- /dev/null
+++ b/spec/ruby/core/exception/frozen_error_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+describe "FrozenError.new" do
+ it "should take optional receiver argument" do
+ o = Object.new
+ FrozenError.new("msg", receiver: o).receiver.should.equal?(o)
+ end
+end
+
+describe "FrozenError#receiver" do
+ it "should return frozen object that modification was attempted on" do
+ o = Object.new.freeze
+ begin
+ def o.x; end
+ rescue => e
+ e.should.is_a?(FrozenError)
+ e.receiver.should.equal?(o)
+ else
+ raise
+ end
+ end
+end
+
+describe "FrozenError#message" do
+ it "includes a receiver" do
+ object = Object.new
+ object.freeze
+
+ msg_class = ruby_version_is("4.0") ? "Object" : "object"
+
+ -> {
+ def object.x; end
+ }.should.raise(FrozenError, "can't modify frozen #{msg_class}: #{object}")
+
+ object = [].freeze
+ -> { object << nil }.should.raise(FrozenError, "can't modify frozen Array: []")
+ end
+end
+
+describe "Modifying a frozen object" do
+ context "#inspect is redefined and modifies the object" do
+ it "returns ... instead of String representation of object" do
+ object = Object.new
+ def object.inspect; @a = 1 end
+ def object.modify; @a = 2 end
+
+ object.freeze
+
+ # CRuby's message contains multiple whitespaces before '...'.
+ # So handle both multiple and single whitespace.
+ -> { object.modify }.should.raise(FrozenError, /can't modify frozen .*?: \s*.../)
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/full_message_spec.rb b/spec/ruby/core/exception/full_message_spec.rb
new file mode 100644
index 0000000000..5a5e0a2b3a
--- /dev/null
+++ b/spec/ruby/core/exception/full_message_spec.rb
@@ -0,0 +1,226 @@
+require_relative '../../spec_helper'
+
+describe "Exception#full_message" do
+ it "returns formatted string of exception using the same format that is used to print an uncaught exceptions to stderr" do
+ e = RuntimeError.new("Some runtime error")
+ e.set_backtrace(["a.rb:1", "b.rb:2"])
+
+ full_message = e.full_message
+ full_message.should.include? "RuntimeError"
+ full_message.should.include? "Some runtime error"
+ full_message.should.include? "a.rb:1"
+ full_message.should.include? "b.rb:2"
+ end
+
+ it "supports :highlight option and adds escape sequences to highlight some strings" do
+ e = RuntimeError.new("Some runtime error")
+
+ full_message = e.full_message(highlight: true, order: :top).lines
+ full_message[0].should.end_with? "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)\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[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n"
+
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.end_with? "Some runtime error (RuntimeError)\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? "Some runtime error (RuntimeError)\n"
+ end
+
+ it "supports :order option and places the error message and the backtrace at the top or the bottom" do
+ e = RuntimeError.new("Some runtime error")
+ e.set_backtrace(["a.rb:1", "b.rb:2"])
+
+ e.full_message(order: :top, highlight: false).should =~ /a.rb:1.*b.rb:2/m
+ e.full_message(order: :bottom, highlight: false).should =~ /b.rb:2.*a.rb:1/m
+ end
+
+ it "shows the caller if the exception has no backtrace" do
+ e = RuntimeError.new("Some runtime error")
+ e.backtrace.should == nil
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{__LINE__-1}:in ")
+ full_message[0].should.end_with?("': Some runtime error (RuntimeError)\n")
+ end
+
+ describe "includes details about whether an exception was handled" do
+ describe "RuntimeError" do
+ it "should report as unhandled if message is empty" do
+ err = RuntimeError.new("")
+
+ err.full_message.should =~ /unhandled exception/
+ err.full_message(highlight: true).should =~ /unhandled exception/
+ err.full_message(highlight: false).should =~ /unhandled exception/
+ end
+
+ it "should not report as unhandled if the message is not empty" do
+ err = RuntimeError.new("non-empty")
+
+ err.full_message.should !~ /unhandled exception/
+ err.full_message(highlight: true).should !~ /unhandled exception/
+ err.full_message(highlight: false).should !~ /unhandled exception/
+ end
+
+ it "should not report as unhandled if the message is nil" do
+ err = RuntimeError.new(nil)
+
+ err.full_message.should !~ /unhandled exception/
+ err.full_message(highlight: true).should !~ /unhandled exception/
+ err.full_message(highlight: false).should !~ /unhandled exception/
+ end
+
+ it "should not report as unhandled if the message is not specified" do
+ err = RuntimeError.new()
+
+ err.full_message.should !~ /unhandled exception/
+ err.full_message(highlight: true).should !~ /unhandled exception/
+ err.full_message(highlight: false).should !~ /unhandled exception/
+ end
+
+ it "adds escape sequences to highlight some strings if the message is not specified and :highlight option is specified" do
+ e = RuntimeError.new("")
+
+ full_message = e.full_message(highlight: true, order: :top).lines
+ full_message[0].should.end_with? "\e[1;4munhandled exception\e[m\n"
+
+ full_message = e.full_message(highlight: true, order: :bottom).lines
+ full_message[0].should == "\e[1mTraceback\e[m (most recent call last):\n"
+ full_message[-1].should.end_with? "\e[1;4munhandled exception\e[m\n"
+
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.end_with? "unhandled exception\n"
+
+ full_message = e.full_message(highlight: false, order: :bottom).lines
+ full_message[0].should == "Traceback (most recent call last):\n"
+ full_message[-1].should.end_with? "unhandled exception\n"
+ end
+ end
+
+ describe "generic Error" do
+ it "should not report as unhandled in any event" do
+ StandardError.new("").full_message.should !~ /unhandled exception/
+ StandardError.new("non-empty").full_message.should !~ /unhandled exception/
+ end
+ end
+ end
+
+ it "shows the exception class at the end of the first line of the message when the message contains multiple lines" do
+ begin
+ line = __LINE__; raise "first line\nsecond line"
+ rescue => e
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{line}:in ")
+ full_message[0].should.end_with?(": first line (RuntimeError)\n")
+ full_message[1].should == "second line\n"
+ end
+ end
+
+ it "highlights the entire message when the message contains multiple lines" do
+ begin
+ line = __LINE__; raise "first line\nsecond line\nthird line"
+ rescue => e
+ full_message = e.full_message(highlight: true, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{line}:in ")
+ full_message[0].should.end_with?(": \e[1mfirst line (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n")
+ full_message[1].should == "\e[1msecond line\e[m\n"
+ full_message[2].should == "\e[1mthird line\e[m\n"
+ end
+ end
+
+ it "contains cause of exception" do
+ begin
+ begin
+ raise 'the cause'
+ rescue
+ raise 'main exception'
+ end
+ rescue => e
+ exception = e
+ end
+
+ exception.full_message.should.include? "main exception"
+ exception.full_message.should.include? "the cause"
+ end
+
+ it 'contains all the chain of exceptions' do
+ begin
+ begin
+ begin
+ raise 'origin exception'
+ rescue
+ raise 'intermediate exception'
+ end
+ rescue
+ raise 'last exception'
+ end
+ rescue => e
+ exception = e
+ end
+
+ exception.full_message.should.include? "last exception"
+ exception.full_message.should.include? "intermediate exception"
+ exception.full_message.should.include? "origin exception"
+ end
+
+ it "relies on #detailed_message" do
+ e = RuntimeError.new("new error")
+ e.define_singleton_method(:detailed_message) { |**| "DETAILED MESSAGE" }
+
+ e.full_message.lines.first.should =~ /DETAILED MESSAGE/
+ end
+
+ 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
+
+ it "converts #detailed_message returned value to String if it isn't a String" do
+ message = Object.new
+ def message.to_str; "DETAILED MESSAGE"; end
+
+ e = RuntimeError.new("new error")
+ e.define_singleton_method(:detailed_message) { |**| message }
+
+ e.full_message.lines.first.should =~ /DETAILED MESSAGE/
+ end
+
+ it "uses class name if #detailed_message returns nil" do
+ e = RuntimeError.new("new error")
+ e.define_singleton_method(:detailed_message) { |**| nil }
+
+ e.full_message(highlight: false).lines.first.should =~ /RuntimeError/
+ e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/
+ end
+
+ it "uses class name if exception object doesn't respond to #detailed_message" do
+ e = RuntimeError.new("new error")
+ class << e
+ undef :detailed_message
+ end
+
+ e.full_message(highlight: false).lines.first.should =~ /RuntimeError/
+ e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/
+ end
+
+ it "allows cause with empty backtrace" do
+ begin
+ raise RuntimeError.new("Some runtime error"), cause: RuntimeError.new("Some other runtime error")
+ rescue => e
+ end
+
+ full_message = e.full_message
+ full_message.should.include? "RuntimeError"
+ full_message.should.include? "Some runtime error"
+ full_message.should.include? "Some other runtime error"
+ end
+end
diff --git a/spec/ruby/core/exception/hierarchy_spec.rb b/spec/ruby/core/exception/hierarchy_spec.rb
new file mode 100644
index 0000000000..6514eb1994
--- /dev/null
+++ b/spec/ruby/core/exception/hierarchy_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+
+describe "Exception" do
+ it "has the right class hierarchy" do
+ hierarchy = {
+ Exception => {
+ NoMemoryError => nil,
+ ScriptError => {
+ LoadError => nil,
+ NotImplementedError => nil,
+ SyntaxError => nil,
+ },
+ SecurityError => nil,
+ SignalException => {
+ Interrupt => nil,
+ },
+ StandardError => {
+ ArgumentError => {
+ UncaughtThrowError => nil,
+ },
+ EncodingError => nil,
+ FiberError => nil,
+ IOError => {
+ EOFError => nil,
+ },
+ IndexError => {
+ KeyError => nil,
+ StopIteration => {
+ ClosedQueueError => nil,
+ },
+ },
+ LocalJumpError => nil,
+ NameError => {
+ NoMethodError => nil,
+ },
+ RangeError => {
+ FloatDomainError => nil,
+ },
+ RegexpError => nil,
+ RuntimeError => {
+ FrozenError => nil,
+ },
+ SystemCallError => nil,
+ ThreadError => nil,
+ TypeError => nil,
+ ZeroDivisionError => nil,
+ },
+ SystemExit => nil,
+ SystemStackError => nil,
+ },
+ }
+
+ traverse = -> parent_class, parent_subclass_hash {
+ parent_subclass_hash.each do |child_class, child_subclass_hash|
+ child_class.class.should == Class
+ child_class.superclass.should == parent_class
+ traverse.call(child_class, child_subclass_hash) if child_subclass_hash
+ end
+ }
+ traverse.call(Object, hierarchy)
+ end
+end
diff --git a/spec/ruby/core/exception/inspect_spec.rb b/spec/ruby/core/exception/inspect_spec.rb
new file mode 100644
index 0000000000..6f380a36c7
--- /dev/null
+++ b/spec/ruby/core/exception/inspect_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#inspect" do
+ it "returns '#<Exception: Exception>' when no message given" do
+ Exception.new.inspect.should == "#<Exception: Exception>"
+ end
+
+ it "keeps message encoding" do
+ Exception.new('å').inspect.should == "#<Exception: å>"
+ end
+
+ it "includes #to_s when the result is non-empty" do
+ ExceptionSpecs::OverrideToS.new.inspect.should == "#<ExceptionSpecs::OverrideToS: this is from #to_s>"
+ end
+
+ it "returns the class name when #to_s returns an empty string" do
+ ExceptionSpecs::EmptyToS.new.inspect.should == "ExceptionSpecs::EmptyToS"
+ end
+
+ it "returns the derived class name with a subclassed Exception" do
+ ExceptionSpecs::UnExceptional.new.inspect.should == "#<ExceptionSpecs::UnExceptional: ExceptionSpecs::UnExceptional>"
+ end
+end
diff --git a/spec/ruby/core/exception/interrupt_spec.rb b/spec/ruby/core/exception/interrupt_spec.rb
new file mode 100644
index 0000000000..90d261e470
--- /dev/null
+++ b/spec/ruby/core/exception/interrupt_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+describe "Interrupt.new" do
+ it "returns an instance of interrupt with no message given" do
+ e = Interrupt.new
+ e.signo.should == Signal.list["INT"]
+ e.signm.should == "Interrupt"
+ end
+
+ it "takes an optional message argument" do
+ e = Interrupt.new("message")
+ e.signo.should == Signal.list["INT"]
+ e.signm.should == "message"
+ end
+end
+
+describe "rescuing Interrupt" do
+ before do
+ @original_sigint_proc = Signal.trap(:INT, :SIG_DFL)
+ end
+
+ after do
+ Signal.trap(:INT, @original_sigint_proc)
+ end
+
+ it "raises an Interrupt when sent a signal SIGINT" do
+ begin
+ Process.kill :INT, Process.pid
+ sleep
+ rescue Interrupt => e
+ e.signo.should == Signal.list["INT"]
+ ["", "Interrupt"].should.include?(e.message)
+ end
+ end
+end
+
+describe "Interrupt" do
+ # This spec is basically the same as above,
+ # but it does not rely on Signal.trap(:INT, :SIG_DFL) which can be tricky
+ it "is raised on the main Thread by the default SIGINT handler" do
+ out = ruby_exe(<<-'RUBY', args: "2>&1")
+ begin
+ Process.kill :INT, Process.pid
+ sleep
+ rescue Interrupt => e
+ puts "Interrupt: #{e.signo}"
+ end
+ RUBY
+ out.should == "Interrupt: #{Signal.list["INT"]}\n"
+ end
+
+ platform_is_not :windows do
+ it "shows the backtrace and has a signaled exit status" do
+ err = IO.popen([*ruby_exe, '-e', 'Process.kill :INT, Process.pid; sleep'], err: [:child, :out], &:read)
+ $?.termsig.should == Signal.list.fetch('INT')
+ err.should.include? ': Interrupt'
+ err.should =~ /from -e:1:in [`']<main>'/
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/io_error_spec.rb b/spec/ruby/core/exception/io_error_spec.rb
new file mode 100644
index 0000000000..940d5be876
--- /dev/null
+++ b/spec/ruby/core/exception/io_error_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+
+describe "IO::EAGAINWaitReadable" do
+ it "combines Errno::EAGAIN and IO::WaitReadable" do
+ IO::EAGAINWaitReadable.superclass.should == Errno::EAGAIN
+ IO::EAGAINWaitReadable.ancestors.should.include? IO::WaitReadable
+ end
+
+ it "is the same as IO::EWOULDBLOCKWaitReadable if Errno::EAGAIN is the same as Errno::EWOULDBLOCK" do
+ if Errno::EAGAIN.equal? Errno::EWOULDBLOCK
+ IO::EAGAINWaitReadable.should.equal? IO::EWOULDBLOCKWaitReadable
+ else
+ IO::EAGAINWaitReadable.should_not.equal? IO::EWOULDBLOCKWaitReadable
+ end
+ end
+end
+
+describe "IO::EWOULDBLOCKWaitReadable" do
+ it "combines Errno::EWOULDBLOCK and IO::WaitReadable" do
+ IO::EWOULDBLOCKWaitReadable.superclass.should == Errno::EWOULDBLOCK
+ IO::EAGAINWaitReadable.ancestors.should.include? IO::WaitReadable
+ end
+end
+
+describe "IO::EAGAINWaitWritable" do
+ it "combines Errno::EAGAIN and IO::WaitWritable" do
+ IO::EAGAINWaitWritable.superclass.should == Errno::EAGAIN
+ IO::EAGAINWaitWritable.ancestors.should.include? IO::WaitWritable
+ end
+
+ it "is the same as IO::EWOULDBLOCKWaitWritable if Errno::EAGAIN is the same as Errno::EWOULDBLOCK" do
+ if Errno::EAGAIN.equal? Errno::EWOULDBLOCK
+ IO::EAGAINWaitWritable.should.equal? IO::EWOULDBLOCKWaitWritable
+ else
+ IO::EAGAINWaitWritable.should_not.equal? IO::EWOULDBLOCKWaitWritable
+ end
+ end
+end
+
+describe "IO::EWOULDBLOCKWaitWritable" do
+ it "combines Errno::EWOULDBLOCK and IO::WaitWritable" do
+ IO::EWOULDBLOCKWaitWritable.superclass.should == Errno::EWOULDBLOCK
+ IO::EAGAINWaitWritable.ancestors.should.include? IO::WaitWritable
+ end
+end
diff --git a/spec/ruby/core/exception/key_error_spec.rb b/spec/ruby/core/exception/key_error_spec.rb
new file mode 100644
index 0000000000..c5e2b1efbc
--- /dev/null
+++ b/spec/ruby/core/exception/key_error_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "KeyError" do
+ it "accepts :receiver and :key options" do
+ receiver = mock("receiver")
+ key = mock("key")
+
+ error = KeyError.new(receiver: receiver, key: key)
+
+ error.receiver.should == receiver
+ error.key.should == key
+
+ error = KeyError.new("message", receiver: receiver, key: key)
+
+ error.message.should == "message"
+ error.receiver.should == receiver
+ error.key.should == key
+ end
+end
diff --git a/spec/ruby/core/exception/load_error_spec.rb b/spec/ruby/core/exception/load_error_spec.rb
new file mode 100644
index 0000000000..0056403e58
--- /dev/null
+++ b/spec/ruby/core/exception/load_error_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "LoadError#path" do
+ before :each do
+ @le = LoadError.new
+ end
+
+ it "is nil when constructed directly" do
+ @le.path.should == nil
+ end
+end
+
+describe "LoadError raised by load or require" do
+ it "provides the failing path in its #path attribute" do
+ begin
+ require 'file_that_does_not_exist'
+ rescue LoadError => le
+ le.path.should == 'file_that_does_not_exist'
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/message_spec.rb b/spec/ruby/core/exception/message_spec.rb
new file mode 100644
index 0000000000..8d7476126e
--- /dev/null
+++ b/spec/ruby/core/exception/message_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#message" do
+ it "returns the class name if there is no message" do
+ Exception.new.message.should == "Exception"
+ end
+
+ it "returns the message passed to #initialize" do
+ Exception.new("Ouch!").message.should == "Ouch!"
+ end
+
+ it "calls #to_s on self" do
+ exc = ExceptionSpecs::OverrideToS.new("you won't see this")
+ exc.message.should == "this is from #to_s"
+ end
+
+ context "when #backtrace is redefined" do
+ it "returns the Exception message" do
+ e = Exception.new
+ e.message.should == 'Exception'
+
+ def e.backtrace; []; end
+ e.message.should == 'Exception'
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/name_error_spec.rb b/spec/ruby/core/exception/name_error_spec.rb
new file mode 100644
index 0000000000..ddd51a92e5
--- /dev/null
+++ b/spec/ruby/core/exception/name_error_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+describe "NameError.new" do
+ it "should take optional name argument" do
+ NameError.new("msg","name").name.should == "name"
+ end
+
+ it "accepts a :receiver keyword argument" do
+ receiver = mock("receiver")
+
+ error = NameError.new("msg", :name, receiver: receiver)
+
+ error.receiver.should == receiver
+ error.name.should == :name
+ end
+end
+
+describe "NameError#dup" do
+ it "copies the name and receiver" do
+ begin
+ foo
+ rescue NameError => ne
+ name_error_dup = ne.dup
+ name_error_dup.name.should == :foo
+ name_error_dup.receiver.should == self
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/name_spec.rb b/spec/ruby/core/exception/name_spec.rb
new file mode 100644
index 0000000000..6e0e99d194
--- /dev/null
+++ b/spec/ruby/core/exception/name_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "NameError#name" do
+ it "returns a method name as a symbol" do
+ -> {
+ doesnt_exist
+ }.should.raise(NameError) {|e| e.name.should == :doesnt_exist }
+ end
+
+ it "returns a constant name as a symbol" do
+ -> {
+ DoesntExist
+ }.should.raise(NameError) {|e| e.name.should == :DoesntExist }
+ end
+
+ it "returns a constant name without namespace as a symbol" do
+ -> {
+ Object::DoesntExist
+ }.should.raise(NameError) {|e| e.name.should == :DoesntExist }
+ end
+
+ it "returns a class variable name as a symbol" do
+ -> {
+ eval("class singleton_class::A; @@doesnt_exist end", binding, __FILE__, __LINE__)
+ }.should.raise(NameError) { |e| e.name.should == :@@doesnt_exist }
+ end
+
+ it "returns the first argument passed to the method when a NameError is raised from #instance_variable_get" do
+ invalid_ivar_name = "invalid_ivar_name"
+
+ -> {
+ Object.new.instance_variable_get(invalid_ivar_name)
+ }.should.raise(NameError) {|e| e.name.should.equal?(invalid_ivar_name) }
+ end
+
+ it "returns the first argument passed to the method when a NameError is raised from #class_variable_get" do
+ invalid_cvar_name = "invalid_cvar_name"
+
+ -> {
+ Object.class_variable_get(invalid_cvar_name)
+ }.should.raise(NameError) {|e| e.name.should.equal?(invalid_cvar_name) }
+ end
+end
diff --git a/spec/ruby/core/exception/new_spec.rb b/spec/ruby/core/exception/new_spec.rb
new file mode 100644
index 0000000000..100dbb0a24
--- /dev/null
+++ b/spec/ruby/core/exception/new_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/new'
+
+describe "Exception.new" do
+ it_behaves_like :exception_new, :new
+end
diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb
new file mode 100644
index 0000000000..9f92104082
--- /dev/null
+++ b/spec/ruby/core/exception/no_method_error_spec.rb
@@ -0,0 +1,224 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "NoMethodError.new" do
+ it "allows passing method args" do
+ NoMethodError.new("msg", "name", ["args"]).args.should == ["args"]
+ end
+
+ it "does not require a name" do
+ NoMethodError.new("msg").message.should == "msg"
+ end
+
+ it "accepts a :receiver keyword argument" do
+ receiver = mock("receiver")
+
+ error = NoMethodError.new("msg", :name, receiver: receiver)
+
+ error.receiver.should == receiver
+ error.name.should == :name
+ end
+end
+
+describe "NoMethodError#args" do
+ it "returns an empty array if the caller method had no arguments" do
+ begin
+ NoMethodErrorSpecs::NoMethodErrorB.new.foo
+ rescue Exception => e
+ e.args.should == []
+ end
+ end
+
+ it "returns an array with the same elements as passed to the method" do
+ begin
+ a = NoMethodErrorSpecs::NoMethodErrorA.new
+ NoMethodErrorSpecs::NoMethodErrorB.new.foo(1,a)
+ rescue Exception => e
+ e.args.should == [1,a]
+ e.args[1].should.equal? a
+ end
+ end
+end
+
+describe "NoMethodError#message" do
+ it "for an undefined method match /undefined method/" do
+ begin
+ NoMethodErrorSpecs::NoMethodErrorD.new.foo
+ rescue Exception => e
+ e.should.is_a?(NoMethodError)
+ end
+ end
+
+ it "for an protected method match /protected method/" do
+ begin
+ NoMethodErrorSpecs::NoMethodErrorC.new.a_protected_method
+ rescue Exception => e
+ e.should.is_a?(NoMethodError)
+ end
+ end
+
+ it "for private method match /private method/" do
+ begin
+ NoMethodErrorSpecs::NoMethodErrorC.new.a_private_method
+ rescue Exception => e
+ e.should.is_a?(NoMethodError)
+ e.message.lines[0].should =~ /private method [`']a_private_method' called for /
+ end
+ end
+
+ 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
+ ScratchPad << :inspect_called
+ "<inspect>"
+ end
+ end
+ instance = test_class.new
+
+ begin
+ instance.bar
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']bar' for an instance of #<Class:0x\h+>\Z/
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "does not truncate long class names" do
+ class_name = 'ExceptionSpecs::A' + 'a'*100
+
+ begin
+ eval <<~RUBY
+ class #{class_name}
+ end
+
+ 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
+
+describe "NoMethodError#dup" do
+ it "copies the name, arguments and receiver" do
+ begin
+ receiver = Object.new
+ receiver.foo(:one, :two)
+ rescue NoMethodError => nme
+ no_method_error_dup = nme.dup
+ no_method_error_dup.name.should == :foo
+ no_method_error_dup.receiver.should == receiver
+ no_method_error_dup.args.should == [:one, :two]
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/reason_spec.rb b/spec/ruby/core/exception/reason_spec.rb
new file mode 100644
index 0000000000..d7022768b6
--- /dev/null
+++ b/spec/ruby/core/exception/reason_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "LocalJumpError#reason" do
+ def get_me_a_return
+ Proc.new { return 42 }
+ end
+
+ it "returns 'return' for a return" do
+ -> { get_me_a_return.call }.should.raise(LocalJumpError) { |e|
+ e.reason.should == :return
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/receiver_spec.rb b/spec/ruby/core/exception/receiver_spec.rb
new file mode 100644
index 0000000000..6ecf640ad8
--- /dev/null
+++ b/spec/ruby/core/exception/receiver_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "NameError#receiver" do
+ class ::ReceiverClass
+ def call_undefined_class_variable; @@doesnt_exist end
+ end
+
+ it "returns the object that raised the exception" do
+ receiver = Object.new
+
+ -> {
+ receiver.doesnt_exist
+ }.should.raise(NameError) {|e| e.receiver.should.equal?(receiver) }
+ end
+
+ it "returns the Object class when an undefined constant is called without namespace" do
+ -> {
+ DoesntExist
+ }.should.raise(NameError) {|e| e.receiver.should.equal?(Object) }
+ end
+
+ it "returns a class when an undefined constant is called" do
+ -> {
+ NameErrorSpecs::ReceiverClass::DoesntExist
+ }.should.raise(NameError) {|e| e.receiver.should.equal?(NameErrorSpecs::ReceiverClass) }
+ end
+
+ it "returns the Object class when an undefined class variable is called" do
+ -> {
+ eval("class singleton_class::A; @@doesnt_exist end", binding, __FILE__, __LINE__)
+ }.should.raise(NameError) {|e| e.receiver.should.equal?(singleton_class::A) }
+ end
+
+ it "returns a class when an undefined class variable is called in a subclass' namespace" do
+ -> {
+ NameErrorSpecs::ReceiverClass.new.call_undefined_class_variable
+ }.should.raise(NameError) {|e| e.receiver.should.equal?(NameErrorSpecs::ReceiverClass) }
+ end
+
+ it "returns the receiver when raised from #instance_variable_get" do
+ receiver = Object.new
+
+ -> {
+ receiver.instance_variable_get("invalid_ivar_name")
+ }.should.raise(NameError) {|e| e.receiver.should.equal?(receiver) }
+ end
+
+ it "returns the receiver when raised from #class_variable_get" do
+ -> {
+ Object.class_variable_get("invalid_cvar_name")
+ }.should.raise(NameError) {|e| e.receiver.should.equal?(Object) }
+ end
+
+ it "raises an ArgumentError when the receiver is none" do
+ -> { NameError.new.receiver }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/exception/result_spec.rb b/spec/ruby/core/exception/result_spec.rb
new file mode 100644
index 0000000000..451ff43af5
--- /dev/null
+++ b/spec/ruby/core/exception/result_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+
+describe "StopIteration#result" do
+ before :each do
+ obj = Object.new
+ def obj.each
+ yield :yield_returned_1
+ yield :yield_returned_2
+ :method_returned
+ end
+ @enum = obj.to_enum
+ end
+
+ it "returns the method-returned-object from an Enumerator" do
+ @enum.next
+ @enum.next
+ -> { @enum.next }.should.raise(StopIteration) { |error|
+ error.result.should.equal?(:method_returned)
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/set_backtrace_spec.rb b/spec/ruby/core/exception/set_backtrace_spec.rb
new file mode 100644
index 0000000000..2cd93326ec
--- /dev/null
+++ b/spec/ruby/core/exception/set_backtrace_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/set_backtrace'
+
+describe "Exception#set_backtrace" do
+ it "allows the user to set the backtrace from a rescued exception" do
+ bt = ExceptionSpecs::Backtrace.backtrace
+ err = RuntimeError.new
+ err.backtrace.should == nil
+ err.backtrace_locations.should == nil
+
+ err.set_backtrace bt
+
+ err.backtrace.should == bt
+ err.backtrace_locations.should == nil
+ end
+
+ it_behaves_like :exception_set_backtrace, -> backtrace {
+ err = RuntimeError.new
+ err.set_backtrace(backtrace)
+ err
+ }
+end
diff --git a/spec/ruby/core/exception/shared/new.rb b/spec/ruby/core/exception/shared/new.rb
new file mode 100644
index 0000000000..048fd14dd2
--- /dev/null
+++ b/spec/ruby/core/exception/shared/new.rb
@@ -0,0 +1,18 @@
+describe :exception_new, shared: true do
+ it "creates a new instance of Exception" do
+ Exception.send(@method).class.ancestors.should.include?(Exception)
+ end
+
+ it "sets the message of the Exception when passes a message" do
+ Exception.send(@method, "I'm broken.").message.should == "I'm broken."
+ end
+
+ it "returns 'Exception' for message when no message given" do
+ Exception.send(@method).message.should == "Exception"
+ end
+
+ it "returns the exception when it has a custom constructor" do
+ ExceptionSpecs::ConstructorException.send(@method).should.is_a?(ExceptionSpecs::ConstructorException)
+ end
+
+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..934bf3dc5f
--- /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 == nil
+ end
+
+ it "raises a TypeError when passed a Symbol" do
+ -> { @method.call(:unhappy) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when the Array contains a Symbol" do
+ -> { @method.call(["String", :unhappy]) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when the array contains nil" do
+ -> { @method.call(["String", nil]) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when the argument is a nested array" do
+ -> { @method.call(["String", ["String"]]) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/exception/signal_exception_spec.rb b/spec/ruby/core/exception/signal_exception_spec.rb
new file mode 100644
index 0000000000..010181bc55
--- /dev/null
+++ b/spec/ruby/core/exception/signal_exception_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../../spec_helper'
+
+describe "SignalException.new" do
+ it "takes a signal number as the first argument" do
+ exc = SignalException.new(Signal.list["INT"])
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "raises an exception with an invalid signal number" do
+ -> { SignalException.new(100000) }.should.raise(ArgumentError)
+ end
+
+ it "takes a signal name without SIG prefix as the first argument" do
+ exc = SignalException.new("INT")
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "takes a signal name with SIG prefix as the first argument" do
+ exc = SignalException.new("SIGINT")
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "raises an exception with an invalid signal name" do
+ -> { SignalException.new("NONEXISTENT") }.should.raise(ArgumentError)
+ end
+
+ it "raises an exception with an invalid first argument type" do
+ -> { SignalException.new(Object.new) }.should.raise(ArgumentError)
+ end
+
+ it "takes a signal symbol without SIG prefix as the first argument" do
+ exc = SignalException.new(:INT)
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "takes a signal symbol with SIG prefix as the first argument" do
+ exc = SignalException.new(:SIGINT)
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "SIGINT"
+ exc.message.should == "SIGINT"
+ end
+
+ it "raises an exception with an invalid signal name" do
+ -> { SignalException.new(:NONEXISTENT) }.should.raise(ArgumentError)
+ end
+
+ it "takes an optional message argument with a signal number" do
+ exc = SignalException.new(Signal.list["INT"], "name")
+ exc.signo.should == Signal.list["INT"]
+ exc.signm.should == "name"
+ exc.message.should == "name"
+ end
+
+ it "raises an exception for an optional argument with a signal name" do
+ -> { SignalException.new("INT","name") }.should.raise(ArgumentError)
+ end
+end
+
+describe "rescuing SignalException" do
+ it "raises a SignalException when sent a signal" do
+ begin
+ Process.kill :TERM, Process.pid
+ sleep
+ rescue SignalException => e
+ e.signo.should == Signal.list["TERM"]
+ e.signm.should == "SIGTERM"
+ e.message.should == "SIGTERM"
+ end
+ end
+end
+
+describe "SignalException" do
+ it "can be rescued" do
+ ruby_exe(<<-RUBY)
+ begin
+ raise SignalException, 'SIGKILL'
+ rescue SignalException
+ exit(0)
+ end
+ exit(1)
+ RUBY
+
+ $?.exitstatus.should == 0
+ end
+
+ platform_is_not :windows do
+ it "runs after at_exit" do
+ output = ruby_exe(<<-RUBY, exit_status: :SIGKILL)
+ at_exit do
+ puts "hello"
+ $stdout.flush
+ end
+
+ raise SignalException, 'SIGKILL'
+ RUBY
+
+ $?.termsig.should == Signal.list.fetch("KILL")
+ output.should == "hello\n"
+ end
+
+ it "cannot be trapped with Signal.trap" do
+ ruby_exe(<<-RUBY, exit_status: :SIGPROF)
+ Signal.trap("PROF") {}
+ raise(SignalException, "PROF")
+ RUBY
+
+ $?.termsig.should == Signal.list.fetch("PROF")
+ end
+
+ it "self-signals for USR1" do
+ ruby_exe("raise(SignalException, 'USR1')", exit_status: :SIGUSR1)
+ $?.termsig.should == Signal.list.fetch('USR1')
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/signm_spec.rb b/spec/ruby/core/exception/signm_spec.rb
new file mode 100644
index 0000000000..cabcc7ad58
--- /dev/null
+++ b/spec/ruby/core/exception/signm_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "SignalException#signm" do
+ it "returns the signal name" do
+ -> { Process.kill(:TERM, Process.pid) }.should.raise(SignalException) { |e|
+ e.signm.should == 'SIGTERM'
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/signo_spec.rb b/spec/ruby/core/exception/signo_spec.rb
new file mode 100644
index 0000000000..46e79a8daf
--- /dev/null
+++ b/spec/ruby/core/exception/signo_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "SignalException#signo" do
+ it "returns the signal number" do
+ -> { Process.kill(:TERM, Process.pid) }.should.raise(SignalException) { |e|
+ e.signo.should == Signal.list['TERM']
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/standard_error_spec.rb b/spec/ruby/core/exception/standard_error_spec.rb
new file mode 100644
index 0000000000..b05d247f67
--- /dev/null
+++ b/spec/ruby/core/exception/standard_error_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "StandardError" do
+ it "rescues StandardError" do
+ begin
+ raise StandardError
+ rescue => exception
+ exception.class.should == StandardError
+ end
+ end
+
+ it "rescues subclass of StandardError" do
+ begin
+ raise RuntimeError
+ rescue => exception
+ exception.class.should == RuntimeError
+ end
+ end
+
+ it "does not rescue superclass of StandardError" do
+ -> { begin; raise Exception; rescue; end }.should.raise(Exception)
+ end
+end
diff --git a/spec/ruby/core/exception/status_spec.rb b/spec/ruby/core/exception/status_spec.rb
new file mode 100644
index 0000000000..7369b0815d
--- /dev/null
+++ b/spec/ruby/core/exception/status_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "SystemExit#status" do
+ it "returns the exit status" do
+ -> { exit 42 }.should.raise(SystemExit) { |e|
+ e.status.should == 42
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/success_spec.rb b/spec/ruby/core/exception/success_spec.rb
new file mode 100644
index 0000000000..5ab8f94454
--- /dev/null
+++ b/spec/ruby/core/exception/success_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "SystemExit#success?" do
+ it "returns true if the process exited successfully" do
+ -> { exit 0 }.should.raise(SystemExit) { |e|
+ e.should.success?
+ }
+ end
+
+ it "returns false if the process exited unsuccessfully" do
+ -> { exit(-1) }.should.raise(SystemExit) { |e|
+ e.should_not.success?
+ }
+ end
+end
diff --git a/spec/ruby/core/exception/syntax_error_spec.rb b/spec/ruby/core/exception/syntax_error_spec.rb
new file mode 100644
index 0000000000..66eb5649aa
--- /dev/null
+++ b/spec/ruby/core/exception/syntax_error_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "SyntaxError#path" do
+ it "returns the file path provided to eval" do
+ filename = "speccing.rb"
+
+ -> {
+ eval("if true", TOPLEVEL_BINDING, filename)
+ }.should.raise(SyntaxError) { |e|
+ e.path.should == filename
+ }
+ end
+
+ it "returns the file path that raised an exception" do
+ expected_path = fixture(__FILE__, "syntax_error.rb")
+
+ -> {
+ require_relative "fixtures/syntax_error"
+ }.should.raise(SyntaxError) { |e| e.path.should == expected_path }
+ end
+
+ it "returns nil when constructed directly" do
+ SyntaxError.new.path.should == nil
+ end
+end
diff --git a/spec/ruby/core/exception/system_call_error_spec.rb b/spec/ruby/core/exception/system_call_error_spec.rb
new file mode 100644
index 0000000000..da01c5b6b0
--- /dev/null
+++ b/spec/ruby/core/exception/system_call_error_spec.rb
@@ -0,0 +1,163 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "SystemCallError" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "can be subclassed" do
+ ExceptionSpecs::SCESub = Class.new(SystemCallError) do
+ def initialize
+ ScratchPad.record :initialize
+ end
+ end
+
+ exc = ExceptionSpecs::SCESub.new
+ ScratchPad.recorded.should.equal?(:initialize)
+ exc.should.instance_of?(ExceptionSpecs::SCESub)
+ ensure
+ ExceptionSpecs.send(:remove_const, :SCESub)
+ end
+end
+
+describe "SystemCallError.new" do
+ before :all do
+ @example_errno = Errno::EINVAL::Errno
+ @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
+ -> { SystemCallError.new }.should.raise(ArgumentError)
+ end
+
+ it "accepts single Integer argument as errno" do
+ SystemCallError.new(-2**24).errno.should == -2**24
+ SystemCallError.new(-1).errno.should == -1
+ SystemCallError.new(0).errno.should == 0
+ SystemCallError.new(@last_known_errno).errno.should == @last_known_errno
+ SystemCallError.new(@unknown_errno).errno.should == @unknown_errno
+ SystemCallError.new(2**24).errno.should == 2**24
+ end
+
+ it "constructs a SystemCallError for an unknown error number" do
+ SystemCallError.new(-2**24).should.instance_of?(SystemCallError)
+ SystemCallError.new(-1).should.instance_of?(SystemCallError)
+ SystemCallError.new(@unknown_errno).should.instance_of?(SystemCallError)
+ SystemCallError.new(2**24).should.instance_of?(SystemCallError)
+ end
+
+ it "constructs the appropriate Errno class" do
+ e = SystemCallError.new(@example_errno)
+ e.should.is_a?(SystemCallError)
+ e.should.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.instance_of?(@example_errno_class)
+ exc.errno.should == @example_errno
+ exc.message.should == 'Invalid argument - custom message'
+ end
+
+ it "accepts an optional third argument specifying the location" do
+ exc = SystemCallError.new("custom message", @example_errno, "location")
+ exc.should.instance_of?(@example_errno_class)
+ exc.errno.should == @example_errno
+ exc.message.should == 'Invalid argument @ location - custom message'
+ end
+
+ it "coerces location if it is not a String" do
+ e = SystemCallError.new('foo', 1, :not_a_string)
+ e.message.should =~ /@ not_a_string - foo/
+ end
+
+ it "returns an arity of -1 for the initialize method" do
+ SystemCallError.instance_method(:initialize).arity.should == -1
+ end
+
+ it "converts to Integer if errno is a Float" do
+ SystemCallError.new('foo', 2.0).should == SystemCallError.new('foo', 2)
+ SystemCallError.new('foo', 2.9).should == SystemCallError.new('foo', 2)
+ end
+
+ it "treats nil errno as unknown error value" do
+ SystemCallError.new(nil).should.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
+
+ it "raises TypeError if message is not a String" do
+ -> { SystemCallError.new(:foo, 1) }.should.raise(TypeError, /no implicit conversion of Symbol into String/)
+ end
+
+ it "raises TypeError if errno is not an Integer" do
+ -> { SystemCallError.new('foo', 'bar') }.should.raise(TypeError, /no implicit conversion of String into Integer/)
+ end
+
+ it "raises RangeError if errno is a Complex not convertible to Integer" do
+ -> { SystemCallError.new('foo', Complex(2.9, 1)) }.should.raise(RangeError, /can't convert/)
+ end
+end
+
+describe "SystemCallError#errno" do
+ it "returns nil when no errno given" do
+ SystemCallError.new("message").errno.should == nil
+ end
+
+ it "returns the errno given as optional argument to new" do
+ SystemCallError.new("message", -2**20).errno.should == -2**20
+ SystemCallError.new("message", -1).errno.should == -1
+ SystemCallError.new("message", 0).errno.should == 0
+ SystemCallError.new("message", 1).errno.should == 1
+ SystemCallError.new("message", 42).errno.should == 42
+ SystemCallError.new("message", 2**20).errno.should == 2**20
+ end
+end
+
+describe "SystemCallError#message" do
+ it "returns the default message when no message is given" do
+ SystemCallError.new(2**28).message.should =~ @some_human_readable
+ end
+
+ it "returns the message given as an argument to new" do
+ SystemCallError.new("message", 1).message.should =~ /message/
+ SystemCallError.new("XXX").message.should =~ /XXX/
+ end
+end
+
+describe "SystemCallError#dup" do
+ it "copies the errno" do
+ dup_sce = SystemCallError.new("message", 42).dup
+ dup_sce.errno.should == 42
+ end
+end
+
+describe "SystemCallError#backtrace" do
+ it "is nil if not raised" do
+ SystemCallError.new("message", 42).backtrace.should == nil
+ end
+end
diff --git a/spec/ruby/core/exception/system_exit_spec.rb b/spec/ruby/core/exception/system_exit_spec.rb
new file mode 100644
index 0000000000..d899844c4e
--- /dev/null
+++ b/spec/ruby/core/exception/system_exit_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+
+describe "SystemExit" do
+ describe "#initialize" do
+ it "accepts a status and message" do
+ exc = SystemExit.new(42, "message")
+ exc.status.should == 42
+ exc.message.should == "message"
+
+ exc = SystemExit.new(true, "message")
+ exc.status.should == 0
+ exc.message.should == "message"
+
+ exc = SystemExit.new(false, "message")
+ exc.status.should == 1
+ exc.message.should == "message"
+ end
+
+ it "accepts a status only" do
+ exc = SystemExit.new(42)
+ exc.status.should == 42
+ exc.message.should == "SystemExit"
+
+ exc = SystemExit.new(true)
+ exc.status.should == 0
+ exc.message.should == "SystemExit"
+
+ exc = SystemExit.new(false)
+ exc.status.should == 1
+ exc.message.should == "SystemExit"
+ end
+
+ it "accepts a message only" do
+ exc = SystemExit.new("message")
+ exc.status.should == 0
+ exc.message.should == "message"
+ end
+
+ it "accepts no arguments" do
+ exc = SystemExit.new
+ exc.status.should == 0
+ exc.message.should == "SystemExit"
+ end
+ end
+
+ it "sets the exit status and exits silently when raised" do
+ code = 'raise SystemExit.new(7)'
+ result = ruby_exe(code, args: "2>&1", exit_status: 7)
+ result.should == ""
+ $?.exitstatus.should == 7
+ end
+
+ it "sets the exit status and exits silently when raised when subclassed" do
+ code = 'class CustomExit < SystemExit; end; raise CustomExit.new(8)'
+ result = ruby_exe(code, args: "2>&1", exit_status: 8)
+ result.should == ""
+ $?.exitstatus.should == 8
+ end
+end
diff --git a/spec/ruby/core/exception/to_s_spec.rb b/spec/ruby/core/exception/to_s_spec.rb
new file mode 100644
index 0000000000..65c0d73a98
--- /dev/null
+++ b/spec/ruby/core/exception/to_s_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#to_s" do
+ it "returns the self's name if no message is set" do
+ Exception.new.to_s.should == 'Exception'
+ ExceptionSpecs::Exceptional.new.to_s.should == 'ExceptionSpecs::Exceptional'
+ end
+
+ it "returns self's message if set" do
+ ExceptionSpecs::Exceptional.new('!!').to_s.should == '!!'
+ end
+
+ it "calls #to_s on the message" do
+ message = mock("message")
+ message.should_receive(:to_s).and_return("message")
+ ExceptionSpecs::Exceptional.new(message).to_s.should == "message"
+ end
+end
+
+describe "NameError#to_s" do
+ it "raises its own message for an undefined variable" do
+ begin
+ puts not_defined
+ rescue => exception
+ exception.message.should =~ /undefined local variable or method [`']not_defined'/
+ end
+ end
+
+ it "raises its own message for an undefined constant" do
+ begin
+ puts NotDefined
+ rescue => exception
+ exception.message.should =~ /uninitialized constant NotDefined/
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/top_level_spec.rb b/spec/ruby/core/exception/top_level_spec.rb
new file mode 100644
index 0000000000..cc961d06d5
--- /dev/null
+++ b/spec/ruby/core/exception/top_level_spec.rb
@@ -0,0 +1,65 @@
+require_relative '../../spec_helper'
+
+describe "An Exception reaching the top level" do
+ it "is printed on STDERR" do
+ ruby_exe('raise "foo"', args: "2>&1", exit_status: 1).should =~ /in [`']<main>': foo \(RuntimeError\)/
+ end
+
+ it "the Exception#cause is printed to STDERR with backtraces" do
+ code = <<-RUBY
+ def raise_cause
+ raise "the cause" # 2
+ end
+ def raise_wrapped
+ raise "wrapped" # 5
+ end
+ begin
+ raise_cause # 8
+ rescue
+ raise_wrapped # 10
+ end
+ RUBY
+ lines = ruby_exe(code, args: "2>&1", exit_status: 1).lines
+
+ lines.map! { |l| l.chomp[/:(\d+:in.+)/, 1] }
+ lines[0].should =~ /\A5:in [`'](?:Object#)?raise_wrapped': wrapped \(RuntimeError\)\z/
+ if lines[1].include? 'rescue in'
+ # CRuby < 3.4 has an extra 'rescue in' backtrace entry
+ lines[1].should =~ /\A10:in [`']rescue in <main>'\z/
+ lines.delete_at 1
+ lines[1].should =~ /\A7:in [`']<main>'\z/
+ else
+ lines[1].should =~ /\A10:in [`']<main>'\z/
+ end
+ lines[2].should =~ /\A2:in [`'](?:Object#)?raise_cause': the cause \(RuntimeError\)\z/
+ lines[3].should =~ /\A8:in [`']<main>'\z/
+ lines.size.should == 4
+ end
+
+ describe "with a custom backtrace" do
+ it "is printed on STDERR" do
+ code = <<-RUBY
+ raise RuntimeError, "foo", [
+ "/dir/foo.rb:10:in `raising'",
+ "/dir/bar.rb:20:in `caller'",
+ ]
+ RUBY
+ ruby_exe(code, args: "2>&1", exit_status: 1).should == <<-EOS
+/dir/foo.rb:10:in `raising': foo (RuntimeError)
+\tfrom /dir/bar.rb:20:in `caller'
+ EOS
+ end
+ end
+
+ describe "kills all threads and fibers, ensure clauses are only run for threads current fibers, not for suspended fibers" do
+ it "with ensure on the root fiber" do
+ file = fixture(__FILE__, "thread_fiber_ensure.rb")
+ ruby_exe(file, args: "2>&1", exit_status: 0).should == "current fiber ensure\n"
+ end
+
+ it "with ensure on non-root fiber" do
+ file = fixture(__FILE__, "thread_fiber_ensure_non_root_fiber.rb")
+ ruby_exe(file, args: "2>&1", exit_status: 0).should == "current fiber ensure\n"
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/uncaught_throw_error_spec.rb b/spec/ruby/core/exception/uncaught_throw_error_spec.rb
new file mode 100644
index 0000000000..9267df6670
--- /dev/null
+++ b/spec/ruby/core/exception/uncaught_throw_error_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "UncaughtThrowError#tag" do
+ it "returns the object thrown" do
+ begin
+ throw :abc
+
+ rescue UncaughtThrowError => e
+ e.tag.should == :abc
+ end
+ end
+end
diff --git a/spec/ruby/core/false/and_spec.rb b/spec/ruby/core/false/and_spec.rb
new file mode 100644
index 0000000000..0b02ae62c5
--- /dev/null
+++ b/spec/ruby/core/false/and_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#&" do
+ it "returns false" do
+ (false & false).should == false
+ (false & true).should == false
+ (false & nil).should == false
+ (false & "").should == false
+ (false & mock('x')).should == false
+ end
+end
diff --git a/spec/ruby/core/false/case_compare_spec.rb b/spec/ruby/core/false/case_compare_spec.rb
new file mode 100644
index 0000000000..0bd0ab44ae
--- /dev/null
+++ b/spec/ruby/core/false/case_compare_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#===" do
+ it "returns true for false" do
+ (false === false).should == true
+ end
+
+ it "returns false for non-false object" do
+ (false === 0).should == false
+ (false === "").should == false
+ (false === Object).should == false
+ (false === nil).should == false
+ end
+end
diff --git a/spec/ruby/core/false/dup_spec.rb b/spec/ruby/core/false/dup_spec.rb
new file mode 100644
index 0000000000..b0eb85529e
--- /dev/null
+++ b/spec/ruby/core/false/dup_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#dup" do
+ it "returns self" do
+ false.dup.should.equal?(false)
+ end
+end
diff --git a/spec/ruby/core/false/falseclass_spec.rb b/spec/ruby/core/false/falseclass_spec.rb
new file mode 100644
index 0000000000..8dfe5ae891
--- /dev/null
+++ b/spec/ruby/core/false/falseclass_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass" do
+ it ".allocate raises a TypeError" do
+ -> do
+ FalseClass.allocate
+ end.should.raise(TypeError)
+ end
+
+ it ".new is undefined" do
+ -> do
+ FalseClass.new
+ end.should.raise(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/false/inspect_spec.rb b/spec/ruby/core/false/inspect_spec.rb
new file mode 100644
index 0000000000..4cbb55d434
--- /dev/null
+++ b/spec/ruby/core/false/inspect_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#inspect" do
+ it "returns the string 'false'" do
+ false.inspect.should == "false"
+ end
+end
diff --git a/spec/ruby/core/false/or_spec.rb b/spec/ruby/core/false/or_spec.rb
new file mode 100644
index 0000000000..f3ee1a3439
--- /dev/null
+++ b/spec/ruby/core/false/or_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#|" do
+ it "returns false if other is nil or false, otherwise true" do
+ (false | false).should == false
+ (false | true).should == true
+ (false | nil).should == false
+ (false | "").should == true
+ (false | mock('x')).should == true
+ end
+end
diff --git a/spec/ruby/core/false/singleton_method_spec.rb b/spec/ruby/core/false/singleton_method_spec.rb
new file mode 100644
index 0000000000..72aee8609a
--- /dev/null
+++ b/spec/ruby/core/false/singleton_method_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#singleton_method" do
+ it "raises regardless of whether FalseClass defines the method" do
+ -> { false.singleton_method(:foo) }.should.raise(NameError)
+ begin
+ def (false).foo; end
+ -> { false.singleton_method(:foo) }.should.raise(NameError)
+ ensure
+ FalseClass.send(:remove_method, :foo)
+ end
+ end
+end
diff --git a/spec/ruby/core/false/to_s_spec.rb b/spec/ruby/core/false/to_s_spec.rb
new file mode 100644
index 0000000000..9e24be26a2
--- /dev/null
+++ b/spec/ruby/core/false/to_s_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#to_s" do
+ it "returns the string 'false'" do
+ false.to_s.should == "false"
+ end
+
+ it "returns a frozen string" do
+ false.to_s.should.frozen?
+ end
+
+ it "always returns the same string" do
+ false.to_s.should.equal?(false.to_s)
+ end
+end
diff --git a/spec/ruby/core/false/xor_spec.rb b/spec/ruby/core/false/xor_spec.rb
new file mode 100644
index 0000000000..1b87b9f412
--- /dev/null
+++ b/spec/ruby/core/false/xor_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "FalseClass#^" do
+ it "returns false if other is nil or false, otherwise true" do
+ (false ^ false).should == false
+ (false ^ true).should == true
+ (false ^ nil).should == false
+ (false ^ "").should == true
+ (false ^ mock('x')).should == true
+ end
+end
diff --git a/spec/ruby/core/fiber/alive_spec.rb b/spec/ruby/core/fiber/alive_spec.rb
new file mode 100644
index 0000000000..6fb1229d95
--- /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 == 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 == true
+ end
+
+ it "returns true when called from its Fiber" do
+ fiber = Fiber.new { fiber.alive?.should == 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(FiberError)
+ fiber.alive?.should == false
+ end
+
+ it "always returns false for a dead Fiber" do
+ fiber = Fiber.new { true }
+ fiber.resume
+ -> { fiber.resume }.should.raise(FiberError)
+ fiber.alive?.should == false
+ -> { fiber.resume }.should.raise(FiberError)
+ fiber.alive?.should == false
+ fiber.alive?.should == false
+ end
+end
diff --git a/spec/ruby/core/fiber/blocking_spec.rb b/spec/ruby/core/fiber/blocking_spec.rb
new file mode 100644
index 0000000000..d5caf81fbe
--- /dev/null
+++ b/spec/ruby/core/fiber/blocking_spec.rb
@@ -0,0 +1,73 @@
+require_relative '../../spec_helper'
+require_relative 'shared/blocking'
+
+describe "Fiber.blocking?" do
+ it_behaves_like :non_blocking_fiber, -> { Fiber.blocking? }
+
+ context "when fiber is blocking" do
+ context "root Fiber of the main thread" do
+ it "returns 1 for blocking: true" do
+ fiber = Fiber.new(blocking: true) { Fiber.blocking? }
+ blocking = fiber.resume
+
+ blocking.should == 1
+ end
+ end
+
+ context "root Fiber of a new thread" do
+ it "returns 1 for blocking: true" do
+ thread = Thread.new do
+ fiber = Fiber.new(blocking: true) { Fiber.blocking? }
+ blocking = fiber.resume
+
+ blocking.should == 1
+ end
+
+ thread.join
+ end
+ end
+ end
+end
+
+describe "Fiber#blocking?" do
+ it_behaves_like :non_blocking_fiber, -> { Fiber.current.blocking? }
+
+ context "when fiber is blocking" do
+ context "root Fiber of the main thread" do
+ it "returns true for blocking: true" do
+ fiber = Fiber.new(blocking: true) { Fiber.current.blocking? }
+ blocking = fiber.resume
+
+ blocking.should == true
+ end
+ end
+
+ context "root Fiber of a new thread" do
+ it "returns true for blocking: true" do
+ thread = Thread.new do
+ fiber = Fiber.new(blocking: true) { Fiber.current.blocking? }
+ blocking = fiber.resume
+
+ blocking.should == true
+ end
+
+ thread.join
+ end
+ end
+ end
+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
+ 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..cc5c9117b6
--- /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.instance_of?(Fiber)
+ # We can always transfer to the root Fiber; it will never die
+ 5.times do
+ root.transfer.should == nil
+ root.alive?.should == true
+ end
+ end
+
+ it "returns the current Fiber when called from a Fiber" do
+ fiber = Fiber.new do
+ this = Fiber.current
+ this.should.instance_of?(Fiber)
+ this.should == fiber
+ this.alive?.should == 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.instance_of?(Fiber)
+ this.should == fiber
+ this.alive?.should == 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
new file mode 100644
index 0000000000..6b0e0fbc42
--- /dev/null
+++ b/spec/ruby/core/fiber/fixtures/classes.rb
@@ -0,0 +1,22 @@
+module FiberSpecs
+
+ class NewFiberToRaise
+ 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, **kwargs)
+ end
+ end
+
+ class CustomError < StandardError; 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
new file mode 100644
index 0000000000..fcfef20716
--- /dev/null
+++ b/spec/ruby/core/fiber/inspect_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+describe "Fiber#inspect" do
+ describe "status" do
+ it "is resumed for the root Fiber of a Thread" do
+ inspected = Thread.new { Fiber.current.inspect }.value
+ inspected.should =~ /\A#<Fiber:0x\h+ .*\(resumed\)>\z/
+ end
+
+ it "is created for a Fiber which did not run yet" do
+ inspected = Fiber.new {}.inspect
+ inspected.should =~ /\A#<Fiber:0x\h+ .+ \(created\)>\z/
+ end
+
+ it "is resumed for a Fiber which was resumed" do
+ inspected = Fiber.new { Fiber.current.inspect }.resume
+ inspected.should =~ /\A#<Fiber:0x\h+ .+ \(resumed\)>\z/
+ end
+
+ it "is resumed for a Fiber which was transferred" do
+ inspected = Fiber.new { Fiber.current.inspect }.transfer
+ inspected.should =~ /\A#<Fiber:0x\h+ .+ \(resumed\)>\z/
+ end
+
+ it "is suspended for a Fiber which was resumed and yielded" do
+ inspected = Fiber.new { Fiber.yield }.tap(&:resume).inspect
+ inspected.should =~ /\A#<Fiber:0x\h+ .+ \(suspended\)>\z/
+ end
+
+ it "is terminated for a Fiber which has terminated" do
+ inspected = Fiber.new {}.tap(&:resume).inspect
+ inspected.should =~ /\A#<Fiber:0x\h+ .+ \(terminated\)>\z/
+ end
+ end
+end
diff --git a/spec/ruby/core/fiber/kill_spec.rb b/spec/ruby/core/fiber/kill_spec.rb
new file mode 100644
index 0000000000..abf23ff176
--- /dev/null
+++ b/spec/ruby/core/fiber/kill_spec.rb
@@ -0,0 +1,88 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/kernel/raise'
+
+describe "Fiber#kill" do
+ it "kills a non-resumed fiber" do
+ fiber = Fiber.new{}
+
+ fiber.alive?.should == true
+
+ fiber.kill
+ fiber.alive?.should == false
+ end
+
+ it "kills a resumed fiber" do
+ fiber = Fiber.new{while true; Fiber.yield; end}
+ fiber.resume
+
+ fiber.alive?.should == true
+
+ fiber.kill
+ fiber.alive?.should == false
+ end
+
+ it "can kill itself" do
+ fiber = Fiber.new do
+ Fiber.current.kill
+ end
+
+ fiber.alive?.should == true
+
+ fiber.resume
+ fiber.alive?.should == false
+ end
+
+ it "kills a resumed fiber from a child" do
+ parent = Fiber.new do
+ child = Fiber.new do
+ parent.kill
+ parent.alive?.should == true
+ end
+
+ child.resume
+ end
+
+ parent.resume
+ parent.alive?.should == false
+ end
+
+ it "executes the ensure block" do
+ ensure_executed = false
+
+ fiber = Fiber.new do
+ while true; Fiber.yield; end
+ ensure
+ ensure_executed = true
+ end
+
+ fiber.resume
+ fiber.kill
+ ensure_executed.should == true
+ end
+
+ it "does not execute rescue block" do
+ rescue_executed = false
+
+ fiber = Fiber.new do
+ while true; Fiber.yield; end
+ rescue Exception
+ rescue_executed = true
+ end
+
+ fiber.resume
+ fiber.kill
+ rescue_executed.should == false
+ end
+
+ it "repeatedly kills a fiber" do
+ fiber = Fiber.new do
+ while true; Fiber.yield; end
+ ensure
+ while true; Fiber.yield; end
+ end
+
+ fiber.kill
+ fiber.alive?.should == false
+ end
+end
diff --git a/spec/ruby/core/fiber/new_spec.rb b/spec/ruby/core/fiber/new_spec.rb
new file mode 100644
index 0000000000..d31167496d
--- /dev/null
+++ b/spec/ruby/core/fiber/new_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Fiber.new" do
+ it "creates a fiber from the given block" do
+ fiber = Fiber.new {}
+ fiber.resume
+ fiber.should.instance_of?(Fiber)
+ end
+
+ it "creates a fiber from a subclass" do
+ class MyFiber < Fiber
+ end
+ fiber = MyFiber.new {}
+ fiber.resume
+ fiber.should.instance_of?(MyFiber)
+ end
+
+ it "raises an ArgumentError if called without a block" do
+ -> { Fiber.new }.should.raise(ArgumentError)
+ end
+
+ it "does not invoke the block" do
+ invoked = false
+ fiber = Fiber.new { invoked = true }
+ invoked.should == false
+ fiber.resume
+ end
+
+ it "closes over lexical environments" do
+ o = Object.new
+ def o.f
+ a = 1
+ f = Fiber.new { a = 2 }
+ f.resume
+ a
+ end
+ o.f.should == 2
+ end
+end
diff --git a/spec/ruby/core/fiber/raise_spec.rb b/spec/ruby/core/fiber/raise_spec.rb
new file mode 100644
index 0000000000..107e5bd4ce
--- /dev/null
+++ b/spec/ruby/core/fiber/raise_spec.rb
@@ -0,0 +1,141 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/kernel/raise'
+
+describe "Fiber#raise" do
+ it "is a public method" do
+ Fiber.public_instance_methods.should.include?(:raise)
+ end
+
+ it_behaves_like :kernel_raise, :raise, FiberSpecs::NewFiberToRaise
+ it_behaves_like :kernel_raise_across_contexts, :raise, FiberSpecs::NewFiberToRaise
+ ruby_version_is "4.0" do
+ it_behaves_like :kernel_raise_with_cause, :raise, FiberSpecs::NewFiberToRaise
+ end
+
+ it 'raises RuntimeError by default' do
+ -> { FiberSpecs::NewFiberToRaise.raise }.should.raise(RuntimeError)
+ end
+
+ it "raises FiberError if Fiber is not born" do
+ fiber = Fiber.new { true }
+ -> { fiber.raise }.should.raise(FiberError, "cannot raise exception on unborn fiber")
+ end
+
+ it "raises FiberError if Fiber is dead" do
+ fiber = Fiber.new { true }
+ fiber.resume
+ -> { fiber.raise }.should.raise(FiberError, /dead fiber called|attempt to resume a terminated fiber/)
+ end
+
+ it 'accepts error class' do
+ -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError }.should.raise(FiberSpecs::CustomError)
+ end
+
+ it 'accepts error message' do
+ -> { FiberSpecs::NewFiberToRaise.raise "error message" }.should.raise(RuntimeError, "error message")
+ end
+
+ it 'does not accept array of backtrace information only' do
+ -> { FiberSpecs::NewFiberToRaise.raise ['foo'] }.should.raise(TypeError)
+ end
+
+ it 'does not accept integer' do
+ -> { FiberSpecs::NewFiberToRaise.raise 100 }.should.raise(TypeError)
+ end
+
+ it 'accepts error class with error message' do
+ -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error' }.should.raise(FiberSpecs::CustomError, 'test error')
+ end
+
+ it 'accepts error class with error message and backtrace information' do
+ -> {
+ FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error', ['foo', 'boo']
+ }.should.raise(FiberSpecs::CustomError) { |e|
+ e.message.should == 'test error'
+ e.backtrace.should == ['foo', 'boo']
+ }
+ end
+
+ it 'does not accept only error message and backtrace information' do
+ -> { FiberSpecs::NewFiberToRaise.raise 'test error', ['foo', 'boo'] }.should.raise(TypeError)
+ end
+
+ it "raises a FiberError if invoked from a different Thread" do
+ fiber = Fiber.new { Fiber.yield }
+ fiber.resume
+ Thread.new do
+ -> {
+ fiber.raise
+ }.should.raise(FiberError, "fiber called across threads")
+ end.join
+ end
+
+ it "kills Fiber" do
+ fiber = Fiber.new { Fiber.yield :first; :second }
+ fiber.resume
+ -> { fiber.raise }.should.raise
+ -> { fiber.resume }.should.raise(FiberError, /dead fiber called|attempt to resume a terminated fiber/)
+ end
+
+ it "returns to calling fiber after raise" do
+ fiber_one = Fiber.new do
+ Fiber.yield :yield_one
+ :unreachable
+ end
+
+ fiber_two = Fiber.new do
+ results = []
+ results << fiber_one.resume
+ begin
+ fiber_one.raise
+ rescue
+ results << :rescued
+ end
+ results
+ end
+
+ fiber_two.resume.should == [:yield_one, :rescued]
+ end
+
+ ruby_version_is "3.4" do
+ it "raises on the resumed fiber" do
+ root_fiber = Fiber.current
+ f1 = Fiber.new { root_fiber.transfer }
+ f2 = Fiber.new { f1.resume }
+ f2.transfer
+
+ -> do
+ f2.raise(RuntimeError, "Expected error")
+ end.should.raise(RuntimeError, "Expected error")
+ end
+
+ it "raises on itself" do
+ -> do
+ Fiber.current.raise(RuntimeError, "Expected error")
+ end.should.raise(RuntimeError, "Expected error")
+ end
+
+ it "should raise on parent fiber" do
+ f2 = nil
+ f1 = Fiber.new do
+ # This is equivalent to Kernel#raise:
+ f2.raise(RuntimeError, "Expected error")
+ end
+ f2 = Fiber.new do
+ f1.resume
+ end
+
+ -> do
+ f2.resume
+ end.should.raise(RuntimeError, "Expected error")
+ end
+ end
+
+ it "transfers and raises on a transferring fiber" do
+ root = Fiber.current
+ fiber = Fiber.new { root.transfer }
+ fiber.transfer
+ -> { fiber.raise "msg" }.should.raise(RuntimeError, "msg")
+ end
+end
diff --git a/spec/ruby/core/fiber/resume_spec.rb b/spec/ruby/core/fiber/resume_spec.rb
new file mode 100644
index 0000000000..e183cc10d9
--- /dev/null
+++ b/spec/ruby/core/fiber/resume_spec.rb
@@ -0,0 +1,83 @@
+require_relative '../../spec_helper'
+require_relative 'shared/resume'
+
+describe "Fiber#resume" do
+ it_behaves_like :fiber_resume, :resume
+end
+
+describe "Fiber#resume" do
+ it "runs until Fiber.yield" do
+ obj = mock('obj')
+ obj.should_not_receive(:do)
+ fiber = Fiber.new { 1 + 2; Fiber.yield; obj.do }
+ fiber.resume
+ end
+
+ it "resumes from the last call to Fiber.yield on subsequent invocations" do
+ fiber = Fiber.new { Fiber.yield :first; :second }
+ fiber.resume.should == :first
+ fiber.resume.should == :second
+ end
+
+ it "sets the block parameters to its arguments on the first invocation" do
+ first = mock('first')
+ first.should_receive(:arg).with(:first).twice
+
+ fiber = Fiber.new { |arg| first.arg arg; Fiber.yield; first.arg arg; }
+ fiber.resume :first
+ fiber.resume :second
+ end
+
+ it "raises a FiberError if the Fiber tries to resume itself" do
+ fiber = Fiber.new { fiber.resume }
+ -> { fiber.resume }.should.raise(FiberError, /current fiber/)
+ end
+
+ it "returns control to the calling Fiber if called from one" do
+ fiber1 = Fiber.new { :fiber1 }
+ fiber2 = Fiber.new { fiber1.resume; :fiber2 }
+ fiber2.resume.should == :fiber2
+ end
+
+ # Redmine #595
+ it "executes the ensure clause" do
+ code = <<-RUBY
+ f = Fiber.new do
+ begin
+ Fiber.yield
+ ensure
+ puts "ensure executed"
+ end
+ end
+
+ # The apparent issue is that when Fiber.yield executes, control
+ # "leaves" the "ensure block" and so the ensure clause should run. But
+ # control really does NOT leave the ensure block when Fiber.yield
+ # executes. It merely pauses there. To require ensure to run when a
+ # Fiber is suspended then makes ensure-in-a-Fiber-context different
+ # than ensure-in-a-Thread-context and this would be very confusing.
+ f.resume
+
+ # When we execute the second #resume call, the ensure block DOES exit,
+ # the ensure clause runs.
+ f.resume
+
+ exit 0
+ RUBY
+
+ 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(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/blocking.rb b/spec/ruby/core/fiber/shared/blocking.rb
new file mode 100644
index 0000000000..21707e1ea7
--- /dev/null
+++ b/spec/ruby/core/fiber/shared/blocking.rb
@@ -0,0 +1,41 @@
+describe :non_blocking_fiber, shared: true do
+ context "root Fiber of the main thread" do
+ it "returns false" do
+ fiber = Fiber.new { @method.call }
+ blocking = fiber.resume
+
+ blocking.should == false
+ end
+
+ it "returns false for blocking: false" do
+ fiber = Fiber.new(blocking: false) { @method.call }
+ blocking = fiber.resume
+
+ blocking.should == false
+ end
+ end
+
+ context "root Fiber of a new thread" do
+ it "returns false" do
+ thread = Thread.new do
+ fiber = Fiber.new { @method.call }
+ blocking = fiber.resume
+
+ blocking.should == false
+ end
+
+ thread.join
+ end
+
+ it "returns false for blocking: false" do
+ thread = Thread.new do
+ fiber = Fiber.new(blocking: false) { @method.call }
+ blocking = fiber.resume
+
+ blocking.should == false
+ end
+
+ thread.join
+ end
+ end
+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..ff4bb72c73
--- /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(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 == true
+ end
+
+ it "returns the last value encountered on first invocation" do
+ fiber = Fiber.new { 1+1; true }
+ fiber.send(@method).should == 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
+ end
+
+ it "raises a FiberError if the Fiber is dead" do
+ fiber = Fiber.new { true }
+ fiber.send(@method)
+ -> { fiber.send(@method) }.should.raise(FiberError)
+ end
+
+ it "raises a LocalJumpError if the block includes a return statement" do
+ fiber = Fiber.new { return; }
+ -> { fiber.send(@method) }.should.raise(LocalJumpError)
+ end
+
+ it "raises a LocalJumpError if the block includes a break statement" do
+ fiber = Fiber.new { break; }
+ -> { fiber.send(@method) }.should.raise(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..04bdded53a
--- /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(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 == nil
+ end
+
+ it "can assign a nil scheduler multiple times" do
+ Fiber.set_scheduler(nil)
+ Fiber.set_scheduler(nil)
+ Fiber.scheduler.should == nil
+ end
+end
diff --git a/spec/ruby/core/fiber/storage_spec.rb b/spec/ruby/core/fiber/storage_spec.rb
new file mode 100644
index 0000000000..a3f6bf9cad
--- /dev/null
+++ b/spec/ruby/core/fiber/storage_spec.rb
@@ -0,0 +1,177 @@
+require_relative '../../spec_helper'
+
+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 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(TypeError)
+ end
+
+ it "cannot create a fiber with a frozen hash as storage" do
+ -> { Fiber.new(storage: {life: 43}.freeze) {} }.should.raise(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(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(ArgumentError, /Fiber storage can only be accessed from the Fiber it belongs to/)
+ end
+end
+
+describe "Fiber#storage=" do
+ it "can clear the storage of the fiber" do
+ fiber = Fiber.new(storage: {life: 42}) do
+ Fiber.current.storage = nil
+ Fiber[:x] = 10
+ Fiber.current.storage
+ end
+ fiber.resume.should == {x: 10}
+ end
+
+ it "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(TypeError)
+ end
+
+ it "can't set the storage of the fiber to a frozen hash" do
+ -> { Fiber.current.storage = {life: 43}.freeze }.should.raise(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(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
+
+ 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 == nil
+ end
+
+ it "returns nil if the current fiber has no storage" do
+ Fiber.new { Fiber[:life] }.resume.should == nil
+ end
+
+ it "can use dynamically defined keys" do
+ key = :"#{self.class.name}#.#{self.object_id}"
+ Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42
+ end
+
+ it "can't use invalid keys" do
+ invalid_keys = [Object.new, 12]
+ invalid_keys.each do |key|
+ -> { Fiber[key] }.should.raise(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 "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 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
+
+ it "does not call #to_sym on the key" do
+ key = mock("key")
+ key.should_not_receive(:to_sym)
+ -> { Fiber[key] }.should.raise(TypeError)
+ 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
+ end
+
+ it "can't access the storage of the fiber with non-symbol keys" do
+ -> { Fiber[Object.new] }.should.raise(TypeError)
+ 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 "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 "does not overwrite the storage of the parent fiber" do
+ f = Fiber.new(storage: {life: 42}) do
+ Fiber.yield Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume
+ Fiber[:life]
+ end
+ f.resume.should == 43 # Value of the inner fiber
+ f.resume.should == 42 # Value of the outer fiber
+ end
+
+ it "can't access the storage of the fiber with non-symbol keys" do
+ -> { Fiber[Object.new] = 44 }.should.raise(TypeError)
+ end
+
+ 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
+
+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..d8737aeeb3
--- /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(FiberError)
+ end
+
+ it "raises a FiberError when transferring to a Fiber which resumes itself" do
+ fiber = Fiber.new { fiber.resume }
+ -> { fiber.transfer }.should.raise(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/fiber/yield_spec.rb b/spec/ruby/core/fiber/yield_spec.rb
new file mode 100644
index 0000000000..12ec6ebcef
--- /dev/null
+++ b/spec/ruby/core/fiber/yield_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Fiber.yield" do
+ it "passes control to the Fiber's caller" do
+ step = 0
+ fiber = Fiber.new { step = 1; Fiber.yield; step = 2; Fiber.yield; step = 3 }
+ fiber.resume
+ step.should == 1
+ fiber.resume
+ step.should == 2
+ end
+
+ it "returns its arguments to the caller" do
+ fiber = Fiber.new { true; Fiber.yield :glark; true }
+ fiber.resume.should == :glark
+ fiber.resume
+ end
+
+ it "returns nil to the caller if given no arguments" do
+ fiber = Fiber.new { true; Fiber.yield; true }
+ fiber.resume.should == nil
+ fiber.resume
+ end
+
+ it "returns to the Fiber the value of the #resume call that invoked it" do
+ fiber = Fiber.new { Fiber.yield.should == :caller }
+ fiber.resume
+ fiber.resume :caller
+ end
+
+ it "does not propagate or reraise a rescued exception" do
+ fiber = Fiber.new do
+ begin
+ raise "an error in a Fiber"
+ rescue
+ Fiber.yield :first
+ end
+
+ :second
+ end
+
+ fiber.resume.should == :first
+ fiber.resume.should == :second
+ end
+
+ it "raises a FiberError if called from the root Fiber" do
+ ->{ Fiber.yield }.should.raise(FiberError)
+ end
+end
diff --git a/spec/ruby/core/file/absolute_path_spec.rb b/spec/ruby/core/file/absolute_path_spec.rb
new file mode 100644
index 0000000000..fc12985a75
--- /dev/null
+++ b/spec/ruby/core/file/absolute_path_spec.rb
@@ -0,0 +1,94 @@
+require_relative '../../spec_helper'
+
+describe "File.absolute_path?" do
+ before :each do
+ @abs = File.expand_path(__FILE__)
+ end
+
+ it "returns true if it's an absolute pathname" do
+ File.absolute_path?(@abs).should == true
+ end
+
+ it "returns false if it's a relative path" do
+ File.absolute_path?(File.basename(__FILE__)).should == false
+ end
+
+ it "returns false if it's a tricky relative path" do
+ File.absolute_path?("C:foo\\bar").should == false
+ end
+
+ it "does not expand '~' to a home directory." do
+ File.absolute_path?('~').should == false
+ end
+
+ it "does not expand '~user' to a home directory." do
+ path = File.dirname(@abs)
+ Dir.chdir(path) do
+ File.absolute_path?('~user').should == false
+ end
+ end
+
+ it "calls #to_path on its argument" do
+ mock = mock_to_path(File.expand_path(__FILE__))
+
+ File.absolute_path?(mock).should == true
+ end
+
+ platform_is_not :windows do
+ it "takes into consideration the platform's root" do
+ File.absolute_path?("C:\\foo\\bar").should == false
+ File.absolute_path?("C:/foo/bar").should == false
+ File.absolute_path?("/foo/bar\\baz").should == true
+ end
+ end
+
+ platform_is :windows do
+ it "takes into consideration the platform path separator(s)" do
+ File.absolute_path?("C:\\foo\\bar").should == true
+ File.absolute_path?("C:/foo/bar").should == true
+ File.absolute_path?("/foo/bar\\baz").should == false
+ end
+ end
+end
+
+describe "File.absolute_path" do
+ before :each do
+ @abs = File.expand_path(__FILE__)
+ end
+
+ it "returns the argument if it's an absolute pathname" do
+ File.absolute_path(@abs).should == @abs
+ end
+
+ it "resolves paths relative to the current working directory" do
+ path = File.dirname(@abs)
+ Dir.chdir(path) do
+ File.absolute_path('hello.txt').should == File.join(Dir.pwd, 'hello.txt')
+ end
+ end
+
+ it "does not expand '~' to a home directory." do
+ File.absolute_path('~').should_not == File.expand_path('~')
+ end
+
+ platform_is_not :windows do
+ it "does not expand '~' when given dir argument" do
+ File.absolute_path('~', '/').should == '/~'
+ end
+ end
+
+ it "does not expand '~user' to a home directory." do
+ path = File.dirname(@abs)
+ Dir.chdir(path) do
+ File.absolute_path('~user').should == File.join(Dir.pwd, '~user')
+ end
+ end
+
+ it "accepts a second argument of a directory from which to resolve the path" do
+ File.absolute_path(__FILE__, __dir__).should == @abs
+ end
+
+ it "calls #to_path on its argument" do
+ File.absolute_path(mock_to_path(@abs)).should == @abs
+ end
+end
diff --git a/spec/ruby/core/file/atime_spec.rb b/spec/ruby/core/file/atime_spec.rb
new file mode 100644
index 0000000000..5c6c110eec
--- /dev/null
+++ b/spec/ruby/core/file/atime_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+
+describe "File.atime" do
+ before :each do
+ @file = tmp('test.txt')
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the last access time for the named file as a Time object" do
+ File.atime(@file)
+ File.atime(@file).should.is_a?(Time)
+ end
+
+ platform_is :linux, :windows do
+ unless ENV.key?('TRAVIS') # https://bugs.ruby-lang.org/issues/17926
+ ## NOTE also that some Linux systems disable atime (e.g. via mount params) for better filesystem speed.
+ it "returns the last access time for the named file with microseconds" do
+ supports_subseconds = Integer(`stat -c%x '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10)
+ if supports_subseconds != 0
+ expected_time = Time.at(Time.now.to_i + 0.123456)
+ File.utime expected_time, 0, @file
+ File.atime(@file).usec.should == expected_time.usec
+ else
+ File.atime(__FILE__).usec.should == 0
+ end
+ rescue Errno::ENOENT => e
+ # Native Windows don't have stat command.
+ skip e.message
+ end
+ end
+ end
+
+ it "raises an Errno::ENOENT exception if the file is not found" do
+ -> { File.atime('a_fake_file') }.should.raise(Errno::ENOENT)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.atime(mock_to_path(@file))
+ end
+end
+
+describe "File#atime" do
+ before :each do
+ @name = File.expand_path(__FILE__)
+ @file = File.open(@name)
+ end
+
+ after :each do
+ @file.close rescue nil
+ end
+
+ it "returns the last access time to self" do
+ @file.atime
+ @file.atime.should.is_a?(Time)
+ end
+end
diff --git a/spec/ruby/core/file/basename_spec.rb b/spec/ruby/core/file/basename_spec.rb
new file mode 100644
index 0000000000..77afe5c22f
--- /dev/null
+++ b/spec/ruby/core/file/basename_spec.rb
@@ -0,0 +1,205 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+# TODO: Fix these
+describe "File.basename" do
+ it "returns the basename of a path (basic cases)" do
+ File.basename("/Some/path/to/test.txt").should == "test.txt"
+ File.basename(File.join("/tmp")).should == "tmp"
+ File.basename(File.join(*%w( g f d s a b))).should == "b"
+ File.basename("/tmp", ".*").should == "tmp"
+ File.basename("/tmp", ".c").should == "tmp"
+ File.basename("/tmp.c", ".c").should == "tmp"
+ File.basename("/tmp.c", ".*").should == "tmp"
+ File.basename("/tmp.c", ".?").should == "tmp.c"
+ File.basename("/tmp.cpp", ".*").should == "tmp"
+ File.basename("/tmp.cpp", ".???").should == "tmp.cpp"
+ File.basename("/tmp.o", ".c").should == "tmp.o"
+ File.basename(File.join("/tmp/")).should == "tmp"
+ File.basename("/").should == "/"
+ File.basename("//").should == "/"
+ File.basename("dir///base", ".*").should == "base"
+ File.basename("dir///base", ".c").should == "base"
+ File.basename("dir///base.c", ".c").should == "base"
+ File.basename("dir///base.c", ".*").should == "base"
+ File.basename("dir///base.o", ".c").should == "base.o"
+ File.basename("dir///base///").should == "base"
+ File.basename("dir//base/", ".*").should == "base"
+ File.basename("dir//base/", ".c").should == "base"
+ File.basename("dir//base.c/", ".c").should == "base"
+ File.basename("dir//base.c/", ".*").should == "base"
+ end
+
+ it "returns the last component of the filename" do
+ File.basename('a').should == 'a'
+ File.basename('/a').should == 'a'
+ File.basename('/a/b').should == 'b'
+ File.basename('/ab/ba/bag').should == 'bag'
+ File.basename('/ab/ba/bag.txt').should == 'bag.txt'
+ File.basename('/').should == '/'
+ File.basename('/foo/bar/baz.rb', '.rb').should == 'baz'
+ File.basename('baz.rb', 'z.rb').should == 'ba'
+ end
+
+ it "returns an string" do
+ File.basename("foo").should.is_a?(String)
+ end
+
+ it "returns the basename for unix format" do
+ File.basename("/foo/bar").should == "bar"
+ File.basename("/foo/bar.txt").should == "bar.txt"
+ File.basename("bar.c").should == "bar.c"
+ File.basename("/bar").should == "bar"
+ File.basename("/bar/").should == "bar"
+
+ # Considered UNC paths on Windows
+ platform_is :windows do
+ File.basename("baz//foo").should =="foo"
+ File.basename("//foo/bar/baz").should == "baz"
+ end
+ end
+
+ it "returns the basename for edge cases" do
+ File.basename("").should == ""
+ File.basename(".").should == "."
+ File.basename("..").should == ".."
+ platform_is_not :windows do
+ File.basename("//foo/").should == "foo"
+ File.basename("//foo//").should == "foo"
+ end
+ File.basename("foo/").should == "foo"
+ end
+
+ it "ignores a trailing directory separator" do
+ File.basename("foo.rb/", '.rb').should == "foo"
+ File.basename("bar.rb///", '.*').should == "bar"
+ end
+
+ it "returns the basename for unix suffix" do
+ File.basename("bar.c", ".c").should == "bar"
+ File.basename("bar.txt", ".txt").should == "bar"
+ File.basename("/bar.txt", ".txt").should == "bar"
+ File.basename("/foo/bar.txt", ".txt").should == "bar"
+ File.basename("bar.txt", ".exe").should == "bar.txt"
+ File.basename("bar.txt.exe", ".exe").should == "bar.txt"
+ File.basename("bar.txt.exe", ".txt").should == "bar.txt.exe"
+ File.basename("bar.txt", ".*").should == "bar"
+ File.basename("bar.txt.exe", ".*").should == "bar.txt"
+ File.basename("bar.txt.exe", ".txt.exe").should == "bar"
+ end
+
+ platform_is_not :windows do
+ it "takes into consideration the platform path separator(s)" do
+ File.basename("C:\\foo\\bar").should == "C:\\foo\\bar"
+ File.basename("C:/foo/bar").should == "bar"
+ File.basename("/foo/bar\\baz").should == "bar\\baz"
+ end
+ end
+
+ platform_is :windows do
+ it "takes into consideration the platform path separator(s)" do
+ File.basename("C:\\foo\\bar").should == "bar"
+ File.basename("C:/foo/bar").should == "bar"
+ File.basename("/foo/bar\\baz").should == "baz"
+ end
+ end
+
+ it "raises a TypeError if the arguments are not String types" do
+ -> { File.basename(nil) }.should.raise(TypeError)
+ -> { File.basename(1) }.should.raise(TypeError)
+ -> { File.basename("bar.txt", 1) }.should.raise(TypeError)
+ -> { File.basename(true) }.should.raise(TypeError)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.basename(mock_to_path("foo.txt"))
+ end
+
+ it "raises an ArgumentError if passed more than two arguments" do
+ -> { File.basename('bar.txt', '.txt', '.txt') }.should.raise(ArgumentError)
+ end
+
+ # specific to MS Windows
+ platform_is :windows do
+ it "returns the basename for windows" do
+ File.basename("C:\\foo\\bar\\baz.txt").should == "baz.txt"
+ File.basename("C:\\foo\\bar").should == "bar"
+ File.basename("C:\\foo\\bar\\").should == "bar"
+ File.basename("C:\\foo").should == "foo"
+ File.basename("C:\\").should == "\\"
+ end
+
+ it "returns basename windows unc" do
+ File.basename("\\\\foo\\bar\\baz.txt").should == "baz.txt"
+ File.basename("\\\\foo\\bar\\baz").should =="baz"
+ end
+
+ it "returns basename windows forward slash" do
+ File.basename("C:/").should == "/"
+ File.basename("C:/foo").should == "foo"
+ File.basename("C:/foo/bar").should == "bar"
+ File.basename("C:/foo/bar/").should == "bar"
+ File.basename("C:/foo/bar//").should == "bar"
+ end
+
+ it "returns basename with windows suffix" do
+ File.basename("c:\\bar.txt", ".txt").should == "bar"
+ File.basename("c:\\foo\\bar.txt", ".txt").should == "bar"
+ File.basename("c:\\bar.txt", ".exe").should == "bar.txt"
+ File.basename("c:\\bar.txt.exe", ".exe").should == "bar.txt"
+ File.basename("c:\\bar.txt.exe", ".txt").should == "bar.txt.exe"
+ File.basename("c:\\bar.txt", ".*").should == "bar"
+ File.basename("c:\\bar.txt.exe", ".*").should == "bar.txt"
+ end
+
+ it "handles Shift JIS 0x5C (\\) as second byte of a multi-byte sequence" do
+ # dir\fileソname.txt
+ path = "dir\\file\x83\x5cname.txt".b.force_encoding(Encoding::SHIFT_JIS)
+ path.valid_encoding?.should == true
+ File.basename(path).should == "file\x83\x5cname.txt".b.force_encoding(Encoding::SHIFT_JIS)
+ end
+ end
+
+ it "rejects strings encoded with non ASCII-compatible encodings" do
+ Encoding.list.reject(&:ascii_compatible?).reject(&:dummy?).each do |enc|
+ path = "/foo/bar".encode(enc)
+
+ -> {
+ File.basename(path)
+ }.should.raise(Encoding::CompatibilityError)
+ end
+ end
+
+ it "works with all ASCII-compatible encodings" do
+ Encoding.list.select(&:ascii_compatible?).each do |enc|
+ File.basename("/foo/bar".encode(enc)).should == "bar".encode(enc)
+ end
+ end
+
+ it "returns the extension for a multibyte filename" do
+ File.basename('/path/Офис.m4a').should == "Офис.m4a"
+ end
+
+ it "returns the basename with the same encoding as the original" do
+ basename = File.basename('C:/Users/Scuby Pagrubý'.encode(Encoding::Windows_1250))
+ basename.should == 'Scuby Pagrubý'.encode(Encoding::Windows_1250)
+ basename.encoding.should == Encoding::Windows_1250
+ end
+
+ it "returns a new unfrozen String" do
+ exts = [nil, '.rb', '.*', '.txt']
+ ['foo.rb','//', '/test/', 'test'].each do |example|
+ exts.each do |ext|
+ original = example.freeze
+ result = if ext
+ File.basename(original, ext)
+ else
+ File.basename(original)
+ end
+ result.should_not.equal?(original)
+ result.frozen?.should == false
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/core/file/birthtime_spec.rb b/spec/ruby/core/file/birthtime_spec.rb
new file mode 100644
index 0000000000..039fd7572c
--- /dev/null
+++ b/spec/ruby/core/file/birthtime_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+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
+
+ it "returns the birth time for the named file as a Time object" do
+ File.birthtime(@file)
+ File.birthtime(@file).should.is_a?(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(Errno::ENOENT)
+ rescue NotImplementedError => e
+ e.message.should.start_with?(*not_implemented_messages)
+ end
+ end
+
+ describe "File#birthtime" do
+ before :each do
+ @file = File.open(__FILE__)
+ end
+
+ after :each do
+ @file.close
+ @file = nil
+ end
+
+ it "returns the birth time for self" do
+ @file.birthtime
+ @file.birthtime.should.is_a?(Time)
+ rescue NotImplementedError => e
+ e.message.should.start_with?(*not_implemented_messages)
+ end
+ end
+end
diff --git a/spec/ruby/core/file/blockdev_spec.rb b/spec/ruby/core/file/blockdev_spec.rb
new file mode 100644
index 0000000000..9ba9afc251
--- /dev/null
+++ b/spec/ruby/core/file/blockdev_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/blockdev'
+
+describe "File.blockdev?" do
+ it_behaves_like :file_blockdev, :blockdev?, File
+end
diff --git a/spec/ruby/core/file/chardev_spec.rb b/spec/ruby/core/file/chardev_spec.rb
new file mode 100644
index 0000000000..1fc932ee4e
--- /dev/null
+++ b/spec/ruby/core/file/chardev_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/chardev'
+
+describe "File.chardev?" do
+ it_behaves_like :file_chardev, :chardev?, File
+end
diff --git a/spec/ruby/core/file/chmod_spec.rb b/spec/ruby/core/file/chmod_spec.rb
new file mode 100644
index 0000000000..e0fd10ceb1
--- /dev/null
+++ b/spec/ruby/core/file/chmod_spec.rb
@@ -0,0 +1,185 @@
+require_relative '../../spec_helper'
+
+describe "File#chmod" do
+ before :each do
+ @filename = tmp('i_exist.exe')
+ @file = File.open(@filename, 'w')
+ end
+
+ after :each do
+ @file.close
+ rm_r @filename
+ end
+
+ it "returns 0 if successful" do
+ @file.chmod(0755).should == 0
+ end
+
+ it "raises RangeError with too large values" do
+ -> { @file.chmod(2**64) }.should.raise(RangeError)
+ -> { @file.chmod(-2**63 - 1) }.should.raise(RangeError)
+ end
+
+ it "invokes to_int on non-integer argument" do
+ mode = File.stat(@filename).mode
+ (obj = mock('mode')).should_receive(:to_int).and_return(mode)
+ @file.chmod(obj)
+ File.stat(@filename).mode.should == mode
+ end
+
+ platform_is :windows do
+ it "with '0444' makes file readable and executable but not writable" do
+ @file.chmod(0444)
+ File.readable?(@filename).should == true
+ File.writable?(@filename).should == false
+ File.executable?(@filename).should == true
+ end
+
+ it "with '0644' makes file readable and writable and also executable" do
+ @file.chmod(0644)
+ File.readable?(@filename).should == true
+ File.writable?(@filename).should == true
+ File.executable?(@filename).should == true
+ end
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "with '0222' makes file writable but not readable or executable" do
+ @file.chmod(0222)
+ File.readable?(@filename).should == false
+ File.writable?(@filename).should == true
+ File.executable?(@filename).should == false
+ end
+
+ it "with '0444' makes file readable but not writable or executable" do
+ @file.chmod(0444)
+ File.readable?(@filename).should == true
+ File.writable?(@filename).should == false
+ File.executable?(@filename).should == false
+ end
+
+ it "with '0666' makes file readable and writable but not executable" do
+ @file.chmod(0666)
+ File.readable?(@filename).should == true
+ File.writable?(@filename).should == true
+ File.executable?(@filename).should == false
+ end
+
+ it "with '0111' makes file executable but not readable or writable" do
+ @file.chmod(0111)
+ File.readable?(@filename).should == false
+ File.writable?(@filename).should == false
+ File.executable?(@filename).should == true
+ end
+
+ it "modifies the permission bits of the files specified" do
+ @file.chmod(0755)
+ File.stat(@filename).mode.should == 33261
+ end
+ end
+ end
+end
+
+describe "File.chmod" do
+ before :each do
+ @file = tmp('i_exist.exe')
+ touch @file
+ @count = File.chmod(0755, @file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the number of files modified" do
+ @count.should == 1
+ end
+
+ it "raises RangeError with too large values" do
+ -> { File.chmod(2**64, @file) }.should.raise(RangeError)
+ -> { File.chmod(-2**63 - 1, @file) }.should.raise(RangeError)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.chmod(0, mock_to_path(@file))
+ end
+
+ it "throws a TypeError if the given path is not coercible into a string" do
+ -> { File.chmod(0, []) }.should.raise(TypeError)
+ end
+
+ it "raises an error for a non existent path" do
+ -> {
+ File.chmod(0644, "#{@file}.not.existing")
+ }.should.raise(Errno::ENOENT)
+ end
+
+ it "invokes to_int on non-integer argument" do
+ mode = File.stat(@file).mode
+ (obj = mock('mode')).should_receive(:to_int).and_return(mode)
+ File.chmod(obj, @file)
+ File.stat(@file).mode.should == mode
+ end
+
+ it "invokes to_str on non-string file names" do
+ mode = File.stat(@file).mode
+ (obj = mock('path')).should_receive(:to_str).and_return(@file)
+ File.chmod(mode, obj)
+ File.stat(@file).mode.should == mode
+ end
+
+ platform_is :windows do
+ it "with '0444' makes file readable and executable but not writable" do
+ File.chmod(0444, @file)
+ File.readable?(@file).should == true
+ File.writable?(@file).should == false
+ File.executable?(@file).should == true
+ end
+
+ it "with '0644' makes file readable and writable and also executable" do
+ File.chmod(0644, @file)
+ File.readable?(@file).should == true
+ File.writable?(@file).should == true
+ File.executable?(@file).should == true
+ end
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "with '0222' makes file writable but not readable or executable" do
+ File.chmod(0222, @file)
+ File.readable?(@file).should == false
+ File.writable?(@file).should == true
+ File.executable?(@file).should == false
+ end
+
+ it "with '0444' makes file readable but not writable or executable" do
+ File.chmod(0444, @file)
+ File.readable?(@file).should == true
+ File.writable?(@file).should == false
+ File.executable?(@file).should == false
+ end
+ end
+
+ it "with '0666' makes file readable and writable but not executable" do
+ File.chmod(0666, @file)
+ File.readable?(@file).should == true
+ File.writable?(@file).should == true
+ File.executable?(@file).should == false
+ end
+
+ as_user do
+ it "with '0111' makes file executable but not readable or writable" do
+ File.chmod(0111, @file)
+ File.readable?(@file).should == false
+ File.writable?(@file).should == false
+ File.executable?(@file).should == true
+ end
+ end
+
+ it "modifies the permission bits of the files specified" do
+ File.stat(@file).mode.should == 33261
+ end
+ end
+end
diff --git a/spec/ruby/core/file/chown_spec.rb b/spec/ruby/core/file/chown_spec.rb
new file mode 100644
index 0000000000..3353aafc70
--- /dev/null
+++ b/spec/ruby/core/file/chown_spec.rb
@@ -0,0 +1,144 @@
+require_relative '../../spec_helper'
+
+describe "File.chown" do
+ before :each do
+ @fname = tmp('file_chown_test')
+ touch @fname
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ as_superuser do
+ platform_is :windows do
+ it "does not modify the owner id of the file" do
+ File.chown 0, nil, @fname
+ File.stat(@fname).uid.should == 0
+ File.chown 501, nil, @fname
+ File.stat(@fname).uid.should == 0
+ end
+
+ it "does not modify the group id of the file" do
+ File.chown nil, 0, @fname
+ File.stat(@fname).gid.should == 0
+ File.chown nil, 501, @fname
+ File.stat(@fname).gid.should == 0
+ end
+ end
+
+ platform_is_not :windows do
+ it "changes the owner id of the file" do
+ File.chown 501, nil, @fname
+ File.stat(@fname).uid.should == 501
+ File.chown 0, nil, @fname
+ File.stat(@fname).uid.should == 0
+ end
+
+ it "changes the group id of the file" do
+ File.chown nil, 501, @fname
+ File.stat(@fname).gid.should == 501
+ File.chown nil, 0, @fname
+ File.stat(@fname).uid.should == 0
+ end
+
+ it "does not modify the owner id of the file if passed nil or -1" do
+ File.chown 501, nil, @fname
+ File.chown nil, nil, @fname
+ File.stat(@fname).uid.should == 501
+ File.chown nil, -1, @fname
+ File.stat(@fname).uid.should == 501
+ end
+
+ it "does not modify the group id of the file if passed nil or -1" do
+ File.chown nil, 501, @fname
+ File.chown nil, nil, @fname
+ File.stat(@fname).gid.should == 501
+ File.chown nil, -1, @fname
+ File.stat(@fname).gid.should == 501
+ end
+ end
+ end
+
+ it "returns the number of files processed" do
+ File.chown(nil, nil, @fname, @fname).should == 2
+ end
+
+ platform_is_not :windows do
+ it "raises an error for a non existent path" do
+ -> {
+ File.chown(nil, nil, "#{@fname}_not_existing")
+ }.should.raise(Errno::ENOENT)
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.chown(nil, nil, mock_to_path(@fname)).should == 1
+ end
+end
+
+describe "File#chown" do
+ 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
+
+ as_superuser do
+ platform_is :windows do
+ it "does not modify the owner id of the file" do
+ @file.chown 0, nil
+ @file.stat.uid.should == 0
+ @file.chown 501, nil
+ @file.stat.uid.should == 0
+ end
+
+ it "does not modify the group id of the file" do
+ @file.chown nil, 0
+ @file.stat.gid.should == 0
+ @file.chown nil, 501
+ @file.stat.gid.should == 0
+ end
+ end
+
+ platform_is_not :windows do
+ it "changes the owner id of the file" do
+ @file.chown 501, nil
+ @file.stat.uid.should == 501
+ @file.chown 0, nil
+ @file.stat.uid.should == 0
+ end
+
+ it "changes the group id of the file" do
+ @file.chown nil, 501
+ @file.stat.gid.should == 501
+ @file.chown nil, 0
+ @file.stat.uid.should == 0
+ end
+
+ it "does not modify the owner id of the file if passed nil or -1" do
+ @file.chown 501, nil
+ @file.chown nil, nil
+ @file.stat.uid.should == 501
+ @file.chown nil, -1
+ @file.stat.uid.should == 501
+ end
+
+ it "does not modify the group id of the file if passed nil or -1" do
+ @file.chown nil, 501
+ @file.chown nil, nil
+ @file.stat.gid.should == 501
+ @file.chown nil, -1
+ @file.stat.gid.should == 501
+ end
+ end
+ end
+
+ it "returns 0" do
+ @file.chown(nil, nil).should == 0
+ end
+end
diff --git a/spec/ruby/core/file/constants/constants_spec.rb b/spec/ruby/core/file/constants/constants_spec.rb
new file mode 100644
index 0000000000..9d9c1c3b25
--- /dev/null
+++ b/spec/ruby/core/file/constants/constants_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../../spec_helper'
+
+["APPEND", "CREAT", "EXCL", "FNM_CASEFOLD",
+ "FNM_DOTMATCH", "FNM_EXTGLOB", "FNM_NOESCAPE", "FNM_PATHNAME",
+ "FNM_SYSCASE", "LOCK_EX", "LOCK_NB", "LOCK_SH",
+ "LOCK_UN", "NONBLOCK", "RDONLY",
+ "RDWR", "TRUNC", "WRONLY", "SHARE_DELETE"].each do |const|
+ describe "File::Constants::#{const}" do
+ it "is defined" do
+ File::Constants.const_defined?(const).should == true
+ end
+ end
+end
+
+platform_is :windows do
+ describe "File::Constants::BINARY" do
+ it "is defined" do
+ File::Constants.const_defined?(:BINARY).should == true
+ end
+ end
+end
+
+platform_is_not :windows do
+ ["NOCTTY", "SYNC"].each do |const|
+ describe "File::Constants::#{const}" do
+ it "is defined" do
+ File::Constants.const_defined?(const).should == true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/constants_spec.rb b/spec/ruby/core/file/constants_spec.rb
new file mode 100644
index 0000000000..5f058a7f40
--- /dev/null
+++ b/spec/ruby/core/file/constants_spec.rb
@@ -0,0 +1,141 @@
+require_relative '../../spec_helper'
+
+# TODO: migrate these to constants/constants_spec.rb
+
+describe "File::Constants" do
+ it "matches mode constants" do
+ File::FNM_NOESCAPE.should_not == nil
+ File::FNM_PATHNAME.should_not == nil
+ File::FNM_DOTMATCH.should_not == nil
+ File::FNM_CASEFOLD.should_not == nil
+ File::FNM_SYSCASE.should_not == nil
+
+ platform_is :windows do #|| VMS
+ File::FNM_SYSCASE.should == 8
+ end
+ end
+
+ # Only these constants are not inherited from the IO class
+ it "the separator constant" do
+ File::SEPARATOR.should_not == nil
+ File::Separator.should_not == nil
+ File::PATH_SEPARATOR.should_not == nil
+ File::SEPARATOR.should == "/"
+
+ platform_is :windows do #|| VMS
+ File::ALT_SEPARATOR.should_not == nil
+ File::PATH_SEPARATOR.should == ";"
+ end
+
+ platform_is_not :windows do
+ File::ALT_SEPARATOR.should == nil
+ File::PATH_SEPARATOR.should == ":"
+ end
+ end
+
+ it "the open mode constants" do
+ File::APPEND.should_not == nil
+ File::CREAT.should_not == nil
+ File::EXCL.should_not == nil
+ File::NONBLOCK.should_not == nil
+ File::RDONLY.should_not == nil
+ File::RDWR.should_not == nil
+ File::TRUNC.should_not == nil
+ File::WRONLY.should_not == nil
+
+ platform_is_not :windows do # Not sure about VMS here
+ File::NOCTTY.should_not == nil
+ end
+ end
+
+ it "lock mode constants" do
+ File::LOCK_EX.should_not == nil
+ File::LOCK_NB.should_not == nil
+ File::LOCK_SH.should_not == nil
+ File::LOCK_UN.should_not == nil
+ end
+end
+
+describe "File::Constants" do
+ # These mode and permission bits are platform dependent
+ it "File::RDONLY" do
+ defined?(File::RDONLY).should == "constant"
+ end
+
+ it "File::WRONLY" do
+ defined?(File::WRONLY).should == "constant"
+ end
+
+ it "File::CREAT" do
+ defined?(File::CREAT).should == "constant"
+ end
+
+ it "File::RDWR" do
+ defined?(File::RDWR).should == "constant"
+ end
+
+ it "File::APPEND" do
+ defined?(File::APPEND).should == "constant"
+ end
+
+ it "File::TRUNC" do
+ defined?(File::TRUNC).should == "constant"
+ end
+
+ platform_is_not :windows do # Not sure about VMS here
+ it "File::NOCTTY" do
+ defined?(File::NOCTTY).should == "constant"
+ end
+ end
+
+ it "File::NONBLOCK" do
+ defined?(File::NONBLOCK).should == "constant"
+ end
+
+ it "File::LOCK_EX" do
+ defined?(File::LOCK_EX).should == "constant"
+ end
+
+ it "File::LOCK_NB" do
+ defined?(File::LOCK_NB).should == "constant"
+ end
+
+ it "File::LOCK_SH" do
+ defined?(File::LOCK_SH).should == "constant"
+ end
+
+ it "File::LOCK_UN" do
+ defined?(File::LOCK_UN).should == "constant"
+ end
+
+ it "File::SEPARATOR" do
+ defined?(File::SEPARATOR).should == "constant"
+ end
+ it "File::Separator" do
+ defined?(File::Separator).should == "constant"
+ end
+
+ it "File::PATH_SEPARATOR" do
+ defined?(File::PATH_SEPARATOR).should == "constant"
+ end
+
+ it "File::SEPARATOR" do
+ defined?(File::SEPARATOR).should == "constant"
+ File::SEPARATOR.should == "/"
+ end
+
+ platform_is :windows do #|| VMS
+ it "File::ALT_SEPARATOR" do
+ defined?(File::ALT_SEPARATOR).should == "constant"
+ File::PATH_SEPARATOR.should == ";"
+ end
+ end
+
+ platform_is_not :windows do
+ it "File::PATH_SEPARATOR" do
+ defined?(File::PATH_SEPARATOR).should == "constant"
+ File::PATH_SEPARATOR.should == ":"
+ end
+ end
+
+end
diff --git a/spec/ruby/core/file/ctime_spec.rb b/spec/ruby/core/file/ctime_spec.rb
new file mode 100644
index 0000000000..cf37d1f4ee
--- /dev/null
+++ b/spec/ruby/core/file/ctime_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+describe "File.ctime" do
+ before :each do
+ @file = __FILE__
+ end
+
+ after :each do
+ @file = nil
+ end
+
+ it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself)." do
+ File.ctime(@file)
+ File.ctime(@file).should.is_a?(Time)
+ end
+
+ platform_is :linux, :windows do
+ it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself) with microseconds." do
+ supports_subseconds = Integer(`stat -c%z '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10)
+ if supports_subseconds != 0
+ File.ctime(__FILE__).usec.should > 0
+ else
+ File.ctime(__FILE__).usec.should == 0
+ end
+ rescue Errno::ENOENT => e
+ # Windows don't have stat command.
+ skip e.message
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.ctime(mock_to_path(@file))
+ end
+
+ it "raises an Errno::ENOENT exception if the file is not found" do
+ -> { File.ctime('bogus') }.should.raise(Errno::ENOENT)
+ end
+end
+
+describe "File#ctime" do
+ before :each do
+ @file = File.open(__FILE__)
+ end
+
+ after :each do
+ @file.close
+ @file = nil
+ end
+
+ it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself)." do
+ @file.ctime
+ @file.ctime.should.is_a?(Time)
+ end
+end
diff --git a/spec/ruby/core/file/delete_spec.rb b/spec/ruby/core/file/delete_spec.rb
new file mode 100644
index 0000000000..4098499942
--- /dev/null
+++ b/spec/ruby/core/file/delete_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/unlink'
+
+describe "File.delete" do
+ it_behaves_like :file_unlink, :delete
+end
diff --git a/spec/ruby/core/file/directory_spec.rb b/spec/ruby/core/file/directory_spec.rb
new file mode 100644
index 0000000000..8014a7a03d
--- /dev/null
+++ b/spec/ruby/core/file/directory_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/directory'
+
+describe "File.directory?" do
+ it_behaves_like :file_directory, :directory?, File
+end
+
+describe "File.directory?" do
+ it_behaves_like :file_directory_io, :directory?, File
+end
diff --git a/spec/ruby/core/file/dirname_spec.rb b/spec/ruby/core/file/dirname_spec.rb
new file mode 100644
index 0000000000..855148a684
--- /dev/null
+++ b/spec/ruby/core/file/dirname_spec.rb
@@ -0,0 +1,170 @@
+require_relative '../../spec_helper'
+
+describe "File.dirname" do
+ it "returns all the components of filename except the last one" do
+ File.dirname('/home/jason').should == '/home'
+ File.dirname('/home/jason/poot.txt').should == '/home/jason'
+ File.dirname('poot.txt').should == '.'
+ File.dirname('/holy///schnikies//w00t.bin').should == '/holy///schnikies'
+ File.dirname('').should == '.'
+ File.dirname('/').should == '/'
+ File.dirname('/foo/foo').should == '/foo'
+ 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(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
+
+ it "returns a String" do
+ File.dirname("foo").should.is_a?(String)
+ end
+
+ it "does not modify its argument" do
+ x = "/usr/bin"
+ File.dirname(x)
+ x.should == "/usr/bin"
+ end
+
+ it "ignores a trailing /" do
+ File.dirname("/foo/bar/").should == "/foo"
+ end
+
+ it "returns the return all the components of filename except the last one (unix format)" do
+ File.dirname("foo").should =="."
+ File.dirname("/foo").should =="/"
+ File.dirname("/foo/bar").should =="/foo"
+ File.dirname("/foo/bar.txt").should =="/foo"
+ File.dirname("/foo/bar/baz").should =="/foo/bar"
+ 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"
+ end
+
+ it "rejects strings encoded with non ASCII-compatible encodings" do
+ Encoding.list.reject(&:ascii_compatible?).reject(&:dummy?).each do |enc|
+ path = "/foo/bar".encode(enc)
+ -> {
+ File.dirname(path)
+ }.should.raise(Encoding::CompatibilityError)
+ end
+ end
+
+ it "works with all ASCII-compatible encodings" do
+ Encoding.list.select(&:ascii_compatible?).each do |enc|
+ File.dirname("/foo/bar".encode(enc)).should == "/foo".encode(enc)
+ end
+ end
+
+ it "handles Shift JIS 0x5C (\\) as second byte of a multi-byte sequence" do
+ # dir/fileソname.txt
+ path = "dir/file\x83\x5cname.txt".b.force_encoding(Encoding::SHIFT_JIS)
+ path.valid_encoding?.should == true
+ File.dirname(path).should == "dir"
+ end
+
+ platform_is_not :windows do
+ it "ignores repeated leading / (edge cases on non-windows)" do
+ File.dirname("/////foo/bar/").should == "/foo"
+ end
+
+ it "returns all the components of filename except the last one (edge cases on non-windows)" do
+ File.dirname('/////').should == '/'
+ File.dirname("//foo//").should == "/"
+ File.dirname('foo\bar').should == '.'
+ File.dirname('/foo\bar').should == '/'
+ File.dirname('foo/bar\baz').should == 'foo'
+ end
+ end
+
+ platform_is :windows do
+ it "returns all the components of filename except the last one (edge cases on windows)" do
+ File.dirname("//foo").should == "//foo"
+ File.dirname("//foo//").should == "//foo"
+ File.dirname('/////').should == '//'
+ end
+
+ it "handles Shift JIS 0x5C (\\) as second byte of a multi-byte sequence (windows)" do
+ # dir\fileソname.txt
+ path = "dir\\file\x83\x5cname.txt".b.force_encoding(Encoding::SHIFT_JIS)
+ path.valid_encoding?.should == true
+ File.dirname(path).should == "dir"
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.dirname(mock_to_path("/")).should == "/"
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { File.dirname(nil) }.should.raise(TypeError)
+ -> { File.dirname(0) }.should.raise(TypeError)
+ -> { File.dirname(true) }.should.raise(TypeError)
+ -> { File.dirname(false) }.should.raise(TypeError)
+ end
+
+ # Windows specific tests
+ platform_is :windows do
+ it "returns the return all the components of filename except the last one (Windows format)" do
+ File.dirname("C:\\foo\\bar\\baz.txt").should =="C:\\foo\\bar"
+ File.dirname("C:\\foo\\bar").should =="C:\\foo"
+ File.dirname("C:\\foo\\bar\\").should == "C:\\foo"
+ File.dirname("C:\\foo").should == "C:\\"
+ File.dirname("C:\\").should =="C:\\"
+ end
+
+ it "returns the return all the components of filename except the last one (windows unc)" do
+ File.dirname("\\\\foo\\bar\\baz.txt").should == "\\\\foo\\bar"
+ File.dirname("\\\\foo\\bar\\baz").should == "\\\\foo\\bar"
+ File.dirname("\\\\foo").should =="\\\\foo"
+ File.dirname("\\\\foo\\bar").should =="\\\\foo\\bar"
+ File.dirname("\\\\\\foo\\bar").should =="\\\\foo\\bar"
+ File.dirname("\\\\\\foo").should =="\\\\foo"
+ end
+
+ it "returns the return all the components of filename except the last one (forward_slash)" do
+ File.dirname("C:/").should == "C:/"
+ File.dirname("C:/foo").should == "C:/"
+ File.dirname("C:/foo/bar").should == "C:/foo"
+ File.dirname("C:/foo/bar/").should == "C:/foo"
+ File.dirname("C:/foo/bar//").should == "C:/foo"
+ end
+ end
+end
diff --git a/spec/ruby/core/file/empty_spec.rb b/spec/ruby/core/file/empty_spec.rb
new file mode 100644
index 0000000000..e8c9941676
--- /dev/null
+++ b/spec/ruby/core/file/empty_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/zero'
+
+describe "File.empty?" do
+ it_behaves_like :file_zero, :empty?, File
+ it_behaves_like :file_zero_missing, :empty?, File
+end
diff --git a/spec/ruby/core/file/executable_real_spec.rb b/spec/ruby/core/file/executable_real_spec.rb
new file mode 100644
index 0000000000..0cb848b201
--- /dev/null
+++ b/spec/ruby/core/file/executable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/executable_real'
+
+describe "File.executable_real?" do
+ it_behaves_like :file_executable_real, :executable_real?, File
+ it_behaves_like :file_executable_real_missing, :executable_real?, File
+end
diff --git a/spec/ruby/core/file/executable_spec.rb b/spec/ruby/core/file/executable_spec.rb
new file mode 100644
index 0000000000..1dbb3b233d
--- /dev/null
+++ b/spec/ruby/core/file/executable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/executable'
+
+describe "File.executable?" do
+ it_behaves_like :file_executable, :executable?, File
+ it_behaves_like :file_executable_missing, :executable?, File
+end
diff --git a/spec/ruby/core/file/exist_spec.rb b/spec/ruby/core/file/exist_spec.rb
new file mode 100644
index 0000000000..b5600e5b07
--- /dev/null
+++ b/spec/ruby/core/file/exist_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/exist'
+
+describe "File.exist?" do
+ it_behaves_like :file_exist, :exist?, File
+end
+
+describe "File.exists?" do
+ it "has been removed" do
+ File.should_not.respond_to?(:exists?)
+ end
+end
diff --git a/spec/ruby/core/file/expand_path_spec.rb b/spec/ruby/core/file/expand_path_spec.rb
new file mode 100644
index 0000000000..160494f145
--- /dev/null
+++ b/spec/ruby/core/file/expand_path_spec.rb
@@ -0,0 +1,265 @@
+# -*- encoding: utf-8 -*-
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require 'etc'
+
+describe "File.expand_path" do
+ before :each do
+ platform_is :windows do
+ @base = `cd`.chomp.tr '\\', '/'
+ @tmpdir = "c:/tmp"
+ @rootdir = "c:/"
+ end
+
+ platform_is_not :windows do
+ @base = Dir.pwd
+ @tmpdir = "/tmp"
+ @rootdir = "/"
+ end
+ end
+
+ before :each do
+ @external = Encoding.default_external
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ end
+
+ it "converts a pathname to an absolute pathname" do
+ File.expand_path('').should == @base
+ File.expand_path('a').should == File.join(@base, 'a')
+ File.expand_path('a', nil).should == File.join(@base, 'a')
+ end
+
+ it "converts a pathname to an absolute pathname, Ruby-Talk:18512" do
+ # See Ruby-Talk:18512
+ File.expand_path('.a').should == File.join(@base, '.a')
+ File.expand_path('..a').should == File.join(@base, '..a')
+ File.expand_path('a../b').should == File.join(@base, 'a../b')
+ end
+
+ platform_is_not :windows do
+ it "keeps trailing dots on absolute pathname" do
+ # See Ruby-Talk:18512
+ File.expand_path('a.').should == File.join(@base, 'a.')
+ File.expand_path('a..').should == File.join(@base, 'a..')
+ end
+ end
+
+ it "converts a pathname to an absolute pathname, using a complete path" do
+ File.expand_path("", "#{@tmpdir}").should == "#{@tmpdir}"
+ File.expand_path("a", "#{@tmpdir}").should =="#{@tmpdir}/a"
+ File.expand_path("../a", "#{@tmpdir}/xxx").should == "#{@tmpdir}/a"
+ File.expand_path(".", "#{@rootdir}").should == "#{@rootdir}"
+ end
+
+ platform_is_not :windows do
+ before do
+ @var_home = ENV['HOME'].chomp('/')
+ @db_home = Dir.home(ENV['USER'])
+ end
+
+ # FIXME: these are insane!
+ it "expand path with" do
+ File.expand_path("../../bin", "/tmp/x").should == "/bin"
+ File.expand_path("../../bin", "/tmp").should == "/bin"
+ File.expand_path("../../bin", "/").should == "/bin"
+ File.expand_path("../bin", "tmp/x").should == File.join(@base, 'tmp', 'bin')
+ File.expand_path("../bin", "x/../tmp").should == File.join(@base, 'bin')
+ end
+
+ it "expand_path for common unix path gives a full path" do
+ File.expand_path('/tmp/').should =='/tmp'
+ File.expand_path('/tmp/../../../tmp').should == '/tmp'
+ File.expand_path('').should == Dir.pwd
+ File.expand_path('./////').should == Dir.pwd
+ File.expand_path('.').should == Dir.pwd
+ File.expand_path(Dir.pwd).should == Dir.pwd
+ File.expand_path('~/').should == @var_home
+ File.expand_path('~/..badfilename').should == "#{@var_home}/..badfilename"
+ File.expand_path('~/a','~/b').should == "#{@var_home}/a"
+ File.expand_path('..').should == File.dirname(Dir.pwd)
+ end
+
+ it "does not replace multiple '/' at the beginning of the path" do
+ File.expand_path('////some/path').should == "////some/path"
+ end
+
+ it "replaces multiple '/' with a single '/'" do
+ File.expand_path('/some////path').should == "/some/path"
+ end
+
+ it "raises an ArgumentError if the path is not valid" do
+ -> { File.expand_path("~a_not_existing_user") }.should.raise(ArgumentError)
+ end
+
+ it "expands ~ENV['USER'] to the user's home directory" do
+ File.expand_path("~#{ENV['USER']}").should == @db_home
+ end
+
+ it "expands ~ENV['USER']/a to a in the user's home directory" do
+ File.expand_path("~#{ENV['USER']}/a").should == "#{@db_home}/a"
+ end
+
+ it "does not expand ~ENV['USER'] when it's not at the start" do
+ File.expand_path("/~#{ENV['USER']}/a").should == "/~#{ENV['USER']}/a"
+ end
+
+ it "expands ../foo with ~/dir as base dir to /path/to/user/home/foo" do
+ File.expand_path('../foo', '~/dir').should == "#{@var_home}/foo"
+ end
+ end
+
+ it "accepts objects that have a #to_path method" do
+ File.expand_path(mock_to_path("a"), mock_to_path("#{@tmpdir}"))
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { File.expand_path(1) }.should.raise(TypeError)
+ -> { File.expand_path(nil) }.should.raise(TypeError)
+ -> { File.expand_path(true) }.should.raise(TypeError)
+ end
+
+ platform_is_not :windows do
+ it "expands /./dir to /dir" do
+ File.expand_path("/./dir").should == "/dir"
+ end
+ end
+
+ platform_is :windows do
+ it "expands C:/./dir to C:/dir" do
+ File.expand_path("C:/./dir").should == "C:/dir"
+ end
+ end
+
+ it "returns a String in the same encoding as the argument" do
+ Encoding.default_external = Encoding::SHIFT_JIS
+
+ path = "./a".dup.force_encoding Encoding::CP1251
+ File.expand_path(path).encoding.should.equal?(Encoding::CP1251)
+
+ weird_path = [222, 173, 190, 175].pack('C*')
+ File.expand_path(weird_path).encoding.should.equal?(Encoding::BINARY)
+ end
+
+ platform_is_not :windows do
+ it "expands a path when the default external encoding is BINARY" do
+ Encoding.default_external = Encoding::BINARY
+ path_8bit = [222, 173, 190, 175].pack('C*')
+ File.expand_path( path_8bit, @rootdir).should == "#{@rootdir}" + path_8bit
+ end
+ end
+
+ it "expands a path with multi-byte characters" do
+ File.expand_path("Ångström").should == "#{@base}/Ångström"
+ end
+
+ platform_is_not :windows do
+ it "raises an Encoding::CompatibilityError if the external encoding is not compatible" do
+ Encoding.default_external = Encoding::UTF_16BE
+ -> { File.expand_path("./a") }.should.raise(Encoding::CompatibilityError)
+ end
+ end
+
+ it "does not modify the string argument" do
+ str = "./a/b/../c"
+ File.expand_path(str, @base).should == "#{@base}/a/c"
+ str.should == "./a/b/../c"
+ end
+
+ it "does not modify a HOME string argument" do
+ str = "~/a"
+ File.expand_path(str).should == "#{Dir.home}/a"
+ str.should == "~/a"
+ end
+
+ it "returns a String when passed a String subclass" do
+ str = FileSpecs::SubString.new "./a/b/../c"
+ path = File.expand_path(str, @base)
+ path.should == "#{@base}/a/c"
+ path.should.instance_of?(String)
+ end
+end
+
+platform_is_not :windows do
+ describe "File.expand_path when HOME is set" do
+ before :each do
+ @home = ENV["HOME"]
+ ENV["HOME"] = "/rubyspec_home"
+ end
+
+ after :each do
+ ENV["HOME"] = @home
+ end
+
+ it "converts a pathname to an absolute pathname, using ~ (home) as base" do
+ home = "/rubyspec_home"
+ File.expand_path('~').should == home
+ File.expand_path('~', '/tmp/gumby/ddd').should == home
+ File.expand_path('~/a', '/tmp/gumby/ddd').should == File.join(home, 'a')
+ end
+
+ it "does not return a frozen string" do
+ home = "/rubyspec_home"
+ File.expand_path('~').should_not.frozen?
+ File.expand_path('~', '/tmp/gumby/ddd').should_not.frozen?
+ File.expand_path('~/a', '/tmp/gumby/ddd').should_not.frozen?
+ end
+ end
+
+
+ describe "File.expand_path when HOME is not set" do
+ before :each do
+ @home = ENV["HOME"]
+ end
+
+ after :each do
+ ENV["HOME"] = @home
+ end
+
+ guard -> {
+ # We need to check if getlogin(3) returns non-NULL,
+ # as MRI only checks getlogin(3) for expanding '~' if $HOME is not set.
+ user = ENV.delete("USER")
+ begin
+ Etc.getlogin != nil
+ rescue
+ false
+ ensure
+ ENV["USER"] = user
+ end
+ } do
+ it "uses the user database when passed '~' if HOME is nil" do
+ ENV.delete "HOME"
+ File.directory?(File.expand_path("~")).should == true
+ end
+
+ it "uses the user database when passed '~/' if HOME is nil" do
+ ENV.delete "HOME"
+ File.directory?(File.expand_path("~/")).should == true
+ end
+ end
+
+ it "raises an ArgumentError when passed '~' if HOME == ''" do
+ ENV["HOME"] = ""
+ -> { File.expand_path("~") }.should.raise(ArgumentError)
+ end
+ end
+
+ describe "File.expand_path with a non-absolute HOME" do
+ before :each do
+ @home = ENV["HOME"]
+ end
+
+ after :each do
+ ENV["HOME"] = @home
+ end
+
+ it "raises an ArgumentError" do
+ ENV["HOME"] = "non-absolute"
+ -> { File.expand_path("~") }.should.raise(ArgumentError, 'non-absolute home')
+ end
+ end
+end
diff --git a/spec/ruby/core/file/extname_spec.rb b/spec/ruby/core/file/extname_spec.rb
new file mode 100644
index 0000000000..995d0ea31a
--- /dev/null
+++ b/spec/ruby/core/file/extname_spec.rb
@@ -0,0 +1,76 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "File.extname" do
+ it "returns the extension (the portion of file name in path after the period)" do
+ File.extname("foo.rb").should == ".rb"
+ File.extname("/foo/bar.rb").should == ".rb"
+ File.extname("/foo.rb/bar.c").should == ".c"
+ File.extname("bar").should == ""
+ File.extname(".bashrc").should == ""
+ File.extname("/foo.bar/baz").should == ""
+ File.extname(".app.conf").should == ".conf"
+ end
+
+ it "returns unfrozen strings" do
+ File.extname("foo.rb").frozen?.should == false
+ File.extname("/foo/bar.rb").frozen?.should == false
+ File.extname("/foo.rb/bar.c").frozen?.should == false
+ File.extname("bar").frozen?.should == false
+ File.extname(".bashrc").frozen?.should == false
+ File.extname("/foo.bar/baz").frozen?.should == false
+ File.extname(".app.conf").frozen?.should == false
+ end
+
+ it "returns the extension for edge cases" do
+ File.extname("").should == ""
+ File.extname(".").should == ""
+ File.extname("/").should == ""
+ File.extname("/.").should == ""
+ File.extname("..").should == ""
+ File.extname("...").should == ""
+ File.extname("....").should == ""
+ end
+
+ describe "for a filename ending with a dot" do
+ platform_is :windows do
+ it "returns ''" do
+ File.extname(".foo.").should == ""
+ File.extname("foo.").should == ""
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns '.'" do
+ File.extname(".foo.").should == "."
+ File.extname("foo.").should == "."
+ end
+ end
+ end
+
+ it "returns only the last extension of a file with several dots" do
+ File.extname("a.b.c.d.e").should == ".e"
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.extname(mock_to_path("a.b.c.d.e")).should == ".e"
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { File.extname(nil) }.should.raise(TypeError)
+ -> { File.extname(0) }.should.raise(TypeError)
+ -> { File.extname(true) }.should.raise(TypeError)
+ -> { File.extname(false) }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { File.extname }.should.raise(ArgumentError)
+ -> { File.extname("foo.bar", "foo.baz") }.should.raise(ArgumentError)
+ end
+
+
+ it "returns the extension for a multibyte filename" do
+ File.extname('Имя.m4a').should == ".m4a"
+ end
+
+end
diff --git a/spec/ruby/core/file/file_spec.rb b/spec/ruby/core/file/file_spec.rb
new file mode 100644
index 0000000000..8a9dfd5fe2
--- /dev/null
+++ b/spec/ruby/core/file/file_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/file'
+
+describe "File" do
+ it "includes Enumerable" do
+ File.include?(Enumerable).should == true
+ end
+
+ it "includes File::Constants" do
+ File.include?(File::Constants).should == true
+ end
+end
+
+describe "File.file?" do
+ it_behaves_like :file_file, :file?, File
+end
diff --git a/spec/ruby/core/file/fixtures/common.rb b/spec/ruby/core/file/fixtures/common.rb
new file mode 100644
index 0000000000..50721388ad
--- /dev/null
+++ b/spec/ruby/core/file/fixtures/common.rb
@@ -0,0 +1,22 @@
+module FileSpecs
+ class SubString < String; end
+
+ def self.make_closer(obj, exc=nil)
+ ScratchPad << :file_opened
+
+ class << obj
+ attr_accessor :close_exception
+
+ alias_method :original_close, :close
+
+ def close
+ original_close
+ ScratchPad << :file_closed
+
+ raise @close_exception if @close_exception
+ end
+ end
+
+ obj.close_exception = exc
+ end
+end
diff --git a/spec/ruby/core/file/fixtures/do_not_remove b/spec/ruby/core/file/fixtures/do_not_remove
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/spec/ruby/core/file/fixtures/do_not_remove
@@ -0,0 +1 @@
+
diff --git a/spec/ruby/core/file/fixtures/file_types.rb b/spec/ruby/core/file/fixtures/file_types.rb
new file mode 100644
index 0000000000..109bcfe42e
--- /dev/null
+++ b/spec/ruby/core/file/fixtures/file_types.rb
@@ -0,0 +1,66 @@
+module FileSpecs
+ def self.configure_types
+ return if @configured
+
+ @file = tmp("test.txt")
+ @dir = Dir.pwd
+ @fifo = tmp("test_fifo")
+ @link = tmp("test_link")
+
+ platform_is_not :windows do
+ @block = `find /dev /devices -type b 2>/dev/null`.split("\n").first
+ @char = `{ tty || find /dev /devices -type c; } 2>/dev/null`.split("\n").last
+ end
+
+ @configured = true
+ end
+
+ def self.normal_file
+ touch(@file)
+ yield @file
+ ensure
+ rm_r @file
+ end
+
+ def self.directory
+ yield @dir
+ end
+
+ def self.fifo
+ File.mkfifo(@fifo)
+ yield @fifo
+ ensure
+ rm_r @fifo
+ end
+
+ def self.block_device
+ raise "Could not find a block device" unless @block
+ yield @block
+ end
+
+ def self.character_device
+ raise "Could not find a character device" unless @char
+ yield @char
+ end
+
+ def self.symlink
+ touch(@file)
+ File.symlink(@file, @link)
+ yield @link
+ ensure
+ rm_r @file, @link
+ end
+
+ def self.socket
+ require_relative '../../../library/socket/fixtures/classes.rb'
+
+ name = SocketSpecs.socket_path
+ socket = UNIXServer.new name
+ begin
+ yield name
+ ensure
+ socket.close
+ rm_r name
+ end
+ end
+end
diff --git a/spec/ruby/core/file/flock_spec.rb b/spec/ruby/core/file/flock_spec.rb
new file mode 100644
index 0000000000..23ddf89ed8
--- /dev/null
+++ b/spec/ruby/core/file/flock_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+
+describe "File#flock" do
+ before :each do
+ ScratchPad.record []
+
+ @name = tmp("flock_test")
+ touch(@name)
+
+ @file = File.open @name, "w+"
+ end
+
+ after :each do
+ @file.flock File::LOCK_UN
+ @file.close
+
+ rm_r @name
+ end
+
+ it "exclusively locks a file" do
+ @file.flock(File::LOCK_EX).should == 0
+ @file.flock(File::LOCK_UN).should == 0
+ end
+
+ it "non-exclusively locks a file" do
+ @file.flock(File::LOCK_SH).should == 0
+ @file.flock(File::LOCK_UN).should == 0
+ end
+
+ it "returns false if trying to lock an exclusively locked file" do
+ @file.flock File::LOCK_EX
+
+ ruby_exe(<<-END_OF_CODE).should == "false"
+ File.open('#{@name}', "w") do |f2|
+ print f2.flock(File::LOCK_EX | File::LOCK_NB).to_s
+ end
+ END_OF_CODE
+ end
+
+ it "blocks if trying to lock an exclusively locked file" do
+ @file.flock File::LOCK_EX
+
+ out = ruby_exe(<<-END_OF_CODE)
+ running = false
+
+ t = Thread.new do
+ File.open('#{@name}', "w") do |f2|
+ puts "before"
+ running = true
+ f2.flock(File::LOCK_EX)
+ puts "after"
+ end
+ end
+
+ Thread.pass until running
+ Thread.pass while t.status and t.status != "sleep"
+ sleep 0.1
+
+ t.kill
+ t.join
+ END_OF_CODE
+
+ out.should == "before\n"
+ end
+
+ it "returns 0 if trying to lock a non-exclusively locked file" do
+ @file.flock File::LOCK_SH
+
+ File.open(@name, "r") do |f2|
+ f2.flock(File::LOCK_SH | File::LOCK_NB).should == 0
+ f2.flock(File::LOCK_UN).should == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/file/fnmatch_spec.rb b/spec/ruby/core/file/fnmatch_spec.rb
new file mode 100644
index 0000000000..a1b7fa12b3
--- /dev/null
+++ b/spec/ruby/core/file/fnmatch_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/fnmatch'
+
+describe "File.fnmatch" do
+ it_behaves_like :file_fnmatch, :fnmatch
+end
+
+describe "File.fnmatch?" do
+ it_behaves_like :file_fnmatch, :fnmatch?
+end
diff --git a/spec/ruby/core/file/ftype_spec.rb b/spec/ruby/core/file/ftype_spec.rb
new file mode 100644
index 0000000000..ab9f76b79b
--- /dev/null
+++ b/spec/ruby/core/file/ftype_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/file_types'
+
+describe "File.ftype" do
+ before :all do
+ FileSpecs.configure_types
+ end
+
+ it "raises ArgumentError if not given exactly one filename" do
+ -> { File.ftype }.should.raise(ArgumentError)
+ -> { File.ftype('blah', 'bleh') }.should.raise(ArgumentError)
+ end
+
+ it "raises Errno::ENOENT if the file is not valid" do
+ -> {
+ File.ftype("/#{$$}#{Time.now.to_f}")
+ }.should.raise(Errno::ENOENT)
+ end
+
+ it "returns a String" do
+ FileSpecs.normal_file do |file|
+ File.ftype(file).should.is_a?(String)
+ end
+ end
+
+ it "returns 'file' when the file is a file" do
+ FileSpecs.normal_file do |file|
+ File.ftype(file).should == 'file'
+ end
+ end
+
+ it "returns 'directory' when the file is a dir" do
+ FileSpecs.directory do |dir|
+ File.ftype(dir).should == 'directory'
+ end
+ end
+
+ it "uses to_path to convert arguments" do
+ FileSpecs.normal_file do |file|
+ obj = mock('path')
+ obj.should_receive(:to_path).and_return(file)
+ File.ftype(obj).should == 'file'
+ end
+ end
+
+ # Both FreeBSD and Windows does not have block devices
+ platform_is_not :freebsd, :windows do
+ with_block_device do
+ it "returns 'blockSpecial' when the file is a block" do
+ FileSpecs.block_device do |block|
+ File.ftype(block).should == 'blockSpecial'
+ end
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns 'characterSpecial' when the file is a char" do
+ FileSpecs.character_device do |char|
+ File.ftype(char).should == 'characterSpecial'
+ end
+ end
+
+ it "returns 'link' when the file is a link" do
+ FileSpecs.symlink do |link|
+ File.ftype(link).should == 'link'
+ end
+ end
+
+ it "returns fifo when the file is a fifo" do
+ FileSpecs.fifo do |fifo|
+ File.ftype(fifo).should == 'fifo'
+ end
+ end
+
+ it "returns 'socket' when the file is a socket" do
+ FileSpecs.socket do |socket|
+ File.ftype(socket).should == 'socket'
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/grpowned_spec.rb b/spec/ruby/core/file/grpowned_spec.rb
new file mode 100644
index 0000000000..8ddac5237c
--- /dev/null
+++ b/spec/ruby/core/file/grpowned_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/grpowned'
+
+describe "File.grpowned?" do
+ it_behaves_like :file_grpowned, :grpowned?, File
+
+ it "returns false if file the does not exist" do
+ File.grpowned?("i_am_a_bogus_file").should == false
+ end
+end
diff --git a/spec/ruby/core/file/identical_spec.rb b/spec/ruby/core/file/identical_spec.rb
new file mode 100644
index 0000000000..bbeaef24d2
--- /dev/null
+++ b/spec/ruby/core/file/identical_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/identical'
+
+describe "File.identical?" do
+ it_behaves_like :file_identical, :identical?, File
+end
diff --git a/spec/ruby/core/file/initialize_spec.rb b/spec/ruby/core/file/initialize_spec.rb
new file mode 100644
index 0000000000..9a76a95260
--- /dev/null
+++ b/spec/ruby/core/file/initialize_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "File#initialize" do
+ after :each do
+ @io.close if @io
+ end
+
+ it "accepts encoding options in mode parameter" do
+ @io = File.new(__FILE__, 'r:UTF-8:iso-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "accepts encoding options as a hash parameter" do
+ @io = File.new(__FILE__, 'r', encoding: 'UTF-8:iso-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+end
diff --git a/spec/ruby/core/file/inspect_spec.rb b/spec/ruby/core/file/inspect_spec.rb
new file mode 100644
index 0000000000..fe87429e8d
--- /dev/null
+++ b/spec/ruby/core/file/inspect_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "File#inspect" do
+ before :each do
+ @name = tmp("file_inspect.txt")
+ @file = File.open @name, "w"
+ end
+
+ after :each do
+ @file.close unless @file.closed?
+ rm_r @name
+ end
+
+ it "returns a String" do
+ @file.inspect.should.instance_of?(String)
+ end
+end
diff --git a/spec/ruby/core/file/join_spec.rb b/spec/ruby/core/file/join_spec.rb
new file mode 100644
index 0000000000..0f0911ea31
--- /dev/null
+++ b/spec/ruby/core/file/join_spec.rb
@@ -0,0 +1,148 @@
+require_relative '../../spec_helper'
+
+describe "File.join" do
+ # see [ruby-core:46804] for the 4 following rules
+ it "changes only boundaries separators" do
+ File.join("file/\\/usr/", "/bin").should == "file/\\/usr/bin"
+ File.join("file://usr", "bin").should == "file://usr/bin"
+ end
+
+ it "respects the given separator if only one part has a boundary separator" do
+ File.join("usr/", "bin").should == "usr/bin"
+ File.join("usr", "/bin").should == "usr/bin"
+ File.join("usr//", "bin").should == "usr//bin"
+ File.join("usr", "//bin").should == "usr//bin"
+ end
+
+ it "joins parts using File::SEPARATOR if there are no boundary separators" do
+ File.join("usr", "bin").should == "usr/bin"
+ end
+
+ it "prefers the separator of the right part if both parts have separators" do
+ File.join("usr/", "//bin").should == "usr//bin"
+ File.join("usr//", "/bin").should == "usr/bin"
+ end
+
+ platform_is :windows do
+ it "respects given separator if only one part has a boundary separator" do
+ File.join("C:\\", 'windows').should == "C:\\windows"
+ File.join("C:", "\\windows").should == "C:\\windows"
+ File.join("\\\\", "usr").should == "\\\\usr"
+ end
+
+ it "prefers the separator of the right part if both parts have separators" do
+ File.join("C:/", "\\windows").should == "C:\\windows"
+ File.join("C:\\", "/windows").should == "C:/windows"
+ end
+ end
+
+ platform_is_not :windows do
+ it "does not treat \\ as a separator on non-Windows" do
+ File.join("usr\\", 'bin').should == "usr\\/bin"
+ File.join("usr", "\\bin").should == "usr/\\bin"
+ File.join("usr/", "\\bin").should == "usr/\\bin"
+ File.join("usr\\", "/bin").should == "usr\\/bin"
+ end
+ end
+
+ it "returns an empty string when given no arguments" do
+ File.join.should == ""
+ end
+
+ it "returns a duplicate string when given a single argument" do
+ str = "usr"
+ File.join(str).should == str
+ File.join(str).should_not.equal?(str)
+ end
+
+ it "supports any number of arguments" do
+ File.join("a", "b", "c", "d").should == "a/b/c/d"
+ end
+
+ it "flattens nested arrays" do
+ File.join(["a", "b", "c"]).should == "a/b/c"
+ File.join(["a", ["b", ["c"]]]).should == "a/b/c"
+ end
+
+ it "inserts the separator in between empty strings and arrays" do
+ File.join("").should == ""
+ File.join("", "").should == "/"
+ File.join(["", ""]).should == "/"
+ File.join("a", "").should == "a/"
+ File.join("", "a").should == "/a"
+
+ File.join([]).should == ""
+ File.join([], []).should == "/"
+ File.join([[], []]).should == "/"
+ File.join("a", []).should == "a/"
+ File.join([], "a").should == "/a"
+ end
+
+ it "handles leading parts edge cases" do
+ File.join("/bin") .should == "/bin"
+ File.join("", "bin") .should == "/bin"
+ File.join("/", "bin") .should == "/bin"
+ File.join("/", "/bin").should == "/bin"
+ end
+
+ it "handles trailing parts edge cases" do
+ File.join("bin", "") .should == "bin/"
+ File.join("bin/") .should == "bin/"
+ File.join("bin/", "") .should == "bin/"
+ File.join("bin", "/") .should == "bin/"
+ File.join("bin/", "/").should == "bin/"
+ end
+
+ it "handles middle parts edge cases" do
+ File.join("usr", "", "bin") .should == "usr/bin"
+ File.join("usr/", "", "bin") .should == "usr/bin"
+ File.join("usr", "", "/bin").should == "usr/bin"
+ File.join("usr/", "", "/bin").should == "usr/bin"
+ end
+
+ # TODO: See MRI svn r23306. Add patchlevel when there is a release.
+ it "raises an ArgumentError if passed a recursive array" do
+ a = ["a"]
+ a << a
+ -> { File.join a }.should.raise(ArgumentError)
+ end
+
+ it "raises a TypeError exception when args are nil" do
+ -> { File.join nil }.should.raise(TypeError)
+ end
+
+ it "calls #to_str" do
+ -> { File.join(mock('x')) }.should.raise(TypeError)
+
+ bin = mock("bin")
+ bin.should_receive(:to_str).exactly(:twice).and_return("bin")
+ File.join(bin).should == "bin"
+ File.join("usr", bin).should == "usr/bin"
+ end
+
+ it "doesn't mutate the object when calling #to_str" do
+ usr = mock("usr")
+ str = "usr"
+ usr.should_receive(:to_str).and_return(str)
+ File.join(usr, "bin").should == "usr/bin"
+ str.should == "usr"
+ end
+
+ it "calls #to_path" do
+ -> { File.join(mock('x')) }.should.raise(TypeError)
+
+ bin = mock("bin")
+ bin.should_receive(:to_path).exactly(:twice).and_return("bin")
+ File.join(bin).should == "bin"
+ File.join("usr", bin).should == "usr/bin"
+ end
+
+ it "raises errors for null bytes" do
+ -> { File.join("\x00x", "metadata.gz") }.should.raise(ArgumentError) { |e|
+ e.message.should == 'string contains null byte'
+ }
+ -> { File.join("metadata.gz", "\x00x") }.should.raise(ArgumentError) { |e|
+ e.message.should == 'string contains null byte'
+ }
+ end
+end
diff --git a/spec/ruby/core/file/lchmod_spec.rb b/spec/ruby/core/file/lchmod_spec.rb
new file mode 100644
index 0000000000..3c44374983
--- /dev/null
+++ b/spec/ruby/core/file/lchmod_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "File.lchmod" do
+ platform_is_not :linux, :windows, :openbsd, :aix do
+ before :each do
+ @fname = tmp('file_chmod_test')
+ @lname = @fname + '.lnk'
+
+ touch(@fname) { |f| f.write "rubinius" }
+
+ rm_r @lname
+ File.symlink @fname, @lname
+ end
+
+ after :each do
+ rm_r @lname, @fname
+ end
+
+ it "changes the file mode of the link and not of the file" do
+ File.chmod(0222, @lname).should == 1
+ File.lchmod(0755, @lname).should == 1
+
+ File.lstat(@lname).should.executable?
+ File.lstat(@lname).should.readable?
+ File.lstat(@lname).should.writable?
+
+ File.stat(@lname).should_not.executable?
+ File.stat(@lname).should_not.readable?
+ File.stat(@lname).should.writable?
+ end
+ end
+end
diff --git a/spec/ruby/core/file/lchown_spec.rb b/spec/ruby/core/file/lchown_spec.rb
new file mode 100644
index 0000000000..8d95d287ba
--- /dev/null
+++ b/spec/ruby/core/file/lchown_spec.rb
@@ -0,0 +1,59 @@
+require_relative '../../spec_helper'
+
+as_superuser do
+ describe "File.lchown" do
+ platform_is_not :windows do
+ before :each do
+ @fname = tmp('file_chown_test')
+ @lname = @fname + '.lnk'
+
+ touch(@fname) { |f| f.chown 501, 501 }
+
+ rm_r @lname
+ File.symlink @fname, @lname
+ end
+
+ after :each do
+ rm_r @lname, @fname
+ end
+
+ it "changes the owner id of the file" do
+ File.lchown 502, nil, @lname
+ File.stat(@fname).uid.should == 501
+ File.lstat(@lname).uid.should == 502
+ File.lchown 0, nil, @lname
+ File.stat(@fname).uid.should == 501
+ File.lstat(@lname).uid.should == 0
+ end
+
+ it "changes the group id of the file" do
+ File.lchown nil, 502, @lname
+ File.stat(@fname).gid.should == 501
+ File.lstat(@lname).gid.should == 502
+ File.lchown nil, 0, @lname
+ File.stat(@fname).uid.should == 501
+ File.lstat(@lname).uid.should == 0
+ end
+
+ it "does not modify the owner id of the file if passed nil or -1" do
+ File.lchown 502, nil, @lname
+ File.lchown nil, nil, @lname
+ File.lstat(@lname).uid.should == 502
+ File.lchown nil, -1, @lname
+ File.lstat(@lname).uid.should == 502
+ end
+
+ it "does not modify the group id of the file if passed nil or -1" do
+ File.lchown nil, 502, @lname
+ File.lchown nil, nil, @lname
+ File.lstat(@lname).gid.should == 502
+ File.lchown nil, -1, @lname
+ File.lstat(@lname).gid.should == 502
+ end
+
+ it "returns the number of files processed" do
+ File.lchown(nil, nil, @lname, @lname).should == 2
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/link_spec.rb b/spec/ruby/core/file/link_spec.rb
new file mode 100644
index 0000000000..768ee4b0fa
--- /dev/null
+++ b/spec/ruby/core/file/link_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "File.link" do
+ before :each do
+ @file = tmp("file_link.txt")
+ @link = tmp("file_link.lnk")
+
+ rm_r @link
+ touch @file
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows, :android do
+ it "link a file with another" do
+ File.link(@file, @link).should == 0
+ File.should.exist?(@link)
+ File.identical?(@file, @link).should == true
+ end
+
+ it "raises an Errno::EEXIST if the target already exists" do
+ File.link(@file, @link)
+ -> { File.link(@file, @link) }.should.raise(Errno::EEXIST)
+ end
+
+ it "raises an ArgumentError if not passed two arguments" do
+ -> { File.link }.should.raise(ArgumentError)
+ -> { File.link(@file) }.should.raise(ArgumentError)
+ -> { File.link(@file, @link, @file) }.should.raise(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed String types" do
+ -> { File.link(@file, nil) }.should.raise(TypeError)
+ -> { File.link(@file, 1) }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/file/lstat_spec.rb b/spec/ruby/core/file/lstat_spec.rb
new file mode 100644
index 0000000000..a5ea9d15a5
--- /dev/null
+++ b/spec/ruby/core/file/lstat_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+require_relative 'shared/stat'
+
+describe "File.lstat" do
+ it_behaves_like :file_stat, :lstat
+end
+
+describe "File.lstat" do
+
+ before :each do
+ @file = tmp('i_exist')
+ @link = tmp('i_am_a_symlink')
+ touch(@file) { |f| f.write 'rubinius' }
+ File.symlink(@file, @link)
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows do
+ it "returns a File::Stat object with symlink properties for a symlink" do
+ st = File.lstat(@link)
+
+ st.should.symlink?
+ st.should_not.file?
+ end
+ end
+end
+
+describe "File#lstat" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/file/lutime_spec.rb b/spec/ruby/core/file/lutime_spec.rb
new file mode 100644
index 0000000000..0f6df42ea3
--- /dev/null
+++ b/spec/ruby/core/file/lutime_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'shared/update_time'
+
+platform_is_not :windows do
+ describe "File.lutime" do
+ it_behaves_like :update_time, :lutime
+ end
+
+ describe "File.lutime" do
+ before :each do
+ @atime = Time.utc(2000)
+ @mtime = Time.utc(2001)
+ @file = tmp("specs_lutime_file")
+ @symlink = tmp("specs_lutime_symlink")
+ touch @file
+ File.symlink(@file, @symlink)
+ end
+
+ after :each do
+ rm_r @file, @symlink
+ end
+
+ it "sets the access and modification time for a regular file" do
+ File.lutime(@atime, @mtime, @file)
+ stat = File.stat(@file)
+ stat.atime.should == @atime
+ stat.mtime.should === @mtime
+ end
+
+ it "sets the access and modification time for a symlink" do
+ original = File.stat(@file)
+
+ File.lutime(@atime, @mtime, @symlink)
+ stat = File.lstat(@symlink)
+ stat.atime.should == @atime
+ stat.mtime.should === @mtime
+
+ file = File.stat(@file)
+ file.atime.should == original.atime
+ file.mtime.should == original.mtime
+ end
+ end
+end
diff --git a/spec/ruby/core/file/mkfifo_spec.rb b/spec/ruby/core/file/mkfifo_spec.rb
new file mode 100644
index 0000000000..ce4a67fe31
--- /dev/null
+++ b/spec/ruby/core/file/mkfifo_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "File.mkfifo" do
+ platform_is_not :windows do
+ before do
+ @path = tmp('fifo')
+ end
+
+ after do
+ rm_r(@path)
+ end
+
+ context "when path passed responds to :to_path" do
+ it "creates a FIFO file at the path specified" do
+ File.mkfifo(@path)
+ File.ftype(@path).should == "fifo"
+ end
+ end
+
+ context "when path passed is not a String value" do
+ it "raises a TypeError" do
+ -> { File.mkfifo(:"/tmp/fifo") }.should.raise(TypeError)
+ end
+ end
+
+ context "when path does not exist" do
+ it "raises an Errno::ENOENT exception" do
+ -> { File.mkfifo("/bogus/path") }.should.raise(Errno::ENOENT)
+ end
+ end
+
+ it "creates a FIFO file at the passed path" do
+ File.mkfifo(@path.to_s)
+ File.ftype(@path).should == "fifo"
+ end
+
+ it "creates a FIFO file with passed mode & ~umask" do
+ File.mkfifo(@path, 0755)
+ File.stat(@path).mode.should == 010755 & ~File.umask
+ end
+
+ it "creates a FIFO file with a default mode of 0666 & ~umask" do
+ File.mkfifo(@path)
+ File.stat(@path).mode.should == 010666 & ~File.umask
+ end
+
+ it "returns 0 after creating the FIFO file" do
+ File.mkfifo(@path).should == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/file/mtime_spec.rb b/spec/ruby/core/file/mtime_spec.rb
new file mode 100644
index 0000000000..d83725e25d
--- /dev/null
+++ b/spec/ruby/core/file/mtime_spec.rb
@@ -0,0 +1,56 @@
+require_relative '../../spec_helper'
+
+describe "File.mtime" do
+ before :each do
+ @filename = tmp('i_exist')
+ touch(@filename) { @mtime = Time.now }
+ end
+
+ after :each do
+ rm_r @filename
+ end
+
+ it "returns the modification Time of the file" do
+ File.mtime(@filename).should.is_a?(Time)
+ File.mtime(@filename).should be_close(@mtime, TIME_TOLERANCE)
+ end
+
+ platform_is :linux, :windows do
+ unless ENV.key?('TRAVIS') # https://bugs.ruby-lang.org/issues/17926
+ it "returns the modification Time of the file with microseconds" do
+ supports_subseconds = Integer(`stat -c%y '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10)
+ if supports_subseconds != 0
+ expected_time = Time.at(Time.now.to_i + 0.123456)
+ File.utime 0, expected_time, @filename
+ File.mtime(@filename).usec.should == expected_time.usec
+ else
+ File.mtime(__FILE__).usec.should == 0
+ end
+ rescue Errno::ENOENT => e
+ # Windows don't have stat command.
+ skip e.message
+ end
+ end
+ end
+
+ it "raises an Errno::ENOENT exception if the file is not found" do
+ -> { File.mtime('bogus') }.should.raise(Errno::ENOENT)
+ end
+end
+
+describe "File#mtime" do
+ before :each do
+ @filename = tmp('i_exist')
+ @f = File.open(@filename, 'w')
+ end
+
+ after :each do
+ @f.close
+ rm_r @filename
+ end
+
+ it "returns the modification Time of the file" do
+ @f.mtime.should.is_a?(Time)
+ end
+
+end
diff --git a/spec/ruby/core/file/new_spec.rb b/spec/ruby/core/file/new_spec.rb
new file mode 100644
index 0000000000..4cd2cb5dcb
--- /dev/null
+++ b/spec/ruby/core/file/new_spec.rb
@@ -0,0 +1,223 @@
+require_relative '../../spec_helper'
+require_relative 'shared/open'
+
+describe "File.new" do
+ before :each do
+ @file = tmp('test.txt')
+ @fh = nil
+ @flags = File::CREAT | File::TRUNC | File::WRONLY
+ touch @file
+ end
+
+ after :each do
+ @fh.close if @fh
+ rm_r @file
+ end
+
+ it "returns a new File with mode string" do
+ @fh = File.new(@file, 'w')
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File with mode num" do
+ @fh = File.new(@file, @flags)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File with modus num and permissions" do
+ rm_r @file
+ File.umask(0011)
+ @fh = File.new(@file, @flags, 0755)
+ @fh.should.is_a?(File)
+ platform_is_not :windows do
+ File.stat(@file).mode.to_s(8).should == "100744"
+ end
+ File.should.exist?(@file)
+ end
+
+ it "creates the file and returns writable descriptor when called with 'w' mode and r-o permissions" do
+ # it should be possible to write to such a file via returned descriptor,
+ # even though the file permissions are r-r-r.
+
+ rm_r @file
+ begin
+ f = File.new(@file, "w", 0444)
+ -> { f.puts("test") }.should_not.raise(IOError)
+ ensure
+ f.close
+ end
+ File.should.exist?(@file)
+ File.read(@file).should == "test\n"
+ end
+
+ platform_is_not :windows do
+ it "opens the existing file, does not change permissions even when they are specified" do
+ File.chmod(0644, @file) # r-w perms
+ orig_perms = File.stat(@file).mode & 0777
+ begin
+ f = File.new(@file, "w", 0444) # r-o perms, but they should be ignored
+ f.puts("test")
+ ensure
+ f.close
+ end
+ perms = File.stat(@file).mode & 0777
+ perms.should == orig_perms
+
+ # it should be still possible to read from the file
+ File.read(@file).should == "test\n"
+ end
+ end
+
+ it "returns a new File with modus fd" do
+ @fh = File.new(@file)
+ fh_copy = File.new(@fh.fileno)
+ fh_copy.autoclose = false
+ fh_copy.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new read-only File when mode is not specified" do
+ @fh = File.new(@file)
+
+ -> { @fh.puts("test") }.should.raise(IOError)
+ @fh.read.should == ""
+ File.should.exist?(@file)
+ end
+
+ it "returns a new read-only File when mode is not specified but flags option is present" do
+ @fh = File.new(@file, flags: File::CREAT)
+
+ -> { @fh.puts("test") }.should.raise(IOError)
+ @fh.read.should == ""
+ File.should.exist?(@file)
+ end
+
+ it "creates a new file when use File::EXCL mode" do
+ @fh = File.new(@file, File::EXCL)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "raises an Errno::EEXIST if the file exists when create a new file with File::CREAT|File::EXCL" do
+ -> { @fh = File.new(@file, File::CREAT|File::EXCL) }.should.raise(Errno::EEXIST)
+ end
+
+ it "creates a new file when use File::WRONLY|File::APPEND mode" do
+ @fh = File.new(@file, File::WRONLY|File::APPEND)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File when use File::APPEND mode" do
+ @fh = File.new(@file, File::APPEND)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File when use File::RDONLY|File::APPEND mode" do
+ @fh = File.new(@file, File::RDONLY|File::APPEND)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new File when use File::RDONLY|File::WRONLY mode" do
+ @fh = File.new(@file, File::RDONLY|File::WRONLY)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "creates a new file when use File::WRONLY|File::TRUNC mode" do
+ @fh = File.new(@file, File::WRONLY|File::TRUNC)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "returns a new read-only File when use File::RDONLY|File::CREAT mode" do
+ @fh = File.new(@file, File::RDONLY|File::CREAT)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+
+ # it's read-only
+ -> { @fh.puts("test") }.should.raise(IOError)
+ @fh.read.should == ""
+ end
+
+ it "returns a new read-only File when use File::CREAT mode" do
+ @fh = File.new(@file, File::CREAT)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+
+ # it's read-only
+ -> { @fh.puts("test") }.should.raise(IOError)
+ @fh.read.should == ""
+ end
+
+ it "coerces filename using to_str" do
+ name = mock("file")
+ name.should_receive(:to_str).and_return(@file)
+ @fh = File.new(name, "w")
+ File.should.exist?(@file)
+ end
+
+ it "coerces filename using #to_path" do
+ name = mock("file")
+ name.should_receive(:to_path).and_return(@file)
+ @fh = File.new(name, "w")
+ File.should.exist?(@file)
+ end
+
+ it "accepts options as a keyword argument" do
+ @fh = File.new(@file, 'w', 0755, flags: @flags)
+ @fh.should.is_a?(File)
+ @fh.close
+
+ -> {
+ @fh = File.new(@file, 'w', 0755, {flags: @flags})
+ }.should.raise(ArgumentError, "wrong number of arguments (given 4, expected 1..3)")
+ end
+
+ it "bitwise-ORs mode and flags option" do
+ -> {
+ @fh = File.new(@file, 'w', flags: File::EXCL)
+ }.should.raise(Errno::EEXIST, /File exists/)
+
+ -> {
+ @fh = File.new(@file, mode: 'w', flags: File::EXCL)
+ }.should.raise(Errno::EEXIST, /File exists/)
+ end
+
+ it "does not use the given block and warns to use File::open" do
+ -> {
+ @fh = File.new(@file) { raise }
+ }.should complain(/warning: File::new\(\) does not take block; use File::open\(\) instead/)
+ end
+
+ it "raises a TypeError if the first parameter can't be coerced to a string" do
+ -> { File.new(true) }.should.raise(TypeError)
+ -> { File.new(false) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if the first parameter is nil" do
+ -> { File.new(nil) }.should.raise(TypeError)
+ end
+
+ it "raises an Errno::EBADF if the first parameter is an invalid file descriptor" do
+ -> { File.new(-1) }.should.raise(Errno::EBADF)
+ end
+
+ platform_is_not :windows do
+ it "can't alter mode or permissions when opening a file" do
+ @fh = File.new(@file)
+ -> {
+ f = File.new(@fh.fileno, @flags)
+ f.autoclose = false
+ }.should.raise(Errno::EINVAL)
+ end
+ end
+
+ platform_is_not :windows do
+ it_behaves_like :open_directory, :new
+ end
+end
diff --git a/spec/ruby/core/file/null_spec.rb b/spec/ruby/core/file/null_spec.rb
new file mode 100644
index 0000000000..355b72b799
--- /dev/null
+++ b/spec/ruby/core/file/null_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "File::NULL" do
+ platform_is :windows do
+ it "returns NUL as a string" do
+ File::NULL.should == 'NUL'
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns /dev/null as a string" do
+ File::NULL.should == '/dev/null'
+ end
+ end
+end
diff --git a/spec/ruby/core/file/open_spec.rb b/spec/ruby/core/file/open_spec.rb
new file mode 100644
index 0000000000..7318c31636
--- /dev/null
+++ b/spec/ruby/core/file/open_spec.rb
@@ -0,0 +1,713 @@
+# encoding: utf-8
+
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/open'
+
+describe "File.open" do
+ before :all do
+ @file = tmp("file_open.txt")
+ @unicode_path = tmp("こんにちは.txt")
+ @nonexistent = tmp("fake.txt")
+ rm_r @file, @nonexistent
+ end
+
+ before :each do
+ ScratchPad.record []
+
+ @fh = @fd = nil
+ @flags = File::CREAT | File::TRUNC | File::WRONLY
+ touch @file
+ end
+
+ after :each do
+ @fh.close if @fh and not @fh.closed?
+ rm_r @file, @unicode_path, @nonexistent
+ end
+
+ describe "with a block" do
+ it "does not raise error when file is closed inside the block" do
+ @fh = File.open(@file) { |fh| fh.close; fh }
+ @fh.should.closed?
+ end
+
+ it "invokes close on an opened file when exiting the block" do
+ File.open(@file, 'r') { |f| FileSpecs.make_closer f }
+
+ ScratchPad.recorded.should == [:file_opened, :file_closed]
+ end
+
+ it "propagates non-StandardErrors produced by close" do
+ -> {
+ File.open(@file, 'r') { |f| FileSpecs.make_closer f, Exception }
+ }.should.raise(Exception)
+
+ ScratchPad.recorded.should == [:file_opened, :file_closed]
+ end
+
+ it "propagates StandardErrors produced by close" do
+ -> {
+ File.open(@file, 'r') { |f| FileSpecs.make_closer f, StandardError }
+ }.should.raise(StandardError)
+
+ ScratchPad.recorded.should == [:file_opened, :file_closed]
+ end
+
+ it "does not propagate IOError with 'closed stream' message produced by close" do
+ File.open(@file, 'r') { |f| FileSpecs.make_closer f, IOError.new('closed stream') }
+
+ ScratchPad.recorded.should == [:file_opened, :file_closed]
+ end
+ end
+
+ it "opens the file (basic case)" do
+ @fh = File.open(@file)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens the file with unicode characters" do
+ @fh = File.open(@unicode_path, "w")
+ @fh.should.is_a?(File)
+ File.should.exist?(@unicode_path)
+ end
+
+ it "opens a file when called with a block" do
+ File.open(@file) { |fh| }
+ File.should.exist?(@file)
+ end
+
+ it "opens with mode string" do
+ @fh = File.open(@file, 'w')
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file with mode string and block" do
+ File.open(@file, 'w') { |fh| }
+ File.should.exist?(@file)
+ end
+
+ it "opens a file with mode num" do
+ @fh = File.open(@file, @flags)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file with mode num and block" do
+ File.open(@file, 'w') { |fh| }
+ File.should.exist?(@file)
+ end
+
+ it "opens a file with mode and permission as nil" do
+ @fh = File.open(@file, nil, nil)
+ @fh.should.is_a?(File)
+ end
+
+ # For this test we delete the file first to reset the perms
+ it "opens the file when passed mode, num and permissions" do
+ rm_r @file
+ File.umask(0011)
+ @fh = File.open(@file, @flags, 0755)
+ @fh.should.is_a?(File)
+ platform_is_not :windows do
+ @fh.lstat.mode.to_s(8).should == "100744"
+ end
+ File.should.exist?(@file)
+ end
+
+ # For this test we delete the file first to reset the perms
+ it "opens the file when passed mode, num, permissions and block" do
+ rm_r @file
+ File.umask(0022)
+ File.open(@file, "w", 0755){ |fh| }
+ platform_is_not :windows do
+ File.stat(@file).mode.to_s(8).should == "100755"
+ end
+ File.should.exist?(@file)
+ end
+
+ it "creates the file and returns writable descriptor when called with 'w' mode and r-o permissions" do
+ # it should be possible to write to such a file via returned descriptor,
+ # even though the file permissions are r-r-r.
+
+ File.open(@file, "w", 0444) { |f| f.write("test") }
+ File.read(@file).should == "test"
+ end
+
+ platform_is_not :windows do
+ it "opens the existing file, does not change permissions even when they are specified" do
+ File.chmod(0664, @file)
+ orig_perms = File.stat(@file).mode.to_s(8)
+ File.open(@file, "w", 0444) { |f| f.write("test") }
+
+ File.stat(@file).mode.to_s(8).should == orig_perms
+ File.read(@file).should == "test"
+ end
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "creates a new write-only file when invoked with 'w' and '0222'" do
+ rm_r @file
+ File.open(@file, 'w', 0222) {}
+ File.readable?(@file).should == false
+ File.writable?(@file).should == true
+ end
+ end
+ end
+
+ it "opens the file when call with fd" do
+ @fh = File.open(@file)
+ fh_copy = File.open(@fh.fileno)
+ fh_copy.autoclose = false
+ fh_copy.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file that no exists when use File::WRONLY mode" do
+ -> { File.open(@nonexistent, File::WRONLY) }.should.raise(Errno::ENOENT)
+ end
+
+ it "opens a file that no exists when use File::RDONLY mode" do
+ -> { File.open(@nonexistent, File::RDONLY) }.should.raise(Errno::ENOENT)
+ end
+
+ it "opens a file that no exists when use 'r' mode" do
+ -> { File.open(@nonexistent, 'r') }.should.raise(Errno::ENOENT)
+ end
+
+ it "opens a file that no exists when use File::EXCL mode" do
+ -> { File.open(@nonexistent, File::EXCL) }.should.raise(Errno::ENOENT)
+ end
+
+ it "opens a file that no exists when use File::NONBLOCK mode" do
+ -> { File.open(@nonexistent, File::NONBLOCK) }.should.raise(Errno::ENOENT)
+ end
+
+ platform_is_not :openbsd, :windows do
+ it "opens a file that no exists when use File::TRUNC mode" do
+ -> { File.open(@nonexistent, File::TRUNC) }.should.raise(Errno::ENOENT)
+ end
+ end
+
+ platform_is :openbsd, :windows do
+ it "does not open a file that does no exists when using File::TRUNC mode" do
+ -> { File.open(@nonexistent, File::TRUNC) }.should.raise(Errno::EINVAL)
+ end
+ end
+
+ platform_is_not :windows do
+ it "opens a file that no exists when use File::NOCTTY mode" do
+ -> { File.open(@nonexistent, File::NOCTTY) }.should.raise(Errno::ENOENT)
+ end
+ end
+
+ it "opens a file that no exists when use File::CREAT mode" do
+ @fh = File.open(@nonexistent, File::CREAT) { |f| f }
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file that no exists when use 'a' mode" do
+ @fh = File.open(@nonexistent, 'a') { |f| f }
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file that no exists when use 'w' mode" do
+ @fh = File.open(@nonexistent, 'w') { |f| f }
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ # Check the grants associated to the different open modes combinations.
+ it "raises an ArgumentError exception when call with an unknown mode" do
+ -> { File.open(@file, "q") }.should.raise(ArgumentError)
+ end
+
+ it "can read in a block when call open with RDONLY mode" do
+ File.open(@file, File::RDONLY) do |f|
+ f.gets.should == nil
+ end
+ end
+
+ it "can read in a block when call open with 'r' mode" do
+ File.open(@file, "r") do |f|
+ f.gets.should == nil
+ end
+ end
+
+ it "raises an IO exception when write in a block opened with RDONLY mode" do
+ File.open(@file, File::RDONLY) do |f|
+ -> { f.puts "writing ..." }.should.raise(IOError)
+ end
+ end
+
+ it "raises an IO exception when write in a block opened with 'r' mode" do
+ File.open(@file, "r") do |f|
+ -> { f.puts "writing ..." }.should.raise(IOError)
+ end
+ end
+
+ it "can't write in a block when call open with File::WRONLY||File::RDONLY mode" do
+ File.open(@file, File::WRONLY|File::RDONLY ) do |f|
+ f.puts("writing").should == nil
+ end
+ end
+
+ it "can't read in a block when call open with File::WRONLY||File::RDONLY mode" do
+ -> {
+ File.open(@file, File::WRONLY|File::RDONLY ) do |f|
+ f.gets.should == nil
+ end
+ }.should.raise(IOError)
+ end
+
+ it "can write in a block when call open with WRONLY mode" do
+ File.open(@file, File::WRONLY) do |f|
+ f.puts("writing").should == nil
+ end
+ end
+
+ it "can write in a block when call open with 'w' mode" do
+ File.open(@file, "w") do |f|
+ f.puts("writing").should == nil
+ end
+ end
+
+ it "raises an IOError when read in a block opened with WRONLY mode" do
+ File.open(@file, File::WRONLY) do |f|
+ -> { f.gets }.should.raise(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with 'w' mode" do
+ File.open(@file, "w") do |f|
+ -> { f.gets }.should.raise(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with 'a' mode" do
+ File.open(@file, "a") do |f|
+ -> { f.gets }.should.raise(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with 'a' mode" do
+ File.open(@file, "a") do |f|
+ f.puts("writing").should == nil
+ -> { f.gets }.should.raise(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with 'a' mode" do
+ File.open(@file, File::WRONLY|File::APPEND ) do |f|
+ -> { f.gets }.should.raise(IOError)
+ end
+ end
+
+ it "raises an IOError when read in a block opened with File::WRONLY|File::APPEND mode" do
+ File.open(@file, File::WRONLY|File::APPEND ) do |f|
+ f.puts("writing").should == nil
+ -> { f.gets }.should.raise(IOError)
+ end
+ end
+
+ it "raises an IOError when write in a block opened with File::RDONLY|File::APPEND mode" do
+ -> {
+ File.open(@file, File::RDONLY|File::APPEND ) do |f|
+ f.puts("writing")
+ end
+ }.should.raise(IOError)
+ end
+
+ it "can read and write in a block when call open with RDWR mode" do
+ File.open(@file, File::RDWR) do |f|
+ f.gets.should == nil
+ f.puts("writing").should == nil
+ f.rewind
+ f.gets.should == "writing\n"
+ end
+ end
+
+ it "can't read in a block when call open with File::EXCL mode" do
+ -> {
+ File.open(@file, File::EXCL) do |f|
+ f.puts("writing").should == nil
+ end
+ }.should.raise(IOError)
+ end
+
+ it "can read in a block when call open with File::EXCL mode" do
+ File.open(@file, File::EXCL) do |f|
+ f.gets.should == nil
+ end
+ end
+
+ it "can read and write in a block when call open with File::RDWR|File::EXCL mode" do
+ File.open(@file, File::RDWR|File::EXCL) do |f|
+ f.gets.should == nil
+ f.puts("writing").should == nil
+ f.rewind
+ f.gets.should == "writing\n"
+ end
+ end
+
+ it "raises an Errno::EEXIST if the file exists when open with File::CREAT|File::EXCL" do
+ -> {
+ File.open(@file, File::CREAT|File::EXCL) do |f|
+ f.puts("writing")
+ end
+ }.should.raise(Errno::EEXIST)
+ end
+
+ it "creates a new file when use File::WRONLY|File::APPEND mode" do
+ @fh = File.open(@file, File::WRONLY|File::APPEND)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file when use File::WRONLY|File::APPEND mode" do
+ File.open(@file, File::WRONLY) do |f|
+ f.puts("hello file")
+ end
+ File.open(@file, File::RDWR|File::APPEND) do |f|
+ f.puts("bye file")
+ f.rewind
+ f.gets.should == "hello file\n"
+ f.gets.should == "bye file\n"
+ f.gets.should == nil
+ end
+ end
+
+ it "raises an IOError if the file exists when open with File::RDONLY|File::APPEND" do
+ -> {
+ File.open(@file, File::RDONLY|File::APPEND) do |f|
+ f.puts("writing").should == nil
+ end
+ }.should.raise(IOError)
+ end
+
+ platform_is_not :openbsd, :windows do
+ it "truncates the file when passed File::TRUNC mode" do
+ File.open(@file, File::RDWR) { |f| f.puts "hello file" }
+ @fh = File.open(@file, File::TRUNC)
+ @fh.gets.should == nil
+ end
+
+ it "can't read in a block when call open with File::TRUNC mode" do
+ File.open(@file, File::TRUNC) do |f|
+ f.gets.should == nil
+ end
+ end
+ end
+
+ it "opens a file when use File::WRONLY|File::TRUNC mode" do
+ fh1 = File.open(@file, "w")
+ begin
+ @fh = File.open(@file, File::WRONLY|File::TRUNC)
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ ensure
+ fh1.close
+ end
+ end
+
+ platform_is_not :openbsd, :windows do
+ it "can't write in a block when call open with File::TRUNC mode" do
+ -> {
+ File.open(@file, File::TRUNC) do |f|
+ f.puts("writing")
+ end
+ }.should.raise(IOError)
+ end
+
+ it "raises an Errno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do
+ -> {
+ File.open(@file, File::RDONLY|File::TRUNC) do |f|
+ f.puts("writing").should == nil
+ end
+ }.should.raise(IOError)
+ end
+ end
+
+ platform_is :openbsd, :windows do
+ it "can't write in a block when call open with File::TRUNC mode" do
+ -> {
+ File.open(@file, File::TRUNC) do |f|
+ f.puts("writing")
+ end
+ }.should.raise(Errno::EINVAL)
+ end
+
+ it "raises an Errno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do
+ -> {
+ File.open(@file, File::RDONLY|File::TRUNC) do |f|
+ f.puts("writing").should == nil
+ end
+ }.should.raise(Errno::EINVAL)
+ end
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "raises an Errno::EACCES when opening non-permitted file" do
+ @fh = File.open(@file, "w")
+ @fh.chmod(000)
+ -> { fh1 = File.open(@file); fh1.close }.should.raise(Errno::EACCES)
+ end
+ end
+ end
+
+ as_user do
+ it "raises an Errno::EACCES when opening read-only file" do
+ @fh = File.open(@file, "w")
+ @fh.chmod(0444)
+ -> { File.open(@file, "w") }.should.raise(Errno::EACCES)
+ end
+ end
+
+ it "opens a file for binary read" do
+ @fh = File.open(@file, "rb")
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file for binary write" do
+ @fh = File.open(@file, "wb")
+ @fh.should.is_a?(File)
+ File.should.exist?(@file)
+ end
+
+ it "opens a file for read-write and truncate the file" do
+ File.open(@file, "w") { |f| f.puts "testing" }
+ File.size(@file).should > 0
+ File.open(@file, "w+") do |f|
+ f.pos.should == 0
+ f.should.eof?
+ end
+ File.size(@file).should == 0
+ end
+
+ it "opens a file for binary read-write starting at the beginning of the file" do
+ File.open(@file, "w") { |f| f.puts "testing" }
+ File.size(@file).should > 0
+ File.open(@file, "rb+") do |f|
+ f.binmode?.should == true
+ f.external_encoding.should == Encoding::ASCII_8BIT
+ f.pos.should == 0
+ f.should_not.eof?
+ end
+ File.open(@file, "r+b") do |f|
+ f.binmode?.should == true
+ f.external_encoding.should == Encoding::ASCII_8BIT
+ f.pos.should == 0
+ f.should_not.eof?
+ end
+ end
+
+ it "opens a file for binary read-write and truncate the file" do
+ File.open(@file, "w") { |f| f.puts "testing" }
+ File.size(@file).should > 0
+ File.open(@file, "wb+") do |f|
+ f.pos.should == 0
+ f.should.eof?
+ end
+ File.size(@file).should == 0
+ end
+
+ platform_is :linux do
+ guard -> { defined?(File::TMPFILE) } do
+ it "creates an unnamed temporary file with File::TMPFILE" do
+ dir = tmp("tmpfilespec")
+ mkdir_p dir
+ begin
+ Dir["#{dir}/*"].should == []
+ File.open(dir, "r+", flags: File::TMPFILE) do |io|
+ io.write("ruby")
+ io.flush
+ io.rewind
+ io.read.should == "ruby"
+ Dir["#{dir}/*"].should == []
+ end
+ rescue Errno::EOPNOTSUPP
+ skip "no support from the filesystem"
+ rescue Errno::EINVAL, Errno::EISDIR
+ skip "presumably bug in glibc"
+ ensure
+ rm_r dir
+ end
+ end
+ end
+ end
+
+ it "raises a TypeError if passed a filename that is not a String or Integer type" do
+ -> { File.open(true) }.should.raise(TypeError)
+ -> { File.open(false) }.should.raise(TypeError)
+ -> { File.open(nil) }.should.raise(TypeError)
+ end
+
+ it "raises a SystemCallError if passed an invalid Integer type" do
+ -> { File.open(-1) }.should.raise(SystemCallError)
+ end
+
+ it "raises an ArgumentError if passed the wrong number of arguments" do
+ -> { File.open(@file, File::CREAT, 0755, 'test') }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed an invalid string for mode" do
+ -> { File.open(@file, 'fake') }.should.raise(ArgumentError)
+ end
+
+ it "defaults external_encoding to BINARY for binary modes" do
+ File.open(@file, 'rb') {|f| f.external_encoding.should == Encoding::BINARY}
+ File.open(@file, 'wb+') {|f| f.external_encoding.should == Encoding::BINARY}
+ end
+
+ it "accepts options as a keyword argument" do
+ @fh = File.open(@file, 'w', 0755, flags: File::CREAT)
+ @fh.should.instance_of?(File)
+
+ -> {
+ File.open(@file, 'w', 0755, {flags: File::CREAT})
+ }.should.raise(ArgumentError, "wrong number of arguments (given 4, expected 1..3)")
+ end
+
+ it "uses the second argument as an options Hash" do
+ @fh = File.open(@file, mode: "r")
+ @fh.should.instance_of?(File)
+ end
+
+ it "calls #to_hash to convert the second argument to a Hash" do
+ options = mock("file open options")
+ options.should_receive(:to_hash).and_return({ mode: "r" })
+
+ @fh = File.open(@file, **options)
+ end
+
+ it "accepts extra flags as a keyword argument and combine with a string mode" do
+ -> {
+ File.open(@file, "w", flags: File::EXCL) { }
+ }.should.raise(Errno::EEXIST)
+
+ -> {
+ File.open(@file, mode: "w", flags: File::EXCL) { }
+ }.should.raise(Errno::EEXIST)
+ end
+
+ it "accepts extra flags as a keyword argument and combine with an integer mode" do
+ -> {
+ File.open(@file, File::WRONLY | File::CREAT, flags: File::EXCL) { }
+ }.should.raise(Errno::EEXIST)
+ end
+
+ platform_is_not :windows do
+ describe "on a FIFO" do
+ before :each do
+ @fifo = tmp("File_open_fifo")
+ File.mkfifo(@fifo)
+ end
+
+ after :each do
+ rm_r @fifo
+ end
+
+ it "opens it as a normal file" do
+ file_w, file_r, read_bytes, written_length = nil
+
+ # open in threads, due to blocking open and writes
+ writer = Thread.new do
+ file_w = File.open(@fifo, 'w')
+ written_length = file_w.syswrite('hello')
+ end
+ reader = Thread.new do
+ file_r = File.open(@fifo, 'r')
+ read_bytes = file_r.sysread(5)
+ end
+
+ begin
+ writer.join
+ reader.join
+
+ written_length.should == 5
+ read_bytes.should == 'hello'
+ ensure
+ file_w.close if file_w
+ file_r.close if file_r
+ end
+ end
+ end
+ end
+
+ it "raises ArgumentError if mixing :newline and binary mode" do
+ -> {
+ File.open(@file, "rb", newline: :universal) {}
+ }.should.raise(ArgumentError, "newline decorator with binary mode")
+ end
+
+ context "'x' flag" do
+ before :each do
+ @xfile = tmp("x-flag")
+ rm_r @xfile
+ end
+
+ after :each do
+ rm_r @xfile
+ end
+
+ it "does nothing if the file doesn't exist" do
+ File.open(@xfile, "wx") { |f| f.write("content") }
+ File.read(@xfile).should == "content"
+ end
+
+ it "throws a Errno::EEXIST error if the file exists" do
+ touch @xfile
+ -> { File.open(@xfile, "wx") }.should.raise(Errno::EEXIST)
+ end
+
+ it "can't be used with 'r' and 'a' flags" do
+ -> { File.open(@xfile, "rx") }.should.raise(ArgumentError, 'invalid access mode rx')
+ -> { File.open(@xfile, "ax") }.should.raise(ArgumentError, 'invalid access mode ax')
+ end
+ end
+end
+
+describe "File.open when passed a file descriptor" do
+ before do
+ @content = "File#open when passed a file descriptor"
+ @name = tmp("file_open_with_fd.txt")
+ @fd = new_fd @name, "w:utf-8"
+ @file = nil
+ end
+
+ after do
+ @file.close if @file and not @file.closed?
+ rm_r @name
+ end
+
+ it "opens a file" do
+ @file = File.open(@fd, "w")
+ @file.should.instance_of?(File)
+ @file.fileno.should.equal?(@fd)
+ @file.write @content
+ @file.flush
+ File.read(@name).should == @content
+ end
+
+ it "opens a file when passed a block" do
+ @file = File.open(@fd, "w") do |f|
+ f.should.instance_of?(File)
+ f.fileno.should.equal?(@fd)
+ f.write @content
+ f
+ end
+ File.read(@name).should == @content
+ end
+end
+
+platform_is_not :windows do
+ describe "File.open" do
+ it_behaves_like :open_directory, :open
+ end
+end
diff --git a/spec/ruby/core/file/owned_spec.rb b/spec/ruby/core/file/owned_spec.rb
new file mode 100644
index 0000000000..06d6796da9
--- /dev/null
+++ b/spec/ruby/core/file/owned_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/owned'
+
+describe "File.owned?" do
+ it_behaves_like :file_owned, :owned?, File
+end
+
+describe "File.owned?" do
+ before :each do
+ @filename = tmp("i_exist")
+ touch(@filename)
+ end
+
+ after :each do
+ rm_r @filename
+ end
+
+ it "returns false if file does not exist" do
+ File.owned?("I_am_a_bogus_file").should == false
+ end
+
+ it "returns true if the file exist and is owned by the user" do
+ File.owned?(@filename).should == true
+ end
+
+ platform_is_not :windows do
+ as_user do
+ it "returns false when the file is not owned by the user" do
+ system_file = '/etc/passwd'
+ File.owned?(system_file).should == false
+ end
+ end
+ end
+
+end
diff --git a/spec/ruby/core/file/path_spec.rb b/spec/ruby/core/file/path_spec.rb
new file mode 100644
index 0000000000..ce78dbeede
--- /dev/null
+++ b/spec/ruby/core/file/path_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'shared/path'
+
+describe "File#path" do
+ it_behaves_like :file_path, :path
+end
+
+describe "File.path" do
+ before :each do
+ @name = tmp("file_path")
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns the string argument without any change" do
+ File.path("abc").should == "abc"
+ File.path("./abc").should == "./abc"
+ File.path("../abc").should == "../abc"
+ File.path("/./a/../bc").should == "/./a/../bc"
+ end
+
+ it "returns path for File argument" do
+ File.open(@name, "w") do |f|
+ File.path(f).should == @name
+ end
+ end
+
+ it "returns path for Pathname argument" do
+ require "pathname"
+ File.path(Pathname.new(@name)).should == @name
+ end
+
+ it "calls #to_path for non-string argument and returns result" do
+ path = mock("path")
+ 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 TypeError
+
+ path = mock("path")
+ path.should_receive(:to_path).and_return(42)
+ -> { File.path(path) }.should.raise TypeError
+ end
+
+ it "raises ArgumentError for string argument contains NUL character" do
+ -> { File.path("\0") }.should.raise ArgumentError
+ -> { File.path("a\0") }.should.raise ArgumentError
+ -> { File.path("a\0c") }.should.raise 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 ArgumentError
+
+ path = mock("path")
+ path.should_receive(:to_path).and_return("a\0")
+ -> { File.path(path) }.should.raise ArgumentError
+
+ path = mock("path")
+ path.should_receive(:to_path).and_return("a\0c")
+ -> { File.path(path) }.should.raise ArgumentError
+ end
+
+ it "raises Encoding::CompatibilityError for ASCII-incompatible string argument" do
+ path = "abc".encode(Encoding::UTF_32BE)
+ -> { File.path(path) }.should.raise 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 Encoding::CompatibilityError
+ end
+end
diff --git a/spec/ruby/core/file/pipe_spec.rb b/spec/ruby/core/file/pipe_spec.rb
new file mode 100644
index 0000000000..01d72dbe85
--- /dev/null
+++ b/spec/ruby/core/file/pipe_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/pipe'
+
+describe "File.pipe?" do
+ it_behaves_like :file_pipe, :pipe?, File
+end
+
+describe "File.pipe?" do
+ it "returns false if file does not exist" do
+ File.pipe?("I_am_a_bogus_file").should == false
+ end
+
+ it "returns false if the file is not a pipe" do
+ filename = tmp("i_exist")
+ touch(filename)
+
+ File.pipe?(filename).should == false
+
+ rm_r filename
+ end
+
+ platform_is_not :windows do
+ it "returns true if the file is a pipe" do
+ filename = tmp("i_am_a_pipe")
+ File.mkfifo(filename)
+
+ File.pipe?(filename).should == true
+
+ rm_r filename
+ end
+ end
+end
diff --git a/spec/ruby/core/file/printf_spec.rb b/spec/ruby/core/file/printf_spec.rb
new file mode 100644
index 0000000000..2530419fc7
--- /dev/null
+++ b/spec/ruby/core/file/printf_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative '../kernel/shared/sprintf'
+
+describe "File#printf" do
+ it_behaves_like :kernel_sprintf, -> format, *args {
+ begin
+ @filename = tmp("printf.txt")
+
+ File.open(@filename, "w", encoding: "utf-8") do |f|
+ f.printf(format, *args)
+ end
+
+ File.read(@filename, encoding: "utf-8")
+ ensure
+ rm_r @filename
+ end
+ }
+end
diff --git a/spec/ruby/core/file/read_spec.rb b/spec/ruby/core/file/read_spec.rb
new file mode 100644
index 0000000000..67a3325cbd
--- /dev/null
+++ b/spec/ruby/core/file/read_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/read'
+
+describe "File.read" do
+ it_behaves_like :file_read_directory, :read, File
+end
diff --git a/spec/ruby/core/file/readable_real_spec.rb b/spec/ruby/core/file/readable_real_spec.rb
new file mode 100644
index 0000000000..524466cd96
--- /dev/null
+++ b/spec/ruby/core/file/readable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/readable_real'
+
+describe "File.readable_real?" do
+ it_behaves_like :file_readable_real, :readable_real?, File
+ it_behaves_like :file_readable_real_missing, :readable_real?, File
+end
diff --git a/spec/ruby/core/file/readable_spec.rb b/spec/ruby/core/file/readable_spec.rb
new file mode 100644
index 0000000000..ed75a23f39
--- /dev/null
+++ b/spec/ruby/core/file/readable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/readable'
+
+describe "File.readable?" do
+ it_behaves_like :file_readable, :readable?, File
+ it_behaves_like :file_readable_missing, :readable?, File
+end
diff --git a/spec/ruby/core/file/readlink_spec.rb b/spec/ruby/core/file/readlink_spec.rb
new file mode 100644
index 0000000000..568692b9b6
--- /dev/null
+++ b/spec/ruby/core/file/readlink_spec.rb
@@ -0,0 +1,86 @@
+require_relative '../../spec_helper'
+
+describe "File.readlink" do
+ # symlink/readlink are not supported on Windows
+ platform_is_not :windows do
+ describe "with absolute paths" do
+ before :each do
+ @file = tmp('file_readlink.txt')
+ @link = tmp('file_readlink.lnk')
+
+ File.symlink(@file, @link)
+ end
+
+ after :each do
+ rm_r @file, @link
+ end
+
+ it "returns the name of the file referenced by the given link" do
+ touch @file
+ File.readlink(@link).should == @file
+ end
+
+ it "returns the name of the file referenced by the given link when the file does not exist" do
+ File.readlink(@link).should == @file
+ end
+
+ it "raises an Errno::ENOENT if there is no such file" do
+ # TODO: missing_file
+ -> { File.readlink("/this/surely/does/not/exist") }.should.raise(Errno::ENOENT)
+ end
+
+ it "raises an Errno::EINVAL if called with a normal file" do
+ touch @file
+ -> { File.readlink(@file) }.should.raise(Errno::EINVAL)
+ end
+ end
+
+ describe "with paths containing unicode characters" do
+ before :each do
+ @file = tmp('tàrget.txt')
+ @link = tmp('lïnk.lnk')
+ File.symlink(@file, @link)
+ end
+
+ after :each do
+ rm_r @file, @link
+ end
+
+ it "returns the name of the file referenced by the given link" do
+ touch @file
+ result = File.readlink(@link)
+ result.encoding.should.equal? Encoding.find('filesystem')
+ result.should == @file.dup.force_encoding(Encoding.find('filesystem'))
+ end
+ end
+
+ describe "when changing the working directory" do
+ before :each do
+ @cwd = Dir.pwd
+ @tmpdir = tmp("/readlink")
+ Dir.mkdir @tmpdir
+ Dir.chdir @tmpdir
+
+ @link = 'readlink_link'
+ @file = 'readlink_file'
+
+ File.symlink(@file, @link)
+ end
+
+ after :each do
+ rm_r @file, @link
+ Dir.chdir @cwd
+ Dir.rmdir @tmpdir
+ end
+
+ it "returns the name of the file referenced by the given link" do
+ touch @file
+ File.readlink(@link).should == @file
+ end
+
+ it "returns the name of the file referenced by the given link when the file does not exist" do
+ File.readlink(@link).should == @file
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/realdirpath_spec.rb b/spec/ruby/core/file/realdirpath_spec.rb
new file mode 100644
index 0000000000..ecf1e0c6d9
--- /dev/null
+++ b/spec/ruby/core/file/realdirpath_spec.rb
@@ -0,0 +1,104 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "File.realdirpath" do
+ before :each do
+ @real_dir = tmp('dir_realdirpath_real')
+ @fake_dir = tmp('dir_realdirpath_fake')
+ @link_dir = tmp('dir_realdirpath_link')
+
+ mkdir_p @real_dir
+ File.symlink(@real_dir, @link_dir)
+
+ @file = File.join(@real_dir, 'file')
+ @link = File.join(@link_dir, 'link')
+
+ touch @file
+ File.symlink(@file, @link)
+
+ @fake_file_in_real_dir = File.join(@real_dir, 'fake_file_in_real_dir')
+ @fake_file_in_fake_dir = File.join(@fake_dir, 'fake_file_in_fake_dir')
+ @fake_link_to_real_dir = File.join(@link_dir, 'fake_link_to_real_dir')
+ @fake_link_to_fake_dir = File.join(@link_dir, 'fake_link_to_fake_dir')
+
+ File.symlink(@fake_file_in_real_dir, @fake_link_to_real_dir)
+ File.symlink(@fake_file_in_fake_dir, @fake_link_to_fake_dir)
+
+ @dir_for_relative_link = File.join(@real_dir, 'dir1')
+ mkdir_p @dir_for_relative_link
+
+ @relative_path_to_file = File.join('..', 'file')
+ @relative_symlink = File.join(@dir_for_relative_link, 'link')
+ File.symlink(@relative_path_to_file, @relative_symlink)
+ end
+
+ after :each do
+ rm_r @file, @link, @fake_link_to_real_dir, @fake_link_to_fake_dir, @real_dir, @link_dir
+ end
+
+ it "returns '/' when passed '/'" do
+ File.realdirpath('/').should == '/'
+ end
+
+ it "returns the real (absolute) pathname not containing symlinks" do
+ File.realdirpath(@link).should == @file
+ end
+
+ it "uses base directory for interpreting relative pathname" do
+ File.realdirpath(File.basename(@link), @link_dir).should == @file
+ end
+
+ it "uses current directory for interpreting relative pathname" do
+ Dir.chdir @link_dir do
+ File.realdirpath(File.basename(@link)).should == @file
+ end
+ end
+
+ it "uses link directory for expanding relative links" do
+ File.realdirpath(@relative_symlink).should == @file
+ end
+
+ it "raises an Errno::ELOOP if the symlink points to itself" do
+ File.unlink @link
+ File.symlink(@link, @link)
+ -> { File.realdirpath(@link) }.should.raise(Errno::ELOOP)
+ end
+
+ it "returns the real (absolute) pathname if the file is absent" do
+ File.realdirpath(@fake_file_in_real_dir).should == @fake_file_in_real_dir
+ end
+
+ it "raises Errno::ENOENT if the directory is absent" do
+ -> { File.realdirpath(@fake_file_in_fake_dir) }.should.raise(Errno::ENOENT)
+ end
+
+ it "returns the real (absolute) pathname if the symlink points to an absent file" do
+ File.realdirpath(@fake_link_to_real_dir).should == @fake_file_in_real_dir
+ end
+
+ it "raises Errno::ENOENT if the symlink points to an absent directory" do
+ -> { File.realdirpath(@fake_link_to_fake_dir) }.should.raise(Errno::ENOENT)
+ end
+ end
+end
+
+platform_is :windows do
+ describe "File.realdirpath" do
+ before :each do
+ @file = tmp("realdirpath")
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the same path" do
+ touch @file
+ File.realdirpath(@file).should == @file
+ end
+
+ it "returns the same path even if the last component does not exist" do
+ File.realdirpath(@file).should == @file
+ end
+ end
+end
diff --git a/spec/ruby/core/file/realpath_spec.rb b/spec/ruby/core/file/realpath_spec.rb
new file mode 100644
index 0000000000..ccb981eff1
--- /dev/null
+++ b/spec/ruby/core/file/realpath_spec.rb
@@ -0,0 +1,98 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "File.realpath" do
+ before :each do
+ @real_dir = tmp('dir_realpath_real')
+ @link_dir = tmp('dir_realpath_link')
+
+ mkdir_p @real_dir
+ File.symlink(@real_dir, @link_dir)
+
+ @file = File.join(@real_dir, 'file')
+ @link = File.join(@link_dir, 'link')
+
+ touch @file
+ File.symlink(@file, @link)
+
+ @fake_file = File.join(@real_dir, 'fake_file')
+ @fake_link = File.join(@link_dir, 'fake_link')
+
+ File.symlink(@fake_file, @fake_link)
+
+ @dir_for_relative_link = File.join(@real_dir, 'dir1')
+ mkdir_p @dir_for_relative_link
+
+ @relative_path_to_file = File.join('..', 'file')
+ @relative_symlink = File.join(@dir_for_relative_link, 'link')
+ File.symlink(@relative_path_to_file, @relative_symlink)
+ end
+
+ after :each do
+ rm_r @file, @link, @fake_link, @real_dir, @link_dir
+ end
+
+ it "returns '/' when passed '/'" do
+ File.realpath('/').should == '/'
+ end
+
+ it "returns the real (absolute) pathname not containing symlinks" do
+ File.realpath(@link).should == @file
+ end
+
+ it "uses base directory for interpreting relative pathname" do
+ File.realpath(File.basename(@link), @link_dir).should == @file
+ end
+
+ it "uses current directory for interpreting relative pathname" do
+ Dir.chdir @link_dir do
+ File.realpath(File.basename(@link)).should == @file
+ end
+ end
+
+ it "uses link directory for expanding relative links" do
+ File.realpath(@relative_symlink).should == @file
+ end
+
+ it "removes the file element when going one level up" do
+ File.realpath('../', @file).should == @real_dir
+ end
+
+ it "raises an Errno::ELOOP if the symlink points to itself" do
+ File.unlink @link
+ File.symlink(@link, @link)
+ -> { File.realpath(@link) }.should.raise(Errno::ELOOP)
+ end
+
+ it "raises Errno::ENOENT if the file is absent" do
+ -> { File.realpath(@fake_file) }.should.raise(Errno::ENOENT)
+ end
+
+ it "raises Errno::ENOENT if the symlink points to an absent file" do
+ -> { File.realpath(@fake_link) }.should.raise(Errno::ENOENT)
+ end
+
+ it "converts the argument with #to_path" do
+ path = mock("path")
+ path.should_receive(:to_path).and_return(__FILE__)
+ File.realpath(path).should == File.realpath(__FILE__ )
+ end
+ end
+end
+
+platform_is :windows do
+ describe "File.realpath" do
+ before :each do
+ @file = tmp("realpath")
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the same path" do
+ File.realpath(@file).should == @file
+ end
+ end
+end
diff --git a/spec/ruby/core/file/rename_spec.rb b/spec/ruby/core/file/rename_spec.rb
new file mode 100644
index 0000000000..70ea669a68
--- /dev/null
+++ b/spec/ruby/core/file/rename_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "File.rename" do
+ before :each do
+ @old = tmp("file_rename.txt")
+ @new = tmp("file_rename.new")
+
+ rm_r @new
+ touch(@old) { |f| f.puts "hello" }
+ end
+
+ after :each do
+ rm_r @old, @new
+ end
+
+ it "renames a file" do
+ File.should.exist?(@old)
+ File.should_not.exist?(@new)
+ File.rename(@old, @new)
+ File.should_not.exist?(@old)
+ File.should.exist?(@new)
+ end
+
+ it "raises an Errno::ENOENT if the source does not exist" do
+ rm_r @old
+ -> { File.rename(@old, @new) }.should.raise(Errno::ENOENT)
+ end
+
+ it "raises an ArgumentError if not passed two arguments" do
+ -> { File.rename }.should.raise(ArgumentError)
+ -> { File.rename(@file) }.should.raise(ArgumentError)
+ end
+
+ it "raises a TypeError if not passed String types" do
+ -> { File.rename(1, 2) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/file/reopen_spec.rb b/spec/ruby/core/file/reopen_spec.rb
new file mode 100644
index 0000000000..858d424c67
--- /dev/null
+++ b/spec/ruby/core/file/reopen_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "File#reopen" do
+ before :each do
+ @name_a = tmp("file_reopen_a.txt")
+ @name_b = tmp("file_reopen_b.txt")
+ @content_a = "File#reopen a"
+ @content_b = "File#reopen b"
+
+ touch(@name_a) { |f| f.write @content_a }
+ touch(@name_b) { |f| f.write @content_b }
+
+ @file = nil
+ end
+
+ after :each do
+ @file.close if @file and not @file.closed?
+ rm_r @name_a, @name_b
+ end
+
+ it "resets the stream to a new file path" do
+ file = File.new @name_a, "r"
+ file.read.should == @content_a
+ @file = file.reopen(@name_b, "r")
+ @file.read.should == @content_b
+ end
+
+ it "calls #to_path to convert an Object" do
+ @file = File.new(@name_a).reopen(mock_to_path(@name_b), "r")
+ @file.read.should == @content_b
+ end
+end
diff --git a/spec/ruby/core/file/setgid_spec.rb b/spec/ruby/core/file/setgid_spec.rb
new file mode 100644
index 0000000000..f5df5390f5
--- /dev/null
+++ b/spec/ruby/core/file/setgid_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/setgid'
+
+describe "File.setgid?" do
+ it_behaves_like :file_setgid, :setgid?, File
+end
+
+describe "File.setgid?" do
+ before :each do
+ @name = tmp('test.txt')
+ touch @name
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns false if the file was just made" do
+ File.setgid?(@name).should == false
+ end
+
+ it "returns false if the file does not exist" do
+ rm_r @name # delete it prematurely, just for this part
+ File.setgid?(@name).should == false
+ end
+
+ as_superuser do
+ platform_is_not :windows do
+ it "returns true when the gid bit is set" do
+ system "chmod g+s #{@name}"
+
+ File.setgid?(@name).should == true
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/setuid_spec.rb b/spec/ruby/core/file/setuid_spec.rb
new file mode 100644
index 0000000000..9e5e86df61
--- /dev/null
+++ b/spec/ruby/core/file/setuid_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/setuid'
+
+describe "File.setuid?" do
+ it_behaves_like :file_setuid, :setuid?, File
+end
+
+describe "File.setuid?" do
+ before :each do
+ @name = tmp('test.txt')
+ touch @name
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns false if the file was just made" do
+ File.setuid?(@name).should == false
+ end
+
+ it "returns false if the file does not exist" do
+ rm_r @name # delete it prematurely, just for this part
+ File.setuid?(@name).should == false
+ end
+
+ platform_is_not :windows do
+ it "returns true when the gid bit is set" do
+ system "chmod u+s #{@name}"
+
+ File.setuid?(@name).should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/fnmatch.rb b/spec/ruby/core/file/shared/fnmatch.rb
new file mode 100644
index 0000000000..b9140d027d
--- /dev/null
+++ b/spec/ruby/core/file/shared/fnmatch.rb
@@ -0,0 +1,294 @@
+describe :file_fnmatch, shared: true do
+ it "matches entire strings" do
+ File.send(@method, 'cat', 'cat').should == true
+ end
+
+ it "does not match partial strings" do
+ File.send(@method, 'cat', 'category').should == false
+ end
+
+ it "does not support { } patterns by default" do
+ File.send(@method, 'c{at,ub}s', 'cats').should == false
+ File.send(@method, 'c{at,ub}s', 'c{at,ub}s').should == true
+ end
+
+ it "supports some { } patterns when File::FNM_EXTGLOB is passed" do
+ File.send(@method, "{a,b}", "a", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{a,b}", "b", File::FNM_EXTGLOB).should == true
+ File.send(@method, "c{at,ub}s", "cats", File::FNM_EXTGLOB).should == true
+ File.send(@method, "c{at,ub}s", "cubs", File::FNM_EXTGLOB).should == true
+ File.send(@method, "-c{at,ub}s-", "-cats-", File::FNM_EXTGLOB).should == true
+ File.send(@method, "-c{at,ub}s-", "-cubs-", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{a,b,c}{d,e,f}{g,h}", "adg", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{a,b,c}{d,e,f}{g,h}", "bdg", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{a,b,c}{d,e,f}{g,h}", "ceh", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{aa,bb,cc,dd}", "aa", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{aa,bb,cc,dd}", "bb", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{aa,bb,cc,dd}", "cc", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{aa,bb,cc,dd}", "dd", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{1,5{a,b{c,d}}}", "1", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{1,5{a,b{c,d}}}", "5a", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{1,5{a,b{c,d}}}", "5bc", File::FNM_EXTGLOB).should == true
+ File.send(@method, "{1,5{a,b{c,d}}}", "5bd", File::FNM_EXTGLOB).should == true
+ File.send(@method, "\\\\{a\\,b,b\\}c}", "\\a,b", File::FNM_EXTGLOB).should == true
+ File.send(@method, "\\\\{a\\,b,b\\}c}", "\\b}c", File::FNM_EXTGLOB).should == true
+ end
+
+ it "doesn't support some { } patterns even when File::FNM_EXTGLOB is passed" do
+ File.send(@method, "a{0..3}b", "a0b", File::FNM_EXTGLOB).should == false
+ File.send(@method, "a{0..3}b", "a1b", File::FNM_EXTGLOB).should == false
+ File.send(@method, "a{0..3}b", "a2b", File::FNM_EXTGLOB).should == false
+ File.send(@method, "a{0..3}b", "a3b", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{0..12}", "0", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{0..12}", "6", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{0..12}", "12", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{3..-2}", "3", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{3..-2}", "0", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{3..-2}", "-2", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{a..g}", "a", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{a..g}", "d", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{a..g}", "g", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{g..a}", "a", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{g..a}", "d", File::FNM_EXTGLOB).should == false
+ File.send(@method, "{g..a}", "g", File::FNM_EXTGLOB).should == false
+ File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false
+ File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: ,", File::FNM_EXTGLOB).should == false
+ File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: }", File::FNM_EXTGLOB).should == false
+ File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false
+ end
+
+ it "doesn't match an extra } when File::FNM_EXTGLOB is passed" do
+ File.send(@method, 'c{at,ub}}s', 'cats', File::FNM_EXTGLOB).should == false
+ end
+
+ it "matches when both FNM_EXTGLOB and FNM_PATHNAME are passed" do
+ File.send(@method, "?.md", "a.md", File::FNM_EXTGLOB | File::FNM_PATHNAME).should == true
+ end
+
+ it "matches a single character for each ? character" do
+ File.send(@method, 'c?t', 'cat').should == true
+ File.send(@method, 'c??t', 'cat').should == false
+ end
+
+ it "matches zero or more characters for each * character" do
+ File.send(@method, 'c*', 'cats').should == true
+ File.send(@method, 'c*t', 'c/a/b/t').should == true
+ end
+
+ it "does not match unterminated range of characters" do
+ File.send(@method, 'abc[de', 'abcd').should == false
+ end
+
+ it "does not match unterminated range of characters as a literal" do
+ File.send(@method, 'abc[de', 'abc[de').should == false
+ end
+
+ it "matches ranges of characters using bracket expression (e.g. [a-z])" do
+ File.send(@method, 'ca[a-z]', 'cat').should == true
+ end
+
+ it "matches ranges of characters using bracket expression, taking case into account" do
+ File.send(@method, '[a-z]', 'D').should == false
+ File.send(@method, '[^a-z]', 'D').should == true
+ File.send(@method, '[A-Z]', 'd').should == false
+ File.send(@method, '[^A-Z]', 'd').should == true
+ File.send(@method, '[a-z]', 'D', File::FNM_CASEFOLD).should == true
+ end
+
+ it "does not match characters outside of the range of the bracket expression" do
+ File.send(@method, 'ca[x-z]', 'cat').should == false
+ File.send(@method, '/ca[s][s-t]/rul[a-b]/[z]he/[x-Z]orld', '/cats/rule/the/World').should == false
+ end
+
+ it "matches ranges of characters using exclusive bracket expression (e.g. [^t] or [!t])" do
+ File.send(@method, 'ca[^t]', 'cat').should == false
+ File.send(@method, 'ca[^t]', 'cas').should == true
+ File.send(@method, 'ca[!t]', 'cat').should == false
+ end
+
+ it "matches characters with a case sensitive comparison" do
+ File.send(@method, 'cat', 'CAT').should == false
+ end
+
+ it "matches characters with case insensitive comparison when flags includes FNM_CASEFOLD" do
+ File.send(@method, 'cat', 'CAT', File::FNM_CASEFOLD).should == true
+ end
+
+ platform_is_not :windows do
+ it "doesn't match case sensitive characters on platforms with case sensitive paths, when flags include FNM_SYSCASE" do
+ File.send(@method, 'cat', 'CAT', File::FNM_SYSCASE).should == false
+ end
+ end
+
+ platform_is :windows do
+ it "matches case sensitive characters on platforms with case insensitive paths, when flags include FNM_SYSCASE" do
+ File.send(@method, 'cat', 'CAT', File::FNM_SYSCASE).should == true
+ end
+ end
+
+ it "matches wildcard with characters when flags includes FNM_PATHNAME" do
+ File.send(@method, '*a', 'aa', File::FNM_PATHNAME).should == true
+ File.send(@method, 'a*', 'aa', File::FNM_PATHNAME).should == true
+ File.send(@method, 'a*', 'aaa', File::FNM_PATHNAME).should == true
+ File.send(@method, '*a', 'aaa', File::FNM_PATHNAME).should == true
+ end
+
+ it "does not match '/' characters with ? or * when flags includes FNM_PATHNAME" do
+ File.send(@method, '?', '/', File::FNM_PATHNAME).should == false
+ File.send(@method, '*', '/', File::FNM_PATHNAME).should == false
+ end
+
+ it "does not match '/' characters inside bracket expressions when flags includes FNM_PATHNAME" do
+ File.send(@method, '[/]', '/', File::FNM_PATHNAME).should == false
+ end
+
+ it "matches literal ? or * in path when pattern includes \\? or \\*" do
+ File.send(@method, '\?', '?').should == true
+ File.send(@method, '\?', 'a').should == false
+
+ File.send(@method, '\*', '*').should == true
+ File.send(@method, '\*', 'a').should == false
+ end
+
+ it "matches literal character (e.g. 'a') in path when pattern includes escaped character (e.g. \\a)" do
+ File.send(@method, '\a', 'a').should == true
+ File.send(@method, 'this\b', 'thisb').should == true
+ end
+
+ it "matches '\\' characters in path when flags includes FNM_NOESACPE" do
+ File.send(@method, '\a', '\a', File::FNM_NOESCAPE).should == true
+ File.send(@method, '\a', 'a', File::FNM_NOESCAPE).should == false
+ File.send(@method, '\[foo\]\[bar\]', '[foo][bar]', File::FNM_NOESCAPE).should == false
+ end
+
+ it "escapes special characters inside bracket expression" do
+ File.send(@method, '[\?]', '?').should == true
+ File.send(@method, '[\*]', '*').should == true
+ end
+
+ it "does not match leading periods in filenames with wildcards by default" do
+ File.should_not.send(@method, '*', '.profile')
+ File.should.send(@method, '*', 'home/.profile')
+ File.should.send(@method, '*/*', 'home/.profile')
+ File.should_not.send(@method, '*/*', 'dave/.profile', File::FNM_PATHNAME)
+ end
+
+ it "matches patterns with leading periods to dotfiles" do
+ File.send(@method, '.*', '.profile').should == true
+ File.send(@method, '.*', '.profile', File::FNM_PATHNAME).should == true
+ File.send(@method, ".*file", "nondotfile").should == false
+ File.send(@method, ".*file", "nondotfile", File::FNM_PATHNAME).should == false
+ end
+
+ it "does not match directories with leading periods by default with FNM_PATHNAME" do
+ File.send(@method, '.*', '.directory/nondotfile', File::FNM_PATHNAME).should == false
+ File.send(@method, '.*', '.directory/.profile', File::FNM_PATHNAME).should == false
+ File.send(@method, '.*', 'foo/.directory/nondotfile', File::FNM_PATHNAME).should == false
+ File.send(@method, '.*', 'foo/.directory/.profile', File::FNM_PATHNAME).should == false
+ File.send(@method, '**/.dotfile', '.dotsubdir/.dotfile', File::FNM_PATHNAME).should == false
+ end
+
+ it "matches leading periods in filenames when flags includes FNM_DOTMATCH" do
+ File.send(@method, '*', '.profile', File::FNM_DOTMATCH).should == true
+ File.send(@method, '*', 'home/.profile', File::FNM_DOTMATCH).should == true
+ end
+
+ it "matches multiple directories with ** and *" do
+ files = '**/*.rb'
+ File.send(@method, files, 'main.rb').should == false
+ File.send(@method, files, './main.rb').should == false
+ File.send(@method, files, 'lib/song.rb').should == true
+ File.send(@method, '**.rb', 'main.rb').should == true
+ File.send(@method, '**.rb', './main.rb').should == false
+ File.send(@method, '**.rb', 'lib/song.rb').should == true
+ File.send(@method, '*', 'dave/.profile').should == true
+ end
+
+ it "matches multiple directories with ** when flags includes File::FNM_PATHNAME" do
+ files = '**/*.rb'
+ flags = File::FNM_PATHNAME
+
+ File.send(@method, files, 'main.rb', flags).should == true
+ File.send(@method, files, 'one/two/three/main.rb', flags).should == true
+ File.send(@method, files, './main.rb', flags).should == false
+
+ flags = File::FNM_PATHNAME | File::FNM_DOTMATCH
+
+ File.send(@method, files, './main.rb', flags).should == true
+ File.send(@method, files, 'one/two/.main.rb', flags).should == true
+
+ File.send(@method, "**/best/*", 'lib/my/best/song.rb').should == true
+ end
+
+ it "returns false if '/' in pattern do not match '/' in path when flags includes FNM_PATHNAME" do
+ pattern = '*/*'
+ File.send(@method, pattern, 'dave/.profile', File::FNM_PATHNAME).should == false
+
+ pattern = '**/foo'
+ File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME).should == false
+ end
+
+ it "returns true if '/' in pattern match '/' in path when flags includes FNM_PATHNAME" do
+ pattern = '*/*'
+ File.send(@method, pattern, 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true
+
+ pattern = '**/foo'
+ File.send(@method, pattern, 'a/b/c/foo', File::FNM_PATHNAME).should == true
+ File.send(@method, pattern, '/a/b/c/foo', File::FNM_PATHNAME).should == true
+ File.send(@method, pattern, 'c:/a/b/c/foo', File::FNM_PATHNAME).should == true
+ File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true
+ end
+
+ it "has special handling for ./ when using * and FNM_PATHNAME" do
+ File.send(@method, './*', '.', File::FNM_PATHNAME).should == false
+ File.send(@method, './*', './', File::FNM_PATHNAME).should == true
+ File.send(@method, './*/', './', File::FNM_PATHNAME).should == false
+ File.send(@method, './**', './', File::FNM_PATHNAME).should == true
+ File.send(@method, './**/', './', File::FNM_PATHNAME).should == true
+ File.send(@method, './*', '.', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false
+ File.send(@method, './*', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true
+ File.send(@method, './*/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false
+ File.send(@method, './**', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true
+ File.send(@method, './**/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true
+ end
+
+ it "matches **/* with FNM_PATHNAME to recurse directories" do
+ File.send(@method, 'nested/**/*', 'nested/subdir', File::FNM_PATHNAME).should == true
+ File.send(@method, 'nested/**/*', 'nested/subdir/file', File::FNM_PATHNAME).should == true
+ File.send(@method, 'nested/**/*', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true
+ File.send(@method, 'nested/**/*', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true
+ end
+
+ it "matches ** with FNM_PATHNAME only in current directory" do
+ File.send(@method, 'nested/**', 'nested/subdir', File::FNM_PATHNAME).should == true
+ File.send(@method, 'nested/**', 'nested/subdir/file', File::FNM_PATHNAME).should == false
+ File.send(@method, 'nested/**', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == true
+ File.send(@method, 'nested/**', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should == false
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.send(@method, '\*', mock_to_path('a')).should == false
+ end
+
+ it "raises a TypeError if the first and second arguments are not string-like" do
+ -> { File.send(@method, nil, nil, 0, 0) }.should.raise(ArgumentError)
+ -> { File.send(@method, 1, 'some/thing') }.should.raise(TypeError)
+ -> { File.send(@method, 'some/thing', 1) }.should.raise(TypeError)
+ -> { File.send(@method, 1, 1) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if the third argument is not an Integer" do
+ -> { File.send(@method, "*/place", "path/to/file", "flags") }.should.raise(TypeError)
+ -> { File.send(@method, "*/place", "path/to/file", nil) }.should.raise(TypeError)
+ end
+
+ it "does not raise a TypeError if the third argument can be coerced to an Integer" do
+ flags = mock("flags")
+ flags.should_receive(:to_int).and_return(10)
+ -> { File.send(@method, "*/place", "path/to/file", flags) }.should_not.raise
+ end
+
+ it "matches multibyte characters" do
+ File.fnmatch("*/ä/ø/ñ", "a/ä/ø/ñ").should == true
+ end
+end
diff --git a/spec/ruby/core/file/shared/open.rb b/spec/ruby/core/file/shared/open.rb
new file mode 100644
index 0000000000..67149235ca
--- /dev/null
+++ b/spec/ruby/core/file/shared/open.rb
@@ -0,0 +1,12 @@
+require_relative '../../dir/fixtures/common'
+
+describe :open_directory, shared: true do
+ it "opens directories" do
+ file = File.send(@method, tmp(""))
+ begin
+ file.should.is_a?(File)
+ ensure
+ file.close
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/path.rb b/spec/ruby/core/file/shared/path.rb
new file mode 100644
index 0000000000..6c6f7d4234
--- /dev/null
+++ b/spec/ruby/core/file/shared/path.rb
@@ -0,0 +1,82 @@
+describe :file_path, shared: true do
+ before :each do
+ @path = tmp("file_to_path")
+ @name = File.basename(@path)
+ touch @path
+ end
+
+ after :each do
+ @file.close if @file and !@file.closed?
+ rm_r @path
+ end
+
+ it "returns a String" do
+ @file = File.new @path
+ @file.send(@method).should.instance_of?(String)
+ end
+
+ it "returns a different String on every call" do
+ @file = File.new @path
+ path1 = @file.send(@method)
+ path2 = @file.send(@method)
+ path1.should == path2
+ path1.should_not.equal?(path2)
+ end
+
+ it "returns a mutable String" do
+ @file = File.new @path.dup.freeze
+ path = @file.send(@method)
+ path.should == @path
+ path.should_not.frozen?
+ path << "test"
+ @file.send(@method).should == @path
+ end
+
+ it "calls to_str on argument and returns exact value" do
+ path = mock('path')
+ path.should_receive(:to_str).and_return(@path)
+ @file = File.new path
+ @file.send(@method).should == @path
+ end
+
+ it "does not normalise the path it returns" do
+ Dir.chdir(tmp("")) do
+ unorm = "./#{@name}"
+ @file = File.new unorm
+ @file.send(@method).should == unorm
+ end
+ end
+
+ it "does not canonicalize the path it returns" do
+ dir = File.basename tmp("")
+ path = "#{tmp("")}../#{dir}/#{@name}"
+ @file = File.new path
+ @file.send(@method).should == path
+ end
+
+ it "does not absolute-ise the path it returns" do
+ Dir.chdir(tmp("")) do
+ @file = File.new @name
+ @file.send(@method).should == @name
+ end
+ end
+
+ it "preserves the encoding of the path" do
+ path = @path.force_encoding("euc-jp")
+ @file = File.new path
+ @file.send(@method).encoding.should == Encoding.find("euc-jp")
+ end
+
+ platform_is :linux do
+ guard -> { defined?(File::TMPFILE) } do
+ before :each do
+ @dir = tmp("tmpfilespec")
+ mkdir_p @dir
+ end
+
+ after :each do
+ rm_r @dir
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/read.rb b/spec/ruby/core/file/shared/read.rb
new file mode 100644
index 0000000000..f60800bb2f
--- /dev/null
+++ b/spec/ruby/core/file/shared/read.rb
@@ -0,0 +1,15 @@
+require_relative '../../dir/fixtures/common'
+
+describe :file_read_directory, shared: true do
+ platform_is :darwin, :linux, :freebsd, :openbsd, :windows do
+ it "raises an Errno::EISDIR when passed a path that is a directory" do
+ -> { @object.send(@method, ".") }.should.raise(Errno::EISDIR)
+ end
+ end
+
+ platform_is :netbsd do
+ it "does not raises any exception when passed a path that is a directory" do
+ -> { @object.send(@method, ".") }.should_not.raise
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/stat.rb b/spec/ruby/core/file/shared/stat.rb
new file mode 100644
index 0000000000..879a7f11ff
--- /dev/null
+++ b/spec/ruby/core/file/shared/stat.rb
@@ -0,0 +1,32 @@
+describe :file_stat, shared: true do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns a File::Stat object if the given file exists" do
+ st = File.send(@method, @file)
+ st.should.instance_of?(File::Stat)
+ end
+
+ it "returns a File::Stat object when called on an instance of File" do
+ File.open(@file) do |f|
+ st = f.send(@method)
+ st.should.instance_of?(File::Stat)
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.send(@method, mock_to_path(@file))
+ end
+
+ it "raises an Errno::ENOENT if the file does not exist" do
+ -> {
+ File.send(@method, "fake_file")
+ }.should.raise(Errno::ENOENT)
+ end
+end
diff --git a/spec/ruby/core/file/shared/unlink.rb b/spec/ruby/core/file/shared/unlink.rb
new file mode 100644
index 0000000000..0032907ba2
--- /dev/null
+++ b/spec/ruby/core/file/shared/unlink.rb
@@ -0,0 +1,61 @@
+describe :file_unlink, shared: true do
+ before :each do
+ @file1 = tmp('test.txt')
+ @file2 = tmp('test2.txt')
+
+ touch @file1
+ touch @file2
+ end
+
+ after :each do
+ File.send(@method, @file1) if File.exist?(@file1)
+ File.send(@method, @file2) if File.exist?(@file2)
+
+ @file1 = nil
+ @file2 = nil
+ end
+
+ it "returns 0 when called without arguments" do
+ File.send(@method).should == 0
+ end
+
+ it "deletes a single file" do
+ File.send(@method, @file1).should == 1
+ File.should_not.exist?(@file1)
+ end
+
+ it "deletes multiple files" do
+ File.send(@method, @file1, @file2).should == 2
+ File.should_not.exist?(@file1)
+ File.should_not.exist?(@file2)
+ end
+
+ it "raises a TypeError if not passed a String type" do
+ -> { File.send(@method, 1) }.should.raise(TypeError)
+ end
+
+ it "raises an Errno::ENOENT when the given file doesn't exist" do
+ -> { File.send(@method, 'bogus') }.should.raise(Errno::ENOENT)
+ end
+
+ it "coerces a given parameter into a string if possible" do
+ mock = mock("to_str")
+ mock.should_receive(:to_str).and_return(@file1)
+ File.send(@method, mock).should == 1
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.send(@method, mock_to_path(@file1)).should == 1
+ end
+
+ platform_is :windows do
+ it "allows deleting an open file with File::SHARE_DELETE" do
+ path = tmp("share_delete.txt")
+ File.open(path, mode: File::CREAT | File::WRONLY | File::BINARY | File::SHARE_DELETE) do |f|
+ File.should.exist?(path)
+ File.send(@method, path)
+ end
+ File.should_not.exist?(path)
+ end
+ end
+end
diff --git a/spec/ruby/core/file/shared/update_time.rb b/spec/ruby/core/file/shared/update_time.rb
new file mode 100644
index 0000000000..3fe7266a00
--- /dev/null
+++ b/spec/ruby/core/file/shared/update_time.rb
@@ -0,0 +1,105 @@
+describe :update_time, shared: true do
+ before :all do
+ @time_is_float = platform_is :windows
+ end
+
+ before :each do
+ @atime = Time.now
+ @mtime = Time.now
+ @file1 = tmp("specs_file_utime1")
+ @file2 = tmp("specs_file_utime2")
+ touch @file1
+ touch @file2
+ end
+
+ after :each do
+ rm_r @file1, @file2
+ end
+
+ it "sets the access and modification time of each file" do
+ File.send(@method, @atime, @mtime, @file1, @file2)
+
+ if @time_is_float
+ File.atime(@file1).should be_close(@atime, 0.0001)
+ File.mtime(@file1).should be_close(@mtime, 0.0001)
+ File.atime(@file2).should be_close(@atime, 0.0001)
+ File.mtime(@file2).should be_close(@mtime, 0.0001)
+ else
+ File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
+ File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
+ File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
+ File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
+ end
+ end
+
+ it "uses the current times if two nil values are passed" do
+ tn = Time.now
+ File.send(@method, nil, nil, @file1, @file2)
+
+ if @time_is_float
+ File.atime(@file1).should be_close(tn, 0.050)
+ File.mtime(@file1).should be_close(tn, 0.050)
+ File.atime(@file2).should be_close(tn, 0.050)
+ File.mtime(@file2).should be_close(tn, 0.050)
+ else
+ File.atime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
+ File.mtime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
+ File.atime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
+ File.mtime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
+ end
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.send(@method, @atime, @mtime, mock_to_path(@file1), mock_to_path(@file2))
+ end
+
+ it "accepts numeric atime and mtime arguments" do
+ if @time_is_float
+ File.send(@method, @atime.to_f, @mtime.to_f, @file1, @file2)
+
+ File.atime(@file1).should be_close(@atime, 0.0001)
+ File.mtime(@file1).should be_close(@mtime, 0.0001)
+ File.atime(@file2).should be_close(@atime, 0.0001)
+ File.mtime(@file2).should be_close(@mtime, 0.0001)
+ else
+ File.send(@method, @atime.to_i, @mtime.to_i, @file1, @file2)
+
+ File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
+ File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
+ File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
+ File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
+ end
+ end
+
+ it "may set nanosecond precision" do
+ t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r)
+ File.send(@method, t, t, @file1)
+
+ File.atime(@file1).nsec.should.between?(0, 123500000)
+ File.mtime(@file1).nsec.should.between?(0, 123500000)
+ end
+
+ it "returns the number of filenames in the arguments" do
+ File.send(@method, @atime.to_f, @mtime.to_f, @file1, @file2).should == 2
+ end
+
+ platform_is :linux do
+ platform_is pointer_size: 64 do
+ it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19 or 2486-07-02)" do
+ # https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps
+ # "Therefore, timestamps should not overflow until May 2446."
+ # https://lwn.net/Articles/804382/
+ # "On-disk timestamps hitting the y2038 limit..."
+ # The problem seems to be being improved, but currently it actually fails on XFS on RHEL8
+ # https://rubyci.org/logs/rubyci.s3.amazonaws.com/rhel8/ruby-master/log/20201112T123004Z.fail.html.gz
+ # Amazon Linux 2023 returns 2486-07-02 in this example
+ # http://rubyci.s3.amazonaws.com/amazon2023/ruby-master/log/20230322T063004Z.fail.html.gz
+ time = Time.at(1<<44)
+ File.send(@method, time, time, @file1)
+
+ [559444, 2486, 2446, 2038].should.include? File.atime(@file1).year
+ [559444, 2486, 2446, 2038].should.include? File.mtime(@file1).year
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/size_spec.rb b/spec/ruby/core/file/size_spec.rb
new file mode 100644
index 0000000000..784c18c26e
--- /dev/null
+++ b/spec/ruby/core/file/size_spec.rb
@@ -0,0 +1,119 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/size'
+
+describe "File.size?" do
+ it_behaves_like :file_size, :size?, File
+end
+
+describe "File.size?" do
+ it_behaves_like :file_size_to_io, :size?, File
+end
+
+describe "File.size?" do
+ it_behaves_like :file_size_nil_when_missing, :size?, File
+end
+
+describe "File.size?" do
+ it_behaves_like :file_size_nil_when_empty, :size?, File
+end
+
+describe "File.size?" do
+ it_behaves_like :file_size_with_file_argument, :size?, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size, :size, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size_to_io, :size, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size_raise_when_missing, :size, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size_0_when_empty, :size, File
+end
+
+describe "File.size" do
+ it_behaves_like :file_size_with_file_argument, :size, File
+end
+
+describe "File#size" do
+
+ before :each do
+ @name = tmp('i_exist')
+ touch(@name) { |f| f.write 'rubinius' }
+ @file = File.new @name
+ @file_org = @file
+ end
+
+ after :each do
+ @file_org.close unless @file_org.closed?
+ rm_r @name
+ end
+
+ it "is an instance method" do
+ @file.respond_to?(:size).should == true
+ end
+
+ it "returns the file's size as an Integer" do
+ @file.size.should.instance_of?(Integer)
+ end
+
+ it "returns the file's size in bytes" do
+ @file.size.should == 8
+ end
+
+ platform_is_not :windows do # impossible to remove opened file on Windows
+ it "returns the cached size of the file if subsequently deleted" do
+ rm_r @file.path
+ @file.size.should == 8
+ end
+ end
+
+ it "returns the file's current size even if modified" do
+ File.open(@file.path,'a') {|f| f.write '!'}
+ @file.size.should == 9
+ end
+
+ it "raises an IOError on a closed file" do
+ @file.close
+ -> { @file.size }.should.raise(IOError)
+ end
+
+ platform_is_not :windows do
+ it "follows symlinks if necessary" do
+ ln_file = tmp('i_exist_ln')
+ rm_r ln_file
+
+ begin
+ File.symlink(@file.path, ln_file).should == 0
+ file = File.new(ln_file)
+ file.size.should == 8
+ ensure
+ file.close if file && !file.closed?
+ rm_r ln_file
+ end
+ end
+ end
+end
+
+describe "File#size for an empty file" do
+ before :each do
+ @name = tmp('empty')
+ touch(@name)
+ @file = File.new @name
+ end
+
+ after :each do
+ @file.close unless @file.closed?
+ rm_r @name
+ end
+
+ it "returns 0" do
+ @file.size.should == 0
+ end
+end
diff --git a/spec/ruby/core/file/socket_spec.rb b/spec/ruby/core/file/socket_spec.rb
new file mode 100644
index 0000000000..d3f4eb013a
--- /dev/null
+++ b/spec/ruby/core/file/socket_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/socket'
+
+describe "File.socket?" do
+ it_behaves_like :file_socket, :socket?, File
+
+ it "returns false if file does not exist" do
+ File.socket?("I_am_a_bogus_file").should == false
+ end
+end
diff --git a/spec/ruby/core/file/split_spec.rb b/spec/ruby/core/file/split_spec.rb
new file mode 100644
index 0000000000..e989a6b86e
--- /dev/null
+++ b/spec/ruby/core/file/split_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+
+describe "File.split" do
+ before :each do
+ @backslash_ext = "C:\\foo\\bar\\baz.rb"
+ @backslash = "C:\\foo\\bar\\baz"
+ end
+
+ it "splits the string at the last '/' when the last component does not have an extension" do
+ File.split("/foo/bar/baz").should == ["/foo/bar", "baz"]
+ File.split("C:/foo/bar/baz").should == ["C:/foo/bar", "baz"]
+ end
+
+ it "splits the string at the last '/' when the last component has an extension" do
+ File.split("/foo/bar/baz.rb").should == ["/foo/bar", "baz.rb"]
+ File.split("C:/foo/bar/baz.rb").should == ["C:/foo/bar", "baz.rb"]
+ end
+
+ it "splits an empty string into a '.' and an empty string" do
+ File.split("").should == [".", ""]
+ end
+
+ platform_is_not :windows do
+ it "collapses multiple '/' characters and strips trailing ones" do
+ File.split("//foo////").should == ["/", "foo"]
+ end
+ end
+
+ platform_is_not :windows do
+ it "does not split a string that contains '\\'" do
+ File.split(@backslash).should == [".", "C:\\foo\\bar\\baz"]
+ File.split(@backslash_ext).should == [".", "C:\\foo\\bar\\baz.rb"]
+ end
+ end
+
+ platform_is :windows do
+ it "splits the string at the last '\\' when the last component does not have an extension" do
+ File.split(@backslash).should == ["C:\\foo\\bar", "baz"]
+ end
+
+ it "splits the string at the last '\\' when the last component has an extension" do
+ File.split(@backslash_ext).should == ["C:\\foo\\bar", "baz.rb"]
+ end
+ end
+
+ it "raises an ArgumentError when not passed a single argument" do
+ -> { File.split }.should.raise(ArgumentError)
+ -> { File.split('string', 'another string') }.should.raise(ArgumentError)
+ end
+
+ it "raises a TypeError if the argument is not a String type" do
+ -> { File.split(1) }.should.raise(TypeError)
+ end
+
+ it "coerces the argument with to_str if it is not a String type" do
+ obj = mock("str")
+ obj.should_receive(:to_str).and_return("/one/two/three")
+ File.split(obj).should == ["/one/two", "three"]
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.split(mock_to_path("")).should == [".", ""]
+ end
+end
diff --git a/spec/ruby/core/file/stat/atime_spec.rb b/spec/ruby/core/file/stat/atime_spec.rb
new file mode 100644
index 0000000000..2fecaed300
--- /dev/null
+++ b/spec/ruby/core/file/stat/atime_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#atime" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the atime of a File::Stat object" do
+ st = File.stat(@file)
+ st.atime.should.is_a?(Time)
+ st.atime.should <= Time.now
+ end
+end
diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb
new file mode 100644
index 0000000000..728f635397
--- /dev/null
+++ b/spec/ruby/core/file/stat/birthtime_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../../spec_helper'
+
+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
+ ]
+
+ 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
+
+ it "returns the birthtime of a File::Stat object" do
+ st = File.stat(@file)
+ st.birthtime.should.is_a?(Time)
+ st.birthtime.should <= Time.now
+ rescue NotImplementedError => e
+ e.message.should.start_with?(*not_implemented_messages)
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/blksize_spec.rb b/spec/ruby/core/file/stat/blksize_spec.rb
new file mode 100644
index 0000000000..4d85b05e4d
--- /dev/null
+++ b/spec/ruby/core/file/stat/blksize_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#blksize" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns the blksize of a File::Stat object" do
+ st = File.stat(@file)
+ st.blksize.is_a?(Integer).should == true
+ st.blksize.should > 0
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ st = File.stat(@file)
+ st.blksize.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/blockdev_spec.rb b/spec/ruby/core/file/stat/blockdev_spec.rb
new file mode 100644
index 0000000000..f986c18125
--- /dev/null
+++ b/spec/ruby/core/file/stat/blockdev_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/blockdev'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#blockdev?" do
+ it_behaves_like :file_blockdev, :blockdev?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/blocks_spec.rb b/spec/ruby/core/file/stat/blocks_spec.rb
new file mode 100644
index 0000000000..5e0efc8bc2
--- /dev/null
+++ b/spec/ruby/core/file/stat/blocks_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#blocks" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns a non-negative integer" do
+ st = File.stat(@file)
+ st.blocks.is_a?(Integer).should == true
+ st.blocks.should >= 0
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ st = File.stat(@file)
+ st.blocks.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/chardev_spec.rb b/spec/ruby/core/file/stat/chardev_spec.rb
new file mode 100644
index 0000000000..622fb2052d
--- /dev/null
+++ b/spec/ruby/core/file/stat/chardev_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/chardev'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#chardev?" do
+ it_behaves_like :file_chardev, :chardev?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/comparison_spec.rb b/spec/ruby/core/file/stat/comparison_spec.rb
new file mode 100644
index 0000000000..faa3b6bf62
--- /dev/null
+++ b/spec/ruby/core/file/stat/comparison_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#<=>" do
+ before :each do
+ @name1 = tmp("i_exist")
+ @name2 = tmp("i_exist_too")
+ touch @name1
+ touch @name2
+ end
+
+ after :each do
+ rm_r @name1, @name2
+ end
+
+ it "is able to compare files by the same modification times" do
+ now = Time.now - 1 # 1 second ago to avoid NFS cache issue
+ File.utime(now, now, @name1)
+ File.utime(now, now, @name2)
+
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat <=> file2.stat).should == 0
+ }
+ }
+ end
+
+ it "is able to compare files by different modification times" do
+ now = Time.now
+ File.utime(now, now + 100, @name2)
+
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat <=> file2.stat).should == -1
+ }
+ }
+
+ File.utime(now, now - 100, @name2)
+
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat <=> file2.stat).should == 1
+ }
+ }
+ end
+
+ # TODO: Fix
+ it "includes Comparable and #== shows mtime equality between two File::Stat objects" do
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat == file1.stat).should == true
+ (file2.stat == file2.stat).should == true
+ }
+ }
+
+ now = Time.now
+ File.utime(now, now + 100, @name2)
+
+ File.open(@name1) { |file1|
+ File.open(@name2) { |file2|
+ (file1.stat == file2.stat).should == false
+ (file1.stat == file1.stat).should == true
+ (file2.stat == file2.stat).should == true
+ }
+ }
+ end
+end
diff --git a/spec/ruby/core/file/stat/ctime_spec.rb b/spec/ruby/core/file/stat/ctime_spec.rb
new file mode 100644
index 0000000000..dbf43a7453
--- /dev/null
+++ b/spec/ruby/core/file/stat/ctime_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#ctime" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the ctime of a File::Stat object" do
+ st = File.stat(@file)
+ st.ctime.should.is_a?(Time)
+ st.ctime.should <= Time.now
+ end
+end
diff --git a/spec/ruby/core/file/stat/dev_major_spec.rb b/spec/ruby/core/file/stat/dev_major_spec.rb
new file mode 100644
index 0000000000..a199eaaa11
--- /dev/null
+++ b/spec/ruby/core/file/stat/dev_major_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#dev_major" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ after :each do
+ rm_r @name
+ end
+
+ platform_is_not :windows do
+ it "returns the major part of File::Stat#dev" do
+ File.stat(@name).dev_major.should.is_a?(Integer)
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ File.stat(@name).dev_major.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/dev_minor_spec.rb b/spec/ruby/core/file/stat/dev_minor_spec.rb
new file mode 100644
index 0000000000..8ce94778ca
--- /dev/null
+++ b/spec/ruby/core/file/stat/dev_minor_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#dev_minor" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ after :each do
+ rm_r @name
+ end
+
+ platform_is_not :windows do
+ it "returns the minor part of File::Stat#dev" do
+ File.stat(@name).dev_minor.should.is_a?(Integer)
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ File.stat(@name).dev_minor.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/dev_spec.rb b/spec/ruby/core/file/stat/dev_spec.rb
new file mode 100644
index 0000000000..cd5e3d175e
--- /dev/null
+++ b/spec/ruby/core/file/stat/dev_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#dev" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ after :each do
+ rm_r @name
+ end
+
+ it "returns the number of the device on which the file exists" do
+ File.stat(@name).dev.should.is_a?(Integer)
+ end
+end
diff --git a/spec/ruby/core/file/stat/directory_spec.rb b/spec/ruby/core/file/stat/directory_spec.rb
new file mode 100644
index 0000000000..c03610388b
--- /dev/null
+++ b/spec/ruby/core/file/stat/directory_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/directory'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#directory?" do
+ it_behaves_like :file_directory, :directory?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/executable_real_spec.rb b/spec/ruby/core/file/stat/executable_real_spec.rb
new file mode 100644
index 0000000000..23bffe89c5
--- /dev/null
+++ b/spec/ruby/core/file/stat/executable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/executable_real'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#executable_real?" do
+ it_behaves_like :file_executable_real, :executable_real?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/executable_spec.rb b/spec/ruby/core/file/stat/executable_spec.rb
new file mode 100644
index 0000000000..422975d14b
--- /dev/null
+++ b/spec/ruby/core/file/stat/executable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/executable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#executable?" do
+ it_behaves_like :file_executable, :executable?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/file_spec.rb b/spec/ruby/core/file/stat/file_spec.rb
new file mode 100644
index 0000000000..d141536b4b
--- /dev/null
+++ b/spec/ruby/core/file/stat/file_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/file'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#file?" do
+ it_behaves_like :file_file, :file?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/fixtures/classes.rb b/spec/ruby/core/file/stat/fixtures/classes.rb
new file mode 100644
index 0000000000..4fe9a2a30f
--- /dev/null
+++ b/spec/ruby/core/file/stat/fixtures/classes.rb
@@ -0,0 +1,5 @@
+class FileStat
+ def self.method_missing(meth, file)
+ File.lstat(file).send(meth)
+ end
+end
diff --git a/spec/ruby/core/file/stat/ftype_spec.rb b/spec/ruby/core/file/stat/ftype_spec.rb
new file mode 100644
index 0000000000..df2e3ada1e
--- /dev/null
+++ b/spec/ruby/core/file/stat/ftype_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/file_types'
+
+describe "File::Stat#ftype" do
+ before :all do
+ FileSpecs.configure_types
+ end
+
+ it "returns a String" do
+ FileSpecs.normal_file do |file|
+ File.lstat(file).ftype.should.is_a?(String)
+ end
+ end
+
+ it "returns 'file' when the file is a file" do
+ FileSpecs.normal_file do |file|
+ File.lstat(file).ftype.should == 'file'
+ end
+ end
+
+ it "returns 'directory' when the file is a dir" do
+ FileSpecs.directory do |dir|
+ File.lstat(dir).ftype.should == 'directory'
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns 'characterSpecial' when the file is a char" do
+ FileSpecs.character_device do |char|
+ File.lstat(char).ftype.should == 'characterSpecial'
+ end
+ end
+ end
+
+ platform_is_not :freebsd do # FreeBSD does not have block devices
+ with_block_device do
+ it "returns 'blockSpecial' when the file is a block" do
+ FileSpecs.block_device do |block|
+ File.lstat(block).ftype.should == 'blockSpecial'
+ end
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it "returns 'link' when the file is a link" do
+ FileSpecs.symlink do |link|
+ File.lstat(link).ftype.should == 'link'
+ end
+ end
+
+ it "returns fifo when the file is a fifo" do
+ FileSpecs.fifo do |fifo|
+ File.lstat(fifo).ftype.should == 'fifo'
+ end
+ end
+
+ it "returns 'socket' when the file is a socket" do
+ FileSpecs.socket do |socket|
+ File.lstat(socket).ftype.should == 'socket'
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/gid_spec.rb b/spec/ruby/core/file/stat/gid_spec.rb
new file mode 100644
index 0000000000..3bba65bc82
--- /dev/null
+++ b/spec/ruby/core/file/stat/gid_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#gid" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ File.chown(nil, Process.gid, @file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the group owner attribute of a File::Stat object" do
+ st = File.stat(@file)
+ st.gid.is_a?(Integer).should == true
+ st.gid.should == Process.gid
+ end
+end
diff --git a/spec/ruby/core/file/stat/grpowned_spec.rb b/spec/ruby/core/file/stat/grpowned_spec.rb
new file mode 100644
index 0000000000..e7278e229b
--- /dev/null
+++ b/spec/ruby/core/file/stat/grpowned_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/grpowned'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#grpowned?" do
+ it_behaves_like :file_grpowned, :grpowned?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/ino_spec.rb b/spec/ruby/core/file/stat/ino_spec.rb
new file mode 100644
index 0000000000..c09b6448c7
--- /dev/null
+++ b/spec/ruby/core/file/stat/ino_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#ino" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ platform_is_not :windows do
+ it "returns the ino of a File::Stat object" do
+ st = File.stat(@file)
+ st.ino.should.is_a?(Integer)
+ st.ino.should > 0
+ end
+ end
+
+ platform_is :windows do
+ it "returns BY_HANDLE_FILE_INFORMATION.nFileIndexHigh/Low of a File::Stat object" do
+ st = File.stat(@file)
+ st.ino.should.is_a?(Integer)
+ st.ino.should > 0
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/inspect_spec.rb b/spec/ruby/core/file/stat/inspect_spec.rb
new file mode 100644
index 0000000000..1613b427d0
--- /dev/null
+++ b/spec/ruby/core/file/stat/inspect_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#inspect" do
+
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "produces a nicely formatted description of a File::Stat object" do
+ st = File.stat(@file)
+ expected = "#<File::Stat dev=0x#{st.dev.to_s(16)}, ino=#{st.ino}, mode=#{sprintf("%07o", st.mode)}, nlink=#{st.nlink}"
+ expected << ", uid=#{st.uid}, gid=#{st.gid}, rdev=0x#{st.rdev.to_s(16)}, size=#{st.size}, blksize=#{st.blksize.inspect}"
+ expected << ", blocks=#{st.blocks.inspect}, atime=#{st.atime.inspect}, mtime=#{st.mtime.inspect}, ctime=#{st.ctime.inspect}"
+ platform_is :netbsd, :freebsd, :darwin do
+ # Windows has File.birthtime but it's not here since already shown by ctime.
+ expected << ", birthtime=#{st.birthtime.inspect}"
+ end
+ expected << ">"
+ st.inspect.should == expected
+ end
+end
diff --git a/spec/ruby/core/file/stat/mode_spec.rb b/spec/ruby/core/file/stat/mode_spec.rb
new file mode 100644
index 0000000000..c85fb85a58
--- /dev/null
+++ b/spec/ruby/core/file/stat/mode_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#mode" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ File.chmod(0644, @file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the mode of a File::Stat object" do
+ st = File.stat(@file)
+ st.mode.is_a?(Integer).should == true
+ (st.mode & 0777).should == 0644
+ end
+end
diff --git a/spec/ruby/core/file/stat/mtime_spec.rb b/spec/ruby/core/file/stat/mtime_spec.rb
new file mode 100644
index 0000000000..7844491212
--- /dev/null
+++ b/spec/ruby/core/file/stat/mtime_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#mtime" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the mtime of a File::Stat object" do
+ st = File.stat(@file)
+ st.mtime.should.is_a?(Time)
+ st.mtime.should <= Time.now
+ end
+end
diff --git a/spec/ruby/core/file/stat/new_spec.rb b/spec/ruby/core/file/stat/new_spec.rb
new file mode 100644
index 0000000000..b8c3600028
--- /dev/null
+++ b/spec/ruby/core/file/stat/new_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#initialize" do
+
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ File.chmod(0755, @file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "raises an exception if the file doesn't exist" do
+ -> {
+ File::Stat.new(tmp("i_am_a_dummy_file_that_doesnt_exist"))
+ }.should.raise(Errno::ENOENT)
+ end
+
+ it "creates a File::Stat object for the given file" do
+ st = File::Stat.new(@file)
+ st.should.is_a?(File::Stat)
+ st.ftype.should == 'file'
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return @file
+ File::Stat.new p
+ end
+end
diff --git a/spec/ruby/core/file/stat/nlink_spec.rb b/spec/ruby/core/file/stat/nlink_spec.rb
new file mode 100644
index 0000000000..7143923cfc
--- /dev/null
+++ b/spec/ruby/core/file/stat/nlink_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#nlink" do
+ before :each do
+ @file = tmp("stat_nlink")
+ @link = @file + ".lnk"
+ touch @file
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows, :android do
+ it "returns the number of links to a file" do
+ File::Stat.new(@file).nlink.should == 1
+ File.link(@file, @link)
+ File::Stat.new(@file).nlink.should == 2
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/owned_spec.rb b/spec/ruby/core/file/stat/owned_spec.rb
new file mode 100644
index 0000000000..a23ad850c5
--- /dev/null
+++ b/spec/ruby/core/file/stat/owned_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/owned'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#owned?" do
+ it_behaves_like :file_owned, :owned?, FileStat
+end
+
+describe "File::Stat#owned?" do
+ before :each do
+ @file = tmp("i_exist")
+ touch(@file)
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns true if the file is owned by the user" do
+ st = File.stat(@file)
+ st.should.owned?
+ end
+
+ platform_is_not :windows, :android do
+ as_user do
+ it "returns false if the file is not owned by the user" do
+ system_file = '/etc/passwd'
+ st = File.stat(system_file)
+ st.should_not.owned?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/pipe_spec.rb b/spec/ruby/core/file/stat/pipe_spec.rb
new file mode 100644
index 0000000000..692dfbf42a
--- /dev/null
+++ b/spec/ruby/core/file/stat/pipe_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/pipe'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#pipe?" do
+ it_behaves_like :file_pipe, :pipe?, FileStat
+end
+
+describe "File::Stat#pipe?" do
+ it "returns false if the file is not a pipe" do
+ filename = tmp("i_exist")
+ touch(filename)
+
+ st = File.stat(filename)
+ st.should_not.pipe?
+
+ rm_r filename
+ end
+
+ platform_is_not :windows do
+ it "returns true if the file is a pipe" do
+ filename = tmp("i_am_a_pipe")
+ File.mkfifo(filename)
+
+ st = File.stat(filename)
+ st.should.pipe?
+
+ rm_r filename
+ 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
new file mode 100644
index 0000000000..e1b44fc2d0
--- /dev/null
+++ b/spec/ruby/core/file/stat/rdev_major_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#rdev_major" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ platform_is_not :windows do
+ it "returns the major part of File::Stat#rdev" do
+ File.stat(@name).rdev_major.should.is_a?(Integer)
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ File.stat(@name).rdev_major.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/rdev_minor_spec.rb b/spec/ruby/core/file/stat/rdev_minor_spec.rb
new file mode 100644
index 0000000000..8af3b9f587
--- /dev/null
+++ b/spec/ruby/core/file/stat/rdev_minor_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#rdev_minor" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ platform_is_not :windows do
+ it "returns the minor part of File::Stat#rdev" do
+ File.stat(@name).rdev_minor.should.is_a?(Integer)
+ end
+ end
+
+ platform_is :windows do
+ it "returns nil" do
+ File.stat(@name).rdev_minor.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/file/stat/rdev_spec.rb b/spec/ruby/core/file/stat/rdev_spec.rb
new file mode 100644
index 0000000000..7e4252fcc6
--- /dev/null
+++ b/spec/ruby/core/file/stat/rdev_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#rdev" do
+ before :each do
+ @name = tmp("file.txt")
+ touch(@name)
+ end
+ after :each do
+ rm_r @name
+ end
+
+ it "returns the number of the device this file represents which the file exists" do
+ File.stat(@name).rdev.should.is_a?(Integer)
+ end
+end
diff --git a/spec/ruby/core/file/stat/readable_real_spec.rb b/spec/ruby/core/file/stat/readable_real_spec.rb
new file mode 100644
index 0000000000..f138fd7b00
--- /dev/null
+++ b/spec/ruby/core/file/stat/readable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/readable_real'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#readable_real?" do
+ it_behaves_like :file_readable_real, :readable_real?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/readable_spec.rb b/spec/ruby/core/file/stat/readable_spec.rb
new file mode 100644
index 0000000000..e99e48feed
--- /dev/null
+++ b/spec/ruby/core/file/stat/readable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/readable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#readable?" do
+ it_behaves_like :file_readable, :readable?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/setgid_spec.rb b/spec/ruby/core/file/stat/setgid_spec.rb
new file mode 100644
index 0000000000..c0748ede57
--- /dev/null
+++ b/spec/ruby/core/file/stat/setgid_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/setgid'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#setgid?" do
+ it_behaves_like :file_setgid, :setgid?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/setuid_spec.rb b/spec/ruby/core/file/stat/setuid_spec.rb
new file mode 100644
index 0000000000..6408120fc4
--- /dev/null
+++ b/spec/ruby/core/file/stat/setuid_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/setuid'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#setuid?" do
+ it_behaves_like :file_setuid, :setuid?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/size_spec.rb b/spec/ruby/core/file/stat/size_spec.rb
new file mode 100644
index 0000000000..4b4f57f8c8
--- /dev/null
+++ b/spec/ruby/core/file/stat/size_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/size'
+require_relative 'fixtures/classes'
+
+describe "File::Stat.size?" do
+ it_behaves_like :file_size, :size?, FileStat
+ it_behaves_like :file_size_nil_when_empty, :size?, FileStat
+end
+
+describe "File::Stat.size" do
+ it_behaves_like :file_size, :size, FileStat
+ it_behaves_like :file_size_0_when_empty, :size, FileStat
+end
+
+describe "File::Stat#size" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "File::Stat#size?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/file/stat/socket_spec.rb b/spec/ruby/core/file/stat/socket_spec.rb
new file mode 100644
index 0000000000..09740be110
--- /dev/null
+++ b/spec/ruby/core/file/stat/socket_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/socket'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#socket?" do
+ it_behaves_like :file_socket, :socket?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/sticky_spec.rb b/spec/ruby/core/file/stat/sticky_spec.rb
new file mode 100644
index 0000000000..7083e644e9
--- /dev/null
+++ b/spec/ruby/core/file/stat/sticky_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/sticky'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#sticky?" do
+ it_behaves_like :file_sticky, :sticky?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/symlink_spec.rb b/spec/ruby/core/file/stat/symlink_spec.rb
new file mode 100644
index 0000000000..0def832a4c
--- /dev/null
+++ b/spec/ruby/core/file/stat/symlink_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/symlink'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#symlink?" do
+ it_behaves_like :file_symlink, :symlink?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/uid_spec.rb b/spec/ruby/core/file/stat/uid_spec.rb
new file mode 100644
index 0000000000..b97147db21
--- /dev/null
+++ b/spec/ruby/core/file/stat/uid_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../../spec_helper'
+
+describe "File::Stat#uid" do
+ before :each do
+ @file = tmp('i_exist')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @file
+ end
+
+ it "returns the owner attribute of a File::Stat object" do
+ st = File.stat(@file)
+ st.uid.is_a?(Integer).should == true
+ st.uid.should == Process.uid
+ end
+end
diff --git a/spec/ruby/core/file/stat/world_readable_spec.rb b/spec/ruby/core/file/stat/world_readable_spec.rb
new file mode 100644
index 0000000000..d94a02205e
--- /dev/null
+++ b/spec/ruby/core/file/stat/world_readable_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/world_readable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat.world_readable?" do
+ it_behaves_like :file_world_readable, :world_readable?, FileStat
+end
+
+describe "File::Stat#world_readable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/file/stat/world_writable_spec.rb b/spec/ruby/core/file/stat/world_writable_spec.rb
new file mode 100644
index 0000000000..8100008344
--- /dev/null
+++ b/spec/ruby/core/file/stat/world_writable_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/world_writable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat.world_writable?" do
+ it_behaves_like :file_world_writable, :world_writable?, FileStat
+end
+
+describe "File::Stat#world_writable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/file/stat/writable_real_spec.rb b/spec/ruby/core/file/stat/writable_real_spec.rb
new file mode 100644
index 0000000000..4c9e78eb70
--- /dev/null
+++ b/spec/ruby/core/file/stat/writable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/writable_real'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#writable_real?" do
+ it_behaves_like :file_writable_real, :writable_real?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/writable_spec.rb b/spec/ruby/core/file/stat/writable_spec.rb
new file mode 100644
index 0000000000..551268751f
--- /dev/null
+++ b/spec/ruby/core/file/stat/writable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/writable'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#writable?" do
+ it_behaves_like :file_writable, :writable?, FileStat
+end
diff --git a/spec/ruby/core/file/stat/zero_spec.rb b/spec/ruby/core/file/stat/zero_spec.rb
new file mode 100644
index 0000000000..74facac66a
--- /dev/null
+++ b/spec/ruby/core/file/stat/zero_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+require_relative '../../../shared/file/zero'
+require_relative 'fixtures/classes'
+
+describe "File::Stat#zero?" do
+ it_behaves_like :file_zero, :zero?, FileStat
+end
diff --git a/spec/ruby/core/file/stat_spec.rb b/spec/ruby/core/file/stat_spec.rb
new file mode 100644
index 0000000000..d5238b6331
--- /dev/null
+++ b/spec/ruby/core/file/stat_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+require_relative 'shared/stat'
+
+describe "File.stat" do
+ it_behaves_like :file_stat, :stat
+end
+
+platform_is_not :windows do
+ describe "File.stat" do
+ before :each do
+ @file = tmp('i_exist')
+ @link = tmp('i_am_a_symlink')
+ touch(@file) { |f| f.write "rubinius" }
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ it "returns information for a file that has been deleted but is still open" do
+ File.open(@file) do |f|
+ rm_r @file
+
+ st = f.stat
+
+ st.should.file?
+ st.should_not.zero?
+ st.size.should == 8
+ st.size?.should == 8
+ st.blksize.should >= 0
+ st.atime.should.is_a?(Time)
+ st.ctime.should.is_a?(Time)
+ st.mtime.should.is_a?(Time)
+ end
+ end
+
+ it "returns a File::Stat object with file properties for a symlink" do
+ File.symlink(@file, @link)
+ st = File.stat(@link)
+
+ st.should.file?
+ st.should_not.symlink?
+ end
+
+ it "returns an error when given missing non-ASCII path" do
+ missing_path = "/missingfilepath\xE3E4".b
+ -> {
+ File.stat(missing_path)
+ }.should.raise(SystemCallError) { |e|
+ [Errno::ENOENT, Errno::EILSEQ].should.include?(e.class)
+ e.message.should.include?(missing_path)
+ }
+ end
+ end
+end
diff --git a/spec/ruby/core/file/sticky_spec.rb b/spec/ruby/core/file/sticky_spec.rb
new file mode 100644
index 0000000000..5f7b2d93eb
--- /dev/null
+++ b/spec/ruby/core/file/sticky_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/sticky'
+
+describe "File.sticky?" do
+ it_behaves_like :file_sticky, :sticky?, File
+ it_behaves_like :file_sticky_missing, :sticky?, File
+end
+
+describe "File.sticky?" do
+ platform_is_not :windows do
+ it "returns false if file does not exist" do
+ File.sticky?("I_am_a_bogus_file").should == false
+ end
+
+ it "returns false if the file has not sticky bit set" do
+ filename = tmp("i_exist")
+ touch(filename)
+
+ File.sticky?(filename).should == false
+
+ rm_r filename
+ end
+ end
+
+ platform_is :linux, :darwin do
+ it "returns true if the file has sticky bit set" do
+ filename = tmp("i_exist")
+ touch(filename)
+ system "chmod +t #{filename}"
+
+ File.sticky?(filename).should == true
+
+ rm_r filename
+ end
+ end
+
+ platform_is :bsd do
+ # FreeBSD and NetBSD can't set sticky bit to a normal file
+ it "cannot set sticky bit to a normal file" do
+ filename = tmp("i_exist")
+ touch(filename)
+ stat = File.stat(filename)
+ mode = stat.mode
+ raise_error(Errno::EFTYPE){File.chmod(mode|01000, filename)}
+ File.sticky?(filename).should == false
+
+ rm_r filename
+ end
+ end
+end
diff --git a/spec/ruby/core/file/symlink_spec.rb b/spec/ruby/core/file/symlink_spec.rb
new file mode 100644
index 0000000000..4ceeb28c84
--- /dev/null
+++ b/spec/ruby/core/file/symlink_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/symlink'
+
+describe "File.symlink" do
+ before :each do
+ @file = tmp("file_symlink.txt")
+ @link = tmp("file_symlink.lnk")
+
+ rm_r @link
+ touch @file
+ end
+
+ after :each do
+ rm_r @link, @file
+ end
+
+ platform_is_not :windows do
+ it "creates a symlink between a source and target file" do
+ File.symlink(@file, @link).should == 0
+ File.identical?(@file, @link).should == true
+ end
+
+ it "creates a symbolic link" do
+ File.symlink(@file, @link)
+ File.symlink?(@link).should == true
+ end
+
+ it "accepts args that have #to_path methods" do
+ File.symlink(mock_to_path(@file), mock_to_path(@link))
+ File.symlink?(@link).should == true
+ end
+
+ it "raises an Errno::EEXIST if the target already exists" do
+ File.symlink(@file, @link)
+ -> { File.symlink(@file, @link) }.should.raise(Errno::EEXIST)
+ end
+
+ it "raises an ArgumentError if not called with two arguments" do
+ -> { File.symlink }.should.raise(ArgumentError)
+ -> { File.symlink(@file) }.should.raise(ArgumentError)
+ end
+
+ it "raises a TypeError if not called with String types" do
+ -> { File.symlink(@file, nil) }.should.raise(TypeError)
+ -> { File.symlink(@file, 1) }.should.raise(TypeError)
+ -> { File.symlink(1, 1) }.should.raise(TypeError)
+ end
+ end
+end
+
+describe "File.symlink?" do
+ it_behaves_like :file_symlink, :symlink?, File
+end
+
+describe "File.symlink?" do
+ it_behaves_like :file_symlink_nonexistent, :symlink?, File
+end
diff --git a/spec/ruby/core/file/to_path_spec.rb b/spec/ruby/core/file/to_path_spec.rb
new file mode 100644
index 0000000000..6d168a065c
--- /dev/null
+++ b/spec/ruby/core/file/to_path_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/path'
+
+describe "File#to_path" do
+ it_behaves_like :file_path, :to_path
+end
diff --git a/spec/ruby/core/file/truncate_spec.rb b/spec/ruby/core/file/truncate_spec.rb
new file mode 100644
index 0000000000..5f37f34155
--- /dev/null
+++ b/spec/ruby/core/file/truncate_spec.rb
@@ -0,0 +1,177 @@
+require_relative '../../spec_helper'
+
+describe "File.truncate" do
+ before :each do
+ @name = tmp("test.txt")
+ touch(@name) { |f| f.write("1234567890") }
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "truncates a file" do
+ File.size(@name).should == 10
+
+ File.truncate(@name, 5)
+ File.size(@name).should == 5
+
+ File.open(@name, "r") do |f|
+ f.read(99).should == "12345"
+ f.should.eof?
+ end
+ end
+
+ it "truncate a file size to 0" do
+ File.truncate(@name, 0).should == 0
+ IO.read(@name).should == ""
+ end
+
+ it "truncate a file size to 5" do
+ File.size(@name).should == 10
+ File.truncate(@name, 5)
+ File.size(@name).should == 5
+ IO.read(@name).should == "12345"
+ end
+
+ it "truncates to a larger file size than the original file" do
+ File.truncate(@name, 12)
+ File.size(@name).should == 12
+ IO.read(@name).should == "1234567890\000\000"
+ end
+
+ it "truncates to the same size as the original file" do
+ File.truncate(@name, File.size(@name))
+ File.size(@name).should == 10
+ IO.read(@name).should == "1234567890"
+ end
+
+ it "raises an Errno::ENOENT if the file does not exist" do
+ # TODO: missing_file
+ not_existing_file = tmp("file-does-not-exist-for-sure.txt")
+
+ # make sure it doesn't exist for real
+ rm_r not_existing_file
+
+ begin
+ -> { File.truncate(not_existing_file, 5) }.should.raise(Errno::ENOENT)
+ ensure
+ rm_r not_existing_file
+ end
+ end
+
+ it "raises an ArgumentError if not passed two arguments" do
+ -> { File.truncate }.should.raise(ArgumentError)
+ -> { File.truncate(@name) }.should.raise(ArgumentError)
+ end
+
+ platform_is_not :netbsd, :openbsd do
+ it "raises an Errno::EINVAL if the length argument is not valid" do
+ -> { File.truncate(@name, -1) }.should.raise(Errno::EINVAL) # May fail
+ end
+ end
+
+ it "raises a TypeError if not passed a String type for the first argument" do
+ -> { File.truncate(1, 1) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if not passed an Integer type for the second argument" do
+ -> { File.truncate(@name, nil) }.should.raise(TypeError)
+ end
+
+ it "accepts an object that has a #to_path method" do
+ File.truncate(mock_to_path(@name), 0).should == 0
+ end
+end
+
+
+describe "File#truncate" do
+ before :each do
+ @name = tmp("test.txt")
+ @file = File.open @name, 'w'
+ @file.write "1234567890"
+ @file.flush
+ end
+
+ after :each do
+ @file.close unless @file.closed?
+ rm_r @name
+ end
+
+ it "does not move the file write pointer to the specified byte offset" do
+ @file.truncate(3)
+ @file.write "abc"
+ @file.close
+ File.read(@name).should == "123\x00\x00\x00\x00\x00\x00\x00abc"
+ end
+
+ it "does not move the file read pointer to the specified byte offset" do
+ File.open(@name, "r+") do |f|
+ f.read(1).should == "1"
+ f.truncate(0)
+ f.read(1).should == nil
+ end
+ end
+
+ it "truncates a file" do
+ File.size(@name).should == 10
+
+ @file.truncate(5)
+ File.size(@name).should == 5
+ File.open(@name, "r") do |f|
+ f.read(99).should == "12345"
+ f.should.eof?
+ end
+ end
+
+ it "truncates a file size to 0" do
+ @file.truncate(0).should == 0
+ IO.read(@name).should == ""
+ end
+
+ it "truncates a file size to 5" do
+ File.size(@name).should == 10
+ @file.truncate(5)
+ File.size(@name).should == 5
+ IO.read(@name).should == "12345"
+ end
+
+ it "truncates a file to a larger size than the original file" do
+ @file.truncate(12)
+ File.size(@name).should == 12
+ IO.read(@name).should == "1234567890\000\000"
+ end
+
+ it "truncates a file to the same size as the original file" do
+ @file.truncate(File.size(@name))
+ File.size(@name).should == 10
+ IO.read(@name).should == "1234567890"
+ end
+
+ it "raises an ArgumentError if not passed one argument" do
+ -> { @file.truncate }.should.raise(ArgumentError)
+ -> { @file.truncate(1) }.should_not.raise(ArgumentError)
+ end
+
+ platform_is_not :netbsd do
+ it "raises an Errno::EINVAL if the length argument is not valid" do
+ -> { @file.truncate(-1) }.should.raise(Errno::EINVAL) # May fail
+ end
+ end
+
+ it "raises an IOError if file is closed" do
+ @file.close
+ @file.should.closed?
+ -> { @file.truncate(42) }.should.raise(IOError)
+ end
+
+ it "raises an IOError if file is not opened for writing" do
+ File.open(@name, 'r') do |file|
+ -> { file.truncate(42) }.should.raise(IOError)
+ end
+ end
+
+ it "raises a TypeError if not passed an Integer type for the for the argument" do
+ -> { @file.truncate(nil) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/file/umask_spec.rb b/spec/ruby/core/file/umask_spec.rb
new file mode 100644
index 0000000000..fea8cf7633
--- /dev/null
+++ b/spec/ruby/core/file/umask_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+
+describe "File.umask" do
+ before :each do
+ @orig_umask = File.umask
+ @file = tmp('test.txt')
+ touch @file
+ end
+
+ after :each do
+ rm_r @file
+ File.umask(@orig_umask)
+ end
+
+ it "returns an Integer" do
+ File.umask.should.is_a?(Integer)
+ end
+
+ platform_is_not :windows do
+ it "returns the current umask value for the process" do
+ File.umask(022)
+ File.umask(006).should == 022
+ File.umask.should == 006
+ end
+
+ it "invokes to_int on non-integer argument" do
+ (obj = mock(022)).should_receive(:to_int).any_number_of_times.and_return(022)
+ File.umask(obj)
+ File.umask(obj).should == 022
+ end
+ end
+
+ platform_is :windows do
+ it "returns the current umask value for this process (basic)" do
+ File.umask.should == 0
+ File.umask(022).should == 0
+ File.umask(044).should == 0
+ end
+
+ # The value used here is the value of _S_IWRITE.
+ it "returns the current umask value for this process" do
+ File.umask(0000200)
+ File.umask.should == 0000200
+ File.umask(0006)
+ File.umask.should == 0
+ end
+ end
+
+ it "raises RangeError with too large values" do
+ -> { File.umask(2**64) }.should.raise(RangeError)
+ -> { File.umask(-2**63 - 1) }.should.raise(RangeError)
+ end
+
+ it "raises ArgumentError when more than one argument is provided" do
+ -> { File.umask(022, 022) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/file/unlink_spec.rb b/spec/ruby/core/file/unlink_spec.rb
new file mode 100644
index 0000000000..28872d55ed
--- /dev/null
+++ b/spec/ruby/core/file/unlink_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/unlink'
+
+describe "File.unlink" do
+ it_behaves_like :file_unlink, :unlink
+end
diff --git a/spec/ruby/core/file/utime_spec.rb b/spec/ruby/core/file/utime_spec.rb
new file mode 100644
index 0000000000..d87626be50
--- /dev/null
+++ b/spec/ruby/core/file/utime_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/update_time'
+
+describe "File.utime" do
+ it_behaves_like :update_time, :utime
+end
diff --git a/spec/ruby/core/file/world_readable_spec.rb b/spec/ruby/core/file/world_readable_spec.rb
new file mode 100644
index 0000000000..0f5e0b13d7
--- /dev/null
+++ b/spec/ruby/core/file/world_readable_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/world_readable'
+
+describe "File.world_readable?" do
+ it_behaves_like :file_world_readable, :world_readable?, File
+
+ it "returns nil if the file does not exist" do
+ file = rand.to_s + $$.to_s
+ File.should_not.exist?(file)
+ File.world_readable?(file).should == nil
+ end
+end
diff --git a/spec/ruby/core/file/world_writable_spec.rb b/spec/ruby/core/file/world_writable_spec.rb
new file mode 100644
index 0000000000..46ba6832f9
--- /dev/null
+++ b/spec/ruby/core/file/world_writable_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/world_writable'
+
+describe "File.world_writable?" do
+ it_behaves_like :file_world_writable, :world_writable?, File
+
+ it "returns nil if the file does not exist" do
+ file = rand.to_s + $$.to_s
+ File.should_not.exist?(file)
+ File.world_writable?(file).should == nil
+ end
+end
diff --git a/spec/ruby/core/file/writable_real_spec.rb b/spec/ruby/core/file/writable_real_spec.rb
new file mode 100644
index 0000000000..bea4c4c262
--- /dev/null
+++ b/spec/ruby/core/file/writable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/writable_real'
+
+describe "File.writable_real?" do
+ it_behaves_like :file_writable_real, :writable_real?, File
+ it_behaves_like :file_writable_real_missing, :writable_real?, File
+end
diff --git a/spec/ruby/core/file/writable_spec.rb b/spec/ruby/core/file/writable_spec.rb
new file mode 100644
index 0000000000..519837b0d1
--- /dev/null
+++ b/spec/ruby/core/file/writable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/writable'
+
+describe "File.writable?" do
+ it_behaves_like :file_writable, :writable?, File
+ it_behaves_like :file_writable_missing, :writable?, File
+end
diff --git a/spec/ruby/core/file/zero_spec.rb b/spec/ruby/core/file/zero_spec.rb
new file mode 100644
index 0000000000..01c7505ef2
--- /dev/null
+++ b/spec/ruby/core/file/zero_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/zero'
+
+describe "File.zero?" do
+ it_behaves_like :file_zero, :zero?, File
+ it_behaves_like :file_zero_missing, :zero?, File
+end
diff --git a/spec/ruby/core/filetest/blockdev_spec.rb b/spec/ruby/core/filetest/blockdev_spec.rb
new file mode 100644
index 0000000000..4f32991c4a
--- /dev/null
+++ b/spec/ruby/core/filetest/blockdev_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/blockdev'
+
+describe "FileTest.blockdev?" do
+ it_behaves_like :file_blockdev, :blockdev?, FileTest
+end
diff --git a/spec/ruby/core/filetest/chardev_spec.rb b/spec/ruby/core/filetest/chardev_spec.rb
new file mode 100644
index 0000000000..59c48bb2d5
--- /dev/null
+++ b/spec/ruby/core/filetest/chardev_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/chardev'
+
+describe "FileTest.chardev?" do
+ it_behaves_like :file_chardev, :chardev?, FileTest
+end
diff --git a/spec/ruby/core/filetest/directory_spec.rb b/spec/ruby/core/filetest/directory_spec.rb
new file mode 100644
index 0000000000..8f9d0e3901
--- /dev/null
+++ b/spec/ruby/core/filetest/directory_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/directory'
+
+describe "FileTest.directory?" do
+ it_behaves_like :file_directory, :directory?, FileTest
+end
+
+describe "FileTest.directory?" do
+ it_behaves_like :file_directory_io, :directory?, FileTest
+end
diff --git a/spec/ruby/core/filetest/executable_real_spec.rb b/spec/ruby/core/filetest/executable_real_spec.rb
new file mode 100644
index 0000000000..da65245785
--- /dev/null
+++ b/spec/ruby/core/filetest/executable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/executable_real'
+
+describe "FileTest.executable_real?" do
+ it_behaves_like :file_executable_real, :executable_real?, FileTest
+ it_behaves_like :file_executable_real_missing, :executable_real?, FileTest
+end
diff --git a/spec/ruby/core/filetest/executable_spec.rb b/spec/ruby/core/filetest/executable_spec.rb
new file mode 100644
index 0000000000..03056669f6
--- /dev/null
+++ b/spec/ruby/core/filetest/executable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/executable'
+
+describe "FileTest.executable?" do
+ it_behaves_like :file_executable, :executable?, FileTest
+ it_behaves_like :file_executable_missing, :executable?, FileTest
+end
diff --git a/spec/ruby/core/filetest/exist_spec.rb b/spec/ruby/core/filetest/exist_spec.rb
new file mode 100644
index 0000000000..612ffa9fcb
--- /dev/null
+++ b/spec/ruby/core/filetest/exist_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/exist'
+
+describe "FileTest.exist?" do
+ it_behaves_like :file_exist, :exist?, FileTest
+end
+
+describe "FileTest.exists?" do
+ it "has been removed" do
+ FileTest.should_not.respond_to?(:exists?)
+ end
+end
diff --git a/spec/ruby/core/filetest/file_spec.rb b/spec/ruby/core/filetest/file_spec.rb
new file mode 100644
index 0000000000..0c0cb82f96
--- /dev/null
+++ b/spec/ruby/core/filetest/file_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/file'
+
+describe "File.file?" do
+ it_behaves_like :file_file, :file?, File
+end
+
+describe "FileTest.file?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/filetest/grpowned_spec.rb b/spec/ruby/core/filetest/grpowned_spec.rb
new file mode 100644
index 0000000000..d6bd6abd71
--- /dev/null
+++ b/spec/ruby/core/filetest/grpowned_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/grpowned'
+
+describe "FileTest.grpowned?" do
+ it_behaves_like :file_grpowned, :grpowned?, FileTest
+
+ it "returns false if the file doesn't exist" do
+ FileTest.grpowned?("xxx-tmp-doesnt_exist-blah").should == false
+ end
+end
diff --git a/spec/ruby/core/filetest/identical_spec.rb b/spec/ruby/core/filetest/identical_spec.rb
new file mode 100644
index 0000000000..b00c5b75e8
--- /dev/null
+++ b/spec/ruby/core/filetest/identical_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/identical'
+
+describe "FileTest.identical?" do
+ it_behaves_like :file_identical, :identical?, FileTest
+end
diff --git a/spec/ruby/core/filetest/owned_spec.rb b/spec/ruby/core/filetest/owned_spec.rb
new file mode 100644
index 0000000000..b26165f98d
--- /dev/null
+++ b/spec/ruby/core/filetest/owned_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/owned'
+
+describe "FileTest.owned?" do
+ it_behaves_like :file_owned, :owned?, FileTest
+end
diff --git a/spec/ruby/core/filetest/pipe_spec.rb b/spec/ruby/core/filetest/pipe_spec.rb
new file mode 100644
index 0000000000..8ce67568fb
--- /dev/null
+++ b/spec/ruby/core/filetest/pipe_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/pipe'
+
+describe "FileTest.pipe?" do
+ it_behaves_like :file_pipe, :pipe?, FileTest
+end
diff --git a/spec/ruby/core/filetest/readable_real_spec.rb b/spec/ruby/core/filetest/readable_real_spec.rb
new file mode 100644
index 0000000000..82c62fe8f0
--- /dev/null
+++ b/spec/ruby/core/filetest/readable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/readable_real'
+
+describe "FileTest.readable_real?" do
+ it_behaves_like :file_readable_real, :readable_real?, FileTest
+ it_behaves_like :file_readable_real_missing, :readable_real?, FileTest
+end
diff --git a/spec/ruby/core/filetest/readable_spec.rb b/spec/ruby/core/filetest/readable_spec.rb
new file mode 100644
index 0000000000..039ca56ca3
--- /dev/null
+++ b/spec/ruby/core/filetest/readable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/readable'
+
+describe "FileTest.readable?" do
+ it_behaves_like :file_readable, :readable?, FileTest
+ it_behaves_like :file_readable_missing, :readable?, FileTest
+end
diff --git a/spec/ruby/core/filetest/setgid_spec.rb b/spec/ruby/core/filetest/setgid_spec.rb
new file mode 100644
index 0000000000..c83ffccb2a
--- /dev/null
+++ b/spec/ruby/core/filetest/setgid_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/setgid'
+
+describe "FileTest.setgid?" do
+ it_behaves_like :file_setgid, :setgid?, FileTest
+end
diff --git a/spec/ruby/core/filetest/setuid_spec.rb b/spec/ruby/core/filetest/setuid_spec.rb
new file mode 100644
index 0000000000..1c1fd2efe4
--- /dev/null
+++ b/spec/ruby/core/filetest/setuid_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/setuid'
+
+describe "FileTest.setuid?" do
+ it_behaves_like :file_setuid, :setuid?, FileTest
+end
diff --git a/spec/ruby/core/filetest/size_spec.rb b/spec/ruby/core/filetest/size_spec.rb
new file mode 100644
index 0000000000..dc3ddb127f
--- /dev/null
+++ b/spec/ruby/core/filetest/size_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/size'
+
+describe "FileTest.size?" do
+ it_behaves_like :file_size, :size?, FileTest
+end
+
+describe "FileTest.size?" do
+ it_behaves_like :file_size_nil_when_missing, :size?, FileTest
+end
+
+describe "FileTest.size?" do
+ it_behaves_like :file_size_nil_when_empty, :size?, FileTest
+end
+
+describe "FileTest.size?" do
+ it_behaves_like :file_size_with_file_argument, :size?, FileTest
+end
+
+describe "FileTest.size" do
+ it_behaves_like :file_size, :size, FileTest
+end
+
+describe "FileTest.size" do
+ it_behaves_like :file_size_raise_when_missing, :size, FileTest
+end
+
+describe "FileTest.size" do
+ it_behaves_like :file_size_0_when_empty, :size, FileTest
+end
+
+describe "FileTest.size" do
+ it_behaves_like :file_size_with_file_argument, :size, FileTest
+end
diff --git a/spec/ruby/core/filetest/socket_spec.rb b/spec/ruby/core/filetest/socket_spec.rb
new file mode 100644
index 0000000000..f274be6318
--- /dev/null
+++ b/spec/ruby/core/filetest/socket_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+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/sticky_spec.rb b/spec/ruby/core/filetest/sticky_spec.rb
new file mode 100644
index 0000000000..8b776b6672
--- /dev/null
+++ b/spec/ruby/core/filetest/sticky_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/sticky'
+
+describe "FileTest.sticky?" do
+ it_behaves_like :file_sticky, :sticky?, FileTest
+ it_behaves_like :file_sticky_missing, :sticky?, FileTest
+end
diff --git a/spec/ruby/core/filetest/symlink_spec.rb b/spec/ruby/core/filetest/symlink_spec.rb
new file mode 100644
index 0000000000..41c924dc1a
--- /dev/null
+++ b/spec/ruby/core/filetest/symlink_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/symlink'
+
+describe "FileTest.symlink?" do
+ it_behaves_like :file_symlink, :symlink?, FileTest
+end
+
+describe "FileTest.symlink?" do
+ it_behaves_like :file_symlink_nonexistent, :symlink?, File
+end
diff --git a/spec/ruby/core/filetest/world_readable_spec.rb b/spec/ruby/core/filetest/world_readable_spec.rb
new file mode 100644
index 0000000000..72abdd9e03
--- /dev/null
+++ b/spec/ruby/core/filetest/world_readable_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "FileTest.world_readable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/filetest/world_writable_spec.rb b/spec/ruby/core/filetest/world_writable_spec.rb
new file mode 100644
index 0000000000..533f698fd3
--- /dev/null
+++ b/spec/ruby/core/filetest/world_writable_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "FileTest.world_writable?" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/filetest/writable_real_spec.rb b/spec/ruby/core/filetest/writable_real_spec.rb
new file mode 100644
index 0000000000..64abe4cd3f
--- /dev/null
+++ b/spec/ruby/core/filetest/writable_real_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/writable_real'
+
+describe "FileTest.writable_real?" do
+ it_behaves_like :file_writable_real, :writable_real?, FileTest
+ it_behaves_like :file_writable_real_missing, :writable_real?, FileTest
+end
diff --git a/spec/ruby/core/filetest/writable_spec.rb b/spec/ruby/core/filetest/writable_spec.rb
new file mode 100644
index 0000000000..e921a5887b
--- /dev/null
+++ b/spec/ruby/core/filetest/writable_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/writable'
+
+describe "FileTest.writable?" do
+ it_behaves_like :file_writable, :writable?, FileTest
+ it_behaves_like :file_writable_missing, :writable?, FileTest
+end
diff --git a/spec/ruby/core/filetest/zero_spec.rb b/spec/ruby/core/filetest/zero_spec.rb
new file mode 100644
index 0000000000..92cab67f1b
--- /dev/null
+++ b/spec/ruby/core/filetest/zero_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/file/zero'
+
+describe "FileTest.zero?" do
+ it_behaves_like :file_zero, :zero?, FileTest
+ it_behaves_like :file_zero_missing, :zero?, FileTest
+end
diff --git a/spec/ruby/core/float/abs_spec.rb b/spec/ruby/core/float/abs_spec.rb
new file mode 100644
index 0000000000..a08601926d
--- /dev/null
+++ b/spec/ruby/core/float/abs_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Float#abs" do
+ it_behaves_like :float_abs, :abs
+end
diff --git a/spec/ruby/core/float/angle_spec.rb b/spec/ruby/core/float/angle_spec.rb
new file mode 100644
index 0000000000..c07249aa97
--- /dev/null
+++ b/spec/ruby/core/float/angle_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Float#angle" do
+ it_behaves_like :float_arg, :angle
+end
diff --git a/spec/ruby/core/float/arg_spec.rb b/spec/ruby/core/float/arg_spec.rb
new file mode 100644
index 0000000000..d3a50668f8
--- /dev/null
+++ b/spec/ruby/core/float/arg_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Float#arg" do
+ it_behaves_like :float_arg, :arg
+end
diff --git a/spec/ruby/core/float/case_compare_spec.rb b/spec/ruby/core/float/case_compare_spec.rb
new file mode 100644
index 0000000000..b902fbea18
--- /dev/null
+++ b/spec/ruby/core/float/case_compare_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Float#===" do
+ it_behaves_like :float_equal, :===
+end
diff --git a/spec/ruby/core/float/ceil_spec.rb b/spec/ruby/core/float/ceil_spec.rb
new file mode 100644
index 0000000000..efd1e6feb2
--- /dev/null
+++ b/spec/ruby/core/float/ceil_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative '../integer/shared/integer_ceil_precision'
+
+describe "Float#ceil" do
+ context "with values equal to integers" 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)
+ 0.0.ceil.should.eql?( 0 )
+ 1.3.ceil.should.eql?( 2 )
+ 3.0.ceil.should.eql?( 3 )
+ -9223372036854775808.1.ceil.should.eql?(-9223372036854775808)
+ +9223372036854775808.1.ceil.should.eql?(+9223372036854775808)
+ end
+
+ it "returns the smallest number greater than or equal to self with an optionally given precision" do
+ 2.1679.ceil(0).should.eql?(3)
+ 214.94.ceil(-1).should.eql?(220)
+ 7.0.ceil(1).should.eql?(7.0)
+ 200.0.ceil(-2).should.eql?(200)
+ -1.234.ceil(2).should.eql?(-1.23)
+ 5.123812.ceil(4).should.eql?(5.1239)
+ 10.00001.ceil(5).should.eql?(10.00001)
+ end
+end
diff --git a/spec/ruby/core/float/coerce_spec.rb b/spec/ruby/core/float/coerce_spec.rb
new file mode 100644
index 0000000000..baa831dcf6
--- /dev/null
+++ b/spec/ruby/core/float/coerce_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "Float#coerce" do
+ it "returns [other, self] both as Floats" do
+ 1.2.coerce(1).should == [1.0, 1.2]
+ 5.28.coerce(1.0).should == [1.0, 5.28]
+ 1.0.coerce(1).should == [1.0, 1.0]
+ 1.0.coerce("2.5").should == [2.5, 1.0]
+ 1.0.coerce(3.14).should == [3.14, 1.0]
+
+ a, b = -0.0.coerce(bignum_value)
+ a.should be_close(18446744073709551616.0, TOLERANCE)
+ b.should be_close(-0.0, TOLERANCE)
+ a, b = 1.0.coerce(bignum_value)
+ a.should be_close(18446744073709551616.0, TOLERANCE)
+ b.should be_close(1.0, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/float/comparison_spec.rb b/spec/ruby/core/float/comparison_spec.rb
new file mode 100644
index 0000000000..e9adf2fd6a
--- /dev/null
+++ b/spec/ruby/core/float/comparison_spec.rb
@@ -0,0 +1,113 @@
+require_relative '../../spec_helper'
+
+describe "Float#<=>" do
+ it "returns -1, 0, 1 when self is less than, equal, or greater than other" do
+ (1.5 <=> 5).should == -1
+ (2.45 <=> 2.45).should == 0
+ ((bignum_value*1.1) <=> bignum_value).should == 1
+ end
+
+ it "returns nil if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value <=> n).should == nil
+ (n <=> nan_value).should == nil
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value <=> n).should == 1
+ (n <=> infinity_value).should == -1
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value <=> n).should == -1
+ (n <=> -infinity_value).should == 1
+ }
+ end
+
+ it "returns nil when the given argument is not a Float" do
+ (1.0 <=> "1").should == nil
+ (1.0 <=> "1".freeze).should == nil
+ (1.0 <=> :one).should == nil
+ (1.0 <=> true).should == nil
+ end
+
+ it "compares using #coerce when argument is not a Float" do
+ klass = Class.new do
+ attr_reader :call_count
+ def coerce(other)
+ @call_count ||= 0
+ @call_count += 1
+ [other, 42.0]
+ end
+ end
+
+ coercible = klass.new
+ (2.33 <=> coercible).should == -1
+ (42.0 <=> coercible).should == 0
+ (43.0 <=> coercible).should == 1
+ coercible.call_count.should == 3
+ end
+
+ it "raises TypeError when #coerce misbehaves" do
+ klass = Class.new do
+ def coerce(other)
+ :incorrect
+ end
+ end
+
+ bad_coercible = klass.new
+ -> {
+ 4.2 <=> bad_coercible
+ }.should.raise(TypeError, "coerce must return [x, y]")
+ end
+
+ it "returns the correct result when one side is infinite" do
+ (infinity_value <=> Float::MAX.to_i*2).should == 1
+ (-Float::MAX.to_i*2 <=> infinity_value).should == -1
+ (-infinity_value <=> -Float::MAX.to_i*2).should == -1
+ (-Float::MAX.to_i*2 <=> -infinity_value).should == 1
+ end
+
+ it "returns 0 when self is Infinity and other is infinite?=1" do
+ obj = Object.new
+ def obj.infinite?
+ 1
+ end
+ (infinity_value <=> obj).should == 0
+ end
+
+ it "returns 1 when self is Infinity and other is infinite?=-1" do
+ obj = Object.new
+ def obj.infinite?
+ -1
+ end
+ (infinity_value <=> obj).should == 1
+ end
+
+ it "returns 1 when self is Infinity and other is infinite?=nil (which means finite)" do
+ obj = Object.new
+ def obj.infinite?
+ nil
+ end
+ (infinity_value <=> obj).should == 1
+ end
+
+ it "returns 0 for -0.0 and 0.0" do
+ (-0.0 <=> 0.0).should == 0
+ (0.0 <=> -0.0).should == 0
+ end
+
+ it "returns 0 for -0.0 and 0" do
+ (-0.0 <=> 0).should == 0
+ (0 <=> -0.0).should == 0
+ end
+
+ it "returns 0 for 0.0 and 0" do
+ (0.0 <=> 0).should == 0
+ (0 <=> 0.0).should == 0
+ end
+end
diff --git a/spec/ruby/core/float/constants_spec.rb b/spec/ruby/core/float/constants_spec.rb
new file mode 100644
index 0000000000..1b71ee8adf
--- /dev/null
+++ b/spec/ruby/core/float/constants_spec.rb
@@ -0,0 +1,55 @@
+require_relative '../../spec_helper'
+
+describe "Float constant" do
+ it "DIG is 15" do
+ Float::DIG.should == 15
+ end
+
+ it "EPSILON is 2.220446049250313e-16" do
+ Float::EPSILON.should == 2.0 ** -52
+ Float::EPSILON.should == 2.220446049250313e-16
+ end
+
+ it "MANT_DIG is 53" do
+ Float::MANT_DIG.should == 53
+ end
+
+ it "MAX_10_EXP is 308" do
+ Float::MAX_10_EXP.should == 308
+ end
+
+ it "MIN_10_EXP is -308" do
+ Float::MIN_10_EXP.should == -307
+ end
+
+ it "MAX_EXP is 1024" do
+ Float::MAX_EXP.should == 1024
+ end
+
+ it "MIN_EXP is -1021" do
+ Float::MIN_EXP.should == -1021
+ end
+
+ it "MAX is 1.7976931348623157e+308" do
+ # See https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Double-precision_examples
+ Float::MAX.should == (1 + (1 - (2 ** -52))) * (2.0 ** 1023)
+ Float::MAX.should == 1.7976931348623157e+308
+ end
+
+ it "MIN is 2.2250738585072014e-308" do
+ Float::MIN.should == (2.0 ** -1022)
+ Float::MIN.should == 2.2250738585072014e-308
+ end
+
+ it "RADIX is 2" do
+ Float::RADIX.should == 2
+ end
+
+ it "INFINITY is the positive infinity" do
+ Float::INFINITY.infinite?.should == 1
+ end
+
+ it "NAN is 'not a number'" do
+ Float::NAN.nan?.should == true
+ end
+end
diff --git a/spec/ruby/core/float/denominator_spec.rb b/spec/ruby/core/float/denominator_spec.rb
new file mode 100644
index 0000000000..85beaa98cd
--- /dev/null
+++ b/spec/ruby/core/float/denominator_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+
+describe "Float#denominator" do
+ before :each do
+ @numbers = [
+ 0.0,
+ 29871.22736282,
+ 7772222663.0,
+ 1.4592,
+ ].map {|n| [0-n, n]}.flatten
+ end
+
+ it "returns an Integer" do
+ @numbers.each do |number|
+ number.denominator.should.is_a?(Integer)
+ end
+ end
+
+ it "converts self to a Rational and returns the denominator" do
+ @numbers.each do |number|
+ number.denominator.should == Rational(number).denominator
+ end
+ end
+
+ it "returns 1 for NaN and Infinity" do
+ nan_value.denominator.should == 1
+ infinity_value.denominator.should == 1
+ end
+end
diff --git a/spec/ruby/core/float/divide_spec.rb b/spec/ruby/core/float/divide_spec.rb
new file mode 100644
index 0000000000..68a2c550a7
--- /dev/null
+++ b/spec/ruby/core/float/divide_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/coerce'
+require_relative 'shared/arithmetic_exception_in_coerce'
+
+describe "Float#/" do
+ it_behaves_like :float_arithmetic_exception_in_coerce, :/
+
+ it "returns self divided by other" do
+ (5.75 / -2).should be_close(-2.875,TOLERANCE)
+ (451.0 / 9.3).should be_close(48.494623655914,TOLERANCE)
+ (91.1 / -0xffffffff).should be_close(-2.12108716418061e-08, TOLERANCE)
+ end
+
+ it "properly coerces objects" do
+ (5.0 / FloatSpecs::CanCoerce.new(5)).should be_close(0, TOLERANCE)
+ end
+
+ it "returns +Infinity when dividing non-zero by zero of the same sign" do
+ (1.0 / 0.0).should.infinite? == 1
+ (-1.0 / -0.0).should.infinite? == 1
+ end
+
+ it "returns -Infinity when dividing non-zero by zero of opposite sign" do
+ (-1.0 / 0.0).should.infinite? == -1
+ (1.0 / -0.0).should.infinite? == -1
+ end
+
+ it "returns NaN when dividing zero by zero" do
+ (0.0 / 0.0).should.nan?
+ (-0.0 / 0.0).should.nan?
+ (0.0 / -0.0).should.nan?
+ (-0.0 / -0.0).should.nan?
+ end
+
+ it "raises a TypeError when given a non-Numeric" do
+ -> { 13.0 / "10" }.should.raise(TypeError)
+ -> { 13.0 / :symbol }.should.raise(TypeError)
+ end
+
+ it "divides correctly by Rational numbers" do
+ (1.2345678901234567 / Rational(1, 10000000000000000000)).should == 1.2345678901234567e+19
+ end
+end
diff --git a/spec/ruby/core/float/divmod_spec.rb b/spec/ruby/core/float/divmod_spec.rb
new file mode 100644
index 0000000000..7ed6cd3487
--- /dev/null
+++ b/spec/ruby/core/float/divmod_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "Float#divmod" do
+ it "returns an [quotient, modulus] from dividing self by other" do
+ values = 3.14.divmod(2)
+ values[0].should.eql?(1)
+ values[1].should be_close(1.14, TOLERANCE)
+ values = 2.8284.divmod(3.1415)
+ values[0].should.eql?(0)
+ values[1].should be_close(2.8284, TOLERANCE)
+ values = -1.0.divmod(bignum_value)
+ values[0].should.eql?(-1)
+ values[1].should be_close(18446744073709551616.0, TOLERANCE)
+ values = -1.0.divmod(1)
+ values[0].should.eql?(-1)
+ values[1].should.eql?(0.0)
+ end
+
+ # Behaviour established as correct in r23953
+ it "raises a FloatDomainError if self is NaN" do
+ -> { nan_value.divmod(1) }.should.raise(FloatDomainError)
+ end
+
+ # Behaviour established as correct in r23953
+ it "raises a FloatDomainError if other is NaN" do
+ -> { 1.0.divmod(nan_value) }.should.raise(FloatDomainError)
+ end
+
+ # Behaviour established as correct in r23953
+ it "raises a FloatDomainError if self is Infinity" do
+ -> { infinity_value.divmod(1) }.should.raise(FloatDomainError)
+ end
+
+ it "raises a ZeroDivisionError if other is zero" do
+ -> { 1.0.divmod(0) }.should.raise(ZeroDivisionError)
+ -> { 1.0.divmod(0.0) }.should.raise(ZeroDivisionError)
+ end
+
+ # redmine #5276"
+ it "returns the correct [quotient, modulus] even for large quotient" do
+ 0.59.divmod(7.761021455128987e-11).first.should.eql?(7602092113)
+ end
+end
diff --git a/spec/ruby/core/float/dup_spec.rb b/spec/ruby/core/float/dup_spec.rb
new file mode 100644
index 0000000000..b474e21325
--- /dev/null
+++ b/spec/ruby/core/float/dup_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+
+describe "Float#dup" do
+ it "returns self" do
+ float = 2.4
+ float.dup.should.equal?(float)
+ end
+end
diff --git a/spec/ruby/core/float/eql_spec.rb b/spec/ruby/core/float/eql_spec.rb
new file mode 100644
index 0000000000..cf1ad8416f
--- /dev/null
+++ b/spec/ruby/core/float/eql_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Float#eql?" do
+ it "returns true if other is a Float equal to self" do
+ 0.0.eql?(0.0).should == true
+ end
+
+ it "returns false if other is a Float not equal to self" do
+ 1.0.eql?(1.1).should == false
+ end
+
+ it "returns false if other is not a Float" do
+ 1.0.eql?(1).should == false
+ 1.0.eql?(:one).should == false
+ end
+end
diff --git a/spec/ruby/core/float/equal_value_spec.rb b/spec/ruby/core/float/equal_value_spec.rb
new file mode 100644
index 0000000000..03eea5108e
--- /dev/null
+++ b/spec/ruby/core/float/equal_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Float#==" do
+ it_behaves_like :float_equal, :==
+end
diff --git a/spec/ruby/core/float/exponent_spec.rb b/spec/ruby/core/float/exponent_spec.rb
new file mode 100644
index 0000000000..a4c03469a7
--- /dev/null
+++ b/spec/ruby/core/float/exponent_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "Float#**" do
+ it "returns self raise to the other power" do
+ (2.3 ** 3).should be_close(12.167,TOLERANCE)
+ (5.2 ** -1).should be_close(0.192307692307692,TOLERANCE)
+ (9.5 ** 0.5).should be_close(3.08220700148449, TOLERANCE)
+ (9.5 ** 0xffffffff).to_s.should == 'Infinity'
+ end
+
+ it "returns a complex number when negative and raised to a fractional power" do
+ ((-8.0) ** (1.0/3)) .should be_close(Complex(1, 1.73205), TOLERANCE)
+ ((-8.0) ** Rational(1,3)).should be_close(Complex(1, 1.73205), TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/float/fdiv_spec.rb b/spec/ruby/core/float/fdiv_spec.rb
new file mode 100644
index 0000000000..be25ee283b
--- /dev/null
+++ b/spec/ruby/core/float/fdiv_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quo'
+
+describe "Float#fdiv" do
+ it_behaves_like :float_quo, :fdiv
+end
diff --git a/spec/ruby/core/float/finite_spec.rb b/spec/ruby/core/float/finite_spec.rb
new file mode 100644
index 0000000000..d839b30e32
--- /dev/null
+++ b/spec/ruby/core/float/finite_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Float#finite?" do
+ it "returns true for finite values" do
+ 3.14159.should.finite?
+ end
+
+ it "returns false for positive infinity" do
+ infinity_value.should_not.finite?
+ end
+
+ it "returns false for negative infinity" do
+ (-infinity_value).should_not.finite?
+ end
+
+ it "returns false for NaN" do
+ nan_value.should_not.finite?
+ end
+end
diff --git a/spec/ruby/core/float/fixtures/classes.rb b/spec/ruby/core/float/fixtures/classes.rb
new file mode 100644
index 0000000000..2d80184e7d
--- /dev/null
+++ b/spec/ruby/core/float/fixtures/classes.rb
@@ -0,0 +1,4 @@
+module FloatSpecs
+ class CoerceError < StandardError
+ end
+end
diff --git a/spec/ruby/core/float/fixtures/coerce.rb b/spec/ruby/core/float/fixtures/coerce.rb
new file mode 100644
index 0000000000..2cf155be95
--- /dev/null
+++ b/spec/ruby/core/float/fixtures/coerce.rb
@@ -0,0 +1,15 @@
+module FloatSpecs
+ class CanCoerce
+ def initialize(a)
+ @a = a
+ end
+
+ def coerce(b)
+ [self.class.new(b), @a]
+ end
+
+ def /(b)
+ @a.to_i % b.to_i
+ end
+ end
+end
diff --git a/spec/ruby/core/float/float_spec.rb b/spec/ruby/core/float/float_spec.rb
new file mode 100644
index 0000000000..46b2eff372
--- /dev/null
+++ b/spec/ruby/core/float/float_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Float" do
+ it "includes Comparable" do
+ Float.include?(Comparable).should == true
+ end
+
+ it ".allocate raises a TypeError" do
+ -> do
+ Float.allocate
+ end.should.raise(TypeError)
+ end
+
+ it ".new is undefined" do
+ -> do
+ Float.new
+ end.should.raise(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/float/floor_spec.rb b/spec/ruby/core/float/floor_spec.rb
new file mode 100644
index 0000000000..f77300eb04
--- /dev/null
+++ b/spec/ruby/core/float/floor_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+require_relative '../integer/shared/integer_floor_precision'
+
+describe "Float#floor" do
+ context "with values equal to integers" 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)
+ 0.0.floor.should.eql?( 0 )
+ 1.0.floor.should.eql?( 1 )
+ 5.9.floor.should.eql?( 5 )
+ -9223372036854775808.1.floor.should.eql?(-9223372036854775808)
+ +9223372036854775808.1.floor.should.eql?(+9223372036854775808)
+ end
+
+ it "returns the largest number less than or equal to self with an optionally given precision" do
+ 2.1679.floor(0).should.eql?(2)
+ 214.94.floor(-1).should.eql?(210)
+ 7.0.floor(1).should.eql?(7.0)
+ 200.0.floor(-2).should.eql?(200)
+ -1.234.floor(2).should.eql?(-1.24)
+ 5.123812.floor(4).should.eql?(5.1238)
+ 10.00001.floor(5).should.eql?(10.00001)
+ end
+end
diff --git a/spec/ruby/core/float/gt_spec.rb b/spec/ruby/core/float/gt_spec.rb
new file mode 100644
index 0000000000..5194796b46
--- /dev/null
+++ b/spec/ruby/core/float/gt_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_exception_in_coerce'
+
+describe "Float#>" do
+ it_behaves_like :float_comparison_exception_in_coerce, :>
+
+ it "returns true if self is greater than other" do
+ (1.5 > 1).should == true
+ (2.5 > 3).should == false
+ (45.91 > bignum_value).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Numeric" do
+ -> { 5.0 > "4" }.should.raise(ArgumentError)
+ -> { 5.0 > mock('x') }.should.raise(ArgumentError)
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value > n).should == false
+ (n > nan_value).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value > n).should == true
+ (n > infinity_value).should == false
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value > n).should == false
+ (n > -infinity_value).should == true
+ }
+ end
+end
diff --git a/spec/ruby/core/float/gte_spec.rb b/spec/ruby/core/float/gte_spec.rb
new file mode 100644
index 0000000000..4a62725d53
--- /dev/null
+++ b/spec/ruby/core/float/gte_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_exception_in_coerce'
+
+describe "Float#>=" do
+ it_behaves_like :float_comparison_exception_in_coerce, :>=
+
+ it "returns true if self is greater than or equal to other" do
+ (5.2 >= 5.2).should == true
+ (9.71 >= 1).should == true
+ (5.55382 >= 0xfabdafbafcab).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Numeric" do
+ -> { 5.0 >= "4" }.should.raise(ArgumentError)
+ -> { 5.0 >= mock('x') }.should.raise(ArgumentError)
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value >= n).should == false
+ (n >= nan_value).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value >= n).should == true
+ (n >= infinity_value).should == false
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value >= n).should == false
+ (n >= -infinity_value).should == true
+ }
+ end
+end
diff --git a/spec/ruby/core/float/hash_spec.rb b/spec/ruby/core/float/hash_spec.rb
new file mode 100644
index 0000000000..5f77e3b4a1
--- /dev/null
+++ b/spec/ruby/core/float/hash_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Float#hash" do
+ it "is provided" do
+ 0.0.respond_to?(:hash).should == true
+ end
+
+ it "is stable" do
+ 1.0.hash.should == 1.0.hash
+ end
+end
diff --git a/spec/ruby/core/float/infinite_spec.rb b/spec/ruby/core/float/infinite_spec.rb
new file mode 100644
index 0000000000..901c2738aa
--- /dev/null
+++ b/spec/ruby/core/float/infinite_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Float#infinite?" do
+ it "returns nil for finite values" do
+ 1.0.infinite?.should == nil
+ end
+
+ it "returns 1 for positive infinity" do
+ infinity_value.infinite?.should == 1
+ end
+
+ it "returns -1 for negative infinity" do
+ (-infinity_value).infinite?.should == -1
+ end
+
+ it "returns nil for NaN" do
+ nan_value.infinite?.should == nil
+ end
+end
diff --git a/spec/ruby/core/float/inspect_spec.rb b/spec/ruby/core/float/inspect_spec.rb
new file mode 100644
index 0000000000..4be1927d84
--- /dev/null
+++ b/spec/ruby/core/float/inspect_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Float#inspect" do
+ it_behaves_like :float_to_s, :inspect
+end
diff --git a/spec/ruby/core/float/lt_spec.rb b/spec/ruby/core/float/lt_spec.rb
new file mode 100644
index 0000000000..0f0e1752bd
--- /dev/null
+++ b/spec/ruby/core/float/lt_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_exception_in_coerce'
+
+describe "Float#<" do
+ it_behaves_like :float_comparison_exception_in_coerce, :<
+
+ it "returns true if self is less than other" do
+ (71.3 < 91.8).should == true
+ (192.6 < -500).should == false
+ (-0.12 < 0x4fffffff).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Numeric" do
+ -> { 5.0 < "4" }.should.raise(ArgumentError)
+ -> { 5.0 < mock('x') }.should.raise(ArgumentError)
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value < n).should == false
+ (n < nan_value).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value < n).should == false
+ (n < infinity_value).should == true
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value < n).should == true
+ (n < -infinity_value).should == false
+ }
+ end
+end
diff --git a/spec/ruby/core/float/lte_spec.rb b/spec/ruby/core/float/lte_spec.rb
new file mode 100644
index 0000000000..afb64ade09
--- /dev/null
+++ b/spec/ruby/core/float/lte_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_exception_in_coerce'
+
+describe "Float#<=" do
+ it_behaves_like :float_comparison_exception_in_coerce, :>=
+
+ it "returns true if self is less than or equal to other" do
+ (2.0 <= 3.14159).should == true
+ (-2.7183 <= -24).should == false
+ (0.0 <= 0.0).should == true
+ (9_235.9 <= bignum_value).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Numeric" do
+ -> { 5.0 <= "4" }.should.raise(ArgumentError)
+ -> { 5.0 <= mock('x') }.should.raise(ArgumentError)
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value <= n).should == false
+ (n <= nan_value).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value <= n).should == false
+ (n <= infinity_value).should == true
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (-infinity_value <= n).should == true
+ (n <= -infinity_value).should == false
+ }
+ end
+end
diff --git a/spec/ruby/core/float/magnitude_spec.rb b/spec/ruby/core/float/magnitude_spec.rb
new file mode 100644
index 0000000000..7cdd8ef28a
--- /dev/null
+++ b/spec/ruby/core/float/magnitude_spec.rb
@@ -0,0 +1,6 @@
+require_relative "../../spec_helper"
+require_relative 'shared/abs'
+
+describe "Float#magnitude" do
+ it_behaves_like :float_abs, :magnitude
+end
diff --git a/spec/ruby/core/float/minus_spec.rb b/spec/ruby/core/float/minus_spec.rb
new file mode 100644
index 0000000000..a4281a397b
--- /dev/null
+++ b/spec/ruby/core/float/minus_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_exception_in_coerce'
+
+describe "Float#-" do
+ it_behaves_like :float_arithmetic_exception_in_coerce, :-
+
+ it "returns self minus other" do
+ (9_237_212.5280 - 5_280).should be_close(9231932.528, TOLERANCE)
+ (2_560_496.1691 - bignum_value).should be_close(-18446744073706991616.0, TOLERANCE)
+ (5.5 - 5.5).should be_close(0.0,TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/float/modulo_spec.rb b/spec/ruby/core/float/modulo_spec.rb
new file mode 100644
index 0000000000..8ae80a0b05
--- /dev/null
+++ b/spec/ruby/core/float/modulo_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/modulo'
+
+describe "Float#%" do
+ it_behaves_like :float_modulo, :%
+end
+
+describe "Float#modulo" do
+ it_behaves_like :float_modulo, :modulo
+end
diff --git a/spec/ruby/core/float/multiply_spec.rb b/spec/ruby/core/float/multiply_spec.rb
new file mode 100644
index 0000000000..edaaba7e61
--- /dev/null
+++ b/spec/ruby/core/float/multiply_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_exception_in_coerce'
+
+describe "Float#*" do
+ it_behaves_like :float_arithmetic_exception_in_coerce, :*
+
+ it "returns self multiplied by other" do
+ (4923.98221 * 2).should be_close(9847.96442, TOLERANCE)
+ (6712.5 * 0.25).should be_close(1678.125, TOLERANCE)
+ (256.4096 * bignum_value).should be_close(4729922269242236862464.0, TOLERANCE)
+ end
+
+ it "raises a TypeError when given a non-Numeric" do
+ -> { 13.0 * "10" }.should.raise(TypeError)
+ -> { 13.0 * :symbol }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/float/nan_spec.rb b/spec/ruby/core/float/nan_spec.rb
new file mode 100644
index 0000000000..c1043ef21b
--- /dev/null
+++ b/spec/ruby/core/float/nan_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Float#nan?" do
+ it "returns true if self is not a valid IEEE floating-point number" do
+ 0.0.should_not.nan?
+ -1.5.should_not.nan?
+ nan_value.should.nan?
+ end
+end
diff --git a/spec/ruby/core/float/negative_spec.rb b/spec/ruby/core/float/negative_spec.rb
new file mode 100644
index 0000000000..484e636adb
--- /dev/null
+++ b/spec/ruby/core/float/negative_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Float#negative?" do
+ describe "on positive numbers" do
+ it "returns false" do
+ 0.1.negative?.should == false
+ end
+ end
+
+ describe "on zero" do
+ it "returns false" do
+ 0.0.negative?.should == false
+ end
+ end
+
+ describe "on negative zero" do
+ it "returns false" do
+ -0.0.negative?.should == false
+ end
+ end
+
+ describe "on negative numbers" do
+ it "returns true" do
+ -0.1.negative?.should == true
+ end
+ end
+
+ describe "on NaN" do
+ it "returns false" do
+ nan_value.negative?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/float/next_float_spec.rb b/spec/ruby/core/float/next_float_spec.rb
new file mode 100644
index 0000000000..59892be343
--- /dev/null
+++ b/spec/ruby/core/float/next_float_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Float#next_float" do
+ it "returns a float the smallest possible step greater than the receiver" do
+ barely_positive = 0.0.next_float
+ barely_positive.should == 0.0.next_float
+
+ barely_positive.should > 0.0
+ barely_positive.should < barely_positive.next_float
+
+ midpoint = barely_positive / 2
+ [0.0, barely_positive].should.include? midpoint
+ end
+
+ it "returns Float::INFINITY for Float::INFINITY" do
+ Float::INFINITY.next_float.should == Float::INFINITY
+ end
+
+ it "steps directly between MAX and INFINITY" do
+ (-Float::INFINITY).next_float.should == -Float::MAX
+ Float::MAX.next_float.should == Float::INFINITY
+ end
+
+ it "steps directly between 1.0 and 1.0 + EPSILON" do
+ 1.0.next_float.should == 1.0 + Float::EPSILON
+ end
+
+ it "steps directly between -1.0 and -1.0 + EPSILON/2" do
+ (-1.0).next_float.should == -1.0 + Float::EPSILON/2
+ end
+
+ it "reverses the effect of prev_float for all Floats except INFINITY and +0.0" do
+ num = -rand
+ num.prev_float.next_float.should == num
+ end
+
+ it "returns negative zero when stepping upward from just below zero" do
+ x = (-0.0).prev_float.next_float
+ (1/x).should == -Float::INFINITY
+ end
+
+ it "gives the same result for -0.0 as for +0.0" do
+ (-0.0).next_float.should == (0.0).next_float
+ end
+
+ it "returns NAN if NAN was the receiver" do
+ Float::NAN.next_float.should.nan?
+ end
+end
diff --git a/spec/ruby/core/float/numerator_spec.rb b/spec/ruby/core/float/numerator_spec.rb
new file mode 100644
index 0000000000..53b32fdd3d
--- /dev/null
+++ b/spec/ruby/core/float/numerator_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Float#numerator" do
+ before :all do
+ @numbers = [
+ 29871.2722891,
+ 999.1**99.928888,
+ -72628191273.22,
+ 29282.2827,
+ -2927.00091,
+ 12.0,
+ Float::MAX,
+ ]
+ end
+
+ it "converts self to a Rational object then returns its numerator" do
+ @numbers.each do |number|
+ number.infinite?.should == nil
+ number.numerator.should == Rational(number).numerator
+ end
+ end
+
+ it "returns 0 for 0.0" do
+ 0.0.numerator.should == 0
+ end
+
+ it "returns NaN for NaN" do
+ nan_value.numerator.nan?.should == true
+ end
+
+ it "returns Infinity for Infinity" do
+ infinity_value.numerator.infinite?.should == 1
+ end
+
+ it "returns -Infinity for -Infinity" do
+ (-infinity_value).numerator.infinite?.should == -1
+ end
+
+end
diff --git a/spec/ruby/core/float/phase_spec.rb b/spec/ruby/core/float/phase_spec.rb
new file mode 100644
index 0000000000..2aa84024b4
--- /dev/null
+++ b/spec/ruby/core/float/phase_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arg'
+
+describe "Float#phase" do
+ it_behaves_like :float_arg, :phase
+end
diff --git a/spec/ruby/core/float/plus_spec.rb b/spec/ruby/core/float/plus_spec.rb
new file mode 100644
index 0000000000..e3e19d7f39
--- /dev/null
+++ b/spec/ruby/core/float/plus_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_exception_in_coerce'
+
+describe "Float#+" do
+ it_behaves_like :float_arithmetic_exception_in_coerce, :+
+
+ it "returns self plus other" do
+ (491.213 + 2).should be_close(493.213, TOLERANCE)
+ (9.99 + bignum_value).should be_close(18446744073709551616.0, TOLERANCE)
+ (1001.99 + 5.219).should be_close(1007.209, TOLERANCE)
+ end
+end
diff --git a/spec/ruby/core/float/positive_spec.rb b/spec/ruby/core/float/positive_spec.rb
new file mode 100644
index 0000000000..aa87c03151
--- /dev/null
+++ b/spec/ruby/core/float/positive_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../spec_helper'
+
+describe "Float#positive?" do
+ describe "on positive numbers" do
+ it "returns true" do
+ 0.1.positive?.should == true
+ end
+ end
+
+ describe "on zero" do
+ it "returns false" do
+ 0.0.positive?.should == false
+ end
+ end
+
+ describe "on negative zero" do
+ it "returns false" do
+ -0.0.positive?.should == false
+ end
+ end
+
+ describe "on negative numbers" do
+ it "returns false" do
+ -0.1.positive?.should == false
+ end
+ end
+
+ describe "on NaN" do
+ it "returns false" do
+ nan_value.positive?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/float/prev_float_spec.rb b/spec/ruby/core/float/prev_float_spec.rb
new file mode 100644
index 0000000000..2d1f136254
--- /dev/null
+++ b/spec/ruby/core/float/prev_float_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+
+describe "Float#prev_float" do
+ it "returns a float the smallest possible step smaller than the receiver" do
+ barely_negative = 0.0.prev_float
+ barely_negative.should == 0.0.prev_float
+
+ barely_negative.should < 0.0
+ barely_negative.should > barely_negative.prev_float
+
+ midpoint = barely_negative / 2
+ [0.0, barely_negative].should.include? midpoint
+ end
+
+ it "returns -Float::INFINITY for -Float::INFINITY" do
+ (-Float::INFINITY).prev_float.should == -Float::INFINITY
+ end
+
+ it "steps directly between MAX and INFINITY" do
+ Float::INFINITY.prev_float.should == Float::MAX
+ (-Float::MAX).prev_float.should == -Float::INFINITY
+ end
+
+ it "steps directly between 1.0 and 1.0 - EPSILON/2" do
+ 1.0.prev_float.should == 1.0 - Float::EPSILON/2
+ end
+
+ it "steps directly between -1.0 and -1.0 - EPSILON" do
+ (-1.0).prev_float.should == -1.0 - Float::EPSILON
+ end
+
+ it "reverses the effect of next_float for all Floats except -INFINITY and -0.0" do
+ num = rand
+ num.next_float.prev_float.should == num
+ end
+
+ it "returns positive zero when stepping downward from just above zero" do
+ x = 0.0.next_float.prev_float
+ (1/x).should == Float::INFINITY
+ end
+
+ it "gives the same result for -0.0 as for +0.0" do
+ (0.0).prev_float.should == (-0.0).prev_float
+ end
+
+ it "returns NAN if NAN was the receiver" do
+ Float::NAN.prev_float.should.nan?
+ end
+end
diff --git a/spec/ruby/core/float/quo_spec.rb b/spec/ruby/core/float/quo_spec.rb
new file mode 100644
index 0000000000..b5c64f9d87
--- /dev/null
+++ b/spec/ruby/core/float/quo_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/quo'
+
+describe "Float#quo" do
+ it_behaves_like :float_quo, :quo
+end
diff --git a/spec/ruby/core/float/rationalize_spec.rb b/spec/ruby/core/float/rationalize_spec.rb
new file mode 100644
index 0000000000..fa8fb5c841
--- /dev/null
+++ b/spec/ruby/core/float/rationalize_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+
+describe "Float#rationalize" do
+ it "returns self as a simplified Rational with no argument" do
+ (3382729202.92822).rationalize.should == Rational(4806858197361, 1421)
+ end
+
+ # FIXME: These specs need reviewing by somebody familiar with the
+ # algorithm used by #rationalize
+ it "simplifies self to the degree specified by a Rational argument" do
+ f = 0.3
+ f.rationalize(Rational(1,10)).should == Rational(1,3)
+ f.rationalize(Rational(-1,10)).should == Rational(1,3)
+
+ f = -f
+ f.rationalize(Rational(1,10)).should == Rational(-1,3)
+ f.rationalize(Rational(-1,10)).should == Rational(-1,3)
+
+ end
+
+ it "simplifies self to the degree specified by a Float argument" do
+ f = 0.3
+ f.rationalize(0.05).should == Rational(1,3)
+ f.rationalize(0.001).should == Rational(3, 10)
+
+ f = -f
+ f.rationalize(0.05).should == Rational(-1,3)
+ f.rationalize(0.001).should == Rational(-3,10)
+ end
+
+ it "raises a FloatDomainError for Infinity" do
+ -> {infinity_value.rationalize}.should.raise(FloatDomainError)
+ end
+
+ it "raises a FloatDomainError for NaN" do
+ -> { nan_value.rationalize }.should.raise(FloatDomainError)
+ end
+
+ it "raises ArgumentError when passed more than one argument" do
+ -> { 0.3.rationalize(0.1, 0.1) }.should.raise(ArgumentError)
+ -> { 0.3.rationalize(0.1, 0.1, 2) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/float/round_spec.rb b/spec/ruby/core/float/round_spec.rb
new file mode 100644
index 0000000000..63c1d5689c
--- /dev/null
+++ b/spec/ruby/core/float/round_spec.rb
@@ -0,0 +1,208 @@
+require_relative '../../spec_helper'
+
+describe "Float#round" do
+ it "returns the nearest Integer" do
+ 5.5.round.should == 6
+ 0.4.round.should == 0
+ 0.6.round.should == 1
+ -1.4.round.should == -1
+ -2.8.round.should == -3
+ 0.0.round.should == 0
+ end
+
+ it "returns the nearest Integer for Float near the limit" do
+ 0.49999999999999994.round.should == 0
+ -0.49999999999999994.round.should == 0
+ end
+
+ it "raises FloatDomainError for exceptional values" do
+ -> { (+infinity_value).round }.should.raise(FloatDomainError)
+ -> { (-infinity_value).round }.should.raise(FloatDomainError)
+ -> { nan_value.round }.should.raise(FloatDomainError)
+ end
+
+ it "rounds self to an optionally given precision" do
+ 5.5.round(0).should.eql?(6)
+ 5.7.round(1).should.eql?(5.7)
+ 1.2345678.round(2).should == 1.23
+ 123456.78.round(-2).should.eql?(123500) # rounded up
+ -123456.78.round(-2).should.eql?(-123500)
+ 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
+
+ it "raises a TypeError when its argument can not be converted to an Integer" do
+ -> { 1.0.round("4") }.should.raise(TypeError)
+ -> { 1.0.round(nil) }.should.raise(TypeError)
+ end
+
+ it "raises FloatDomainError for exceptional values when passed a non-positive precision" do
+ -> { Float::INFINITY.round( 0) }.should.raise(FloatDomainError)
+ -> { Float::INFINITY.round(-2) }.should.raise(FloatDomainError)
+ -> { (-Float::INFINITY).round( 0) }.should.raise(FloatDomainError)
+ -> { (-Float::INFINITY).round(-2) }.should.raise(FloatDomainError)
+ end
+
+ it "raises RangeError for NAN when passed a non-positive precision" do
+ -> { Float::NAN.round(0) }.should.raise(RangeError)
+ -> { Float::NAN.round(-2) }.should.raise(RangeError)
+ end
+
+ it "returns self for exceptional values when passed a non-negative precision" do
+ Float::INFINITY.round(2).should == Float::INFINITY
+ (-Float::INFINITY).round(2).should == -Float::INFINITY
+ Float::NAN.round(2).should.nan?
+ end
+
+ # redmine:5227
+ it "works for corner cases" do
+ 42.0.round(308).should.eql?(42.0)
+ 1.0e307.round(2).should.eql?(1.0e307)
+ 120.0.round(-1).should.eql?(120)
+ end
+
+ # redmine:5271
+ it "returns rounded values for big argument" 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 )
+ end
+
+ # redmine #5272
+ it "returns rounded values for big values" do
+ +2.4e20.round(-20).should.eql?( +2 * 10 ** 20 )
+ -2.4e20.round(-20).should.eql?( -2 * 10 ** 20 )
+ +2.5e200.round(-200).should.eql?( +3 * 10 ** 200 )
+ +2.4e200.round(-200).should.eql?( +2 * 10 ** 200 )
+ -2.5e200.round(-200).should.eql?( -3 * 10 ** 200 )
+ -2.4e200.round(-200).should.eql?( -2 * 10 ** 200 )
+ end
+
+ it "returns different rounded values depending on the half option" do
+ 2.5.round(half: nil).should.eql?(3)
+ 2.5.round(half: :up).should.eql?(3)
+ 2.5.round(half: :down).should.eql?(2)
+ 2.5.round(half: :even).should.eql?(2)
+ 3.5.round(half: nil).should.eql?(4)
+ 3.5.round(half: :up).should.eql?(4)
+ 3.5.round(half: :down).should.eql?(3)
+ 3.5.round(half: :even).should.eql?(4)
+ (-2.5).round(half: nil).should.eql?(-3)
+ (-2.5).round(half: :up).should.eql?(-3)
+ (-2.5).round(half: :down).should.eql?(-2)
+ (-2.5).round(half: :even).should.eql?(-2)
+ end
+
+ it "rounds self to an optionally given precision with a half option" do
+ 5.55.round(1, half: nil).should.eql?(5.6)
+ 5.55.round(1, half: :up).should.eql?(5.6)
+ 5.55.round(1, half: :down).should.eql?(5.5)
+ 5.55.round(1, half: :even).should.eql?(5.6)
+ -5.55.round(1, half: nil).should.eql?(-5.6)
+ -5.55.round(1, half: :up).should.eql?(-5.6)
+ -5.55.round(1, half: :down).should.eql?(-5.5)
+ -5.55.round(1, half: :even).should.eql?(-5.6)
+ end
+
+ it "preserves cases where neighbouring floating pointer number increase the decimal places" do
+ 4.8100000000000005.round(5, half: nil).should.eql?(4.81)
+ 4.8100000000000005.round(5, half: :up).should.eql?(4.81)
+ 4.8100000000000005.round(5, half: :down).should.eql?(4.81)
+ 4.8100000000000005.round(5, half: :even).should.eql?(4.81)
+ -4.8100000000000005.round(5, half: nil).should.eql?(-4.81)
+ -4.8100000000000005.round(5, half: :up).should.eql?(-4.81)
+ -4.8100000000000005.round(5, half: :down).should.eql?(-4.81)
+ -4.8100000000000005.round(5, half: :even).should.eql?(-4.81)
+ 4.81.round(5, half: nil).should.eql?(4.81)
+ 4.81.round(5, half: :up).should.eql?(4.81)
+ 4.81.round(5, half: :down).should.eql?(4.81)
+ 4.81.round(5, half: :even).should.eql?(4.81)
+ -4.81.round(5, half: nil).should.eql?(-4.81)
+ -4.81.round(5, half: :up).should.eql?(-4.81)
+ -4.81.round(5, half: :down).should.eql?(-4.81)
+ -4.81.round(5, half: :even).should.eql?(-4.81)
+ 4.809999999999999.round(5, half: nil).should.eql?(4.81)
+ 4.809999999999999.round(5, half: :up).should.eql?(4.81)
+ 4.809999999999999.round(5, half: :down).should.eql?(4.81)
+ 4.809999999999999.round(5, half: :even).should.eql?(4.81)
+ -4.809999999999999.round(5, half: nil).should.eql?(-4.81)
+ -4.809999999999999.round(5, half: :up).should.eql?(-4.81)
+ -4.809999999999999.round(5, half: :down).should.eql?(-4.81)
+ -4.809999999999999.round(5, half: :even).should.eql?(-4.81)
+ end
+
+ # These numbers are neighbouring floating point numbers round a
+ # precise value. They test that the rounding modes work correctly
+ # round that value and precision is not lost which might cause
+ # incorrect results.
+ it "does not lose precision during the rounding process" do
+ 767573.1875850001.round(5, half: nil).should.eql?(767573.18759)
+ 767573.1875850001.round(5, half: :up).should.eql?(767573.18759)
+ 767573.1875850001.round(5, half: :down).should.eql?(767573.18759)
+ 767573.1875850001.round(5, half: :even).should.eql?(767573.18759)
+ -767573.1875850001.round(5, half: nil).should.eql?(-767573.18759)
+ -767573.1875850001.round(5, half: :up).should.eql?(-767573.18759)
+ -767573.1875850001.round(5, half: :down).should.eql?(-767573.18759)
+ -767573.1875850001.round(5, half: :even).should.eql?(-767573.18759)
+ 767573.187585.round(5, half: nil).should.eql?(767573.18759)
+ 767573.187585.round(5, half: :up).should.eql?(767573.18759)
+ 767573.187585.round(5, half: :down).should.eql?(767573.18758)
+ 767573.187585.round(5, half: :even).should.eql?(767573.18758)
+ -767573.187585.round(5, half: nil).should.eql?(-767573.18759)
+ -767573.187585.round(5, half: :up).should.eql?(-767573.18759)
+ -767573.187585.round(5, half: :down).should.eql?(-767573.18758)
+ -767573.187585.round(5, half: :even).should.eql?(-767573.18758)
+ 767573.1875849998.round(5, half: nil).should.eql?(767573.18758)
+ 767573.1875849998.round(5, half: :up).should.eql?(767573.18758)
+ 767573.1875849998.round(5, half: :down).should.eql?(767573.18758)
+ 767573.1875849998.round(5, half: :even).should.eql?(767573.18758)
+ -767573.1875849998.round(5, half: nil).should.eql?(-767573.18758)
+ -767573.1875849998.round(5, half: :up).should.eql?(-767573.18758)
+ -767573.1875849998.round(5, half: :down).should.eql?(-767573.18758)
+ -767573.1875849998.round(5, half: :even).should.eql?(-767573.18758)
+ end
+
+ it "raises FloatDomainError for exceptional values with a half option" do
+ -> { (+infinity_value).round(half: :up) }.should.raise(FloatDomainError)
+ -> { (-infinity_value).round(half: :down) }.should.raise(FloatDomainError)
+ -> { nan_value.round(half: :even) }.should.raise(FloatDomainError)
+ end
+
+ it "raise for a non-existent round mode" do
+ -> { 14.2.round(half: :nonsense) }.should.raise(ArgumentError, "invalid rounding mode: nonsense")
+ end
+
+ describe "when 0.0 is given" do
+ it "returns self for positive ndigits" do
+ (0.0).round(5).inspect.should == "0.0"
+ (-0.0).round(1).inspect.should == "-0.0"
+ end
+
+ it "returns 0 for 0 or undefined ndigits" do
+ (0.0).round.should == 0
+ (-0.0).round(0).should == 0
+ (0.0).round(half: :up).should == 0
+ end
+
+ it "returns 0 for negative ndigits" do
+ (0.0).round(-1).should == 0
+ (-0.0).round(-1).should == 0
+ (0.0).round(-1, half: :up).should == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/float/shared/abs.rb b/spec/ruby/core/float/shared/abs.rb
new file mode 100644
index 0000000000..ab21480e24
--- /dev/null
+++ b/spec/ruby/core/float/shared/abs.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+describe :float_abs, shared: true do
+ it "returns the absolute value" do
+ -99.1.send(@method).should be_close(99.1, TOLERANCE)
+ 4.5.send(@method).should be_close(4.5, TOLERANCE)
+ 0.0.send(@method).should be_close(0.0, TOLERANCE)
+ end
+
+ it "returns 0.0 if -0.0" do
+ (-0.0).send(@method).should be_positive_zero
+ end
+
+ it "returns Infinity if -Infinity" do
+ (-infinity_value).send(@method).infinite?.should == 1
+ end
+
+ it "returns NaN if NaN" do
+ nan_value.send(@method).nan?.should == true
+ end
+end
diff --git a/spec/ruby/core/float/shared/arg.rb b/spec/ruby/core/float/shared/arg.rb
new file mode 100644
index 0000000000..de0024313d
--- /dev/null
+++ b/spec/ruby/core/float/shared/arg.rb
@@ -0,0 +1,36 @@
+describe :float_arg, shared: true do
+ it "returns NaN if NaN" do
+ f = nan_value
+ f.send(@method).nan?.should == true
+ end
+
+ it "returns self if NaN" do
+ f = nan_value
+ f.send(@method).should.equal?(f)
+ end
+
+ it "returns 0 if positive" do
+ 1.0.send(@method).should == 0
+ end
+
+ it "returns 0 if +0.0" do
+ 0.0.send(@method).should == 0
+ end
+
+ it "returns 0 if +Infinity" do
+ infinity_value.send(@method).should == 0
+ end
+
+ it "returns Pi if negative" do
+ (-1.0).send(@method).should == Math::PI
+ end
+
+ # This was established in r23960
+ it "returns Pi if -0.0" do
+ (-0.0).send(@method).should == Math::PI
+ end
+
+ it "returns Pi if -Infinity" do
+ (-infinity_value).send(@method).should == Math::PI
+ end
+end
diff --git a/spec/ruby/core/float/shared/arithmetic_exception_in_coerce.rb b/spec/ruby/core/float/shared/arithmetic_exception_in_coerce.rb
new file mode 100644
index 0000000000..bd3bf9019f
--- /dev/null
+++ b/spec/ruby/core/float/shared/arithmetic_exception_in_coerce.rb
@@ -0,0 +1,11 @@
+require_relative '../fixtures/classes'
+
+describe :float_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(FloatSpecs::CoerceError)
+
+ # e.g. 1.0 > b
+ -> { 1.0.send(@method, b) }.should.raise(FloatSpecs::CoerceError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/comparison_exception_in_coerce.rb b/spec/ruby/core/float/shared/comparison_exception_in_coerce.rb
new file mode 100644
index 0000000000..eec5d8daf9
--- /dev/null
+++ b/spec/ruby/core/float/shared/comparison_exception_in_coerce.rb
@@ -0,0 +1,11 @@
+require_relative '../fixtures/classes'
+
+describe :float_comparison_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(FloatSpecs::CoerceError)
+
+ # e.g. 1.0 > b
+ -> { 1.0.send(@method, b) }.should.raise(FloatSpecs::CoerceError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/equal.rb b/spec/ruby/core/float/shared/equal.rb
new file mode 100644
index 0000000000..4d524e1cf2
--- /dev/null
+++ b/spec/ruby/core/float/shared/equal.rb
@@ -0,0 +1,38 @@
+describe :float_equal, shared: true do
+ it "returns true if self has the same value as other" do
+ 1.0.send(@method, 1).should == true
+ 2.71828.send(@method, 1.428).should == false
+ -4.2.send(@method, 4.2).should == false
+ end
+
+ it "calls 'other == self' if coercion fails" do
+ x = mock('other')
+ def x.==(other)
+ 2.0 == other
+ end
+
+ 1.0.send(@method, x).should == false
+ 2.0.send(@method, x).should == true
+ end
+
+ it "returns false if one side is NaN" do
+ [1.0, 42, bignum_value].each { |n|
+ (nan_value.send(@method, n)).should == false
+ (n.send(@method, nan_value)).should == false
+ }
+ end
+
+ it "handles positive infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ (infinity_value.send(@method, n)).should == false
+ (n.send(@method, infinity_value)).should == false
+ }
+ end
+
+ it "handles negative infinity" do
+ [1.0, 42, bignum_value].each { |n|
+ ((-infinity_value).send(@method, n)).should == false
+ (n.send(@method, -infinity_value)).should == false
+ }
+ end
+end
diff --git a/spec/ruby/core/float/shared/modulo.rb b/spec/ruby/core/float/shared/modulo.rb
new file mode 100644
index 0000000000..1efee1476d
--- /dev/null
+++ b/spec/ruby/core/float/shared/modulo.rb
@@ -0,0 +1,48 @@
+describe :float_modulo, shared: true do
+ it "returns self modulo other" do
+ 6543.21.send(@method, 137).should be_close(104.21, TOLERANCE)
+ 5667.19.send(@method, bignum_value).should be_close(5667.19, TOLERANCE)
+ 6543.21.send(@method, 137.24).should be_close(92.9299999999996, TOLERANCE)
+
+ -1.0.send(@method, 1).should == 0
+ end
+
+ it "returns self when modulus is +Infinity" do
+ 4.2.send(@method, Float::INFINITY).should == 4.2
+ end
+
+ it "returns -Infinity when modulus is -Infinity" do
+ 4.2.send(@method, -Float::INFINITY).should == -Float::INFINITY
+ end
+
+ it "returns NaN when called on NaN or Infinities" do
+ Float::NAN.send(@method, 42).should.nan?
+ Float::INFINITY.send(@method, 42).should.nan?
+ (-Float::INFINITY).send(@method, 42).should.nan?
+ end
+
+ it "returns NaN when modulus is NaN" do
+ 4.2.send(@method, Float::NAN).should.nan?
+ end
+
+ it "returns -0.0 when called on -0.0 with a non zero modulus" do
+ r = (-0.0).send(@method, 42)
+ r.should == 0
+ (1/r).should < 0
+
+ r = (-0.0).send(@method, Float::INFINITY)
+ r.should == 0
+ (1/r).should < 0
+ end
+
+ it "tries to coerce the modulus" do
+ obj = mock("modulus")
+ obj.should_receive(:coerce).with(1.25).and_return([1.25, 0.5])
+ (1.25 % obj).should == 0.25
+ end
+
+ it "raises a ZeroDivisionError if other is zero" do
+ -> { 1.0.send(@method, 0) }.should.raise(ZeroDivisionError)
+ -> { 1.0.send(@method, 0.0) }.should.raise(ZeroDivisionError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/quo.rb b/spec/ruby/core/float/shared/quo.rb
new file mode 100644
index 0000000000..930187aaf7
--- /dev/null
+++ b/spec/ruby/core/float/shared/quo.rb
@@ -0,0 +1,59 @@
+describe :float_quo, shared: true do
+ it "performs floating-point division between self and an Integer" do
+ 8.9.send(@method, 7).should == 1.2714285714285716
+ end
+
+ it "performs floating-point division between self and an Integer" do
+ 8.9.send(@method, 9999999999999**9).should == 8.900000000008011e-117
+ end
+
+ it "performs floating-point division between self and a Float" do
+ 2827.22.send(@method, 872.111111).should == 3.2418116961704433
+ end
+
+ it "returns NaN when the argument is NaN" do
+ -1819.999999.send(@method, nan_value).nan?.should == true
+ 11109.1981271.send(@method, nan_value).nan?.should == true
+ end
+
+ it "returns Infinity when the argument is 0.0" do
+ 2827.22.send(@method, 0.0).infinite?.should == 1
+ end
+
+ it "returns -Infinity when the argument is 0.0 and self is negative" do
+ -48229.282.send(@method, 0.0).infinite?.should == -1
+ end
+
+ it "returns Infinity when the argument is 0" do
+ 2827.22.send(@method, 0).infinite?.should == 1
+ end
+
+ it "returns -Infinity when the argument is 0 and self is negative" do
+ -48229.282.send(@method, 0).infinite?.should == -1
+ end
+
+ it "returns 0.0 when the argument is Infinity" do
+ 47292.2821.send(@method, infinity_value).should == 0.0
+ end
+
+ it "returns -0.0 when the argument is -Infinity" do
+ 1.9999918.send(@method, -infinity_value).should == -0.0
+ end
+
+ it "performs floating-point division between self and a Rational" do
+ 74620.09.send(@method, Rational(2,3)).should == 111930.135
+ end
+
+ it "performs floating-point division between self and a Complex" do
+ 74620.09.send(@method, Complex(8,2)).should == Complex(
+ 8778.834117647059, -2194.7085294117646)
+ end
+
+ it "raises a TypeError when argument isn't numeric" do
+ -> { 27292.2.send(@method, mock('non-numeric')) }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError when passed multiple arguments" do
+ -> { 272.221.send(@method, 6,0.2) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/to_i.rb b/spec/ruby/core/float/shared/to_i.rb
new file mode 100644
index 0000000000..1e6f941467
--- /dev/null
+++ b/spec/ruby/core/float/shared/to_i.rb
@@ -0,0 +1,14 @@
+describe :float_to_i, shared: true do
+ it "returns self truncated to an Integer" do
+ 899.2.send(@method).should.eql?(899)
+ -1.122256e-45.send(@method).should.eql?(0)
+ 5_213_451.9201.send(@method).should.eql?(5213451)
+ 1.233450999123389e+12.send(@method).should.eql?(1233450999123)
+ -9223372036854775808.1.send(@method).should.eql?(-9223372036854775808)
+ 9223372036854775808.1.send(@method).should.eql?(9223372036854775808)
+ end
+
+ it "raises a FloatDomainError for NaN" do
+ -> { nan_value.send(@method) }.should.raise(FloatDomainError)
+ end
+end
diff --git a/spec/ruby/core/float/shared/to_s.rb b/spec/ruby/core/float/shared/to_s.rb
new file mode 100644
index 0000000000..81ffdd9e81
--- /dev/null
+++ b/spec/ruby/core/float/shared/to_s.rb
@@ -0,0 +1,308 @@
+describe :float_to_s, shared: true do
+ it "returns 'NaN' for NaN" do
+ nan_value().send(@method).should == 'NaN'
+ end
+
+ it "returns 'Infinity' for positive infinity" do
+ infinity_value().send(@method).should == 'Infinity'
+ end
+
+ it "returns '-Infinity' for negative infinity" do
+ (-infinity_value()).send(@method).should == '-Infinity'
+ end
+
+ it "returns '0.0' for 0.0" do
+ 0.0.send(@method).should == "0.0"
+ end
+
+ platform_is_not :openbsd do
+ it "emits '-' for -0.0" do
+ -0.0.send(@method).should == "-0.0"
+ end
+ end
+
+ it "emits a '-' for negative values" do
+ -3.14.send(@method).should == "-3.14"
+ end
+
+ it "emits a trailing '.0' for a whole number" do
+ 50.0.send(@method).should == "50.0"
+ end
+
+ it "emits a trailing '.0' for the mantissa in e format" do
+ 1.0e20.send(@method).should == "1.0e+20"
+ end
+
+ it "uses non-e format for a positive value with fractional part having 5 significant figures" do
+ 0.0001.send(@method).should == "0.0001"
+ end
+
+ it "uses non-e format for a negative value with fractional part having 5 significant figures" do
+ -0.0001.send(@method).should == "-0.0001"
+ end
+
+ it "uses e format for a positive value with fractional part having 6 significant figures" do
+ 0.00001.send(@method).should == "1.0e-05"
+ end
+
+ it "uses e format for a negative value with fractional part having 6 significant figures" do
+ -0.00001.send(@method).should == "-1.0e-05"
+ end
+
+ it "uses non-e format for a positive value with whole part having 15 significant figures" do
+ 10000000000000.0.send(@method).should == "10000000000000.0"
+ end
+
+ it "uses non-e format for a negative value with whole part having 15 significant figures" do
+ -10000000000000.0.send(@method).should == "-10000000000000.0"
+ end
+
+ it "uses non-e format for a positive value with whole part having 16 significant figures" do
+ 100000000000000.0.send(@method).should == "100000000000000.0"
+ end
+
+ it "uses non-e format for a negative value with whole part having 16 significant figures" do
+ -100000000000000.0.send(@method).should == "-100000000000000.0"
+ end
+
+ it "uses e format for a positive value with whole part having 18 significant figures" do
+ 10000000000000000.0.send(@method).should == "1.0e+16"
+ end
+
+ it "uses e format for a negative value with whole part having 18 significant figures" do
+ -10000000000000000.0.send(@method).should == "-1.0e+16"
+ end
+
+ it "uses e format for a positive value with whole part having 17 significant figures" do
+ 1000000000000000.0.send(@method).should == "1.0e+15"
+ end
+
+ it "uses e format for a negative value with whole part having 17 significant figures" do
+ -1000000000000000.0.send(@method).should == "-1.0e+15"
+ end
+
+ # #3273
+ it "outputs the minimal, unique form necessary to recreate the value" do
+ value = 0.21611564636388508
+ string = "0.21611564636388508"
+
+ value.send(@method).should == string
+ string.to_f.should == value
+ end
+
+ it "outputs the minimal, unique form to represent the value" do
+ 0.56.send(@method).should == "0.56"
+ end
+
+ describe "matches" do
+ it "random examples in all ranges" do
+ # 50.times do
+ # bytes = (0...8).map { rand(256) }
+ # string = bytes.pack('C8')
+ # float = string.unpack('D').first
+ # puts "#{'%.20g' % float}.send(@method).should == #{float.send(@method).inspect}"
+ # end
+
+ 2.5540217314354050325e+163.send(@method).should == "2.554021731435405e+163"
+ 2.5492588360356597544e-172.send(@method).should == "2.5492588360356598e-172"
+ 1.742770260934704852e-82.send(@method).should == "1.7427702609347049e-82"
+ 6.2108093676180883209e-104.send(@method).should == "6.210809367618088e-104"
+ -3.3448803488331067402e-143.send(@method).should == "-3.3448803488331067e-143"
+ -2.2740074343500832557e-168.send(@method).should == "-2.2740074343500833e-168"
+ 7.0587971678048535732e+191.send(@method).should == "7.058797167804854e+191"
+ -284438.88327586348169.send(@method).should == "-284438.8832758635"
+ 3.953272468476091301e+105.send(@method).should == "3.9532724684760913e+105"
+ -3.6361359552959847853e+100.send(@method).should == "-3.636135955295985e+100"
+ -1.3222325865575206185e-31.send(@method).should == "-1.3222325865575206e-31"
+ 1.1440138916932761366e+130.send(@method).should == "1.1440138916932761e+130"
+ 4.8750891560387561157e-286.send(@method).should == "4.875089156038756e-286"
+ 5.6101113356591453525e-257.send(@method).should == "5.610111335659145e-257"
+ -3.829644279545809575e-100.send(@method).should == "-3.8296442795458096e-100"
+ 1.5342839401396406117e-194.send(@method).should == "1.5342839401396406e-194"
+ 2.2284972755169921402e-144.send(@method).should == "2.228497275516992e-144"
+ 2.1825655917065601737e-61.send(@method).should == "2.1825655917065602e-61"
+ -2.6672271363524338322e-62.send(@method).should == "-2.667227136352434e-62"
+ -1.9257995160119059415e+21.send(@method).should == "-1.925799516011906e+21"
+ -8.9096732962887121718e-198.send(@method).should == "-8.909673296288712e-198"
+ 2.0202075376548644959e-90.send(@method).should == "2.0202075376548645e-90"
+ -7.7341602581786258961e-266.send(@method).should == "-7.734160258178626e-266"
+ 3.5134482598733635046e+98.send(@method).should == "3.5134482598733635e+98"
+ -2.124411722371029134e+154.send(@method).should == "-2.124411722371029e+154"
+ -4.573908787355718687e+110.send(@method).should == "-4.573908787355719e+110"
+ -1.9344425934170969879e-232.send(@method).should == "-1.934442593417097e-232"
+ -1.3274227399979271095e+171.send(@method).should == "-1.3274227399979271e+171"
+ 9.3495270482104442383e-283.send(@method).should == "9.349527048210444e-283"
+ -4.2046059371986483233e+307.send(@method).should == "-4.2046059371986483e+307"
+ 3.6133547278583543004e-117.send(@method).should == "3.613354727858354e-117"
+ 4.9247416523566613499e-08.send(@method).should == "4.9247416523566613e-08"
+ 1.6936145488250064007e-71.send(@method).should == "1.6936145488250064e-71"
+ 2.4455483206829433098e+96.send(@method).should == "2.4455483206829433e+96"
+ 7.9797449851436455384e+124.send(@method).should == "7.979744985143646e+124"
+ -1.3873689634457876774e-129.send(@method).should == "-1.3873689634457877e-129"
+ 3.9761102037533483075e+284.send(@method).should == "3.976110203753348e+284"
+ -4.2819791952139402486e-303.send(@method).should == "-4.28197919521394e-303"
+ -5.7981017546689831298e-116.send(@method).should == "-5.798101754668983e-116"
+ -3.953266497860534199e-28.send(@method).should == "-3.953266497860534e-28"
+ -2.0659852720290440959e-243.send(@method).should == "-2.065985272029044e-243"
+ 8.9670488995878688018e-05.send(@method).should == "8.967048899587869e-05"
+ -1.2317943708113061768e-98.send(@method).should == "-1.2317943708113062e-98"
+ -3.8930768307633080463e+248.send(@method).should == "-3.893076830763308e+248"
+ 6.5854032671803925627e-239.send(@method).should == "6.5854032671803926e-239"
+ 4.6257022188980878952e+177.send(@method).should == "4.625702218898088e+177"
+ -1.9397155125507235603e-187.send(@method).should == "-1.9397155125507236e-187"
+ 8.5752156951245705056e+117.send(@method).should == "8.57521569512457e+117"
+ -2.4784875958162501671e-132.send(@method).should == "-2.4784875958162502e-132"
+ -4.4125691841230058457e-203.send(@method).should == "-4.412569184123006e-203"
+ end
+
+ it "random examples in human ranges" do
+ # 50.times do
+ # formatted = ''
+ # rand(1..3).times do
+ # formatted << rand(10).to_s
+ # end
+ # formatted << '.'
+ # rand(1..9).times do
+ # formatted << rand(10).to_s
+ # end
+ # float = formatted.to_f
+ # puts "#{'%.20f' % float}.send(@method).should == #{float.send(@method).inspect}"
+ # end
+
+ 5.17869899999999994122.send(@method).should == "5.178699"
+ 905.62695729999995819526.send(@method).should == "905.6269573"
+ 62.75999999999999801048.send(@method).should == "62.76"
+ 6.93856795800000014651.send(@method).should == "6.938567958"
+ 4.95999999999999996447.send(@method).should == "4.96"
+ 32.77993899999999882766.send(@method).should == "32.779939"
+ 544.12756779999995160324.send(@method).should == "544.1275678"
+ 66.25801119999999855281.send(@method).should == "66.2580112"
+ 7.90000000000000035527.send(@method).should == "7.9"
+ 5.93100000000000004974.send(@method).should == "5.931"
+ 5.21229313600000043749.send(@method).should == "5.212293136"
+ 503.44173809000000119340.send(@method).should == "503.44173809"
+ 79.26000000000000511591.send(@method).should == "79.26"
+ 8.51524999999999998579.send(@method).should == "8.51525"
+ 174.00000000000000000000.send(@method).should == "174.0"
+ 50.39580000000000126192.send(@method).should == "50.3958"
+ 35.28999999999999914735.send(@method).should == "35.29"
+ 5.43136675399999990788.send(@method).should == "5.431366754"
+ 654.07680000000004838512.send(@method).should == "654.0768"
+ 6.07423700000000010846.send(@method).should == "6.074237"
+ 102.25779799999999397642.send(@method).should == "102.257798"
+ 5.08129999999999970584.send(@method).should == "5.0813"
+ 6.00000000000000000000.send(@method).should == "6.0"
+ 8.30000000000000071054.send(@method).should == "8.3"
+ 32.68345999999999662577.send(@method).should == "32.68346"
+ 581.11170000000004165486.send(@method).should == "581.1117"
+ 76.31342999999999676675.send(@method).should == "76.31343"
+ 438.30826000000001840817.send(@method).should == "438.30826"
+ 482.06631994000002805478.send(@method).should == "482.06631994"
+ 55.92721026899999969828.send(@method).should == "55.927210269"
+ 4.00000000000000000000.send(@method).should == "4.0"
+ 55.86693999999999959982.send(@method).should == "55.86694"
+ 787.98299999999994724931.send(@method).should == "787.983"
+ 5.73810511000000023074.send(@method).should == "5.73810511"
+ 74.51926810000000500622.send(@method).should == "74.5192681"
+ 892.89999999999997726263.send(@method).should == "892.9"
+ 68.27299999999999613465.send(@method).should == "68.273"
+ 904.10000000000002273737.send(@method).should == "904.1"
+ 5.23200000000000020606.send(@method).should == "5.232"
+ 4.09628000000000014325.send(@method).should == "4.09628"
+ 46.05152633699999853434.send(@method).should == "46.051526337"
+ 142.12884990599999923688.send(@method).should == "142.128849906"
+ 3.83057023500000015659.send(@method).should == "3.830570235"
+ 11.81684594699999912848.send(@method).should == "11.816845947"
+ 80.50000000000000000000.send(@method).should == "80.5"
+ 382.18215010000000120272.send(@method).should == "382.1821501"
+ 55.38444606899999911320.send(@method).should == "55.384446069"
+ 5.78000000000000024869.send(@method).should == "5.78"
+ 2.88244999999999995666.send(@method).should == "2.88245"
+ 43.27709999999999723741.send(@method).should == "43.2771"
+ end
+
+ it "random values from divisions" do
+ (1.0 / 7).send(@method).should == "0.14285714285714285"
+
+ # 50.times do
+ # a = rand(10)
+ # b = rand(10)
+ # c = rand(10)
+ # d = rand(10)
+ # expression = "#{a}.#{b} / #{c}.#{d}"
+ # puts " (#{expression}).send(@method).should == #{eval(expression).send(@method).inspect}"
+ # end
+
+ (1.1 / 7.1).send(@method).should == "0.15492957746478875"
+ (6.5 / 8.8).send(@method).should == "0.7386363636363635"
+ (4.8 / 4.3).send(@method).should == "1.1162790697674418"
+ (4.0 / 1.9).send(@method).should == "2.1052631578947367"
+ (9.1 / 0.8).send(@method).should == "11.374999999999998"
+ (5.3 / 7.5).send(@method).should == "0.7066666666666667"
+ (2.8 / 1.8).send(@method).should == "1.5555555555555554"
+ (2.1 / 2.5).send(@method).should == "0.8400000000000001"
+ (3.5 / 6.0).send(@method).should == "0.5833333333333334"
+ (4.6 / 0.3).send(@method).should == "15.333333333333332"
+ (0.6 / 2.4).send(@method).should == "0.25"
+ (1.3 / 9.1).send(@method).should == "0.14285714285714288"
+ (0.3 / 5.0).send(@method).should == "0.06"
+ (5.0 / 4.2).send(@method).should == "1.1904761904761905"
+ (3.0 / 2.0).send(@method).should == "1.5"
+ (6.3 / 2.0).send(@method).should == "3.15"
+ (5.4 / 6.0).send(@method).should == "0.9"
+ (9.6 / 8.1).send(@method).should == "1.1851851851851851"
+ (8.7 / 1.6).send(@method).should == "5.437499999999999"
+ (1.9 / 7.8).send(@method).should == "0.24358974358974358"
+ (0.5 / 2.1).send(@method).should == "0.23809523809523808"
+ (9.3 / 5.8).send(@method).should == "1.6034482758620692"
+ (2.7 / 8.0).send(@method).should == "0.3375"
+ (9.7 / 7.8).send(@method).should == "1.2435897435897436"
+ (8.1 / 2.4).send(@method).should == "3.375"
+ (7.7 / 2.7).send(@method).should == "2.8518518518518516"
+ (7.9 / 1.7).send(@method).should == "4.647058823529412"
+ (6.5 / 8.2).send(@method).should == "0.7926829268292683"
+ (7.8 / 9.6).send(@method).should == "0.8125"
+ (2.2 / 4.6).send(@method).should == "0.47826086956521746"
+ (0.0 / 1.0).send(@method).should == "0.0"
+ (8.3 / 2.9).send(@method).should == "2.8620689655172415"
+ (3.1 / 6.1).send(@method).should == "0.5081967213114754"
+ (2.8 / 7.8).send(@method).should == "0.358974358974359"
+ (8.0 / 0.1).send(@method).should == "80.0"
+ (1.7 / 6.4).send(@method).should == "0.265625"
+ (1.8 / 5.4).send(@method).should == "0.3333333333333333"
+ (8.0 / 5.8).send(@method).should == "1.3793103448275863"
+ (5.2 / 4.1).send(@method).should == "1.2682926829268295"
+ (9.8 / 5.8).send(@method).should == "1.6896551724137934"
+ (5.4 / 9.5).send(@method).should == "0.5684210526315789"
+ (8.4 / 4.9).send(@method).should == "1.7142857142857142"
+ (1.7 / 3.5).send(@method).should == "0.4857142857142857"
+ (1.2 / 5.1).send(@method).should == "0.23529411764705882"
+ (1.4 / 2.0).send(@method).should == "0.7"
+ (4.8 / 8.0).send(@method).should == "0.6"
+ (9.0 / 2.5).send(@method).should == "3.6"
+ (0.2 / 0.6).send(@method).should == "0.33333333333333337"
+ (7.8 / 5.2).send(@method).should == "1.5"
+ (9.5 / 5.5).send(@method).should == "1.7272727272727273"
+ end
+ end
+
+ describe 'encoding' do
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ 1.23.send(@method).encoding.should.equal?(Encoding::US_ASCII)
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do
+ Encoding.default_internal = Encoding::IBM437
+ 5.47.send(@method).encoding.should.equal?(Encoding::US_ASCII)
+ end
+ end
+end
diff --git a/spec/ruby/core/float/to_f_spec.rb b/spec/ruby/core/float/to_f_spec.rb
new file mode 100644
index 0000000000..6677556cd9
--- /dev/null
+++ b/spec/ruby/core/float/to_f_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Float#to_f" do
+ it "returns self" do
+ -500.3.to_f.should == -500.3
+ 267.51.to_f.should == 267.51
+ 1.1412.to_f.should == 1.1412
+ end
+end
diff --git a/spec/ruby/core/float/to_i_spec.rb b/spec/ruby/core/float/to_i_spec.rb
new file mode 100644
index 0000000000..91d84c5fa3
--- /dev/null
+++ b/spec/ruby/core/float/to_i_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Float#to_i" do
+ it_behaves_like :float_to_i, :to_i
+end
diff --git a/spec/ruby/core/float/to_int_spec.rb b/spec/ruby/core/float/to_int_spec.rb
new file mode 100644
index 0000000000..084a58b431
--- /dev/null
+++ b/spec/ruby/core/float/to_int_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Float#to_int" do
+ it_behaves_like :float_to_i, :to_int
+end
diff --git a/spec/ruby/core/float/to_r_spec.rb b/spec/ruby/core/float/to_r_spec.rb
new file mode 100644
index 0000000000..907ff08f27
--- /dev/null
+++ b/spec/ruby/core/float/to_r_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "Float#to_r" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/float/to_s_spec.rb b/spec/ruby/core/float/to_s_spec.rb
new file mode 100644
index 0000000000..6727a883f8
--- /dev/null
+++ b/spec/ruby/core/float/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Float#to_s" do
+ it_behaves_like :float_to_s, :to_s
+end
diff --git a/spec/ruby/core/float/truncate_spec.rb b/spec/ruby/core/float/truncate_spec.rb
new file mode 100644
index 0000000000..1750e3fdbc
--- /dev/null
+++ b/spec/ruby/core/float/truncate_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Float#truncate" do
+ it_behaves_like :float_to_i, :truncate
+
+ it "returns self truncated to an optionally given precision" do
+ 2.1679.truncate(0).should.eql?(2)
+ 7.1.truncate(1).should.eql?(7.1)
+ 214.94.truncate(-1).should.eql?(210)
+ -1.234.truncate(2).should.eql?(-1.23)
+ 5.123812.truncate(4).should.eql?(5.1238)
+ end
+end
diff --git a/spec/ruby/core/float/uminus_spec.rb b/spec/ruby/core/float/uminus_spec.rb
new file mode 100644
index 0000000000..57bae0fb4b
--- /dev/null
+++ b/spec/ruby/core/float/uminus_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../spec_helper'
+
+describe "Float#-@" do
+ it "negates self" do
+ (2.221.send(:-@)).should be_close(-2.221, TOLERANCE)
+ -2.01.should be_close(-2.01,TOLERANCE)
+ -2_455_999_221.5512.should be_close(-2455999221.5512, TOLERANCE)
+ (--5.5).should be_close(5.5, TOLERANCE)
+ -8.551.send(:-@).should be_close(8.551, TOLERANCE)
+ end
+
+ it "negates self at Float boundaries" do
+ Float::MAX.send(:-@).should be_close(0.0 - Float::MAX, TOLERANCE)
+ Float::MIN.send(:-@).should be_close(0.0 - Float::MIN, TOLERANCE)
+ end
+
+ it "returns negative infinity for positive infinity" do
+ infinity_value.send(:-@).infinite?.should == -1
+ end
+
+ it "returns positive infinity for negative infinity" do
+ (-infinity_value).send(:-@).infinite?.should == 1
+ end
+
+ it "returns NaN for NaN" do
+ nan_value.send(:-@).should.nan?
+ end
+end
diff --git a/spec/ruby/core/float/uplus_spec.rb b/spec/ruby/core/float/uplus_spec.rb
new file mode 100644
index 0000000000..b979b2717a
--- /dev/null
+++ b/spec/ruby/core/float/uplus_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Float#+@" do
+ it "returns the same value with same sign (twos complement)" do
+ 34.56.send(:+@).should == 34.56
+ -34.56.send(:+@).should == -34.56
+ 0.0.send(:+@).should.eql?(0.0)
+ end
+end
diff --git a/spec/ruby/core/float/zero_spec.rb b/spec/ruby/core/float/zero_spec.rb
new file mode 100644
index 0000000000..1f3de27793
--- /dev/null
+++ b/spec/ruby/core/float/zero_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+
+describe "Float#zero?" do
+ it "returns true if self is 0.0" do
+ 0.0.should.zero?
+ 1.0.should_not.zero?
+ -1.0.should_not.zero?
+ end
+end
diff --git a/spec/ruby/core/gc/auto_compact_spec.rb b/spec/ruby/core/gc/auto_compact_spec.rb
new file mode 100644
index 0000000000..33ad1cb56c
--- /dev/null
+++ b/spec/ruby/core/gc/auto_compact_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "GC.auto_compact" do
+ it "can set and get a boolean value" do
+ begin
+ GC.auto_compact = GC.auto_compact
+ rescue NotImplementedError # platform does not support autocompact
+ skip
+ end
+
+ original = GC.auto_compact
+ begin
+ GC.auto_compact = !original
+ rescue NotImplementedError # platform does not support autocompact
+ skip
+ end
+
+ begin
+ GC.auto_compact.should == !original
+ ensure
+ GC.auto_compact = original
+ end
+ end
+end
diff --git a/spec/ruby/core/gc/config_spec.rb b/spec/ruby/core/gc/config_spec.rb
new file mode 100644
index 0000000000..57160f122c
--- /dev/null
+++ b/spec/ruby/core/gc/config_spec.rb
@@ -0,0 +1,97 @@
+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.is_a?(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.is_a?(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
+
+ ruby_version_is ""..."4.0" do
+ it "returns the same as GC.config but without the :implementation key" do
+ previous = GC.config
+ GC.config({}).should == previous.except(:implementation)
+ end
+ end
+
+ ruby_version_is "4.0" do
+ it "returns the same as GC.config, including the :implementation key" do
+ previous = GC.config
+ GC.config({}).should == previous
+ end
+ end
+
+ it "raises an ArgumentError if options include global keys" do
+ -> { GC.config(implementation: "default") }.should.raise(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(ArgumentError)
+ -> { GC.config("default") }.should.raise(ArgumentError)
+ -> { GC.config(1) }.should.raise(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 == 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/count_spec.rb b/spec/ruby/core/gc/count_spec.rb
new file mode 100644
index 0000000000..fe20c4ed2b
--- /dev/null
+++ b/spec/ruby/core/gc/count_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "GC.count" do
+ it "returns an integer" do
+ GC.count.should.is_a?(Integer)
+ end
+
+ it "increases as collections are run" do
+ count_before = GC.count
+ i = 0
+ while GC.count <= count_before and i < 10
+ GC.start
+ i += 1
+ end
+ GC.count.should > count_before
+ end
+end
diff --git a/spec/ruby/core/gc/disable_spec.rb b/spec/ruby/core/gc/disable_spec.rb
new file mode 100644
index 0000000000..f89a9d2768
--- /dev/null
+++ b/spec/ruby/core/gc/disable_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "GC.disable" do
+ after :each do
+ GC.enable
+ end
+
+ it "returns true if and only if the garbage collection was previously disabled" do
+ GC.enable
+ GC.disable.should == false
+ GC.disable.should == true
+ GC.disable.should == true
+ GC.enable
+ GC.disable.should == false
+ GC.disable.should == true
+ end
+
+end
diff --git a/spec/ruby/core/gc/enable_spec.rb b/spec/ruby/core/gc/enable_spec.rb
new file mode 100644
index 0000000000..ca4488547a
--- /dev/null
+++ b/spec/ruby/core/gc/enable_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "GC.enable" do
+
+ it "returns true if and only if the garbage collection was already disabled" do
+ GC.enable
+ GC.enable.should == false
+ GC.disable
+ GC.enable.should == true
+ GC.enable.should == false
+ end
+
+end
diff --git a/spec/ruby/core/gc/garbage_collect_spec.rb b/spec/ruby/core/gc/garbage_collect_spec.rb
new file mode 100644
index 0000000000..f67f0486c8
--- /dev/null
+++ b/spec/ruby/core/gc/garbage_collect_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "GC#garbage_collect" do
+
+ before :each do
+ @obj = Object.new
+ @obj.extend(GC)
+ end
+
+ it "always returns nil" do
+ @obj.garbage_collect.should == nil
+ @obj.garbage_collect.should == nil
+ end
+
+end
diff --git a/spec/ruby/core/gc/measure_total_time_spec.rb b/spec/ruby/core/gc/measure_total_time_spec.rb
new file mode 100644
index 0000000000..f5377c18fd
--- /dev/null
+++ b/spec/ruby/core/gc/measure_total_time_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "GC.measure_total_time" do
+ before :each do
+ @default = GC.measure_total_time
+ 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
+end
diff --git a/spec/ruby/core/gc/profiler/clear_spec.rb b/spec/ruby/core/gc/profiler/clear_spec.rb
new file mode 100644
index 0000000000..95c3d4ed65
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/clear_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.clear" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/gc/profiler/disable_spec.rb b/spec/ruby/core/gc/profiler/disable_spec.rb
new file mode 100644
index 0000000000..74089693eb
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/disable_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.disable" do
+ before do
+ @status = GC::Profiler.enabled?
+ end
+
+ after do
+ @status ? GC::Profiler.enable : GC::Profiler.disable
+ end
+
+ it "disables the profiler" do
+ GC::Profiler.disable
+ GC::Profiler.should_not.enabled?
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/enable_spec.rb b/spec/ruby/core/gc/profiler/enable_spec.rb
new file mode 100644
index 0000000000..313ca3d949
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/enable_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.enable" do
+
+ before do
+ @status = GC::Profiler.enabled?
+ end
+
+ after do
+ @status ? GC::Profiler.enable : GC::Profiler.disable
+ end
+
+ it "enables the profiler" do
+ GC::Profiler.enable
+ GC::Profiler.should.enabled?
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/enabled_spec.rb b/spec/ruby/core/gc/profiler/enabled_spec.rb
new file mode 100644
index 0000000000..cfcd0951f0
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/enabled_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.enabled?" do
+ before do
+ @status = GC::Profiler.enabled?
+ end
+
+ after do
+ @status ? GC::Profiler.enable : GC::Profiler.disable
+ end
+
+ it "reports as enabled when enabled" do
+ GC::Profiler.enable
+ GC::Profiler.enabled?.should == true
+ end
+
+ it "reports as disabled when disabled" do
+ GC::Profiler.disable
+ GC::Profiler.enabled?.should == false
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/report_spec.rb b/spec/ruby/core/gc/profiler/report_spec.rb
new file mode 100644
index 0000000000..732b1d0f51
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/report_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.report" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/gc/profiler/result_spec.rb b/spec/ruby/core/gc/profiler/result_spec.rb
new file mode 100644
index 0000000000..e888f975dc
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/result_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.result" do
+ it "returns a string" do
+ GC::Profiler.result.should.is_a?(String)
+ end
+end
diff --git a/spec/ruby/core/gc/profiler/total_time_spec.rb b/spec/ruby/core/gc/profiler/total_time_spec.rb
new file mode 100644
index 0000000000..a351e922af
--- /dev/null
+++ b/spec/ruby/core/gc/profiler/total_time_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../../spec_helper'
+
+describe "GC::Profiler.total_time" do
+ it "returns an float" do
+ GC::Profiler.total_time.should.is_a?(Float)
+ end
+end
diff --git a/spec/ruby/core/gc/start_spec.rb b/spec/ruby/core/gc/start_spec.rb
new file mode 100644
index 0000000000..c941058969
--- /dev/null
+++ b/spec/ruby/core/gc/start_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "GC.start" do
+ it "always returns nil" do
+ GC.start.should == nil
+ GC.start.should == nil
+ end
+
+ it "accepts keyword arguments" do
+ GC.start(full_mark: true, immediate_sweep: true).should == nil
+ end
+end
diff --git a/spec/ruby/core/gc/stat_spec.rb b/spec/ruby/core/gc/stat_spec.rb
new file mode 100644
index 0000000000..6e4800b3e3
--- /dev/null
+++ b/spec/ruby/core/gc/stat_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+
+describe "GC.stat" do
+ it "returns hash of values" do
+ stat = GC.stat
+ stat.should.is_a?(Hash)
+ stat.keys.should.include?(:count)
+ end
+
+ it "updates the given hash values" do
+ hash = { count: "hello", __other__: "world" }
+ stat = GC.stat(hash)
+
+ stat.should.is_a?(Hash)
+ stat.should.equal? hash
+ stat[:count].should.is_a?(Integer)
+ stat[:__other__].should == "world"
+ end
+
+ it "the values are all Integer since rb_gc_stat() returns size_t" do
+ GC.stat.values.each { |value| value.should.is_a?(Integer) }
+ end
+
+ it "can return a single value" do
+ GC.stat(:count).should.is_a?(Integer)
+ end
+
+ it "increases count after GC is run" do
+ count = GC.stat(:count)
+ GC.start
+ GC.stat(:count).should > count
+ end
+
+ it "increases major_gc_count after GC is run" do
+ count = GC.stat(:major_gc_count)
+ GC.start
+ GC.stat(:major_gc_count).should > count
+ end
+
+ it "provides some number for count" do
+ GC.stat(:count).should.is_a?(Integer)
+ GC.stat[:count].should.is_a?(Integer)
+ end
+
+ it "provides some number for heap_free_slots" do
+ GC.stat(:heap_free_slots).should.is_a?(Integer)
+ GC.stat[:heap_free_slots].should.is_a?(Integer)
+ end
+
+ it "provides some number for total_allocated_objects" do
+ GC.stat(:total_allocated_objects).should.is_a?(Integer)
+ GC.stat[:total_allocated_objects].should.is_a?(Integer)
+ end
+
+ it "raises an error if argument is not nil, a symbol, or a hash" do
+ -> { GC.stat(7) }.should.raise(TypeError, "non-hash or symbol given")
+ end
+
+ it "raises an error if an unknown key is given" do
+ -> { GC.stat(:foo) }.should.raise(ArgumentError, "unknown key: foo")
+ end
+end
diff --git a/spec/ruby/core/gc/stress_spec.rb b/spec/ruby/core/gc/stress_spec.rb
new file mode 100644
index 0000000000..227a795e7f
--- /dev/null
+++ b/spec/ruby/core/gc/stress_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+describe "GC.stress" do
+ after :each do
+ # make sure that we never leave these tests in stress enabled GC!
+ GC.stress = false
+ end
+
+ it "returns current status of GC stress mode" do
+ GC.stress.should == false
+ GC.stress = true
+ GC.stress.should == true
+ GC.stress = false
+ GC.stress.should == false
+ end
+end
+
+describe "GC.stress=" do
+ after :each do
+ GC.stress = false
+ end
+
+ it "sets the stress mode" do
+ GC.stress = true
+ GC.stress.should == true
+ end
+end
diff --git a/spec/ruby/core/gc/total_time_spec.rb b/spec/ruby/core/gc/total_time_spec.rb
new file mode 100644
index 0000000000..f10e0ad742
--- /dev/null
+++ b/spec/ruby/core/gc/total_time_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "GC.total_time" do
+ it "returns an Integer" do
+ GC.total_time.should.is_a?(Integer)
+ 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/allocate_spec.rb b/spec/ruby/core/hash/allocate_spec.rb
new file mode 100644
index 0000000000..a92accc161
--- /dev/null
+++ b/spec/ruby/core/hash/allocate_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+
+describe "Hash.allocate" do
+ it "returns an instance of Hash" do
+ hsh = Hash.allocate
+ hsh.should.instance_of?(Hash)
+ end
+
+ it "returns a fully-formed instance of Hash" do
+ hsh = Hash.allocate
+ hsh.size.should == 0
+ hsh[:a] = 1
+ hsh.should == { a: 1 }
+ end
+end
diff --git a/spec/ruby/core/hash/any_spec.rb b/spec/ruby/core/hash/any_spec.rb
new file mode 100644
index 0000000000..c26dfabde6
--- /dev/null
+++ b/spec/ruby/core/hash/any_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+describe "Hash#any?" do
+ describe 'with no block given' do
+ it "checks if there are any members of a Hash" do
+ empty_hash = {}
+ empty_hash.should_not.any?
+
+ hash_with_members = { 'key' => 'value' }
+ hash_with_members.should.any?
+ end
+ end
+
+ describe 'with a block given' do
+ it 'is false if the hash is empty' do
+ empty_hash = {}
+ empty_hash.any? {|k,v| 1 == 1 }.should == false
+ end
+
+ it 'is true if the block returns true for any member of the hash' do
+ hash_with_members = { 'a' => false, 'b' => false, 'c' => true, 'd' => false }
+ hash_with_members.any? {|k,v| v == true}.should == true
+ end
+
+ it 'is false if the block returns false for all members of the hash' do
+ hash_with_members = { 'a' => false, 'b' => false, 'c' => true, 'd' => false }
+ hash_with_members.any? {|k,v| v == 42}.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/assoc_spec.rb b/spec/ruby/core/hash/assoc_spec.rb
new file mode 100644
index 0000000000..51af51a20f
--- /dev/null
+++ b/spec/ruby/core/hash/assoc_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+
+describe "Hash#assoc" do
+ before :each do
+ @h = {apple: :green, orange: :orange, grape: :green, banana: :yellow}
+ end
+
+ it "returns an Array if the argument is == to a key of the Hash" do
+ @h.assoc(:apple).should.instance_of?(Array)
+ end
+
+ it "returns a 2-element Array if the argument is == to a key of the Hash" do
+ @h.assoc(:grape).size.should == 2
+ end
+
+ it "sets the first element of the Array to the located key" do
+ @h.assoc(:banana).first.should == :banana
+ end
+
+ it "sets the last element of the Array to the value of the located key" do
+ @h.assoc(:banana).last.should == :yellow
+ end
+
+ it "only returns the first matching key-value pair for identity hashes" do
+ # Avoid literal String keys since string literals can be frozen and interned e.g. with --enable-frozen-string-literal
+ h = {}.compare_by_identity
+ k1 = 'pear'.dup
+ h[k1] = :red
+ k2 = 'pear'.dup
+ h[k2] = :green
+ h.size.should == 2
+ h.keys.grep(/pear/).size.should == 2
+ h.assoc('pear').should == ['pear', :red]
+ end
+
+ it "uses #== to compare the argument to the keys" do
+ @h[1.0] = :value
+ 1.should == 1.0
+ @h.assoc(1).should == [1.0, :value]
+ end
+
+ it "returns nil if the argument is not a key of the Hash" do
+ @h.assoc(:green).should == nil
+ end
+
+ it "returns nil if the argument is not a key of the Hash even when there is a default" do
+ Hash.new(42).merge!( foo: :bar ).assoc(42).should == nil
+ Hash.new{|h, k| h[k] = 42}.merge!( foo: :bar ).assoc(42).should == nil
+ end
+end
diff --git a/spec/ruby/core/hash/clear_spec.rb b/spec/ruby/core/hash/clear_spec.rb
new file mode 100644
index 0000000000..beff925a63
--- /dev/null
+++ b/spec/ruby/core/hash/clear_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#clear" do
+ it "removes all key, value pairs" do
+ h = { 1 => 2, 3 => 4 }
+ h.clear.should.equal?(h)
+ h.should == {}
+ end
+
+ it "does not remove default values" do
+ h = Hash.new(5)
+ h.clear
+ h.default.should == 5
+
+ h = { "a" => 100, "b" => 200 }
+ h.default = "Go fish"
+ h.clear
+ h["z"].should == "Go fish"
+ end
+
+ it "does not remove default procs" do
+ h = Hash.new { 5 }
+ h.clear
+ h.default_proc.should_not == nil
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.clear }.should.raise(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.clear }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/clone_spec.rb b/spec/ruby/core/hash/clone_spec.rb
new file mode 100644
index 0000000000..d135a84453
--- /dev/null
+++ b/spec/ruby/core/hash/clone_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "Hash#clone" do
+ it "copies instance variable but not the objects they refer to" do
+ hash = { 'key' => 'value' }
+
+ clone = hash.clone
+
+ clone.should == hash
+ clone.should_not.equal? hash
+ end
+end
diff --git a/spec/ruby/core/hash/compact_spec.rb b/spec/ruby/core/hash/compact_spec.rb
new file mode 100644
index 0000000000..de83dabed3
--- /dev/null
+++ b/spec/ruby/core/hash/compact_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#compact" do
+ before :each do
+ @hash = { truthy: true, false: false, nil: nil, nil => true }
+ @initial_pairs = @hash.dup
+ @compact = { truthy: true, false: false, nil => true }
+ end
+
+ it "returns new object that rejects pair has nil value" do
+ ret = @hash.compact
+ ret.should_not.equal?(@hash)
+ ret.should == @compact
+ end
+
+ it "keeps own pairs" do
+ @hash.compact
+ @hash.should == @initial_pairs
+ end
+
+ it "retains the default value" do
+ hash = Hash.new(1)
+ hash.compact.default.should == 1
+ hash[:a] = 1
+ hash.compact.default.should == 1
+ end
+
+ it "retains the default_proc" do
+ pr = proc { |h, k| h[k] = [] }
+ hash = Hash.new(&pr)
+ hash.compact.default_proc.should == pr
+ hash[:a] = 1
+ hash.compact.default_proc.should == pr
+ end
+
+ it "retains compare_by_identity flag" do
+ hash = {}.compare_by_identity
+ hash.compact.compare_by_identity?.should == true
+ hash[:a] = 1
+ hash.compact.compare_by_identity?.should == true
+ end
+end
+
+describe "Hash#compact!" do
+ before :each do
+ @hash = { truthy: true, false: false, nil: nil, nil => true }
+ @initial_pairs = @hash.dup
+ @compact = { truthy: true, false: false, nil => true }
+ end
+
+ it "returns self" do
+ @hash.compact!.should.equal?(@hash)
+ end
+
+ it "rejects own pair has nil value" do
+ @hash.compact!
+ @hash.should == @compact
+ end
+
+ context "when each pair does not have nil value" do
+ before :each do
+ @hash.compact!
+ end
+
+ it "returns nil" do
+ @hash.compact!.should == nil
+ end
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @hash.freeze
+ end
+
+ it "keeps pairs and raises a FrozenError" do
+ ->{ @hash.compact! }.should.raise(FrozenError)
+ @hash.should == @initial_pairs
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/compare_by_identity_spec.rb b/spec/ruby/core/hash/compare_by_identity_spec.rb
new file mode 100644
index 0000000000..1abf9d5666
--- /dev/null
+++ b/spec/ruby/core/hash/compare_by_identity_spec.rb
@@ -0,0 +1,147 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#compare_by_identity" do
+ before :each do
+ @h = {}
+ @idh = {}.compare_by_identity
+ end
+
+ it "causes future comparisons on the receiver to be made by identity" do
+ @h[[1]] = :a
+ @h[[1]].should == :a
+ @h.compare_by_identity
+ @h[[1].dup].should == nil
+ end
+
+ it "rehashes internally so that old keys can be looked up" do
+ h = {}
+ (1..10).each { |k| h[k] = k }
+ o = Object.new
+ def o.hash; 123; end
+ h[o] = 1
+ h.compare_by_identity
+ h[o].should == 1
+ end
+
+ it "returns self" do
+ h = {}
+ h[:foo] = :bar
+ h.compare_by_identity.should.equal? h
+ end
+
+ it "has no effect on an already compare_by_identity hash" do
+ @idh[:foo] = :bar
+ @idh.compare_by_identity.should.equal? @idh
+ @idh.should.compare_by_identity?
+ @idh[:foo].should == :bar
+ end
+
+ it "uses the semantics of BasicObject#equal? to determine key identity" do
+ [1].should_not.equal?([1])
+ @idh[[1]] = :c
+ @idh[[1]] = :d
+ :bar.should.equal?(:bar)
+ @idh[:bar] = :e
+ @idh[:bar] = :f
+ @idh.values.should == [:c, :d, :f]
+ end
+
+ it "uses #equal? semantics, but doesn't actually call #equal? to determine identity" do
+ obj = mock('equal')
+ obj.should_not_receive(:equal?)
+ @idh[:foo] = :glark
+ @idh[obj] = :a
+ @idh[obj].should == :a
+ end
+
+ it "does not call #hash on keys" do
+ key = HashSpecs::ByIdentityKey.new
+ @idh[key] = 1
+ @idh[key].should == 1
+ end
+
+ it "regards #dup'd objects as having different identities" do
+ key = ['foo']
+ @idh[key.dup] = :str
+ @idh[key].should == nil
+ end
+
+ it "regards #clone'd objects as having different identities" do
+ key = ['foo']
+ @idh[key.clone] = :str
+ @idh[key].should == nil
+ end
+
+ it "regards references to the same object as having the same identity" do
+ o = Object.new
+ @h[o] = :o
+ @h[:a] = :a
+ @h[o].should == :o
+ end
+
+ it "raises a FrozenError on frozen hashes" do
+ @h = @h.freeze
+ -> { @h.compare_by_identity }.should.raise(FrozenError)
+ end
+
+ # Behaviour confirmed in https://bugs.ruby-lang.org/issues/1871
+ it "persists over #dups" do
+ @idh['foo'.dup] = :bar
+ @idh['foo'.dup] = :glark
+ @idh.dup.should == @idh
+ @idh.dup.size.should == @idh.size
+ @idh.dup.should.compare_by_identity?
+ end
+
+ it "persists over #clones" do
+ @idh['foo'.dup] = :bar
+ @idh['foo'.dup] = :glark
+ @idh.clone.should == @idh
+ @idh.clone.size.should == @idh.size
+ @idh.dup.should.compare_by_identity?
+ end
+
+ it "does not copy string keys" do
+ foo = 'foo'
+ @idh[foo] = true
+ @idh[foo] = true
+ @idh.size.should == 1
+ @idh.keys.first.should.equal? foo
+ end
+
+ # Check `#[]=` call with a String literal.
+ # Don't use `#+` because with `#+` it's no longer a String literal.
+ #
+ # See https://bugs.ruby-lang.org/issues/12855
+ it "gives different identity for string literals" do
+ eval <<~RUBY
+ # frozen_string_literal: false
+ @idh['foo'] = 1
+ @idh['foo'] = 2
+ RUBY
+ @idh.values.should == [1, 2]
+ @idh.size.should == 2
+ end
+end
+
+describe "Hash#compare_by_identity?" do
+ it "returns false by default" do
+ h = {}
+ h.compare_by_identity?.should == false
+ end
+
+ it "returns true once #compare_by_identity has been invoked on self" do
+ h = {}
+ h.compare_by_identity
+ h.compare_by_identity?.should == true
+ end
+
+ it "returns true when called multiple times on the same ident hash" do
+ h = {}
+ h.compare_by_identity
+ h.compare_by_identity?.should == true
+ h.compare_by_identity?.should == true
+ h.compare_by_identity?.should == true
+ end
+end
diff --git a/spec/ruby/core/hash/constructor_spec.rb b/spec/ruby/core/hash/constructor_spec.rb
new file mode 100644
index 0000000000..c16f373160
--- /dev/null
+++ b/spec/ruby/core/hash/constructor_spec.rb
@@ -0,0 +1,127 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash.[]" do
+ describe "passed zero arguments" do
+ it "returns an empty hash" do
+ Hash[].should == {}
+ end
+ end
+
+ it "creates a Hash; values can be provided as the argument list" do
+ Hash[:a, 1, :b, 2].should == { a: 1, b: 2 }
+ Hash[].should == {}
+ Hash[:a, 1, :b, { c: 2 }].should == { a: 1, b: { c: 2 } }
+ end
+
+ it "creates a Hash; values can be provided as one single hash" do
+ Hash[a: 1, b: 2].should == { a: 1, b: 2 }
+ Hash[{1 => 2, 3 => 4}].should == {1 => 2, 3 => 4}
+ Hash[{}].should == {}
+ end
+
+ describe "passed an array" do
+ it "treats elements that are 2 element arrays as key and value" do
+ Hash[[[:a, :b], [:c, :d]]].should == { a: :b, c: :d }
+ end
+
+ it "treats elements that are 1 element arrays as keys with value nil" do
+ Hash[[[:a]]].should == { a: nil }
+ end
+ end
+
+ # #1000 #1385
+ it "creates a Hash; values can be provided as a list of value-pairs in an array" do
+ Hash[[[:a, 1], [:b, 2]]].should == { a: 1, b: 2 }
+ end
+
+ it "coerces a single argument which responds to #to_ary" do
+ ary = mock('to_ary')
+ ary.should_receive(:to_ary).and_return([[:a, :b]])
+
+ Hash[ary].should == { a: :b }
+ end
+
+ it "raises for elements that are not arrays" do
+ -> {
+ Hash[[:a]]
+ }.should.raise(ArgumentError, "wrong element type Symbol at 0 (expected array)")
+ -> {
+ Hash[[nil]]
+ }.should.raise(ArgumentError, "wrong element type nil at 0 (expected array)")
+ end
+
+ it "raises an ArgumentError for arrays of more than 2 elements" do
+ ->{
+ Hash[[[:a, :b, :c]]]
+ }.should.raise(ArgumentError, "invalid number of elements (3 for 1..2)")
+ end
+
+ it "raises an ArgumentError when passed a list of value-invalid-pairs in an array" do
+ -> {
+ Hash[[[:a, 1], [:b], 42, [:d, 2], [:e, 2, 3], []]]
+ }.should.raise(ArgumentError, "wrong element type Integer at 2 (expected array)")
+ end
+
+ describe "passed a single argument which responds to #to_hash" do
+ it "coerces it and returns a copy" do
+ h = { a: :b, c: :d }
+ to_hash = mock('to_hash')
+ to_hash.should_receive(:to_hash).and_return(h)
+
+ result = Hash[to_hash]
+ result.should == h
+ result.should_not.equal?(h)
+ end
+ end
+
+ it "raises an ArgumentError when passed an odd number of arguments" do
+ -> { Hash[1, 2, 3] }.should.raise(ArgumentError)
+ -> { Hash[1, 2, { 3 => 4 }] }.should.raise(ArgumentError)
+ end
+
+ it "calls to_hash" do
+ obj = mock('x')
+ def obj.to_hash() { 1 => 2, 3 => 4 } end
+ Hash[obj].should == { 1 => 2, 3 => 4 }
+ end
+
+ it "returns an instance of a subclass when passed an Array" do
+ HashSpecs::MyHash[1,2,3,4].should.instance_of?(HashSpecs::MyHash)
+ end
+
+ it "returns instances of subclasses" do
+ HashSpecs::MyHash[].should.instance_of?(HashSpecs::MyHash)
+ end
+
+ it "returns an instance of the class it's called on" do
+ Hash[HashSpecs::MyHash[1, 2]].class.should == Hash
+ HashSpecs::MyHash[Hash[1, 2]].should.instance_of?(HashSpecs::MyHash)
+ end
+
+ it "does not call #initialize on the subclass instance" do
+ HashSpecs::MyInitializerHash[Hash[1, 2]].should.instance_of?(HashSpecs::MyInitializerHash)
+ end
+
+ it "does not retain the default value" do
+ hash = Hash.new(1)
+ Hash[hash].default.should == nil
+ hash[:a] = 1
+ Hash[hash].default.should == nil
+ end
+
+ it "does not retain the default_proc" do
+ hash = Hash.new { |h, k| h[k] = [] }
+ Hash[hash].default_proc.should == nil
+ hash[:a] = 1
+ Hash[hash].default_proc.should == nil
+ end
+
+ it "does not retain compare_by_identity flag" do
+ hash = { a: 1 }.compare_by_identity
+ Hash[hash].compare_by_identity?.should == false
+
+ hash = {}.compare_by_identity
+ Hash[hash].compare_by_identity?.should == false
+ end
+end
diff --git a/spec/ruby/core/hash/deconstruct_keys_spec.rb b/spec/ruby/core/hash/deconstruct_keys_spec.rb
new file mode 100644
index 0000000000..f6ff3a2818
--- /dev/null
+++ b/spec/ruby/core/hash/deconstruct_keys_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Hash#deconstruct_keys" do
+ it "returns self" do
+ hash = {a: 1, b: 2}
+
+ hash.deconstruct_keys([:a, :b]).should.equal? hash
+ end
+
+ it "requires one argument" do
+ -> {
+ {a: 1}.deconstruct_keys
+ }.should.raise(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/)
+ end
+
+ it "ignores argument" do
+ hash = {a: 1, b: 2}
+
+ hash.deconstruct_keys([:a]).should == {a: 1, b: 2}
+ hash.deconstruct_keys(0 ).should == {a: 1, b: 2}
+ hash.deconstruct_keys('' ).should == {a: 1, b: 2}
+ end
+end
diff --git a/spec/ruby/core/hash/default_proc_spec.rb b/spec/ruby/core/hash/default_proc_spec.rb
new file mode 100644
index 0000000000..4830e02acd
--- /dev/null
+++ b/spec/ruby/core/hash/default_proc_spec.rb
@@ -0,0 +1,80 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#default_proc" do
+ it "returns the block passed to Hash.new" do
+ h = Hash.new { 'Paris' }
+ p = h.default_proc
+ p.call(1).should == 'Paris'
+ end
+
+ it "returns nil if no block was passed to proc" do
+ {}.default_proc.should == nil
+ end
+end
+
+describe "Hash#default_proc=" do
+ it "replaces the block passed to Hash.new" do
+ h = Hash.new { 'Paris' }
+ h.default_proc = Proc.new { 'Montreal' }
+ p = h.default_proc
+ p.call(1).should == 'Montreal'
+ end
+
+ it "uses :to_proc on its argument" do
+ h = Hash.new { 'Paris' }
+ obj = mock('to_proc')
+ obj.should_receive(:to_proc).and_return(Proc.new { 'Montreal' })
+ (h.default_proc = obj).should.equal?(obj)
+ h[:cool_city].should == 'Montreal'
+ end
+
+ it "overrides the static default" do
+ h = Hash.new(42)
+ h.default_proc = Proc.new { 6 }
+ h.default.should == nil
+ h.default_proc.call.should == 6
+ end
+
+ it "raises an error if passed stuff not convertible to procs" do
+ ->{{}.default_proc = 42}.should.raise(TypeError)
+ end
+
+ it "returns the passed Proc" do
+ new_proc = Proc.new {}
+ ({}.default_proc = new_proc).should.equal?(new_proc)
+ end
+
+ it "clears the default proc if passed nil" do
+ h = Hash.new { 'Paris' }
+ h.default_proc = nil
+ h.default_proc.should == nil
+ h[:city].should == nil
+ end
+
+ it "returns nil if passed nil" do
+ ({}.default_proc = nil).should == nil
+ end
+
+ it "accepts a lambda with an arity of 2" do
+ h = {}
+ -> do
+ h.default_proc = -> a, b { }
+ end.should_not.raise(TypeError)
+ end
+
+ it "raises a TypeError if passed a lambda with an arity other than 2" do
+ h = {}
+ -> do
+ h.default_proc = -> a { }
+ end.should.raise(TypeError)
+ -> do
+ h.default_proc = -> a, b, c { }
+ end.should.raise(TypeError)
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ -> { {}.freeze.default_proc = Proc.new {} }.should.raise(FrozenError)
+ -> { {}.freeze.default_proc = nil }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/default_spec.rb b/spec/ruby/core/hash/default_spec.rb
new file mode 100644
index 0000000000..17ee032e2b
--- /dev/null
+++ b/spec/ruby/core/hash/default_spec.rb
@@ -0,0 +1,46 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#default" do
+ it "returns the default value" do
+ h = Hash.new(5)
+ h.default.should == 5
+ h.default(4).should == 5
+ {}.default.should == nil
+ {}.default(4).should == nil
+ end
+
+ it "uses the default proc to compute a default value, passing given key" do
+ h = Hash.new { |*args| args }
+ h.default(nil).should == [h, nil]
+ h.default(5).should == [h, 5]
+ end
+
+ it "calls default proc with nil arg if passed a default proc but no arg" do
+ h = Hash.new { |*args| args }
+ h.default.should == nil
+ end
+end
+
+describe "Hash#default=" do
+ it "sets the default value" do
+ h = {}
+ h.default = 99
+ h.default.should == 99
+ end
+
+ it "unsets the default proc" do
+ [99, nil, -> { 6 }].each do |default|
+ h = Hash.new { 5 }
+ h.default_proc.should_not == nil
+ h.default = default
+ h.default.should == default
+ h.default_proc.should == nil
+ end
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.default = nil }.should.raise(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.default = nil }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/delete_if_spec.rb b/spec/ruby/core/hash/delete_if_spec.rb
new file mode 100644
index 0000000000..3090633d88
--- /dev/null
+++ b/spec/ruby/core/hash/delete_if_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#delete_if" do
+ it "yields two arguments: key and value" do
+ all_args = []
+ { 1 => 2, 3 => 4 }.delete_if { |*args| all_args << args }
+ all_args.sort.should == [[1, 2], [3, 4]]
+ end
+
+ it "removes every entry for which block is true and returns self" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+ h.delete_if { |k,v| v % 2 == 1 }.should.equal?(h)
+ h.should == { b: 2, d: 4 }
+ end
+
+ it "removes all entries if the block is true" do
+ h = { a: 1, b: 2, c: 3 }
+ h.delete_if { |k,v| true }.should.equal?(h)
+ h.should == {}
+ end
+
+ it "processes entries with the same order as each()" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+
+ each_pairs = []
+ delete_pairs = []
+
+ h.each_pair { |k,v| each_pairs << [k, v] }
+ h.delete_if { |k,v| delete_pairs << [k,v] }
+
+ each_pairs.should == delete_pairs
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.delete_if { false } }.should.raise(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.delete_if { true } }.should.raise(FrozenError)
+ end
+
+ it_behaves_like :hash_iteration_no_block, :delete_if
+ it_behaves_like :enumeratorized_with_origin_size, :delete_if, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/delete_spec.rb b/spec/ruby/core/hash/delete_spec.rb
new file mode 100644
index 0000000000..33ff01bf36
--- /dev/null
+++ b/spec/ruby/core/hash/delete_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#delete" do
+ it "removes the entry and returns the deleted value" do
+ h = { a: 5, b: 2 }
+ h.delete(:b).should == 2
+ h.should == { a: 5 }
+ end
+
+ it "calls supplied block if the key is not found" do
+ { a: 1, b: 10, c: 100 }.delete(:d) { 5 }.should == 5
+ Hash.new(:default).delete(:d) { 5 }.should == 5
+ Hash.new { :default }.delete(:d) { 5 }.should == 5
+ end
+
+ it "returns nil if the key is not found when no block is given" do
+ { a: 1, b: 10, c: 100 }.delete(:d).should == nil
+ Hash.new(:default).delete(:d).should == nil
+ Hash.new { :default }.delete(:d).should == nil
+ end
+
+ # MRI explicitly implements this behavior
+ it "allows removing a key while iterating" do
+ h = { a: 1, b: 2 }
+ visited = []
+ h.each_pair { |k, v|
+ visited << k
+ h.delete(k)
+ }
+ visited.should == [:a, :b]
+ h.should == {}
+ end
+
+ it "allows removing a key while iterating for big hashes" do
+ h = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10,
+ k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20,
+ u: 21, v: 22, w: 23, x: 24, y: 25, z: 26 }
+ visited = []
+ h.each_pair { |k, v|
+ visited << k
+ h.delete(k)
+ }
+ visited.should == [:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m,
+ :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z]
+ h.should == {}
+ end
+
+ it "accepts keys with private #hash method" do
+ key = HashSpecs::KeyWithPrivateHash.new
+ { key => 5 }.delete(key).should == 5
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.delete("foo") }.should.raise(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.delete("foo") }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/dig_spec.rb b/spec/ruby/core/hash/dig_spec.rb
new file mode 100644
index 0000000000..94ac94e850
--- /dev/null
+++ b/spec/ruby/core/hash/dig_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../spec_helper'
+
+describe "Hash#dig" do
+
+ it "returns #[] with one arg" do
+ h = { 0 => false, a: 1 }
+ h.dig(:a).should == 1
+ h.dig(0).should == false
+ h.dig(1).should == nil
+ end
+
+ it "returns the nested value specified by the sequence of keys" do
+ h = { foo: { bar: { baz: 1 } } }
+ h.dig(:foo, :bar, :baz).should == 1
+ h.dig(:foo, :bar, :nope).should == nil
+ h.dig(:foo, :baz).should == nil
+ h.dig(:bar, :baz, :foo).should == nil
+ end
+
+ it "returns the nested value specified if the sequence includes an index" do
+ h = { foo: [1, 2, 3] }
+ h.dig(:foo, 2).should == 3
+ end
+
+ it "returns nil if any intermediate step is nil" do
+ h = { foo: { bar: { baz: 1 } } }
+ h.dig(:foo, :zot, :xyz).should == nil
+ end
+
+ it "raises an ArgumentError if no arguments provided" do
+ -> { { the: 'borg' }.dig() }.should.raise(ArgumentError)
+ end
+
+ it "handles type-mixed deep digging" do
+ h = {}
+ h[:foo] = [ { bar: [ 1 ] }, [ obj = Object.new, 'str' ] ]
+ def obj.dig(*args); [ 42 ] end
+
+ h.dig(:foo, 0, :bar).should == [ 1 ]
+ h.dig(:foo, 0, :bar, 0).should == 1
+ h.dig(:foo, 1, 1).should == 'str'
+ # MRI does not recurse values returned from `obj.dig`
+ h.dig(:foo, 1, 0, 0).should == [ 42 ]
+ h.dig(:foo, 1, 0, 0, 10).should == [ 42 ]
+ end
+
+ it "raises TypeError if an intermediate element does not respond to #dig" do
+ h = {}
+ h[:foo] = [ { bar: [ 1 ] }, [ nil, 'str' ] ]
+ -> { h.dig(:foo, 0, :bar, 0, 0) }.should.raise(TypeError)
+ -> { h.dig(:foo, 1, 1, 0) }.should.raise(TypeError)
+ end
+
+ it "calls #dig on the result of #[] with the remaining arguments" do
+ h = { foo: { bar: { baz: 42 } } }
+ h[:foo].should_receive(:dig).with(:bar, :baz).and_return(42)
+ h.dig(:foo, :bar, :baz).should == 42
+ end
+
+ it "respects Hash's default" do
+ default = {bar: 42}
+ h = Hash.new(default)
+ h.dig(:foo).should.equal? default
+ h.dig(:foo, :bar).should == 42
+ end
+end
diff --git a/spec/ruby/core/hash/each_key_spec.rb b/spec/ruby/core/hash/each_key_spec.rb
new file mode 100644
index 0000000000..5b2e9deb63
--- /dev/null
+++ b/spec/ruby/core/hash/each_key_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#each_key" do
+ it "calls block once for each key, passing key" do
+ r = {}
+ h = { 1 => -1, 2 => -2, 3 => -3, 4 => -4 }
+ h.each_key { |k| r[k] = k }.should.equal?(h)
+ r.should == { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
+ end
+
+ it "processes keys in the same order as keys()" do
+ keys = []
+ h = { 1 => -1, 2 => -2, 3 => -3, 4 => -4 }
+ h.each_key { |k| keys << k }
+ keys.should == h.keys
+ end
+
+ it_behaves_like :hash_iteration_no_block, :each_key
+ it_behaves_like :enumeratorized_with_origin_size, :each_key, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/each_pair_spec.rb b/spec/ruby/core/hash/each_pair_spec.rb
new file mode 100644
index 0000000000..01810c130c
--- /dev/null
+++ b/spec/ruby/core/hash/each_pair_spec.rb
@@ -0,0 +1,111 @@
+require_relative '../../spec_helper'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#each_pair" do
+ it_behaves_like :hash_iteration_no_block, :each_pair
+ it_behaves_like :enumeratorized_with_origin_size, :each_pair, { 1 => 2, 3 => 4, 5 => 6 }
+
+ # This is inconsistent with below, MRI checks the block arity in rb_hash_each_pair()
+ it "yields a [[key, value]] Array for each pair to a block expecting |*args|" do
+ all_args = []
+ { 1 => 2, 3 => 4 }.each_pair { |*args| all_args << args }
+ all_args.sort.should == [[[1, 2]], [[3, 4]]]
+ end
+
+ it "yields the key and value of each pair to a block expecting |key, value|" do
+ r = {}
+ h = { a: 1, b: 2, c: 3, d: 5 }
+ h.each_pair { |k,v| r[k.to_s] = v.to_s }.should.equal?(h)
+ r.should == { "a" => "1", "b" => "2", "c" => "3", "d" => "5" }
+ end
+
+ it "yields the key only to a block expecting |key,|" do
+ ary = []
+ h = { "a" => 1, "b" => 2, "c" => 3 }
+ h.each_pair { |k,| ary << k }
+ ary.sort.should == ["a", "b", "c"]
+ end
+
+ it "always yields an Array of 2 elements, even when given a callable of arity 2" do
+ obj = Object.new
+ def obj.foo(key, value)
+ end
+
+ -> {
+ { "a" => 1 }.each_pair(&obj.method(:foo))
+ }.should.raise(ArgumentError)
+
+ -> {
+ { "a" => 1 }.each_pair(&-> key, value { })
+ }.should.raise(ArgumentError)
+ end
+
+ it "yields an Array of 2 elements when given a callable of arity 1" do
+ obj = Object.new
+ def obj.foo(key_value)
+ ScratchPad << key_value
+ end
+
+ ScratchPad.record([])
+ { "a" => 1 }.each_pair(&obj.method(:foo))
+ ScratchPad.recorded.should == [["a", 1]]
+ end
+
+ it "raises an error for a Hash when an arity enforcing callable of arity >2 is passed in" do
+ obj = Object.new
+ def obj.foo(key, value, extra)
+ end
+
+ -> {
+ { "a" => 1 }.each_pair(&obj.method(:foo))
+ }.should.raise(ArgumentError)
+ end
+
+ it "uses the same order as keys() and values()" do
+ h = { a: 1, b: 2, c: 3, d: 5 }
+ keys = []
+ values = []
+
+ h.each_pair do |k, v|
+ keys << k
+ values << v
+ end
+
+ keys.should == h.keys
+ values.should == h.values
+ end
+
+ # Confirming the argument-splatting works from child class for both k, v and [k, v]
+ it "properly expands (or not) child class's 'each'-yielded args" do
+ cls1 = Class.new(Hash) do
+ attr_accessor :k_v
+ def each
+ super do |k, v|
+ @k_v = [k, v]
+ yield k, v
+ end
+ end
+ end
+
+ cls2 = Class.new(Hash) do
+ attr_accessor :k_v
+ def each
+ super do |k, v|
+ @k_v = [k, v]
+ yield([k, v])
+ end
+ end
+ end
+
+ obj1 = cls1.new
+ obj1['a'] = 'b'
+ obj1.map {|k, v| [k, v]}.should == [['a', 'b']]
+ obj1.k_v.should == ['a', 'b']
+
+ obj2 = cls2.new
+ obj2['a'] = 'b'
+ obj2.map {|k, v| [k, v]}.should == [['a', 'b']]
+ obj2.k_v.should == ['a', 'b']
+ end
+end
diff --git a/spec/ruby/core/hash/each_spec.rb b/spec/ruby/core/hash/each_spec.rb
new file mode 100644
index 0000000000..1644b63216
--- /dev/null
+++ b/spec/ruby/core/hash/each_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Hash#each" do
+ it "is an alias of Hash#each_pair" do
+ Hash.instance_method(:each).should == Hash.instance_method(:each_pair)
+ end
+end
diff --git a/spec/ruby/core/hash/each_value_spec.rb b/spec/ruby/core/hash/each_value_spec.rb
new file mode 100644
index 0000000000..2df95a8789
--- /dev/null
+++ b/spec/ruby/core/hash/each_value_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#each_value" do
+ it "calls block once for each key, passing value" do
+ r = []
+ h = { a: -5, b: -3, c: -2, d: -1, e: -1 }
+ h.each_value { |v| r << v }.should.equal?(h)
+ r.sort.should == [-5, -3, -2, -1, -1]
+ end
+
+ it "processes values in the same order as values()" do
+ values = []
+ h = { a: -5, b: -3, c: -2, d: -1, e: -1 }
+ h.each_value { |v| values << v }
+ values.should == h.values
+ end
+
+ it_behaves_like :hash_iteration_no_block, :each_value
+ it_behaves_like :enumeratorized_with_origin_size, :each_value, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb
new file mode 100644
index 0000000000..3d074b346d
--- /dev/null
+++ b/spec/ruby/core/hash/element_reference_spec.rb
@@ -0,0 +1,134 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#[]" do
+ it "returns the value for key" do
+ obj = mock('x')
+ h = { 1 => 2, 3 => 4, "foo" => "bar", obj => obj, [] => "baz" }
+ h[1].should == 2
+ h[3].should == 4
+ h["foo"].should == "bar"
+ h[obj].should == obj
+ h[[]].should == "baz"
+ end
+
+ it "returns nil as default value" do
+ { 0 => 0 }[5].should == nil
+ end
+
+ it "returns the default (immediate) value for missing keys" do
+ h = Hash.new(5)
+ h[:a].should == 5
+ h[:a] = 0
+ h[:a].should == 0
+ h[:b].should == 5
+ end
+
+ it "calls subclass implementations of default" do
+ h = HashSpecs::DefaultHash.new
+ h[:nothing].should == 100
+ end
+
+ it "does not create copies of the immediate default value" do
+ str = +"foo"
+ h = Hash.new(str)
+ a = h[:a]
+ b = h[:b]
+ a << "bar"
+
+ a.should.equal?(b)
+ a.should == "foobar"
+ b.should == "foobar"
+ end
+
+ it "returns the default (dynamic) value for missing keys" do
+ h = Hash.new { |hsh, k| k.kind_of?(Numeric) ? hsh[k] = k + 2 : hsh[k] = k }
+ h[1].should == 3
+ h['this'].should == 'this'
+ h.should == { 1 => 3, 'this' => 'this' }
+
+ i = 0
+ h = Hash.new { |hsh, key| i += 1 }
+ h[:foo].should == 1
+ h[:foo].should == 2
+ h[:bar].should == 3
+ end
+
+ it "does not return default values for keys with nil values" do
+ h = Hash.new(5)
+ h[:a] = nil
+ h[:a].should == nil
+
+ h = Hash.new { 5 }
+ h[:a] = nil
+ h[:a].should == nil
+ end
+
+ it "compares keys with eql? semantics" do
+ { 1.0 => "x" }[1].should == nil
+ { 1.0 => "x" }[1.0].should == "x"
+ { 1 => "x" }[1.0].should == nil
+ { 1 => "x" }[1].should == "x"
+ end
+
+ it "compares key via hash" do
+ x = mock('0')
+ x.should_receive(:hash).and_return(0)
+
+ h = {}
+ # 1.9 only calls #hash if the hash had at least one entry beforehand.
+ h[:foo] = :bar
+ h[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)
+
+ { y => 1 }[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)
+
+ { y => 1 }[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.
+
+ { x => :x }[x].should == :x
+ end
+
+ it "supports keys with private #hash method" do
+ key = HashSpecs::KeyWithPrivateHash.new
+ { key => 42 }[key].should == 42
+ end
+
+ it "does not dispatch to hash for Boolean, Integer, Float, String, or Symbol" do
+ code = <<-EOC
+ load '#{fixture __FILE__, "name.rb"}'
+ hash = { true => 42, false => 42, 1 => 42, 2.0 => 42, "hello" => 42, :ok => 42 }
+ [true, false, 1, 2.0, "hello", :ok].each do |value|
+ raise "incorrect value" unless hash[value] == 42
+ end
+ puts "Ok."
+ EOC
+ result = ruby_exe(code, args: "2>&1")
+ result.should == "Ok.\n"
+ end
+
+end
diff --git a/spec/ruby/core/hash/element_set_spec.rb b/spec/ruby/core/hash/element_set_spec.rb
new file mode 100644
index 0000000000..bb805477ca
--- /dev/null
+++ b/spec/ruby/core/hash/element_set_spec.rb
@@ -0,0 +1,121 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#[]=" do
+ it "associates the key with the value" do
+ h = { a: 1 }
+ h[:b] = 2
+ h.should == { b:2, a:1 }
+ end
+
+ it "returns the assigned value" do
+ h = {}
+ h.send(:[]=, 1, 2).should == 2
+ end
+
+ it "duplicates string keys using dup semantics" do
+ # dup doesn't copy singleton methods
+ key = +"foo"
+ def key.reverse() "bar" end
+ h = {}
+ h[key] = 0
+ h.keys[0].reverse.should == "oof"
+ end
+
+ it "stores unequal keys that hash to the same value" do
+ h = {}
+ k1 = ["x"]
+ k2 = ["y"]
+ # So they end up in the same bucket
+ k1.should_receive(:hash).and_return(0)
+ k2.should_receive(:hash).and_return(0)
+
+ h[k1] = 1
+ h[k2] = 2
+ h.size.should == 2
+ end
+
+ it "accepts keys with private #hash method" do
+ key = HashSpecs::KeyWithPrivateHash.new
+ h = {}
+ h[key] = "foo"
+ h[key].should == "foo"
+ end
+
+ it " accepts keys with an Integer hash" do
+ o = mock(hash: 1 << 100)
+ h = {}
+ h[o] = 1
+ h[o].should == 1
+ end
+
+ it "duplicates and freezes string keys" do
+ key = +"foo"
+ h = {}
+ h[key] = 0
+ key << "bar"
+
+ h.should == { "foo" => 0 }
+ h.keys[0].should.frozen?
+ end
+
+ it "doesn't duplicate and freeze already frozen string keys" do
+ key = "foo".freeze
+ h = {}
+ h[key] = 0
+ h.keys[0].should.equal?(key)
+ end
+
+ it "keeps the existing key in the hash if there is a matching one" do
+ h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 }
+ key1 = HashSpecs::ByValueKey.new(13)
+ key2 = HashSpecs::ByValueKey.new(13)
+ h[key1] = 41
+ key_in_hash = h.keys.last
+ key_in_hash.should.equal?(key1)
+ h[key2] = 42
+ last_key = h.keys.last
+ last_key.should.equal?(key_in_hash)
+ last_key.should_not.equal?(key2)
+ end
+
+ it "keeps the existing String key in the hash if there is a matching one" do
+ h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 }
+ key1 = "foo".dup
+ key2 = "foo".dup
+ key1.should_not.equal?(key2)
+ h[key1] = 41
+ frozen_key = h.keys.last
+ frozen_key.should_not.equal?(key1)
+ h[key2] = 42
+ h.keys.last.should.equal?(frozen_key)
+ h.keys.last.should_not.equal?(key2)
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash[1] = 2 }.should.raise(FrozenError)
+ 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[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
+ code = <<-EOC
+ load '#{fixture __FILE__, "name.rb"}'
+ hash = {}
+ [true, false, 1, 2.0, "hello", :ok].each do |value|
+ hash[value] = 42
+ raise "incorrect value" unless hash[value] == 42
+ hash[value] = 43
+ raise "incorrect value" unless hash[value] == 43
+ end
+ puts "OK"
+ puts hash.size
+ EOC
+ result = ruby_exe(code, args: "2>&1")
+ result.should == "OK\n6\n"
+ end
+end
diff --git a/spec/ruby/core/hash/empty_spec.rb b/spec/ruby/core/hash/empty_spec.rb
new file mode 100644
index 0000000000..881e1cc34b
--- /dev/null
+++ b/spec/ruby/core/hash/empty_spec.rb
@@ -0,0 +1,15 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#empty?" do
+ it "returns true if the hash has no entries" do
+ {}.should.empty?
+ { 1 => 1 }.should_not.empty?
+ end
+
+ it "returns true if the hash has no entries and has a default value" do
+ Hash.new(5).should.empty?
+ Hash.new { 5 }.should.empty?
+ Hash.new { |hsh, k| hsh[k] = k }.should.empty?
+ end
+end
diff --git a/spec/ruby/core/hash/eql_spec.rb b/spec/ruby/core/hash/eql_spec.rb
new file mode 100644
index 0000000000..9013e12ffd
--- /dev/null
+++ b/spec/ruby/core/hash/eql_spec.rb
@@ -0,0 +1,9 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eql'
+
+describe "Hash#eql?" do
+ it_behaves_like :hash_eql, :eql?
+ it_behaves_like :hash_eql_additional, :eql?
+ it_behaves_like :hash_eql_additional_more, :eql?
+end
diff --git a/spec/ruby/core/hash/equal_value_spec.rb b/spec/ruby/core/hash/equal_value_spec.rb
new file mode 100644
index 0000000000..ab14f81489
--- /dev/null
+++ b/spec/ruby/core/hash/equal_value_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/eql'
+
+describe "Hash#==" do
+ it_behaves_like :hash_eql, :==
+ it_behaves_like :hash_eql_additional, :==
+ it_behaves_like :hash_eql_additional_more, :==
+
+ it "compares values with == semantics" do
+ l_val = mock("left")
+ r_val = mock("right")
+
+ l_val.should_receive(:==).with(r_val).and_return(true)
+
+ ({ 1 => l_val } == { 1 => r_val }).should == true
+ end
+end
diff --git a/spec/ruby/core/hash/except_spec.rb b/spec/ruby/core/hash/except_spec.rb
new file mode 100644
index 0000000000..77a020e9b7
--- /dev/null
+++ b/spec/ruby/core/hash/except_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+
+describe "Hash#except" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns a new duplicate hash without arguments" do
+ ret = @hash.except
+ ret.should_not.equal?(@hash)
+ ret.should == @hash
+ end
+
+ it "returns a hash without the requested subset" do
+ @hash.except(:c, :a).should == { b: 2 }
+ end
+
+ it "ignores keys not present in the original hash" do
+ @hash.except(:a, :chunky_bacon).should == { b: 2, c: 3 }
+ end
+
+ it "does not retain the default value" do
+ h = Hash.new(1)
+ h.except(:a).default.should == nil
+ h[:a] = 1
+ h.except(:a).default.should == 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 == nil
+ h[:a] = 1
+ h.except(:a).default_proc.should == 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/fetch_spec.rb b/spec/ruby/core/hash/fetch_spec.rb
new file mode 100644
index 0000000000..f9b80b6c49
--- /dev/null
+++ b/spec/ruby/core/hash/fetch_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/hash/key_error'
+
+describe "Hash#fetch" do
+ context "when the key is not found" do
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new({ a: 5 })
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, {}
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new { 5 }
+ it_behaves_like :key_error, -> obj, key { obj.fetch(key) }, Hash.new(5)
+
+ it "formats the object with #inspect in the KeyError message" do
+ -> {
+ {}.fetch('foo')
+ }.should.raise(KeyError, 'key not found: "foo"')
+ end
+ end
+
+ it "returns the value for key" do
+ { a: 1, b: -1 }.fetch(:b).should == -1
+ end
+
+ it "returns default if key is not found when passed a default" do
+ {}.fetch(:a, nil).should == nil
+ {}.fetch(:a, 'not here!').should == "not here!"
+ { a: nil }.fetch(:a, 'not here!').should == nil
+ end
+
+ it "returns value of block if key is not found when passed a block" do
+ {}.fetch('a') { |k| k + '!' }.should == "a!"
+ end
+
+ it "gives precedence to the default block over the default argument when passed both" do
+ -> {
+ @result = {}.fetch(9, :foo) { |i| i * i }
+ }.should complain(/block supersedes default value argument/)
+ @result.should == 81
+ end
+
+ it "raises an ArgumentError when not passed one or two arguments" do
+ -> { {}.fetch() }.should.raise(ArgumentError)
+ -> { {}.fetch(1, 2, 3) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/hash/fetch_values_spec.rb b/spec/ruby/core/hash/fetch_values_spec.rb
new file mode 100644
index 0000000000..0cd48565af
--- /dev/null
+++ b/spec/ruby/core/hash/fetch_values_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/hash/key_error'
+
+describe "Hash#fetch_values" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ describe "with matched keys" do
+ it "returns the values for keys" do
+ @hash.fetch_values(:a).should == [1]
+ @hash.fetch_values(:a, :c).should == [1, 3]
+ end
+
+ it "returns the values for keys ordered in the order of the requested keys" do
+ @hash.fetch_values(:c, :a).should == [3, 1]
+ end
+ end
+
+ describe "with unmatched keys" do
+ it_behaves_like :key_error, -> obj, key { obj.fetch_values(key) }, Hash.new({ a: 5 })
+
+ it "returns the default value from block" do
+ @hash.fetch_values(:z) { |key| "`#{key}' is not found" }.should == ["`z' is not found"]
+ @hash.fetch_values(:a, :z) { |key| "`#{key}' is not found" }.should == [1, "`z' is not found"]
+ end
+ end
+
+ describe "without keys" do
+ it "returns an empty Array" do
+ @hash.fetch_values.should == []
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/filter_spec.rb b/spec/ruby/core/hash/filter_spec.rb
new file mode 100644
index 0000000000..625a7feb90
--- /dev/null
+++ b/spec/ruby/core/hash/filter_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Hash#filter" do
+ it "is an alias of Hash#select" do
+ Hash.instance_method(:filter).should == Hash.instance_method(:select)
+ end
+end
+
+describe "Hash#filter!" do
+ it "is an alias of Hash#select!" do
+ Hash.instance_method(:filter!).should == Hash.instance_method(:select!)
+ end
+end
diff --git a/spec/ruby/core/hash/fixtures/classes.rb b/spec/ruby/core/hash/fixtures/classes.rb
new file mode 100644
index 0000000000..ae907aaff6
--- /dev/null
+++ b/spec/ruby/core/hash/fixtures/classes.rb
@@ -0,0 +1,75 @@
+module HashSpecs
+ class MyHash < Hash; end
+
+ class MyInitializerHash < Hash
+
+ def initialize
+ raise "Constructor called"
+ end
+
+ end
+
+ class NewHash < Hash
+ def initialize(*args)
+ args.each_with_index do |val, index|
+ self[index] = val
+ end
+ end
+ end
+
+ class SubHashSettingInInitialize < Hash
+ def initialize(*args, &block)
+ self[:foo] = :bar
+ super(*args, &block)
+ end
+ end
+
+ class DefaultHash < Hash
+ def default(key)
+ 100
+ end
+ end
+
+ class ToHashHash < Hash
+ def to_hash
+ { "to_hash" => "was", "called!" => "duh." }
+ end
+ end
+
+ class KeyWithPrivateHash
+ private :hash
+ end
+
+ class ByIdentityKey
+ def hash
+ fail("#hash should not be called on compare_by_identity Hash")
+ end
+ end
+
+ class ByValueKey
+ attr_reader :n
+ def initialize(n)
+ @n = n
+ end
+
+ def hash
+ n
+ end
+
+ def eql? other
+ ByValueKey === other and @n == other.n
+ end
+ end
+
+ def self.empty_frozen_hash
+ @empty ||= {}
+ @empty.freeze
+ @empty
+ end
+
+ def self.frozen_hash
+ @hash ||= { 1 => 2, 3 => 4 }
+ @hash.freeze
+ @hash
+ end
+end
diff --git a/spec/ruby/core/hash/fixtures/name.rb b/spec/ruby/core/hash/fixtures/name.rb
new file mode 100644
index 0000000000..b203bf6ae4
--- /dev/null
+++ b/spec/ruby/core/hash/fixtures/name.rb
@@ -0,0 +1,30 @@
+class TrueClass
+ def hash
+ raise "TrueClass#hash should not be called"
+ end
+end
+class FalseClass
+ def hash
+ raise "FalseClass#hash should not be called"
+ end
+end
+class Integer
+ def hash
+ raise "Integer#hash should not be called"
+ end
+end
+class Float
+ def hash
+ raise "Float#hash should not be called"
+ end
+end
+class String
+ def hash
+ raise "String#hash should not be called"
+ end
+end
+class Symbol
+ def hash
+ raise "Symbol#hash should not be called"
+ end
+end
diff --git a/spec/ruby/core/hash/flatten_spec.rb b/spec/ruby/core/hash/flatten_spec.rb
new file mode 100644
index 0000000000..18e9a7b56a
--- /dev/null
+++ b/spec/ruby/core/hash/flatten_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+
+describe "Hash#flatten" do
+
+ before :each do
+ @h = {plato: :greek,
+ witgenstein: [:austrian, :british],
+ russell: :welsh}
+ end
+
+ it "returns an Array" do
+ {}.flatten.should.instance_of?(Array)
+ end
+
+ it "returns an empty Array for an empty Hash" do
+ {}.flatten.should == []
+ end
+
+ it "sets each even index of the Array to a key of the Hash" do
+ a = @h.flatten
+ a[0].should == :plato
+ a[2].should == :witgenstein
+ a[4].should == :russell
+ end
+
+ it "sets each odd index of the Array to the value corresponding to the previous element" do
+ a = @h.flatten
+ a[1].should == :greek
+ a[3].should == [:austrian, :british]
+ a[5].should == :welsh
+ end
+
+ it "does not recursively flatten Array values when called without arguments" do
+ a = @h.flatten
+ a[3].should == [:austrian, :british]
+ end
+
+ it "does not recursively flatten Hash values when called without arguments" do
+ @h[:russell] = {born: :wales, influenced_by: :mill }
+ a = @h.flatten
+ a[5].should_not == {born: :wales, influenced_by: :mill }.flatten
+ end
+
+ it "recursively flattens Array values when called with an argument >= 2" do
+ a = @h.flatten(2)
+ a[3].should == :austrian
+ a[4].should == :british
+ end
+
+ it "recursively flattens Array values to the given depth" do
+ @h[:russell] = [[:born, :wales], [:influenced_by, :mill]]
+ a = @h.flatten(2)
+ a[6].should == [:born, :wales]
+ a[7].should == [:influenced_by, :mill]
+ end
+
+ it "raises a TypeError if given a non-Integer argument" do
+ -> do
+ @h.flatten(Object.new)
+ end.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/hash/gt_spec.rb b/spec/ruby/core/hash/gt_spec.rb
new file mode 100644
index 0000000000..69d23b7d29
--- /dev/null
+++ b/spec/ruby/core/hash/gt_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison'
+require_relative 'shared/greater_than'
+
+describe "Hash#>" do
+ it_behaves_like :hash_comparison, :>
+ it_behaves_like :hash_greater_than, :>
+
+ it "returns false if both hashes are identical" do
+ h = { a: 1, b: 2 }
+ (h > h).should == false
+ end
+end
+
+describe "Hash#>" do
+ before :each do
+ @hash = {a:1, b:2}
+ @bigger = {a:1, b:2, c:3}
+ @unrelated = {c:3, d:4}
+ @similar = {a:2, b:3}
+ end
+
+ it "returns false when receiver size is smaller than argument" do
+ (@hash > @bigger).should == false
+ (@unrelated > @bigger).should == false
+ end
+
+ it "returns false when receiver size is the same as argument" do
+ (@hash > @hash).should == false
+ (@hash > @unrelated).should == false
+ (@unrelated > @hash).should == false
+ end
+
+ it "returns true when argument is a subset of receiver" do
+ (@bigger > @hash).should == true
+ end
+
+ it "returns false when keys match but values don't" do
+ (@hash > @similar).should == false
+ (@similar > @hash).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/gte_spec.rb b/spec/ruby/core/hash/gte_spec.rb
new file mode 100644
index 0000000000..be5060ea4f
--- /dev/null
+++ b/spec/ruby/core/hash/gte_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison'
+require_relative 'shared/greater_than'
+
+describe "Hash#>=" do
+ it_behaves_like :hash_comparison, :>=
+ it_behaves_like :hash_greater_than, :>=
+
+ it "returns true if both hashes are identical" do
+ h = { a: 1, b: 2 }
+ (h >= h).should == true
+ end
+end
+
+describe "Hash#>=" do
+ before :each do
+ @hash = {a:1, b:2}
+ @bigger = {a:1, b:2, c:3}
+ @unrelated = {c:3, d:4}
+ @similar = {a:2, b:3}
+ end
+
+ it "returns false when receiver size is smaller than argument" do
+ (@hash >= @bigger).should == false
+ (@unrelated >= @bigger).should == false
+ end
+
+ it "returns false when argument is not a subset or not equals to receiver" do
+ (@hash >= @unrelated).should == false
+ (@unrelated >= @hash).should == false
+ end
+
+ it "returns true when argument is a subset of receiver or equals to receiver" do
+ (@bigger >= @hash).should == true
+ (@hash >= @hash).should == true
+ end
+
+ it "returns false when keys match but values don't" do
+ (@hash >= @similar).should == false
+ (@similar >= @hash).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/has_key_spec.rb b/spec/ruby/core/hash/has_key_spec.rb
new file mode 100644
index 0000000000..9a6244c6f6
--- /dev/null
+++ b/spec/ruby/core/hash/has_key_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Hash#has_key?" do
+ it "is an alias of Hash#include?" do
+ Hash.instance_method(:has_key?).should == Hash.instance_method(:include?)
+ end
+end
diff --git a/spec/ruby/core/hash/has_value_spec.rb b/spec/ruby/core/hash/has_value_spec.rb
new file mode 100644
index 0000000000..d40e52ebfd
--- /dev/null
+++ b/spec/ruby/core/hash/has_value_spec.rb
@@ -0,0 +1,16 @@
+require_relative '../../spec_helper'
+
+describe "Hash#has_value?" do
+ it "returns true if the value exists in the hash" do
+ { a: :b }.has_value?(:a).should == false
+ { 1 => 2 }.has_value?(2).should == true
+ h = Hash.new(5)
+ h.has_value?(5).should == false
+ h = Hash.new { 5 }
+ h.has_value?(5).should == false
+ end
+
+ it "uses == semantics for comparing values" do
+ { 5 => 2.0 }.has_value?(2).should == true
+ end
+end
diff --git a/spec/ruby/core/hash/hash_spec.rb b/spec/ruby/core/hash/hash_spec.rb
new file mode 100644
index 0000000000..cd67f7a652
--- /dev/null
+++ b/spec/ruby/core/hash/hash_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "Hash" do
+ it "includes Enumerable" do
+ Hash.include?(Enumerable).should == true
+ end
+end
+
+describe "Hash#hash" do
+ it "returns a value which doesn't depend on the hash order" do
+ { 0=>2, 11=>1 }.hash.should == { 11=>1, 0=>2 }.hash
+ end
+
+ it "returns a value in which element values do not cancel each other out" do
+ { a: 2, b: 2 }.hash.should_not == { a: 7, b: 7 }.hash
+ end
+
+ it "returns a value in which element keys and values do not cancel each other out" do
+ { :a => :a }.hash.should_not == { :b => :b }.hash
+ end
+
+ it "generates a hash for recursive hash structures" do
+ h = {}
+ h[:a] = h
+ (h.hash == h[:a].hash).should == true
+ end
+
+ it "returns the same hash for recursive hashes" do
+ h = {} ; h[:x] = h
+ h.hash.should == {x: h}.hash
+ h.hash.should == {x: {x: h}}.hash
+ # This is because h.eql?(x: h)
+ # Remember that if two objects are eql?
+ # then the need to have the same hash.
+ # Check the Hash#eql? specs!
+ end
+
+ it "returns the same hash for recursive hashes through arrays" do
+ h = {} ; rec = [h] ; h[:x] = rec
+ h.hash.should == {x: rec}.hash
+ h.hash.should == {x: [h]}.hash
+ # Like above, because h.eql?(x: [h])
+ end
+
+ it "allows omitting values" do
+ a = 1
+ b = 2
+
+ {a:, b:}.should == { a: 1, b: 2 }
+ end
+end
diff --git a/spec/ruby/core/hash/include_spec.rb b/spec/ruby/core/hash/include_spec.rb
new file mode 100644
index 0000000000..1c32e9fb23
--- /dev/null
+++ b/spec/ruby/core/hash/include_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+
+describe "Hash#include?" do
+ it "returns true if argument is a key" do
+ h = { a: 1, b: 2, c: 3, 4 => 0 }
+ h.include?(:a).should == true
+ h.include?(:b).should == true
+ h.include?(2).should == false
+ h.include?(4).should == true
+
+ not_supported_on :opal do
+ h.include?('b').should == false
+ h.include?(4.0).should == false
+ end
+ end
+
+ it "returns true if the key's matching value was nil" do
+ { xyz: nil }.include?(:xyz).should == true
+ end
+
+ it "returns true if the key's matching value was false" do
+ { xyz: false }.include?(:xyz).should == true
+ end
+
+ it "returns true if the key is nil" do
+ { nil => 'b' }.include?(nil).should == true
+ { nil => nil }.include?(nil).should == true
+ end
+
+ it "compares keys with the same #hash value via #eql?" do
+ x = mock('x')
+ x.stub!(:hash).and_return(42)
+
+ y = mock('y')
+ y.stub!(:hash).and_return(42)
+ y.should_receive(:eql?).and_return(false)
+
+ { x => nil }.include?(y).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/initialize_spec.rb b/spec/ruby/core/hash/initialize_spec.rb
new file mode 100644
index 0000000000..cdba598813
--- /dev/null
+++ b/spec/ruby/core/hash/initialize_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#initialize" do
+ it "is private" do
+ Hash.private_instance_methods(false).should.include?(:initialize)
+ end
+
+ it "can be used to reset default_proc" do
+ h = { "foo" => 1, "bar" => 2 }
+ h.default_proc.should == nil
+ h.send(:initialize) { |_, k| k * 2 }
+ h.default_proc.should_not == nil
+ h["a"].should == "aa"
+ end
+
+ it "can be used to reset the default value" do
+ h = {}
+ h.default = 42
+ h.default.should == 42
+ h.send(:initialize, 1)
+ h.default.should == 1
+ h.send(:initialize)
+ h.default.should == nil
+ end
+
+ it "receives the arguments passed to Hash#new" do
+ HashSpecs::NewHash.new(:one, :two)[0].should == :one
+ HashSpecs::NewHash.new(:one, :two)[1].should == :two
+ end
+
+ it "does not change the storage, only the default value or proc" do
+ h = HashSpecs::SubHashSettingInInitialize.new
+ h.to_a.should == [[:foo, :bar]]
+
+ h = HashSpecs::SubHashSettingInInitialize.new(:default)
+ h.to_a.should == [[:foo, :bar]]
+
+ h = HashSpecs::SubHashSettingInInitialize.new { :default_block }
+ h.to_a.should == [[:foo, :bar]]
+ end
+
+ it "returns self" do
+ h = Hash.new
+ h.send(:initialize).should.equal?(h)
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ block = -> { HashSpecs.frozen_hash.instance_eval { initialize() }}
+ block.should.raise(FrozenError)
+
+ block = -> { HashSpecs.frozen_hash.instance_eval { initialize(nil) } }
+ block.should.raise(FrozenError)
+
+ block = -> { HashSpecs.frozen_hash.instance_eval { initialize(5) } }
+ block.should.raise(FrozenError)
+
+ block = -> { HashSpecs.frozen_hash.instance_eval { initialize { 5 } } }
+ block.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/inspect_spec.rb b/spec/ruby/core/hash/inspect_spec.rb
new file mode 100644
index 0000000000..359b13360f
--- /dev/null
+++ b/spec/ruby/core/hash/inspect_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../../spec_helper'
+
+describe "Hash#inspect" do
+ it "returns a string representation with same order as each()" do
+ h = { a: [1, 2], b: -2, d: -6, nil => nil }
+ 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.inspect.should == expected
+ end
+
+ it "calls #inspect on keys and values" do
+ key = mock('key')
+ val = mock('val')
+ key.should_receive(:inspect).and_return('key')
+ val.should_receive(:inspect).and_return('val')
+ expected = ruby_version_is("3.4") ? "{key => val}" : "{key=>val}"
+ { key => val }.inspect.should == expected
+ end
+
+ it "does not call #to_s on a String returned from #inspect" do
+ str = +"abc"
+ str.should_not_receive(:to_s)
+ expected = ruby_version_is("3.4") ? '{a: "abc"}' : '{:a=>"abc"}'
+ { a: str }.inspect.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")
+ expected = ruby_version_is("3.4") ? "{a: abc}" : "{:a=>abc}"
+ { a: obj }.inspect.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)
+ expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/
+ { a: obj }.inspect.should =~ expected_pattern
+ end
+
+ it "does not call #to_str on the object returned from #to_s when it is not a String" do
+ obj = mock("Hash#inspect/to_s does not call #to_str on #to_s result")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_return(obj)
+ obj.should_not_receive(:to_str)
+ expected_pattern = ruby_version_is("3.4") ? /^\{a: #<MockObject:0x[0-9a-f]+>\}$/ : /^\{:a=>#<MockObject:0x[0-9a-f]+>\}$/
+ { a: obj }.inspect.should =~ expected_pattern
+ end
+
+ it "does not swallow exceptions raised by #to_s" do
+ obj = mock("Hash#inspect/to_s does not swallow #to_s exceptions")
+ obj.should_receive(:inspect).and_return(obj)
+ obj.should_receive(:to_s).and_raise(Exception)
+
+ -> { { a: obj }.inspect }.should.raise(Exception)
+ end
+
+ it "handles hashes with recursive values" do
+ x = {}
+ x[0] = x
+ expected = ruby_version_is("3.4") ? '{0 => {...}}' : '{0=>{...}}'
+ x.inspect.should == expected
+
+ x = {}
+ y = {}
+ x[0] = y
+ y[1] = x
+ expected_x = ruby_version_is("3.4") ? '{0 => {1 => {...}}}' : '{0=>{1=>{...}}}'
+ expected_y = ruby_version_is("3.4") ? '{1 => {0 => {...}}}' : '{1=>{0=>{...}}}'
+ x.inspect.should == expected_x
+ y.inspect.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))
+ expected = ruby_version_is("3.4") ? '{a: "utf_16be \u3042"}' : '{:a=>"utf_16be \u3042"}'
+ {a: utf_16be}.inspect.should == expected
+ end
+
+ it "works for keys and values whose #inspect return a frozen String" do
+ expected = ruby_version_is("3.4") ? "{true => false}" : "{true=>false}"
+ { true => false }.inspect.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 }.inspect.should == '{"needs-quotes": 1}'
+ end
+
+ it "can be evaled" do
+ no_quote = '{a: 1, a!: 1, a?: 1}'
+ eval(no_quote).inspect.should == no_quote
+ [
+ '{"": 1}',
+ '{"0": 1, "!": 1, "%": 1, "&": 1, "*": 1, "+": 1, "-": 1, "/": 1, "<": 1, ">": 1, "^": 1, "`": 1, "|": 1, "~": 1}',
+ '{"@a": 1, "$a": 1, "+@": 1, "a=": 1, "[]": 1}',
+ '{"a\"b": 1, "@@a": 1, "<=>": 1, "===": 1, "[]=": 1}',
+ ].each do |quote|
+ eval(quote).inspect.should == quote
+ end
+ end
+
+ it "can be evaled when Encoding.default_external is changed" do
+ external = Encoding.default_external
+
+ Encoding.default_external = Encoding::ASCII
+ utf8_ascii_hash = '{"\\u3042": 1}'
+ eval(utf8_ascii_hash).inspect.should == utf8_ascii_hash
+
+ Encoding.default_external = Encoding::UTF_8
+ utf8_hash = "{\u3042: 1}"
+ eval(utf8_hash).inspect.should == utf8_hash
+
+ Encoding.default_external = Encoding::Windows_31J
+ sjis_hash = "{\x87]: 1}".dup.force_encoding('sjis')
+ eval(sjis_hash).inspect.should == sjis_hash
+ ensure
+ Encoding.default_external = external
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/invert_spec.rb b/spec/ruby/core/hash/invert_spec.rb
new file mode 100644
index 0000000000..ea441751f2
--- /dev/null
+++ b/spec/ruby/core/hash/invert_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#invert" do
+ it "returns a new hash where keys are values and vice versa" do
+ { 1 => 'a', 2 => 'b', 3 => 'c' }.invert.should ==
+ { 'a' => 1, 'b' => 2, 'c' => 3 }
+ end
+
+ it "handles collisions by overriding with the key coming later in keys()" do
+ h = { a: 1, b: 1 }
+ override_key = h.keys.last
+ h.invert[1].should == override_key
+ end
+
+ it "compares new keys with eql? semantics" do
+ h = { a: 1.0, b: 1 }
+ i = h.invert
+ i[1.0].should == :a
+ i[1].should == :b
+ end
+
+ it "does not return subclass instances for subclasses" 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 == nil
+ h[:a] = 1
+ h.invert.default.should == 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 == nil
+ h[:a] = 1
+ h.invert.default_proc.should == 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/keep_if_spec.rb b/spec/ruby/core/hash/keep_if_spec.rb
new file mode 100644
index 0000000000..c975efc5f8
--- /dev/null
+++ b/spec/ruby/core/hash/keep_if_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#keep_if" do
+ it "yields two arguments: key and value" do
+ all_args = []
+ { 1 => 2, 3 => 4 }.keep_if { |*args| all_args << args }
+ all_args.should == [[1, 2], [3, 4]]
+ end
+
+ it "keeps every entry for which block is true and returns self" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+ h.keep_if { |k,v| v % 2 == 0 }.should.equal?(h)
+ h.should == { b: 2, d: 4 }
+ end
+
+ it "removes all entries if the block is false" do
+ h = { a: 1, b: 2, c: 3 }
+ h.keep_if { |k,v| false }.should.equal?(h)
+ h.should == {}
+ end
+
+ it "returns self even if unmodified" do
+ h = { 1 => 2, 3 => 4 }
+ h.keep_if { true }.should.equal?(h)
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.keep_if { true } }.should.raise(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.keep_if { false } }.should.raise(FrozenError)
+ end
+
+ it_behaves_like :hash_iteration_no_block, :keep_if
+ it_behaves_like :enumeratorized_with_origin_size, :keep_if, { 1 => 2, 3 => 4, 5 => 6 }
+end
diff --git a/spec/ruby/core/hash/key_spec.rb b/spec/ruby/core/hash/key_spec.rb
new file mode 100644
index 0000000000..c2d7049aed
--- /dev/null
+++ b/spec/ruby/core/hash/key_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Hash#key?" do
+ it "is an alias of Hash#include?" do
+ Hash.instance_method(:key?).should == Hash.instance_method(:include?)
+ end
+end
+
+describe "Hash#key" do
+ it "returns the corresponding key for value" do
+ { 2 => 'a', 1 => 'b' }.key('b').should == 1
+ end
+
+ it "returns nil if the value is not found" do
+ { a: -1, b: 3.14, c: 2.718 }.key(1).should == nil
+ end
+
+ it "doesn't return default value if the value is not found" do
+ Hash.new(5).key(5).should == nil
+ end
+
+ it "compares values using ==" do
+ { 1 => 0 }.key(0.0).should == 1
+ { 1 => 0.0 }.key(0).should == 1
+
+ needle = mock('needle')
+ inhash = mock('inhash')
+ inhash.should_receive(:==).with(needle).and_return(true)
+
+ { 1 => inhash }.key(needle).should == 1
+ end
+end
diff --git a/spec/ruby/core/hash/keys_spec.rb b/spec/ruby/core/hash/keys_spec.rb
new file mode 100644
index 0000000000..789c413bee
--- /dev/null
+++ b/spec/ruby/core/hash/keys_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#keys" do
+
+ it "returns an array with the keys in the order they were inserted" do
+ {}.keys.should == []
+ {}.keys.should.is_a?(Array)
+ Hash.new(5).keys.should == []
+ Hash.new { 5 }.keys.should == []
+ { 1 => 2, 4 => 8, 2 => 4 }.keys.should == [1, 4, 2]
+ { 1 => 2, 2 => 4, 4 => 8 }.keys.should.is_a?(Array)
+ { nil => nil }.keys.should == [nil]
+ end
+
+ it "uses the same order as #values" do
+ h = { 1 => "1", 2 => "2", 3 => "3", 4 => "4" }
+
+ h.size.times do |i|
+ h[h.keys[i]].should == h.values[i]
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/length_spec.rb b/spec/ruby/core/hash/length_spec.rb
new file mode 100644
index 0000000000..325973306f
--- /dev/null
+++ b/spec/ruby/core/hash/length_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Hash#length" do
+ it "is an alias of Hash#size" do
+ Hash.instance_method(:size).should == Hash.instance_method(:length)
+ end
+end
diff --git a/spec/ruby/core/hash/lt_spec.rb b/spec/ruby/core/hash/lt_spec.rb
new file mode 100644
index 0000000000..4ca326d077
--- /dev/null
+++ b/spec/ruby/core/hash/lt_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison'
+require_relative 'shared/less_than'
+
+describe "Hash#<" do
+ it_behaves_like :hash_comparison, :<
+ it_behaves_like :hash_less_than, :<
+
+ it "returns false if both hashes are identical" do
+ h = { a: 1, b: 2 }
+ (h < h).should == false
+ end
+end
+
+describe "Hash#<" do
+ before :each do
+ @hash = {a:1, b:2}
+ @bigger = {a:1, b:2, c:3}
+ @unrelated = {c:3, d:4}
+ @similar = {a:2, b:3}
+ end
+
+ it "returns false when receiver size is larger than argument" do
+ (@bigger < @hash).should == false
+ (@bigger < @unrelated).should == false
+ end
+
+ it "returns false when receiver size is the same as argument" do
+ (@hash < @hash).should == false
+ (@hash < @unrelated).should == false
+ (@unrelated < @hash).should == false
+ end
+
+ it "returns true when receiver is a subset of argument" do
+ (@hash < @bigger).should == true
+ end
+
+ it "returns false when keys match but values don't" do
+ (@hash < @similar).should == false
+ (@similar < @hash).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/lte_spec.rb b/spec/ruby/core/hash/lte_spec.rb
new file mode 100644
index 0000000000..29e60273d1
--- /dev/null
+++ b/spec/ruby/core/hash/lte_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison'
+require_relative 'shared/less_than'
+
+describe "Hash#<=" do
+ it_behaves_like :hash_comparison, :<=
+ it_behaves_like :hash_less_than, :<=
+
+ it "returns true if both hashes are identical" do
+ h = { a: 1, b: 2 }
+ (h <= h).should == true
+ end
+end
+
+describe "Hash#<=" do
+ before :each do
+ @hash = {a:1, b:2}
+ @bigger = {a:1, b:2, c:3}
+ @unrelated = {c:3, d:4}
+ @similar = {a:2, b:3}
+ end
+
+ it "returns false when receiver size is larger than argument" do
+ (@bigger <= @hash).should == false
+ (@bigger <= @unrelated).should == false
+ end
+
+ it "returns false when receiver size is the same as argument" do
+ (@hash <= @unrelated).should == false
+ (@unrelated <= @hash).should == false
+ end
+
+ it "returns true when receiver is a subset of argument or equals to argument" do
+ (@hash <= @bigger).should == true
+ (@hash <= @hash).should == true
+ end
+
+ it "returns false when keys match but values don't" do
+ (@hash <= @similar).should == false
+ (@similar <= @hash).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/member_spec.rb b/spec/ruby/core/hash/member_spec.rb
new file mode 100644
index 0000000000..e7309c3f7d
--- /dev/null
+++ b/spec/ruby/core/hash/member_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Hash#member?" do
+ it "is an alias of Hash#include?" do
+ Hash.instance_method(:member?).should == Hash.instance_method(:include?)
+ end
+end
diff --git a/spec/ruby/core/hash/merge_spec.rb b/spec/ruby/core/hash/merge_spec.rb
new file mode 100644
index 0000000000..9e566fcee9
--- /dev/null
+++ b/spec/ruby/core/hash/merge_spec.rb
@@ -0,0 +1,123 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#merge" do
+ it "returns a new hash by combining self with the contents of other" do
+ h = { 1 => :a, 2 => :b, 3 => :c }.merge(a: 1, c: 2)
+ h.should == { c: 2, 1 => :a, 2 => :b, a: 1, 3 => :c }
+
+ hash = { a: 1, b: 2 }
+ {}.merge(hash).should == hash
+ hash.merge({}).should == hash
+
+ h = { 1 => :a, 2 => :b, 3 => :c }.merge(1 => :b)
+ h.should == { 1 => :b, 2 => :b, 3 => :c }
+
+ h = { 1 => :a, 2 => :b }.merge(1 => :b, 3 => :c)
+ h.should == { 1 => :b, 2 => :b, 3 => :c }
+ end
+
+ it "sets any duplicate key to the value of block if passed a block" do
+ h1 = { a: 2, b: 1, d: 5 }
+ h2 = { a: -2, b: 4, c: -3 }
+ r = h1.merge(h2) { |k,x,y| nil }
+ r.should == { a: nil, b: nil, c: -3, d: 5 }
+
+ r = h1.merge(h2) { |k,x,y| "#{k}:#{x+2*y}" }
+ r.should == { a: "a:-2", b: "b:9", c: -3, d: 5 }
+
+ -> {
+ h1.merge(h2) { |k, x, y| raise(IndexError) }
+ }.should.raise(IndexError)
+
+ r = h1.merge(h1) { |k,x,y| :x }
+ r.should == { a: :x, b: :x, d: :x }
+ end
+
+ it "tries to convert the passed argument to a hash using #to_hash" do
+ obj = mock('{1=>2}')
+ obj.should_receive(:to_hash).and_return({ 1 => 2 })
+ { 3 => 4 }.merge(obj).should == { 1 => 2, 3 => 4 }
+ end
+
+ it "does not call to_hash on hash subclasses" do
+ { 3 => 4 }.merge(HashSpecs::ToHashHash[1 => 2]).should == { 1 => 2, 3 => 4 }
+ end
+
+ it "returns subclass instance for subclasses" do
+ HashSpecs::MyHash[1 => 2, 3 => 4].merge({ 1 => 2 }).should.instance_of?(HashSpecs::MyHash)
+ HashSpecs::MyHash[].merge({ 1 => 2 }).should.instance_of?(HashSpecs::MyHash)
+
+ { 1 => 2, 3 => 4 }.merge(HashSpecs::MyHash[1 => 2]).class.should == Hash
+ {}.merge(HashSpecs::MyHash[1 => 2]).class.should == Hash
+ end
+
+ it "processes entries with same order as each()" do
+ h = { 1 => 2, 3 => 4, 5 => 6, "x" => nil, nil => 5, [] => [] }
+ merge_pairs = []
+ each_pairs = []
+ h.each_pair { |k, v| each_pairs << [k, v] }
+ h.merge(h) { |k, v1, v2| merge_pairs << [k, v1] }
+ merge_pairs.should == each_pairs
+ end
+
+ it "preserves the order of merged elements" do
+ h1 = { 1 => 2, 3 => 4, 5 => 6 }
+ h2 = { 1 => 7 }
+ merge_pairs = []
+ h1.merge(h2).each_pair { |k, v| merge_pairs << [k, v] }
+ merge_pairs.should == [[1,7], [3, 4], [5, 6]]
+ end
+
+ it "preserves the order of merged elements for large hashes" do
+ h1 = {}
+ h2 = {}
+ merge_pairs = []
+ expected_pairs = []
+ (1..100).each { |x| h1[x] = x; h2[101 - x] = x; expected_pairs << [x, 101 - x] }
+ h1.merge(h2).each_pair { |k, v| merge_pairs << [k, v] }
+ merge_pairs.should == expected_pairs
+ end
+
+ it "accepts multiple hashes" do
+ result = { a: 1 }.merge({ b: 2 }, { c: 3 }, { d: 4 })
+ result.should == { a: 1, b: 2, c: 3, d: 4 }
+ end
+
+ it "accepts zero arguments and returns a copy of self" do
+ hash = { a: 1 }
+ merged = hash.merge
+
+ 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
+ it "is an alias of Hash#update" do
+ Hash.instance_method(:merge!).should == Hash.instance_method(:update)
+ end
+end
diff --git a/spec/ruby/core/hash/new_spec.rb b/spec/ruby/core/hash/new_spec.rb
new file mode 100644
index 0000000000..207fc2931f
--- /dev/null
+++ b/spec/ruby/core/hash/new_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+# Actually these are specs of Hash#initialize, there is no Hash.new it's just Class.new.
+describe "Hash.new" do
+ it "creates an empty Hash if passed no arguments" do
+ Hash.new.should == {}
+ Hash.new.size.should == 0
+ end
+
+ it "creates a new Hash with default object if passed a default argument" do
+ Hash.new(5).default.should == 5
+ Hash.new({}).default.should == {}
+ end
+
+ it "does not create a copy of the default argument" do
+ str = "foo"
+ Hash.new(str).default.should.equal?(str)
+ end
+
+ it "creates a Hash with a default_proc if passed a block" do
+ Hash.new.default_proc.should == nil
+
+ h = Hash.new { |x| "Answer to #{x}" }
+ h.default_proc.call(5).should == "Answer to 5"
+ h.default_proc.call("x").should == "Answer to x"
+ end
+
+ it "raises an ArgumentError if more than one argument is passed" do
+ -> { Hash.new(5,6) }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if passed both default argument and default block" do
+ -> { Hash.new(5) { 0 } }.should.raise(ArgumentError)
+ -> { Hash.new(nil) { 0 } }.should.raise(ArgumentError)
+ end
+
+ ruby_version_is ""..."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"))
+ )
+
+ -> { Hash.new(1, unknown: true) }.should.raise(ArgumentError)
+ -> { Hash.new(unknown: true) { 0 } }.should.raise(ArgumentError)
+
+ 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
+ end
+
+ it "raises an error if unknown keyword arguments are passed" do
+ -> { Hash.new(unknown: true) }.should.raise(ArgumentError)
+ -> { Hash.new(1, unknown: true) }.should.raise(ArgumentError)
+ -> { Hash.new(unknown: true) { 0 } }.should.raise(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/rassoc_spec.rb b/spec/ruby/core/hash/rassoc_spec.rb
new file mode 100644
index 0000000000..73379f6867
--- /dev/null
+++ b/spec/ruby/core/hash/rassoc_spec.rb
@@ -0,0 +1,42 @@
+require_relative '../../spec_helper'
+
+describe "Hash#rassoc" do
+ before :each do
+ @h = {apple: :green, orange: :orange, grape: :green, banana: :yellow}
+ end
+
+ it "returns an Array if the argument is a value of the Hash" do
+ @h.rassoc(:green).should.instance_of?(Array)
+ end
+
+ it "returns a 2-element Array if the argument is a value of the Hash" do
+ @h.rassoc(:orange).size.should == 2
+ end
+
+ it "sets the first element of the Array to the key of the located value" do
+ @h.rassoc(:yellow).first.should == :banana
+ end
+
+ it "sets the last element of the Array to the located value" do
+ @h.rassoc(:yellow).last.should == :yellow
+ end
+
+ it "only returns the first matching key-value pair" do
+ @h.rassoc(:green).should == [:apple, :green]
+ end
+
+ it "uses #== to compare the argument to the values" do
+ @h[:key] = 1.0
+ 1.should == 1.0
+ @h.rassoc(1).should.eql? [:key, 1.0]
+ end
+
+ it "returns nil if the argument is not a value of the Hash" do
+ @h.rassoc(:banana).should == nil
+ end
+
+ it "returns nil if the argument is not a value of the Hash even when there is a default" do
+ Hash.new(42).merge!( foo: :bar ).rassoc(42).should == nil
+ Hash.new{|h, k| h[k] = 42}.merge!( foo: :bar ).rassoc(42).should == nil
+ end
+end
diff --git a/spec/ruby/core/hash/rehash_spec.rb b/spec/ruby/core/hash/rehash_spec.rb
new file mode 100644
index 0000000000..fcd5a037bd
--- /dev/null
+++ b/spec/ruby/core/hash/rehash_spec.rb
@@ -0,0 +1,114 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#rehash" do
+ it "reorganizes the Hash by recomputing all key hash codes" do
+ k1 = Object.new
+ k2 = Object.new
+ def k1.hash; 0; end
+ def k2.hash; 1; end
+
+ h = {}
+ h[k1] = :v1
+ h[k2] = :v2
+
+ def k1.hash; 1; end
+
+ # The key should no longer be found as the #hash changed.
+ # Hash values 0 and 1 should not conflict, even with 1-bit stored hash.
+ h.key?(k1).should == false
+
+ h.keys.include?(k1).should == true
+
+ h.rehash.should.equal?(h)
+ h.key?(k1).should == true
+ h[k1].should == :v1
+ end
+
+ it "calls #hash for each key" do
+ k1 = mock('k1')
+ k2 = mock('k2')
+ v1 = mock('v1')
+ v2 = mock('v2')
+
+ v1.should_not_receive(:hash)
+ v2.should_not_receive(:hash)
+
+ h = { k1 => v1, k2 => v2 }
+
+ k1.should_receive(:hash).twice.and_return(0)
+ k2.should_receive(:hash).twice.and_return(0)
+
+ h.rehash
+ h[k1].should == v1
+ h[k2].should == v2
+ end
+
+ it "removes duplicate keys" do
+ a = [1,2]
+ b = [1]
+
+ h = {}
+ h[a] = true
+ h[b] = true
+ b << 2
+ h.size.should == 2
+ h.keys.should == [a, b]
+ h.rehash
+ h.size.should == 1
+ h.keys.should == [a]
+ end
+
+ it "removes duplicate keys for large hashes" do
+ a = [1,2]
+ b = [1]
+
+ h = {}
+ h[a] = true
+ h[b] = true
+ 100.times { |n| h[n] = true }
+ b << 2
+ h.size.should == 102
+ h.keys.should.include? a
+ h.keys.should.include? b
+ h.rehash
+ h.size.should == 101
+ h.keys.should.include? a
+ h.keys.should_not.include? [1]
+ end
+
+ it "iterates keys in insertion order" do
+ key = Class.new do
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def hash
+ 123
+ end
+ end
+
+ a, b, c, d = key.new('a'), key.new('b'), key.new('c'), key.new('d')
+ h = { a => 1, b => 2, c => 3, d => 4 }
+ h.size.should == 4
+
+ key.class_exec do
+ def eql?(other)
+ true
+ end
+ end
+
+ h.rehash
+ h.size.should == 1
+ k, v = h.first
+ k.name.should == 'a'
+ v.should == 4
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.rehash }.should.raise(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.rehash }.should.raise(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/reject_spec.rb b/spec/ruby/core/hash/reject_spec.rb
new file mode 100644
index 0000000000..aa1a074897
--- /dev/null
+++ b/spec/ruby/core/hash/reject_spec.rb
@@ -0,0 +1,116 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+require_relative '../enumerable/shared/enumeratorized'
+
+describe "Hash#reject" do
+ it "returns a new hash removing keys for which the block yields true" do
+ h = { 1=>false, 2=>true, 3=>false, 4=>true }
+ h.reject { |k,v| v }.keys.sort.should == [1,3]
+ end
+
+ it "is equivalent to hsh.dup.delete_if" do
+ h = { a: 'a', b: 'b', c: 'd' }
+ h.reject { |k,v| k == 'd' }.should == (h.dup.delete_if { |k, v| k == 'd' })
+
+ all_args_reject = []
+ all_args_delete_if = []
+ h = { 1 => 2, 3 => 4 }
+ h.reject { |*args| all_args_reject << args }
+ h.delete_if { |*args| all_args_delete_if << args }
+ all_args_reject.should == all_args_delete_if
+
+ h = { 1 => 2 }
+ # dup doesn't copy singleton methods
+ def h.to_a() end
+ h.reject { false }.to_a.should == [[1, 2]]
+ end
+
+ context "with extra state" do
+ it "returns Hash instance for subclasses" do
+ HashSpecs::MyHash[1 => 2, 3 => 4].reject { false }.should.is_a?(Hash)
+ HashSpecs::MyHash[1 => 2, 3 => 4].reject { true }.should.is_a?(Hash)
+ end
+ end
+
+ it "processes entries with the same order as reject!" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+
+ reject_pairs = []
+ reject_bang_pairs = []
+ h.dup.reject { |*pair| reject_pairs << pair }
+ h.reject! { |*pair| reject_bang_pairs << pair }
+
+ reject_pairs.should == reject_bang_pairs
+ end
+
+ it "does not retain the default value" do
+ h = Hash.new(1)
+ h.reject { false }.default.should == nil
+ h[:a] = 1
+ h.reject { false }.default.should == 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 == nil
+ h[:a] = 1
+ h.reject { false }.default_proc.should == 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
+
+describe "Hash#reject!" do
+ it "removes keys from self for which the block yields true" do
+ hsh = {}
+ (1 .. 10).each { |k| hsh[k] = (k % 2 == 0) }
+ hsh.reject! { |k,v| v }
+ hsh.keys.sort.should == [1,3,5,7,9]
+ end
+
+ it "removes all entries if the block is true" do
+ h = { a: 1, b: 2, c: 3 }
+ h.reject! { |k,v| true }.should.equal?(h)
+ h.should == {}
+ end
+
+ it "is equivalent to delete_if if changes are made" do
+ hsh = { a: 1 }
+ hsh.reject! { |k,v| v < 2 }.should == hsh.dup.delete_if { |k, v| v < 2 }
+ end
+
+ it "returns nil if no changes were made" do
+ { a: 1 }.reject! { |k,v| v > 1 }.should == nil
+ end
+
+ it "processes entries with the same order as delete_if" do
+ h = { a: 1, b: 2, c: 3, d: 4 }
+
+ reject_bang_pairs = []
+ delete_if_pairs = []
+ h.dup.reject! { |*pair| reject_bang_pairs << pair }
+ h.dup.delete_if { |*pair| delete_if_pairs << pair }
+
+ reject_bang_pairs.should == delete_if_pairs
+ end
+
+ it "raises a FrozenError if called on a frozen instance that is modified" do
+ -> { HashSpecs.empty_frozen_hash.reject! { true } }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError if called on a frozen instance that would not be modified" do
+ -> { HashSpecs.frozen_hash.reject! { false } }.should.raise(FrozenError)
+ 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
new file mode 100644
index 0000000000..4dacbf9779
--- /dev/null
+++ b/spec/ruby/core/hash/replace_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#replace" do
+ 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 == 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 == 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(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(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb
new file mode 100644
index 0000000000..609a53f81f
--- /dev/null
+++ b/spec/ruby/core/hash/ruby2_keywords_hash_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash.ruby2_keywords_hash?" do
+ it "returns false if the Hash is not a keywords Hash" do
+ Hash.ruby2_keywords_hash?({}).should == false
+ end
+
+ it "returns true if the Hash is a keywords Hash marked by Module#ruby2_keywords" do
+ obj = Class.new {
+ ruby2_keywords def m(*args)
+ args.last
+ end
+ }.new
+ Hash.ruby2_keywords_hash?(obj.m(a: 1)).should == true
+ end
+
+ it "raises TypeError for non-Hash" do
+ -> { Hash.ruby2_keywords_hash?(nil) }.should.raise(TypeError)
+ end
+end
+
+describe "Hash.ruby2_keywords_hash" do
+ it "returns a copy of a Hash and marks the copy as a keywords Hash" do
+ h = {a: 1}.freeze
+ kw = Hash.ruby2_keywords_hash(h)
+ Hash.ruby2_keywords_hash?(h).should == false
+ Hash.ruby2_keywords_hash?(kw).should == true
+ kw.should == h
+ end
+
+ it "returns an instance of the subclass if called on an instance of a subclass of Hash" do
+ h = HashSpecs::MyHash.new
+ h[:a] = 1
+ kw = Hash.ruby2_keywords_hash(h)
+ kw.class.should == HashSpecs::MyHash
+ Hash.ruby2_keywords_hash?(h).should == false
+ Hash.ruby2_keywords_hash?(kw).should == true
+ kw.should == h
+ end
+
+ it "copies instance variables" do
+ h = {a: 1}
+ h.instance_variable_set(:@foo, 42)
+ kw = Hash.ruby2_keywords_hash(h)
+ kw.instance_variable_get(:@foo).should == 42
+ end
+
+ it "copies the hash internals" do
+ h = {a: 1}
+ kw = Hash.ruby2_keywords_hash(h)
+ h[:a] = 2
+ kw[:a].should == 1
+ end
+
+ it "raises TypeError for non-Hash" do
+ -> { Hash.ruby2_keywords_hash(nil) }.should.raise(TypeError)
+ end
+
+ it "retains the default value" do
+ hash = Hash.new(1)
+ Hash.ruby2_keywords_hash(hash).default.should == 1
+ hash[:a] = 1
+ Hash.ruby2_keywords_hash(hash).default.should == 1
+ end
+
+ it "retains the default_proc" do
+ pr = proc { |h, k| h[k] = [] }
+ hash = Hash.new(&pr)
+ Hash.ruby2_keywords_hash(hash).default_proc.should == pr
+ hash[:a] = 1
+ Hash.ruby2_keywords_hash(hash).default_proc.should == pr
+ end
+
+ it "retains compare_by_identity_flag" do
+ hash = {}.compare_by_identity
+ Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true
+ hash[:a] = 1
+ Hash.ruby2_keywords_hash(hash).compare_by_identity?.should == true
+ end
+end
diff --git a/spec/ruby/core/hash/select_spec.rb b/spec/ruby/core/hash/select_spec.rb
new file mode 100644
index 0000000000..60b2ce67c1
--- /dev/null
+++ b/spec/ruby/core/hash/select_spec.rb
@@ -0,0 +1,112 @@
+require_relative '../../spec_helper'
+require_relative '../enumerable/shared/enumeratorized'
+require_relative 'fixtures/classes'
+require_relative 'shared/iteration'
+
+describe "Hash#select" do
+ before :each do
+ @hsh = { 1 => 2, 3 => 4, 5 => 6 }
+ @empty = {}
+ end
+
+ it "yields two arguments: key and value" do
+ all_args = []
+ { 1 => 2, 3 => 4 }.select { |*args| all_args << args }
+ all_args.sort.should == [[1, 2], [3, 4]]
+ end
+
+ it "returns a Hash of entries for which block is true" do
+ a_pairs = { 'a' => 9, 'c' => 4, 'b' => 5, 'd' => 2 }.select { |k,v| v % 2 == 0 }
+ a_pairs.should.instance_of?(Hash)
+ a_pairs.sort.should == [['c', 4], ['d', 2]]
+ end
+
+ it "processes entries with the same order as reject" do
+ h = { a: 9, c: 4, b: 5, d: 2 }
+
+ select_pairs = []
+ reject_pairs = []
+ h.dup.select{ |*pair| select_pairs << pair }
+ h.reject { |*pair| reject_pairs << pair }
+
+ select_pairs.should == reject_pairs
+ end
+
+ it "returns an Enumerator when called on a non-empty hash without a block" do
+ @hsh.select.should.instance_of?(Enumerator)
+ end
+
+ it "returns an Enumerator when called on an empty hash without a block" do
+ @empty.select.should.instance_of?(Enumerator)
+ end
+
+ it "does not retain the default value" do
+ h = Hash.new(1)
+ h.select { true }.default.should == nil
+ h[:a] = 1
+ h.select { true }.default.should == nil
+ end
+
+ it "does not retain the default_proc" do
+ pr = proc { |h, k| h[k] = [] }
+ h = Hash.new(&pr)
+ h.select { true }.default_proc.should == nil
+ h[:a] = 1
+ h.select { true }.default_proc.should == nil
+ end
+
+ it "retains compare_by_identity flag" do
+ h = { a: 9, c: 4 }.compare_by_identity
+ h2 = h.select { |k, _| k == :a }
+ h2.compare_by_identity?.should == true
+ end
+
+ it_behaves_like :hash_iteration_no_block, :select
+
+ before :each do
+ @object = { 1 => 2, 3 => 4, 5 => 6 }
+ end
+ it_behaves_like :enumeratorized_with_origin_size, :select
+end
+
+describe "Hash#select!" do
+ before :each do
+ @hsh = { 1 => 2, 3 => 4, 5 => 6 }
+ @empty = {}
+ end
+
+ it "is equivalent to keep_if if changes are made" do
+ h = { a: 2 }
+ h.select! { |k,v| v <= 1 }.should.equal? h
+
+ h = { 1 => 2, 3 => 4 }
+ all_args_select = []
+ h.dup.select! { |*args| all_args_select << args }
+ all_args_select.should == [[1, 2], [3, 4]]
+ end
+
+ it "removes all entries if the block is false" do
+ h = { a: 1, b: 2, c: 3 }
+ h.select! { |k,v| false }.should.equal?(h)
+ h.should == {}
+ end
+
+ it "returns nil if no changes were made" do
+ { a: 1 }.select! { |k,v| v <= 1 }.should == nil
+ end
+
+ it "raises a FrozenError if called on an empty frozen instance" do
+ -> { HashSpecs.empty_frozen_hash.select! { false } }.should.raise(FrozenError)
+ end
+
+ it "raises a FrozenError if called on a frozen instance that would not be modified" do
+ -> { HashSpecs.frozen_hash.select! { true } }.should.raise(FrozenError)
+ end
+
+ it_behaves_like :hash_iteration_no_block, :select!
+
+ before :each do
+ @object = { 1 => 2, 3 => 4, 5 => 6 }
+ end
+ it_behaves_like :enumeratorized_with_origin_size, :select!
+end
diff --git a/spec/ruby/core/hash/shared/comparison.rb b/spec/ruby/core/hash/shared/comparison.rb
new file mode 100644
index 0000000000..2b62837232
--- /dev/null
+++ b/spec/ruby/core/hash/shared/comparison.rb
@@ -0,0 +1,15 @@
+describe :hash_comparison, shared: true do
+ it "raises a TypeError if the right operand is not a hash" do
+ -> { { a: 1 }.send(@method, 1) }.should.raise(TypeError)
+ -> { { a: 1 }.send(@method, nil) }.should.raise(TypeError)
+ -> { { a: 1 }.send(@method, []) }.should.raise(TypeError)
+ end
+
+ it "returns false if both hashes have the same keys but different values" do
+ h1 = { a: 1 }
+ h2 = { a: 2 }
+
+ h1.send(@method, h2).should == false
+ h2.send(@method, h1).should == false
+ end
+end
diff --git a/spec/ruby/core/hash/shared/eql.rb b/spec/ruby/core/hash/shared/eql.rb
new file mode 100644
index 0000000000..512e1ad016
--- /dev/null
+++ b/spec/ruby/core/hash/shared/eql.rb
@@ -0,0 +1,204 @@
+describe :hash_eql, shared: true do
+ it "does not compare values when keys don't match" do
+ value = mock('x')
+ value.should_not_receive(:==)
+ value.should_not_receive(:eql?)
+ { 1 => value }.send(@method, { 2 => value }).should == false
+ end
+
+ it "returns false when the numbers of keys differ without comparing any elements" do
+ obj = mock('x')
+ h = { obj => obj }
+
+ obj.should_not_receive(:==)
+ obj.should_not_receive(:eql?)
+
+ {}.send(@method, h).should == false
+ h.send(@method, {}).should == false
+ end
+
+ it "first compares keys via hash" do
+ x = mock('x')
+ x.should_receive(:hash).any_number_of_times.and_return(0)
+ y = mock('y')
+ y.should_receive(:hash).any_number_of_times.and_return(0)
+
+ { x => 1 }.send(@method, { y => 1 }).should == false
+ end
+
+ it "does not compare keys with different hash codes via eql?" do
+ x = mock('x')
+ y = mock('y')
+ x.should_not_receive(:eql?)
+ y.should_not_receive(:eql?)
+
+ x.should_receive(:hash).any_number_of_times.and_return(0)
+ y.should_receive(:hash).any_number_of_times.and_return(1)
+
+ { x => 1 }.send(@method, { y => 1 }).should == false
+ end
+
+ it "computes equality for recursive hashes" do
+ h = {}
+ h[:a] = h
+ h.send(@method, h[:a]).should == true
+ (h == h[:a]).should == true
+ end
+
+ it "doesn't call to_hash on objects" do
+ mock_hash = mock("fake hash")
+ def mock_hash.to_hash() {} end
+ {}.send(@method, mock_hash).should == false
+ end
+
+ it "computes equality for complex recursive hashes" do
+ a, b = {}, {}
+ a.merge! self: a, other: b
+ b.merge! self: b, other: a
+ a.send(@method, b).should == true # they both have the same structure!
+
+ c = {}
+ c.merge! other: c, self: c
+ c.send(@method, a).should == true # subtle, but they both have the same structure!
+ a[:delta] = c[:delta] = a
+ c.send(@method, a).should == false # not quite the same structure, as a[:other][:delta] = nil
+ c[:delta] = 42
+ c.send(@method, a).should == false
+ a[:delta] = 42
+ c.send(@method, a).should == false
+ b[:delta] = 42
+ c.send(@method, a).should == true
+ end
+
+ it "computes equality for recursive hashes & arrays" do
+ x, y, z = [], [], []
+ a, b, c = {foo: x, bar: 42}, {foo: y, bar: 42}, {foo: z, bar: 42}
+ x << a
+ y << c
+ z << b
+ b.send(@method, c).should == true # they clearly have the same structure!
+ y.send(@method, z).should == true
+ a.send(@method, b).should == true # subtle, but they both have the same structure!
+ x.send(@method, y).should == true
+ y << x
+ y.send(@method, z).should == false
+ z << x
+ y.send(@method, z).should == true
+
+ a[:foo], a[:bar] = a[:bar], a[:foo]
+ a.send(@method, b).should == false
+ b[:bar] = b[:foo]
+ b.send(@method, c).should == false
+ end
+end
+
+describe :hash_eql_additional, shared: true do
+ it "compares values when keys match" do
+ x = mock('x')
+ y = mock('y')
+ def x.==(o) false end
+ def y.==(o) false end
+ def x.eql?(o) false end
+ def y.eql?(o) false end
+ { 1 => x }.send(@method, { 1 => y }).should == false
+
+ x = mock('x')
+ y = mock('y')
+ def x.==(o) true end
+ def y.==(o) true end
+ def x.eql?(o) true end
+ def y.eql?(o) true end
+ { 1 => x }.send(@method, { 1 => y }).should == true
+ end
+
+ it "compares keys with eql? semantics" do
+ { 1.0 => "x" }.send(@method, { 1.0 => "x" }).should == true
+ { 1.0 => "x" }.send(@method, { 1.0 => "x" }).should == true
+ { 1 => "x" }.send(@method, { 1.0 => "x" }).should == false
+ { 1.0 => "x" }.send(@method, { 1 => "x" }).should == false
+ end
+
+ it "returns true if and only if other Hash has the same number of keys and each key-value pair matches" do
+ a = { a: 5 }
+ b = {}
+ a.send(@method, b).should == false
+
+ b[:a] = 5
+ a.send(@method, b).should == true
+
+ not_supported_on :opal do
+ c = { "a" => 5 }
+ a.send(@method, c).should == false
+ end
+
+ c = { "A" => 5 }
+ a.send(@method, c).should == false
+
+ c = { a: 6 }
+ a.send(@method, c).should == false
+ end
+
+ it "does not call to_hash on hash subclasses" do
+ { 5 => 6 }.send(@method, HashSpecs::ToHashHash[5 => 6]).should == true
+ end
+
+ it "ignores hash class differences" do
+ h = { 1 => 2, 3 => 4 }
+ HashSpecs::MyHash[h].send(@method, h).should == true
+ HashSpecs::MyHash[h].send(@method, HashSpecs::MyHash[h]).should == true
+ h.send(@method, HashSpecs::MyHash[h]).should == true
+ end
+
+ # Why isn't this true of eql? too ?
+ it "compares keys with matching hash codes via eql?" do
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ return true if self.equal?(o)
+ false
+ end
+
+ obj
+ end
+
+ { a[0] => 1 }.send(@method, { a[1] => 1 }).should == false
+
+ a = Array.new(2) do
+ obj = mock('0')
+ obj.should_receive(:hash).at_least(1).and_return(0)
+
+ def obj.eql?(o)
+ true
+ end
+
+ obj
+ end
+
+ { a[0] => 1 }.send(@method, { a[1] => 1 }).should == true
+ end
+
+ it "compares the values in self to values in other hash" do
+ l_val = mock("left")
+ r_val = mock("right")
+
+ l_val.should_receive(:eql?).with(r_val).and_return(true)
+
+ { 1 => l_val }.eql?({ 1 => r_val }).should == true
+ end
+end
+
+describe :hash_eql_additional_more, shared: true do
+ it "returns true if other Hash has the same number of keys and each key-value pair matches, even though the default-value are not same" do
+ Hash.new(5).send(@method, Hash.new(1)).should == true
+ Hash.new {|h, k| 1}.send(@method, Hash.new {}).should == true
+ Hash.new {|h, k| 1}.send(@method, Hash.new(2)).should == true
+
+ d = Hash.new {|h, k| 1}
+ e = Hash.new {}
+ d[1] = 2
+ e[1] = 2
+ d.send(@method, e).should == true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/greater_than.rb b/spec/ruby/core/hash/shared/greater_than.rb
new file mode 100644
index 0000000000..dfe0b80250
--- /dev/null
+++ b/spec/ruby/core/hash/shared/greater_than.rb
@@ -0,0 +1,23 @@
+describe :hash_greater_than, shared: true do
+ before do
+ @h1 = { a: 1, b: 2, c: 3 }
+ @h2 = { a: 1, b: 2 }
+ end
+
+ it "returns true if the other hash is a subset of self" do
+ @h1.send(@method, @h2).should == true
+ end
+
+ it "returns false if the other hash is not a subset of self" do
+ @h2.send(@method, @h1).should == false
+ end
+
+ it "converts the right operand to a hash before comparing" do
+ o = Object.new
+ def o.to_hash
+ { a: 1, b: 2 }
+ end
+
+ @h1.send(@method, o).should == true
+ end
+end
diff --git a/spec/ruby/core/hash/shared/iteration.rb b/spec/ruby/core/hash/shared/iteration.rb
new file mode 100644
index 0000000000..322e4b9a2f
--- /dev/null
+++ b/spec/ruby/core/hash/shared/iteration.rb
@@ -0,0 +1,19 @@
+describe :hash_iteration_no_block, shared: true do
+ before :each do
+ @hsh = { 1 => 2, 3 => 4, 5 => 6 }
+ @empty = {}
+ end
+
+ it "returns an Enumerator if called on a non-empty hash without a block" do
+ @hsh.send(@method).should.instance_of?(Enumerator)
+ end
+
+ it "returns an Enumerator if called on an empty hash without a block" do
+ @empty.send(@method).should.instance_of?(Enumerator)
+ end
+
+ it "returns an Enumerator if called on a frozen instance" do
+ @hsh.freeze
+ @hsh.send(@method).should.instance_of?(Enumerator)
+ end
+end
diff --git a/spec/ruby/core/hash/shared/less_than.rb b/spec/ruby/core/hash/shared/less_than.rb
new file mode 100644
index 0000000000..071b3e97bb
--- /dev/null
+++ b/spec/ruby/core/hash/shared/less_than.rb
@@ -0,0 +1,23 @@
+describe :hash_less_than, shared: true do
+ before do
+ @h1 = { a: 1, b: 2 }
+ @h2 = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns true if self is a subset of the other hash" do
+ @h1.send(@method, @h2).should == true
+ end
+
+ it "returns false if self is not a subset of the other hash" do
+ @h2.send(@method, @h1).should == false
+ end
+
+ it "converts the right operand to a hash before comparing" do
+ o = Object.new
+ def o.to_hash
+ { a: 1, b: 2, c: 3 }
+ end
+
+ @h1.send(@method, o).should == true
+ end
+end
diff --git a/spec/ruby/core/hash/shift_spec.rb b/spec/ruby/core/hash/shift_spec.rb
new file mode 100644
index 0000000000..6095d2e55f
--- /dev/null
+++ b/spec/ruby/core/hash/shift_spec.rb
@@ -0,0 +1,78 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#shift" do
+ it "removes a pair from hash and return it" do
+ h = { a: 1, b: 2, "c" => 3, nil => 4, [] => 5 }
+ h2 = h.dup
+
+ h.size.times do |i|
+ r = h.shift
+ r.should.is_a?(Array)
+ h2[r.first].should == r.last
+ h.size.should == h2.size - i - 1
+ end
+
+ h.should == {}
+ end
+
+ # MRI explicitly implements this behavior
+ it "allows shifting entries while iterating" do
+ h = { a: 1, b: 2, c: 3 }
+ visited = []
+ shifted = []
+ h.each_pair { |k,v|
+ visited << k
+ shifted << h.shift
+ }
+ visited.should == [:a, :b, :c]
+ shifted.should == [[:a, 1], [:b, 2], [:c, 3]]
+ h.should == {}
+ end
+
+ 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
+
+ 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
+ h = { :a => 1, :b => 2 }
+ h.shift.should == [:a, 1]
+ h.shift.should == [:b, 2]
+ h[:c] = 3
+ h.should == {:c => 3}
+ end
+
+ it "raises a FrozenError if called on a frozen instance" do
+ -> { HashSpecs.frozen_hash.shift }.should.raise(FrozenError)
+ -> { HashSpecs.empty_frozen_hash.shift }.should.raise(FrozenError)
+ end
+
+ it "works when the hash is at capacity" do
+ # We try a wide range of sizes in hopes that this will cover all implementations' base Hash size.
+ results = []
+ 1.upto(100) do |n|
+ h = {}
+ n.times do |i|
+ h[i] = i
+ end
+ h.shift
+ results << h.size
+ end
+
+ results.should == 0.upto(99).to_a
+ end
+end
diff --git a/spec/ruby/core/hash/size_spec.rb b/spec/ruby/core/hash/size_spec.rb
new file mode 100644
index 0000000000..5e5008a5dc
--- /dev/null
+++ b/spec/ruby/core/hash/size_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+
+describe "Hash#size" do
+ it "returns the number of entries" do
+ { a: 1, b: 'c' }.size.should == 2
+ h = { a: 1, b: 2 }
+ h[:a] = 2
+ h.size.should == 2
+ { a: 1, b: 1, c: 1 }.size.should == 3
+ {}.size.should == 0
+ Hash.new(5).size.should == 0
+ Hash.new { 5 }.size.should == 0
+ end
+end
diff --git a/spec/ruby/core/hash/slice_spec.rb b/spec/ruby/core/hash/slice_spec.rb
new file mode 100644
index 0000000000..fd93254517
--- /dev/null
+++ b/spec/ruby/core/hash/slice_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+
+describe "Hash#slice" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns a new empty hash without arguments" do
+ ret = @hash.slice
+ ret.should_not.equal?(@hash)
+ ret.should.instance_of?(Hash)
+ ret.should == {}
+ end
+
+ it "returns the requested subset" do
+ @hash.slice(:c, :a).should == { c: 3, a: 1 }
+ end
+
+ it "returns a hash ordered in the order of the requested keys" do
+ @hash.slice(:c, :a).keys.should == [:c, :a]
+ end
+
+ it "returns only the keys of the original hash" do
+ @hash.slice(:a, :chunky_bacon).should == { a: 1 }
+ end
+
+ it "returns a Hash instance, even on subclasses" do
+ klass = Class.new(Hash)
+ h = klass.new
+ h[:bar] = 12
+ h[:foo] = 42
+ r = h.slice(:foo)
+ r.should == {foo: 42}
+ r.class.should == Hash
+ end
+
+ it "uses the regular Hash#[] method, even on subclasses that override it" do
+ ScratchPad.record []
+ klass = Class.new(Hash) do
+ def [](value)
+ ScratchPad << :used_subclassed_operator
+ super
+ end
+ end
+
+ h = klass.new
+ h[:bar] = 12
+ h[:foo] = 42
+ h.slice(:foo)
+
+ ScratchPad.recorded.should == []
+ end
+
+ it "does not retain the default value" do
+ h = Hash.new(1)
+ h.slice(:a).default.should == nil
+ h[:a] = 1
+ h.slice(:a).default.should == 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 == nil
+ h[:a] = 1
+ h.slice(:a).default_proc.should == 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/sort_spec.rb b/spec/ruby/core/hash/sort_spec.rb
new file mode 100644
index 0000000000..26058c845e
--- /dev/null
+++ b/spec/ruby/core/hash/sort_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#sort" do
+ it "converts self to a nested array of [key, value] arrays and sort with Array#sort" do
+ { 'a' => 'b', '1' => '2', 'b' => 'a' }.sort.should ==
+ [["1", "2"], ["a", "b"], ["b", "a"]]
+ end
+
+ it "works when some of the keys are themselves arrays" do
+ { [1,2] => 5, [1,1] => 5 }.sort.should == [[[1,1],5], [[1,2],5]]
+ end
+
+ it "uses block to sort array if passed a block" do
+ { 1 => 2, 2 => 9, 3 => 4 }.sort { |a,b| b <=> a }.should == [[3, 4], [2, 9], [1, 2]]
+ end
+end
diff --git a/spec/ruby/core/hash/store_spec.rb b/spec/ruby/core/hash/store_spec.rb
new file mode 100644
index 0000000000..7017d8ba2b
--- /dev/null
+++ b/spec/ruby/core/hash/store_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Hash#store" do
+ it "is an alias of Hash#[]=" do
+ Hash.instance_method(:store).should == Hash.instance_method(:[]=)
+ end
+end
diff --git a/spec/ruby/core/hash/to_a_spec.rb b/spec/ruby/core/hash/to_a_spec.rb
new file mode 100644
index 0000000000..8c638db6c3
--- /dev/null
+++ b/spec/ruby/core/hash/to_a_spec.rb
@@ -0,0 +1,29 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#to_a" do
+ it "returns a list of [key, value] pairs with same order as each()" do
+ h = { a: 1, 1 => :a, 3 => :b, b: 5 }
+ pairs = []
+
+ h.each_pair do |key, value|
+ pairs << [key, value]
+ end
+
+ h.to_a.should.is_a?(Array)
+ h.to_a.should == pairs
+ end
+
+ it "is called for Enumerable#entries" do
+ h = { a: 1, 1 => :a, 3 => :b, b: 5 }
+ pairs = []
+
+ h.each_pair do |key, value|
+ pairs << [key, value]
+ end
+
+ ent = h.entries
+ ent.should.is_a?(Array)
+ ent.should == pairs
+ end
+end
diff --git a/spec/ruby/core/hash/to_h_spec.rb b/spec/ruby/core/hash/to_h_spec.rb
new file mode 100644
index 0000000000..5d5a280dac
--- /dev/null
+++ b/spec/ruby/core/hash/to_h_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#to_h" do
+ it "returns self for Hash instances" do
+ h = {}
+ h.to_h.should.equal?(h)
+ end
+
+ describe "when called on a subclass of Hash" do
+ before :each do
+ @h = HashSpecs::MyHash.new
+ @h[:foo] = :bar
+ end
+
+ it "returns a new Hash instance" do
+ @h.to_h.should.instance_of?(Hash)
+ @h.to_h.should == @h
+ @h[:foo].should == :bar
+ end
+
+ it "retains the default" do
+ @h.default = 42
+ @h.to_h.default.should == 42
+ @h[:hello].should == 42
+ end
+
+ 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
+ it "converts [key, value] pairs returned by the block to a hash" 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] }
+ end.should.raise(ArgumentError, /element has wrong array length/)
+
+ -> do
+ { a: 1, b: 2 }.to_h { |k, v| [k] }
+ end.should.raise(ArgumentError, /element has wrong array length/)
+ end
+
+ it "raises TypeError if block returns something other than Array" do
+ -> do
+ { a: 1, b: 2 }.to_h { |k, v| "not-array" }
+ end.should.raise(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'])
+
+ { a: 1 }.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'])
+
+ -> do
+ { a: 1 }.to_h { |k| x }
+ end.should.raise(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 == 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 == 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/to_hash_spec.rb b/spec/ruby/core/hash/to_hash_spec.rb
new file mode 100644
index 0000000000..f5622b3d9c
--- /dev/null
+++ b/spec/ruby/core/hash/to_hash_spec.rb
@@ -0,0 +1,14 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#to_hash" do
+ it "returns self for Hash instances" do
+ h = {}
+ h.to_hash.should.equal?(h)
+ end
+
+ it "returns self for instances of subclasses of Hash" do
+ h = HashSpecs::MyHash.new
+ h.to_hash.should.equal?(h)
+ end
+end
diff --git a/spec/ruby/core/hash/to_proc_spec.rb b/spec/ruby/core/hash/to_proc_spec.rb
new file mode 100644
index 0000000000..bc4756600d
--- /dev/null
+++ b/spec/ruby/core/hash/to_proc_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#to_proc" do
+ before :each do
+ @key = Object.new
+ @value = Object.new
+ @hash = { @key => @value }
+ @default = Object.new
+ @unstored = Object.new
+ end
+
+ it "returns an instance of Proc" do
+ @hash.to_proc.should.instance_of? Proc
+ end
+
+ describe "the returned proc" do
+ before :each do
+ @proc = @hash.to_proc
+ end
+
+ it "is a lambda" do
+ @proc.should.lambda?
+ end
+
+ it "has an arity of 1" do
+ @proc.arity.should == 1
+ end
+
+ it "raises ArgumentError if not passed exactly one argument" do
+ -> {
+ @proc.call
+ }.should.raise(ArgumentError)
+
+ -> {
+ @proc.call 1, 2
+ }.should.raise(ArgumentError)
+ end
+
+ context "with a stored key" do
+ it "returns the paired value" do
+ @proc.call(@key).should.equal?(@value)
+ end
+ end
+
+ context "passed as a block" do
+ it "retrieves the hash's values" do
+ [@key].map(&@proc)[0].should.equal?(@value)
+ end
+
+ context "to instance_exec" do
+ it "always retrieves the original hash's values" do
+ hash = {foo: 1, bar: 2}
+ proc = hash.to_proc
+
+ hash.instance_exec(:foo, &proc).should == 1
+
+ hash2 = {quux: 1}
+ hash2.instance_exec(:foo, &proc).should == 1
+ end
+ end
+ end
+
+ context "with no stored key" do
+ it "returns nil" do
+ @proc.call(@unstored).should == nil
+ end
+
+ context "when the hash has a default value" do
+ before :each do
+ @hash.default = @default
+ end
+
+ it "returns the default value" do
+ @proc.call(@unstored).should.equal?(@default)
+ end
+ end
+
+ context "when the hash has a default proc" do
+ it "returns an evaluated value from the default proc" do
+ @hash.default_proc = -> hash, called_with { [hash.keys, called_with] }
+ @proc.call(@unstored).should == [[@key], @unstored]
+ end
+ end
+ end
+
+ it "raises an ArgumentError when calling #call on the Proc with no arguments" do
+ -> { @hash.to_proc.call }.should.raise(ArgumentError)
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/to_s_spec.rb b/spec/ruby/core/hash/to_s_spec.rb
new file mode 100644
index 0000000000..2915db6ef8
--- /dev/null
+++ b/spec/ruby/core/hash/to_s_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Hash#to_s" do
+ it "is an alias of Hash#inspect" do
+ Hash.instance_method(:to_s).should == Hash.instance_method(:inspect)
+ end
+end
diff --git a/spec/ruby/core/hash/transform_keys_spec.rb b/spec/ruby/core/hash/transform_keys_spec.rb
new file mode 100644
index 0000000000..d37a2b8616
--- /dev/null
+++ b/spec/ruby/core/hash/transform_keys_spec.rb
@@ -0,0 +1,150 @@
+require_relative '../../spec_helper'
+
+describe "Hash#transform_keys" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns new hash" do
+ ret = @hash.transform_keys(&:succ)
+ ret.should_not.equal?(@hash)
+ ret.should.instance_of?(Hash)
+ end
+
+ it "sets the result as transformed keys with the given block" do
+ @hash.transform_keys(&:succ).should == { b: 1, c: 2, d: 3 }
+ end
+
+ it "keeps last pair if new keys conflict" do
+ @hash.transform_keys { |_| :a }.should == { a: 3 }
+ end
+
+ it "makes both hashes to share values" do
+ value = [1, 2, 3]
+ new_hash = { a: value }.transform_keys(&:upcase)
+ new_hash[:A].should.equal?(value)
+ end
+
+ context "when no block is given" do
+ it "returns a sized Enumerator" do
+ enumerator = @hash.transform_keys
+ enumerator.should.instance_of?(Enumerator)
+ enumerator.size.should == @hash.size
+ enumerator.each(&:succ).should == { b: 1, c: 2, d: 3 }
+ end
+ end
+
+ it "returns a Hash instance, even on subclasses" do
+ klass = Class.new(Hash)
+ h = klass.new
+ h[:foo] = 42
+ r = h.transform_keys{|v| :"x#{v}"}
+ r.keys.should == [:xfoo]
+ r.class.should == Hash
+ end
+
+ it "allows a hash argument" do
+ @hash.transform_keys({ a: :A, b: :B, c: :C }).should == { A: 1, B: 2, C: 3 }
+ end
+
+ it "allows a partial transformation of keys when using a hash argument" do
+ @hash.transform_keys({ a: :A, c: :C }).should == { A: 1, b: 2, C: 3 }
+ end
+
+ 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 == nil
+ h[:a] = 1
+ h.transform_keys(&:succ).default.should == 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 == nil
+ h[:a] = 1
+ h.transform_values(&:succ).default_proc.should == 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
+ before :each do
+ @hash = { a: 1, b: 2, c: 3, d: 4 }
+ @initial_pairs = @hash.dup
+ end
+
+ it "returns self" do
+ @hash.transform_keys!(&:succ).should.equal?(@hash)
+ end
+
+ it "updates self as transformed values with the given block" do
+ @hash.transform_keys!(&:to_s)
+ @hash.should == { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }
+ end
+
+ it "prevents conflicts between new keys and old ones" do
+ @hash.transform_keys!(&:succ)
+ @hash.should == { b: 1, c: 2, d: 3, e: 4 }
+ end
+
+ 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
+ @hash.transform_keys! { |_| :a }.should == { a: 4 }
+ end
+
+ context "when no block is given" do
+ it "returns a sized Enumerator" do
+ enumerator = @hash.transform_keys!
+ enumerator.should.instance_of?(Enumerator)
+ enumerator.size.should == @hash.size
+ enumerator.each(&:upcase).should == { A: 1, B: 2, C: 3, D: 4 }
+ end
+ end
+
+ it "allows a hash argument" do
+ @hash.transform_keys!({ a: :A, b: :B, c: :C, d: :D })
+ @hash.should == { A: 1, B: 2, C: 3, D: 4 }
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @hash.freeze
+ end
+
+ it "raises a FrozenError on an empty hash" do
+ ->{ {}.freeze.transform_keys!(&:upcase) }.should.raise(FrozenError)
+ end
+
+ it "keeps pairs and raises a FrozenError" do
+ ->{ @hash.transform_keys!(&:upcase) }.should.raise(FrozenError)
+ @hash.should == @initial_pairs
+ end
+
+ it "raises a FrozenError on hash argument" do
+ ->{ @hash.transform_keys!({ a: :A, b: :B, c: :C }) }.should.raise(FrozenError)
+ end
+
+ context "when no block is given" do
+ it "does not raise an exception" do
+ @hash.transform_keys!.should.instance_of?(Enumerator)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/transform_values_spec.rb b/spec/ruby/core/hash/transform_values_spec.rb
new file mode 100644
index 0000000000..b19543a9f4
--- /dev/null
+++ b/spec/ruby/core/hash/transform_values_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+
+describe "Hash#transform_values" do
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ end
+
+ it "returns new hash" do
+ ret = @hash.transform_values(&:succ)
+ ret.should_not.equal?(@hash)
+ ret.should.instance_of?(Hash)
+ end
+
+ it "sets the result as transformed values with the given block" do
+ @hash.transform_values(&:succ).should == { a: 2, b: 3, c: 4 }
+ end
+
+ it "makes both hashes to share keys" do
+ key = [1, 2, 3]
+ new_hash = { key => 1 }.transform_values(&:succ)
+ new_hash[key].should == 2
+ new_hash.keys[0].should.equal?(key)
+ end
+
+ context "when no block is given" do
+ it "returns a sized Enumerator" do
+ enumerator = @hash.transform_values
+ enumerator.should.instance_of?(Enumerator)
+ enumerator.size.should == @hash.size
+ enumerator.each(&:succ).should == { a: 2, b: 3, c: 4 }
+ end
+ end
+
+ it "returns a Hash instance, even on subclasses" do
+ klass = Class.new(Hash)
+ h = klass.new
+ h[:foo] = 42
+ r = h.transform_values{|v| 2 * v}
+ 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 == nil
+ h[:a] = 1
+ h.transform_values(&:succ).default.should == 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 == nil
+ h[:a] = 1
+ h.transform_values(&:succ).default_proc.should == 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
+ before :each do
+ @hash = { a: 1, b: 2, c: 3 }
+ @initial_pairs = @hash.dup
+ end
+
+ it "returns self" do
+ @hash.transform_values!(&:succ).should.equal?(@hash)
+ end
+
+ it "updates self as transformed values with the given block" do
+ @hash.transform_values!(&:succ)
+ @hash.should == { a: 2, b: 3, c: 4 }
+ end
+
+ it "partially modifies the contents if we broke from the block" do
+ @hash.transform_values! do |v|
+ break if v == 3
+ 100 + v
+ end
+ @hash.should == { a: 101, b: 102, c: 3}
+ end
+
+ context "when no block is given" do
+ it "returns a sized Enumerator" do
+ enumerator = @hash.transform_values!
+ enumerator.should.instance_of?(Enumerator)
+ enumerator.size.should == @hash.size
+ enumerator.each(&:succ)
+ @hash.should == { a: 2, b: 3, c: 4 }
+ end
+ end
+
+ describe "on frozen instance" do
+ before :each do
+ @hash.freeze
+ end
+
+ it "raises a FrozenError on an empty hash" do
+ ->{ {}.freeze.transform_values!(&:succ) }.should.raise(FrozenError)
+ end
+
+ it "keeps pairs and raises a FrozenError" do
+ ->{ @hash.transform_values!(&:succ) }.should.raise(FrozenError)
+ @hash.should == @initial_pairs
+ end
+
+ context "when no block is given" do
+ it "does not raise an exception" do
+ @hash.transform_values!.should.instance_of?(Enumerator)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/hash/try_convert_spec.rb b/spec/ruby/core/hash/try_convert_spec.rb
new file mode 100644
index 0000000000..c321183356
--- /dev/null
+++ b/spec/ruby/core/hash/try_convert_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash.try_convert" do
+ it "returns the argument if it's a Hash" do
+ x = Hash.new
+ Hash.try_convert(x).should.equal?(x)
+ end
+
+ it "returns the argument if it's a kind of Hash" do
+ x = HashSpecs::MyHash.new
+ Hash.try_convert(x).should.equal?(x)
+ end
+
+ it "returns nil when the argument does not respond to #to_hash" do
+ Hash.try_convert(Object.new).should == nil
+ end
+
+ it "sends #to_hash to the argument and returns the result if it's nil" do
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_return(nil)
+ Hash.try_convert(obj).should == nil
+ end
+
+ it "sends #to_hash to the argument and returns the result if it's a Hash" do
+ x = Hash.new
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_return(x)
+ Hash.try_convert(obj).should.equal?(x)
+ end
+
+ it "sends #to_hash to the argument and returns the result if it's a kind of Hash" do
+ x = HashSpecs::MyHash.new
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_return(x)
+ Hash.try_convert(obj).should.equal?(x)
+ end
+
+ it "sends #to_hash to the argument and raises TypeError if it's not a kind of Hash" do
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_return(Object.new)
+ -> { Hash.try_convert obj }.should raise_consistent_error(TypeError, "can't convert MockObject into Hash (MockObject#to_hash gives Object)")
+ end
+
+ it "does not rescue exceptions raised by #to_hash" do
+ obj = mock("to_hash")
+ obj.should_receive(:to_hash).and_raise(RuntimeError)
+ -> { Hash.try_convert obj }.should.raise(RuntimeError)
+ end
+end
diff --git a/spec/ruby/core/hash/update_spec.rb b/spec/ruby/core/hash/update_spec.rb
new file mode 100644
index 0000000000..f3a3e6b4db
--- /dev/null
+++ b/spec/ruby/core/hash/update_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#update" do
+ it "adds the entries from other, overwriting duplicate keys. Returns self" do
+ h = { _1: 'a', _2: '3' }
+ h.update(_1: '9', _9: 2).should.equal?(h)
+ h.should == { _1: "9", _2: "3", _9: 2 }
+ end
+
+ it "sets any duplicate key to the value of block if passed a block" do
+ h1 = { a: 2, b: -1 }
+ h2 = { a: -2, c: 1 }
+ h1.update(h2) { |k,x,y| 3.14 }.should.equal?(h1)
+ h1.should == { c: 1, b: -1, a: 3.14 }
+
+ h1.update(h1) { nil }
+ h1.should == { a: nil, b: nil, c: nil }
+ end
+
+ it "tries to convert the passed argument to a hash using #to_hash" do
+ obj = mock('{1=>2}')
+ obj.should_receive(:to_hash).and_return({ 1 => 2 })
+ { 3 => 4 }.update(obj).should == { 1 => 2, 3 => 4 }
+ end
+
+ it "does not call to_hash on hash subclasses" do
+ { 3 => 4 }.update(HashSpecs::ToHashHash[1 => 2]).should == { 1 => 2, 3 => 4 }
+ end
+
+ it "processes entries with same order as merge()" do
+ h = { 1 => 2, 3 => 4, 5 => 6, "x" => nil, nil => 5, [] => [] }
+ merge_bang_pairs = []
+ merge_pairs = []
+ h.merge(h) { |*arg| merge_pairs << arg }
+ h.update(h) { |*arg| merge_bang_pairs << arg }
+ merge_bang_pairs.should == merge_pairs
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ -> do
+ HashSpecs.frozen_hash.update(1 => 2)
+ end.should.raise(FrozenError)
+ end
+
+ it "checks frozen status before coercing an object with #to_hash" do
+ obj = mock("to_hash frozen")
+ # This is necessary because mock cleanup code cannot run on the frozen
+ # object.
+ def obj.to_hash() raise Exception, "should not receive #to_hash" end
+ obj.freeze
+
+ -> { HashSpecs.frozen_hash.update(obj) }.should.raise(FrozenError)
+ end
+
+ # see redmine #1571
+ it "raises a FrozenError on a frozen instance that would not be modified" do
+ -> do
+ HashSpecs.frozen_hash.update(HashSpecs.empty_frozen_hash)
+ end.should.raise(FrozenError)
+ 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}
+ hash2 = {1 => :foo, 3 => :bar}
+ hash.each { hash.update(hash2) }
+ hash.should == {1 => :foo, 3 => :bar, 5 => 6}
+ end
+
+ it "accepts multiple hashes" do
+ result = { a: 1 }.update({ b: 2 }, { c: 3 }, { d: 4 })
+ result.should == { a: 1, b: 2, c: 3, d: 4 }
+ end
+
+ it "accepts zero arguments" do
+ hash = { a: 1 }
+ hash.update.should.eql?(hash)
+ end
+end
diff --git a/spec/ruby/core/hash/value_spec.rb b/spec/ruby/core/hash/value_spec.rb
new file mode 100644
index 0000000000..9cfbe576d2
--- /dev/null
+++ b/spec/ruby/core/hash/value_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+
+describe "Hash#value?" do
+ it "is an alias of Hash#has_value?" do
+ Hash.instance_method(:value?).should == Hash.instance_method(:has_value?)
+ end
+end
diff --git a/spec/ruby/core/hash/values_at_spec.rb b/spec/ruby/core/hash/values_at_spec.rb
new file mode 100644
index 0000000000..78dcd8df6a
--- /dev/null
+++ b/spec/ruby/core/hash/values_at_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Hash#values_at" do
+ it "returns an array of values for the given keys" do
+ h = { a: 9, b: 'a', c: -10, d: nil }
+ h.values_at.should.is_a?(Array)
+ h.values_at.should == []
+ h.values_at(:a, :d, :b).should.is_a?(Array)
+ h.values_at(:a, :d, :b).should == [9, nil, 'a']
+ end
+end
diff --git a/spec/ruby/core/hash/values_spec.rb b/spec/ruby/core/hash/values_spec.rb
new file mode 100644
index 0000000000..1fe5f48ad6
--- /dev/null
+++ b/spec/ruby/core/hash/values_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Hash#values" do
+ it "returns an array of values" do
+ h = { 1 => :a, 'a' => :a, 'the' => 'lang' }
+ h.values.should.is_a?(Array)
+ h.values.sort {|a, b| a.to_s <=> b.to_s}.should == [:a, :a, 'lang']
+ end
+end
diff --git a/spec/ruby/core/integer/abs_spec.rb b/spec/ruby/core/integer/abs_spec.rb
new file mode 100644
index 0000000000..c40356db12
--- /dev/null
+++ b/spec/ruby/core/integer/abs_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Integer#abs" do
+ it_behaves_like :integer_abs, :abs
+end
diff --git a/spec/ruby/core/integer/allbits_spec.rb b/spec/ruby/core/integer/allbits_spec.rb
new file mode 100644
index 0000000000..6023cc32bf
--- /dev/null
+++ b/spec/ruby/core/integer/allbits_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+
+describe "Integer#allbits?" do
+ it "returns true if and only if all the bits of the argument are set in the receiver" do
+ 42.allbits?(42).should == true
+ 0b1010_1010.allbits?(0b1000_0010).should == true
+ 0b1010_1010.allbits?(0b1000_0001).should == false
+ 0b1000_0010.allbits?(0b1010_1010).should == false
+ (0b1010_1010 | bignum_value).allbits?(0b1000_0010 | bignum_value).should == true
+ (0b1010_1010 | bignum_value).allbits?(0b1000_0001 | bignum_value).should == false
+ (0b1000_0010 | bignum_value).allbits?(0b1010_1010 | bignum_value).should == false
+ end
+
+ it "handles negative values using two's complement notation" do
+ (~0b1).allbits?(42).should == true
+ (-42).allbits?(-42).should == true
+ (~0b1010_1010).allbits?(~0b1110_1011).should == true
+ (~0b1010_1010).allbits?(~0b1000_0010).should == false
+ (~(0b1010_1010 | bignum_value)).allbits?(~(0b1110_1011 | bignum_value)).should == true
+ (~(0b1010_1010 | bignum_value)).allbits?(~(0b1000_0010 | bignum_value)).should == false
+ end
+
+ it "coerces the rhs using to_int" do
+ obj = mock("the int 0b10")
+ obj.should_receive(:to_int).and_return(0b10)
+ 0b110.allbits?(obj).should == true
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:coerce).any_number_of_times.and_return([42,10])
+ 13.allbits?(obj)
+ }.should.raise(TypeError)
+ -> { 13.allbits?("10") }.should.raise(TypeError)
+ -> { 13.allbits?(:symbol) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/anybits_spec.rb b/spec/ruby/core/integer/anybits_spec.rb
new file mode 100644
index 0000000000..1ea47b76dc
--- /dev/null
+++ b/spec/ruby/core/integer/anybits_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Integer#anybits?" do
+ it "returns true if and only if all the bits of the argument are set in the receiver" do
+ 42.anybits?(42).should == true
+ 0b1010_1010.anybits?(0b1000_0010).should == true
+ 0b1010_1010.anybits?(0b1000_0001).should == true
+ 0b1000_0010.anybits?(0b0010_1100).should == false
+ different_bignum = (2 * bignum_value) & (~bignum_value)
+ (0b1010_1010 | different_bignum).anybits?(0b1000_0010 | bignum_value).should == true
+ (0b1010_1010 | different_bignum).anybits?(0b0010_1100 | bignum_value).should == true
+ (0b1000_0010 | different_bignum).anybits?(0b0010_1100 | bignum_value).should == false
+ end
+
+ it "handles negative values using two's complement notation" do
+ (~42).anybits?(42).should == false
+ (-42).anybits?(-42).should == true
+ (~0b100).anybits?(~0b1).should == true
+ (~(0b100 | bignum_value)).anybits?(~(0b1 | bignum_value)).should == true
+ end
+
+ it "coerces the rhs using to_int" do
+ obj = mock("the int 0b10")
+ obj.should_receive(:to_int).and_return(0b10)
+ 0b110.anybits?(obj).should == true
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:coerce).any_number_of_times.and_return([42,10])
+ 13.anybits?(obj)
+ }.should.raise(TypeError)
+ -> { 13.anybits?("10") }.should.raise(TypeError)
+ -> { 13.anybits?(:symbol) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/bit_and_spec.rb b/spec/ruby/core/integer/bit_and_spec.rb
new file mode 100644
index 0000000000..4098ad7c7b
--- /dev/null
+++ b/spec/ruby/core/integer/bit_and_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+
+describe "Integer#&" do
+ context "fixnum" do
+ it "returns self bitwise AND other" do
+ (256 & 16).should == 0
+ (2010 & 5).should == 0
+ (65535 & 1).should == 1
+ (0xffff & bignum_value + 0xffff_ffff).should == 65535
+ end
+
+ it "returns self bitwise AND other when one operand is negative" do
+ ((1 << 33) & -1).should == (1 << 33)
+ (-1 & (1 << 33)).should == (1 << 33)
+
+ ((-(1<<33)-1) & 5).should == 5
+ (5 & (-(1<<33)-1)).should == 5
+ end
+
+ it "returns self bitwise AND other when both operands are negative" do
+ (-5 & -1).should == -5
+ (-3 & -4).should == -4
+ (-12 & -13).should == -16
+ (-13 & -12).should == -16
+ end
+
+ it "returns self bitwise AND a bignum" do
+ (-1 & 2**64).should == 18446744073709551616
+ end
+
+ it "coerces the rhs and calls #coerce" do
+ obj = mock("fixnum bit and")
+ obj.should_receive(:coerce).with(6).and_return([6, 3])
+ (6 & obj).should == 2
+ end
+
+ it "raises a TypeError when passed a Float" do
+ -> { (3 & 3.4) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("fixnum bit and")
+ obj.should_not_receive(:to_int)
+
+ -> { 3 & obj }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(5)
+ end
+
+ it "returns self bitwise AND other" do
+ @bignum = bignum_value(5)
+ (@bignum & 3).should == 1
+ (@bignum & 52).should == 4
+ (@bignum & bignum_value(9921)).should == 18446744073709551617
+
+ ((2*bignum_value) & 1).should == 0
+ ((2*bignum_value) & (2*bignum_value)).should == 36893488147419103232
+ end
+
+ it "returns self bitwise AND other when one operand is negative" do
+ ((2*bignum_value) & -1).should == (2*bignum_value)
+ ((4*bignum_value) & -1).should == (4*bignum_value)
+ (@bignum & -0xffffffffffffff5).should == 18446744073709551617
+ (@bignum & -@bignum).should == 1
+ (@bignum & -0x8000000000000000).should == 18446744073709551616
+ end
+
+ it "returns self bitwise AND other when both operands are negative" do
+ (-@bignum & -0x4000000000000005).should == -23058430092136939525
+ (-@bignum & -@bignum).should == -18446744073709551621
+ (-@bignum & -0x4000000000000000).should == -23058430092136939520
+ end
+
+ it "returns self bitwise AND other when both are negative and a multiple in bitsize of Fixnum::MIN" do
+ val = - ((1 << 93) - 1)
+ (val & val).should == val
+
+ val = - ((1 << 126) - 1)
+ (val & val).should == val
+ end
+
+ it "raises a TypeError when passed a Float" do
+ -> { (@bignum & 3.4) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("bignum bit and")
+ obj.should_not_receive(:to_int)
+
+ -> { @bignum & obj }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/bit_length_spec.rb b/spec/ruby/core/integer/bit_length_spec.rb
new file mode 100644
index 0000000000..827007b7e0
--- /dev/null
+++ b/spec/ruby/core/integer/bit_length_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+
+describe "Integer#bit_length" do
+ context "fixnum" do
+ it "returns the position of the leftmost bit of a positive number" do
+ 0.bit_length.should == 0
+ 1.bit_length.should == 1
+ 2.bit_length.should == 2
+ 3.bit_length.should == 2
+ 4.bit_length.should == 3
+ n = fixnum_max.bit_length
+ fixnum_max[n].should == 0
+ fixnum_max[n - 1].should == 1
+
+ 0.bit_length.should == 0
+ 1.bit_length.should == 1
+ 0xff.bit_length.should == 8
+ 0x100.bit_length.should == 9
+ (2**12 - 1).bit_length.should == 12
+ (2**12).bit_length.should == 13
+ (2**12 + 1).bit_length.should == 13
+ end
+
+ it "returns the position of the leftmost 0 bit of a negative number" do
+ -1.bit_length.should == 0
+ -2.bit_length.should == 1
+ -3.bit_length.should == 2
+ -4.bit_length.should == 2
+ -5.bit_length.should == 3
+ n = fixnum_min.bit_length
+ fixnum_min[n].should == 1
+ fixnum_min[n - 1].should == 0
+
+ (-2**12 - 1).bit_length.should == 13
+ (-2**12).bit_length.should == 12
+ (-2**12 + 1).bit_length.should == 12
+ -0x101.bit_length.should == 9
+ -0x100.bit_length.should == 8
+ -0xff.bit_length.should == 8
+ -2.bit_length.should == 1
+ -1.bit_length.should == 0
+ end
+ end
+
+ context "bignum" do
+ it "returns the position of the leftmost bit of a positive number" do
+ (2**1000-1).bit_length.should == 1000
+ (2**1000).bit_length.should == 1001
+ (2**1000+1).bit_length.should == 1001
+
+ (2**10000-1).bit_length.should == 10000
+ (2**10000).bit_length.should == 10001
+ (2**10000+1).bit_length.should == 10001
+
+ (1 << 100).bit_length.should == 101
+ (1 << 100).succ.bit_length.should == 101
+ (1 << 100).pred.bit_length.should == 100
+ (1 << 10000).bit_length.should == 10001
+ end
+
+ it "returns the position of the leftmost 0 bit of a negative number" do
+ (-2**10000-1).bit_length.should == 10001
+ (-2**10000).bit_length.should == 10000
+ (-2**10000+1).bit_length.should == 10000
+
+ (-2**1000-1).bit_length.should == 1001
+ (-2**1000).bit_length.should == 1000
+ (-2**1000+1).bit_length.should == 1000
+
+ ((-1 << 100)-1).bit_length.should == 101
+ ((-1 << 100)-1).succ.bit_length.should == 100
+ ((-1 << 100)-1).pred.bit_length.should == 101
+ ((-1 << 10000)-1).bit_length.should == 10001
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/bit_or_spec.rb b/spec/ruby/core/integer/bit_or_spec.rb
new file mode 100644
index 0000000000..c380cd729e
--- /dev/null
+++ b/spec/ruby/core/integer/bit_or_spec.rb
@@ -0,0 +1,89 @@
+require_relative '../../spec_helper'
+
+describe "Integer#|" do
+ context "fixnum" do
+ it "returns self bitwise OR other" do
+ (1 | 0).should == 1
+ (5 | 4).should == 5
+ (5 | 6).should == 7
+ (248 | 4096).should == 4344
+ (0xffff | bignum_value + 0xf0f0).should == 0x1_0000_0000_0000_ffff
+ end
+
+ it "returns self bitwise OR other when one operand is negative" do
+ ((1 << 33) | -1).should == -1
+ (-1 | (1 << 33)).should == -1
+
+ ((-(1<<33)-1) | 5).should == -8589934593
+ (5 | (-(1<<33)-1)).should == -8589934593
+ end
+
+ it "returns self bitwise OR other when both operands are negative" do
+ (-5 | -1).should == -1
+ (-3 | -4).should == -3
+ (-12 | -13).should == -9
+ (-13 | -12).should == -9
+ end
+
+ it "returns self bitwise OR a bignum" do
+ (-1 | 2**64).should == -1
+ end
+
+ it "coerces the rhs and calls #coerce" do
+ obj = mock("fixnum bit or")
+ obj.should_receive(:coerce).with(6).and_return([6, 3])
+ (6 | obj).should == 7
+ end
+
+ it "raises a TypeError when passed a Float" do
+ -> { (3 | 3.4) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("integer bit or")
+ obj.should_not_receive(:to_int)
+
+ -> { 3 | obj }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(11)
+ end
+
+ it "returns self bitwise OR other" do
+ (@bignum | 2).should == 18446744073709551627
+ (@bignum | 9).should == 18446744073709551627
+ (@bignum | bignum_value).should == 18446744073709551627
+ end
+
+ it "returns self bitwise OR other when one operand is negative" do
+ (@bignum | -0x40000000000000000).should == -55340232221128654837
+ (@bignum | -@bignum).should == -1
+ (@bignum | -0x8000000000000000).should == -9223372036854775797
+ end
+
+ it "returns self bitwise OR other when both operands are negative" do
+ (-@bignum | -0x4000000000000005).should == -1
+ (-@bignum | -@bignum).should == -18446744073709551627
+ (-@bignum | -0x4000000000000000).should == -11
+ end
+
+ it "raises a TypeError when passed a Float" do
+ not_supported_on :opal do
+ -> {
+ bignum_value | bignum_value(0xffff).to_f
+ }.should.raise(TypeError)
+ end
+ -> { @bignum | 9.9 }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("bignum bit or")
+ obj.should_not_receive(:to_int)
+
+ -> { @bignum | obj }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/bit_xor_spec.rb b/spec/ruby/core/integer/bit_xor_spec.rb
new file mode 100644
index 0000000000..c0cf8405f7
--- /dev/null
+++ b/spec/ruby/core/integer/bit_xor_spec.rb
@@ -0,0 +1,93 @@
+require_relative '../../spec_helper'
+
+describe "Integer#^" do
+ context "fixnum" do
+ it "returns self bitwise EXCLUSIVE OR other" do
+ (3 ^ 5).should == 6
+ (-2 ^ -255).should == 255
+ (5 ^ bignum_value + 0xffff_ffff).should == 0x1_0000_0000_ffff_fffa
+ end
+
+ it "returns self bitwise XOR other when one operand is negative" do
+ ((1 << 33) ^ -1).should == -8589934593
+ (-1 ^ (1 << 33)).should == -8589934593
+
+ ((-(1<<33)-1) ^ 5).should == -8589934598
+ (5 ^ (-(1<<33)-1)).should == -8589934598
+ end
+
+ it "returns self bitwise XOR other when both operands are negative" do
+ (-5 ^ -1).should == 4
+ (-3 ^ -4).should == 1
+ (-12 ^ -13).should == 7
+ (-13 ^ -12).should == 7
+ end
+
+ it "returns self bitwise EXCLUSIVE OR a bignum" do
+ (-1 ^ 2**64).should == -18446744073709551617
+ end
+
+ it "coerces the rhs and calls #coerce" do
+ obj = mock("fixnum bit xor")
+ obj.should_receive(:coerce).with(6).and_return([6, 3])
+ (6 ^ obj).should == 5
+ end
+
+ it "raises a TypeError when passed a Float" do
+ -> { (3 ^ 3.4) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("integer bit xor")
+ obj.should_not_receive(:to_int)
+
+ -> { 3 ^ obj }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(18)
+ end
+
+ it "returns self bitwise EXCLUSIVE OR other" do
+ (@bignum ^ 2).should == 18446744073709551632
+ (@bignum ^ @bignum).should == 0
+ (@bignum ^ 14).should == 18446744073709551644
+ end
+
+ it "returns self bitwise EXCLUSIVE OR other when one operand is negative" do
+ (@bignum ^ -0x40000000000000000).should == -55340232221128654830
+ (@bignum ^ -@bignum).should == -4
+ (@bignum ^ -0x8000000000000000).should == -27670116110564327406
+ end
+
+ it "returns self bitwise EXCLUSIVE OR other when both operands are negative" do
+ (-@bignum ^ -0x40000000000000000).should == 55340232221128654830
+ (-@bignum ^ -@bignum).should == 0
+ (-@bignum ^ -0x4000000000000000).should == 23058430092136939502
+ end
+
+ it "returns self bitwise EXCLUSIVE OR other when all bits are 1 and other value is negative" do
+ (9903520314283042199192993791 ^ -1).should == -9903520314283042199192993792
+ (784637716923335095479473677900958302012794430558004314111 ^ -1).should ==
+ -784637716923335095479473677900958302012794430558004314112
+ end
+
+ it "raises a TypeError when passed a Float" do
+ not_supported_on :opal do
+ -> {
+ bignum_value ^ bignum_value(0xffff).to_f
+ }.should.raise(TypeError)
+ end
+ -> { @bignum ^ 14.5 }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError and does not call #to_int when defined on an object" do
+ obj = mock("bignum bit xor")
+ obj.should_not_receive(:to_int)
+
+ -> { @bignum ^ obj }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/case_compare_spec.rb b/spec/ruby/core/integer/case_compare_spec.rb
new file mode 100644
index 0000000000..e5dde2c64a
--- /dev/null
+++ b/spec/ruby/core/integer/case_compare_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Integer#===" do
+ it_behaves_like :integer_equal, :===
+end
diff --git a/spec/ruby/core/integer/ceil_spec.rb b/spec/ruby/core/integer/ceil_spec.rb
new file mode 100644
index 0000000000..395be58fbd
--- /dev/null
+++ b/spec/ruby/core/integer/ceil_spec.rb
@@ -0,0 +1,13 @@
+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
+end
diff --git a/spec/ruby/core/integer/ceildiv_spec.rb b/spec/ruby/core/integer/ceildiv_spec.rb
new file mode 100644
index 0000000000..91f63832f8
--- /dev/null
+++ b/spec/ruby/core/integer/ceildiv_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Integer#ceildiv" 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)
+
+ 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)
+
+ (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
new file mode 100644
index 0000000000..72784e1833
--- /dev/null
+++ b/spec/ruby/core/integer/chr_spec.rb
@@ -0,0 +1,257 @@
+require_relative '../../spec_helper'
+
+describe "Integer#chr without argument" do
+ it "returns a String" do
+ 17.chr.should.instance_of?(String)
+ end
+
+ it "returns a new String for each call" do
+ 82.chr.should_not.equal?(82.chr)
+ end
+
+ it "raises a RangeError is self is less than 0" do
+ -> { -1.chr }.should.raise(RangeError, /-1 out of char range/)
+ -> { (-bignum_value).chr }.should.raise(RangeError, /bignum out of char range/)
+ end
+
+ it "raises a RangeError if self is too large" do
+ -> { 2206368128.chr(Encoding::UTF_8) }.should.raise(RangeError, /2206368128 out of char range/)
+ end
+
+ describe "when Encoding.default_internal is nil" do
+ describe "and self is between 0 and 127 (inclusive)" do
+ it "returns a US-ASCII String" do
+ (0..127).each do |c|
+ c.chr.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "returns a String encoding self interpreted as a US-ASCII codepoint" do
+ (0..127).each do |c|
+ c.chr.bytes.to_a.should == [c]
+ end
+ end
+ end
+
+ describe "and self is between 128 and 255 (inclusive)" do
+ it "returns a binary String" do
+ (128..255).each do |c|
+ c.chr.encoding.should == Encoding::BINARY
+ end
+ end
+
+ it "returns a String containing self interpreted as a byte" do
+ (128..255).each do |c|
+ c.chr.bytes.to_a.should == [c]
+ end
+ end
+ end
+
+ it "raises a RangeError is self is greater than 255" do
+ -> { 256.chr }.should.raise(RangeError, /256 out of char range/)
+ -> { bignum_value.chr }.should.raise(RangeError, /bignum out of char range/)
+ end
+ end
+
+ describe "when Encoding.default_internal is not nil" do
+ before do
+ @default_internal = Encoding.default_internal
+ end
+
+ after do
+ Encoding.default_internal = @default_internal
+ end
+
+ describe "and self is between 0 and 127 (inclusive)" do
+ it "returns a US-ASCII String" do
+ (0..127).each do |c|
+ Encoding.default_internal = Encoding::UTF_8
+ c.chr.encoding.should == Encoding::US_ASCII
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ c.chr.encoding.should == Encoding::US_ASCII
+ end
+ end
+
+ it "returns a String encoding self interpreted as a US-ASCII codepoint" do
+ (0..127).each do |c|
+ Encoding.default_internal = Encoding::UTF_8
+ c.chr.bytes.to_a.should == [c]
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ c.chr.bytes.to_a.should == [c]
+ end
+ end
+ end
+
+ describe "and self is between 128 and 255 (inclusive)" do
+ it "returns a binary String" do
+ (128..255).each do |c|
+ Encoding.default_internal = Encoding::UTF_8
+ c.chr.encoding.should == Encoding::BINARY
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ c.chr.encoding.should == Encoding::BINARY
+ end
+ end
+
+ it "returns a String containing self interpreted as a byte" do
+ (128..255).each do |c|
+ Encoding.default_internal = Encoding::UTF_8
+ c.chr.bytes.to_a.should == [c]
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ c.chr.bytes.to_a.should == [c]
+ end
+ end
+ end
+
+ describe "and self is greater than 255" do
+ it "returns a String with the default internal encoding" do
+ Encoding.default_internal = Encoding::UTF_8
+ 0x0100.chr.encoding.should == Encoding::UTF_8
+ 0x3000.chr.encoding.should == Encoding::UTF_8
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ 0x8140.chr.encoding.should == Encoding::SHIFT_JIS
+ 0xFC4B.chr.encoding.should == Encoding::SHIFT_JIS
+ end
+
+ it "returns a String encoding self interpreted as a codepoint in the default internal encoding" do
+ Encoding.default_internal = Encoding::UTF_8
+ 0x0100.chr.bytes.to_a.should == [0xC4, 0x80]
+ 0x3000.chr.bytes.to_a.should == [0xE3, 0x80, 0x80]
+
+ Encoding.default_internal = Encoding::SHIFT_JIS
+ 0x8140.chr.bytes.to_a.should == [0x81, 0x40] # Smallest assigned CP932 codepoint greater than 255
+ 0xFC4B.chr.bytes.to_a.should == [0xFC, 0x4B] # Largest assigned CP932 codepoint
+ end
+
+ # #5864
+ it "raises RangeError if self is invalid as a codepoint in the default internal encoding" do
+ [ [0x0100, "US-ASCII"],
+ [0x0100, "BINARY"],
+ [0x0100, "EUC-JP"],
+ [0xA1A0, "EUC-JP"],
+ [0x0100, "ISO-8859-9"],
+ [620, "TIS-620"]
+ ].each do |integer, encoding_name|
+ Encoding.default_internal = Encoding.find(encoding_name)
+ -> { integer.chr }.should.raise(RangeError, /(invalid codepoint|out of char range)/)
+ end
+ end
+ end
+ end
+end
+
+describe "Integer#chr with an encoding argument" do
+ it "returns a String" do
+ 900.chr(Encoding::UTF_8).should.instance_of?(String)
+ end
+
+ it "returns a new String for each call" do
+ 8287.chr(Encoding::UTF_8).should_not.equal?(8287.chr(Encoding::UTF_8))
+ end
+
+ it "accepts a String as an argument" do
+ -> { 0xA4A2.chr('euc-jp') }.should_not.raise
+ end
+
+ it "converts a String to an Encoding as Encoding.find does" do
+ ['utf-8', 'UTF-8', 'Utf-8'].each do |encoding|
+ 7894.chr(encoding).encoding.should == Encoding::UTF_8
+ end
+ end
+
+ # 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(RangeError, /-1 out of char range/)
+ -> { (-bignum_value).chr(Encoding::EUC_JP) }.should.raise(RangeError, /bignum out of char range/)
+ end
+
+ it "raises a RangeError if self is too large" do
+ -> { 2206368128.chr(Encoding::UTF_8) }.should.raise(RangeError, /2206368128 out of char range/)
+ end
+
+ it "returns a String with the specified encoding" do
+ 0x0000.chr(Encoding::US_ASCII).encoding.should == Encoding::US_ASCII
+ 0x007F.chr(Encoding::US_ASCII).encoding.should == Encoding::US_ASCII
+
+ 0x0000.chr(Encoding::BINARY).encoding.should == Encoding::BINARY
+ 0x007F.chr(Encoding::BINARY).encoding.should == Encoding::BINARY
+ 0x0080.chr(Encoding::BINARY).encoding.should == Encoding::BINARY
+ 0x00FF.chr(Encoding::BINARY).encoding.should == Encoding::BINARY
+
+ 0x0000.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x007F.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x0080.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x00FF.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x0100.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+ 0x3000.chr(Encoding::UTF_8).encoding.should == Encoding::UTF_8
+
+ 0x0000.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0x007F.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0x00A1.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0x00DF.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0x8140.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ 0xFC4B.chr(Encoding::SHIFT_JIS).encoding.should == Encoding::SHIFT_JIS
+ end
+
+ it "returns a String encoding self interpreted as a codepoint in the specified encoding" do
+ 0x0000.chr(Encoding::US_ASCII).bytes.to_a.should == [0x00]
+ 0x007F.chr(Encoding::US_ASCII).bytes.to_a.should == [0x7F]
+
+ 0x0000.chr(Encoding::BINARY).bytes.to_a.should == [0x00]
+ 0x007F.chr(Encoding::BINARY).bytes.to_a.should == [0x7F]
+ 0x0080.chr(Encoding::BINARY).bytes.to_a.should == [0x80]
+ 0x00FF.chr(Encoding::BINARY).bytes.to_a.should == [0xFF]
+
+ 0x0000.chr(Encoding::UTF_8).bytes.to_a.should == [0x00]
+ 0x007F.chr(Encoding::UTF_8).bytes.to_a.should == [0x7F]
+ 0x0080.chr(Encoding::UTF_8).bytes.to_a.should == [0xC2, 0x80]
+ 0x00FF.chr(Encoding::UTF_8).bytes.to_a.should == [0xC3, 0xBF]
+ 0x0100.chr(Encoding::UTF_8).bytes.to_a.should == [0xC4, 0x80]
+ 0x3000.chr(Encoding::UTF_8).bytes.to_a.should == [0xE3, 0x80, 0x80]
+
+ 0x0000.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0x00]
+ 0x007F.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0x7F]
+ 0x00A1.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0xA1]
+ 0x00DF.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0xDF]
+ 0x8140.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0x81, 0x40] # Smallest assigned CP932 codepoint greater than 255
+ 0xFC4B.chr(Encoding::SHIFT_JIS).bytes.to_a.should == [0xFC, 0x4B] # Largest assigned CP932 codepoint
+ end
+
+ # #5864
+ it "raises RangeError if self is invalid as a codepoint in the specified encoding" do
+ -> { 0x80.chr("US-ASCII") }.should.raise(RangeError)
+ -> { 0x0100.chr("BINARY") }.should.raise(RangeError)
+ -> { 0x0100.chr("EUC-JP") }.should.raise(RangeError)
+ -> { 0xA1A0.chr("EUC-JP") }.should.raise(RangeError)
+ -> { 0xA1.chr("EUC-JP") }.should.raise(RangeError)
+ -> { 0x80.chr("SHIFT_JIS") }.should.raise(RangeError)
+ -> { 0xE0.chr("SHIFT_JIS") }.should.raise(RangeError)
+ -> { 0x0100.chr("ISO-8859-9") }.should.raise(RangeError)
+ -> { 620.chr("TIS-620") }.should.raise(RangeError)
+ # UTF-16 surrogate range
+ -> { 0xD800.chr("UTF-8") }.should.raise(RangeError)
+ -> { 0xDBFF.chr("UTF-8") }.should.raise(RangeError)
+ -> { 0xDC00.chr("UTF-8") }.should.raise(RangeError)
+ -> { 0xDFFF.chr("UTF-8") }.should.raise(RangeError)
+ # UTF-16 surrogate range
+ -> { 0xD800.chr("UTF-16") }.should.raise(RangeError)
+ -> { 0xDBFF.chr("UTF-16") }.should.raise(RangeError)
+ -> { 0xDC00.chr("UTF-16") }.should.raise(RangeError)
+ -> { 0xDFFF.chr("UTF-16") }.should.raise(RangeError)
+ end
+
+ it 'returns a String encoding self interpreted as a codepoint in the CESU-8 encoding' do
+ # see more details here https://en.wikipedia.org/wiki/CESU-8
+ # code points from U+0000 to U+FFFF is encoded in the same way as in UTF-8
+ 0x0045.chr(Encoding::CESU_8).bytes.should == 0x0045.chr(Encoding::UTF_8).bytes
+
+ # code points in range from U+10000 to U+10FFFF is CESU-8 data containing a 6-byte surrogate pair,
+ # which decodes to a 4-byte UTF-8 string
+ 0x10400.chr(Encoding::CESU_8).bytes.should != 0x10400.chr(Encoding::UTF_8).bytes
+ 0x10400.chr(Encoding::CESU_8).bytes.to_a.should == [0xED, 0xA0, 0x81, 0xED, 0xB0, 0x80]
+ end
+end
diff --git a/spec/ruby/core/integer/coerce_spec.rb b/spec/ruby/core/integer/coerce_spec.rb
new file mode 100644
index 0000000000..c0e642da03
--- /dev/null
+++ b/spec/ruby/core/integer/coerce_spec.rb
@@ -0,0 +1,91 @@
+require_relative '../../spec_helper'
+
+describe "Integer#coerce" do
+ context "fixnum" do
+ describe "when given a Fixnum" do
+ it "returns an array containing two Fixnums" do
+ 1.coerce(2).should == [2, 1]
+ 1.coerce(2).map { |i| i.class }.should == [Integer, Integer]
+ end
+ end
+
+ describe "when given a String" do
+ it "raises an ArgumentError when trying to coerce with a non-number String" do
+ -> { 1.coerce(":)") }.should.raise(ArgumentError)
+ end
+
+ it "returns an array containing two Floats" do
+ 1.coerce("2").should == [2.0, 1.0]
+ 1.coerce("-2").should == [-2.0, 1.0]
+ end
+ end
+
+ it "raises a TypeError when trying to coerce with nil" do
+ -> { 1.coerce(nil) }.should.raise(TypeError)
+ end
+
+ it "tries to convert the given Object into a Float by using #to_f" do
+ (obj = mock('1.0')).should_receive(:to_f).and_return(1.0)
+ 2.coerce(obj).should == [1.0, 2.0]
+
+ (obj = mock('0')).should_receive(:to_f).and_return('0')
+ -> { 2.coerce(obj).should == [1.0, 2.0] }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when given an Object that does not respond to #to_f" do
+ -> { 1.coerce(mock('x')) }.should.raise(TypeError)
+ -> { 1.coerce(1..4) }.should.raise(TypeError)
+ -> { 1.coerce(:test) }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ it "coerces other to a Bignum and returns [other, self] when passed a Fixnum" do
+ a = bignum_value
+ ary = a.coerce(2)
+
+ ary[0].should.is_a?(Integer)
+ ary[1].should.is_a?(Integer)
+ ary.should == [2, a]
+ end
+
+ it "returns [other, self] when passed a Bignum" do
+ a = bignum_value
+ b = bignum_value
+ ary = a.coerce(b)
+
+ ary[0].should.is_a?(Integer)
+ ary[1].should.is_a?(Integer)
+ ary.should == [b, a]
+ end
+
+ it "raises a TypeError when not passed a Fixnum or Bignum" do
+ a = bignum_value
+
+ -> { a.coerce(nil) }.should.raise(TypeError)
+ -> { a.coerce(mock('str')) }.should.raise(TypeError)
+ -> { a.coerce(1..4) }.should.raise(TypeError)
+ -> { a.coerce(:test) }.should.raise(TypeError)
+ end
+
+ it "coerces both values to Floats and returns [other, self] when passed a Float" do
+ a = bignum_value
+ a.coerce(1.2).should == [1.2, a.to_f]
+ end
+
+ it "coerces both values to Floats and returns [other, self] when passed a String" do
+ a = bignum_value
+ a.coerce("123").should == [123.0, a.to_f]
+ end
+
+ it "calls #to_f to coerce other to a Float" do
+ b = mock("bignum value")
+ b.should_receive(:to_f).and_return(1.2)
+
+ a = bignum_value
+ ary = a.coerce(b)
+
+ ary.should == [1.2, a.to_f]
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/comparison_spec.rb b/spec/ruby/core/integer/comparison_spec.rb
new file mode 100644
index 0000000000..cc5cbe2506
--- /dev/null
+++ b/spec/ruby/core/integer/comparison_spec.rb
@@ -0,0 +1,185 @@
+require_relative '../../spec_helper'
+
+describe "Integer#<=>" do
+ context "fixnum" do
+ it "returns -1 when self is less than the given argument" do
+ (-3 <=> -1).should == -1
+ (-5 <=> 10).should == -1
+ (-5 <=> -4.5).should == -1
+ end
+
+ it "returns 0 when self is equal to the given argument" do
+ (0 <=> 0).should == 0
+ (954 <=> 954).should == 0
+ (954 <=> 954.0).should == 0
+ end
+
+ it "returns 1 when self is greater than the given argument" do
+ (496 <=> 5).should == 1
+ (200 <=> 100).should == 1
+ (51 <=> 50.5).should == 1
+ end
+
+ it "returns nil when the given argument is not an Integer" do
+ (3 <=> mock('x')).should == nil
+ (3 <=> 'test').should == nil
+ end
+ end
+
+ context "bignum" do
+ describe "with a Fixnum" do
+ it "returns -1 when other is larger" do
+ (-bignum_value <=> 2).should == -1
+ end
+
+ it "returns 1 when other is smaller" do
+ (bignum_value <=> 2).should == 1
+ end
+ end
+
+ describe "with a Bignum" do
+ describe "when other is negative" do
+ it "returns -1 when self is negative and other is larger" do
+ (-bignum_value(42) <=> -bignum_value).should == -1
+ end
+
+ it "returns 0 when other is equal" do
+ (-bignum_value <=> -bignum_value).should == 0
+ end
+
+ it "returns 1 when self is negative and other is smaller" do
+ (-bignum_value <=> -bignum_value(94)).should == 1
+ end
+
+ it "returns 1 when self is positive" do
+ (bignum_value <=> -bignum_value).should == 1
+ end
+ end
+
+ describe "when other is positive" do
+ it "returns -1 when self is negative" do
+ (-bignum_value <=> bignum_value).should == -1
+ end
+
+ it "returns -1 when self is positive and other is larger" do
+ (bignum_value <=> bignum_value(38)).should == -1
+ end
+
+ it "returns 0 when other is equal" do
+ (bignum_value <=> bignum_value).should == 0
+ end
+
+ it "returns 1 when other is smaller" do
+ (bignum_value(56) <=> bignum_value).should == 1
+ end
+ end
+ end
+
+ describe "with a Float" do
+ describe "when other is negative" do
+ it "returns -1 when self is negative and other is larger" do
+ (-bignum_value(0xffff) <=> -bignum_value.to_f).should == -1
+ end
+
+ it "returns 0 when other is equal" do
+ (-bignum_value <=> -bignum_value.to_f).should == 0
+ end
+
+ it "returns 1 when self is negative and other is smaller" do
+ (-bignum_value <=> -bignum_value(0xffef).to_f).should == 1
+ end
+
+ it "returns 1 when self is positive" do
+ (bignum_value <=> -bignum_value.to_f).should == 1
+ end
+ end
+
+ describe "when other is positive" do
+ it "returns -1 when self is negative" do
+ (-bignum_value <=> bignum_value.to_f).should == -1
+ end
+
+ it "returns -1 when self is positive and other is larger" do
+ (bignum_value <=> bignum_value(0xfffe).to_f).should == -1
+ end
+
+ it "returns 0 when other is equal" do
+ (bignum_value <=> bignum_value.to_f).should == 0
+ end
+
+ it "returns 1 when other is smaller" do
+ (bignum_value(0xfeff) <=> bignum_value.to_f).should == 1
+ end
+ end
+ end
+
+ describe "with an Object" do
+ before :each do
+ @big = bignum_value
+ @num = mock("value for Integer#<=>")
+ end
+
+ it "calls #coerce on other" do
+ @num.should_receive(:coerce).with(@big).and_return([@big.to_f, 2.5])
+ @big <=> @num
+ end
+
+ it "lets the exception go through if #coerce raises an exception" do
+ @num.should_receive(:coerce).with(@big).and_raise(RuntimeError.new("my error"))
+ -> {
+ @big <=> @num
+ }.should.raise(RuntimeError, "my error")
+ end
+
+ it "raises an exception if #coerce raises a non-StandardError exception" do
+ @num.should_receive(:coerce).with(@big).and_raise(Exception)
+ -> { @big <=> @num }.should.raise(Exception)
+ end
+
+ it "returns nil if #coerce does not return an Array" do
+ @num.should_receive(:coerce).with(@big).and_return(nil)
+ (@big <=> @num).should == nil
+ end
+
+ it "returns -1 if the coerced value is larger" do
+ @num.should_receive(:coerce).with(@big).and_return([@big, bignum_value(10)])
+ (@big <=> @num).should == -1
+ end
+
+ it "returns 0 if the coerced value is equal" do
+ @num.should_receive(:coerce).with(@big).and_return([@big, bignum_value])
+ (@big <=> @num).should == 0
+ end
+
+ it "returns 1 if the coerced value is smaller" do
+ @num.should_receive(:coerce).with(@big).and_return([@big, 22])
+ (@big <=> @num).should == 1
+ end
+ end
+
+ describe "with a Float" do
+ it "does not lose precision for values that don't fit in a double" do
+ (bignum_value(1) <=> bignum_value.to_f).should == 1
+ (bignum_value <=> bignum_value.to_f).should == 0
+ ((bignum_value - 1) <=> bignum_value.to_f).should == -1
+ end
+ end
+
+ # The tests below are taken from matz's revision 23730 for Ruby trunk
+ it "returns 1 when self is Infinity and other is a Bignum" do
+ (infinity_value <=> Float::MAX.to_i*2).should == 1
+ end
+
+ it "returns -1 when self is negative and other is Infinity" do
+ (-Float::MAX.to_i*2 <=> infinity_value).should == -1
+ end
+
+ it "returns 1 when self is negative and other is -Infinity" do
+ (-Float::MAX.to_i*2 <=> -infinity_value).should == 1
+ end
+
+ it "returns -1 when self is -Infinity and other is negative" do
+ (-infinity_value <=> -Float::MAX.to_i*2).should == -1
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/complement_spec.rb b/spec/ruby/core/integer/complement_spec.rb
new file mode 100644
index 0000000000..baf5e6c457
--- /dev/null
+++ b/spec/ruby/core/integer/complement_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Integer#~" do
+ context "fixnum" do
+ it "returns self with each bit flipped" do
+ (~0).should == -1
+ (~1221).should == -1222
+ (~-2).should == 1
+ (~-599).should == 598
+ end
+ end
+
+ context "bignum" do
+ it "returns self with each bit flipped" do
+ (~bignum_value(48)).should == -18446744073709551665
+ (~(-bignum_value(21))).should == 18446744073709551636
+ (~bignum_value(1)).should == -18446744073709551618
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/constants_spec.rb b/spec/ruby/core/integer/constants_spec.rb
new file mode 100644
index 0000000000..937806c72f
--- /dev/null
+++ b/spec/ruby/core/integer/constants_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Fixnum" do
+ it "is no longer defined" do
+ Object.should_not.const_defined?(:Fixnum)
+ end
+end
+
+describe "Bignum" do
+ it "is no longer defined" do
+ Object.should_not.const_defined?(:Bignum)
+ end
+end
diff --git a/spec/ruby/core/integer/denominator_spec.rb b/spec/ruby/core/integer/denominator_spec.rb
new file mode 100644
index 0000000000..c1477d0757
--- /dev/null
+++ b/spec/ruby/core/integer/denominator_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Integer#denominator" do
+ # The Numeric child classes override this method, so their behaviour is
+ # specified in the appropriate place
+ before :each do
+ @numbers = [
+ 20, # Integer
+ -2709, # Negative Integer
+ 99999999**99, # Bignum
+ -99999**621, # Negative BigNum
+ 0,
+ 1
+ ]
+ end
+
+ it "returns 1" do
+ @numbers.each {|number| number.denominator.should == 1}
+ end
+end
diff --git a/spec/ruby/core/integer/digits_spec.rb b/spec/ruby/core/integer/digits_spec.rb
new file mode 100644
index 0000000000..c4ebf2cd88
--- /dev/null
+++ b/spec/ruby/core/integer/digits_spec.rb
@@ -0,0 +1,41 @@
+require_relative '../../spec_helper'
+
+describe "Integer#digits" do
+ it "returns an array of place values in base-10 by default" do
+ 12345.digits.should == [5,4,3,2,1]
+ end
+
+ it "returns digits by place value of a given radix" do
+ 12345.digits(7).should == [4,6,6,0,5]
+ end
+
+ it "converts the radix with #to_int" do
+ 12345.digits(mock_int(2)).should == [1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1]
+ end
+
+ it "returns [0] when called on 0, regardless of base" do
+ 0.digits.should == [0]
+ 0.digits(7).should == [0]
+ end
+
+ it "raises ArgumentError when calling with a radix less than 2" do
+ -> { 12345.digits(1) }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError when calling with a negative radix" do
+ -> { 12345.digits(-2) }.should.raise(ArgumentError)
+ end
+
+ it "raises Math::DomainError when calling digits on a negative number" do
+ -> { -12345.digits(7) }.should.raise(Math::DomainError)
+ end
+
+ it "returns integer values > 9 when base is above 10" do
+ 1234.digits(16).should == [2, 13, 4]
+ end
+
+ it "can be used with base > 37" do
+ 1234.digits(100).should == [34, 12]
+ 980099.digits(100).should == [99, 0, 98]
+ end
+end
diff --git a/spec/ruby/core/integer/div_spec.rb b/spec/ruby/core/integer/div_spec.rb
new file mode 100644
index 0000000000..220ca4a73e
--- /dev/null
+++ b/spec/ruby/core/integer/div_spec.rb
@@ -0,0 +1,154 @@
+require_relative '../../spec_helper'
+
+describe "Integer#div" do
+ context "fixnum" do
+ it "returns self divided by the given argument as an Integer" do
+ 2.div(2).should == 1
+ 1.div(2).should == 0
+ 5.div(2).should == 2
+ end
+
+ it "rounds towards -inf" do
+ 8192.div(10).should == 819
+ 8192.div(-10).should == -820
+ (-8192).div(10).should == -820
+ (-8192).div(-10).should == 819
+ end
+
+ it "means (x / y).floor" do
+ 5.div(2).should == (5 / 2).floor
+ 5.div(2.0).should == (5 / 2.0).floor
+ 5.div(-2).should == (5 / -2).floor
+
+ 5.div(100).should == (5 / 100).floor
+ 5.div(100.0).should == (5 / 100.0).floor
+ 5.div(-100).should == (5 / -100).floor
+ end
+
+ it "calls #coerce and #div if argument responds to #coerce" do
+ x = mock("x")
+ y = mock("y")
+ result = mock("result")
+
+ y.should_receive(:coerce).and_return([x, y])
+ x.should_receive(:div).with(y).and_return(result)
+
+ 10.div(y).should == result
+ end
+
+ it "coerces self and the given argument to Floats and returns self divided by other as Integer" do
+ 1.div(0.2).should == 5
+ 1.div(0.16).should == 6
+ 1.div(0.169).should == 5
+ -1.div(50.4).should == -1
+ 1.div(bignum_value).should == 0
+ 1.div(Rational(1, 5)).should == 5
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { 0.div(0.0) }.should.raise(ZeroDivisionError)
+ -> { 10.div(0.0) }.should.raise(ZeroDivisionError)
+ -> { -10.div(0.0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and not a Float" do
+ -> { 13.div(0) }.should.raise(ZeroDivisionError)
+ -> { 13.div(-0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-numeric argument" do
+ -> { 13.div(mock('10')) }.should.raise(TypeError)
+ -> { 5.div("2") }.should.raise(TypeError)
+ -> { 5.div(:"2") }.should.raise(TypeError)
+ -> { 5.div([]) }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(88)
+ end
+
+ it "returns self divided by other" do
+ @bignum.div(4).should == 4611686018427387926
+ @bignum.div(Rational(4, 1)).should == 4611686018427387926
+ @bignum.div(bignum_value(2)).should == 1
+
+ (-(10**50)).div(-(10**40 + 1)).should == 9999999999
+ (10**50).div(10**40 + 1).should == 9999999999
+
+ (-10**50).div(10**40 + 1).should == -10000000000
+ (10**50).div(-(10**40 + 1)).should == -10000000000
+ end
+
+ it "handles fixnum_min / -1" do
+ (fixnum_min / -1).should == -fixnum_min
+ (fixnum_min / -1).should > 0
+
+ int_min = -2147483648
+ (int_min / -1).should == 2147483648
+ end
+
+ it "calls #coerce and #div if argument responds to #coerce" do
+ x = mock("x")
+ y = mock("y")
+ result = mock("result")
+
+ y.should_receive(:coerce).and_return([x, y])
+ x.should_receive(:div).with(y).and_return(result)
+
+ @bignum.div(y).should == result
+ end
+
+ it "means (x / y).floor" do
+ @bignum.div(2).should == (@bignum / 2).floor
+ @bignum.div(-2).should == (@bignum / -2).floor
+
+ @bignum.div(@bignum+1).should == (@bignum / (@bignum+1)).floor
+ @bignum.div(-(@bignum+1)).should == (@bignum / -(@bignum+1)).floor
+
+ @bignum.div(2.0).should == (@bignum / 2.0).floor
+ @bignum.div(100.0).should == (@bignum / 100.0).floor
+ end
+
+ it "looses precision if passed Float argument" do
+ @bignum.div(1).should_not == @bignum.div(1.0)
+ @bignum.div(4).should_not == @bignum.div(4.0)
+ @bignum.div(21).should_not == @bignum.div(21.0)
+ end
+
+ it "raises a TypeError when given a non-numeric" do
+ -> { @bignum.div(mock("10")) }.should.raise(TypeError)
+ -> { @bignum.div("2") }.should.raise(TypeError)
+ -> { @bignum.div(:symbol) }.should.raise(TypeError)
+ end
+
+ it "returns a result of integer division of self by a float argument" do
+ @bignum.div(4294967295.5).should.eql?(4294967296)
+ not_supported_on :opal do
+ @bignum.div(4294967295.0).should.eql?(4294967297)
+ @bignum.div(bignum_value(88).to_f).should.eql?(1)
+ @bignum.div((-bignum_value(88)).to_f).should.eql?(-1)
+ end
+ end
+
+ # #5490
+ it "raises ZeroDivisionError if the argument is 0 and is a Float" do
+ -> { @bignum.div(0.0) }.should.raise(ZeroDivisionError)
+ -> { @bignum.div(-0.0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises ZeroDivisionError if the argument is 0 and is not a Float" do
+ -> { @bignum.div(0) }.should.raise(ZeroDivisionError)
+ -> { @bignum.div(-0) }.should.raise(ZeroDivisionError)
+ end
+ end
+
+ context "rational" do
+ it "returns self divided by the given argument as an Integer" do
+ 2.div(6/5r).should == 1
+ 1.div(6/5r).should == 0
+ 5.div(6/5r).should == 4
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/divide_spec.rb b/spec/ruby/core/integer/divide_spec.rb
new file mode 100644
index 0000000000..e75432fd83
--- /dev/null
+++ b/spec/ruby/core/integer/divide_spec.rb
@@ -0,0 +1,126 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_coerce'
+
+describe "Integer#/" do
+ it_behaves_like :integer_arithmetic_coerce_not_rescue, :/
+
+ context "fixnum" do
+ it "returns self divided by the given argument" do
+ (2 / 2).should == 1
+ (3 / 2).should == 1
+ end
+
+ 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
+ (3 / 2).should == 1
+ (3 / 2.0).should == 1.5
+ (3 / Rational(2, 1)).should == Rational(3, 2)
+ end
+
+ it "raises a ZeroDivisionError if the given argument is zero and not a Float" do
+ -> { 1 / 0 }.should.raise(ZeroDivisionError)
+ end
+
+ it "does NOT raise ZeroDivisionError if the given argument is zero and is a Float" do
+ (1 / 0.0).to_s.should == 'Infinity'
+ (-1 / 0.0).to_s.should == '-Infinity'
+ end
+
+ it "coerces fixnum and return self divided by other" do
+ (-1 / 50.4).should be_close(-0.0198412698412698, TOLERANCE)
+ (1 / bignum_value).should == 0
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { 13 / mock('10') }.should.raise(TypeError)
+ -> { 13 / "10" }.should.raise(TypeError)
+ -> { 13 / :symbol }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(88)
+ end
+
+ it "returns self divided by other" do
+ (@bignum / 4).should == 4611686018427387926
+
+ (@bignum / bignum_value(2)).should == 1
+
+ (-(10**50) / -(10**40 + 1)).should == 9999999999
+ ((10**50) / (10**40 + 1)).should == 9999999999
+
+ ((-10**50) / (10**40 + 1)).should == -10000000000
+ ((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)
+ end
+ (bignum_value(88) / 4294967295.5).should be_close(4294967296.5, TOLERANCE)
+ end
+
+ it "returns result the same class as the argument" do
+ (@bignum / 4).should == 4611686018427387926
+ (@bignum / 4.0).should be_close(4611686018427387926, TOLERANCE)
+ (@bignum / Rational(4, 1)).should == Rational(4611686018427387926, 1)
+ end
+
+ it "does NOT raise ZeroDivisionError if other is zero and is a Float" do
+ (bignum_value / 0.0).to_s.should == 'Infinity'
+ (bignum_value / -0.0).to_s.should == '-Infinity'
+ end
+
+ it "raises a ZeroDivisionError if other is zero and not a Float" do
+ -> { @bignum / 0 }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-numeric" do
+ -> { @bignum / mock('10') }.should.raise(TypeError)
+ -> { @bignum / "2" }.should.raise(TypeError)
+ -> { @bignum / :symbol }.should.raise(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/divmod_spec.rb b/spec/ruby/core/integer/divmod_spec.rb
new file mode 100644
index 0000000000..db470c5731
--- /dev/null
+++ b/spec/ruby/core/integer/divmod_spec.rb
@@ -0,0 +1,117 @@
+require_relative '../../spec_helper'
+
+describe "Integer#divmod" do
+ context "fixnum" do
+ it "returns an Array containing quotient and modulus obtained from dividing self by the given argument" do
+ 13.divmod(4).should == [3, 1]
+ 4.divmod(13).should == [0, 4]
+
+ 13.divmod(4.0).should == [3, 1]
+ 4.divmod(13.0).should == [0, 4]
+
+ 1.divmod(2.0).should == [0, 1.0]
+ 200.divmod(bignum_value).should == [0, 200]
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0" do
+ -> { 13.divmod(0) }.should.raise(ZeroDivisionError)
+ -> { 0.divmod(0) }.should.raise(ZeroDivisionError)
+ -> { -10.divmod(0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { 0.divmod(0.0) }.should.raise(ZeroDivisionError)
+ -> { 10.divmod(0.0) }.should.raise(ZeroDivisionError)
+ -> { -10.divmod(0.0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13.divmod(obj)
+ }.should.raise(TypeError)
+ -> { 13.divmod("10") }.should.raise(TypeError)
+ -> { 13.divmod(:symbol) }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(55)
+ end
+
+ # Based on MRI's test/test_integer.rb (test_divmod),
+ # MRI maintains the following property:
+ # if q, r = a.divmod(b) ==>
+ # assert(0 < b ? (0 <= r && r < b) : (b < r && r <= 0))
+ # So, r is always between 0 and b.
+ it "returns an Array containing quotient and modulus obtained from dividing self by the given argument" do
+ @bignum.divmod(4).should == [4611686018427387917, 3]
+ @bignum.divmod(13).should == [1418980313362273205, 6]
+
+ @bignum.divmod(4.5).should == [4099276460824344576, 2.5]
+
+ not_supported_on :opal do
+ @bignum.divmod(4.0).should == [4611686018427387904, 0.0]
+ @bignum.divmod(13.0).should == [1418980313362273280, 3.0]
+
+ @bignum.divmod(2.0).should == [9223372036854775808, 0.0]
+ end
+
+ @bignum.divmod(bignum_value).should == [1, 55]
+
+ (-(10**50)).divmod(-(10**40 + 1)).should == [9999999999, -9999999999999999999999999999990000000001]
+ (10**50).divmod(10**40 + 1).should == [9999999999, 9999999999999999999999999999990000000001]
+
+ (-10**50).divmod(10**40 + 1).should == [-10000000000, 10000000000]
+ (10**50).divmod(-(10**40 + 1)).should == [-10000000000, -10000000000]
+ end
+
+ describe "with q = floor(x/y), a = q*b + r," do
+ it "returns [q,r] when a < 0, b > 0 and |a| < b" do
+ a = -@bignum + 1
+ b = @bignum
+ a.divmod(b).should == [-1, 1]
+ end
+
+ it "returns [q,r] when a > 0, b < 0 and a > |b|" do
+ b = -@bignum + 1
+ a = @bignum
+ a.divmod(b).should == [-2, -@bignum + 2]
+ end
+
+ it "returns [q,r] when a > 0, b < 0 and a < |b|" do
+ a = @bignum - 1
+ b = -@bignum
+ a.divmod(b).should == [-1, -1]
+ end
+
+ it "returns [q,r] when a < 0, b < 0 and |a| < |b|" do
+ a = -@bignum + 1
+ b = -@bignum
+ a.divmod(b).should == [0, -@bignum + 1]
+ end
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0" do
+ -> { @bignum.divmod(0) }.should.raise(ZeroDivisionError)
+ -> { (-@bignum).divmod(0) }.should.raise(ZeroDivisionError)
+ end
+
+ # Behaviour established as correct in r23953
+ it "raises a FloatDomainError if other is NaN" do
+ -> { @bignum.divmod(nan_value) }.should.raise(FloatDomainError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { @bignum.divmod(0.0) }.should.raise(ZeroDivisionError)
+ -> { (-@bignum).divmod(0.0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when the given argument is not an Integer" do
+ -> { @bignum.divmod(mock('10')) }.should.raise(TypeError)
+ -> { @bignum.divmod("10") }.should.raise(TypeError)
+ -> { @bignum.divmod(:symbol) }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/downto_spec.rb b/spec/ruby/core/integer/downto_spec.rb
new file mode 100644
index 0000000000..a244d3df55
--- /dev/null
+++ b/spec/ruby/core/integer/downto_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Integer#downto [stop] when self and stop are Integers" do
+ it "does not yield when stop is greater than self" do
+ result = []
+ 5.downto(6) { |x| result << x }
+ result.should == []
+ end
+
+ it "yields once when stop equals self" do
+ result = []
+ 5.downto(5) { |x| result << x }
+ result.should == [5]
+ end
+
+ it "yields while decreasing self until it is less than stop" do
+ result = []
+ 5.downto(2) { |x| result << x }
+ result.should == [5, 4, 3, 2]
+ end
+
+ it "yields while decreasing self until it less than ceil for a Float endpoint" do
+ result = []
+ 9.downto(1.3) {|i| result << i}
+ 3.downto(-1.3) {|i| result << i}
+ result.should == [9, 8, 7, 6, 5, 4, 3, 2, 3, 2, 1, 0, -1]
+ end
+
+ it "raises an ArgumentError for invalid endpoints" do
+ -> {1.downto("A") {|x| p x } }.should.raise(ArgumentError)
+ -> {1.downto(nil) {|x| p x } }.should.raise(ArgumentError)
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ result = []
+
+ enum = 5.downto(2)
+ enum.each { |i| result << i }
+
+ result.should == [5, 4, 3, 2]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "raises an ArgumentError for invalid endpoints" do
+ enum = 1.downto("A")
+ -> { enum.size }.should.raise(ArgumentError)
+ enum = 1.downto(nil)
+ -> { enum.size }.should.raise(ArgumentError)
+ end
+
+ it "returns self - stop + 1" do
+ 10.downto(5).size.should == 6
+ 10.downto(1).size.should == 10
+ 10.downto(0).size.should == 11
+ 0.downto(0).size.should == 1
+ -3.downto(-5).size.should == 3
+ end
+
+ it "returns 0 when stop > self" do
+ 4.downto(5).size.should == 0
+ -5.downto(0).size.should == 0
+ -5.downto(-3).size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/dup_spec.rb b/spec/ruby/core/integer/dup_spec.rb
new file mode 100644
index 0000000000..3e5739ad37
--- /dev/null
+++ b/spec/ruby/core/integer/dup_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Integer#dup" do
+ it "returns self for small integers" do
+ integer = 1_000
+ integer.dup.should.equal?(integer)
+ end
+
+ it "returns self for large integers" do
+ integer = 4_611_686_018_427_387_905
+ integer.dup.should.equal?(integer)
+ end
+end
diff --git a/spec/ruby/core/integer/element_reference_spec.rb b/spec/ruby/core/integer/element_reference_spec.rb
new file mode 100644
index 0000000000..c69ec2315d
--- /dev/null
+++ b/spec/ruby/core/integer/element_reference_spec.rb
@@ -0,0 +1,188 @@
+require_relative '../../spec_helper'
+
+describe "Integer#[]" do
+ context "fixnum" do
+ it "behaves like (n >> b) & 1" do
+ 0b101[1].should == 0
+ 0b101[2].should == 1
+ end
+
+ it "returns 1 if the nth bit is set" do
+ 15[1].should == 1
+ end
+
+ it "returns 1 if the nth bit is set (in two's-complement representation)" do
+ (-1)[1].should == 1
+ end
+
+ it "returns 0 if the nth bit is not set" do
+ 8[2].should == 0
+ end
+
+ it "returns 0 if the nth bit is not set (in two's-complement representation)" do
+ (-2)[0].should == 0
+ end
+
+ it "returns 0 if the nth bit is greater than the most significant bit" do
+ 2[3].should == 0
+ end
+
+ it "returns 1 if self is negative and the nth bit is greater than the most significant bit" do
+ (-1)[3].should == 1
+ end
+
+ it "returns 0 when passed a negative argument" do
+ 3[-1].should == 0
+ (-1)[-1].should == 0
+ end
+
+ it "calls #to_int to convert the argument to an Integer and returns 1 if the nth bit is set" do
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+
+ 2[obj].should == 1
+ end
+
+ it "calls #to_int to convert the argument to an Integer and returns 0 if the nth bit is set" do
+ obj = mock('0')
+ obj.should_receive(:to_int).and_return(0)
+
+ 2[obj].should == 0
+ end
+
+ it "accepts a Float argument and returns 0 if the bit at the truncated value is not set" do
+ 13[1.3].should == 0
+ end
+
+ it "accepts a Float argument and returns 1 if the bit at the truncated value is set" do
+ 13[2.1].should == 1
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { 3["3"] }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock('asdf')
+ obj.should_receive(:to_int).and_return("asdf")
+ -> { 3[obj] }.should.raise(TypeError)
+ end
+
+ it "calls #to_int to coerce a String to a Bignum and returns 0" do
+ obj = mock('bignum value')
+ obj.should_receive(:to_int).and_return(bignum_value)
+
+ 3[obj].should == 0
+ end
+
+ it "returns 0 when passed a Float in the range of a Bignum" do
+ 3[bignum_value.to_f].should == 0
+ end
+
+ context "when index and length passed" do
+ it "returns specified number of bits from specified position" do
+ 0b101001101[2, 4].should == 0b0011
+ 0b101001101[2, 5].should == 0b10011
+ 0b101001101[2, 7].should == 0b1010011
+ end
+
+ it "ensures n[i, len] equals to (n >> i) & ((1 << len) - 1)" do
+ n = 0b101001101; i = 2; len = 4
+ n[i, len].should == (n >> i) & ((1 << len) - 1)
+ end
+
+ it "moves start position to the most significant bits when negative index passed" do
+ 0b000001[-1, 4].should == 0b10
+ 0b000001[-2, 4].should == 0b100
+ 0b000001[-3, 4].should == 0b1000
+ end
+
+ it "ignores negative length" do
+ 0b101001101[1, -1].should == 0b10100110
+ 0b101001101[2, -1].should == 0b1010011
+ 0b101001101[3, -1].should == 0b101001
+
+ 0b101001101[3, -5].should == 0b101001
+ 0b101001101[3, -15].should == 0b101001
+ 0b101001101[3, -125].should == 0b101001
+ end
+ end
+
+ context "when range passed" do
+ it "returns bits specified by range" do
+ 0b101001101[2..5].should == 0b0011
+ 0b101001101[2..6].should == 0b10011
+ 0b101001101[2..8].should == 0b1010011
+ end
+
+ it "ensures n[i..j] equals to (n >> i) & ((1 << (j - i + 1)) - 1)" do
+ n = 0b101001101; i = 2; j = 5
+ n[i..j].should == (n >> i) & ((1 << (j - i + 1)) - 1)
+ end
+
+ it "ensures n[i..] equals to (n >> i)" do
+ eval("0b101001101[3..]").should == 0b101001101 >> 3
+ end
+
+ it "moves lower boundary to the most significant bits when negative value passed" do
+ 0b000001[-1, 4].should == 0b10
+ 0b000001[-2, 4].should == 0b100
+ 0b000001[-3, 4].should == 0b1000
+ end
+
+ it "ignores upper boundary smaller than lower boundary" do
+ 0b101001101[4..1].should == 0b10100
+ 0b101001101[4..2].should == 0b10100
+ 0b101001101[-4..-5].should == 0b1010011010000
+ end
+
+ it "raises FloatDomainError if any boundary is infinity" do
+ -> { 0x0001[3..Float::INFINITY] }.should.raise(FloatDomainError, /Infinity/)
+ -> { 0x0001[-Float::INFINITY..3] }.should.raise(FloatDomainError, /-Infinity/)
+ end
+
+ context "when passed (..i)" do
+ it "returns 0 if all i bits equal 0" do
+ eval("0b10000[..1]").should == 0
+ eval("0b10000[..2]").should == 0
+ eval("0b10000[..3]").should == 0
+ end
+
+ it "raises ArgumentError if any of i bit equals 1" do
+ -> {
+ eval("0b111110[..3]")
+ }.should.raise(ArgumentError, /The beginless range for Integer#\[\] results in infinity/)
+ end
+ end
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(4996)
+ end
+
+ it "returns the nth bit in the binary representation of self" do
+ @bignum[2].should == 1
+ @bignum[9.2].should == 1
+ @bignum[21].should == 0
+ @bignum[0xffffffff].should == 0
+ @bignum[-0xffffffff].should == 0
+ end
+
+ it "tries to convert the given argument to an Integer using #to_int" do
+ @bignum[1.3].should == @bignum[1]
+
+ (obj = mock('2')).should_receive(:to_int).at_least(1).and_return(2)
+ @bignum[obj].should == 1
+ end
+
+ it "raises a TypeError when the given argument can't be converted to Integer" do
+ obj = mock('asdf')
+ -> { @bignum[obj] }.should.raise(TypeError)
+
+ obj.should_receive(:to_int).and_return("asdf")
+ -> { @bignum[obj] }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/eql_spec.rb b/spec/ruby/core/integer/eql_spec.rb
new file mode 100644
index 0000000000..9c80173206
--- /dev/null
+++ b/spec/ruby/core/integer/eql_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+describe "Integer#eql?" do
+ context "bignum" do
+ it "returns true for the same value" do
+ bignum_value.eql?(bignum_value).should == true
+ end
+
+ it "returns false for a different Integer value" do
+ bignum_value.eql?(bignum_value(1)).should == false
+ end
+
+ it "returns false for a Float with the same numeric value" do
+ bignum_value.eql?(bignum_value.to_f).should == false
+ end
+
+ it "returns false for a Rational with the same numeric value" do
+ bignum_value.eql?(Rational(bignum_value)).should == false
+ end
+
+ it "returns false for a Fixnum-range Integer" do
+ bignum_value.eql?(42).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/equal_value_spec.rb b/spec/ruby/core/integer/equal_value_spec.rb
new file mode 100644
index 0000000000..67a73713af
--- /dev/null
+++ b/spec/ruby/core/integer/equal_value_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/equal'
+
+describe "Integer#==" do
+ it_behaves_like :integer_equal, :==
+end
diff --git a/spec/ruby/core/integer/even_spec.rb b/spec/ruby/core/integer/even_spec.rb
new file mode 100644
index 0000000000..578979320e
--- /dev/null
+++ b/spec/ruby/core/integer/even_spec.rb
@@ -0,0 +1,40 @@
+require_relative '../../spec_helper'
+
+describe "Integer#even?" do
+ context "fixnum" do
+ it "returns true for a Fixnum when it is an even number" do
+ (-2).even?.should == true
+ (-1).even?.should == false
+
+ 0.even?.should == true
+ 1.even?.should == false
+ 2.even?.should == true
+ end
+
+ it "returns true for a Bignum when it is an even number" do
+ bignum_value(0).even?.should == true
+ bignum_value(1).even?.should == false
+
+ (-bignum_value(0)).even?.should == true
+ (-bignum_value(1)).even?.should == false
+ end
+ end
+
+ context "bignum" do
+ it "returns true if self is even and positive" do
+ (10000**10).even?.should == true
+ end
+
+ it "returns true if self is even and negative" do
+ (-10000**10).even?.should == true
+ end
+
+ it "returns false if self is odd and positive" do
+ (9879**976).even?.should == false
+ end
+
+ it "returns false if self is odd and negative" do
+ (-9879**976).even?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/exponent_spec.rb b/spec/ruby/core/integer/exponent_spec.rb
new file mode 100644
index 0000000000..c610661aff
--- /dev/null
+++ b/spec/ruby/core/integer/exponent_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exponent'
+
+describe "Integer#**" do
+ it_behaves_like :integer_exponent, :**
+end
diff --git a/spec/ruby/core/integer/fdiv_spec.rb b/spec/ruby/core/integer/fdiv_spec.rb
new file mode 100644
index 0000000000..7854d66dec
--- /dev/null
+++ b/spec/ruby/core/integer/fdiv_spec.rb
@@ -0,0 +1,100 @@
+require_relative '../../spec_helper'
+
+describe "Integer#fdiv" do
+ it "performs floating-point division between self and a fixnum" do
+ 8.fdiv(7).should be_close(1.14285714285714, TOLERANCE)
+ end
+
+ it "performs floating-point division between self and a bignum" do
+ 8.fdiv(bignum_value).should be_close(8.673617379884035e-19, TOLERANCE)
+ end
+
+ it "performs floating-point division between self bignum and a bignum" do
+ num = 1000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146010000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146010000000000000000000000000000000000048148248609680896326399448564623182963452541226153892315137780403285956264146009
+ den = 2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ num.fdiv(den).should == 500.0
+ end
+
+ it "rounds to the correct value for bignums" do
+ den = 9 * 10**342
+
+ num = 1 * 10**344
+ num.fdiv(den).should == 11.11111111111111
+
+ num = 1 * 10**343
+ num.fdiv(den).should == 1.1111111111111112
+
+ num = 1 * 10**342
+ num.fdiv(den).should == 0.1111111111111111
+
+ num = 2 * 10**342
+ num.fdiv(den).should == 0.2222222222222222
+
+ num = 3 * 10**342
+ num.fdiv(den).should == 0.3333333333333333
+
+ num = 4 * 10**342
+ num.fdiv(den).should == 0.4444444444444444
+
+ num = 5 * 10**342
+ num.fdiv(den).should == 0.5555555555555556
+
+ num = 6 * 10**342
+ num.fdiv(den).should == 0.6666666666666666
+
+ num = 7 * 10**342
+ num.fdiv(den).should == 0.7777777777777778
+
+ num = 8 * 10**342
+ num.fdiv(den).should == 0.8888888888888888
+
+ num = 9 * 10**342
+ num.fdiv(den).should == 1.0
+
+ num = -5 * 10**342
+ num.fdiv(den).should == -0.5555555555555556
+ end
+
+ it "rounds to the correct float for bignum denominators" do
+ 1.fdiv(10**324).should == 0.0
+ 1.fdiv(10**323).should == 1.0e-323
+ end
+
+ it "performs floating-point division between self and a Float" do
+ 8.fdiv(9.0).should be_close(0.888888888888889, TOLERANCE)
+ end
+
+ it "returns NaN when the argument is NaN" do
+ -1.fdiv(nan_value).nan?.should == true
+ 1.fdiv(nan_value).nan?.should == true
+ end
+
+ it "returns Infinity when the argument is 0" do
+ 1.fdiv(0).infinite?.should == 1
+ end
+
+ it "returns -Infinity when the argument is 0 and self is negative" do
+ -1.fdiv(0).infinite?.should == -1
+ end
+
+ it "returns Infinity when the argument is 0.0" do
+ 1.fdiv(0.0).infinite?.should == 1
+ end
+
+ it "returns -Infinity when the argument is 0.0 and self is negative" do
+ -1.fdiv(0.0).infinite?.should == -1
+ end
+
+ it "raises a TypeError when argument isn't numeric" do
+ -> { 1.fdiv(mock('non-numeric')) }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError when passed multiple arguments" do
+ -> { 1.fdiv(6,0.2) }.should.raise(ArgumentError)
+ end
+
+ it "follows the coercion protocol" do
+ (obj = mock('10')).should_receive(:coerce).with(1).and_return([1, 10])
+ 1.fdiv(obj).should == 0.1
+ end
+end
diff --git a/spec/ruby/core/integer/fixtures/classes.rb b/spec/ruby/core/integer/fixtures/classes.rb
new file mode 100644
index 0000000000..65948efb9f
--- /dev/null
+++ b/spec/ruby/core/integer/fixtures/classes.rb
@@ -0,0 +1,14 @@
+module IntegerSpecs
+ class CoerceError < StandardError
+ end
+
+ class CoercibleNumeric
+ def initialize(v) @v = v end
+ def coerce(other) [self.class.new(other), self] end
+ def >(other) @v.to_i > other.to_i end
+ def >=(other) @v.to_i >= other.to_i end
+ def <(other) @v.to_i < other.to_i end
+ def <=(other) @v.to_i <= other.to_i end
+ def to_i() @v.to_i end
+ end
+end
diff --git a/spec/ruby/core/integer/floor_spec.rb b/spec/ruby/core/integer/floor_spec.rb
new file mode 100644
index 0000000000..8fb84d58cb
--- /dev/null
+++ b/spec/ruby/core/integer/floor_spec.rb
@@ -0,0 +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 "with precision" do
+ it_behaves_like :integer_floor_precision, :Integer
+ end
+end
diff --git a/spec/ruby/core/integer/gcd_spec.rb b/spec/ruby/core/integer/gcd_spec.rb
new file mode 100644
index 0000000000..1fff785927
--- /dev/null
+++ b/spec/ruby/core/integer/gcd_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Integer#gcd" do
+ it "returns self if equal to the argument" do
+ 1.gcd(1).should == 1
+ 398.gcd(398).should == 398
+ end
+
+ it "returns an Integer" do
+ 36.gcd(6).should.is_a?(Integer)
+ 4.gcd(20981).should.is_a?(Integer)
+ end
+
+ it "returns the greatest common divisor of self and argument" do
+ 10.gcd(5).should == 5
+ 200.gcd(20).should == 20
+ end
+
+ it "returns a positive integer even if self is negative" do
+ -12.gcd(6).should == 6
+ -100.gcd(100).should == 100
+ end
+
+ it "returns a positive integer even if the argument is negative" do
+ 12.gcd(-6).should == 6
+ 100.gcd(-100).should == 100
+ end
+
+ it "returns a positive integer even if both self and argument are negative" do
+ -12.gcd(-6).should == 6
+ -100.gcd(-100).should == 100
+ end
+
+ it "accepts a Bignum argument" do
+ bignum = 9999**99
+ bignum.should.is_a?(Integer)
+ 99.gcd(bignum).should == 99
+ end
+
+ it "works if self is a Bignum" do
+ bignum = 9999**99
+ bignum.should.is_a?(Integer)
+ bignum.gcd(99).should == 99
+ end
+
+ it "doesn't cause an integer overflow" do
+ [2 ** (1.size * 8 - 2), 0x8000000000000000].each do |max|
+ [max - 1, max, max + 1].each do |num|
+ num.gcd(num).should == num
+ (-num).gcd(num).should == num
+ (-num).gcd(-num).should == num
+ num.gcd(-num).should == num
+ end
+ end
+ end
+
+ it "raises an ArgumentError if not given an argument" do
+ -> { 12.gcd }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if given more than one argument" do
+ -> { 12.gcd(30, 20) }.should.raise(ArgumentError)
+ end
+
+ it "raises a TypeError unless the argument is an Integer" do
+ -> { 39.gcd(3.8) }.should.raise(TypeError)
+ -> { 45872.gcd([]) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/gcdlcm_spec.rb b/spec/ruby/core/integer/gcdlcm_spec.rb
new file mode 100644
index 0000000000..419bf9f275
--- /dev/null
+++ b/spec/ruby/core/integer/gcdlcm_spec.rb
@@ -0,0 +1,53 @@
+require_relative '../../spec_helper'
+
+describe "Integer#gcdlcm" do
+ it "returns [self, self] if self is equal to the argument" do
+ 1.gcdlcm(1).should == [1, 1]
+ 398.gcdlcm(398).should == [398, 398]
+ end
+
+ it "returns an Array" do
+ 36.gcdlcm(6).should.is_a?(Array)
+ 4.gcdlcm(20981).should.is_a?(Array)
+ end
+
+ it "returns a two-element Array" do
+ 36.gcdlcm(876).size.should == 2
+ 29.gcdlcm(17).size.should == 2
+ end
+
+ it "returns the greatest common divisor of self and argument as the first element" do
+ 10.gcdlcm(5)[0].should == 10.gcd(5)
+ 200.gcdlcm(20)[0].should == 200.gcd(20)
+ end
+
+ it "returns the least common multiple of self and argument as the last element" do
+ 10.gcdlcm(5)[1].should == 10.lcm(5)
+ 200.gcdlcm(20)[1].should == 200.lcm(20)
+ end
+
+ it "accepts a Bignum argument" do
+ bignum = 91999**99
+ bignum.should.is_a?(Integer)
+ 99.gcdlcm(bignum).should == [99.gcd(bignum), 99.lcm(bignum)]
+ end
+
+ it "works if self is a Bignum" do
+ bignum = 9999**89
+ bignum.should.is_a?(Integer)
+ bignum.gcdlcm(99).should == [bignum.gcd(99), bignum.lcm(99)]
+ end
+
+ it "raises an ArgumentError if not given an argument" do
+ -> { 12.gcdlcm }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if given more than one argument" do
+ -> { 12.gcdlcm(30, 20) }.should.raise(ArgumentError)
+ end
+
+ it "raises a TypeError unless the argument is an Integer" do
+ -> { 39.gcdlcm(3.8) }.should.raise(TypeError)
+ -> { 45872.gcdlcm([]) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/gt_spec.rb b/spec/ruby/core/integer/gt_spec.rb
new file mode 100644
index 0000000000..75436144cf
--- /dev/null
+++ b/spec/ruby/core/integer/gt_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_coerce'
+
+describe "Integer#>" do
+ it_behaves_like :integer_comparison_coerce_not_rescue, :>
+
+ context "fixnum" do
+ it "returns true if self is greater than the given argument" do
+ (13 > 2).should == true
+ (-500 > -600).should == true
+
+ (1 > 5).should == false
+ (5 > 5).should == false
+
+ (900 > bignum_value).should == false
+ (5 > 4.999).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { 5 > "4" }.should.raise(ArgumentError)
+ -> { 5 > mock('x') }.should.raise(ArgumentError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(732)
+ end
+
+ it "returns true if self is greater than the given argument" do
+ (@bignum > (@bignum - 1)).should == true
+ (@bignum > 14.6).should == true
+ (@bignum > 10).should == true
+
+ (@bignum > (@bignum + 500)).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { @bignum > "4" }.should.raise(ArgumentError)
+ -> { @bignum > mock('str') }.should.raise(ArgumentError)
+ end
+
+ it "dispatches the correct operator after coercion" do
+ (bignum_value > IntegerSpecs::CoercibleNumeric.new(1)).should == true
+ (bignum_value > IntegerSpecs::CoercibleNumeric.new(bignum_value * 2)).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/gte_spec.rb b/spec/ruby/core/integer/gte_spec.rb
new file mode 100644
index 0000000000..e5cb892efd
--- /dev/null
+++ b/spec/ruby/core/integer/gte_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_coerce'
+
+describe "Integer#>=" do
+ it_behaves_like :integer_comparison_coerce_not_rescue, :>=
+
+ context "fixnum" do
+ it "returns true if self is greater than or equal to the given argument" do
+ (13 >= 2).should == true
+ (-500 >= -600).should == true
+
+ (1 >= 5).should == false
+ (2 >= 2).should == true
+ (5 >= 5).should == true
+
+ (900 >= bignum_value).should == false
+ (5 >= 4.999).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { 5 >= "4" }.should.raise(ArgumentError)
+ -> { 5 >= mock('x') }.should.raise(ArgumentError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(14)
+ end
+
+ it "returns true if self is greater than or equal to other" do
+ (@bignum >= @bignum).should == true
+ (@bignum >= (@bignum + 2)).should == false
+ (@bignum >= 5664.2).should == true
+ (@bignum >= 4).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { @bignum >= "4" }.should.raise(ArgumentError)
+ -> { @bignum >= mock('str') }.should.raise(ArgumentError)
+ end
+
+ it "dispatches the correct operator after coercion" do
+ (bignum_value >= IntegerSpecs::CoercibleNumeric.new(1)).should == true
+ (bignum_value >= IntegerSpecs::CoercibleNumeric.new(bignum_value * 2)).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/integer_spec.rb b/spec/ruby/core/integer/integer_spec.rb
new file mode 100644
index 0000000000..19a962d548
--- /dev/null
+++ b/spec/ruby/core/integer/integer_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+
+describe "Integer" do
+ it "includes Comparable" do
+ Integer.include?(Comparable).should == true
+ end
+
+ it "is the class of both small and large integers" do
+ 42.class.should.equal?(Integer)
+ bignum_value.class.should.equal?(Integer)
+ end
+end
+
+describe "Integer#integer?" do
+ it "returns true for Integers" do
+ 0.should.integer?
+ -1.should.integer?
+ bignum_value.should.integer?
+ end
+end
diff --git a/spec/ruby/core/integer/lcm_spec.rb b/spec/ruby/core/integer/lcm_spec.rb
new file mode 100644
index 0000000000..6659247d1e
--- /dev/null
+++ b/spec/ruby/core/integer/lcm_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+
+describe "Integer#lcm" do
+ it "returns self if equal to the argument" do
+ 1.lcm(1).should == 1
+ 398.lcm(398).should == 398
+ end
+
+ it "returns an Integer" do
+ 36.lcm(6).should.is_a?(Integer)
+ 4.lcm(20981).should.is_a?(Integer)
+ end
+
+ it "returns the least common multiple of self and argument" do
+ 200.lcm(2001).should == 400200
+ 99.lcm(90).should == 990
+ end
+
+ it "returns a positive integer even if self is negative" do
+ -12.lcm(6).should == 12
+ -100.lcm(100).should == 100
+ end
+
+ it "returns a positive integer even if the argument is negative" do
+ 12.lcm(-6).should == 12
+ 100.lcm(-100).should == 100
+ end
+
+ it "returns a positive integer even if both self and argument are negative" do
+ -12.lcm(-6).should == 12
+ -100.lcm(-100).should == 100
+ end
+
+ it "accepts a Bignum argument" do
+ bignum = 9999**99
+ bignum.should.is_a?(Integer)
+ 99.lcm(bignum).should == bignum
+ end
+
+ it "works if self is a Bignum" do
+ bignum = 9999**99
+ bignum.should.is_a?(Integer)
+ bignum.lcm(99).should == bignum
+ end
+
+ it "raises an ArgumentError if not given an argument" do
+ -> { 12.lcm }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError if given more than one argument" do
+ -> { 12.lcm(30, 20) }.should.raise(ArgumentError)
+ end
+
+ it "raises a TypeError unless the argument is an Integer" do
+ -> { 39.lcm(3.8) }.should.raise(TypeError)
+ -> { 45872.lcm([]) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/left_shift_spec.rb b/spec/ruby/core/integer/left_shift_spec.rb
new file mode 100644
index 0000000000..7eedb91228
--- /dev/null
+++ b/spec/ruby/core/integer/left_shift_spec.rb
@@ -0,0 +1,211 @@
+require_relative '../../spec_helper'
+
+describe "Integer#<< (with n << m)" do
+ context "fixnum" do
+ it "returns n shifted left m bits when n > 0, m > 0" do
+ (1 << 1).should == 2
+ end
+
+ it "returns n shifted left m bits when n < 0, m > 0" do
+ (-1 << 1).should == -2
+ (-7 << 1).should == -14
+ (-42 << 2).should == -168
+ end
+
+ it "returns n shifted right m bits when n > 0, m < 0" do
+ (2 << -1).should == 1
+ end
+
+ it "returns n shifted right m bits when n < 0, m < 0" do
+ (-2 << -1).should == -1
+ end
+
+ it "returns 0 when n == 0" do
+ (0 << 1).should == 0
+ end
+
+ it "returns n when n > 0, m == 0" do
+ (1 << 0).should == 1
+ end
+
+ it "returns n when n < 0, m == 0" do
+ (-1 << 0).should == -1
+ end
+
+ it "returns 0 when n > 0, m < 0 and n < 2**-m" do
+ (3 << -2).should == 0
+ (7 << -3).should == 0
+ (127 << -7).should == 0
+
+ # To make sure the exponent is not truncated
+ (7 << -32).should == 0
+ (7 << -64).should == 0
+ end
+
+ it "returns -1 when n < 0, m < 0 and n > -(2**-m)" do
+ (-3 << -2).should == -1
+ (-7 << -3).should == -1
+ (-127 << -7).should == -1
+
+ # To make sure the exponent is not truncated
+ (-7 << -32).should == -1
+ (-7 << -64).should == -1
+ end
+
+ it "returns 0 when m < 0 and m is a Bignum" do
+ (3 << -bignum_value).should == 0
+ end
+
+ it "returns a Bignum == fixnum_max * 2 when fixnum_max << 1 and n > 0" do
+ result = fixnum_max << 1
+ result.should.instance_of?(Integer)
+ result.should == fixnum_max * 2
+ end
+
+ it "returns a Bignum == fixnum_min * 2 when fixnum_min << 1 and n < 0" do
+ result = fixnum_min << 1
+ result.should.instance_of?(Integer)
+ result.should == fixnum_min * 2
+ end
+
+ it "calls #to_int to convert the argument to an Integer" do
+ obj = mock("4")
+ obj.should_receive(:to_int).and_return(4)
+ (3 << obj).should == 48
+
+ obj = mock("to_int_neg_bignum")
+ obj.should_receive(:to_int).and_return(-bignum_value)
+ (3 << obj).should == 0
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("a string")
+ obj.should_receive(:to_int).and_return("asdf")
+
+ -> { 3 << obj }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { 3 << nil }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { 3 << "4" }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value * 8 # 2 ** 67
+ end
+
+ it "returns n shifted left m bits when n > 0, m > 0" do
+ (@bignum << 4).should == 2361183241434822606848
+ end
+
+ it "returns n shifted left m bits when n < 0, m > 0" do
+ (-@bignum << 9).should == -75557863725914323419136
+ end
+
+ it "returns n shifted right m bits when n > 0, m < 0" do
+ (@bignum << -1).should == 73786976294838206464
+ end
+
+ it "returns n shifted right m bits when n < 0, m < 0" do
+ (-@bignum << -2).should == -36893488147419103232
+ end
+
+ it "returns n when n > 0, m == 0" do
+ (@bignum << 0).should == @bignum
+ end
+
+ it "returns n when n < 0, m == 0" do
+ (-@bignum << 0).should == -@bignum
+ end
+
+ it "returns 0 when m < 0 and m == p where 2**p > n >= 2**(p-1)" do
+ (@bignum << -68).should == 0
+ end
+
+ it "returns a Fixnum == fixnum_max when (fixnum_max * 2) << -1 and n > 0" do
+ result = (fixnum_max * 2) << -1
+ result.should.instance_of?(Integer)
+ result.should == fixnum_max
+ end
+
+ it "returns a Fixnum == fixnum_min when (fixnum_min * 2) << -1 and n < 0" do
+ result = (fixnum_min * 2) << -1
+ result.should.instance_of?(Integer)
+ result.should == fixnum_min
+ end
+
+ it "calls #to_int to convert the argument to an Integer" do
+ obj = mock("4")
+ obj.should_receive(:to_int).and_return(4)
+
+ (@bignum << obj).should == 2361183241434822606848
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("a string")
+ obj.should_receive(:to_int).and_return("asdf")
+
+ -> { @bignum << obj }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @bignum << nil }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { @bignum << "4" }.should.raise(TypeError)
+ end
+ end
+
+ context "when m is a bignum or larger than int" do
+ it "returns -1 when m < 0 and n < 0" do
+ (-1 << -bignum_value).should == -1
+ (-1 << -(2**40)).should == -1
+
+ (-bignum_value << -bignum_value).should == -1
+ (-bignum_value << -(2**40)).should == -1
+ end
+
+ it "returns 0 when m < 0 and n >= 0" do
+ (0 << -bignum_value).should == 0
+ (1 << -bignum_value).should == 0
+ (bignum_value << -bignum_value).should == 0
+
+ (0 << -(2**40)).should == 0
+ (1 << -(2**40)).should == 0
+ (bignum_value << -(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
+ (0 << bignum_value).should == 0
+ end
+
+ it "raises RangeError when m > 0 and n != 0" do
+ # https://bugs.ruby-lang.org/issues/18518#note-9
+ limit = RUBY_ENGINE == 'ruby' ? 2**67 : 2**32
+
+ coerce_long = mock("long")
+ coerce_long.stub!(:to_int).and_return(limit)
+ coerce_bignum = mock("bignum")
+ coerce_bignum.stub!(:to_int).and_return(bignum_value)
+ exps = [limit, coerce_long]
+ exps << bignum_value << coerce_bignum if bignum_value >= limit
+
+ exps.each { |exp|
+ -> { (1 << exp) }.should.raise(RangeError, 'shift width too big')
+ -> { (-1 << exp) }.should.raise(RangeError, 'shift width too big')
+ -> { (bignum_value << exp) }.should.raise(RangeError, 'shift width too big')
+ -> { (-bignum_value << exp) }.should.raise(RangeError, 'shift width too big')
+ }
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/lt_spec.rb b/spec/ruby/core/integer/lt_spec.rb
new file mode 100644
index 0000000000..dd1391d093
--- /dev/null
+++ b/spec/ruby/core/integer/lt_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_coerce'
+
+describe "Integer#<" do
+ it_behaves_like :integer_comparison_coerce_not_rescue, :<
+
+ context "fixnum" do
+ it "returns true if self is less than the given argument" do
+ (2 < 13).should == true
+ (-600 < -500).should == true
+
+ (5 < 1).should == false
+ (5 < 5).should == false
+
+ (900 < bignum_value).should == true
+ (5 < 4.999).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { 5 < "4" }.should.raise(ArgumentError)
+ -> { 5 < mock('x') }.should.raise(ArgumentError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(32)
+ end
+
+ it "returns true if self is less than the given argument" do
+ (@bignum < @bignum + 1).should == true
+ (-@bignum < -(@bignum - 1)).should == true
+
+ (@bignum < 1).should == false
+ (@bignum < 5).should == false
+
+ (@bignum < 4.999).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { @bignum < "4" }.should.raise(ArgumentError)
+ -> { @bignum < mock('str') }.should.raise(ArgumentError)
+ end
+
+ it "dispatches the correct operator after coercion" do
+ (bignum_value < IntegerSpecs::CoercibleNumeric.new(bignum_value * 2)).should == true
+ (bignum_value < IntegerSpecs::CoercibleNumeric.new(1)).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/lte_spec.rb b/spec/ruby/core/integer/lte_spec.rb
new file mode 100644
index 0000000000..9ef1c9f604
--- /dev/null
+++ b/spec/ruby/core/integer/lte_spec.rb
@@ -0,0 +1,58 @@
+require_relative '../../spec_helper'
+require_relative 'shared/comparison_coerce'
+
+describe "Integer#<=" do
+ it_behaves_like :integer_comparison_coerce_not_rescue, :<=
+
+ context "fixnum" do
+ it "returns true if self is less than or equal to other" do
+ (2 <= 13).should == true
+ (-600 <= -500).should == true
+
+ (5 <= 1).should == false
+ (5 <= 5).should == true
+ (-2 <= -2).should == true
+
+ (900 <= bignum_value).should == true
+ (5 <= 4.999).should == false
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { 5 <= "4" }.should.raise(ArgumentError)
+ -> { 5 <= mock('x') }.should.raise(ArgumentError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(39)
+ end
+
+ it "returns true if self is less than or equal to other" do
+ (@bignum <= @bignum).should == true
+ (-@bignum <= -(@bignum - 1)).should == true
+
+ (@bignum <= 4.999).should == false
+ end
+
+ it "returns false if compares with near float" do
+ (@bignum <= (@bignum + 0.0)).should == false
+ (@bignum <= (@bignum + 0.5)).should == false
+ end
+
+ it "returns true for bignums compare to a bigger float" do
+ (18446744073709551616 <= 1.8446744073709552e+19).should == true
+ (@bignum <= (@bignum + 9999.0)).should == true
+ end
+
+ it "raises an ArgumentError when given a non-Integer" do
+ -> { @bignum <= "4" }.should.raise(ArgumentError)
+ -> { @bignum <= mock('str') }.should.raise(ArgumentError)
+ end
+
+ it "dispatches the correct operator after coercion" do
+ (bignum_value <= IntegerSpecs::CoercibleNumeric.new(bignum_value * 2)).should == true
+ (bignum_value <= IntegerSpecs::CoercibleNumeric.new(1)).should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/magnitude_spec.rb b/spec/ruby/core/integer/magnitude_spec.rb
new file mode 100644
index 0000000000..48cf1a8534
--- /dev/null
+++ b/spec/ruby/core/integer/magnitude_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/abs'
+
+describe "Integer#magnitude" do
+ it_behaves_like :integer_abs, :magnitude
+end
diff --git a/spec/ruby/core/integer/minus_spec.rb b/spec/ruby/core/integer/minus_spec.rb
new file mode 100644
index 0000000000..5fd3a81a72
--- /dev/null
+++ b/spec/ruby/core/integer/minus_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_coerce'
+
+describe "Integer#-" do
+ it_behaves_like :integer_arithmetic_coerce_not_rescue, :-
+
+ context "fixnum" do
+ it "returns self minus the given Integer" do
+ (5 - 10).should == -5
+ (9237212 - 5_280).should == 9231932
+
+ (781 - 0.5).should == 780.5
+ (2_560_496 - bignum_value).should == -18446744073706991120
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13 - obj
+ }.should.raise(TypeError)
+ -> { 13 - "10" }.should.raise(TypeError)
+ -> { 13 - :symbol }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(314)
+ end
+
+ it "returns self minus the given Integer" do
+ (@bignum - 9).should == 18446744073709551921
+ (@bignum - 12.57).should be_close(18446744073709551917.43, TOLERANCE)
+ (@bignum - bignum_value(42)).should == 272
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum - mock('10') }.should.raise(TypeError)
+ -> { @bignum - "10" }.should.raise(TypeError)
+ -> { @bignum - :symbol }.should.raise(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/modulo_spec.rb b/spec/ruby/core/integer/modulo_spec.rb
new file mode 100644
index 0000000000..e263338e38
--- /dev/null
+++ b/spec/ruby/core/integer/modulo_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/modulo'
+
+describe "Integer#%" do
+ it_behaves_like :integer_modulo, :%
+end
+
+describe "Integer#modulo" do
+ it_behaves_like :integer_modulo, :modulo
+end
diff --git a/spec/ruby/core/integer/multiply_spec.rb b/spec/ruby/core/integer/multiply_spec.rb
new file mode 100644
index 0000000000..7f30eebaa5
--- /dev/null
+++ b/spec/ruby/core/integer/multiply_spec.rb
@@ -0,0 +1,45 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_coerce'
+
+describe "Integer#*" do
+ it_behaves_like :integer_arithmetic_coerce_not_rescue, :*
+
+ context "fixnum" do
+ it "returns self multiplied by the given Integer" do
+ (4923 * 2).should == 9846
+ (1342177 * 800).should == 1073741600
+ (65536 * 65536).should == 4294967296
+
+ (256 * bignum_value).should == 4722366482869645213696
+ (6712 * 0.25).should == 1678.0
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13 * obj
+ }.should.raise(TypeError)
+ -> { 13 * "10" }.should.raise(TypeError)
+ -> { 13 * :symbol }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(772)
+ end
+
+ it "returns self multiplied by the given Integer" do
+ (@bignum * (1/bignum_value(0xffff).to_f)).should be_close(1.0, TOLERANCE)
+ (@bignum * (1/bignum_value(0xffff).to_f)).should be_close(1.0, TOLERANCE)
+ (@bignum * 10).should == 184467440737095523880
+ (@bignum * (@bignum - 40)).should == 340282366920938491207277694290934407024
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum * mock('10') }.should.raise(TypeError)
+ -> { @bignum * "10" }.should.raise(TypeError)
+ -> { @bignum * :symbol }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/next_spec.rb b/spec/ruby/core/integer/next_spec.rb
new file mode 100644
index 0000000000..63c4e67893
--- /dev/null
+++ b/spec/ruby/core/integer/next_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/next'
+
+describe "Integer#next" do
+ it_behaves_like :integer_next, :next
+end
diff --git a/spec/ruby/core/integer/nobits_spec.rb b/spec/ruby/core/integer/nobits_spec.rb
new file mode 100644
index 0000000000..f1a3aca9d5
--- /dev/null
+++ b/spec/ruby/core/integer/nobits_spec.rb
@@ -0,0 +1,36 @@
+require_relative '../../spec_helper'
+
+describe "Integer#nobits?" do
+ it "returns true if and only if all no bits of the argument are set in the receiver" do
+ 42.nobits?(42).should == false
+ 0b1010_1010.nobits?(0b1000_0010).should == false
+ 0b1010_1010.nobits?(0b1000_0001).should == false
+ 0b0100_0101.nobits?(0b1010_1010).should == true
+ different_bignum = (2 * bignum_value) & (~bignum_value)
+ (0b1010_1010 | different_bignum).nobits?(0b1000_0010 | bignum_value).should == false
+ (0b1010_1010 | different_bignum).nobits?(0b1000_0001 | bignum_value).should == false
+ (0b0100_0101 | different_bignum).nobits?(0b1010_1010 | bignum_value).should == true
+ end
+
+ it "handles negative values using two's complement notation" do
+ (~0b1101).nobits?(0b1101).should == true
+ (-42).nobits?(-42).should == false
+ (~0b1101).nobits?(~0b10).should == false
+ (~(0b1101 | bignum_value)).nobits?(~(0b10 | bignum_value)).should == false
+ end
+
+ it "coerces the rhs using to_int" do
+ obj = mock("the int 0b10")
+ obj.should_receive(:to_int).and_return(0b10)
+ 0b110.nobits?(obj).should == false
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:coerce).any_number_of_times.and_return([42,10])
+ 13.nobits?(obj)
+ }.should.raise(TypeError)
+ -> { 13.nobits?("10") }.should.raise(TypeError)
+ -> { 13.nobits?(:symbol) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/numerator_spec.rb b/spec/ruby/core/integer/numerator_spec.rb
new file mode 100644
index 0000000000..f4149d770e
--- /dev/null
+++ b/spec/ruby/core/integer/numerator_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+
+describe "Integer#numerator" do
+ before :all do
+ @numbers = [
+ 0,
+ 29871,
+ 99999999999999**99,
+ 72628191273,
+ ].map{|n| [-n, n]}.flatten
+ end
+
+ it "returns self" do
+ @numbers.each do |number|
+ number.numerator.should == number
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/odd_spec.rb b/spec/ruby/core/integer/odd_spec.rb
new file mode 100644
index 0000000000..f9e50ec790
--- /dev/null
+++ b/spec/ruby/core/integer/odd_spec.rb
@@ -0,0 +1,38 @@
+require_relative '../../spec_helper'
+
+describe "Integer#odd?" do
+ context "fixnum" do
+ it "returns true when self is an odd number" do
+ (-2).odd?.should == false
+ (-1).odd?.should == true
+
+ 0.odd?.should == false
+ 1.odd?.should == true
+ 2.odd?.should == false
+
+ bignum_value(0).odd?.should == false
+ bignum_value(1).odd?.should == true
+
+ (-bignum_value(0)).odd?.should == false
+ (-bignum_value(1)).odd?.should == true
+ end
+ end
+
+ context "bignum" do
+ it "returns true if self is odd and positive" do
+ (987279**19).odd?.should == true
+ end
+
+ it "returns true if self is odd and negative" do
+ (-9873389**97).odd?.should == true
+ end
+
+ it "returns false if self is even and positive" do
+ (10000000**10).odd?.should == false
+ end
+
+ it "returns false if self is even and negative" do
+ (-1000000**100).odd?.should == false
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/ord_spec.rb b/spec/ruby/core/integer/ord_spec.rb
new file mode 100644
index 0000000000..8b7dfe460d
--- /dev/null
+++ b/spec/ruby/core/integer/ord_spec.rb
@@ -0,0 +1,17 @@
+require_relative '../../spec_helper'
+
+describe "Integer#ord" do
+ it "returns self" do
+ 20.ord.should.eql?(20)
+ 40.ord.should.eql?(40)
+
+ 0.ord.should.eql?(0)
+ (-10).ord.should.eql?(-10)
+
+ ?a.ord.should.eql?(97)
+ ?Z.ord.should.eql?(90)
+
+ bignum_value.ord.should.eql?(bignum_value)
+ (-bignum_value).ord.should.eql?(-bignum_value)
+ end
+end
diff --git a/spec/ruby/core/integer/plus_spec.rb b/spec/ruby/core/integer/plus_spec.rb
new file mode 100644
index 0000000000..b684377bd5
--- /dev/null
+++ b/spec/ruby/core/integer/plus_spec.rb
@@ -0,0 +1,75 @@
+require_relative '../../spec_helper'
+require_relative 'shared/arithmetic_coerce'
+
+describe "Integer#+" do
+ it_behaves_like :integer_arithmetic_coerce_not_rescue, :+
+
+ context "fixnum" do
+ it "returns self plus the given Integer" do
+ (491 + 2).should == 493
+ (90210 + 10).should == 90220
+
+ (9 + bignum_value).should == 18446744073709551625
+ (1001 + 5.219).should == 1006.219
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13 + obj
+ }.should.raise(TypeError)
+ -> { 13 + "10" }.should.raise(TypeError)
+ -> { 13 + :symbol }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(76)
+ end
+
+ it "returns self plus the given Integer" do
+ (@bignum + 4).should == 18446744073709551696
+ (@bignum + 4.2).should be_close(18446744073709551696.2, TOLERANCE)
+ (@bignum + bignum_value(3)).should == 36893488147419103311
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum + mock('10') }.should.raise(TypeError)
+ -> { @bignum + "10" }.should.raise(TypeError)
+ -> { @bignum + :symbol}.should.raise(TypeError)
+ end
+ end
+
+ it "can be redefined" do
+ code = <<~RUBY
+ class Integer
+ alias_method :old_plus, :+
+ def +(other)
+ self - other
+ end
+ end
+ result = 1 + 2
+ Integer.alias_method :+, :old_plus
+ print result
+ 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
new file mode 100644
index 0000000000..12a3839c40
--- /dev/null
+++ b/spec/ruby/core/integer/pow_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exponent'
+
+describe "Integer#pow" do
+ context "one argument is passed" do
+ it_behaves_like :integer_exponent, :pow
+ end
+
+ context "two arguments are passed" do
+ it "returns modulo of self raised to the given power" do
+ 2.pow(5, 12).should == 8
+ 2.pow(6, 13).should == 12
+ 2.pow(7, 14).should == 2
+ 2.pow(8, 15).should == 1
+ end
+
+ it "works well with bignums" 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
+ 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
+ end
+
+ it "ensures all arguments are integers" do
+ -> { 2.pow(5, 12.0) }.should.raise(TypeError, /2nd argument not allowed unless all arguments are integers/)
+ -> { 2.pow(5, Rational(12, 1)) }.should.raise(TypeError, /2nd argument not allowed unless all arguments are integers/)
+ end
+
+ it "raises TypeError for non-numeric value" do
+ -> { 2.pow(5, "12") }.should.raise(TypeError)
+ -> { 2.pow(5, []) }.should.raise(TypeError)
+ -> { 2.pow(5, nil) }.should.raise(TypeError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0" do
+ -> { 2.pow(5, 0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a RangeError when the first argument is negative and the second argument is present" do
+ -> { 2.pow(-5, 1) }.should.raise(RangeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/pred_spec.rb b/spec/ruby/core/integer/pred_spec.rb
new file mode 100644
index 0000000000..fce536b5a2
--- /dev/null
+++ b/spec/ruby/core/integer/pred_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "Integer#pred" do
+ it "returns the Integer equal to self - 1" do
+ 0.pred.should.eql?(-1)
+ -1.pred.should.eql?(-2)
+ bignum_value.pred.should.eql?(bignum_value(-1))
+ fixnum_min.pred.should.eql?(fixnum_min - 1)
+ 20.pred.should.eql?(19)
+ end
+end
diff --git a/spec/ruby/core/integer/rationalize_spec.rb b/spec/ruby/core/integer/rationalize_spec.rb
new file mode 100644
index 0000000000..272eca6911
--- /dev/null
+++ b/spec/ruby/core/integer/rationalize_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+
+describe "Integer#rationalize" do
+ before :all do
+ @numbers = [
+ 0,
+ 29871,
+ 99999999999999**99,
+ -72628191273,
+ ]
+ end
+
+ it "returns a Rational object" do
+ @numbers.each do |number|
+ number.rationalize.should.instance_of?(Rational)
+ end
+ end
+
+ it "uses self as the numerator" do
+ @numbers.each do |number|
+ number.rationalize.numerator.should == number
+ end
+ end
+
+ it "uses 1 as the denominator" do
+ @numbers.each do |number|
+ number.rationalize.denominator.should == 1
+ end
+ end
+
+ it "ignores a single argument" do
+ 1.rationalize(0.1).should == Rational(1,1)
+ end
+
+ it "raises ArgumentError when passed more than one argument" do
+ -> { 1.rationalize(0.1, 0.1) }.should.raise(ArgumentError)
+ -> { 1.rationalize(0.1, 0.1, 2) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/integer/remainder_spec.rb b/spec/ruby/core/integer/remainder_spec.rb
new file mode 100644
index 0000000000..deb81fb2a5
--- /dev/null
+++ b/spec/ruby/core/integer/remainder_spec.rb
@@ -0,0 +1,51 @@
+require_relative '../../spec_helper'
+
+describe "Integer#remainder" do
+ context "fixnum" do
+ it "returns the remainder of dividing self by other" do
+ 5.remainder(3).should == 2
+ 5.remainder(3.0).should == 2.0
+ 5.remainder(Rational(3, 1)).should == Rational(2, 1)
+ end
+
+ it "means x-y*(x/y).truncate" do
+ 5.remainder(3).should == 2
+ 5.remainder(3.3).should be_close(1.7, TOLERANCE)
+ 5.remainder(3.7).should be_close(1.3, TOLERANCE)
+ 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
+ end
+
+ it "raises TypeError if passed non-numeric argument" do
+ -> { 5.remainder("3") }.should.raise(TypeError)
+ -> { 5.remainder(:"3") }.should.raise(TypeError)
+ -> { 5.remainder([]) }.should.raise(TypeError)
+ -> { 5.remainder(nil) }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ it "returns the remainder of dividing self by other" do
+ a = bignum_value(79)
+ a.remainder(2).should == 1
+ a.remainder(97.345).should be_close(93.1349992295444, TOLERANCE)
+ a.remainder(bignum_value).should == 79
+ end
+
+ it "raises a ZeroDivisionError if other is zero and not a Float" do
+ -> { bignum_value(66).remainder(0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "does raises ZeroDivisionError if other is zero and a Float" do
+ a = bignum_value(7)
+ b = bignum_value(32)
+ -> { a.remainder(0.0) }.should.raise(ZeroDivisionError)
+ -> { b.remainder(-0.0) }.should.raise(ZeroDivisionError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/right_shift_spec.rb b/spec/ruby/core/integer/right_shift_spec.rb
new file mode 100644
index 0000000000..4281d3b7ab
--- /dev/null
+++ b/spec/ruby/core/integer/right_shift_spec.rb
@@ -0,0 +1,233 @@
+require_relative '../../spec_helper'
+
+describe "Integer#>> (with n >> m)" do
+ context "fixnum" do
+ it "returns n shifted right m bits when n > 0, m > 0" do
+ (2 >> 1).should == 1
+ end
+
+ it "returns n shifted right m bits when n < 0, m > 0" do
+ (-2 >> 1).should == -1
+ (-7 >> 1).should == -4
+ (-42 >> 2).should == -11
+ end
+
+ it "returns n shifted left m bits when n > 0, m < 0" do
+ (1 >> -1).should == 2
+ end
+
+ it "returns n shifted left m bits when n < 0, m < 0" do
+ (-1 >> -1).should == -2
+ end
+
+ it "returns 0 when n == 0" do
+ (0 >> 1).should == 0
+ end
+
+ it "returns n when n > 0, m == 0" do
+ (1 >> 0).should == 1
+ end
+
+ it "returns n when n < 0, m == 0" do
+ (-1 >> 0).should == -1
+ end
+
+ it "returns 0 when n > 0, m > 0 and n < 2**m" do
+ (3 >> 2).should == 0
+ (7 >> 3).should == 0
+ (127 >> 7).should == 0
+
+ # To make sure the exponent is not truncated
+ (7 >> 32).should == 0
+ (7 >> 64).should == 0
+ end
+
+ it "returns -1 when n < 0, m > 0 and n > -(2**m)" do
+ (-3 >> 2).should == -1
+ (-7 >> 3).should == -1
+ (-127 >> 7).should == -1
+
+ # To make sure the exponent is not truncated
+ (-7 >> 32).should == -1
+ (-7 >> 64).should == -1
+ end
+
+ it "returns a Bignum == fixnum_max * 2 when fixnum_max >> -1 and n > 0" do
+ result = fixnum_max >> -1
+ result.should.instance_of?(Integer)
+ result.should == fixnum_max * 2
+ end
+
+ it "returns a Bignum == fixnum_min * 2 when fixnum_min >> -1 and n < 0" do
+ result = fixnum_min >> -1
+ result.should.instance_of?(Integer)
+ result.should == fixnum_min * 2
+ end
+
+ it "calls #to_int to convert the argument to an Integer" do
+ obj = mock("2")
+ obj.should_receive(:to_int).and_return(2)
+ (8 >> obj).should == 2
+
+ obj = mock("to_int_bignum")
+ obj.should_receive(:to_int).and_return(bignum_value)
+ (8 >> obj).should == 0
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("a string")
+ obj.should_receive(:to_int).and_return("asdf")
+
+ -> { 3 >> obj }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { 3 >> nil }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { 3 >> "4" }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value * 8 # 2 ** 67
+ end
+
+ it "returns n shifted right m bits when n > 0, m > 0" do
+ (@bignum >> 1).should == 73786976294838206464
+ end
+
+ it "returns n shifted right m bits when n < 0, m > 0" do
+ (-@bignum >> 2).should == -36893488147419103232
+ end
+
+ it "respects twos complement signed shifting" do
+ # This explicit left hand value is important because it is the
+ # exact bit pattern that matters, so it's important it's right
+ # here to show the significance.
+ #
+
+ (-42949672980000000000000 >> 14).should == -2621440001220703125
+ (-42949672980000000000001 >> 14).should == -2621440001220703126
+ # Note the off by one -------------------- ^^^^^^^^^^^^^^^^^^^^
+ # This is because even though we discard the lowest bit, in twos
+ # complement it would influence the bits to the left of it.
+
+ (-42949672980000000000000 >> 15).should == -1310720000610351563
+ (-42949672980000000000001 >> 15).should == -1310720000610351563
+
+ (-0xfffffffffffffffff >> 32).should == -68719476736
+ end
+
+ it "respects twos complement signed shifting for very large values" do
+ giant = 42949672980000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ neg = -giant
+
+ (giant >> 84).should == 2220446050284288846538547929770901490087453566957265138626098632812
+ (neg >> 84).should == -2220446050284288846538547929770901490087453566957265138626098632813
+ end
+
+ it "returns n shifted left m bits when n > 0, m < 0" do
+ (@bignum >> -2).should == 590295810358705651712
+ end
+
+ it "returns n shifted left m bits when n < 0, m < 0" do
+ (-@bignum >> -3).should == -1180591620717411303424
+ end
+
+ it "returns n when n > 0, m == 0" do
+ (@bignum >> 0).should == @bignum
+ end
+
+ it "returns n when n < 0, m == 0" do
+ (-@bignum >> 0).should == -@bignum
+ end
+
+ it "returns 0 when m > 0 and m == p where 2**p > n >= 2**(p-1)" do
+ (@bignum >> 68).should == 0
+ end
+
+ it "returns a Fixnum == fixnum_max when (fixnum_max * 2) >> 1 and n > 0" do
+ result = (fixnum_max * 2) >> 1
+ result.should.instance_of?(Integer)
+ result.should == fixnum_max
+ end
+
+ it "returns a Fixnum == fixnum_min when (fixnum_min * 2) >> 1 and n < 0" do
+ result = (fixnum_min * 2) >> 1
+ result.should.instance_of?(Integer)
+ result.should == fixnum_min
+ end
+
+ it "calls #to_int to convert the argument to an Integer" do
+ obj = mock("2")
+ obj.should_receive(:to_int).and_return(2)
+
+ (@bignum >> obj).should == 36893488147419103232
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("a string")
+ obj.should_receive(:to_int).and_return("asdf")
+
+ -> { @bignum >> obj }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @bignum >> nil }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { @bignum >> "4" }.should.raise(TypeError)
+ end
+ end
+
+ context "when m is a bignum or larger than int" do
+ it "returns -1 when m > 0 and n < 0" do
+ (-1 >> bignum_value).should == -1
+ (-1 >> (2**40)).should == -1
+
+ (-bignum_value >> bignum_value).should == -1
+ (-bignum_value >> (2**40)).should == -1
+ end
+
+ it "returns 0 when m > 0 and n >= 0" do
+ (0 >> bignum_value).should == 0
+ (1 >> bignum_value).should == 0
+ (bignum_value >> bignum_value).should == 0
+
+ (0 >> (2**40)).should == 0
+ (1 >> (2**40)).should == 0
+ (bignum_value >> (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
+ (0 >> -bignum_value).should == 0
+ end
+
+ it "raises RangeError when m < 0 and n != 0" do
+ # https://bugs.ruby-lang.org/issues/18518#note-9
+ limit = RUBY_ENGINE == 'ruby' ? 2**67 : 2**32
+
+ coerce_long = mock("long")
+ coerce_long.stub!(:to_int).and_return(-limit)
+ coerce_bignum = mock("bignum")
+ coerce_bignum.stub!(:to_int).and_return(-bignum_value)
+ exps = [-limit, coerce_long]
+ exps << -bignum_value << coerce_bignum if bignum_value >= limit
+
+ exps.each { |exp|
+ -> { (1 >> exp) }.should.raise(RangeError, 'shift width too big')
+ -> { (-1 >> exp) }.should.raise(RangeError, 'shift width too big')
+ -> { (bignum_value >> exp) }.should.raise(RangeError, 'shift width too big')
+ -> { (-bignum_value >> exp) }.should.raise(RangeError, 'shift width too big')
+ }
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/round_spec.rb b/spec/ruby/core/integer/round_spec.rb
new file mode 100644
index 0000000000..a3f11e7a76
--- /dev/null
+++ b/spec/ruby/core/integer/round_spec.rb
@@ -0,0 +1,81 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+require_relative 'shared/integer_rounding'
+
+describe "Integer#round" do
+ it_behaves_like :integer_to_i, :round
+ it_behaves_like :integer_rounding_positive_precision, :round
+
+ # redmine:5228
+ it "returns itself rounded if passed a negative value" do
+ +249.round(-2).should.eql?(+200)
+ -249.round(-2).should.eql?(-200)
+ (+25 * 10**70 - 1).round(-71).should.eql?(+20 * 10**70)
+ (-25 * 10**70 + 1).round(-71).should.eql?(-20 * 10**70)
+ end
+
+ it "returns itself rounded to nearest if passed a negative value" do
+ +250.round(-2).should.eql?(+300)
+ -250.round(-2).should.eql?(-300)
+ (+25 * 10**70).round(-71).should.eql?(+30 * 10**70)
+ (-25 * 10**70).round(-71).should.eql?(-30 * 10**70)
+ end
+
+ it "raises a RangeError when passed a big negative value" do
+ -> { 42.round(min_long - 1) }.should.raise(RangeError)
+ end
+
+ it "raises a RangeError when passed Float::INFINITY" do
+ -> { 42.round(Float::INFINITY) }.should.raise(RangeError)
+ end
+
+ it "raises a RangeError when passed a beyond signed int" do
+ -> { 42.round(1<<31) }.should.raise(RangeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { 42.round("4") }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when its argument cannot be converted to an Integer" do
+ -> { 42.round(nil) }.should.raise(TypeError)
+ end
+
+ it "calls #to_int on the argument to convert it to an Integer" do
+ obj = mock("Object")
+ obj.should_receive(:to_int).and_return(0)
+ 42.round(obj)
+ end
+
+ it "raises a TypeError when #to_int does not return an Integer" do
+ obj = mock("Object")
+ obj.stub!(:to_int).and_return([])
+ -> { 42.round(obj) }.should.raise(TypeError)
+ end
+
+ it "returns different rounded values depending on the half option" do
+ 25.round(-1, half: :up).should.eql?(30)
+ 25.round(-1, half: :down).should.eql?(20)
+ 25.round(-1, half: :even).should.eql?(20)
+ 25.round(-1, half: nil).should.eql?(30)
+ 35.round(-1, half: :up).should.eql?(40)
+ 35.round(-1, half: :down).should.eql?(30)
+ 35.round(-1, half: :even).should.eql?(40)
+ 35.round(-1, half: nil).should.eql?(40)
+ (-25).round(-1, half: :up).should.eql?(-30)
+ (-25).round(-1, half: :down).should.eql?(-20)
+ (-25).round(-1, half: :even).should.eql?(-20)
+ (-25).round(-1, half: nil).should.eql?(-30)
+ end
+
+ it "returns itself if passed a positive precision and the half option" do
+ 35.round(1, half: :up).should.eql?(35)
+ 35.round(1, half: :down).should.eql?(35)
+ 35.round(1, half: :even).should.eql?(35)
+ end
+
+ it "raises ArgumentError for an unknown rounding mode" do
+ -> { 42.round(-1, half: :foo) }.should.raise(ArgumentError, /invalid rounding mode: foo/)
+ -> { 42.round(1, half: :foo) }.should.raise(ArgumentError, /invalid rounding mode: foo/)
+ end
+end
diff --git a/spec/ruby/core/integer/shared/abs.rb b/spec/ruby/core/integer/shared/abs.rb
new file mode 100644
index 0000000000..43844c9065
--- /dev/null
+++ b/spec/ruby/core/integer/shared/abs.rb
@@ -0,0 +1,18 @@
+describe :integer_abs, shared: true do
+ context "fixnum" do
+ it "returns self's absolute fixnum value" do
+ { 0 => [0, -0, +0], 2 => [2, -2, +2], 100 => [100, -100, +100] }.each do |key, values|
+ values.each do |value|
+ value.send(@method).should == key
+ end
+ end
+ end
+ end
+
+ context "bignum" do
+ it "returns the absolute bignum value" do
+ bignum_value(39).send(@method).should == 18446744073709551655
+ (-bignum_value(18)).send(@method).should == 18446744073709551634
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/arithmetic_coerce.rb b/spec/ruby/core/integer/shared/arithmetic_coerce.rb
new file mode 100644
index 0000000000..561b18fe52
--- /dev/null
+++ b/spec/ruby/core/integer/shared/arithmetic_coerce.rb
@@ -0,0 +1,11 @@
+require_relative '../fixtures/classes'
+
+describe :integer_arithmetic_coerce_not_rescue, 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(IntegerSpecs::CoerceError)
+
+ # e.g. 1 + b
+ -> { 1.send(@method, b) }.should.raise(IntegerSpecs::CoerceError)
+ end
+end
diff --git a/spec/ruby/core/integer/shared/comparison_coerce.rb b/spec/ruby/core/integer/shared/comparison_coerce.rb
new file mode 100644
index 0000000000..4bb7404183
--- /dev/null
+++ b/spec/ruby/core/integer/shared/comparison_coerce.rb
@@ -0,0 +1,11 @@
+require_relative '../fixtures/classes'
+
+describe :integer_comparison_coerce_not_rescue, 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(IntegerSpecs::CoerceError)
+
+ # e.g. 1 > b
+ -> { 1.send(@method, b) }.should.raise(IntegerSpecs::CoerceError)
+ end
+end
diff --git a/spec/ruby/core/integer/shared/equal.rb b/spec/ruby/core/integer/shared/equal.rb
new file mode 100644
index 0000000000..c621ba3f81
--- /dev/null
+++ b/spec/ruby/core/integer/shared/equal.rb
@@ -0,0 +1,63 @@
+describe :integer_equal, shared: true do
+ context "fixnum" do
+ it "returns true if self has the same value as other" do
+ 1.send(@method, 1).should == true
+ 9.send(@method, 5).should == false
+
+ # Actually, these call Float#==, Integer#== etc.
+ 9.send(@method, 9.0).should == true
+ 9.send(@method, 9.01).should == false
+
+ 10.send(@method, bignum_value).should == false
+ end
+
+ it "calls 'other == self' if the given argument is not an Integer" do
+ 1.send(@method, '*').should == false
+
+ obj = mock('one other')
+ obj.should_receive(:==).any_number_of_times.and_return(false)
+ 1.send(@method, obj).should == false
+
+ obj = mock('another')
+ obj.should_receive(:==).any_number_of_times.and_return(true)
+ 2.send(@method, obj).should == true
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value
+ end
+
+ it "returns true if self has the same value as the given argument" do
+ @bignum.send(@method, @bignum).should == true
+ @bignum.send(@method, @bignum.to_f).should == true
+
+ @bignum.send(@method, @bignum + 1).should == false
+ (@bignum + 1).send(@method, @bignum).should == false
+
+ @bignum.send(@method, 9).should == false
+ @bignum.send(@method, 9.01).should == false
+
+ @bignum.send(@method, bignum_value(10)).should == false
+ end
+
+ it "calls 'other == self' if the given argument is not an Integer" do
+ obj = mock('not integer')
+ obj.should_receive(:==).and_return(true)
+ @bignum.send(@method, obj).should == true
+ end
+
+ it "returns the result of 'other == self' as a boolean" do
+ obj = mock('not integer')
+ obj.should_receive(:==).exactly(2).times.and_return("woot", nil)
+ @bignum.send(@method, obj).should == true
+ @bignum.send(@method, obj).should == false
+ end
+
+ it "does not lose precision when comparing with a Float" do
+ (bignum_value(1).send(@method, bignum_value.to_f)).should == false
+ (bignum_value.send(@method, bignum_value.to_f)).should == true
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/exponent.rb b/spec/ruby/core/integer/shared/exponent.rb
new file mode 100644
index 0000000000..0be13859f9
--- /dev/null
+++ b/spec/ruby/core/integer/shared/exponent.rb
@@ -0,0 +1,162 @@
+describe :integer_exponent, shared: true do
+ context "fixnum" do
+ it "returns self raised to the given power" do
+ 2.send(@method, 0).should.eql? 1
+ 2.send(@method, 1).should.eql? 2
+ 2.send(@method, 2).should.eql? 4
+
+ 9.send(@method, 0.5).should.eql? 3.0
+ 9.send(@method, Rational(1, 2)).should.eql? 3.0
+ 5.send(@method, -1).to_f.to_s.should == '0.2'
+
+ 2.send(@method, 40).should.eql? 1099511627776
+ end
+
+ it "overflows the answer to a bignum transparently" do
+ 2.send(@method, 29).should.eql? 536870912
+ 2.send(@method, 30).should.eql? 1073741824
+ 2.send(@method, 31).should.eql? 2147483648
+ 2.send(@method, 32).should.eql? 4294967296
+
+ 2.send(@method, 61).should.eql? 2305843009213693952
+ 2.send(@method, 62).should.eql? 4611686018427387904
+ 2.send(@method, 63).should.eql? 9223372036854775808
+ 2.send(@method, 64).should.eql? 18446744073709551616
+ 8.send(@method, 23).should.eql? 590295810358705651712
+ end
+
+ it "raises negative numbers to the given power" do
+ (-2).send(@method, 29).should.eql?(-536870912)
+ (-2).send(@method, 30).should.eql?(1073741824)
+ (-2).send(@method, 31).should.eql?(-2147483648)
+ (-2).send(@method, 32).should.eql?(4294967296)
+ (-2).send(@method, 33).should.eql?(-8589934592)
+
+ (-2).send(@method, 61).should.eql?(-2305843009213693952)
+ (-2).send(@method, 62).should.eql?(4611686018427387904)
+ (-2).send(@method, 63).should.eql?(-9223372036854775808)
+ (-2).send(@method, 64).should.eql?(18446744073709551616)
+ (-2).send(@method, 65).should.eql?(-36893488147419103232)
+ end
+
+ it "can raise 1 to a bignum safely" do
+ 1.send(@method, 4611686018427387904).should.eql? 1
+ end
+
+ it "can raise -1 to a bignum safely" do
+ (-1).send(@method, 4611686018427387904).should.eql?(1)
+ (-1).send(@method, 4611686018427387905).should.eql?(-1)
+ end
+
+ 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 "returns an Integer for results larger than the old 32MB limit" do
+ # 2 ** 40000000 requires 40000001 bits
+ # This exceeds the old 32MB limit but is within the new 16GB limit
+ result = 2.send(@method, 40000000)
+ result.should.is_a?(Integer)
+ result.bit_length.should == 40000001
+ end
+
+ it "raises an ArgumentError when the result size exceeds the limit" do
+ -> { 100000000.send(@method, 1000000000) }.should.raise(ArgumentError)
+ end
+ end
+
+ it "raises a ZeroDivisionError for 0 ** -1" do
+ -> { 0.send(@method, -1) }.should.raise(ZeroDivisionError)
+ -> { 0.send(@method, Rational(-1, 1)) }.should.raise(ZeroDivisionError)
+ end
+
+ it "returns Float::INFINITY for 0 ** -1.0" do
+ 0.send(@method, -1.0).should == Float::INFINITY
+ end
+
+ it "raises a TypeError when given a non-numeric power" do
+ -> { 13.send(@method, "10") }.should.raise(TypeError)
+ -> { 13.send(@method, :symbol) }.should.raise(TypeError)
+ -> { 13.send(@method, nil) }.should.raise(TypeError)
+ end
+
+ it "coerces power and calls #**" do
+ num_2 = mock("2")
+ num_13 = mock("13")
+ num_2.should_receive(:coerce).with(13).and_return([num_13, num_2])
+ num_13.should_receive(:**).with(num_2).and_return(169)
+
+ 13.send(@method, num_2).should == 169
+ end
+
+ it "returns Float when power is Float" do
+ 2.send(@method, 2.0).should == 4.0
+ end
+
+ it "returns Rational when power is Rational" do
+ 2.send(@method, Rational(2, 1)).should == Rational(4, 1)
+ end
+
+ it "returns a complex number when negative and raised to a fractional power" do
+ (-8).send(@method, 1.0/3) .should be_close(Complex(1, 1.73205), TOLERANCE)
+ (-8).send(@method, Rational(1, 3)).should be_close(Complex(1, 1.73205), TOLERANCE)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @bignum = bignum_value(47)
+ end
+
+ it "returns self raised to other power" do
+ (@bignum.send(@method, 4)).should == 115792089237316196603666111261383895964500887398800252671052694326794607293761
+ (@bignum.send(@method, 1.3)).should be_close(11109528802438156839288832.0, TOLERANCE)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum.send(@method, mock('10')) }.should.raise(TypeError)
+ -> { @bignum.send(@method, "10") }.should.raise(TypeError)
+ -> { @bignum.send(@method, :symbol) }.should.raise(TypeError)
+ end
+
+ 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.is_a?(Float)
+ flt.infinite?.should == 1
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "returns an Integer for large Bignum results exceeding the old limit" do
+ # (2 ** 70) ** 500000 requires 35000001 bits
+ # This exceeds the old 32MB limit but is within the new 16GB limit
+ bignum_base = 2 ** 70
+ result = bignum_base.send(@method, 500000)
+ result.should.is_a?(Integer)
+ result.bit_length.should == 35000001
+ end
+
+ it "raises an ArgumentError when Bignum result exceeds the limit" do
+ # @bignum ** @bignum would require enormous memory
+ -> {
+ @bignum.send(@method, @bignum)
+ }.should.raise(ArgumentError)
+ end
+ end
+
+ it "returns a complex number when negative and raised to a fractional power" do
+ (-bignum_value).send(@method, (1.0/2)).should be_close(Complex(0.0, 4294967296.0), TOLERANCE)
+ (-@bignum).send(@method, (1.0/3)) .should be_close(Complex(1321122.9748145656, 2288252.1154253655), TOLERANCE)
+ (-@bignum).send(@method, Rational(1,3)).should be_close(Complex(1321122.9748145656, 2288252.1154253655), TOLERANCE)
+ end
+ end
+end
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..b23c17937f
--- /dev/null
+++ b/spec/ruby/core/integer/shared/integer_ceil_precision.rb
@@ -0,0 +1,54 @@
+describe :integer_ceil_precision, shared: true do
+ context "precision is zero" do
+ it "returns Integer equal to 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 Integer equal to self if there are already at least precision.abs trailing zeros" do
+ send(@method, 10).ceil(-1).should.eql?(10)
+ send(@method, 100).ceil(-1).should.eql?(100)
+ send(@method, 100).ceil(-2).should.eql?(100)
+ send(@method, -10).ceil(-1).should.eql?(-10)
+ send(@method, -100).ceil(-1).should.eql?(-100)
+ send(@method, -100).ceil(-2).should.eql?(-100)
+ end
+
+ it "returns smallest Integer greater 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)
+
+ send(@method, 100).ceil(-3).should.eql?(1000)
+ send(@method, -100).ceil(-3).should.eql?(0)
+ end
+
+ # Bug #20654
+ it "returns 10**precision.abs when precision.abs has more digits than self" do
+ send(@method, 123).ceil(-20).should.eql?(100000000000000000000)
+ send(@method, 123).ceil(-50).should.eql?(100000000000000000000000000000000000000000000000000)
+ 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..6247907d4c
--- /dev/null
+++ b/spec/ruby/core/integer/shared/integer_floor_precision.rb
@@ -0,0 +1,42 @@
+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
+
+ # Bug #20654
+ 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
diff --git a/spec/ruby/core/integer/shared/integer_rounding.rb b/spec/ruby/core/integer/shared/integer_rounding.rb
new file mode 100644
index 0000000000..92f2a2327c
--- /dev/null
+++ b/spec/ruby/core/integer/shared/integer_rounding.rb
@@ -0,0 +1,19 @@
+describe :integer_rounding_positive_precision, shared: true do
+ it "returns self if not passed a precision" do
+ [2, -4, 10**70, -10**100].each do |v|
+ v.send(@method).should.eql?(v)
+ end
+ end
+
+ it "returns self if passed a precision of zero" do
+ [2, -4, 10**70, -10**100].each do |v|
+ v.send(@method, 0).should.eql?(v)
+ end
+ end
+
+ it "returns itself if passed a positive precision" do
+ [2, -4, 10**70, -10**100].each do |v|
+ v.send(@method, 42).should.eql?(v)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/modulo.rb b/spec/ruby/core/integer/shared/modulo.rb
new file mode 100644
index 0000000000..d0b5e26ed5
--- /dev/null
+++ b/spec/ruby/core/integer/shared/modulo.rb
@@ -0,0 +1,114 @@
+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
+
+ 13.send(@method, 4.0).should == 1
+ 4.send(@method, 13.0).should == 4
+
+ (-200).send(@method, 256).should == 56
+ (-1000).send(@method, 512).should == 24
+
+ (-200).send(@method, -256).should == -200
+ (-1000).send(@method, -512).should == -488
+
+ (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
+ -> { 13.send(@method, 0) }.should.raise(ZeroDivisionError)
+ -> { 0.send(@method, 0) }.should.raise(ZeroDivisionError)
+ -> { -10.send(@method, 0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { 0.send(@method, 0.0) }.should.raise(ZeroDivisionError)
+ -> { 10.send(@method, 0.0) }.should.raise(ZeroDivisionError)
+ -> { -10.send(@method, 0.0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> {
+ (obj = mock('10')).should_receive(:to_int).any_number_of_times.and_return(10)
+ 13.send(@method, obj)
+ }.should.raise(TypeError)
+ -> { 13.send(@method, "10") }.should.raise(TypeError)
+ -> { 13.send(@method, :symbol) }.should.raise(TypeError)
+ end
+ end
+
+ context "bignum" do
+ before :each do
+ @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, 5).should == 4
+ (-@bignum).send(@method, -5).should == -1
+
+ @bignum.send(@method, 2.22).should be_close(1.5603603603605034, TOLERANCE)
+ @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
+ -> { @bignum.send(@method, 0) }.should.raise(ZeroDivisionError)
+ -> { (-@bignum).send(@method, 0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a ZeroDivisionError when the given argument is 0 and a Float" do
+ -> { @bignum.send(@method, 0.0) }.should.raise(ZeroDivisionError)
+ -> { -@bignum.send(@method, 0.0) }.should.raise(ZeroDivisionError)
+ end
+
+ it "raises a TypeError when given a non-Integer" do
+ -> { @bignum.send(@method, mock('10')) }.should.raise(TypeError)
+ -> { @bignum.send(@method, "10") }.should.raise(TypeError)
+ -> { @bignum.send(@method, :symbol) }.should.raise(TypeError)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/shared/next.rb b/spec/ruby/core/integer/shared/next.rb
new file mode 100644
index 0000000000..85b83d6965
--- /dev/null
+++ b/spec/ruby/core/integer/shared/next.rb
@@ -0,0 +1,25 @@
+describe :integer_next, shared: true do
+ it "returns the next larger positive Fixnum" do
+ 2.send(@method).should == 3
+ end
+
+ it "returns the next larger negative Fixnum" do
+ (-2).send(@method).should == -1
+ end
+
+ it "returns the next larger positive Bignum" do
+ bignum_value.send(@method).should == bignum_value(1)
+ end
+
+ it "returns the next larger negative Bignum" do
+ (-bignum_value(1)).send(@method).should == -bignum_value
+ end
+
+ it "overflows a Fixnum to a Bignum" do
+ fixnum_max.send(@method).should == fixnum_max + 1
+ end
+
+ it "underflows a Bignum to a Fixnum" do
+ (fixnum_min - 1).send(@method).should == fixnum_min
+ end
+end
diff --git a/spec/ruby/core/integer/shared/to_i.rb b/spec/ruby/core/integer/shared/to_i.rb
new file mode 100644
index 0000000000..2b6a50484a
--- /dev/null
+++ b/spec/ruby/core/integer/shared/to_i.rb
@@ -0,0 +1,8 @@
+describe :integer_to_i, shared: true do
+ it "returns self" do
+ 10.send(@method).should.eql?(10)
+ (-15).send(@method).should.eql?(-15)
+ bignum_value.send(@method).should.eql?(bignum_value)
+ (-bignum_value).send(@method).should.eql?(-bignum_value)
+ end
+end
diff --git a/spec/ruby/core/integer/size_spec.rb b/spec/ruby/core/integer/size_spec.rb
new file mode 100644
index 0000000000..725e9eb062
--- /dev/null
+++ b/spec/ruby/core/integer/size_spec.rb
@@ -0,0 +1,34 @@
+require_relative '../../spec_helper'
+
+describe "Integer#size" 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
+ 4091.size.should == 4
+ end
+ end
+
+ 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
+ 4091.size.should == 8
+ end
+ end
+
+ context "bignum" do
+ it "returns the number of bytes required to hold the unsigned bignum data" do
+ # that is, n such that 256 * n <= val.abs < 256 * (n+1)
+ (256**7).size.should == 8
+ (256**8).size.should == 9
+ (256**9).size.should == 10
+ (256**10).size.should == 11
+ (256**10-1).size.should == 10
+ (256**11).size.should == 12
+ (256**12).size.should == 13
+ (256**20-1).size.should == 20
+ (256**40-1).size.should == 40
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/sqrt_spec.rb b/spec/ruby/core/integer/sqrt_spec.rb
new file mode 100644
index 0000000000..bf90ea747e
--- /dev/null
+++ b/spec/ruby/core/integer/sqrt_spec.rb
@@ -0,0 +1,31 @@
+require_relative '../../spec_helper'
+
+describe "Integer.sqrt" do
+ it "returns an integer" do
+ Integer.sqrt(10).should.is_a?(Integer)
+ end
+
+ it "returns the integer square root of the argument" do
+ Integer.sqrt(0).should == 0
+ Integer.sqrt(1).should == 1
+ Integer.sqrt(24).should == 4
+ Integer.sqrt(25).should == 5
+ Integer.sqrt(10**400).should == 10**200
+ end
+
+ it "raises a Math::DomainError if the argument is negative" do
+ -> { Integer.sqrt(-4) }.should.raise(Math::DomainError)
+ end
+
+ it "accepts any argument that can be coerced to Integer" do
+ Integer.sqrt(10.0).should == 3
+ end
+
+ it "converts the argument with #to_int" do
+ Integer.sqrt(mock_int(10)).should == 3
+ end
+
+ it "raises a TypeError if the argument cannot be coerced to Integer" do
+ -> { Integer.sqrt("test") }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/integer/succ_spec.rb b/spec/ruby/core/integer/succ_spec.rb
new file mode 100644
index 0000000000..9ae9a14fe7
--- /dev/null
+++ b/spec/ruby/core/integer/succ_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/next'
+
+describe "Integer#succ" do
+ it_behaves_like :integer_next, :succ
+end
diff --git a/spec/ruby/core/integer/times_spec.rb b/spec/ruby/core/integer/times_spec.rb
new file mode 100644
index 0000000000..356340592b
--- /dev/null
+++ b/spec/ruby/core/integer/times_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+
+describe "Integer#times" do
+ it "returns self" do
+ 5.times {}.should == 5
+ 9.times {}.should == 9
+ 9.times { |n| n - 2 }.should == 9
+ end
+
+ it "yields each value from 0 to self - 1" do
+ a = []
+ 9.times { |i| a << i }
+ -2.times { |i| a << i }
+ a.should == [0, 1, 2, 3, 4, 5, 6, 7, 8]
+ end
+
+ it "skips the current iteration when encountering 'next'" do
+ a = []
+ 3.times do |i|
+ next if i == 1
+ a << i
+ end
+ a.should == [0, 2]
+ end
+
+ it "skips all iterations when encountering 'break'" do
+ a = []
+ 5.times do |i|
+ break if i == 3
+ a << i
+ end
+ a.should == [0, 1, 2]
+ end
+
+ it "skips all iterations when encountering break with an argument and returns that argument" do
+ 9.times { break 2 }.should == 2
+ end
+
+ it "executes a nested while loop containing a break expression" do
+ a = [false]
+ b = 1.times do |i|
+ while true
+ a.shift or break
+ end
+ end
+ a.should == []
+ b.should == 1
+ end
+
+ it "executes a nested #times" do
+ a = 0
+ b = 3.times do |i|
+ 2.times { a += 1 }
+ end
+ a.should == 6
+ b.should == 3
+ end
+
+ it "returns an Enumerator" do
+ result = []
+
+ enum = 3.times
+ enum.each { |i| result << i }
+
+ result.should == [0, 1, 2]
+ end
+
+ describe "when no block is given" do
+ describe "returned Enumerator" do
+ describe "size" do
+ it "returns self" do
+ 5.times.size.should == 5
+ 10.times.size.should == 10
+ 0.times.size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/to_f_spec.rb b/spec/ruby/core/integer/to_f_spec.rb
new file mode 100644
index 0000000000..0681236095
--- /dev/null
+++ b/spec/ruby/core/integer/to_f_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "Integer#to_f" do
+ context "fixnum" do
+ it "returns self converted to a Float" do
+ 0.to_f.should == 0.0
+ -500.to_f.should == -500.0
+ 9_641_278.to_f.should == 9641278.0
+ end
+ end
+
+ context "bignum" do
+ it "returns self converted to a Float" do
+ bignum_value(0x4000_0aa0_0bb0_0000).to_f.should.eql?(23_058_441_774_644_068_352.0)
+ bignum_value(0x8000_0000_0000_0ccc).to_f.should.eql?(27_670_116_110_564_330_700.0)
+ (-bignum_value(99)).to_f.should.eql?(-18_446_744_073_709_551_715.0)
+ end
+
+ it "converts number close to Float::MAX without exceeding MAX or producing NaN" do
+ (10**308).to_f.should == 10.0 ** 308
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/to_i_spec.rb b/spec/ruby/core/integer/to_i_spec.rb
new file mode 100644
index 0000000000..1a8e477b3c
--- /dev/null
+++ b/spec/ruby/core/integer/to_i_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Integer#to_i" do
+ it_behaves_like :integer_to_i, :to_i
+end
diff --git a/spec/ruby/core/integer/to_int_spec.rb b/spec/ruby/core/integer/to_int_spec.rb
new file mode 100644
index 0000000000..4b7eccad3a
--- /dev/null
+++ b/spec/ruby/core/integer/to_int_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+
+describe "Integer#to_int" do
+ it_behaves_like :integer_to_i, :to_int
+end
diff --git a/spec/ruby/core/integer/to_r_spec.rb b/spec/ruby/core/integer/to_r_spec.rb
new file mode 100644
index 0000000000..2649c7c78d
--- /dev/null
+++ b/spec/ruby/core/integer/to_r_spec.rb
@@ -0,0 +1,26 @@
+require_relative '../../spec_helper'
+
+describe "Integer#to_r" do
+ it "returns a Rational object" do
+ 309.to_r.should.instance_of?(Rational)
+ end
+
+ it "constructs a rational number with self as the numerator" do
+ 34.to_r.numerator.should == 34
+ end
+
+ it "constructs a rational number with 1 as the denominator" do
+ 298.to_r.denominator.should == 1
+ end
+
+ it "works even if self is a Bignum" do
+ bignum = 99999**999
+ bignum.should.instance_of?(Integer)
+ bignum.to_r.should == Rational(bignum, 1)
+ end
+
+ it "raises an ArgumentError if given any arguments" do
+ -> { 287.to_r(2) }.should.raise(ArgumentError)
+ -> { 9102826.to_r(309, [], 71) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/integer/to_s_spec.rb b/spec/ruby/core/integer/to_s_spec.rb
new file mode 100644
index 0000000000..4970a2a12f
--- /dev/null
+++ b/spec/ruby/core/integer/to_s_spec.rb
@@ -0,0 +1,95 @@
+require_relative '../../spec_helper'
+
+describe "Integer#to_s" do
+ context "fixnum" do
+ context "when given a base" do
+ it "returns self converted to a String in the given base" do
+ 12345.to_s(2).should == "11000000111001"
+ 12345.to_s(8).should == "30071"
+ 12345.to_s(10).should == "12345"
+ 12345.to_s(16).should == "3039"
+ 95.to_s(16).should == "5f"
+ 12345.to_s(36).should == "9ix"
+ end
+
+ it "raises an ArgumentError if the base is less than 2 or higher than 36" do
+ -> { 123.to_s(-1) }.should.raise(ArgumentError)
+ -> { 123.to_s(0) }.should.raise(ArgumentError)
+ -> { 123.to_s(1) }.should.raise(ArgumentError)
+ -> { 123.to_s(37) }.should.raise(ArgumentError)
+ end
+ end
+
+ context "when no base given" do
+ it "returns self converted to a String using base 10" do
+ 255.to_s.should == '255'
+ 3.to_s.should == '3'
+ 0.to_s.should == '0'
+ -9002.to_s.should == '-9002'
+ end
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ 1.to_s.encoding.should.equal?(Encoding::US_ASCII)
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do
+ Encoding.default_internal = Encoding::IBM437
+ 1.to_s.encoding.should.equal?(Encoding::US_ASCII)
+ end
+ end
+
+ context "bignum" do
+ describe "when given a base" do
+ it "returns self converted to a String using the given base" do
+ a = 2**64
+ a.to_s(2).should == "10000000000000000000000000000000000000000000000000000000000000000"
+ a.to_s(8).should == "2000000000000000000000"
+ a.to_s(16).should == "10000000000000000"
+ a.to_s(32).should == "g000000000000"
+ end
+
+ it "raises an ArgumentError if the base is less than 2 or higher than 36" do
+ -> { 123.to_s(-1) }.should.raise(ArgumentError)
+ -> { 123.to_s(0) }.should.raise(ArgumentError)
+ -> { 123.to_s(1) }.should.raise(ArgumentError)
+ -> { 123.to_s(37) }.should.raise(ArgumentError)
+ end
+ end
+
+ describe "when given no base" do
+ it "returns self converted to a String using base 10" do
+ bignum_value(9).to_s.should == "18446744073709551625"
+ bignum_value.to_s.should == "18446744073709551616"
+ (-bignum_value(675)).to_s.should == "-18446744073709552291"
+ end
+ end
+
+ before :each do
+ @internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is nil" do
+ Encoding.default_internal = nil
+ bignum_value.to_s.encoding.should.equal?(Encoding::US_ASCII)
+ end
+
+ it "returns a String in US-ASCII encoding when Encoding.default_internal is not nil" do
+ Encoding.default_internal = Encoding::IBM437
+ bignum_value.to_s.encoding.should.equal?(Encoding::US_ASCII)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/truncate_spec.rb b/spec/ruby/core/integer/truncate_spec.rb
new file mode 100644
index 0000000000..b95c183cf3
--- /dev/null
+++ b/spec/ruby/core/integer/truncate_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_i'
+require_relative 'shared/integer_rounding'
+
+describe "Integer#truncate" do
+ it_behaves_like :integer_to_i, :truncate
+ it_behaves_like :integer_rounding_positive_precision, :truncate
+
+ context "precision argument specified as part of the truncate method is negative" do
+ it "returns an integer with at least precision.abs trailing zeros" do
+ 1832.truncate(-1).should.eql?(1830)
+ 1832.truncate(-2).should.eql?(1800)
+ 1832.truncate(-3).should.eql?(1000)
+ -1832.truncate(-1).should.eql?(-1830)
+ -1832.truncate(-2).should.eql?(-1800)
+ -1832.truncate(-3).should.eql?(-1000)
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/try_convert_spec.rb b/spec/ruby/core/integer/try_convert_spec.rb
new file mode 100644
index 0000000000..8c9b4276b9
--- /dev/null
+++ b/spec/ruby/core/integer/try_convert_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+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 == 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 == 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 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_consistent_error(TypeError, "can't convert MockObject into 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_consistent_error(TypeError, "can't convert MockObject into 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(RuntimeError)
+ end
+end
diff --git a/spec/ruby/core/integer/uminus_spec.rb b/spec/ruby/core/integer/uminus_spec.rb
new file mode 100644
index 0000000000..7a9cfe89bf
--- /dev/null
+++ b/spec/ruby/core/integer/uminus_spec.rb
@@ -0,0 +1,30 @@
+require_relative '../../spec_helper'
+
+describe "Integer#-@" do
+ context "fixnum" do
+ it "returns self as a negative value" do
+ 2.send(:-@).should == -2
+ -2.should == -2
+ -268435455.should == -268435455
+ (--5).should == 5
+ -8.send(:-@).should == 8
+ end
+
+ it "negates self at Fixnum/Bignum boundaries" do
+ (-fixnum_max).should == (0 - fixnum_max)
+ (-fixnum_max).should < 0
+ (-fixnum_min).should == (0 - fixnum_min)
+ (-fixnum_min).should > 0
+ end
+ end
+
+ context "bignum" do
+ it "returns self as a negative value" do
+ bignum_value.send(:-@).should == -18446744073709551616
+ (-bignum_value).send(:-@).should == 18446744073709551616
+
+ bignum_value(921).send(:-@).should == -18446744073709552537
+ (-bignum_value(921).send(:-@)).should == 18446744073709552537
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/upto_spec.rb b/spec/ruby/core/integer/upto_spec.rb
new file mode 100644
index 0000000000..ba63dcc9ea
--- /dev/null
+++ b/spec/ruby/core/integer/upto_spec.rb
@@ -0,0 +1,69 @@
+require_relative '../../spec_helper'
+
+describe "Integer#upto [stop] when self and stop are Integers" do
+ it "does not yield when stop is less than self" do
+ result = []
+ 5.upto(4) { |x| result << x }
+ result.should == []
+ end
+
+ it "yields once when stop equals self" do
+ result = []
+ 5.upto(5) { |x| result << x }
+ result.should == [5]
+ end
+
+ it "yields while increasing self until it is less than stop" do
+ result = []
+ 2.upto(5) { |x| result << x }
+ result.should == [2, 3, 4, 5]
+ end
+
+ it "yields while increasing self until it is greater than floor of a Float endpoint" do
+ result = []
+ 9.upto(13.3) {|i| result << i}
+ -5.upto(-1.3) {|i| result << i}
+ result.should == [9,10,11,12,13,-5,-4,-3,-2]
+ end
+
+ it "raises an ArgumentError for non-numeric endpoints" do
+ -> { 1.upto("A") {|x| p x} }.should.raise(ArgumentError)
+ -> { 1.upto(nil) {|x| p x} }.should.raise(ArgumentError)
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ result = []
+
+ enum = 2.upto(5)
+ enum.each { |i| result << i }
+
+ result.should == [2, 3, 4, 5]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "raises an ArgumentError for non-numeric endpoints" do
+ enum = 1.upto("A")
+ -> { enum.size }.should.raise(ArgumentError)
+ enum = 1.upto(nil)
+ -> { enum.size }.should.raise(ArgumentError)
+ end
+
+ it "returns stop - self + 1" do
+ 5.upto(10).size.should == 6
+ 1.upto(10).size.should == 10
+ 0.upto(10).size.should == 11
+ 0.upto(0).size.should == 1
+ -5.upto(-3).size.should == 3
+ end
+
+ it "returns 0 when stop < self" do
+ 5.upto(4).size.should == 0
+ 0.upto(-5).size.should == 0
+ -3.upto(-5).size.should == 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/integer/zero_spec.rb b/spec/ruby/core/integer/zero_spec.rb
new file mode 100644
index 0000000000..bd362c4181
--- /dev/null
+++ b/spec/ruby/core/integer/zero_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../spec_helper'
+
+describe "Integer#zero?" do
+ it "returns true if self is 0" do
+ 0.should.zero?
+ 1.should_not.zero?
+ -1.should_not.zero?
+ end
+
+ it "Integer#zero? overrides Numeric#zero?" do
+ 42.method(:zero?).owner.should == Integer
+ end
+end
diff --git a/spec/ruby/core/io/advise_spec.rb b/spec/ruby/core/io/advise_spec.rb
new file mode 100644
index 0000000000..b77ed53ad4
--- /dev/null
+++ b/spec/ruby/core/io/advise_spec.rb
@@ -0,0 +1,86 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#advise" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "raises a TypeError if advise is not a Symbol" do
+ -> {
+ @io.advise("normal")
+ }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if offset cannot be coerced to an Integer" do
+ -> {
+ @io.advise(:normal, "wat")
+ }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if len cannot be coerced to an Integer" do
+ -> {
+ @io.advise(:normal, 0, "wat")
+ }.should.raise(TypeError)
+ end
+
+ it "raises a RangeError if offset is too big" do
+ -> {
+ @io.advise(:normal, 10 ** 32)
+ }.should.raise(RangeError)
+ end
+
+ it "raises a RangeError if len is too big" do
+ -> {
+ @io.advise(:normal, 0, 10 ** 32)
+ }.should.raise(RangeError)
+ end
+
+ it "raises a NotImplementedError if advise is not recognized" do
+ ->{
+ @io.advise(:foo)
+ }.should.raise(NotImplementedError)
+ end
+
+ it "supports the normal advice type" do
+ @io.advise(:normal).should == nil
+ end
+
+ it "supports the sequential advice type" do
+ @io.advise(:sequential).should == nil
+ end
+
+ it "supports the random advice type" do
+ @io.advise(:random).should == nil
+ end
+
+ it "supports the dontneed advice type" do
+ @io.advise(:dontneed).should == nil
+ end
+
+ it "supports the noreuse advice type" do
+ @io.advise(:noreuse).should == nil
+ end
+
+ platform_is_not :linux do
+ it "supports the willneed advice type" do
+ @io.advise(:willneed).should == nil
+ end
+ end
+
+ guard -> { platform_is :linux and kernel_version_is '3.6' } do # [ruby-core:65355] tmpfs is not supported
+ it "supports the willneed advice type" do
+ @io.advise(:willneed).should == nil
+ end
+ end
+
+ it "raises an IOError if the stream is closed" do
+ @io.close
+ -> { @io.advise(:normal) }.should.raise(IOError)
+ 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..596f3d6934
--- /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(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(IOError, /closed stream/)
+ end
+end
diff --git a/spec/ruby/core/io/binmode_spec.rb b/spec/ruby/core/io/binmode_spec.rb
new file mode 100644
index 0000000000..8117229c91
--- /dev/null
+++ b/spec/ruby/core/io/binmode_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#binmode" do
+ before :each do
+ @name = tmp("io_binmode.txt")
+ end
+
+ after :each do
+ @io.close if @io and !@io.closed?
+ rm_r @name
+ end
+
+ it "returns self" do
+ @io = new_io(@name)
+ @io.binmode.should.equal?(@io)
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.binmode }.should.raise(IOError)
+ end
+
+ it "sets external encoding to binary" do
+ @io = new_io(@name, "w:utf-8")
+ @io.binmode
+ @io.external_encoding.should == Encoding::BINARY
+ end
+
+ it "sets internal encoding to nil" do
+ @io = new_io(@name, "w:utf-8:ISO-8859-1")
+ @io.binmode
+ @io.internal_encoding.should == nil
+ end
+end
+
+describe "IO#binmode?" do
+ before :each do
+ @filename = tmp("IO_binmode_file")
+ @file = File.open(@filename, "w")
+ @duped = nil
+ end
+
+ after :each do
+ @duped.close if @duped
+ @file.close
+ rm_r @filename
+ end
+
+ it "is true after a call to IO#binmode" do
+ @file.binmode?.should == false
+ @file.binmode
+ @file.binmode?.should == true
+ end
+
+ it "propagates to dup'ed IO objects" do
+ @file.binmode
+ @duped = @file.dup
+ @duped.binmode?.should == @file.binmode?
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.binmode? }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb
new file mode 100644
index 0000000000..3023c7f177
--- /dev/null
+++ b/spec/ruby/core/io/binread_spec.rb
@@ -0,0 +1,57 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO.binread" do
+ before :each do
+ @internal = Encoding.default_internal
+
+ @fname = tmp('io_read.txt')
+ @contents = "1234567890"
+ touch(@fname) { |f| f.write @contents }
+ end
+
+ after :each do
+ rm_r @fname
+ Encoding.default_internal = @internal
+ end
+
+ it "reads the contents of a file" do
+ IO.binread(@fname).should == @contents
+ end
+
+ it "reads the contents of a file up to a certain size when specified" do
+ IO.binread(@fname, 5).should == @contents.slice(0..4)
+ end
+
+ it "reads the contents of a file from an offset of a specific size when specified" do
+ IO.binread(@fname, 5, 3).should == @contents.slice(3, 5)
+ end
+
+ it "returns a String in BINARY encoding" do
+ IO.binread(@fname).encoding.should == Encoding::BINARY
+ end
+
+ it "returns a String in BINARY encoding regardless of Encoding.default_internal" do
+ Encoding.default_internal = Encoding::EUC_JP
+ IO.binread(@fname).encoding.should == Encoding::BINARY
+ end
+
+ it "raises an ArgumentError when not passed a valid length" do
+ -> { IO.binread @fname, -1 }.should.raise(ArgumentError)
+ end
+
+ it "raises an Errno::EINVAL when not passed a valid offset" do
+ -> { IO.binread @fname, 0, -1 }.should.raise(Errno::EINVAL)
+ end
+
+ ruby_version_is ""..."4.0" do
+ # https://bugs.ruby-lang.org/issues/19630
+ it "warns about deprecation given a path with a pipe" do
+ cmd = "|echo ok"
+ -> {
+ IO.binread(cmd)
+ }.should complain(/IO process creation with a leading '\|'/)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/binwrite_spec.rb b/spec/ruby/core/io/binwrite_spec.rb
new file mode 100644
index 0000000000..8ebc86a52e
--- /dev/null
+++ b/spec/ruby/core/io/binwrite_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/binwrite'
+
+describe "IO.binwrite" do
+ it_behaves_like :io_binwrite, :binwrite
+end
diff --git a/spec/ruby/core/io/buffer/and_spec.rb b/spec/ruby/core/io/buffer/and_spec.rb
new file mode 100644
index 0000000000..3cea163b0e
--- /dev/null
+++ b/spec/ruby/core/io/buffer/and_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../../spec_helper'
+
+describe :io_buffer_and, shared: true do
+ it "applies the argument buffer as an AND bit mask across the whole buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer.send(@method, mask)
+ result.get_string.should == "\x30\x02\x30\x04\x30".b
+ result.free
+ end
+ end
+ end
+
+ it "ignores extra parts of mask if it is longer than source buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F\x00\x00\x00\xFF\xFF") do |mask|
+ result = buffer.send(@method, mask)
+ result.get_string.should == "\x30\x02\x00\x00\x00".b
+ result.free
+ end
+ end
+ end
+
+ it "raises TypeError if mask is not an IO::Buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ -> { buffer.send(@method, "\xF8\x8F") }.should.raise(TypeError, "wrong argument type String (expected IO::Buffer)")
+ -> { buffer.send(@method, 0xF8) }.should.raise(TypeError, "wrong argument type Integer (expected IO::Buffer)")
+ -> { buffer.send(@method, nil) }.should.raise(TypeError, "wrong argument type nil (expected IO::Buffer)")
+ end
+ end
+end
+
+describe "IO::Buffer#&" do
+ it_behaves_like :io_buffer_and, :&
+
+ it "creates a new internal buffer of the same size" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer & mask
+ result.should_not.equal? buffer
+ result.should.internal?
+ result.size.should == buffer.size
+ result.free
+ buffer.get_string.should == "12345".b
+ end
+ end
+ end
+end
+
+describe "IO::Buffer#and!" do
+ it_behaves_like :io_buffer_and, :and!
+
+ it "modifies the buffer in place" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer.and!(mask)
+ result.should.equal? buffer
+ result.should.external?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/buffer/bit_count_spec.rb b/spec/ruby/core/io/buffer/bit_count_spec.rb
new file mode 100644
index 0000000000..62f56f5b98
--- /dev/null
+++ b/spec/ruby/core/io/buffer/bit_count_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../../spec_helper'
+
+ruby_version_is "4.1" do
+ describe "IO::Buffer#bit_count" do
+ it "counts all set bits in the whole buffer" do
+ IO::Buffer.for(+"\xFF\x00\x0F") do |buffer|
+ buffer.bit_count.should == 12
+ end
+ end
+
+ it "returns 0 for a buffer of all zero bytes" do
+ IO::Buffer.for(+"\x00\x00\x00") do |buffer|
+ buffer.bit_count.should == 0
+ end
+ end
+
+ it "returns 8 * size for a buffer of all 0xFF bytes" do
+ IO::Buffer.for(+"\xFF" * 9) do |buffer|
+ buffer.bit_count.should == 72
+ end
+ end
+
+ it "returns 0 for an empty buffer" do
+ IO::Buffer.new(0).bit_count.should == 0
+ end
+
+ it "accepts an offset to start counting from (length defaults to remaining bytes)" do
+ IO::Buffer.for(+"\xFF\x00\x0F") do |buffer|
+ buffer.bit_count(0).should == 12 # offset=0 => entire buffer
+ buffer.bit_count(1).should == 4 # offset=1 => 0x00 + 0x0F
+ buffer.bit_count(2).should == 4 # offset=2 => 0x0F only
+ end
+ end
+
+ it "accepts an offset and length to restrict the counted region" do
+ IO::Buffer.for(+"\xFF\x00\x0F") do |buffer|
+ buffer.bit_count(0, 1).should == 8 # just 0xFF
+ buffer.bit_count(1, 1).should == 0 # just 0x00
+ buffer.bit_count(2, 1).should == 4 # just 0x0F
+ buffer.bit_count(1, 2).should == 4 # 0x00 + 0x0F
+ end
+ end
+
+ it "handles 8-byte aligned buffers efficiently" do
+ IO::Buffer.for(+"\xAA" * 8) do |buffer|
+ # 0xAA = 10101010 => 4 bits per byte => 32 total
+ buffer.bit_count.should == 32
+ end
+ end
+
+ it "raises ArgumentError when offset + length exceeds buffer size" do
+ IO::Buffer.for(+"\xFF") do |buffer|
+ -> { buffer.bit_count(0, 2) }.should.raise(ArgumentError)
+ -> { buffer.bit_count(1, 1) }.should.raise(ArgumentError)
+ end
+ end
+
+ it "returns an Integer" do
+ IO::Buffer.for(+"\xFF") do |buffer|
+ buffer.bit_count.should.is_a?(Integer)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/buffer/empty_spec.rb b/spec/ruby/core/io/buffer/empty_spec.rb
new file mode 100644
index 0000000000..4cceb4dc0d
--- /dev/null
+++ b/spec/ruby/core/io/buffer/empty_spec.rb
@@ -0,0 +1,27 @@
+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 == true
+ end
+
+ it "is true for a 0-length String-backed buffer created with .string" do
+ IO::Buffer.string(0) do |buffer|
+ buffer.empty?.should == true
+ 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 == 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..18b2ee0d06
--- /dev/null
+++ b/spec/ruby/core/io/buffer/external_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer#external?" do
+ after :each do
+ @buffer&.free
+ @buffer = nil
+ end
+
+ it "is true for a buffer with externally-managed memory" do
+ @buffer = IO::Buffer.for("string")
+ @buffer.external?.should == true
+ end
+
+ it "is false for a buffer with self-managed memory" do
+ @buffer = IO::Buffer.new(12, IO::Buffer::MAPPED)
+ @buffer.external?.should == false
+ end
+
+ it "is false for a null buffer" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.external?.should == false
+ end
+end
diff --git a/spec/ruby/core/io/buffer/for_spec.rb b/spec/ruby/core/io/buffer/for_spec.rb
new file mode 100644
index 0000000000..4c614f74b0
--- /dev/null
+++ b/spec/ruby/core/io/buffer/for_spec.rb
@@ -0,0 +1,95 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer.for" do
+ before :each do
+ @string = +"för striñg"
+ end
+
+ after :each do
+ @buffer&.free
+ @buffer = nil
+ end
+
+ context "without a block" do
+ it "copies string's contents, creating a separate read-only buffer" do
+ @buffer = IO::Buffer.for(@string)
+
+ @buffer.size.should == @string.bytesize
+ @buffer.get_string.should == @string.b
+
+ @string[0] = "d"
+ @buffer.get_string(0, 1).should == "f".b
+
+ -> { @buffer.set_string("d") }.should.raise(IO::Buffer::AccessError, "Buffer is not writable!")
+ end
+
+ it "creates an external, read-only buffer" do
+ @buffer = IO::Buffer.for(@string)
+
+ @buffer.should_not.internal?
+ @buffer.should_not.mapped?
+ @buffer.should.external?
+
+ @buffer.should_not.empty?
+ @buffer.should_not.null?
+
+ @buffer.should_not.shared?
+ @buffer.should_not.private?
+ @buffer.should.readonly?
+
+ @buffer.should_not.locked?
+ @buffer.should.valid?
+ end
+ end
+
+ context "with a block" do
+ it "returns the last value in the block" do
+ value =
+ IO::Buffer.for(@string) do |buffer|
+ buffer.size * 3
+ end
+ value.should == @string.bytesize * 3
+ end
+
+ it "frees the buffer at the end of the block" do
+ IO::Buffer.for(@string) do |buffer|
+ @buffer = buffer
+ @buffer.should_not.null?
+ end
+ @buffer.should.null?
+ end
+
+ context "if string is not frozen" do
+ it "creates a modifiable string-backed buffer" do
+ IO::Buffer.for(@string) do |buffer|
+ buffer.size.should == @string.bytesize
+ buffer.get_string.should == @string.b
+
+ buffer.should_not.readonly?
+ buffer.should_not.locked?
+
+ buffer.set_string("ghost shell")
+ @string.should == "ghost shellg"
+ end
+ end
+
+ it "locks the original string to prevent modification" do
+ IO::Buffer.for(@string) do |_buffer|
+ -> { @string[0] = "t" }.should.raise(RuntimeError, "can't modify string; temporarily locked")
+ end
+ @string[1] = "u"
+ @string.should == "fur striñg"
+ end
+ end
+
+ context "if string is frozen" do
+ it "creates a read-only string-backed buffer" do
+ IO::Buffer.for(@string.freeze) do |buffer|
+ buffer.should.readonly?
+
+ -> { buffer.set_string("ghost shell") }.should.raise(IO::Buffer::AccessError, "Buffer is not writable!")
+ 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..20fb7b901f
--- /dev/null
+++ b/spec/ruby/core/io/buffer/free_spec.rb
@@ -0,0 +1,102 @@
+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 == true
+ end
+
+ it "frees mapped memory and nullifies the buffer" do
+ buffer = IO::Buffer.new(4, IO::Buffer::MAPPED)
+ buffer.free
+ buffer.null?.should == 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 == 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 == 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 == true
+ end
+ string.should == "meat"
+ end
+ end
+ end
+
+ 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 == true
+ end
+ string.should == "meat"
+ end
+ end
+
+ it "can be called repeatedly without an error" do
+ buffer = IO::Buffer.new(4)
+ buffer.free
+ buffer.null?.should == true
+ buffer.free
+ buffer.null?.should == true
+ end
+
+ it "is disallowed while locked, raising IO::Buffer::LockedError" do
+ buffer = IO::Buffer.new(4)
+ buffer.locked do
+ -> { buffer.free }.should.raise(IO::Buffer::LockedError, "Buffer is locked!")
+ end
+ buffer.free
+ buffer.null?.should == 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 == true
+ buffer.null?.should == 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 == false
+ slice.valid?.should == 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..dba6fddc79
--- /dev/null
+++ b/spec/ruby/core/io/buffer/initialize_spec.rb
@@ -0,0 +1,119 @@
+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.external?
+
+ @buffer.should_not.shared?
+ @buffer.should_not.private?
+ @buffer.should_not.readonly?
+
+ @buffer.should_not.empty?
+ @buffer.should_not.null?
+
+ @buffer.should_not.locked?
+ @buffer.should.valid?
+ 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_not.empty?
+
+ @buffer.should.internal?
+ @buffer.should_not.mapped?
+ 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.empty?
+
+ @buffer.should_not.internal?
+ @buffer.should.mapped?
+ end
+
+ it "creates a null buffer if size is 0" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.should.null?
+ @buffer.should.empty?
+ end
+
+ it "raises TypeError if size is not an Integer" do
+ -> { IO::Buffer.new(nil) }.should.raise(TypeError, "not an Integer")
+ -> { IO::Buffer.new(10.0) }.should.raise(TypeError, "not an Integer")
+ end
+
+ it "raises ArgumentError if size is negative" do
+ -> { IO::Buffer.new(-1) }.should.raise(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 "allows extra flags" do
+ @buffer = IO::Buffer.new(10, IO::Buffer::INTERNAL | IO::Buffer::SHARED | IO::Buffer::READONLY)
+ @buffer.should.internal?
+ @buffer.should.shared?
+ @buffer.should.readonly?
+ end
+
+ it "ignores flags if size is 0" do
+ @buffer = IO::Buffer.new(0, 0xffff)
+ @buffer.should.null?
+ @buffer.should.empty?
+
+ @buffer.should_not.internal?
+ @buffer.should_not.mapped?
+ @buffer.should_not.external?
+
+ @buffer.should_not.shared?
+ @buffer.should_not.readonly?
+
+ @buffer.should_not.locked?
+ @buffer.should.valid?
+ 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(IO::Buffer::AllocationError, "Could not allocate buffer!")
+ -> { IO::Buffer.new(10, 0) }.should.raise(IO::Buffer::AllocationError, "Could not allocate buffer!")
+ end
+
+ it "raises ArgumentError if flags is negative" do
+ -> { IO::Buffer.new(10, -1) }.should.raise(ArgumentError, "Flags can't be negative!")
+ end
+
+ it "raises TypeError with non-Integer flags" do
+ -> { IO::Buffer.new(10, 0.0) }.should.raise(TypeError, "not an Integer")
+ 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..73e19eb85e
--- /dev/null
+++ b/spec/ruby/core/io/buffer/internal_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer#internal?" do
+ after :each do
+ @buffer&.free
+ @buffer = nil
+ end
+
+ it "is true for an internally-allocated buffer" do
+ @buffer = IO::Buffer.new(12)
+ @buffer.internal?.should == true
+ end
+
+ it "is false for an externally-allocated buffer" do
+ @buffer = IO::Buffer.new(12, IO::Buffer::MAPPED)
+ @buffer.internal?.should == false
+ end
+
+ it "is false for a null buffer" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.internal?.should == false
+ 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..249026aa8a
--- /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(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(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 == true
+ slice.locked?.should == false
+ slice.locked { slice.locked?.should == 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 == true
+ @buffer.locked?.should == false
+ @buffer.locked { @buffer.locked?.should == 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 == false
+ end
+
+ it "is true only inside of #locked block" do
+ @buffer = IO::Buffer.new(4)
+ @buffer.locked do
+ @buffer.locked?.should == true
+ end
+ @buffer.locked?.should == false
+ end
+end
diff --git a/spec/ruby/core/io/buffer/map_spec.rb b/spec/ruby/core/io/buffer/map_spec.rb
new file mode 100644
index 0000000000..97764c2dd7
--- /dev/null
+++ b/spec/ruby/core/io/buffer/map_spec.rb
@@ -0,0 +1,347 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer.map" do
+ before :all do
+ @tmp_files = []
+
+ @big_file_name = nil
+ @small_file_name = nil
+ end
+
+ after :all do
+ @tmp_files.each {|file| File.delete(file)}
+ end
+
+ def open_fixture
+ unless @small_file_name
+ @small_file_name = tmp("read_text.txt")
+ File.copy_stream(fixture(__dir__, "read_text.txt"), @small_file_name)
+ @tmp_files << @small_file_name
+ end
+ File.open(@small_file_name, "rb+")
+ end
+
+ def open_big_file_fixture
+ unless @big_file_name
+ @big_file_name = tmp("big_file")
+ # Usually 4 kibibytes + 16 bytes
+ File.write(@big_file_name, "12345678" * (IO::Buffer::PAGE_SIZE / 8 + 2))
+ @tmp_files << @big_file_name
+ end
+ File.open(@big_file_name, "rb+")
+ end
+
+ after :each do
+ @buffer&.free
+ @buffer = nil
+ @file&.close
+ @file = nil
+ end
+
+ it "creates a new buffer mapped from a file" do
+ @file = open_fixture
+ @buffer = IO::Buffer.map(@file)
+
+ @buffer.size.should == 9
+ @buffer.get_string.should == "abcâdef\n".b
+ end
+
+ it "allows to close the file after creating buffer, retaining mapping" do
+ file = open_fixture
+ @buffer = IO::Buffer.map(file)
+ file.close
+
+ @buffer.get_string.should == "abcâdef\n".b
+ end
+
+ it "creates a mapped, external, shared buffer" do
+ @file = open_fixture
+ @buffer = IO::Buffer.map(@file)
+
+ @buffer.should_not.internal?
+ @buffer.should.mapped?
+ @buffer.should.external?
+
+ @buffer.should_not.empty?
+ @buffer.should_not.null?
+
+ @buffer.should.shared?
+ @buffer.should_not.private?
+ @buffer.should_not.readonly?
+
+ @buffer.should_not.locked?
+ @buffer.should.valid?
+ end
+
+ # IO::Buffer.map seems not shareable across processes on OpenBSD.
+ # See https://rubyci.s3.amazonaws.com/openbsd-current/ruby-master/log/20260129T163005Z.fail.html.gz
+ platform_is_not :openbsd do
+ guard -> { Process.respond_to?(:fork) } do
+ it "is shareable across processes" do
+ file_name = tmp("shared_buffer")
+ @file = File.open(file_name, "w+")
+ @file << "I'm private"
+ @file.rewind
+ @buffer = IO::Buffer.map(@file)
+
+ IO.popen("-") do |child_pipe|
+ if child_pipe
+ # Synchronize on child's output.
+ child_pipe.readlines.first.chomp.should == @buffer.to_s
+ @buffer.get_string.should == "I'm shared!"
+
+ @file.read.should == "I'm shared!"
+ else
+ @buffer.set_string("I'm shared!")
+ puts @buffer
+ end
+ ensure
+ child_pipe&.close
+ end
+ ensure
+ File.unlink(file_name)
+ end
+ end
+ end
+
+ context "with an empty file" do
+ ruby_version_is "4.0" do
+ it "raises ArgumentError" do
+ file_name = tmp("empty.txt")
+ @file = File.open(file_name, "wb+")
+ @tmp_files << file_name
+ -> { IO::Buffer.map(@file) }.should.raise(ArgumentError, "Invalid negative or zero file size!")
+ end
+ end
+ end
+
+ context "with a file opened only for reading" do
+ it "raises a SystemCallError unless read-only" do
+ @file = File.open(fixture(__dir__, "read_text.txt"), "rb")
+ -> { IO::Buffer.map(@file) }.should.raise(SystemCallError)
+ end
+ end
+
+ context "with size argument" do
+ it "limits the buffer to the specified size in bytes, starting from the start of the file" do
+ @file = open_fixture
+ @buffer = IO::Buffer.map(@file, 4)
+
+ @buffer.size.should == 4
+ @buffer.get_string.should == "abc\xC3".b
+ end
+
+ it "maps the whole file if size is nil" do
+ @file = open_fixture
+ @buffer = IO::Buffer.map(@file, nil)
+
+ @buffer.size.should == 9
+ end
+
+ context "if size is 0" do
+ ruby_version_is "4.0" do
+ it "raises ArgumentError" do
+ @file = open_fixture
+ -> { IO::Buffer.map(@file, 0) }.should.raise(ArgumentError, "Size can't be zero!")
+ end
+ end
+ end
+
+ it "raises TypeError if size is not an Integer or nil" do
+ @file = open_fixture
+ -> { IO::Buffer.map(@file, "10") }.should.raise(TypeError, "not an Integer")
+ -> { IO::Buffer.map(@file, 10.0) }.should.raise(TypeError, "not an Integer")
+ end
+
+ it "raises ArgumentError if size is negative" do
+ @file = open_fixture
+ -> { IO::Buffer.map(@file, -1) }.should.raise(ArgumentError, "Size can't be negative!")
+ end
+
+ ruby_version_is ""..."4.0" do
+ # May or may not cause a crash on access.
+ it "is undefined behavior if size is larger than file size"
+ end
+
+ ruby_version_is "4.0" do
+ it "raises ArgumentError if size is larger than file size" do
+ @file = open_fixture
+ -> { IO::Buffer.map(@file, 8192) }.should.raise(ArgumentError, "Size can't be larger than file size!")
+ end
+ end
+ end
+
+ context "with size and offset arguments" do
+ # Neither Windows nor macOS have clear, stable behavior with non-zero offset.
+ # https://bugs.ruby-lang.org/issues/21700
+ platform_is :linux do
+ context "if offset is an allowed value for system call" do
+ it "maps the span specified by size starting from the offset" do
+ @file = open_big_file_fixture
+ @buffer = IO::Buffer.map(@file, 14, IO::Buffer::PAGE_SIZE)
+
+ @buffer.size.should == 14
+ @buffer.get_string(0, 14).should == "12345678123456"
+ end
+
+ context "if size is nil" do
+ ruby_version_is ""..."4.0" do
+ it "maps the rest of the file" do
+ @file = open_big_file_fixture
+ @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE)
+
+ @buffer.get_string(0, 1).should == "1"
+ end
+
+ it "incorrectly sets buffer's size to file's full size" do
+ @file = open_big_file_fixture
+ @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE)
+
+ @buffer.size.should == @file.size
+ end
+ end
+
+ ruby_version_is "4.0" do
+ it "maps the rest of the file" do
+ @file = open_big_file_fixture
+ @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE)
+
+ @buffer.get_string(0, 1).should == "1"
+ end
+
+ it "sets buffer's size to file's remaining size" do
+ @file = open_big_file_fixture
+ @buffer = IO::Buffer.map(@file, nil, IO::Buffer::PAGE_SIZE)
+
+ @buffer.size.should == (@file.size - IO::Buffer::PAGE_SIZE)
+ end
+ end
+ end
+ end
+ end
+
+ it "maps the file from the start if offset is 0" do
+ @file = open_fixture
+ @buffer = IO::Buffer.map(@file, 4, 0)
+
+ @buffer.size.should == 4
+ @buffer.get_string.should == "abc\xC3".b
+ end
+
+ ruby_version_is ""..."4.0" do
+ # May or may not cause a crash on access.
+ it "is undefined behavior if offset+size is larger than file size"
+ end
+
+ ruby_version_is "4.0" do
+ it "raises ArgumentError if offset+size is larger than file size" do
+ @file = open_big_file_fixture
+ -> { IO::Buffer.map(@file, 17, IO::Buffer::PAGE_SIZE) }.should.raise(ArgumentError, "Offset too large!")
+ ensure
+ # Windows requires the file to be closed before deletion.
+ @file.close unless @file.closed?
+ end
+ end
+
+ it "raises TypeError if offset is not convertible to Integer" do
+ @file = open_fixture
+ -> { IO::Buffer.map(@file, 4, "4096") }.should.raise(TypeError, /no implicit conversion/)
+ -> { IO::Buffer.map(@file, 4, nil) }.should.raise(TypeError, /no implicit conversion/)
+ end
+
+ ruby_version_is "4.0" do
+ it "raises ArgumentError if offset is negative" do
+ @file = open_fixture
+ -> { IO::Buffer.map(@file, 4, -1) }.should.raise(ArgumentError, "Offset can't be negative!")
+ end
+ end
+ end
+
+ context "with flags argument" do
+ context "when READONLY flag is specified" do
+ it "sets readonly flag on the buffer, allowing only reads" do
+ @file = open_fixture
+ @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::READONLY)
+
+ @buffer.should.readonly?
+
+ @buffer.get_string.should == "abc\xC3\xA2def\n".b
+ end
+
+ it "allows mapping read-only files" do
+ @file = File.open(fixture(__dir__, "read_text.txt"), "rb")
+ @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::READONLY)
+
+ @buffer.should.readonly?
+
+ @buffer.get_string.should == "abc\xC3\xA2def\n".b
+ end
+
+ it "causes IO::Buffer::AccessError on write" do
+ @file = open_fixture
+ @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::READONLY)
+
+ -> { @buffer.set_string("test") }.should.raise(IO::Buffer::AccessError, "Buffer is not writable!")
+ end
+ end
+
+ context "when PRIVATE is specified" do
+ it "sets private flag on the buffer, making it freely modifiable" do
+ @file = open_fixture
+ @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::PRIVATE)
+
+ @buffer.should.private?
+ @buffer.should_not.shared?
+ @buffer.should_not.external?
+
+ @buffer.get_string.should == "abc\xC3\xA2def\n".b
+ @buffer.set_string("test12345")
+ @buffer.get_string.should == "test12345".b
+
+ @file.read.should == "abcâdef\n".b
+ end
+
+ it "allows mapping read-only files and modifying the buffer" do
+ @file = File.open(fixture(__dir__, "read_text.txt"), "rb")
+ @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::PRIVATE)
+
+ @buffer.should.private?
+ @buffer.should_not.shared?
+ @buffer.should_not.external?
+
+ @buffer.get_string.should == "abc\xC3\xA2def\n".b
+ @buffer.set_string("test12345")
+ @buffer.get_string.should == "test12345".b
+
+ @file.read.should == "abcâdef\n".b
+ end
+
+ guard -> { Process.respond_to?(:fork) } do
+ it "is not shared across processes" do
+ file_name = tmp("shared_buffer")
+ @file = File.open(file_name, "w+")
+ @file << "I'm private"
+ @file.rewind
+ @buffer = IO::Buffer.map(@file, nil, 0, IO::Buffer::PRIVATE)
+
+ IO.popen("-") do |child_pipe|
+ if child_pipe
+ # Synchronize on child's output.
+ child_pipe.readlines.first.chomp.should == @buffer.to_s
+ @buffer.get_string.should == "I'm private"
+
+ @file.read.should == "I'm private"
+ else
+ @buffer.set_string("I'm shared!")
+ puts @buffer
+ end
+ ensure
+ child_pipe&.close
+ end
+ ensure
+ File.unlink(file_name)
+ end
+ end
+ end
+ 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..0decb97704
--- /dev/null
+++ b/spec/ruby/core/io/buffer/mapped_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer#mapped?" do
+ after :each do
+ @buffer&.free
+ @buffer = nil
+ end
+
+ it "is true for a buffer with mapped memory" do
+ @buffer = IO::Buffer.new(12, IO::Buffer::MAPPED)
+ @buffer.mapped?.should == true
+ end
+
+ it "is false for a buffer with non-mapped memory" do
+ @buffer = IO::Buffer.for("string")
+ @buffer.mapped?.should == false
+ end
+
+ it "is false for a null buffer" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.mapped?.should == false
+ end
+end
diff --git a/spec/ruby/core/io/buffer/not_spec.rb b/spec/ruby/core/io/buffer/not_spec.rb
new file mode 100644
index 0000000000..4737a30bde
--- /dev/null
+++ b/spec/ruby/core/io/buffer/not_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../../spec_helper'
+
+describe :io_buffer_not, shared: true do
+ it "inverts every bit of the buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ result = buffer.send(@method)
+ result.get_string.should == "\xCE\xCD\xCC\xCB\xCA".b
+ result.free
+ end
+ end
+end
+
+describe "IO::Buffer#~" do
+ it_behaves_like :io_buffer_not, :~
+
+ it "creates a new internal buffer of the same size" do
+ IO::Buffer.for(+"12345") do |buffer|
+ result = ~buffer
+ result.should_not.equal? buffer
+ result.should.internal?
+ result.size.should == buffer.size
+ result.free
+ end
+ end
+end
+
+describe "IO::Buffer#not!" do
+ it_behaves_like :io_buffer_not, :not!
+
+ it "modifies the buffer in place" do
+ IO::Buffer.for(+"12345") do |buffer|
+ result = buffer.not!
+ result.should.equal? buffer
+ result.should.external?
+ 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..380a98bde1
--- /dev/null
+++ b/spec/ruby/core/io/buffer/null_spec.rb
@@ -0,0 +1,27 @@
+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 == false
+ end
+
+ it "is false for a 0-length String-backed buffer created with .string" do
+ IO::Buffer.string(0) do |buffer|
+ buffer.null?.should == false
+ 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 == false
+ end
+end
diff --git a/spec/ruby/core/io/buffer/or_spec.rb b/spec/ruby/core/io/buffer/or_spec.rb
new file mode 100644
index 0000000000..3e627c216c
--- /dev/null
+++ b/spec/ruby/core/io/buffer/or_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../../spec_helper'
+
+describe :io_buffer_or, shared: true do
+ it "applies the argument buffer as an OR bit mask across the whole buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer.send(@method, mask)
+ result.get_string.should == "\xF9\xBF\xFB\xBF\xFD".b
+ result.free
+ end
+ end
+ end
+
+ it "ignores extra parts of mask if it is longer than source buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F\x00\x00\x00\xFF\xFF") do |mask|
+ result = buffer.send(@method, mask)
+ result.get_string.should == "\xF9\xBF345".b
+ result.free
+ end
+ end
+ end
+
+ it "raises TypeError if mask is not an IO::Buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ -> { buffer.send(@method, "\xF8\x8F") }.should.raise(TypeError, "wrong argument type String (expected IO::Buffer)")
+ -> { buffer.send(@method, 0xF8) }.should.raise(TypeError, "wrong argument type Integer (expected IO::Buffer)")
+ -> { buffer.send(@method, nil) }.should.raise(TypeError, "wrong argument type nil (expected IO::Buffer)")
+ end
+ end
+end
+
+describe "IO::Buffer#|" do
+ it_behaves_like :io_buffer_or, :|
+
+ it "creates a new internal buffer of the same size" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer | mask
+ result.should_not.equal? buffer
+ result.should.internal?
+ result.size.should == buffer.size
+ result.free
+ buffer.get_string.should == "12345".b
+ end
+ end
+ end
+end
+
+describe "IO::Buffer#or!" do
+ it_behaves_like :io_buffer_or, :or!
+
+ it "modifies the buffer in place" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer.or!(mask)
+ result.should.equal? buffer
+ result.should.external?
+ end
+ end
+ 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..6e6afee34c
--- /dev/null
+++ b/spec/ruby/core/io/buffer/private_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer#private?" do
+ after :each do
+ @buffer&.free
+ @buffer = nil
+ end
+
+ it "is true for a buffer created with PRIVATE flag" do
+ @buffer = IO::Buffer.new(12, IO::Buffer::INTERNAL | IO::Buffer::PRIVATE)
+ @buffer.private?.should == true
+ end
+
+ it "is false for a buffer created without PRIVATE flag" do
+ @buffer = IO::Buffer.new(12, IO::Buffer::INTERNAL)
+ @buffer.private?.should == false
+ end
+
+ it "is false for a null buffer" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.private?.should == false
+ 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..4eefc9f29f
--- /dev/null
+++ b/spec/ruby/core/io/buffer/readonly_spec.rb
@@ -0,0 +1,28 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer#readonly?" do
+ after :each do
+ @buffer&.free
+ @buffer = nil
+ end
+
+ it "is true for a buffer created with READONLY flag" do
+ @buffer = IO::Buffer.new(12, IO::Buffer::INTERNAL | IO::Buffer::READONLY)
+ @buffer.readonly?.should == true
+ end
+
+ it "is true for a buffer that is non-writable" do
+ @buffer = IO::Buffer.for("string")
+ @buffer.readonly?.should == true
+ end
+
+ it "is false for a modifiable buffer" do
+ @buffer = IO::Buffer.new(12)
+ @buffer.readonly?.should == false
+ end
+
+ it "is false for a null buffer" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.readonly?.should == false
+ 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..6e684475f3
--- /dev/null
+++ b/spec/ruby/core/io/buffer/resize_spec.rb
@@ -0,0 +1,151 @@
+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 == true
+ @buffer.mapped?.should == 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 == false
+ @buffer.mapped?.should == 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 == true
+ @buffer.mapped?.should == 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(IO::Buffer::AccessError, "Cannot resize external buffer!")
+ end
+ end
+
+ 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
+
+ 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(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(IO::Buffer::AccessError, "Cannot resize external buffer!")
+ end
+ end
+ end
+ end
+
+ 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(IO::Buffer::AccessError, "Cannot resize external buffer!")
+ 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 == false
+ @buffer.mapped?.should == 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 == true
+ @buffer.mapped?.should == false
+ end
+ end
+
+ it "allows resizing to 0, freeing memory" do
+ @buffer = IO::Buffer.new(4)
+ @buffer.resize(0)
+ @buffer.null?.should == 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(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(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(TypeError, "not an Integer")
+ -> { @buffer.resize(10.0) }.should.raise(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..f8abc5f0fc
--- /dev/null
+++ b/spec/ruby/core/io/buffer/shared/null_and_empty.rb
@@ -0,0 +1,57 @@
+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 == false
+ end
+
+ it "is false for a slice with length > 0" do
+ @buffer = IO::Buffer.new(4)
+ @buffer.slice(1, 2).send(@method).should == 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 == 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 == false
+ end
+
+ it "is false for a non-empty String-backed buffer created with .string" do
+ IO::Buffer.string(4) do |buffer|
+ buffer.send(@method).should == false
+ end
+ end
+
+ it "is true for a 0-sized buffer" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.send(@method).should == 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 == true
+ end
+
+ it "is true for a freed buffer" do
+ @buffer = IO::Buffer.new(1)
+ @buffer.free
+ @buffer.send(@method).should == true
+ end
+
+ it "is true for a buffer resized to 0" do
+ @buffer = IO::Buffer.new(1)
+ @buffer.resize(0)
+ @buffer.send(@method).should == 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 == 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..2cc93e6d08
--- /dev/null
+++ b/spec/ruby/core/io/buffer/shared_spec.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer#shared?" do
+ after :each do
+ @buffer&.free
+ @buffer = nil
+ end
+
+ it "is true for a buffer created with SHARED flag" do
+ @buffer = IO::Buffer.new(12, IO::Buffer::INTERNAL | IO::Buffer::SHARED)
+ @buffer.shared?.should == true
+ end
+
+ it "is true for a non-private buffer created with .map" do
+ path = fixture(__dir__, "read_text.txt")
+ file = File.open(path, "r+")
+ @buffer = IO::Buffer.map(file)
+ @buffer.shared?.should == true
+ ensure
+ @buffer.free
+ file.close
+ end
+
+ it "is false for an unshared buffer" do
+ @buffer = IO::Buffer.new(12)
+ @buffer.shared?.should == false
+ end
+
+ it "is false for a null buffer" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.shared?.should == false
+ end
+end
diff --git a/spec/ruby/core/io/buffer/string_spec.rb b/spec/ruby/core/io/buffer/string_spec.rb
new file mode 100644
index 0000000000..4c73ba5e1e
--- /dev/null
+++ b/spec/ruby/core/io/buffer/string_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../../spec_helper'
+
+describe "IO::Buffer.string" do
+ it "creates a modifiable buffer for the duration of the block" do
+ IO::Buffer.string(7) do |buffer|
+ @buffer = buffer
+
+ buffer.size.should == 7
+ buffer.get_string.should == "\0\0\0\0\0\0\0".b
+
+ buffer.set_string("test")
+ buffer.get_string.should == "test\0\0\0"
+ end
+ @buffer.should.null?
+ end
+
+ it "returns contents of the buffer as a binary string" do
+ string =
+ IO::Buffer.string(7) do |buffer|
+ buffer.set_string("ä test")
+ end
+ string.should == "\xC3\xA4 test".b
+ end
+
+ it "creates an external buffer" do
+ IO::Buffer.string(8) do |buffer|
+ buffer.should_not.internal?
+ buffer.should_not.mapped?
+ buffer.should.external?
+
+ buffer.should_not.empty?
+ buffer.should_not.null?
+
+ buffer.should_not.shared?
+ buffer.should_not.private?
+ buffer.should_not.readonly?
+
+ buffer.should_not.locked?
+ buffer.should.valid?
+ end
+ end
+
+ it "returns an empty string if size is 0" do
+ string =
+ IO::Buffer.string(0) do |buffer|
+ buffer.size.should == 0
+ end
+ string.should == ""
+ end
+
+ it "raises ArgumentError if size is negative" do
+ -> { IO::Buffer.string(-1) {} }.should.raise(ArgumentError, "negative string size (or size too big)")
+ end
+
+ it "raises RangeError if size is too large" do
+ -> { IO::Buffer.string(2 ** 232) {} }.should.raise(RangeError, /\Abignum too big to convert into [`']long'\z/)
+ end
+
+ it "raises LocalJumpError if no block is given" do
+ -> { IO::Buffer.string(7) }.should.raise(LocalJumpError, "no block given")
+ 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..3bc08998dd
--- /dev/null
+++ b/spec/ruby/core/io/buffer/transfer_spec.rb
@@ -0,0 +1,117 @@
+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 == 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 == 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 == 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 == 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 == true
+ end
+ @buffer.null?.should == false
+ end
+ end
+ end
+
+ 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 == true
+ end
+ @buffer.null?.should == false
+ 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 == true
+ buffer_2.null?.should == true
+ @buffer.null?.should == false
+ end
+
+ it "is disallowed while locked, raising IO::Buffer::LockedError" do
+ @buffer = IO::Buffer.new(4)
+ @buffer.locked do
+ -> { @buffer.transfer }.should.raise(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)
+ @buffer.set_string("test")
+ slice = @buffer.slice(0, 2)
+ slice.get_string.should == "te"
+
+ new_slice = slice.transfer
+ slice.null?.should == true
+ new_slice.null?.should == false
+ @buffer.null?.should == 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 == false
+ slice.valid?.should == false
+ -> { slice.get_string }.should.raise(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..b84bdd0cfd
--- /dev/null
+++ b/spec/ruby/core/io/buffer/valid_spec.rb
@@ -0,0 +1,99 @@
+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 == true
+ end
+
+ it "is true for a 0-size buffer" do
+ @buffer = IO::Buffer.new(0)
+ @buffer.valid?.should == true
+ end
+
+ it "is true for a freed buffer" do
+ @buffer = IO::Buffer.new(4)
+ @buffer.free
+ @buffer.valid?.should == 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 == true
+ @buffer.free
+ @buffer.valid?.should == true
+ end
+ end
+
+ it "is true for a freed string-backed buffer" do
+ @buffer = IO::Buffer.for("hello")
+ @buffer.valid?.should == true
+ @buffer.free
+ @buffer.valid?.should == 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 == 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 == false
+ 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 == 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 == 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 == true
+ @buffer.free
+ slice.valid?.should == 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 == true
+ @buffer.free
+ slice.valid?.should == 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/buffer/xor_spec.rb b/spec/ruby/core/io/buffer/xor_spec.rb
new file mode 100644
index 0000000000..9287611f7f
--- /dev/null
+++ b/spec/ruby/core/io/buffer/xor_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../../spec_helper'
+
+describe :io_buffer_xor, shared: true do
+ it "applies the argument buffer as an XOR bit mask across the whole buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer.send(@method, mask)
+ result.get_string.should == "\xC9\xBD\xCB\xBB\xCD".b
+ result.free
+ end
+ end
+ end
+
+ it "ignores extra parts of mask if it is longer than source buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F\x00\x00\x00\xFF\xFF") do |mask|
+ result = buffer.send(@method, mask)
+ result.get_string.should == "\xC9\xBD345".b
+ result.free
+ end
+ end
+ end
+
+ it "raises TypeError if mask is not an IO::Buffer" do
+ IO::Buffer.for(+"12345") do |buffer|
+ -> { buffer.send(@method, "\xF8\x8F") }.should.raise(TypeError, "wrong argument type String (expected IO::Buffer)")
+ -> { buffer.send(@method, 0xF8) }.should.raise(TypeError, "wrong argument type Integer (expected IO::Buffer)")
+ -> { buffer.send(@method, nil) }.should.raise(TypeError, "wrong argument type nil (expected IO::Buffer)")
+ end
+ end
+end
+
+describe "IO::Buffer#^" do
+ it_behaves_like :io_buffer_xor, :^
+
+ it "creates a new internal buffer of the same size" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer ^ mask
+ result.should_not.equal? buffer
+ result.should.internal?
+ result.size.should == buffer.size
+ result.free
+ buffer.get_string.should == "12345".b
+ end
+ end
+ end
+end
+
+describe "IO::Buffer#xor!" do
+ it_behaves_like :io_buffer_xor, :xor!
+
+ it "modifies the buffer in place" do
+ IO::Buffer.for(+"12345") do |buffer|
+ IO::Buffer.for(+"\xF8\x8F") do |mask|
+ result = buffer.xor!(mask)
+ result.should.equal? buffer
+ result.should.external?
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/close_on_exec_spec.rb b/spec/ruby/core/io/close_on_exec_spec.rb
new file mode 100644
index 0000000000..28cdb967b9
--- /dev/null
+++ b/spec/ruby/core/io/close_on_exec_spec.rb
@@ -0,0 +1,76 @@
+require_relative '../../spec_helper'
+
+describe "IO#close_on_exec=" do
+ before :each do
+ @name = tmp('io_close_on_exec.txt')
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ guard -> { platform_is_not :windows } do
+ it "sets the close-on-exec flag if true" do
+ @io.close_on_exec = true
+ @io.should.close_on_exec?
+ end
+
+ it "sets the close-on-exec flag if non-false" do
+ @io.close_on_exec = :true
+ @io.should.close_on_exec?
+ end
+
+ it "unsets the close-on-exec flag if false" do
+ @io.close_on_exec = true
+ @io.close_on_exec = false
+ @io.should_not.close_on_exec?
+ end
+
+ it "unsets the close-on-exec flag if nil" do
+ @io.close_on_exec = true
+ @io.close_on_exec = nil
+ @io.should_not.close_on_exec?
+ end
+
+ it "ensures the IO's file descriptor is closed in exec'ed processes" do
+ require 'fcntl'
+ @io.close_on_exec = true
+ (@io.fcntl(Fcntl::F_GETFD) & Fcntl::FD_CLOEXEC).should == Fcntl::FD_CLOEXEC
+ end
+
+ it "raises IOError if called on a closed IO" do
+ @io.close
+ -> { @io.close_on_exec = true }.should.raise(IOError)
+ end
+ end
+end
+
+describe "IO#close_on_exec?" do
+ before :each do
+ @name = tmp('io_is_close_on_exec.txt')
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ guard -> { platform_is_not :windows } do
+ it "returns true by default" do
+ @io.should.close_on_exec?
+ end
+
+ it "returns true if set" do
+ @io.close_on_exec = true
+ @io.should.close_on_exec?
+ end
+
+ it "raises IOError if called on a closed IO" do
+ @io.close
+ -> { @io.close_on_exec? }.should.raise(IOError)
+ end
+ end
+end
diff --git a/spec/ruby/core/io/close_read_spec.rb b/spec/ruby/core/io/close_read_spec.rb
new file mode 100644
index 0000000000..c505289d72
--- /dev/null
+++ b/spec/ruby/core/io/close_read_spec.rb
@@ -0,0 +1,61 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#close_read" do
+
+ before :each do
+ cmd = platform_is(:windows) ? 'rem' : 'cat'
+ @io = IO.popen cmd, "r+"
+ @path = tmp('io.close.txt')
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @path
+ end
+
+ it "closes the read end of a duplex I/O stream" do
+ @io.close_read
+
+ -> { @io.read }.should.raise(IOError)
+ end
+
+ it "does nothing on subsequent invocations" do
+ @io.close_read
+
+ @io.close_read.should == nil
+ end
+
+ it "allows subsequent invocation of close" do
+ @io.close_read
+
+ -> { @io.close }.should_not.raise
+ end
+
+ it "raises an IOError if the stream is writable and not duplexed" do
+ io = File.open @path, 'w'
+
+ begin
+ -> { io.close_read }.should.raise(IOError)
+ ensure
+ io.close unless io.closed?
+ end
+ end
+
+ it "closes the stream if it is neither writable nor duplexed" do
+ io_close_path = @path
+ touch io_close_path
+
+ io = File.open io_close_path
+
+ io.close_read
+
+ io.should.closed?
+ end
+
+ it "does nothing on closed stream" do
+ @io.close
+
+ @io.close_read.should == nil
+ end
+end
diff --git a/spec/ruby/core/io/close_spec.rb b/spec/ruby/core/io/close_spec.rb
new file mode 100644
index 0000000000..afd84ba101
--- /dev/null
+++ b/spec/ruby/core/io/close_spec.rb
@@ -0,0 +1,118 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#close" do
+ before :each do
+ @name = tmp('io_close.txt')
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "closes the stream" do
+ @io.close
+ @io.should.closed?
+ end
+
+ it "returns nil" do
+ @io.close.should == nil
+ end
+
+ it "raises an IOError reading from a closed IO" do
+ @io.close
+ -> { @io.read }.should.raise(IOError)
+ end
+
+ it "raises an IOError writing to a closed IO" do
+ @io.close
+ -> { @io.write "data" }.should.raise(IOError)
+ end
+
+ it 'does not close the stream if autoclose is false' do
+ other_io = IO.new(@io.fileno)
+ other_io.autoclose = false
+ other_io.close
+ -> { @io.write "data" }.should_not.raise(IOError)
+ end
+
+ it "does nothing if already closed" do
+ @io.close
+
+ @io.close.should == nil
+ end
+
+ it "does not call the #flush method but flushes the stream internally" do
+ @io.should_not_receive(:flush)
+ @io.close
+ @io.should.closed?
+ end
+
+ it 'raises an IOError with a clear message' do
+ matching_exception = nil
+
+ -> do
+ IOSpecs::THREAD_CLOSE_RETRIES.times do
+ read_io, write_io = IO.pipe
+ going_to_read = false
+
+ thread = Thread.new do
+ begin
+ going_to_read = true
+ read_io.read
+ rescue IOError => ioe
+ if ioe.message == IOSpecs::THREAD_CLOSE_ERROR_MESSAGE
+ matching_exception = ioe
+ end
+ # try again
+ end
+ end
+
+ # best attempt to ensure the thread is actually blocked on read
+ Thread.pass until going_to_read && thread.stop?
+ sleep(0.001)
+
+ read_io.close
+ thread.join
+ write_io.close
+
+ matching_exception&.tap {|ex| raise ex}
+ end
+ end.should.raise(IOError, IOSpecs::THREAD_CLOSE_ERROR_MESSAGE)
+ end
+end
+
+describe "IO#close on an IO.popen stream" do
+
+ it "clears #pid" do
+ io = IO.popen ruby_cmd('r = loop{puts "y"; 0} rescue 1; exit r'), 'r'
+
+ io.pid.should_not == 0
+
+ io.close
+
+ -> { io.pid }.should.raise(IOError)
+ end
+
+ it "sets $?" do
+ io = IO.popen ruby_cmd('exit 0'), 'r'
+ io.close
+
+ $?.exitstatus.should == 0
+
+ io = IO.popen ruby_cmd('exit 1'), 'r'
+ io.close
+
+ $?.exitstatus.should == 1
+ end
+
+ it "waits for the child to exit" do
+ io = IO.popen ruby_cmd('r = loop{puts "y"; 0} rescue 1; exit r'), 'r'
+ io.close
+
+ $?.exitstatus.should_not == 0
+ end
+
+end
diff --git a/spec/ruby/core/io/close_write_spec.rb b/spec/ruby/core/io/close_write_spec.rb
new file mode 100644
index 0000000000..60b41505c3
--- /dev/null
+++ b/spec/ruby/core/io/close_write_spec.rb
@@ -0,0 +1,68 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#close_write" do
+ before :each do
+ cmd = platform_is(:windows) ? 'rem' : 'cat'
+ @io = IO.popen cmd, 'r+'
+ @path = tmp('io.close.txt')
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @path
+ end
+
+ it "closes the write end of a duplex I/O stream" do
+ @io.close_write
+
+ -> { @io.write "attempt to write" }.should.raise(IOError)
+ end
+
+ it "does nothing on subsequent invocations" do
+ @io.close_write
+
+ @io.close_write.should == nil
+ end
+
+ it "allows subsequent invocation of close" do
+ @io.close_write
+
+ -> { @io.close }.should_not.raise
+ end
+
+ it "raises an IOError if the stream is readable and not duplexed" do
+ io = File.open @path, 'w+'
+
+ begin
+ -> { io.close_write }.should.raise(IOError)
+ ensure
+ io.close unless io.closed?
+ end
+ end
+
+ it "closes the stream if it is neither readable nor duplexed" do
+ io = File.open @path, 'w'
+
+ io.close_write
+
+ io.should.closed?
+ end
+
+ # Windows didn't have command like cat
+ platform_is_not :windows do
+ it "flushes and closes the write stream" do
+ @io.puts '12345'
+
+ @io.close_write
+
+ @io.read.should == "12345\n"
+ end
+ end
+
+ it "does nothing on closed stream" do
+ @io.close
+
+ @io.close_write.should == nil
+ end
+end
diff --git a/spec/ruby/core/io/closed_spec.rb b/spec/ruby/core/io/closed_spec.rb
new file mode 100644
index 0000000000..1f10858e28
--- /dev/null
+++ b/spec/ruby/core/io/closed_spec.rb
@@ -0,0 +1,20 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#closed?" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close
+ end
+
+ it "returns true on closed stream" do
+ IOSpecs.closed_io.closed?.should == true
+ end
+
+ it "returns false on open stream" do
+ @io.closed?.should == false
+ end
+end
diff --git a/spec/ruby/core/io/constants_spec.rb b/spec/ruby/core/io/constants_spec.rb
new file mode 100644
index 0000000000..f9dccd08b9
--- /dev/null
+++ b/spec/ruby/core/io/constants_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "IO::SEEK_SET" do
+ it "is defined" do
+ IO.const_defined?(:SEEK_SET).should == true
+ end
+end
+
+describe "IO::SEEK_CUR" do
+ it "is defined" do
+ IO.const_defined?(:SEEK_CUR).should == true
+ end
+end
+
+describe "IO::SEEK_END" do
+ it "is defined" do
+ IO.const_defined?(:SEEK_END).should == true
+ end
+end
diff --git a/spec/ruby/core/io/copy_stream_spec.rb b/spec/ruby/core/io/copy_stream_spec.rb
new file mode 100644
index 0000000000..31383f9b0f
--- /dev/null
+++ b/spec/ruby/core/io/copy_stream_spec.rb
@@ -0,0 +1,351 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :io_copy_stream_to_file, shared: true do
+ it "copies the entire IO contents to the file" do
+ IO.copy_stream(@object.from, @to_name)
+ File.read(@to_name).should == @content
+ IO.copy_stream(@from_bigfile, @to_name)
+ File.read(@to_name).should == @content_bigfile
+ end
+
+ it "returns the number of bytes copied" do
+ IO.copy_stream(@object.from, @to_name).should == @size
+ IO.copy_stream(@from_bigfile, @to_name).should == @size_bigfile
+ end
+
+ it "copies only length bytes when specified" do
+ IO.copy_stream(@object.from, @to_name, 8).should == 8
+ File.read(@to_name).should == "Line one"
+ end
+
+ it "calls #to_path to convert on object to a file name" do
+ obj = mock("io_copy_stream_to")
+ obj.should_receive(:to_path).and_return(@to_name)
+
+ IO.copy_stream(@object.from, obj)
+ File.read(@to_name).should == @content
+ end
+
+ it "raises a TypeError if #to_path does not return a String" do
+ obj = mock("io_copy_stream_to")
+ obj.should_receive(:to_path).and_return(1)
+
+ -> { IO.copy_stream(@object.from, obj) }.should.raise(TypeError)
+ end
+end
+
+describe :io_copy_stream_to_file_with_offset, shared: true do
+ platform_is_not :windows do
+ it "copies only length bytes from the offset" do
+ IO.copy_stream(@object.from, @to_name, 8, 4).should == 8
+ File.read(@to_name).should == " one\n\nLi"
+ end
+ end
+end
+
+describe :io_copy_stream_to_io, shared: true do
+ it "copies the entire IO contents to the IO" do
+ IO.copy_stream(@object.from, @to_io)
+ File.read(@to_name).should == @content
+ IO.copy_stream(@from_bigfile, @to_io)
+ File.read(@to_name).should == (@content + @content_bigfile)
+ end
+
+ it "returns the number of bytes copied" do
+ IO.copy_stream(@object.from, @to_io).should == @size
+ IO.copy_stream(@from_bigfile, @to_io).should == @size_bigfile
+ end
+
+ it "starts writing at the destination IO's current position" do
+ @to_io.write("prelude ")
+ IO.copy_stream(@object.from, @to_io)
+ File.read(@to_name).should == ("prelude " + @content)
+ end
+
+ it "leaves the destination IO position at the last write" do
+ IO.copy_stream(@object.from, @to_io)
+ @to_io.pos.should == @size
+ end
+
+ it "raises an IOError if the destination IO is not open for writing" do
+ to_io = new_io __FILE__, "r"
+ begin
+ -> { IO.copy_stream @object.from, to_io }.should.raise(IOError)
+ ensure
+ to_io.close
+ end
+ end
+
+ it "does not close the destination IO" do
+ IO.copy_stream(@object.from, @to_io)
+ @to_io.closed?.should == false
+ end
+
+ it "copies only length bytes when specified" do
+ IO.copy_stream(@object.from, @to_io, 8).should == 8
+ File.read(@to_name).should == "Line one"
+ end
+end
+
+describe :io_copy_stream_to_io_with_offset, shared: true do
+ platform_is_not :windows do
+ it "copies only length bytes from the offset" do
+ IO.copy_stream(@object.from, @to_io, 8, 4).should == 8
+ File.read(@to_name).should == " one\n\nLi"
+ end
+ end
+end
+
+describe "IO.copy_stream" do
+ before :each do
+ @from_name = fixture __FILE__, "copy_stream.txt"
+ @to_name = tmp("io_copy_stream_io_name")
+
+ @content = IO.read(@from_name)
+ @size = @content.size
+
+ @from_bigfile = tmp("io_copy_stream_bigfile")
+ @content_bigfile = "A" * 17_000
+ touch(@from_bigfile){|f| f.print @content_bigfile }
+ @size_bigfile = @content_bigfile.size
+ end
+
+ after :each do
+ rm_r @to_name if @to_name
+ rm_r @from_bigfile
+ end
+
+ describe "from an IO" do
+ before :each do
+ @from_io = new_io @from_name, "rb"
+ IOSpecs::CopyStream.from = @from_io
+ end
+
+ after :each do
+ @from_io.close
+ end
+
+ it "raises an IOError if the source IO is not open for reading" do
+ @from_io.close
+ @from_io = new_io @from_bigfile, "a"
+ -> { IO.copy_stream @from_io, @to_name }.should.raise(IOError)
+ end
+
+ it "does not close the source IO" do
+ IO.copy_stream(@from_io, @to_name)
+ @from_io.closed?.should == false
+ end
+
+ platform_is_not :windows do
+ it "does not change the IO offset when an offset is specified" do
+ @from_io.pos = 10
+ IO.copy_stream(@from_io, @to_name, 8, 4)
+ @from_io.pos.should == 10
+ end
+ end
+
+ it "does change the IO offset when an offset is not specified" do
+ @from_io.pos = 10
+ IO.copy_stream(@from_io, @to_name)
+ @from_io.pos.should == 42
+ end
+
+ describe "to a file name" do
+ it_behaves_like :io_copy_stream_to_file, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_file_with_offset, nil, IOSpecs::CopyStream
+ end
+
+ describe "to an IO" do
+ before :each do
+ @to_io = new_io @to_name, "wb"
+ end
+
+ after :each do
+ @to_io.close
+ end
+
+ it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream
+ end
+
+ describe "to a Tempfile" do
+ before :all do
+ require 'tempfile'
+ end
+
+ before :each do
+ @to_io = Tempfile.new("rubyspec_copy_stream", encoding: Encoding::BINARY, mode: File::RDONLY)
+ @to_name = @to_io.path
+ end
+
+ after :each do
+ @to_io.close!
+ @to_name = nil # do not rm_r it, already done by Tempfile#close!
+ end
+
+ it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream
+ end
+ end
+
+ describe "from a file name" do
+ before :each do
+ IOSpecs::CopyStream.from = @from_name
+ end
+
+ it "calls #to_path to convert on object to a file name" do
+ obj = mock("io_copy_stream_from")
+ obj.should_receive(:to_path).and_return(@from_name)
+
+ IO.copy_stream(obj, @to_name)
+ File.read(@to_name).should == @content
+ end
+
+ it "raises a TypeError if #to_path does not return a String" do
+ obj = mock("io_copy_stream_from")
+ obj.should_receive(:to_path).and_return(1)
+
+ -> { IO.copy_stream(obj, @to_name) }.should.raise(TypeError)
+ end
+
+ describe "to a file name" do
+ it_behaves_like :io_copy_stream_to_file, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_file_with_offset, nil, IOSpecs::CopyStream
+ end
+
+ describe "to an IO" do
+ before :each do
+ @to_io = new_io @to_name, "wb"
+ end
+
+ after :each do
+ @to_io.close
+ end
+
+ it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream
+ it_behaves_like :io_copy_stream_to_io_with_offset, nil, IOSpecs::CopyStream
+ end
+ end
+
+ describe "from a pipe IO" do
+ before :each do
+ @from_io = IOSpecs.pipe_fixture(@content)
+ IOSpecs::CopyStream.from = @from_io
+ end
+
+ after :each do
+ @from_io.close
+ end
+
+ it "does not close the source IO" do
+ IO.copy_stream(@from_io, @to_name)
+ @from_io.closed?.should == false
+ end
+
+ platform_is_not :windows do
+ it "raises an error when an offset is specified" do
+ -> { IO.copy_stream(@from_io, @to_name, 8, 4) }.should.raise(Errno::ESPIPE)
+ end
+ end
+
+ describe "to a file name" do
+ it_behaves_like :io_copy_stream_to_file, nil, IOSpecs::CopyStream
+ end
+
+ describe "to an IO" do
+ before :each do
+ @to_io = new_io @to_name, "wb"
+ end
+
+ after :each do
+ @to_io.close
+ end
+
+ it_behaves_like :io_copy_stream_to_io, nil, IOSpecs::CopyStream
+ end
+ end
+
+ describe "with non-IO Objects" do
+ before do
+ @io = new_io @from_name, "rb"
+ end
+
+ after do
+ @io.close unless @io.closed?
+ end
+
+ it "calls #readpartial on the source Object if defined" do
+ from = IOSpecs::CopyStreamReadPartial.new @io
+
+ IO.copy_stream(from, @to_name)
+ File.read(@to_name).should == @content
+ end
+
+ it "calls #read on the source Object" do
+ from = IOSpecs::CopyStreamRead.new @io
+
+ IO.copy_stream(from, @to_name)
+ File.read(@to_name).should == @content
+ end
+
+ it "calls #write on the destination Object" do
+ to = mock("io_copy_stream_to_object")
+ to.should_receive(:write).with(@content).and_return(@content.size)
+
+ IO.copy_stream(@from_name, to)
+ end
+
+ it "does not call #pos on the source if no offset is given" do
+ @io.should_not_receive(:pos)
+ IO.copy_stream(@io, @to_name)
+ end
+
+ it "does not call #read on the source or #write on the destination if zero length is given" do
+ from = mock("io_copy_stream_to_object_zero_length_read")
+ to = mock("io_copy_stream_to_object_zero_length_write")
+ from.should_not_receive(:read)
+ to.should_not_receive(:write)
+ IO.copy_stream(from, to, 0)
+ end
+ end
+
+ describe "with a destination that does partial reads" do
+ before do
+ @from_out, @from_in = IO.pipe
+ @to_out, @to_in = IO.pipe
+ end
+
+ after do
+ [@from_out, @from_in, @to_out, @to_in].each {|io| io.close rescue nil}
+ end
+
+ it "calls #write repeatedly on the destination Object" do
+ @from_in.write "1234"
+ @from_in.close
+
+ th = Thread.new do
+ IO.copy_stream(@from_out, @to_in)
+ end
+
+ copied = ""
+ 4.times do
+ copied += @to_out.read(1)
+ end
+
+ th.join
+
+ copied.should == "1234"
+ end
+
+ end
+end
+
+describe "IO.copy_stream" do
+ it "does not use buffering when writing to STDOUT" do
+ IO.popen([*ruby_exe, fixture(__FILE__ , "copy_in_out.rb")], "r+") do |io|
+ io.write("bar")
+ io.read(3).should == "bar"
+ end
+ end
+end
diff --git a/spec/ruby/core/io/dup_spec.rb b/spec/ruby/core/io/dup_spec.rb
new file mode 100644
index 0000000000..db4e9fe641
--- /dev/null
+++ b/spec/ruby/core/io/dup_spec.rb
@@ -0,0 +1,106 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#dup" do
+ before :each do
+ @file = tmp("spec_io_dup")
+ @f = File.open @file, 'w+'
+ @i = @f.dup
+
+ @f.sync = true
+ @i.sync = true
+ end
+
+ after :each do
+ @i.close if @i && !@i.closed?
+ @f.close if @f && !@f.closed?
+ rm_r @file
+ end
+
+ it "returns a new IO instance" do
+ @i.class.should == @f.class
+ end
+
+ it "sets a new descriptor on the returned object" 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
+
+ s = "Hello, wo.. wait, where am I?\n"
+ s2 = "<evil voice> Muhahahaa!"
+
+ @f.write s
+ @i.pos.should == @f.pos
+
+ @i.rewind
+ @i.gets.should == s
+
+ @i.rewind
+ @i.write s2
+
+ @f.rewind
+ @f.gets.should == "#{s2}\n"
+ end
+ end
+
+ it "allows closing the new IO without affecting the original" do
+ @i.close
+ -> { @f.gets }.should_not.raise(Exception)
+
+ @i.should.closed?
+ @f.should_not.closed?
+ end
+
+ it "allows closing the original IO without affecting the new one" do
+ @f.close
+ -> { @i.gets }.should_not.raise(Exception)
+
+ @i.should_not.closed?
+ @f.should.closed?
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.dup }.should.raise(IOError)
+ end
+
+ it "always sets the close-on-exec flag for the new IO object" do
+ @f.close_on_exec = true
+ dup = @f.dup
+ begin
+ dup.should.close_on_exec?
+ ensure
+ dup.close
+ end
+
+ @f.close_on_exec = false
+ dup = @f.dup
+ begin
+ dup.should.close_on_exec?
+ ensure
+ dup.close
+ end
+ end
+
+ it "always sets the autoclose flag for the new IO object" do
+ @f.autoclose = true
+ dup = @f.dup
+ begin
+ dup.should.autoclose?
+ ensure
+ dup.close
+ end
+
+ @f.autoclose = false
+ dup = @f.dup
+ begin
+ dup.should.autoclose?
+ ensure
+ dup.close
+ @f.autoclose = true
+ end
+ end
+end
diff --git a/spec/ruby/core/io/each_byte_spec.rb b/spec/ruby/core/io/each_byte_spec.rb
new file mode 100644
index 0000000000..fe299f0fba
--- /dev/null
+++ b/spec/ruby/core/io/each_byte_spec.rb
@@ -0,0 +1,57 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#each_byte" do
+ before :each do
+ ScratchPad.record []
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.each_byte {} }.should.raise(IOError)
+ end
+
+ it "yields each byte" do
+ count = 0
+ @io.each_byte do |byte|
+ ScratchPad << byte
+ break if 4 < count += 1
+ end
+
+ ScratchPad.recorded.should == [86, 111, 105, 99, 105]
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ enum = @io.each_byte
+ enum.should.instance_of?(Enumerator)
+ enum.first(5).should == [86, 111, 105, 99, 105]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.each_byte.size.should == nil
+ end
+ end
+ end
+ end
+end
+
+describe "IO#each_byte" do
+ before :each do
+ @io = IOSpecs.io_fixture "empty.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns self on an empty stream" do
+ @io.each_byte { |b| }.should.equal?(@io)
+ end
+end
diff --git a/spec/ruby/core/io/each_char_spec.rb b/spec/ruby/core/io/each_char_spec.rb
new file mode 100644
index 0000000000..5d460a1e7c
--- /dev/null
+++ b/spec/ruby/core/io/each_char_spec.rb
@@ -0,0 +1,12 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/chars'
+
+describe "IO#each_char" do
+ it_behaves_like :io_chars, :each_char
+end
+
+describe "IO#each_char" do
+ it_behaves_like :io_chars_empty, :each_char
+end
diff --git a/spec/ruby/core/io/each_codepoint_spec.rb b/spec/ruby/core/io/each_codepoint_spec.rb
new file mode 100644
index 0000000000..26cc87fc0e
--- /dev/null
+++ b/spec/ruby/core/io/each_codepoint_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/codepoints'
+
+# See redmine #1667
+describe "IO#each_codepoint" do
+ it_behaves_like :io_codepoints, :each_codepoint
+end
+
+describe "IO#each_codepoint" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "calls the given block" do
+ r = []
+ @io.each_codepoint { |c| r << c }
+ r[24].should == 232
+ r.last.should == 10
+ end
+
+ it "returns self" do
+ @io.each_codepoint { |l| l }.should.equal?(@io)
+ end
+end
+
+describe "IO#each_codepoint" do
+ before :each do
+ @io = IOSpecs.io_fixture("incomplete.txt")
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "raises an exception at incomplete character before EOF when conversion takes place" do
+ -> { @io.each_codepoint {} }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/io/each_line_spec.rb b/spec/ruby/core/io/each_line_spec.rb
new file mode 100644
index 0000000000..58d26b325d
--- /dev/null
+++ b/spec/ruby/core/io/each_line_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each'
+
+describe "IO#each_line" do
+ it_behaves_like :io_each, :each_line
+end
+
+describe "IO#each_line" do
+ it_behaves_like :io_each_default_separator, :each_line
+end
diff --git a/spec/ruby/core/io/each_spec.rb b/spec/ruby/core/io/each_spec.rb
new file mode 100644
index 0000000000..91ecbd19c8
--- /dev/null
+++ b/spec/ruby/core/io/each_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/each'
+
+describe "IO#each" do
+ it_behaves_like :io_each, :each
+end
+
+describe "IO#each" do
+ it_behaves_like :io_each_default_separator, :each
+end
diff --git a/spec/ruby/core/io/eof_spec.rb b/spec/ruby/core/io/eof_spec.rb
new file mode 100644
index 0000000000..c8955abde0
--- /dev/null
+++ b/spec/ruby/core/io/eof_spec.rb
@@ -0,0 +1,107 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#eof?" do
+ before :each do
+ @name = tmp("empty.txt")
+ touch @name
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns true on an empty stream that has just been opened" do
+ File.open(@name) { |empty| empty.should.eof? }
+ end
+
+ it "raises IOError on stream not opened for reading" do
+ -> do
+ File.open(@name, "w") { |f| f.eof? }
+ end.should.raise(IOError)
+ end
+end
+
+describe "IO#eof?" do
+ before :each do
+ @name = fixture __FILE__, "lines.txt"
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io && !@io.closed?
+ end
+
+ it "returns false when not at end of file" do
+ @io.read 1
+ @io.should_not.eof?
+ end
+
+ it "returns true after reading with read with no parameters" do
+ @io.read()
+ @io.should.eof?
+ end
+
+ it "returns true after reading with read" do
+ @io.read(File.size(@name))
+ @io.should.eof?
+ end
+
+ it "returns true after reading with sysread" do
+ @io.sysread(File.size(@name))
+ @io.should.eof?
+ end
+
+ it "returns true after reading with readlines" do
+ @io.readlines
+ @io.should.eof?
+ end
+
+ it "returns false on just opened non-empty stream" do
+ @io.should_not.eof?
+ end
+
+ it "does not consume the data from the stream" do
+ @io.should_not.eof?
+ @io.getc.should == 'V'
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.eof? }.should.raise(IOError)
+ end
+
+ it "raises IOError on stream closed for reading by close_read" do
+ @io.close_read
+ -> { @io.eof? }.should.raise(IOError)
+ end
+
+ it "returns true on one-byte stream after single-byte read" do
+ File.open(__dir__ + '/fixtures/one_byte.txt') { |one_byte|
+ one_byte.read(1)
+ one_byte.should.eof?
+ }
+ end
+end
+
+describe "IO#eof?" do
+ after :each do
+ @r.close if @r && !@r.closed?
+ @w.close if @w && !@w.closed?
+ end
+
+ it "returns true on receiving side of Pipe when writing side is closed" do
+ @r, @w = IO.pipe
+ @w.close
+ @r.should.eof?
+ end
+
+ it "returns false on receiving side of Pipe when writing side wrote some data" do
+ @r, @w = IO.pipe
+ @w.puts "hello"
+ @r.should_not.eof?
+ @w.close
+ @r.should_not.eof?
+ @r.read
+ @r.should.eof?
+ end
+end
diff --git a/spec/ruby/core/io/external_encoding_spec.rb b/spec/ruby/core/io/external_encoding_spec.rb
new file mode 100644
index 0000000000..72d246cc2b
--- /dev/null
+++ b/spec/ruby/core/io/external_encoding_spec.rb
@@ -0,0 +1,223 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :io_external_encoding_write, shared: true do
+ describe "when Encoding.default_internal is nil" do
+ before :each do
+ Encoding.default_internal = nil
+ end
+
+ it "returns nil" do
+ @io = new_io @name, @object
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should == nil
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "#{@object}:ibm866"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "#{@object}:ibm866"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should.equal?(Encoding::EUC_JP)
+ end
+ end
+
+ describe "when Encoding.default_external != Encoding.default_internal" do
+ before :each do
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns the value of Encoding.default_external when the instance was created" do
+ @io = new_io @name, @object
+ Encoding.default_external = Encoding::UTF_8
+ @io.external_encoding.should.equal?(Encoding::IBM437)
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "#{@object}:ibm866"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "#{@object}:ibm866"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should.equal?(Encoding::EUC_JP)
+ end
+ end
+
+ describe "when Encoding.default_external == Encoding.default_internal" do
+ before :each do
+ Encoding.default_external = Encoding::IBM866
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns the value of Encoding.default_external when the instance was created" do
+ @io = new_io @name, @object
+ Encoding.default_external = Encoding::UTF_8
+ @io.external_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "#{@object}:ibm866"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "#{@object}:ibm866"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should.equal?(Encoding::EUC_JP)
+ end
+ end
+end
+
+describe "IO#external_encoding" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ @name = tmp("io_external_encoding")
+ touch(@name)
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+
+ @io.close if @io
+ rm_r @name
+ 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
+ describe "when Encoding.default_internal is nil" do
+ before :each do
+ Encoding.default_internal = nil
+ Encoding.default_external = Encoding::IBM866
+ end
+
+ it "returns Encoding.default_external if the external encoding is not set" do
+ @io = new_io @name, "r"
+ @io.external_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "returns Encoding.default_external when that encoding is changed after the instance is created" do
+ @io = new_io @name, "r"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should.equal?(Encoding::IBM437)
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "r:utf-8"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should.equal?(Encoding::UTF_8)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "r:utf-8"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should.equal?(Encoding::EUC_JP)
+ end
+ end
+
+ describe "when Encoding.default_external == Encoding.default_internal" do
+ before :each do
+ Encoding.default_external = Encoding::IBM866
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns the value of Encoding.default_external when the instance was created" do
+ @io = new_io @name, "r"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "r:utf-8"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should.equal?(Encoding::UTF_8)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "r:utf-8"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should.equal?(Encoding::EUC_JP)
+ end
+ end
+
+ describe "when Encoding.default_external != Encoding.default_internal" do
+ before :each do
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+
+ it "returns the external encoding specified when the instance was created" do
+ @io = new_io @name, "r:utf-8"
+ Encoding.default_external = Encoding::IBM437
+ @io.external_encoding.should.equal?(Encoding::UTF_8)
+ end
+
+ it "returns the encoding set by #set_encoding" do
+ @io = new_io @name, "r:utf-8"
+ @io.set_encoding Encoding::EUC_JP, nil
+ @io.external_encoding.should.equal?(Encoding::EUC_JP)
+ end
+ end
+ end
+
+ describe "with 'rb' mode" do
+ it "returns Encoding::BINARY" do
+ @io = new_io @name, "rb"
+ @io.external_encoding.should.equal?(Encoding::BINARY)
+ end
+
+ it "returns the external encoding specified by the mode argument" do
+ @io = new_io @name, "rb:ibm437"
+ @io.external_encoding.should.equal?(Encoding::IBM437)
+ end
+ end
+
+ describe "with 'r+' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "r+"
+ end
+
+ describe "with 'w' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "w"
+ end
+
+ describe "with 'wb' mode" do
+ it "returns Encoding::BINARY" do
+ @io = new_io @name, "wb"
+ @io.external_encoding.should.equal?(Encoding::BINARY)
+ end
+
+ it "returns the external encoding specified by the mode argument" do
+ @io = new_io @name, "wb:ibm437"
+ @io.external_encoding.should.equal?(Encoding::IBM437)
+ end
+ end
+
+ describe "with 'w+' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "w+"
+ end
+
+ describe "with 'a' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "a"
+ end
+
+ describe "with 'a+' mode" do
+ it_behaves_like :io_external_encoding_write, nil, "a+"
+ end
+end
diff --git a/spec/ruby/core/io/fcntl_spec.rb b/spec/ruby/core/io/fcntl_spec.rb
new file mode 100644
index 0000000000..be6d06c672
--- /dev/null
+++ b/spec/ruby/core/io/fcntl_spec.rb
@@ -0,0 +1,8 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#fcntl" do
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.fcntl(5, 5) }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/fdatasync_spec.rb b/spec/ruby/core/io/fdatasync_spec.rb
new file mode 100644
index 0000000000..6242258ea0
--- /dev/null
+++ b/spec/ruby/core/io/fdatasync_spec.rb
@@ -0,0 +1,5 @@
+require_relative '../../spec_helper'
+
+describe "IO#fdatasync" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/ruby/core/io/fileno_spec.rb b/spec/ruby/core/io/fileno_spec.rb
new file mode 100644
index 0000000000..22fd68d166
--- /dev/null
+++ b/spec/ruby/core/io/fileno_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#fileno" do
+ it "returns the numeric file descriptor of the given IO object" do
+ $stdout.fileno.should == 1
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.fileno }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-16BE.txt b/spec/ruby/core/io/fixtures/bom_UTF-16BE.txt
new file mode 100644
index 0000000000..c7c42e9de4
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-16BE.txt
Binary files differ
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-16LE.txt b/spec/ruby/core/io/fixtures/bom_UTF-16LE.txt
new file mode 100644
index 0000000000..53642b6984
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-16LE.txt
Binary files differ
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-32BE.txt b/spec/ruby/core/io/fixtures/bom_UTF-32BE.txt
new file mode 100644
index 0000000000..c5efe6c278
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-32BE.txt
Binary files differ
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-32LE.txt b/spec/ruby/core/io/fixtures/bom_UTF-32LE.txt
new file mode 100644
index 0000000000..1168384393
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-32LE.txt
Binary files differ
diff --git a/spec/ruby/core/io/fixtures/bom_UTF-8.txt b/spec/ruby/core/io/fixtures/bom_UTF-8.txt
new file mode 100644
index 0000000000..ca971bef61
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/bom_UTF-8.txt
@@ -0,0 +1 @@
+UTF-8
diff --git a/spec/ruby/core/io/fixtures/classes.rb b/spec/ruby/core/io/fixtures/classes.rb
new file mode 100644
index 0000000000..204a2a101b
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/classes.rb
@@ -0,0 +1,218 @@
+# -*- encoding: utf-8 -*-
+
+module IOSpecs
+ THREAD_CLOSE_RETRIES = 10
+ THREAD_CLOSE_ERROR_MESSAGE = 'stream closed in another thread'
+
+ class SubIO < IO
+ end
+
+ class SubIOWithRedefinedNew < IO
+ def self.new(...)
+ ScratchPad << :redefined_new_called
+ super
+ end
+
+ def initialize(...)
+ ScratchPad << :call_original_initialize
+ super
+ end
+ end
+
+ def self.collector
+ Proc.new { |x| ScratchPad << x }
+ end
+
+ def self.lines
+ [ "Voici la ligne une.\n",
+ "Qui \303\250 la linea due.\n",
+ "\n",
+ "\n",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.\n",
+ "Hier ist Zeile vier.\n",
+ "\n",
+ "Est\303\241 aqui a linha cinco.\n",
+ "Here is line six.\n" ]
+ end
+
+ def self.lines_without_newline_characters
+ [ "Voici la ligne une.",
+ "Qui \303\250 la linea due.",
+ "",
+ "",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.",
+ "Hier ist Zeile vier.",
+ "",
+ "Est\303\241 aqui a linha cinco.",
+ "Here is line six." ]
+ end
+
+ def self.lines_limit
+ [ "Voici la l",
+ "igne une.\n",
+ "Qui è la ",
+ "linea due.",
+ "\n",
+ "\n",
+ "\n",
+ "Aquí está",
+ " la línea",
+ " tres.\n",
+ "Hier ist Z",
+ "eile vier.",
+ "\n",
+ "\n",
+ "Está aqui",
+ " a linha c",
+ "inco.\n",
+ "Here is li",
+ "ne six.\n" ]
+ end
+
+ def self.lines_space_separator_limit
+ [ "Voici ",
+ "la ",
+ "ligne ",
+ "une.\nQui ",
+ "è ",
+ "la ",
+ "linea ",
+ "due.\n\n\nAqu",
+ "í ",
+ "está ",
+ "la ",
+ "línea ",
+ "tres.\nHier",
+ " ",
+ "ist ",
+ "Zeile ",
+ "vier.\n\nEst",
+ "á ",
+ "aqui ",
+ "a ",
+ "linha ",
+ "cinco.\nHer",
+ "e ",
+ "is ",
+ "line ",
+ "six.\n" ]
+ end
+
+ def self.lines_r_separator
+ [ "Voici la ligne une.\nQui \303\250 la linea due.\n\n\nAqu\303\255 est\303\241 la l\303\255nea tr",
+ "es.\nHier",
+ " ist Zeile vier",
+ ".\n\nEst\303\241 aqui a linha cinco.\nHer",
+ "e is line six.\n" ]
+ end
+
+ def self.lines_empty_separator
+ [ "Voici la ligne une.\nQui \303\250 la linea due.\n\n",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.\n\n",
+ "Est\303\241 aqui a linha cinco.\nHere is line six.\n" ]
+ end
+
+ def self.lines_space_separator
+ [ "Voici ", "la ", "ligne ", "une.\nQui ",
+ "\303\250 ", "la ", "linea ", "due.\n\n\nAqu\303\255 ",
+ "est\303\241 ", "la ", "l\303\255nea ", "tres.\nHier ",
+ "ist ", "Zeile ", "vier.\n\nEst\303\241 ", "aqui ", "a ",
+ "linha ", "cinco.\nHere ", "is ", "line ", "six.\n" ]
+ end
+
+ def self.lines_space_separator_without_trailing_spaces
+ [ "Voici", "la", "ligne", "une.\nQui",
+ "\303\250", "la", "linea", "due.\n\n\nAqu\303\255",
+ "est\303\241", "la", "l\303\255nea", "tres.\nHier",
+ "ist", "Zeile", "vier.\n\nEst\303\241", "aqui", "a",
+ "linha", "cinco.\nHere", "is", "line", "six.\n" ]
+ end
+
+ def self.lines_arbitrary_separator
+ [ "Voici la ligne une.\nQui \303\250",
+ " la linea due.\n\n\nAqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.\n\nEst\303\241 aqui a linha cinco.\nHere is line six.\n" ]
+ end
+
+ def self.paragraphs
+ [ "Voici la ligne une.\nQui \303\250 la linea due.\n\n",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.\n\n",
+ "Est\303\241 aqui a linha cinco.\nHere is line six.\n" ]
+ end
+
+ def self.paragraphs_without_trailing_new_line_characters
+ [ "Voici la ligne une.\nQui \303\250 la linea due.",
+ "Aqu\303\255 est\303\241 la l\303\255nea tres.\nHier ist Zeile vier.",
+ "Est\303\241 aqui a linha cinco.\nHere is line six.\n" ]
+ end
+
+ # Creates an IO instance for an existing fixture file. The
+ # file should obviously not be deleted.
+ def self.io_fixture(name, mode = "r:utf-8")
+ path = fixture __FILE__, name
+ name = path if File.exist? path
+ new_io(name, mode)
+ end
+
+ # Returns a closed instance of IO that was opened to reference
+ # a fixture file (i.e. the IO instance was perfectly valid at
+ # one point but is now closed).
+ def self.closed_io
+ io = io_fixture "lines.txt"
+ io.close
+ io
+ end
+
+ # Creates a pipe-based IO fixture containing the specified
+ # contents
+ def self.pipe_fixture(content)
+ source, sink = IO.pipe
+ sink.write content
+ sink.close
+ source
+ end
+
+ # Defines +method+ on +obj+ using the provided +block+. This
+ # special helper is needed for e.g. IO.open specs to avoid
+ # mock methods preventing IO#close from running.
+ def self.io_mock(obj, method, &block)
+ obj.singleton_class.send(:define_method, method, &block)
+ end
+
+ module CopyStream
+ def self.from=(from)
+ @from = from
+ end
+
+ def self.from
+ @from
+ end
+ end
+
+ class CopyStreamRead
+ def initialize(io)
+ @io = io
+ end
+
+ def read(size, buf)
+ @io.read size, buf
+ end
+
+ def send(*args)
+ raise "send called"
+ end
+ end
+
+ class CopyStreamReadPartial
+ def initialize(io)
+ @io = io
+ end
+
+ def readpartial(size, buf)
+ @io.readpartial size, buf
+ end
+
+ def send(*args)
+ raise "send called"
+ end
+ end
+end
diff --git a/spec/ruby/core/io/fixtures/copy_in_out.rb b/spec/ruby/core/io/fixtures/copy_in_out.rb
new file mode 100644
index 0000000000..b9d4085a47
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/copy_in_out.rb
@@ -0,0 +1,2 @@
+STDOUT.sync = false
+IO.copy_stream(STDIN, STDOUT)
diff --git a/spec/ruby/core/io/fixtures/copy_stream.txt b/spec/ruby/core/io/fixtures/copy_stream.txt
new file mode 100644
index 0000000000..a2d827b351
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/copy_stream.txt
@@ -0,0 +1,6 @@
+Line one
+
+Line three
+Line four
+
+Line last
diff --git a/spec/ruby/core/io/fixtures/empty.txt b/spec/ruby/core/io/fixtures/empty.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/empty.txt
diff --git a/spec/ruby/core/io/fixtures/incomplete.txt b/spec/ruby/core/io/fixtures/incomplete.txt
new file mode 100644
index 0000000000..23d432f642
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/incomplete.txt
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/spec/ruby/core/io/fixtures/lines.txt b/spec/ruby/core/io/fixtures/lines.txt
new file mode 100644
index 0000000000..0959997e7b
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/lines.txt
@@ -0,0 +1,9 @@
+Voici la ligne une.
+Qui è la linea due.
+
+
+Aquí está la línea tres.
+Hier ist Zeile vier.
+
+Está aqui a linha cinco.
+Here is line six.
diff --git a/spec/ruby/core/io/fixtures/no_bom_UTF-8.txt b/spec/ruby/core/io/fixtures/no_bom_UTF-8.txt
new file mode 100644
index 0000000000..7edc66b06a
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/no_bom_UTF-8.txt
@@ -0,0 +1 @@
+UTF-8
diff --git a/spec/ruby/core/io/fixtures/numbered_lines.txt b/spec/ruby/core/io/fixtures/numbered_lines.txt
new file mode 100644
index 0000000000..70e49a3d98
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/numbered_lines.txt
@@ -0,0 +1,5 @@
+Line 1: One
+Line 2: Two
+Line 3: Three
+Line 4: Four
+Line 5: Five
diff --git a/spec/ruby/core/io/fixtures/one_byte.txt b/spec/ruby/core/io/fixtures/one_byte.txt
new file mode 100644
index 0000000000..56a6051ca2
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/one_byte.txt
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/spec/ruby/core/io/fixtures/read_binary.txt b/spec/ruby/core/io/fixtures/read_binary.txt
new file mode 100644
index 0000000000..fa036dca4b
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/read_binary.txt
@@ -0,0 +1 @@
+abcdef
diff --git a/spec/ruby/core/io/fixtures/read_euc_jp.txt b/spec/ruby/core/io/fixtures/read_euc_jp.txt
new file mode 100644
index 0000000000..0e17cd717a
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/read_euc_jp.txt
@@ -0,0 +1 @@
+꤬Ȥ
diff --git a/spec/ruby/core/io/fixtures/read_text.txt b/spec/ruby/core/io/fixtures/read_text.txt
new file mode 100644
index 0000000000..5a7a80f6e4
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/read_text.txt
@@ -0,0 +1 @@
+abcâdef
diff --git a/spec/ruby/core/io/fixtures/reopen_stdout.rb b/spec/ruby/core/io/fixtures/reopen_stdout.rb
new file mode 100644
index 0000000000..506ba072bd
--- /dev/null
+++ b/spec/ruby/core/io/fixtures/reopen_stdout.rb
@@ -0,0 +1,3 @@
+STDOUT.reopen ARGV[0]
+system "echo from system"
+exec "echo from exec"
diff --git a/spec/ruby/core/io/flush_spec.rb b/spec/ruby/core/io/flush_spec.rb
new file mode 100644
index 0000000000..4c3e8d12af
--- /dev/null
+++ b/spec/ruby/core/io/flush_spec.rb
@@ -0,0 +1,37 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#flush" do
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.flush }.should.raise(IOError)
+ end
+
+ describe "on a pipe" do
+ before :each do
+ @r, @w = IO.pipe
+ end
+
+ after :each do
+ @r.close
+ begin
+ @w.close
+ rescue Errno::EPIPE
+ end
+ end
+
+ # [ruby-core:90895] RJIT worker may leave fd open in a forked child.
+ # For instance, RJIT creates a worker before @r.close with fork(), @r.close happens,
+ # and the RJIT worker keeps the pipe open until the worker execve().
+ # TODO: consider acquiring GVL from RJIT worker.
+ guard_not -> { defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? } do
+ it "raises Errno::EPIPE if sync=false and the read end is closed" do
+ @w.sync = false
+ @w.write "foo"
+ @r.close
+
+ -> { @w.flush }.should.raise(Errno::EPIPE, /Broken pipe/)
+ -> { @w.close }.should.raise(Errno::EPIPE, /Broken pipe/)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/for_fd_spec.rb b/spec/ruby/core/io/for_fd_spec.rb
new file mode 100644
index 0000000000..2d86361b73
--- /dev/null
+++ b/spec/ruby/core/io/for_fd_spec.rb
@@ -0,0 +1,10 @@
+require_relative '../../spec_helper'
+require_relative 'shared/new'
+
+describe "IO.for_fd" do
+ it_behaves_like :io_new, :for_fd
+end
+
+describe "IO.for_fd" do
+ it_behaves_like :io_new_errors, :for_fd
+end
diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb
new file mode 100644
index 0000000000..015988f9fb
--- /dev/null
+++ b/spec/ruby/core/io/foreach_spec.rb
@@ -0,0 +1,96 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/readlines'
+
+describe "IO.foreach" do
+ before :each do
+ @name = fixture __FILE__, "lines.txt"
+ @count = 0
+ ScratchPad.record []
+ end
+
+ it "updates $. with each yield" do
+ IO.foreach(@name) { $..should == @count += 1 }
+ 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 }
+ 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 = $$
+
+ 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!
+ end
+ end
+ end
+ end
+
+ # https://bugs.ruby-lang.org/issues/19630
+ it "warns about deprecation given a path with a pipe" do
+ cmd = "|echo ok"
+ -> {
+ IO.foreach(cmd).to_a
+ }.should complain(/IO process creation with a leading '\|'/)
+ end
+ end
+end
+
+describe "IO.foreach" do
+ before :each do
+ @external = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+
+ @name = fixture __FILE__, "lines.txt"
+ ScratchPad.record []
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ end
+
+ it "sets $_ to nil" do
+ $_ = "test"
+ IO.foreach(@name) { }
+ $_.should == nil
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ IO.foreach(@name).should.instance_of?(Enumerator)
+ IO.foreach(@name).to_a.should == IOSpecs.lines
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ IO.foreach(@name).size.should == nil
+ end
+ end
+ end
+ end
+
+ it_behaves_like :io_readlines, :foreach, IOSpecs.collector
+ it_behaves_like :io_readlines_options_19, :foreach, IOSpecs.collector
+end
diff --git a/spec/ruby/core/io/fsync_spec.rb b/spec/ruby/core/io/fsync_spec.rb
new file mode 100644
index 0000000000..0317cbc805
--- /dev/null
+++ b/spec/ruby/core/io/fsync_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#fsync" do
+ before :each do
+ @name = tmp("io_fsync.txt")
+ ScratchPad.clear
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.fsync }.should.raise(IOError)
+ end
+
+ it "writes the buffered data to permanent storage" do
+ File.open(@name, "w") do |f|
+ f.write "one hit wonder"
+ f.fsync.should == 0
+ end
+ end
+end
diff --git a/spec/ruby/core/io/getbyte_spec.rb b/spec/ruby/core/io/getbyte_spec.rb
new file mode 100644
index 0000000000..668d81519c
--- /dev/null
+++ b/spec/ruby/core/io/getbyte_spec.rb
@@ -0,0 +1,58 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#getbyte" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns the next byte from the stream" do
+ @io.readline.should == "Voici la ligne une.\n"
+ letters = @io.getbyte, @io.getbyte, @io.getbyte, @io.getbyte, @io.getbyte
+ letters.should == [81, 117, 105, 32, 195]
+ end
+
+ it "returns nil when invoked at the end of the stream" do
+ @io.read
+ @io.getbyte.should == nil
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.getbyte }.should.raise(IOError)
+ end
+end
+
+describe "IO#getbyte" do
+ before :each do
+ @io = IOSpecs.io_fixture "empty.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns nil on empty stream" do
+ @io.getbyte.should == nil
+ end
+end
+
+describe "IO#getbyte" do
+ before :each do
+ @name = tmp("io_getbyte.txt")
+ @io = new_io(@name, 'w')
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name if @name
+ end
+
+ it "raises an IOError if the stream is not readable" do
+ -> { @io.getbyte }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/getc_spec.rb b/spec/ruby/core/io/getc_spec.rb
new file mode 100644
index 0000000000..3be86203c0
--- /dev/null
+++ b/spec/ruby/core/io/getc_spec.rb
@@ -0,0 +1,42 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#getc" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns the next character from the stream" do
+ @io.readline.should == "Voici la ligne une.\n"
+ letters = @io.getc, @io.getc, @io.getc, @io.getc, @io.getc
+ letters.should == ["Q", "u", "i", " ", "è"]
+ end
+
+ it "returns nil when invoked at the end of the stream" do
+ @io.read
+ @io.getc.should == nil
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.getc }.should.raise(IOError)
+ end
+end
+
+describe "IO#getc" do
+ before :each do
+ @io = IOSpecs.io_fixture "empty.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns nil on empty stream" do
+ @io.getc.should == nil
+ end
+end
diff --git a/spec/ruby/core/io/gets_spec.rb b/spec/ruby/core/io/gets_spec.rb
new file mode 100644
index 0000000000..ce3ee73b94
--- /dev/null
+++ b/spec/ruby/core/io/gets_spec.rb
@@ -0,0 +1,348 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/gets_ascii'
+
+describe "IO#gets" do
+ it_behaves_like :io_gets_ascii, :gets
+end
+
+describe "IO#gets" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @count = 0
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "assigns the returned line to $_" do
+ IOSpecs.lines.each do |line|
+ @io.gets
+ $_.should == line
+ end
+ end
+
+ it "sets $_ to nil after the last line has been read" do
+ while @io.gets
+ end
+ $_.should == nil
+ end
+
+ it "returns nil if called at the end of the stream" do
+ IOSpecs.lines.length.times { @io.gets }
+ @io.gets.should == nil
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.gets }.should.raise(IOError)
+ end
+
+ describe "with no separator" do
+ it "returns the next line of string that is separated by $/" do
+ IOSpecs.lines.each { |line| line.should == @io.gets }
+ end
+
+ it "updates lineno with each invocation" do
+ while @io.gets
+ @io.lineno.should == @count += 1
+ end
+ end
+
+ it "updates $. with each invocation" do
+ while @io.gets
+ $..should == @count += 1
+ end
+ end
+ end
+
+ describe "with nil separator" do
+ it "returns the entire contents" do
+ @io.gets(nil).should == IOSpecs.lines.join("")
+ end
+
+ it "updates lineno with each invocation" do
+ while @io.gets(nil)
+ @io.lineno.should == @count += 1
+ end
+ end
+
+ it "updates $. with each invocation" do
+ while @io.gets(nil)
+ $..should == @count += 1
+ end
+ end
+ end
+
+ describe "with an empty String separator" do
+ # Two successive newlines in the input separate paragraphs.
+ # When there are more than two successive newlines, only two are kept.
+ it "returns the next paragraph" do
+ @io.gets("").should == IOSpecs.lines[0,3].join("")
+ @io.gets("").should == IOSpecs.lines[4,3].join("")
+ @io.gets("").should == IOSpecs.lines[7,2].join("")
+ end
+
+ it "reads until the beginning of the next paragraph" do
+ # There are three newlines between the first and second paragraph
+ @io.gets("")
+ @io.gets.should == IOSpecs.lines[4]
+ end
+
+ it "updates lineno with each invocation" do
+ while @io.gets("")
+ @io.lineno.should == @count += 1
+ end
+ end
+
+ it "updates $. with each invocation" do
+ while @io.gets("")
+ $..should == @count += 1
+ end
+ end
+ end
+
+ describe "with an arbitrary String separator" do
+ it "reads up to and including the separator" do
+ @io.gets("la linea").should == "Voici la ligne une.\nQui \303\250 la linea"
+ end
+
+ it "updates lineno with each invocation" do
+ while (@io.gets("la"))
+ @io.lineno.should == @count += 1
+ end
+ end
+
+ it "updates $. with each invocation" do
+ while @io.gets("la")
+ $..should == @count += 1
+ end
+ end
+
+ describe "that consists of multiple bytes" do
+ platform_is_not :windows do
+ it "should match the separator even if the buffer is filled over successive reads" do
+ IO.pipe do |read, write|
+
+ # Write part of the string with the separator split between two write calls. We want
+ # the read to intertwine such that when the read starts the full data isn't yet
+ # available in the buffer.
+ write.write("Aquí está la línea tres\r\n")
+
+ t = Thread.new do
+ # Continue reading until the separator is encountered or the pipe is closed.
+ read.gets("\r\n\r\n")
+ end
+
+ # Write the other half of the separator, which should cause the `gets` call to now
+ # match. Explicitly close the pipe for good measure so a bug in `gets` doesn't block forever.
+ Thread.pass until t.stop?
+
+ write.write("\r\nelse\r\n\r\n")
+ write.close
+
+ t.value.bytes.should == "Aquí está la línea tres\r\n\r\n".bytes
+ read.read(8).bytes.should == "else\r\n\r\n".bytes
+ end
+ end
+ end
+ end
+ end
+
+ describe "when passed chomp" do
+ it "returns the first line without a trailing newline character" do
+ @io.gets(chomp: true).should == IOSpecs.lines_without_newline_characters[0]
+ end
+
+ it "raises exception when options passed as Hash" do
+ -> { @io.gets({ chomp: true }) }.should.raise(TypeError)
+
+ -> {
+ @io.gets("\n", 1, { chomp: true })
+ }.should.raise(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
+ end
+ end
+end
+
+describe "IO#gets" do
+ before :each do
+ @name = tmp("io_gets")
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "raises an IOError if the stream is opened for append only" do
+ -> { File.open(@name, "a:utf-8") { |f| f.gets } }.should.raise(IOError)
+ end
+
+ it "raises an IOError if the stream is opened for writing only" do
+ -> { File.open(@name, "w:utf-8") { |f| f.gets } }.should.raise(IOError)
+ end
+end
+
+describe "IO#gets" do
+ before :each do
+ @name = tmp("io_gets")
+ touch(@name) { |f| f.write "one\n\ntwo\n\nthree\nfour\n" }
+ @io = new_io @name, "r:utf-8"
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "calls #to_int to convert a single object argument to an Integer limit" do
+ obj = mock("io gets limit")
+ obj.should_receive(:to_int).and_return(6)
+
+ @io.gets(obj).should == "one\n"
+ end
+
+ it "calls #to_int to convert the second object argument to an Integer limit" do
+ obj = mock("io gets limit")
+ obj.should_receive(:to_int).and_return(2)
+
+ @io.gets(nil, obj).should == "on"
+ end
+
+ it "calls #to_str to convert the first argument to a String when passed a limit" do
+ obj = mock("io gets separator")
+ obj.should_receive(:to_str).and_return($/)
+
+ @io.gets(obj, 5).should == "one\n"
+ end
+
+ it "reads to the default separator when passed a single argument greater than the number of bytes to the separator" do
+ @io.gets(6).should == "one\n"
+ end
+
+ it "reads limit bytes when passed a single argument less than the number of bytes to the default separator" do
+ @io.gets(3).should == "one"
+ end
+
+ it "reads limit bytes when passed nil and a limit" do
+ @io.gets(nil, 6).should == "one\n\nt"
+ end
+
+ it "reads all bytes when the limit is higher than the available bytes" do
+ @io.gets(nil, 100).should == "one\n\ntwo\n\nthree\nfour\n"
+ end
+
+ it "reads until the next paragraph when passed '' and a limit greater than the next paragraph" do
+ @io.gets("", 6).should == "one\n\n"
+ end
+
+ it "reads limit bytes when passed '' and a limit less than the next paragraph" do
+ @io.gets("", 3).should == "one"
+ end
+
+ it "reads all bytes when pass a separator and reading more than all bytes" do
+ @io.gets("\t", 100).should == "one\n\ntwo\n\nthree\nfour\n"
+ end
+
+ it "returns empty string when 0 passed as a limit" do
+ @io.gets(0).should == ""
+ @io.gets(nil, 0).should == ""
+ @io.gets("", 0).should == ""
+ end
+
+ it "does not accept limit that doesn't fit in a C off_t" do
+ -> { @io.gets(2**128) }.should.raise(RangeError)
+ end
+end
+
+describe "IO#gets" do
+ before :each do
+ @name = tmp("io_gets")
+ # create data "朝日" + "\xE3\x81" * 100 to avoid utf-8 conflicts
+ data = "朝日" + ([227,129].pack('C*') * 100).force_encoding('utf-8')
+ touch(@name) { |f| f.write data }
+ @io = new_io @name, "r:utf-8"
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "reads limit bytes and extra bytes when limit is reached not at character boundary" do
+ [@io.gets(1), @io.gets(1)].should == ["朝", "日"]
+ end
+
+ it "read limit bytes and extra bytes with maximum of 16" do
+ # create str "朝日\xE3" + "\x81\xE3" * 8 to avoid utf-8 conflicts
+ str = "朝日" + ([227] + [129,227] * 8).pack('C*').force_encoding('utf-8')
+ @io.gets(7).should == str
+ end
+end
+
+describe "IO#gets" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = nil
+
+ @name = tmp("io_gets")
+ touch(@name) { |f| f.write "line" }
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "uses the default external encoding" do
+ @io = new_io @name, 'r'
+ @io.gets.encoding.should == Encoding::UTF_8
+ end
+
+ it "uses the IO object's external encoding, when set" do
+ @io = new_io @name, 'r'
+ @io.set_encoding Encoding::US_ASCII
+ @io.gets.encoding.should == Encoding::US_ASCII
+ end
+
+ it "transcodes into the default internal encoding" do
+ Encoding.default_internal = Encoding::US_ASCII
+ @io = new_io @name, 'r'
+ @io.gets.encoding.should == Encoding::US_ASCII
+ end
+
+ it "transcodes into the IO object's internal encoding, when set" do
+ Encoding.default_internal = Encoding::US_ASCII
+ @io = new_io @name, 'r'
+ @io.set_encoding Encoding::UTF_8, Encoding::UTF_16
+ @io.gets.encoding.should == Encoding::UTF_16
+ end
+
+ it "overwrites the default external encoding with the IO object's own external encoding" do
+ Encoding.default_external = Encoding::BINARY
+ Encoding.default_internal = Encoding::UTF_8
+ @io = new_io @name, 'r'
+ @io.set_encoding Encoding::IBM866
+ @io.gets.encoding.should == Encoding::UTF_8
+ end
+
+ it "ignores the internal encoding if the default external encoding is BINARY" do
+ Encoding.default_external = Encoding::BINARY
+ Encoding.default_internal = Encoding::UTF_8
+ @io = new_io @name, 'r'
+ @io.gets.encoding.should == Encoding::BINARY
+ end
+
+ it "ignores the internal encoding if the IO object's external encoding is BINARY" do
+ Encoding.default_external = Encoding::BINARY
+ Encoding.default_internal = Encoding::UTF_8
+ @io = new_io @name, 'r'
+ @io.set_encoding Encoding::BINARY, Encoding::UTF_8
+ @io.gets.encoding.should == Encoding::BINARY
+ end
+end
diff --git a/spec/ruby/core/io/initialize_spec.rb b/spec/ruby/core/io/initialize_spec.rb
new file mode 100644
index 0000000000..3425e5ac37
--- /dev/null
+++ b/spec/ruby/core/io/initialize_spec.rb
@@ -0,0 +1,60 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#initialize" do
+ before :each do
+ @name = tmp("io_initialize.txt")
+ @io = IO.new(new_fd(@name))
+ @fd = @io.fileno
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "reassociates the IO instance with the new descriptor when passed an Integer" do
+ fd = new_fd @name, "r:utf-8"
+ @io.send :initialize, fd, 'r'
+ @io.fileno.should == fd
+ end
+
+ it "calls #to_int to coerce the object passed as an fd" do
+ obj = mock('fileno')
+ fd = new_fd @name, "r:utf-8"
+ obj.should_receive(:to_int).and_return(fd)
+ @io.send :initialize, obj, 'r'
+ @io.fileno.should == fd
+ end
+
+ it "accepts options as keyword arguments" do
+ fd = new_fd @name, "w:utf-8"
+
+ @io.send(:initialize, fd, "w", flags: File::CREAT)
+ @io.fileno.should == fd
+
+ -> {
+ @io.send(:initialize, fd, "w", {flags: File::CREAT})
+ }.should.raise(ArgumentError, "wrong number of arguments (given 3, expected 1..2)")
+ end
+
+ it "raises a TypeError when passed an IO" do
+ -> { @io.send :initialize, STDOUT, 'w' }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed nil" do
+ -> { @io.send :initialize, nil, 'w' }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError when passed a String" do
+ -> { @io.send :initialize, "4", 'w' }.should.raise(TypeError)
+ end
+
+ it "raises IOError on closed stream" do
+ -> { @io.send :initialize, IOSpecs.closed_io.fileno }.should.raise(IOError)
+ end
+
+ it "raises an Errno::EBADF when given an invalid file descriptor" do
+ -> { @io.send :initialize, -1, 'w' }.should.raise(Errno::EBADF)
+ end
+end
diff --git a/spec/ruby/core/io/inspect_spec.rb b/spec/ruby/core/io/inspect_spec.rb
new file mode 100644
index 0000000000..37dc459f22
--- /dev/null
+++ b/spec/ruby/core/io/inspect_spec.rb
@@ -0,0 +1,23 @@
+require_relative '../../spec_helper'
+
+describe "IO#inspect" do
+ after :each do
+ @r.close if @r && !@r.closed?
+ @w.close if @w && !@w.closed?
+ end
+
+ it "contains the file descriptor number" do
+ @r, @w = IO.pipe
+ @r.inspect.should.include?("fd #{@r.fileno}")
+ end
+
+ it "contains \"(closed)\" if the stream is closed" do
+ @r, @w = IO.pipe
+ @r.close
+ @r.inspect.should.include?("(closed)")
+ end
+
+ it "reports IO as its Method object's owner" do
+ IO.instance_method(:inspect).owner.should == IO
+ end
+end
diff --git a/spec/ruby/core/io/internal_encoding_spec.rb b/spec/ruby/core/io/internal_encoding_spec.rb
new file mode 100644
index 0000000000..9963a93f33
--- /dev/null
+++ b/spec/ruby/core/io/internal_encoding_spec.rb
@@ -0,0 +1,145 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :io_internal_encoding, shared: true do
+ describe "when Encoding.default_internal is not set" do
+ before :each do
+ Encoding.default_internal = nil
+ end
+
+ it "returns nil if the internal encoding is not set" do
+ @io = new_io @name, @object
+ @io.internal_encoding.should == nil
+ end
+
+ it "returns nil if Encoding.default_internal is changed after the instance is created" do
+ @io = new_io @name, @object
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should == nil
+ end
+
+ it "returns the value set when the instance was created" do
+ @io = new_io @name, "#{@object}:utf-8:euc-jp"
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it "returns the value set by #set_encoding" do
+ @io = new_io @name, @object
+ @io.set_encoding(Encoding::US_ASCII, Encoding::IBM437)
+ @io.internal_encoding.should.equal?(Encoding::IBM437)
+ end
+ end
+
+ describe "when Encoding.default_internal == Encoding.default_external" do
+ before :each do
+ Encoding.default_external = Encoding::IBM866
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns nil" do
+ @io = new_io @name, @object
+ @io.internal_encoding.should == nil
+ end
+
+ it "returns nil regardless of Encoding.default_internal changes" do
+ @io = new_io @name, @object
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should == nil
+ end
+ end
+
+ describe "when Encoding.default_internal != Encoding.default_external" do
+ before :each do
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+ end
+
+ it "returns the value of Encoding.default_internal when the instance was created if the internal encoding is not set" do
+ @io = new_io @name, @object
+ @io.internal_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "does not change when Encoding.default_internal is changed" do
+ @io = new_io @name, @object
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "returns the internal encoding set when the instance was created" do
+ @io = new_io @name, "#{@object}:utf-8:euc-jp"
+ @io.internal_encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it "does not change when set and Encoding.default_internal is changed" do
+ @io = new_io @name, "#{@object}:utf-8:euc-jp"
+ Encoding.default_internal = Encoding::IBM437
+ @io.internal_encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it "returns the value set by #set_encoding" do
+ @io = new_io @name, @object
+ @io.set_encoding(Encoding::US_ASCII, Encoding::IBM437)
+ @io.internal_encoding.should.equal?(Encoding::IBM437)
+ end
+
+ it "returns nil when Encoding.default_external is BINARY and the internal encoding is not set" do
+ Encoding.default_external = Encoding::BINARY
+ @io = new_io @name, @object
+ @io.internal_encoding.should == nil
+ end
+
+ it "returns nil when the external encoding is BINARY and the internal encoding is not set" do
+ @io = new_io @name, "#{@object}:binary"
+ @io.internal_encoding.should == nil
+ end
+ end
+end
+
+describe "IO#internal_encoding" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ @name = tmp("io_internal_encoding")
+ touch(@name)
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+
+ Encoding.default_external = @external
+ Encoding.default_internal = @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
+ it_behaves_like :io_internal_encoding, nil, "r"
+ end
+
+ describe "with 'r+' mode" do
+ it_behaves_like :io_internal_encoding, nil, "r+"
+ end
+
+ describe "with 'w' mode" do
+ it_behaves_like :io_internal_encoding, nil, "w"
+ end
+
+ describe "with 'w+' mode" do
+ it_behaves_like :io_internal_encoding, nil, "w+"
+ end
+
+ describe "with 'a' mode" do
+ it_behaves_like :io_internal_encoding, nil, "a"
+ end
+
+ describe "with 'a+' mode" do
+ it_behaves_like :io_internal_encoding, nil, "a+"
+ end
+end
diff --git a/spec/ruby/core/io/io_spec.rb b/spec/ruby/core/io/io_spec.rb
new file mode 100644
index 0000000000..0feb1a8774
--- /dev/null
+++ b/spec/ruby/core/io/io_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+
+describe "IO" do
+ it "includes File::Constants" do
+ IO.include?(File::Constants).should == true
+ end
+
+ it "includes Enumerable" do
+ IO.include?(Enumerable).should == true
+ end
+end
diff --git a/spec/ruby/core/io/ioctl_spec.rb b/spec/ruby/core/io/ioctl_spec.rb
new file mode 100644
index 0000000000..15a5d6ec22
--- /dev/null
+++ b/spec/ruby/core/io/ioctl_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#ioctl" do
+ platform_is_not :windows do
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.ioctl(5, 5) }.should.raise(IOError)
+ end
+ end
+
+ platform_is :linux do
+ guard -> { RUBY_PLATFORM.include?("86") } do # x86 / x86_64
+ it "resizes an empty String to match the output size" do
+ File.open(__FILE__, 'r') do |f|
+ buffer = +''
+ # FIONREAD in /usr/include/asm-generic/ioctls.h
+ f.ioctl 0x541B, buffer
+ buffer.unpack('I').first.should.is_a?(Integer)
+ end
+ end
+ end
+
+ it "raises a system call error when ioctl fails" do
+ File.open(__FILE__, 'r') do |f|
+ -> {
+ # TIOCGWINSZ in /usr/include/asm-generic/ioctls.h
+ f.ioctl 0x5413, nil
+ }.should.raise(SystemCallError)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/isatty_spec.rb b/spec/ruby/core/io/isatty_spec.rb
new file mode 100644
index 0000000000..3b6c69b190
--- /dev/null
+++ b/spec/ruby/core/io/isatty_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/tty'
+
+describe "IO#isatty" do
+ it_behaves_like :io_tty, :isatty
+end
diff --git a/spec/ruby/core/io/lineno_spec.rb b/spec/ruby/core/io/lineno_spec.rb
new file mode 100644
index 0000000000..93b505652a
--- /dev/null
+++ b/spec/ruby/core/io/lineno_spec.rb
@@ -0,0 +1,138 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#lineno" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "raises an IOError on a closed stream" do
+ -> { IOSpecs.closed_io.lineno }.should.raise(IOError)
+ end
+
+ it "raises an IOError on a write-only stream" do
+ name = tmp("io_lineno.txt")
+ begin
+ File.open(name, 'w') do |f|
+ -> { f.lineno }.should.raise(IOError)
+ end
+ ensure
+ rm_r name
+ end
+ end
+
+ it "raises an IOError on a duplexed stream with the read side closed" do
+ cmd = platform_is(:windows) ? 'rem' : 'cat'
+ IO.popen(cmd, 'r+') do |p|
+ p.close_read
+ -> { p.lineno }.should.raise(IOError)
+ end
+ end
+
+ it "returns the current line number" do
+ @io.lineno.should == 0
+
+ count = 0
+ while @io.gets
+ @io.lineno.should == count += 1
+ end
+
+ @io.rewind
+ @io.lineno.should == 0
+ end
+end
+
+describe "IO#lineno=" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "raises an IOError on a closed stream" do
+ -> { IOSpecs.closed_io.lineno = 5 }.should.raise(IOError)
+ end
+
+ it "raises an IOError on a write-only stream" do
+ name = tmp("io_lineno.txt")
+ begin
+ File.open(name, 'w') do |f|
+ -> { f.lineno = 0 }.should.raise(IOError)
+ end
+ ensure
+ rm_r name
+ end
+ end
+
+ it "raises an IOError on a duplexed stream with the read side closed" do
+ cmd = platform_is(:windows) ? 'rem' : 'cat'
+ IO.popen(cmd, 'r+') do |p|
+ p.close_read
+ -> { p.lineno = 0 }.should.raise(IOError)
+ end
+ end
+
+ it "calls #to_int on a non-numeric argument" do
+ obj = mock('123')
+ obj.should_receive(:to_int).and_return(123)
+
+ @io.lineno = obj
+ @io.lineno.should == 123
+ end
+
+ it "truncates a Float argument" do
+ @io.lineno = 1.5
+ @io.lineno.should == 1
+
+ @io.lineno = 92233.72036854775808
+ @io.lineno.should == 92233
+ end
+
+ it "raises TypeError if cannot convert argument to Integer implicitly" do
+ -> { @io.lineno = "1" }.should.raise(TypeError, 'no implicit conversion of String into Integer')
+ -> { @io.lineno = nil }.should raise_consistent_error(TypeError, 'no implicit conversion of nil into Integer')
+ end
+
+ it "does not accept Integers that don't fit in a C int" do
+ -> { @io.lineno = 2**32 }.should.raise(RangeError)
+ end
+
+ it "sets the current line number to the given value" do
+ @io.lineno = count = 500
+
+ while @io.gets
+ @io.lineno.should == count += 1
+ end
+
+ @io.rewind
+ @io.lineno.should == 0
+ end
+
+ it "does not change $." do
+ original_line = $.
+ numbers = [-2**30, -2**16, -2**8, -100, -10, -1, 0, 1, 10, 2**8, 2**16, 2**30]
+ numbers.each do |num|
+ @io.lineno = num
+ @io.lineno.should == num
+ $..should == original_line
+ end
+ end
+
+ it "does not change $. until next read" do
+ $. = 0
+ $..should == 0
+
+ @io.lineno = count = 500
+ $..should == 0
+
+ while @io.gets
+ $..should == count += 1
+ end
+ end
+end
diff --git a/spec/ruby/core/io/new_spec.rb b/spec/ruby/core/io/new_spec.rb
new file mode 100644
index 0000000000..979ac0efcb
--- /dev/null
+++ b/spec/ruby/core/io/new_spec.rb
@@ -0,0 +1,18 @@
+require_relative '../../spec_helper'
+require_relative 'shared/new'
+
+# NOTE: should be synchronized with library/stringio/initialize_spec.rb
+
+describe "IO.new" do
+ it_behaves_like :io_new, :new
+
+ it "does not use the given block and warns to use IO::open" do
+ -> {
+ @io = IO.send(@method, @fd) { raise }
+ }.should complain(/warning: IO::new\(\) does not take block; use IO::open\(\) instead/)
+ end
+end
+
+describe "IO.new" do
+ it_behaves_like :io_new_errors, :new
+end
diff --git a/spec/ruby/core/io/nonblock_spec.rb b/spec/ruby/core/io/nonblock_spec.rb
new file mode 100644
index 0000000000..99dc0cafd0
--- /dev/null
+++ b/spec/ruby/core/io/nonblock_spec.rb
@@ -0,0 +1,48 @@
+require_relative '../../spec_helper'
+
+platform_is_not :windows do
+ describe "IO#nonblock?" do
+ before :all do
+ require 'io/nonblock'
+ end
+
+ it "returns false for a file by default" do
+ File.open(__FILE__) do |f|
+ f.nonblock?.should == false
+ end
+ end
+
+ it "returns true for pipe by default" do
+ r, w = IO.pipe
+ begin
+ r.nonblock?.should == true
+ w.nonblock?.should == true
+ ensure
+ r.close
+ w.close
+ end
+ end
+
+ it "returns true for socket by default" do
+ require 'socket'
+ TCPServer.open(0) do |socket|
+ socket.nonblock?.should == true
+ end
+ end
+ end
+
+ describe "IO#nonblock=" do
+ before :all do
+ require 'io/nonblock'
+ end
+
+ it "changes the IO to non-blocking mode" do
+ File.open(__FILE__) do |f|
+ f.nonblock = true
+ f.nonblock?.should == true
+ f.nonblock = false
+ f.nonblock?.should == false
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/open_spec.rb b/spec/ruby/core/io/open_spec.rb
new file mode 100644
index 0000000000..ff22d14685
--- /dev/null
+++ b/spec/ruby/core/io/open_spec.rb
@@ -0,0 +1,99 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/new'
+
+describe "IO.open" do
+ it_behaves_like :io_new, :open
+end
+
+describe "IO.open" do
+ it_behaves_like :io_new_errors, :open
+end
+
+# These specs use a special mock helper to avoid mock
+# methods from preventing IO#close from running and
+# which would prevent the file referenced by @fd from
+# being deleted on Windows.
+
+describe "IO.open" do
+ before :each do
+ @name = tmp("io_open.txt")
+ @fd = new_fd @name
+ ScratchPad.clear
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "calls #close after yielding to the block" do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ end
+ io.closed?.should == false
+ end
+ ScratchPad.recorded.should == :called
+ end
+
+ it "propagate an exception in the block after calling #close" do
+ -> do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ end
+ raise Exception
+ end
+ end.should.raise(Exception)
+ ScratchPad.recorded.should == :called
+ end
+
+ it "propagates an exception raised by #close that is not a StandardError" do
+ -> do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ raise Exception
+ end
+ end
+ end.should.raise(Exception)
+ ScratchPad.recorded.should == :called
+ end
+
+ it "propagates an exception raised by #close that is a StandardError" do
+ -> do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ raise StandardError
+ end
+ end
+ end.should.raise(StandardError)
+ ScratchPad.recorded.should == :called
+ end
+
+ it "does not propagate an IOError with 'closed stream' message raised by #close" do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ ScratchPad.record :called
+ raise IOError, 'closed stream'
+ end
+ end
+ ScratchPad.recorded.should == :called
+ end
+
+ it "does not set last error when an IOError with 'closed stream' raised by #close" do
+ IO.open(@fd, "w") do |io|
+ IOSpecs.io_mock(io, :close) do
+ super()
+ raise IOError, 'closed stream'
+ end
+ end
+ $!.should == nil
+ end
+end
diff --git a/spec/ruby/core/io/output_spec.rb b/spec/ruby/core/io/output_spec.rb
new file mode 100644
index 0000000000..0decf8c95b
--- /dev/null
+++ b/spec/ruby/core/io/output_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#<<" do
+ it "writes an object to the IO stream" do
+ -> {
+ $stderr << "Oh noes, an error!"
+ }.should output_to_fd("Oh noes, an error!", $stderr)
+ end
+
+ it "calls #to_s on the object to print it" do
+ -> {
+ $stderr << 1337
+ }.should output_to_fd("1337", $stderr)
+ end
+
+ it "raises an error if the stream is closed" do
+ io = IOSpecs.closed_io
+ -> { io << "test" }.should.raise(IOError)
+ end
+
+ it "returns self" do
+ -> {
+ ($stderr << "to_stderr").should == $stderr
+ }.should output(nil, "to_stderr")
+ end
+end
diff --git a/spec/ruby/core/io/path_spec.rb b/spec/ruby/core/io/path_spec.rb
new file mode 100644
index 0000000000..798adb2163
--- /dev/null
+++ b/spec/ruby/core/io/path_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+
+describe "IO#path" 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)
+ end
+end
diff --git a/spec/ruby/core/io/pid_spec.rb b/spec/ruby/core/io/pid_spec.rb
new file mode 100644
index 0000000000..04956887ff
--- /dev/null
+++ b/spec/ruby/core/io/pid_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#pid" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "returns nil for IO not associated with a process" do
+ @io.pid.should == nil
+ end
+end
+
+describe "IO#pid" do
+ before :each do
+ @io = IO.popen ruby_cmd('STDIN.read'), "r+"
+ end
+
+ after :each do
+ @io.close if @io && !@io.closed?
+ end
+
+ it "returns the ID of a process associated with stream" do
+ @io.pid.should_not == nil
+ end
+
+ it "raises an IOError on closed stream" do
+ @io.close
+ -> { @io.pid }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/pipe_spec.rb b/spec/ruby/core/io/pipe_spec.rb
new file mode 100644
index 0000000000..9f1b01a5cd
--- /dev/null
+++ b/spec/ruby/core/io/pipe_spec.rb
@@ -0,0 +1,225 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO.pipe" do
+ after :each do
+ @r.close if @r && !@r.closed?
+ @w.close if @w && !@w.closed?
+ end
+
+ it "creates a two-ended pipe" do
+ @r, @w = IO.pipe
+ @w.puts "test_create_pipe\\n"
+ @w.close
+ @r.read(16).should == "test_create_pipe"
+ end
+
+ it "returns two IO objects" do
+ @r, @w = IO.pipe
+ @r.should.is_a?(IO)
+ @w.should.is_a?(IO)
+ end
+
+ it "returns instances of a subclass when called on a subclass" do
+ @r, @w = IOSpecs::SubIO.pipe
+ @r.should.instance_of?(IOSpecs::SubIO)
+ @w.should.instance_of?(IOSpecs::SubIO)
+ end
+
+ it "does not use IO.new method to create pipes and allows its overriding" do
+ ScratchPad.record []
+
+ # so redefined .new is not called, but original #initialize is
+ @r, @w = IOSpecs::SubIOWithRedefinedNew.pipe
+ ScratchPad.recorded.should == [:call_original_initialize, :call_original_initialize] # called 2 times - for each pipe (r and w)
+
+ @r.should.instance_of?(IOSpecs::SubIOWithRedefinedNew)
+ @w.should.instance_of?(IOSpecs::SubIOWithRedefinedNew)
+ end
+end
+
+describe "IO.pipe" do
+ describe "passed a block" do
+ it "yields two IO objects" do
+ IO.pipe do |r, w|
+ r.should.is_a?(IO)
+ w.should.is_a?(IO)
+ end
+ end
+
+ it "returns the result of the block" do
+ IO.pipe { |r, w| :result }.should == :result
+ end
+
+ it "closes both IO objects" do
+ r, w = IO.pipe do |_r, _w|
+ [_r, _w]
+ end
+ r.should.closed?
+ w.should.closed?
+ end
+
+ it "closes both IO objects when the block raises" do
+ r = w = nil
+ -> do
+ IO.pipe do |_r, _w|
+ r = _r
+ w = _w
+ raise RuntimeError
+ end
+ end.should.raise(RuntimeError)
+ r.should.closed?
+ w.should.closed?
+ end
+
+ it "allows IO objects to be closed within the block" do
+ r, w = IO.pipe do |_r, _w|
+ _r.close
+ _w.close
+ [_r, _w]
+ end
+ r.should.closed?
+ w.should.closed?
+ end
+ end
+end
+
+describe "IO.pipe" do
+ before :each do
+ @default_external = Encoding.default_external
+ @default_internal = Encoding.default_internal
+ end
+
+ after :each do
+ Encoding.default_external = @default_external
+ Encoding.default_internal = @default_internal
+ end
+
+ it "sets the external encoding of the read end to the default when passed no arguments" do
+ Encoding.default_external = Encoding::ISO_8859_1
+
+ IO.pipe do |r, w|
+ r.external_encoding.should == Encoding::ISO_8859_1
+ r.internal_encoding.should == nil
+ end
+ end
+
+ it "sets the internal encoding of the read end to the default when passed no arguments" do
+ Encoding.default_external = Encoding::ISO_8859_1
+ Encoding.default_internal = Encoding::UTF_8
+
+ IO.pipe do |r, w|
+ r.external_encoding.should == Encoding::ISO_8859_1
+ r.internal_encoding.should == Encoding::UTF_8
+ end
+ end
+
+ it "sets the internal encoding to nil if the same as the external" do
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = Encoding::UTF_8
+
+ IO.pipe do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == nil
+ end
+ end
+
+ it "sets the external encoding of the read end when passed an Encoding argument" do
+ IO.pipe(Encoding::UTF_8) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == nil
+ end
+ end
+
+ it "sets the external and internal encodings of the read end when passed two Encoding arguments" do
+ IO.pipe(Encoding::UTF_8, Encoding::UTF_16BE) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::UTF_16BE
+ end
+ end
+
+ it "sets the external encoding of the read end when passed the name of an Encoding" do
+ IO.pipe("UTF-8") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == nil
+ end
+ end
+
+ it "accepts 'bom|' prefix for external encoding" do
+ IO.pipe("BOM|UTF-8") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == nil
+ end
+ end
+
+ it "sets the external and internal encodings specified as a String and separated with a colon" do
+ IO.pipe("UTF-8:ISO-8859-1") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ it "accepts 'bom|' prefix for external encoding when specifying 'external:internal'" do
+ IO.pipe("BOM|UTF-8:ISO-8859-1") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ it "sets the external and internal encoding when passed two String arguments" do
+ IO.pipe("UTF-8", "UTF-16BE") do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::UTF_16BE
+ end
+ end
+
+ it "accepts an options Hash with one String encoding argument" do
+ IO.pipe("BOM|UTF-8:ISO-8859-1", invalid: :replace) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ it "accepts an options Hash with two String encoding arguments" do
+ IO.pipe("UTF-8", "ISO-8859-1", invalid: :replace) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::ISO_8859_1
+ end
+ end
+
+ it "calls #to_hash to convert an options argument" do
+ options = mock("io pipe encoding options")
+ options.should_receive(:to_hash).and_return({ invalid: :replace })
+ IO.pipe("UTF-8", "ISO-8859-1", **options) { |r, w| }
+ end
+
+ it "calls #to_str to convert the first argument to a String" do
+ obj = mock("io_pipe_encoding")
+ obj.should_receive(:to_str).and_return("UTF-8:UTF-16BE")
+ IO.pipe(obj) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::UTF_16BE
+ end
+ end
+
+ it "calls #to_str to convert the second argument to a String" do
+ obj = mock("io_pipe_encoding")
+ obj.should_receive(:to_str).at_least(1).times.and_return("UTF-16BE")
+ IO.pipe(Encoding::UTF_8, obj) do |r, w|
+ r.external_encoding.should == Encoding::UTF_8
+ r.internal_encoding.should == Encoding::UTF_16BE
+ end
+ end
+
+ it "sets no external encoding for the write end" do
+ IO.pipe(Encoding::UTF_8) do |r, w|
+ w.external_encoding.should == nil
+ end
+ end
+
+ it "sets no internal encoding for the write end" do
+ IO.pipe(Encoding::UTF_8) do |r, w|
+ w.external_encoding.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/io/popen_spec.rb b/spec/ruby/core/io/popen_spec.rb
new file mode 100644
index 0000000000..b5747bf255
--- /dev/null
+++ b/spec/ruby/core/io/popen_spec.rb
@@ -0,0 +1,287 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../process/fixtures/common'
+
+describe "IO.popen" do
+ ProcessSpecs.use_system_ruby(self)
+
+ before :each do
+ @fname = tmp("IO_popen_spec")
+ @io = nil
+ @var = "$FOO"
+ platform_is :windows do
+ @var = "%FOO%"
+ end
+ end
+
+ after :each do
+ @io.close if @io and !@io.closed?
+ rm_r @fname
+ end
+
+ it "returns an open IO" do
+ @io = IO.popen(ruby_cmd('exit'), "r")
+ @io.closed?.should == false
+ end
+
+ it "reads a read-only pipe" do
+ @io = IO.popen('echo foo', "r")
+ @io.read.should == "foo\n"
+ end
+
+ it "raises IOError when writing a read-only pipe" do
+ @io = IO.popen('echo foo', "r")
+ -> { @io.write('bar') }.should.raise(IOError)
+ @io.read.should == "foo\n"
+ end
+
+ it "sees an infinitely looping subprocess exit when read pipe is closed" do
+ io = IO.popen ruby_cmd('r = loop{puts "y"; 0} rescue 1; exit r'), 'r'
+ io.close
+
+ $?.exitstatus.should_not == 0
+ end
+
+ it "writes to a write-only pipe" do
+ @io = IO.popen(ruby_cmd('IO.copy_stream(STDIN,STDOUT)', args: "> #{@fname}"), "w")
+ @io.write("bar")
+ @io.close
+
+ File.read(@fname).should == "bar"
+ end
+
+ it "raises IOError when reading a write-only pipe" do
+ @io = IO.popen(ruby_cmd('IO.copy_stream(STDIN,STDOUT)'), "w")
+ -> { @io.read }.should.raise(IOError)
+ end
+
+ it "reads and writes a read/write pipe" do
+ @io = IO.popen(ruby_cmd('IO.copy_stream(STDIN,STDOUT)'), "r+")
+ @io.write("bar")
+ @io.read(3).should == "bar"
+ end
+
+ it "waits for the child to finish" do
+ @io = IO.popen(ruby_cmd('IO.copy_stream(STDIN,STDOUT)', args: "> #{@fname}"), "w")
+ @io.write("bar")
+ @io.close
+
+ $?.exitstatus.should == 0
+
+ File.read(@fname).should == "bar"
+ end
+
+ it "does not throw an exception if child exited and has been waited for" do
+ @io = IO.popen([*ruby_exe, '-e', 'sleep'])
+ pid = @io.pid
+ Process.kill "KILL", pid
+ @io.close
+ platform_is_not :windows do
+ $?.should.signaled?
+ end
+ platform_is :windows do
+ $?.should.exited?
+ end
+ end
+
+ it "returns an instance of a subclass when called on a subclass" do
+ @io = IOSpecs::SubIO.popen(ruby_cmd('exit'), "r")
+ @io.should.instance_of?(IOSpecs::SubIO)
+ end
+
+ it "coerces mode argument with #to_str" do
+ mode = mock("mode")
+ mode.should_receive(:to_str).and_return("r")
+ @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|
+ io.closed?.should == false
+ end
+ end
+
+ it "yields an instance of a subclass when called on a subclass" do
+ IOSpecs::SubIO.popen(ruby_cmd('exit'), "r") do |io|
+ io.should.instance_of?(IOSpecs::SubIO)
+ end
+ end
+
+ it "closes the IO after yielding" do
+ io = IO.popen(ruby_cmd('exit'), "r") { |_io| _io }
+ io.closed?.should == true
+ end
+
+ it "allows the IO to be closed inside the block" do
+ io = IO.popen(ruby_cmd('exit'), 'r') { |_io| _io.close; _io }
+ io.closed?.should == true
+ end
+
+ it "returns the value of the block" do
+ IO.popen(ruby_cmd('exit'), "r") { :hello }.should == :hello
+ end
+ end
+
+ platform_is_not :windows do
+ it "starts returns a forked process if the command is -" do
+ io = IO.popen("-")
+
+ if io # parent
+ begin
+ io.gets.should == "hello from child\n"
+ ensure
+ io.close
+ end
+ else # child
+ puts "hello from child"
+ exit!
+ end
+ end
+ end
+
+ it "has the given external encoding" do
+ @io = IO.popen(ruby_cmd('exit'), external_encoding: Encoding::EUC_JP)
+ @io.external_encoding.should == Encoding::EUC_JP
+ end
+
+ it "has the given internal encoding" do
+ @io = IO.popen(ruby_cmd('exit'), internal_encoding: Encoding::EUC_JP)
+ @io.internal_encoding.should == Encoding::EUC_JP
+ end
+
+ it "sets the internal encoding to nil if it's the same as the external encoding" do
+ @io = IO.popen(ruby_cmd('exit'), external_encoding: Encoding::EUC_JP,
+ internal_encoding: Encoding::EUC_JP)
+ @io.internal_encoding.should == nil
+ end
+
+ context "with a leading ENV Hash" do
+ it "accepts a single String command" do
+ IO.popen({"FOO" => "bar"}, "echo #{@var}") do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts a single String command, and an IO mode" do
+ IO.popen({"FOO" => "bar"}, "echo #{@var}", "r") do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts a single String command with a trailing Hash of Process.exec options" do
+ IO.popen({"FOO" => "bar"}, ruby_cmd('STDERR.puts ENV["FOO"]'),
+ err: [:child, :out]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts a single String command with a trailing Hash of Process.exec options, and an IO mode" do
+ IO.popen({"FOO" => "bar"}, ruby_cmd('STDERR.puts ENV["FOO"]'), "r",
+ err: [:child, :out]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts an Array of command and arguments" do
+ exe, *args = ruby_exe
+ IO.popen({"FOO" => "bar"}, [[exe, "specfu"], *args, "-e", "puts ENV['FOO']"]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts an Array of command and arguments, and an IO mode" do
+ exe, *args = ruby_exe
+ IO.popen({"FOO" => "bar"}, [[exe, "specfu"], *args, "-e", "puts ENV['FOO']"], "r") do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts an Array command with a separate trailing Hash of Process.exec options" do
+ IO.popen({"FOO" => "bar"}, [*ruby_exe, "-e", "STDERR.puts ENV['FOO']"],
+ err: [:child, :out]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts an Array command with a separate trailing Hash of Process.exec options, and an IO mode" do
+ IO.popen({"FOO" => "bar"}, [*ruby_exe, "-e", "STDERR.puts ENV['FOO']"],
+ "r", err: [:child, :out]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+ end
+
+ context "with a leading Array argument" do
+ it "uses the Array as command plus args for the child process" do
+ IO.popen([*ruby_exe, "-e", "puts 'hello'"]) do |io|
+ io.read.should == "hello\n"
+ end
+ end
+
+ it "accepts a leading ENV Hash" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "puts ENV['FOO']"]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts a trailing Hash of Process.exec options" do
+ IO.popen([*ruby_exe, "does_not_exist", {err: [:child, :out]}]) do |io|
+ io.read.should =~ /LoadError/
+ end
+ end
+
+ it "accepts an IO mode argument following the Array" do
+ IO.popen([*ruby_exe, "does_not_exist", {err: [:child, :out]}], "r") do |io|
+ io.read.should =~ /LoadError/
+ end
+ end
+
+ it "accepts [env, command, arg1, arg2, ..., exec options]" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "STDERR.puts ENV[:FOO.to_s]",
+ err: [:child, :out]]) do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts '[env, command, arg1, arg2, ..., exec options], mode'" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "STDERR.puts ENV[:FOO.to_s]",
+ err: [:child, :out]], "r") do |io|
+ io.read.should == "bar\n"
+ end
+ end
+
+ it "accepts '[env, command, arg1, arg2, ..., exec options], mode, IO options'" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "STDERR.puts ENV[:FOO.to_s]",
+ err: [:child, :out]], "r",
+ internal_encoding: Encoding::EUC_JP) do |io|
+ io.read.should == "bar\n"
+ io.internal_encoding.should == Encoding::EUC_JP
+ end
+ end
+
+ it "accepts '[env, command, arg1, arg2, ...], mode, IO + exec options'" do
+ IO.popen([{"FOO" => "bar"}, *ruby_exe, "-e", "STDERR.puts ENV[:FOO.to_s]"], "r",
+ err: [:child, :out], internal_encoding: Encoding::EUC_JP) do |io|
+ io.read.should == "bar\n"
+ io.internal_encoding.should == Encoding::EUC_JP
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/pos_spec.rb b/spec/ruby/core/io/pos_spec.rb
new file mode 100644
index 0000000000..e6cda2643d
--- /dev/null
+++ b/spec/ruby/core/io/pos_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/pos'
+
+describe "IO#pos" do
+ it_behaves_like :io_pos, :pos
+end
+
+describe "IO#pos=" do
+ it_behaves_like :io_set_pos, :pos=
+end
diff --git a/spec/ruby/core/io/pread_spec.rb b/spec/ruby/core/io/pread_spec.rb
new file mode 100644
index 0000000000..cfb8dc4c68
--- /dev/null
+++ b/spec/ruby/core/io/pread_spec.rb
@@ -0,0 +1,138 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "IO#pread" do
+ before :each do
+ @fname = tmp("io_pread.txt")
+ @contents = "1234567890"
+ touch(@fname) { |f| f.write @contents }
+ @file = File.open(@fname, "r+")
+ end
+
+ after :each do
+ @file.close
+ rm_r @fname
+ end
+
+ it "accepts a length, and an offset" do
+ @file.pread(4, 0).should == "1234"
+ @file.pread(3, 4).should == "567"
+ end
+
+ it "accepts a length, an offset, and an output buffer" do
+ buffer = +"foo"
+ @file.pread(3, 4, buffer).should.equal?(buffer)
+ buffer.should == "567"
+ end
+
+ it "shrinks the buffer in case of less bytes read" do
+ buffer = +"foo"
+ @file.pread(1, 0, buffer)
+ buffer.should == "1"
+ end
+
+ it "grows the buffer in case of more bytes read" do
+ buffer = +"foo"
+ @file.pread(5, 0, buffer)
+ buffer.should == "12345"
+ end
+
+ it "preserves the encoding of the given buffer" do
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ @file.pread(10, 0, buffer)
+
+ buffer.encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "does not advance the file pointer" do
+ @file.pread(4, 0).should == "1234"
+ @file.read.should == "1234567890"
+ end
+
+ it "ignores the current offset" do
+ @file.pos = 3
+ @file.pread(4, 0).should == "1234"
+ end
+
+ it "returns an empty string for maxlen = 0" do
+ @file.pread(0, 4).should == ""
+ end
+
+ it "returns a buffer for maxlen = 0 when buffer specified" do
+ buffer = +"foo"
+ @file.pread(0, 4, buffer).should.equal?(buffer)
+ buffer.should == "foo"
+ end
+
+ it "ignores the offset for maxlen = 0, even if it is out of file bounds" do
+ @file.pread(0, 400).should == ""
+ end
+
+ it "does not reset the buffer when reading with maxlen = 0" do
+ buffer = +"foo"
+ @file.pread(0, 4, buffer)
+ buffer.should == "foo"
+
+ @file.pread(0, 400, buffer)
+ buffer.should == "foo"
+ end
+
+ it "converts maxlen to Integer using #to_int" do
+ maxlen = mock('maxlen')
+ maxlen.should_receive(:to_int).and_return(4)
+ @file.pread(maxlen, 0).should == "1234"
+ end
+
+ it "converts offset to Integer using #to_int" do
+ offset = mock('offset')
+ offset.should_receive(:to_int).and_return(0)
+ @file.pread(4, offset).should == "1234"
+ end
+
+ it "converts a buffer to String using to_str" do
+ buffer = mock('buffer')
+ buffer.should_receive(:to_str).at_least(1).and_return(+"foo")
+ @file.pread(4, 0, buffer)
+ buffer.should_not.is_a?(String)
+ buffer.to_str.should == "1234"
+ end
+
+ it "raises TypeError if maxlen is not an Integer and cannot be coerced into Integer" do
+ maxlen = Object.new
+ -> { @file.pread(maxlen, 0) }.should.raise(TypeError, 'no implicit conversion of Object into Integer')
+ end
+
+ it "raises TypeError if offset is not an Integer and cannot be coerced into Integer" do
+ offset = Object.new
+ -> { @file.pread(4, offset) }.should.raise(TypeError, 'no implicit conversion of Object into Integer')
+ end
+
+ it "raises ArgumentError for negative values of maxlen" do
+ -> { @file.pread(-4, 0) }.should.raise(ArgumentError, 'negative string size (or size too big)')
+ end
+
+ it "raised Errno::EINVAL for negative values of offset" do
+ -> { @file.pread(4, -1) }.should.raise(Errno::EINVAL, /Invalid argument/)
+ end
+
+ it "raises TypeError if the buffer is not a String and cannot be coerced into String" do
+ buffer = Object.new
+ -> { @file.pread(4, 0, buffer) }.should.raise(TypeError, 'no implicit conversion of Object into String')
+ end
+
+ it "raises EOFError if end-of-file is reached" do
+ -> { @file.pread(1, 10) }.should.raise(EOFError)
+ end
+
+ it "raises IOError when file is not open in read mode" do
+ File.open(@fname, "w") do |file|
+ -> { file.pread(1, 1) }.should.raise(IOError)
+ end
+ end
+
+ it "raises IOError when file is closed" do
+ file = File.open(@fname, "r+")
+ file.close
+ -> { file.pread(1, 1) }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/print_spec.rb b/spec/ruby/core/io/print_spec.rb
new file mode 100644
index 0000000000..065cb3b8cb
--- /dev/null
+++ b/spec/ruby/core/io/print_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#print" do
+ before :each do
+ @old_record_separator = $\
+ @old_field_separator = $,
+ suppress_warning {
+ $\ = '->'
+ $, = '^^'
+ }
+ @name = tmp("io_print")
+ end
+
+ after :each do
+ suppress_warning {
+ $\ = @old_record_separator
+ $, = @old_field_separator
+ }
+ rm_r @name
+ end
+
+ it "returns nil" do
+ touch(@name) { |f| f.print.should == nil }
+ end
+
+ it "writes $_.to_s followed by $\\ (if any) to the stream if no arguments given" do
+ o = mock('o')
+ o.should_receive(:to_s).and_return("mockmockmock")
+ $_ = o
+
+ touch(@name) { |f| f.print }
+ IO.read(@name).should == "mockmockmock#{$\}"
+
+ # Set $_ to something known
+ string = File.open(__FILE__) {|f| f.gets }
+
+ touch(@name) { |f| f.print }
+ IO.read(@name).should == "#{string}#{$\}"
+ end
+
+ it "calls obj.to_s and not obj.to_str then writes the record separator" do
+ o = mock('o')
+ o.should_not_receive(:to_str)
+ o.should_receive(:to_s).and_return("hello")
+
+ touch(@name) { |f| f.print(o) }
+
+ IO.read(@name).should == "hello#{$\}"
+ end
+
+ it "writes each obj.to_s to the stream separated by $, (if any) and appends $\\ (if any) given multiple objects" do
+ o, o2 = Object.new, Object.new
+ def o.to_s(); 'o'; end
+ def o2.to_s(); 'o2'; end
+
+ suppress_warning {
+ touch(@name) { |f| f.print(o, o2) }
+ }
+ IO.read(@name).should == "#{o.to_s}#{$,}#{o2.to_s}#{$\}"
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.print("stuff") }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/printf_spec.rb b/spec/ruby/core/io/printf_spec.rb
new file mode 100644
index 0000000000..d5519bdaa3
--- /dev/null
+++ b/spec/ruby/core/io/printf_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#printf" do
+ before :each do
+ @name = tmp("io_printf.txt")
+ @io = new_io @name
+ @io.sync = true
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "calls #to_str to convert the format object to a String" do
+ obj = mock("printf format")
+ obj.should_receive(:to_str).and_return("%s")
+
+ @io.printf obj, "printf"
+ File.read(@name).should == "printf"
+ end
+
+ it "writes the #sprintf formatted string" do
+ @io.printf "%d %s", 5, "cookies"
+ File.read(@name).should == "5 cookies"
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.printf("stuff") }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/putc_spec.rb b/spec/ruby/core/io/putc_spec.rb
new file mode 100644
index 0000000000..73473ad821
--- /dev/null
+++ b/spec/ruby/core/io/putc_spec.rb
@@ -0,0 +1,11 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/io/putc'
+
+describe "IO#putc" do
+ before :each do
+ @name = tmp("io_putc.txt")
+ @io_object = @io = new_io(@name)
+ end
+
+ it_behaves_like :io_putc, :putc
+end
diff --git a/spec/ruby/core/io/puts_spec.rb b/spec/ruby/core/io/puts_spec.rb
new file mode 100644
index 0000000000..df526ea56a
--- /dev/null
+++ b/spec/ruby/core/io/puts_spec.rb
@@ -0,0 +1,139 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#puts" do
+ before :each do
+ @before_separator = $/
+ @name = tmp("io_puts.txt")
+ @io = new_io @name
+ ScratchPad.record(+"")
+ def @io.write(str)
+ ScratchPad << str
+ end
+ end
+
+ after :each do
+ ScratchPad.clear
+ @io.close if @io
+ rm_r @name
+ suppress_warning {$/ = @before_separator}
+ end
+
+ it "writes just a newline when given no args" do
+ @io.puts.should == nil
+ ScratchPad.recorded.should == "\n"
+ end
+
+ it "writes just a newline when given just a newline" do
+ -> { $stdout.puts "\n" }.should output_to_fd("\n", $stdout)
+ end
+
+ it "writes empty string with a newline when given nil as an arg" do
+ @io.puts(nil).should == nil
+ ScratchPad.recorded.should == "\n"
+ end
+
+ 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
+
+ it "calls :to_ary before writing non-string objects, regardless of it being implemented in the receiver" do
+ object = mock('hola')
+ object.should_receive(:method_missing).with(:to_ary)
+ object.should_receive(:to_s).and_return("#<Object:0x...>")
+
+ @io.puts(object).should == nil
+ ScratchPad.recorded.should == "#<Object:0x...>\n"
+ end
+
+ it "calls :to_ary before writing non-string objects" do
+ object = mock('hola')
+ object.should_receive(:to_ary).and_return(["hola"])
+
+ @io.puts(object).should == nil
+ ScratchPad.recorded.should == "hola\n"
+ end
+
+ it "calls :to_s before writing non-string objects that don't respond to :to_ary" do
+ object = mock('hola')
+ object.should_receive(:to_s).and_return("hola")
+
+ @io.puts(object).should == nil
+ ScratchPad.recorded.should == "hola\n"
+ end
+
+ it "returns general object info if :to_s does not return a string" do
+ object = mock('hola')
+ object.should_receive(:to_s).and_return(false)
+
+ @io.puts(object).should == nil
+ ScratchPad.recorded.should == object.inspect.split(" ")[0] + ">\n"
+ end
+
+ it "writes each arg if given several" do
+ @io.puts(1, "two", 3).should == nil
+ ScratchPad.recorded.should == "1\ntwo\n3\n"
+ end
+
+ it "flattens a nested array before writing it" do
+ @io.puts([1, 2, [3]]).should == nil
+ ScratchPad.recorded.should == "1\n2\n3\n"
+ end
+
+ it "writes nothing for an empty array" do
+ x = []
+ @io.should_not_receive(:write)
+ @io.puts(x).should == nil
+ end
+
+ it "writes [...] for a recursive array arg" do
+ x = []
+ x << 2 << x
+ @io.puts(x).should == nil
+ ScratchPad.recorded.should == "2\n[...]\n"
+ end
+
+ it "writes a newline after objects that do not end in newlines" do
+ @io.puts(5).should == nil
+ ScratchPad.recorded.should == "5\n"
+ end
+
+ it "does not write a newline after objects that end in newlines" do
+ @io.puts("5\n").should == nil
+ ScratchPad.recorded.should == "5\n"
+ end
+
+ it "ignores the $/ separator global" do
+ suppress_warning {$/ = ":"}
+ @io.puts(5).should == nil
+ ScratchPad.recorded.should == "5\n"
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.puts("stuff") }.should.raise(IOError)
+ end
+
+ it "writes crlf when IO is opened with newline: :crlf" do
+ File.open(@name, 'wt', newline: :crlf) do |file|
+ file.puts
+ end
+ File.binread(@name).should == "\r\n"
+ end
+
+ it "writes cr when IO is opened with newline: :cr" do
+ File.open(@name, 'wt', newline: :cr) do |file|
+ file.puts
+ end
+ File.binread(@name).should == "\r"
+ end
+
+ platform_is_not :windows do # https://bugs.ruby-lang.org/issues/12436
+ it "writes lf when IO is opened with newline: :lf" do
+ File.open(@name, 'wt', newline: :lf) do |file|
+ file.puts
+ end
+ File.binread(@name).should == "\n"
+ end
+ end
+end
diff --git a/spec/ruby/core/io/pwrite_spec.rb b/spec/ruby/core/io/pwrite_spec.rb
new file mode 100644
index 0000000000..c318d551bc
--- /dev/null
+++ b/spec/ruby/core/io/pwrite_spec.rb
@@ -0,0 +1,67 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+
+describe "IO#pwrite" do
+ before :each do
+ @fname = tmp("io_pwrite.txt")
+ @file = File.open(@fname, "w+")
+ end
+
+ after :each do
+ @file.close
+ rm_r @fname
+ end
+
+ it "returns the number of bytes written" do
+ @file.pwrite("foo", 0).should == 3
+ end
+
+ it "accepts a string and an offset" do
+ @file.pwrite("foo", 2)
+ @file.pread(3, 2).should == "foo"
+ end
+
+ it "does not advance the pointer in the file" do
+ @file.pwrite("bar", 3)
+ @file.write("foo")
+ @file.pread(6, 0).should == "foobar"
+ end
+
+ it "calls #to_s on the object to be written" do
+ object = mock("to_s")
+ object.should_receive(:to_s).and_return("foo")
+ @file.pwrite(object, 0)
+ @file.pread(3, 0).should == "foo"
+ end
+
+ it "calls #to_int on the offset" do
+ offset = mock("to_int")
+ offset.should_receive(:to_int).and_return(2)
+ @file.pwrite("foo", offset)
+ @file.pread(3, 2).should == "foo"
+ end
+
+ it "raises IOError when file is not open in write mode" do
+ File.open(@fname, "r") do |file|
+ -> { file.pwrite("foo", 1) }.should.raise(IOError, "not opened for writing")
+ end
+ end
+
+ it "raises IOError when file is closed" do
+ file = File.open(@fname, "w+")
+ file.close
+ -> { file.pwrite("foo", 1) }.should.raise(IOError, "closed stream")
+ end
+
+ it "raises a NoMethodError if object does not respond to #to_s" do
+ -> {
+ @file.pwrite(BasicObject.new, 0)
+ }.should.raise(NoMethodError, /undefined method [`']to_s'/)
+ end
+
+ it "raises a TypeError if the offset cannot be converted to an Integer" do
+ -> {
+ @file.pwrite("foo", Object.new)
+ }.should.raise(TypeError, "no implicit conversion of Object into Integer")
+ end
+end
diff --git a/spec/ruby/core/io/read_nonblock_spec.rb b/spec/ruby/core/io/read_nonblock_spec.rb
new file mode 100644
index 0000000000..511cf03263
--- /dev/null
+++ b/spec/ruby/core/io/read_nonblock_spec.rb
@@ -0,0 +1,148 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#read_nonblock" do
+ before :each do
+ @read, @write = IO.pipe
+ end
+
+ after :each do
+ @read.close if @read && !@read.closed?
+ @write.close if @write && !@write.closed?
+ end
+
+ it "raises an exception extending IO::WaitReadable when there is no data" do
+ -> { @read.read_nonblock(5) }.should.raise(IO::WaitReadable) { |e|
+ platform_is_not :windows do
+ e.should.is_a?(Errno::EAGAIN)
+ end
+ platform_is :windows do
+ e.should.is_a?(Errno::EWOULDBLOCK)
+ end
+ }
+ end
+
+ context "when exception option is set to false" do
+ context "when there is no data" do
+ it "returns :wait_readable" do
+ @read.read_nonblock(5, exception: false).should == :wait_readable
+ end
+ end
+
+ context "when the end is reached" do
+ it "returns nil" do
+ @write << "hello"
+ @write.close
+
+ @read.read_nonblock(5)
+
+ @read.read_nonblock(5, exception: false).should == nil
+ end
+ end
+ end
+
+ platform_is_not :windows do
+ it 'sets the IO in nonblock mode' do
+ require 'io/nonblock'
+ @write.write "abc"
+ @read.read_nonblock(1).should == "a"
+ @read.should.nonblock?
+ end
+ end
+
+ it "returns at most the number of bytes requested" do
+ @write << "hello"
+ @read.read_nonblock(4).should == "hell"
+ end
+
+ it "reads after ungetc with data in the buffer" do
+ @write.write("foobar")
+ @read.set_encoding(
+ 'utf-8', universal_newline: false
+ )
+ c = @read.getc
+ @read.ungetc(c)
+ @read.read_nonblock(3).should == "foo"
+ @read.read_nonblock(3).should == "bar"
+ end
+
+ it "raises an exception after ungetc with data in the buffer and character conversion enabled" do
+ @write.write("foobar")
+ @read.set_encoding(
+ 'utf-8', universal_newline: true
+ )
+ c = @read.getc
+ @read.ungetc(c)
+ -> { @read.read_nonblock(3).should == "foo" }.should.raise(IOError)
+ end
+
+ it "returns less data if that is all that is available" do
+ @write << "hello"
+ @read.read_nonblock(10).should == "hello"
+ end
+
+ it "allows for reading 0 bytes before any write" do
+ @read.read_nonblock(0).should == ""
+ end
+
+ it "allows for reading 0 bytes after a write" do
+ @write.write "1"
+ @read.read_nonblock(0).should == ""
+ @read.read_nonblock(1).should == "1"
+ end
+
+ it "raises ArgumentError when length is less than 0" do
+ -> { @read.read_nonblock(-1) }.should.raise(ArgumentError)
+ end
+
+ it "reads into the passed buffer" do
+ buffer = +""
+ @write.write("1")
+ @read.read_nonblock(1, buffer)
+ buffer.should == "1"
+ end
+
+ it "returns the passed buffer" do
+ buffer = +""
+ @write.write("1")
+ output = @read.read_nonblock(1, buffer)
+ output.should.equal?(buffer)
+ end
+
+ it "discards the existing buffer content upon successful read" do
+ buffer = +"existing content"
+ @write.write("hello world")
+ @write.close
+ @read.read_nonblock(11, buffer)
+ buffer.should == "hello world"
+ end
+
+ it "discards the existing buffer content upon error" do
+ buffer = +"existing content"
+ @write.close
+ -> { @read.read_nonblock(1, buffer) }.should.raise(EOFError)
+ buffer.should.empty?
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.read_nonblock(5) }.should.raise(IOError)
+ end
+
+ it "raises EOFError when the end is reached" do
+ @write << "hello"
+ @write.close
+
+ @read.read_nonblock(5)
+
+ -> { @read.read_nonblock(5) }.should.raise(EOFError)
+ end
+
+ it "preserves the encoding of the given buffer" do
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ @write.write("abc")
+ @write.close
+ @read.read_nonblock(10, buffer)
+
+ buffer.encoding.should == Encoding::ISO_8859_1
+ end
+end
diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb
new file mode 100644
index 0000000000..dd787c9b60
--- /dev/null
+++ b/spec/ruby/core/io/read_spec.rb
@@ -0,0 +1,735 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO.read" do
+ before :each do
+ @fname = tmp("io_read.txt")
+ @contents = "1234567890"
+ touch(@fname) { |f| f.write @contents }
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "reads the contents of a file" do
+ IO.read(@fname).should == @contents
+ end
+
+ it "calls #to_path on non-String arguments" do
+ p = mock('path')
+ p.should_receive(:to_path).and_return(@fname)
+ IO.read(p)
+ end
+
+ # https://bugs.ruby-lang.org/issues/19354
+ it "accepts options as keyword arguments" do
+ IO.read(@fname, 3, 0, mode: "r+").should == @contents[0, 3]
+
+ -> {
+ IO.read(@fname, 3, 0, {mode: "r+"})
+ }.should.raise(ArgumentError, /wrong number of arguments/)
+ end
+
+ it "accepts an empty options Hash" do
+ IO.read(@fname, **{}).should == @contents
+ end
+
+ it "accepts a length, and empty options Hash" do
+ IO.read(@fname, 3, **{}).should == @contents[0, 3]
+ end
+
+ it "accepts a length, offset, and empty options Hash" do
+ IO.read(@fname, 3, 0, **{}).should == @contents[0, 3]
+ end
+
+ it "raises an IOError if the options Hash specifies write mode" do
+ -> { IO.read(@fname, 3, 0, mode: "w") }.should.raise(IOError)
+ end
+
+ it "raises an IOError if the options Hash specifies append only mode" do
+ -> { IO.read(@fname, mode: "a") }.should.raise(IOError)
+ end
+
+ it "reads the file if the options Hash includes read mode" do
+ IO.read(@fname, mode: "r").should == @contents
+ end
+
+ it "reads the file if the options Hash includes read/write mode" do
+ IO.read(@fname, mode: "r+").should == @contents
+ end
+
+ it "reads the file if the options Hash includes read/write append mode" do
+ IO.read(@fname, mode: "a+").should == @contents
+ end
+
+ platform_is_not :windows do
+ end
+
+ it "disregards other options if :open_args is given" do
+ string = IO.read(@fname,mode: "w", encoding: Encoding::UTF_32LE, open_args: ["r", encoding: Encoding::UTF_8])
+ string.encoding.should == Encoding::UTF_8
+ end
+
+ it "doesn't require mode to be specified in :open_args" do
+ string = IO.read(@fname, nil, 0, open_args: [{encoding: Encoding::US_ASCII}])
+ string.encoding.should == Encoding::US_ASCII
+ end
+
+ it "doesn't require mode to be specified in :open_args even if flags option passed" do
+ string = IO.read(@fname, nil, 0, open_args: [{encoding: Encoding::US_ASCII, flags: File::CREAT}])
+ string.encoding.should == Encoding::US_ASCII
+ end
+
+ it "treats second nil argument as no length limit" do
+ IO.read(@fname, nil).should == @contents
+ IO.read(@fname, nil, 5).should == IO.read(@fname, @contents.length, 5)
+ end
+
+ it "treats third nil argument as 0" do
+ IO.read(@fname, nil, nil).should == @contents
+ IO.read(@fname, 5, nil).should == IO.read(@fname, 5, 0)
+ end
+
+ it "reads the contents of a file up to a certain size when specified" do
+ IO.read(@fname, 5).should == @contents.slice(0..4)
+ end
+
+ it "reads the contents of a file from an offset of a specific size when specified" do
+ IO.read(@fname, 5, 3).should == @contents.slice(3, 5)
+ end
+
+ it "returns nil at end-of-file when length is passed" do
+ IO.read(@fname, 1, 10).should == nil
+ end
+
+ it "returns an empty string when reading zero bytes" do
+ IO.read(@fname, 0).should == ''
+ end
+
+ it "returns a String in BINARY when passed a size" do
+ IO.read(@fname, 1).encoding.should == Encoding::BINARY
+ IO.read(@fname, 0).encoding.should == Encoding::BINARY
+ end
+
+ it "raises an Errno::ENOENT when the requested file does not exist" do
+ rm_r @fname
+ -> { IO.read @fname }.should.raise(Errno::ENOENT)
+ end
+
+ it "raises a TypeError when not passed a String type" do
+ -> { IO.read nil }.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError when not passed a valid length" do
+ -> { IO.read @fname, -1 }.should.raise(ArgumentError)
+ end
+
+ it "raises an ArgumentError when not passed a valid offset" do
+ -> { IO.read @fname, 0, -1 }.should.raise(ArgumentError)
+ -> { IO.read @fname, -1, -1 }.should.raise(ArgumentError)
+ end
+
+ it "uses the external encoding specified via the :external_encoding option" do
+ str = IO.read(@fname, external_encoding: Encoding::ISO_8859_1)
+ str.encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "uses the external encoding specified via the :encoding option" do
+ str = IO.read(@fname, encoding: Encoding::ISO_8859_1)
+ str.encoding.should == Encoding::ISO_8859_1
+ end
+
+ platform_is :windows do
+ it "reads the file in text mode" do
+ # 0x1A is CTRL+Z and is EOF in Windows text mode.
+ File.binwrite(@fname, "\x1Abbb")
+ IO.read(@fname).should.empty?
+ end
+ 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
+
+ suppress_warning do # https://bugs.ruby-lang.org/issues/19630
+ IO.read(cmd).should == "hello\n"
+ end
+ 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("|-")
+ end
+
+ if str # parent
+ str.should == "hello from child\n"
+ else #child
+ puts "hello from child"
+ exit!
+ 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
+
+ suppress_warning do # https://bugs.ruby-lang.org/issues/19630
+ IO.read(cmd, 1).should == "h"
+ 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(Errno::ESPIPE)
+ 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)
+ end
+ }.should.raise(Errno::EINVAL)
+ end
+ end
+ end
+
+ # 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
+
+describe "IO.read on an empty file" do
+ before :each do
+ @fname = tmp("io_read_empty.txt")
+ touch(@fname)
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "returns nil when length is passed" do
+ IO.read(@fname, 1).should == nil
+ end
+
+ it "returns an empty string when no length is passed" do
+ IO.read(@fname).should == ""
+ end
+end
+
+describe "IO#read" do
+
+ before :each do
+ @fname = tmp("io_read.txt")
+ @contents = "1234567890"
+ touch(@fname) { |f| f.write @contents }
+
+ @io = open @fname, "r+"
+ end
+
+ after :each do
+ @io.close
+ rm_r @fname
+ end
+
+ it "can be read from consecutively" do
+ @io.read(1).should == '1'
+ @io.read(2).should == '23'
+ @io.read(3).should == '456'
+ @io.read(4).should == '7890'
+ end
+
+ it "treats first nil argument as no length limit" do
+ @io.read(nil).should == @contents
+ end
+
+ it "raises an ArgumentError when not passed a valid length" do
+ -> { @io.read(-1) }.should.raise(ArgumentError)
+ end
+
+ it "clears the output buffer if there is nothing to read" do
+ @io.pos = 10
+
+ buf = +'non-empty string'
+
+ @io.read(10, buf).should == nil
+
+ buf.should == ''
+
+ buf = +'non-empty string'
+
+ @io.read(nil, buf).should == ""
+
+ buf.should == ''
+
+ buf = +'non-empty string'
+
+ @io.read(0, buf).should == ""
+
+ buf.should == ''
+ end
+
+ it "raise FrozenError if the output buffer is frozen" do
+ @io.read
+ -> { @io.read(0, 'frozen-string'.freeze) }.should.raise(FrozenError)
+ -> { @io.read(1, 'frozen-string'.freeze) }.should.raise(FrozenError)
+ -> { @io.read(nil, 'frozen-string'.freeze) }.should.raise(FrozenError)
+ end
+
+ it "raise FrozenError if the output buffer is frozen (2)" do
+ @io.read
+ -> { @io.read(1, ''.freeze) }.should.raise(FrozenError)
+ end
+
+ it "consumes zero bytes when reading zero bytes" do
+ @io.read(0).should == ''
+ @io.pos.should == 0
+
+ @io.getc.should == '1'
+ end
+
+ it "is at end-of-file when everything has been read" do
+ @io.read
+ @io.should.eof?
+ end
+
+ it "reads the contents of a file" do
+ @io.read.should == @contents
+ end
+
+ it "places the specified number of bytes in the buffer" do
+ buf = +""
+ @io.read 5, buf
+
+ buf.should == "12345"
+ end
+
+ it "expands the buffer when too small" do
+ buf = +"ABCDE"
+ @io.read nil, buf
+
+ buf.should == @contents
+ end
+
+ it "overwrites the buffer" do
+ buf = +"ABCDEFGHIJ"
+ @io.read nil, buf
+
+ buf.should == @contents
+ end
+
+ it "truncates the buffer when too big" do
+ buf = +"ABCDEFGHIJKLMNO"
+ @io.read nil, buf
+ buf.should == @contents
+
+ @io.rewind
+
+ buf = +"ABCDEFGHIJKLMNO"
+ @io.read 5, buf
+ buf.should == @contents[0..4]
+ end
+
+ it "preserves the encoding of the given buffer" do
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ @io.read(10, buffer)
+
+ buffer.encoding.should == Encoding::ISO_8859_1
+ end
+
+ # https://bugs.ruby-lang.org/issues/20416
+ it "does not preserve the encoding of the given buffer when max length is not provided" do
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ @io.read(nil, buffer)
+
+ buffer.encoding.should_not == Encoding::ISO_8859_1
+ end
+
+ it "returns the given buffer" do
+ buf = +""
+
+ @io.read(nil, buf).should.equal? buf
+ end
+
+ it "returns the given buffer when there is nothing to read" do
+ buf = +""
+
+ @io.read
+ @io.read(nil, buf).should.equal? buf
+ end
+
+ it "coerces the second argument to string and uses it as a buffer" do
+ buf = +"ABCDE"
+ obj = mock("buff")
+ obj.should_receive(:to_str).any_number_of_times.and_return(buf)
+
+ @io.read(15, obj).should_not.equal? obj
+ buf.should == @contents
+ end
+
+ it "returns an empty string at end-of-file" do
+ @io.read
+ @io.read.should == ''
+ end
+
+ it "reads the contents of a file when more bytes are specified" do
+ @io.read(@contents.length + 1).should == @contents
+ end
+
+ it "returns an empty string at end-of-file" do
+ @io.read
+ @io.read.should == ''
+ end
+
+ it "returns an empty string when the current pos is bigger than the content size" do
+ @io.pos = 1000
+ @io.read.should == ''
+ end
+
+ it "returns nil at end-of-file with a length" do
+ @io.read
+ @io.read(1).should == nil
+ end
+
+ it "with length argument returns nil when the current pos is bigger than the content size" do
+ @io.pos = 1000
+ @io.read(1).should == nil
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.read }.should.raise(IOError)
+ end
+
+ it "raises ArgumentError when length is less than 0" do
+ -> { @io.read(-1) }.should.raise(ArgumentError)
+ end
+
+ platform_is_not :windows do
+ it "raises IOError when stream is closed by another thread" do
+ r, w = IO.pipe
+ t = Thread.new do
+ begin
+ r.read(1)
+ rescue => e
+ e
+ end
+ end
+
+ Thread.pass until t.stop?
+ r.close
+ t.join
+ t.value.should.is_a?(IOError)
+ w.close
+ end
+ end
+end
+
+platform_is :windows do
+ describe "IO#read on Windows" do
+ before :each do
+ @fname = tmp("io_read.txt")
+ touch(@fname, "wb") { |f| f.write "a\r\nb\r\nc" }
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @fname
+ end
+
+ it "normalizes line endings in text mode" do
+ @io = new_io(@fname, "r")
+ @io.read.should == "a\nb\nc"
+ end
+
+ it "does not normalize line endings in binary mode" do
+ @io = new_io(@fname, "rb")
+ @io.read.should == "a\r\nb\r\nc"
+ end
+ end
+end
+
+describe "IO#read" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ it "ignores unicode encoding" do
+ @io.readline.should == "Voici la ligne une.\n"
+ # read "Qui è"
+ @io.read(5).should == "Qui " + [195].pack('C*')
+ end
+end
+
+describe "IO#read in binary mode" do
+ before :each do
+ @internal = Encoding.default_internal
+ @name = fixture __FILE__, "read_binary.txt"
+ end
+
+ after :each do
+ Encoding.default_internal = @internal
+ end
+
+ it "does not transcode file contents when Encoding.default_internal is set" do
+ Encoding.default_internal = "utf-8"
+
+ result = File.open(@name, "rb") { |f| f.read }.chomp
+
+ result.encoding.should == Encoding::BINARY
+ xE2 = [226].pack('C*')
+ result.should == ("abc" + xE2 + "def").force_encoding(Encoding::BINARY)
+ end
+end
+
+describe "IO#read in text mode" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ @name = fixture __FILE__, "read_text.txt"
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "reads data according to the internal encoding" do
+ Encoding.default_internal = "utf-8"
+ Encoding.default_external = "utf-8"
+
+ result = File.open(@name, "rt") { |f| f.read }.chomp
+
+ result.encoding.should == Encoding::UTF_8
+ result.should == "abcâdef"
+ end
+end
+
+describe "IO.read with BOM" do
+ it "reads a file without a bom" do
+ name = fixture __FILE__, "no_bom_UTF-8.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "UTF-8\n"
+ end
+
+ it "reads a file with a utf-8 bom" do
+ name = fixture __FILE__, "bom_UTF-8.txt"
+ result = File.read(name, mode: "rb:BOM|utf-16le")
+ result.force_encoding("binary").should == "UTF-8\n"
+ end
+
+ it "reads a file with a utf-16le bom" do
+ name = fixture __FILE__, "bom_UTF-16LE.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "U\x00T\x00F\x00-\x001\x006\x00L\x00E\x00\n\x00"
+ end
+
+ it "reads a file with a utf-16be bom" do
+ name = fixture __FILE__, "bom_UTF-16BE.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "\x00U\x00T\x00F\x00-\x001\x006\x00B\x00E\x00\n"
+ end
+
+ it "reads a file with a utf-32le bom" do
+ name = fixture __FILE__, "bom_UTF-32LE.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "U\x00\x00\x00T\x00\x00\x00F\x00\x00\x00-\x00\x00\x003\x00\x00\x002\x00\x00\x00L\x00\x00\x00E\x00\x00\x00\n\x00\x00\x00"
+ end
+
+ it "reads a file with a utf-32be bom" do
+ name = fixture __FILE__, "bom_UTF-32BE.txt"
+ result = File.read(name, mode: "rb:BOM|utf-8")
+ result.force_encoding("binary").should == "\x00\x00\x00U\x00\x00\x00T\x00\x00\x00F\x00\x00\x00-\x00\x00\x003\x00\x00\x002\x00\x00\x00B\x00\x00\x00E\x00\x00\x00\n"
+ end
+end
+
+describe :io_read_internal_encoding, shared: true do
+ it "returns a transcoded String" do
+ @io.read.should == "ありがとう\n"
+ end
+
+ it "sets the String encoding to the internal encoding" do
+ @io.read.encoding.should.equal?(Encoding::UTF_8)
+ end
+
+ describe "when passed nil for limit" do
+ it "sets the buffer to a transcoded String" do
+ result = @io.read(nil, buf = +"")
+ buf.should.equal?(result)
+ buf.should == "ありがとう\n"
+ end
+
+ it "sets the buffer's encoding to the internal encoding" do
+ buf = "".dup.force_encoding Encoding::ISO_8859_1
+ @io.read(nil, buf)
+ buf.encoding.should.equal?(Encoding::UTF_8)
+ end
+ end
+end
+
+describe :io_read_size_internal_encoding, shared: true do
+ it "reads bytes when passed a size" do
+ @io.read(2).should == [164, 162].pack('C*').force_encoding(Encoding::BINARY)
+ end
+
+ it "returns a String in BINARY when passed a size" do
+ @io.read(4).encoding.should.equal?(Encoding::BINARY)
+ @io.read(0).encoding.should.equal?(Encoding::BINARY)
+ end
+
+ it "does not change the buffer's encoding when passed a limit" do
+ buf = "".dup.force_encoding Encoding::ISO_8859_1
+ @io.read(4, buf)
+ buf.should == [164, 162, 164, 234].pack('C*').force_encoding(Encoding::ISO_8859_1)
+ buf.encoding.should.equal?(Encoding::ISO_8859_1)
+ end
+
+ it "truncates the buffer but does not change the buffer's encoding when no data remains" do
+ buf = "abc".dup.force_encoding Encoding::ISO_8859_1
+ @io.read
+
+ @io.read(1, buf).should == nil
+ buf.size.should == 0
+ buf.encoding.should.equal?(Encoding::ISO_8859_1)
+ end
+end
+
+describe "IO#read" do
+ describe "when IO#external_encoding and IO#internal_encoding are nil" do
+ before :each do
+ @name = tmp("io_read.txt")
+ touch(@name) { |f| f.write "\x00\x01\x02" }
+ @io = new_io @name, "r+"
+ end
+
+ after :each do
+ @io.close if @io
+ rm_r @name
+ end
+
+ it "sets the String encoding to Encoding.default_external" do
+ @io.read.encoding.should.equal?(Encoding.default_external)
+ end
+ end
+
+ describe "with internal encoding" do
+ after :each do
+ @io.close if @io
+ end
+
+ describe "not specified" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp"
+ end
+
+ it "does not transcode the String" do
+ @io.read.should == ("ありがとう\n").encode(Encoding::EUC_JP)
+ end
+
+ it "sets the String encoding to the external encoding" do
+ @io.read.encoding.should.equal?(Encoding::EUC_JP)
+ end
+
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+
+ describe "specified by open mode" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp:utf-8"
+ end
+
+ it_behaves_like :io_read_internal_encoding, nil
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+
+ describe "specified by mode: option" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", mode: "r:euc-jp:utf-8"
+ end
+
+ it_behaves_like :io_read_internal_encoding, nil
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+
+ describe "specified by internal_encoding: option" do
+ before :each do
+ options = { mode: "r",
+ internal_encoding: "utf-8",
+ external_encoding: "euc-jp" }
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", options
+ end
+
+ it_behaves_like :io_read_internal_encoding, nil
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+
+ describe "specified by encoding: option" do
+ before :each do
+ options = { mode: "r", encoding: "euc-jp:utf-8" }
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", options
+ end
+
+ it_behaves_like :io_read_internal_encoding, nil
+ it_behaves_like :io_read_size_internal_encoding, nil
+ end
+ end
+end
+
+describe "IO#read with large data" do
+ before :each do
+ # TODO: what is the significance of this mystery math?
+ @data_size = 8096 * 2 + 1024
+ @data = "*" * @data_size
+
+ @fname = tmp("io_read.txt")
+ touch(@fname) { |f| f.write @data }
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "reads all the data at once" do
+ File.open(@fname, 'r') { |io| ScratchPad.record io.read }
+
+ ScratchPad.recorded.size.should == @data_size
+ ScratchPad.recorded.should == @data
+ end
+
+ it "reads only the requested number of bytes" do
+ read_size = @data_size / 2
+ File.open(@fname, 'r') { |io| ScratchPad.record io.read(read_size) }
+
+ ScratchPad.recorded.size.should == read_size
+ ScratchPad.recorded.should == @data[0, read_size]
+ end
+end
diff --git a/spec/ruby/core/io/readbyte_spec.rb b/spec/ruby/core/io/readbyte_spec.rb
new file mode 100644
index 0000000000..07da1da919
--- /dev/null
+++ b/spec/ruby/core/io/readbyte_spec.rb
@@ -0,0 +1,24 @@
+require_relative '../../spec_helper'
+
+describe "IO#readbyte" do
+ before :each do
+ @io = File.open(__FILE__, 'r')
+ end
+
+ after :each do
+ @io.close
+ end
+
+ it "reads one byte from the stream" do
+ byte = @io.readbyte
+ byte.should == ?r.getbyte(0)
+ @io.pos.should == 1
+ end
+
+ it "raises EOFError on EOF" do
+ @io.seek(999999)
+ -> do
+ @io.readbyte
+ end.should.raise EOFError
+ end
+end
diff --git a/spec/ruby/core/io/readchar_spec.rb b/spec/ruby/core/io/readchar_spec.rb
new file mode 100644
index 0000000000..29d14880ff
--- /dev/null
+++ b/spec/ruby/core/io/readchar_spec.rb
@@ -0,0 +1,110 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe :io_readchar_internal_encoding, shared: true do
+ it "returns a transcoded String" do
+ @io.readchar.should == "あ"
+ end
+
+ it "sets the String encoding to the internal encoding" do
+ @io.readchar.encoding.should.equal?(Encoding::UTF_8)
+ end
+end
+
+describe "IO#readchar" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns the next string from the stream" do
+ @io.readchar.should == 'V'
+ @io.readchar.should == 'o'
+ @io.readchar.should == 'i'
+ # read the rest of line
+ @io.readline.should == "ci la ligne une.\n"
+ @io.readchar.should == 'Q'
+ end
+
+ it "raises an EOFError when invoked at the end of the stream" do
+ @io.read
+ -> { @io.readchar }.should.raise(EOFError)
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.readchar }.should.raise(IOError)
+ end
+end
+
+describe "IO#readchar with internal encoding" do
+ after :each do
+ @io.close if @io
+ end
+
+ describe "not specified" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp"
+ end
+
+ it "does not transcode the String" do
+ @io.readchar.should == ("あ").encode(Encoding::EUC_JP)
+ end
+
+ it "sets the String encoding to the external encoding" do
+ @io.readchar.encoding.should.equal?(Encoding::EUC_JP)
+ end
+ end
+
+ describe "specified by open mode" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", "r:euc-jp:utf-8"
+ end
+
+ it_behaves_like :io_readchar_internal_encoding, nil
+ end
+
+ describe "specified by mode: option" do
+ before :each do
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", mode: "r:euc-jp:utf-8"
+ end
+
+ it_behaves_like :io_readchar_internal_encoding, nil
+ end
+
+ describe "specified by internal_encoding: option" do
+ before :each do
+ options = { mode: "r",
+ internal_encoding: "utf-8",
+ external_encoding: "euc-jp" }
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", options
+ end
+
+ it_behaves_like :io_readchar_internal_encoding, nil
+ end
+
+ describe "specified by encoding: option" do
+ before :each do
+ options = { mode: "r", encoding: "euc-jp:utf-8" }
+ @io = IOSpecs.io_fixture "read_euc_jp.txt", options
+ end
+
+ it_behaves_like :io_readchar_internal_encoding, nil
+ end
+end
+
+describe "IO#readchar" do
+ before :each do
+ @io = IOSpecs.io_fixture "empty.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "raises EOFError on empty stream" do
+ -> { @io.readchar }.should.raise(EOFError)
+ end
+end
diff --git a/spec/ruby/core/io/readline_spec.rb b/spec/ruby/core/io/readline_spec.rb
new file mode 100644
index 0000000000..009687710a
--- /dev/null
+++ b/spec/ruby/core/io/readline_spec.rb
@@ -0,0 +1,84 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#readline" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns the next line on the stream" do
+ @io.readline.should == "Voici la ligne une.\n"
+ @io.readline.should == "Qui è la linea due.\n"
+ end
+
+ it "goes back to first position after a rewind" do
+ @io.readline.should == "Voici la ligne une.\n"
+ @io.rewind
+ @io.readline.should == "Voici la ligne une.\n"
+ end
+
+ it "returns characters after the position set by #seek" do
+ @io.seek(1)
+ @io.readline.should == "oici la ligne une.\n"
+ end
+
+ it "raises EOFError on end of stream" do
+ IOSpecs.lines.length.times { @io.readline }
+ -> { @io.readline }.should.raise(EOFError)
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.readline }.should.raise(IOError)
+ end
+
+ it "assigns the returned line to $_" do
+ IOSpecs.lines.each do |line|
+ @io.readline
+ $_.should == line
+ end
+ end
+
+ describe "when passed limit" do
+ it "reads limit bytes" do
+ @io.readline(3).should == "Voi"
+ end
+
+ it "returns an empty string when passed 0 as a limit" do
+ @io.readline(0).should == ""
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ -> { @io.readline(2**128) }.should.raise(RangeError)
+ end
+ end
+
+ describe "when passed separator and limit" do
+ it "reads limit bytes till the separator" do
+ # Voici la ligne une.\
+ @io.readline(" ", 4).should == "Voic"
+ @io.readline(" ", 4).should == "i "
+ @io.readline(" ", 4).should == "la "
+ @io.readline(" ", 4).should == "lign"
+ @io.readline(" ", 4).should == "e "
+ end
+ end
+
+ describe "when passed chomp" do
+ it "returns the first line without a trailing newline character" do
+ @io.readline(chomp: true).should == IOSpecs.lines_without_newline_characters[0]
+ end
+
+ it "raises exception when options passed as Hash" do
+ -> { @io.readline({ chomp: true }) }.should.raise(TypeError)
+
+ -> {
+ @io.readline("\n", 1, { chomp: true })
+ }.should.raise(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
+ end
+ end
+end
diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb
new file mode 100644
index 0000000000..640e253200
--- /dev/null
+++ b/spec/ruby/core/io/readlines_spec.rb
@@ -0,0 +1,257 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/readlines'
+
+describe "IO#readlines" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @orig_extenc = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ Encoding.default_external = @orig_extenc
+ end
+
+ it "raises an IOError if the stream is closed" do
+ @io.close
+ -> { @io.readlines }.should.raise(IOError)
+ end
+
+ describe "when passed no arguments" do
+ before :each do
+ suppress_warning {@sep, $/ = $/, " "}
+ end
+
+ after :each do
+ suppress_warning {$/ = @sep}
+ end
+
+ it "returns an Array containing lines based on $/" do
+ @io.readlines.should == IOSpecs.lines_space_separator
+ end
+ end
+
+ describe "when passed no arguments" do
+ it "updates self's position" do
+ @io.readlines
+ @io.pos.should.eql?(137)
+ end
+
+ it "updates self's lineno based on the number of lines read" do
+ @io.readlines
+ @io.lineno.should.eql?(9)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.readlines
+ $_.should == "test"
+ end
+
+ it "returns an empty Array when self is at the end" do
+ @io.readlines.should == IOSpecs.lines
+ @io.readlines.should == []
+ end
+ end
+
+ describe "when passed nil" do
+ it "returns the remaining content as one line starting at the current position" do
+ @io.readlines(nil).should == [IOSpecs.lines.join]
+ end
+ end
+
+ describe "when passed an empty String" do
+ it "returns an Array containing all paragraphs" do
+ @io.readlines("").should == IOSpecs.paragraphs
+ end
+ end
+
+ describe "when passed a separator" do
+ it "returns an Array containing lines based on the separator" do
+ @io.readlines("r").should == IOSpecs.lines_r_separator
+ end
+
+ it "returns an empty Array when self is at the end" do
+ @io.readlines
+ @io.readlines("r").should == []
+ end
+
+ it "updates self's lineno based on the number of lines read" do
+ @io.readlines("r")
+ @io.lineno.should.eql?(5)
+ end
+
+ it "updates self's position based on the number of characters read" do
+ @io.readlines("r")
+ @io.pos.should.eql?(137)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.readlines("r")
+ $_.should == "test"
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock('to_str')
+ obj.stub!(:to_str).and_return("r")
+ @io.readlines(obj).should == IOSpecs.lines_r_separator
+ end
+ end
+
+ describe "when passed limit" do
+ it "raises ArgumentError when passed 0 as a limit" do
+ -> { @io.readlines(0) }.should.raise(ArgumentError)
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ -> { @io.readlines(2**128) }.should.raise(RangeError)
+ end
+ end
+
+ describe "when passed chomp" do
+ it "returns the first line without a trailing newline character" do
+ @io.readlines(chomp: true).should == IOSpecs.lines_without_newline_characters
+ end
+
+ it "raises exception when options passed as Hash" do
+ -> { @io.readlines({ chomp: true }) }.should.raise(TypeError)
+
+ -> {
+ @io.readlines("\n", 1, { chomp: true })
+ }.should.raise(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
+ end
+ end
+
+ describe "when passed arbitrary keyword argument" do
+ it "tolerates it" do
+ @io.readlines(chomp: true, foo: :bar).should == IOSpecs.lines_without_newline_characters
+ end
+ end
+end
+
+describe "IO#readlines" do
+ before :each do
+ @name = tmp("io_readlines")
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "raises an IOError if the stream is opened for append only" do
+ -> do
+ File.open(@name, "a:utf-8") { |f| f.readlines }
+ end.should.raise(IOError)
+ end
+
+ it "raises an IOError if the stream is opened for write only" do
+ -> do
+ File.open(@name, "w:utf-8") { |f| f.readlines }
+ end.should.raise(IOError)
+ end
+end
+
+describe "IO.readlines" do
+ before :each do
+ @external = Encoding.default_external
+ Encoding.default_external = Encoding::UTF_8
+
+ @name = fixture __FILE__, "lines.txt"
+ ScratchPad.record []
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ IO.readlines(@name)
+ $_.should == "test"
+ 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
+
+ lines = nil
+ suppress_warning do # https://bugs.ruby-lang.org/issues/19630
+ lines = IO.readlines(cmd)
+ end
+ lines.should == ["hello\n", "line2\n"]
+ 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("|-")
+ end
+
+ if lines # parent
+ lines.should == ["hello\n", "from a fork\n"]
+ else
+ puts "hello"
+ puts "from a fork"
+ exit!
+ end
+ end
+ end
+ end
+
+ # 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
+
+ it_behaves_like :io_readlines, :readlines
+ it_behaves_like :io_readlines_options_19, :readlines
+end
+
+describe "IO.readlines" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+ @name = fixture __FILE__, "lines.txt"
+ @dollar_slash = $/
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ suppress_warning {$/ = @dollar_slash}
+ end
+
+ it "encodes lines using the default external encoding" do
+ Encoding.default_external = Encoding::UTF_8
+ lines = IO.readlines(@name)
+ lines.all? { |s| s.encoding == Encoding::UTF_8 }.should == true
+ end
+
+ it "encodes lines using the default internal encoding, when set" do
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = Encoding::UTF_16
+ suppress_warning {$/ = $/.encode Encoding::UTF_16}
+ lines = IO.readlines(@name)
+ lines.all? { |s| s.encoding == Encoding::UTF_16 }.should == true
+ end
+
+ it "ignores the default internal encoding if the external encoding is BINARY" do
+ Encoding.default_external = Encoding::BINARY
+ Encoding.default_internal = Encoding::UTF_8
+ lines = IO.readlines(@name)
+ lines.all? { |s| s.encoding == Encoding::BINARY }.should == true
+ end
+end
diff --git a/spec/ruby/core/io/readpartial_spec.rb b/spec/ruby/core/io/readpartial_spec.rb
new file mode 100644
index 0000000000..d3f5545c8f
--- /dev/null
+++ b/spec/ruby/core/io/readpartial_spec.rb
@@ -0,0 +1,115 @@
+# encoding: binary
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#readpartial" do
+ before :each do
+ @rd, @wr = IO.pipe
+ @rd.binmode
+ @wr.binmode
+ end
+
+ after :each do
+ @rd.close unless @rd.closed?
+ @wr.close unless @wr.closed?
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.readpartial(10) }.should.raise(IOError)
+
+ @rd.close
+ -> { @rd.readpartial(10) }.should.raise(IOError)
+ end
+
+ it "reads at most the specified number of bytes" do
+ @wr.write("foobar")
+
+ # buffered read
+ @rd.read(1).should == 'f'
+ # return only specified number, not the whole buffer
+ @rd.readpartial(1).should == "o"
+ end
+
+ it "reads after ungetc with data in the buffer" do
+ @wr.write("foobar")
+ c = @rd.getc
+ @rd.ungetc(c)
+ @rd.readpartial(3).should == "foo"
+ @rd.readpartial(3).should == "bar"
+ end
+
+ it "reads after ungetc with multibyte characters in the buffer" do
+ @wr.write("∂φ/∂x = gaîté")
+ c = @rd.getc
+ @rd.ungetc(c)
+ @rd.readpartial(3).should == "\xE2\x88\x82"
+ @rd.readpartial(3).should == "\xCF\x86/"
+ end
+
+ it "reads after ungetc without data in the buffer" do
+ @wr.write("f")
+ c = @rd.getc
+ @rd.ungetc(c)
+ @rd.readpartial(2).should == "f"
+
+ # now, also check that the ungot char is cleared and
+ # not returned again
+ @wr.write("b")
+ @rd.readpartial(2).should == "b"
+ end
+
+ it "discards the existing buffer content upon successful read" do
+ buffer = +"existing content"
+ @wr.write("hello world")
+ @wr.close
+ @rd.readpartial(11, buffer).should.equal?(buffer)
+ buffer.should == "hello world"
+ end
+
+ it "raises EOFError on EOF" do
+ @wr.write("abc")
+ @wr.close
+ @rd.readpartial(10).should == 'abc'
+ -> { @rd.readpartial(10) }.should.raise(EOFError)
+ end
+
+ it "discards the existing buffer content upon error" do
+ buffer = +'hello'
+ @wr.close
+ -> { @rd.readpartial(1, buffer) }.should.raise(EOFError)
+ buffer.should.empty?
+ end
+
+ it "raises IOError if the stream is closed" do
+ @wr.close
+ -> { @rd.readpartial(1) }.should.raise(IOError)
+ end
+
+ it "raises ArgumentError if the negative argument is provided" do
+ -> { @rd.readpartial(-1) }.should.raise(ArgumentError)
+ end
+
+ it "immediately returns an empty string if the length argument is 0" do
+ @rd.readpartial(0).should == ""
+ end
+
+ it "raises IOError if the stream is closed and the length argument is 0" do
+ @rd.close
+ -> { @rd.readpartial(0) }.should.raise(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
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ @wr.write("abc")
+ @wr.close
+ @rd.readpartial(10, buffer)
+
+ buffer.encoding.should == Encoding::ISO_8859_1
+ end
+end
diff --git a/spec/ruby/core/io/reopen_spec.rb b/spec/ruby/core/io/reopen_spec.rb
new file mode 100644
index 0000000000..3b972d8978
--- /dev/null
+++ b/spec/ruby/core/io/reopen_spec.rb
@@ -0,0 +1,313 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+require 'fcntl'
+
+describe "IO#reopen" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+
+ @io = new_io @name
+ @other_io = File.open @other_name, "w"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close unless @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "calls #to_io to convert an object" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return(@other_io)
+ @io.reopen obj
+ end
+
+ it "changes the class of the instance to the class of the object returned by #to_io" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return(@other_io)
+ @io.reopen(obj).should.instance_of?(File)
+ end
+
+ it "raises an IOError if the object returned by #to_io is closed" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return(IOSpecs.closed_io)
+ -> { @io.reopen obj }.should.raise(IOError)
+ end
+
+ it "raises a TypeError if #to_io does not return an IO instance" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return("something else")
+ -> { @io.reopen obj }.should.raise(TypeError)
+ end
+
+ it "raises an IOError when called on a closed stream with an object" do
+ @io.close
+ obj = mock("io")
+ obj.should_not_receive(:to_io)
+ -> { @io.reopen(STDOUT) }.should.raise(IOError)
+ end
+
+ it "raises an IOError if the IO argument is closed" do
+ -> { @io.reopen(IOSpecs.closed_io) }.should.raise(IOError)
+ end
+
+ it "raises an IOError when called on a closed stream with an IO" do
+ @io.close
+ -> { @io.reopen(STDOUT) }.should.raise(IOError)
+ end
+end
+
+describe "IO#reopen with a String" do
+ before :each do
+ @name = fixture __FILE__, "numbered_lines.txt"
+ @other_name = tmp("io_reopen.txt")
+ touch @other_name
+ @io = IOSpecs.io_fixture "lines.txt"
+
+ @tmp_file = tmp("reopen")
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @other_name, @tmp_file
+ end
+
+ it "does not raise an exception when called on a closed stream with a path" do
+ @io.close
+ @io.reopen @name, "r"
+ @io.closed?.should == false
+ @io.gets.should == "Line 1: One\n"
+ end
+
+ it "returns self" do
+ @io.reopen(@name).should.equal?(@io)
+ end
+
+ it "positions a newly created instance at the beginning of the new stream" do
+ @io.reopen(@name)
+ @io.gets.should == "Line 1: One\n"
+ end
+
+ it "positions an instance that has been read from at the beginning of the new stream" do
+ @io.gets
+ @io.reopen(@name)
+ @io.gets.should == "Line 1: One\n"
+ end
+
+ platform_is_not :windows do
+ it "passes all mode flags through" do
+ @io.reopen(@tmp_file, "ab")
+ (@io.fcntl(Fcntl::F_GETFL) & File::APPEND).should == File::APPEND
+ end
+ end
+
+ platform_is_not :windows do
+ # TODO Should this work on Windows?
+ it "affects exec/system/fork performed after it" do
+ ruby_exe fixture(__FILE__, "reopen_stdout.rb"), args: @tmp_file
+ File.read(@tmp_file).should == "from system\nfrom exec\n"
+ end
+ end
+
+ it "calls #to_path on non-String arguments" do
+ obj = mock('path')
+ obj.should_receive(:to_path).and_return(@other_name)
+ @io.reopen(obj)
+ end
+end
+
+describe "IO#reopen with a String" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+ @other_io = nil
+
+ rm_r @other_name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close if @other_io and not @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "opens a path after writing to the original file descriptor" do
+ @io = new_io @name, "w"
+
+ @io.print "original data"
+ @io.reopen @other_name
+ @io.print "new data"
+ @io.flush
+
+ File.read(@name).should == "original data"
+ File.read(@other_name).should == "new data"
+ end
+
+ it "always resets the close-on-exec flag to true on non-STDIO objects" do
+ @io = new_io @name, "w"
+
+ @io.close_on_exec = true
+ @io.reopen @other_name
+ @io.should.close_on_exec?
+
+ @io.close_on_exec = false
+ @io.reopen @other_name
+ @io.should.close_on_exec?
+ end
+
+ it "creates the file if it doesn't exist if the IO is opened in write mode" do
+ @io = new_io @name, "w"
+
+ @io.reopen(@other_name)
+ File.should.exist?(@other_name)
+ end
+
+ it "creates the file if it doesn't exist if the IO is opened in write mode" do
+ @io = new_io @name, "a"
+
+ @io.reopen(@other_name)
+ File.should.exist?(@other_name)
+ end
+end
+
+describe "IO#reopen with a String" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+
+ touch @name
+ rm_r @other_name
+ end
+
+ after :each do
+ @io.close
+ rm_r @name, @other_name
+ end
+
+ it "raises an Errno::ENOENT if the file does not exist and the IO is not opened in write mode" do
+ @io = new_io @name, "r"
+ -> { @io.reopen(@other_name) }.should.raise(Errno::ENOENT)
+ end
+end
+
+describe "IO#reopen with an IO at EOF" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ touch(@name) { |f| f.puts "a line" }
+ @other_name = tmp("io_reopen_other.txt")
+ touch(@other_name) do |f|
+ f.puts "Line 1"
+ f.puts "Line 2"
+ end
+
+ @io = new_io @name, "r"
+ @other_io = new_io @other_name, "r"
+ @io.read
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close unless @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "resets the EOF status to false" do
+ @io.eof?.should == true
+ @io.reopen @other_io
+ @io.eof?.should == false
+ end
+end
+
+describe "IO#reopen with an IO" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+ touch(@other_name) do |f|
+ f.puts "Line 1"
+ f.puts "Line 2"
+ end
+
+ @io = new_io @name
+ @other_io = IO.new(new_fd(@other_name, "r"), "r")
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close unless @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "does not call #to_io" do
+ # Why do we not use #should_not_receive(:to_io) here? Because
+ # MRI actually changes the class of @io in the call to #reopen
+ # but does not preserve the existing singleton class of @io.
+ def @io.to_io; flunk; end
+ @io.reopen(@other_io).should.instance_of?(IO)
+ end
+
+ it "does not change the object_id" do
+ obj_id = @io.object_id
+ @io.reopen @other_io
+ @io.object_id.should == obj_id
+ end
+
+ it "reads from the beginning if the other IO has not been read from" do
+ @io.reopen @other_io
+ @io.gets.should == "Line 1\n"
+ end
+
+ it "reads from the current position of the other IO's stream" do
+ @other_io.gets.should == "Line 1\n"
+ @io.reopen @other_io
+ @io.gets.should == "Line 2\n"
+ end
+end
+
+describe "IO#reopen with an IO" do
+ before :each do
+ @name = tmp("io_reopen.txt")
+ @other_name = tmp("io_reopen_other.txt")
+
+ @io = new_io @name
+ @other_io = File.open @other_name, "w"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ @other_io.close unless @other_io.closed?
+ rm_r @name, @other_name
+ end
+
+ it "associates the IO instance with the other IO's stream" do
+ File.read(@other_name).should == ""
+ @io.reopen @other_io
+ @io.print "io data"
+ @io.flush
+ File.read(@name).should == ""
+ File.read(@other_name).should == "io data"
+ end
+
+ it "always resets the close-on-exec flag to true on non-STDIO objects" do
+ @other_io.close_on_exec = true
+ @io.close_on_exec = true
+ @io.reopen @other_io
+ @io.should.close_on_exec?
+
+ @other_io.close_on_exec = false
+ @io.close_on_exec = false
+ @io.reopen @other_io
+ @io.should.close_on_exec?
+ end
+
+ it "may change the class of the instance" do
+ @io.reopen @other_io
+ @io.should.instance_of?(File)
+ end
+
+ it "sets path equals to the other IO's path if other IO is File" do
+ @io.reopen @other_io
+ @io.path.should == @other_io.path
+ end
+end
diff --git a/spec/ruby/core/io/rewind_spec.rb b/spec/ruby/core/io/rewind_spec.rb
new file mode 100644
index 0000000000..43834ef307
--- /dev/null
+++ b/spec/ruby/core/io/rewind_spec.rb
@@ -0,0 +1,53 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#rewind" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "positions the instance to the beginning of input" do
+ @io.readline.should == "Voici la ligne une.\n"
+ @io.readline.should == "Qui è la linea due.\n"
+ @io.rewind
+ @io.readline.should == "Voici la ligne une.\n"
+ end
+
+ it "positions the instance to the beginning of output for write-only IO" do
+ name = tmp("io_rewind_spec")
+ io = File.open(name, "w")
+ io.write("Voici la ligne une.\n")
+ io.rewind
+ io.pos.should == 0
+ ensure
+ io.close
+ rm_r name
+ end
+
+ it "positions the instance to the beginning of input and clears EOF" do
+ value = @io.read
+ @io.rewind
+ @io.should_not.eof?
+ value.should == @io.read
+ end
+
+ it "sets lineno to 0" do
+ @io.readline.should == "Voici la ligne une.\n"
+ @io.lineno.should == 1
+ @io.rewind
+ @io.lineno.should == 0
+ end
+
+ it "returns 0" do
+ @io.rewind.should == 0
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.rewind }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/seek_spec.rb b/spec/ruby/core/io/seek_spec.rb
new file mode 100644
index 0000000000..d26629fb89
--- /dev/null
+++ b/spec/ruby/core/io/seek_spec.rb
@@ -0,0 +1,79 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/pos'
+
+describe "IO#seek" do
+ it_behaves_like :io_set_pos, :seek
+end
+
+describe "IO#seek" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "moves the read position relative to the current position with SEEK_CUR" do
+ -> { @io.seek(-1) }.should.raise(Errno::EINVAL)
+ @io.seek(10, IO::SEEK_CUR)
+ @io.readline.should == "igne une.\n"
+ @io.seek(-5, IO::SEEK_CUR)
+ @io.readline.should == "une.\n"
+ end
+
+ it "moves the read position relative to the start with SEEK_SET" do
+ @io.seek(1)
+ @io.pos.should == 1
+ @io.rewind
+ @io.seek(43, IO::SEEK_SET)
+ @io.readline.should == "Aquí está la línea tres.\n"
+ @io.seek(5, IO::SEEK_SET)
+ @io.readline.should == " la ligne une.\n"
+ end
+
+ it "moves the read position relative to the end with SEEK_END" do
+ @io.seek(0, IO::SEEK_END)
+ @io.tell.should == 137
+ @io.seek(-25, IO::SEEK_END)
+ @io.readline.should == "cinco.\n"
+ end
+
+ it "moves the read position and clears EOF with SEEK_SET" do
+ value = @io.read
+ @io.seek(0, IO::SEEK_SET)
+ @io.should_not.eof?
+ value.should == @io.read
+ end
+
+ it "moves the read position and clears EOF with SEEK_CUR" do
+ value = @io.read
+ @io.seek(-1, IO::SEEK_CUR)
+ @io.should_not.eof?
+ value[-1].should == @io.read[0]
+ end
+
+ it "moves the read position and clears EOF with SEEK_END" do
+ value = @io.read
+ @io.seek(-1, IO::SEEK_END)
+ @io.should_not.eof?
+ value[-1].should == @io.read[0]
+ end
+
+ platform_is :darwin do
+ it "supports seek offsets greater than 2^32" do
+ begin
+ zero = File.open('/dev/zero')
+ offset = 2**33
+ zero.seek(offset, File::SEEK_SET)
+ pos = zero.pos
+
+ pos.should == offset
+ ensure
+ zero.close rescue nil
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/select_spec.rb b/spec/ruby/core/io/select_spec.rb
new file mode 100644
index 0000000000..0a43fc6f5f
--- /dev/null
+++ b/spec/ruby/core/io/select_spec.rb
@@ -0,0 +1,190 @@
+require_relative '../../spec_helper'
+
+describe "IO.select" do
+ before :each do
+ @rd, @wr = IO.pipe
+ end
+
+ after :each do
+ @rd.close unless @rd.closed?
+ @wr.close unless @wr.closed?
+ end
+
+ it "blocks for duration of timeout and returns nil if there are no objects ready for I/O" do
+ IO.select([@rd], nil, nil, 0.001).should == nil
+ end
+
+ it "returns immediately all objects that are ready for I/O when timeout is 0" do
+ @wr.syswrite("be ready")
+ IO.pipe do |_, wr|
+ result = IO.select [@rd], [wr], nil, 0
+ unless result
+ # On some platforms (e.g., Windows), pipe readiness may not be immediate
+ result = IO.select [@rd], [wr], nil, 2
+ end
+ result.should == [[@rd], [wr], []]
+ end
+ end
+
+ it "returns nil after timeout if there are no objects ready for I/O" do
+ result = IO.select [@rd], nil, nil, 0
+ result.should == nil
+ end
+
+ it "returns supplied objects when they are ready for I/O" do
+ main = Thread.current
+ t = Thread.new {
+ Thread.pass until main.status == "sleep"
+ @wr.write "be ready"
+ }
+ result = IO.select [@rd], nil, nil, nil
+ result.should == [[@rd], [], []]
+ t.join
+ end
+
+ it "leaves out IO objects for which there is no I/O ready" do
+ @wr.write "be ready"
+ platform_is :aix do
+ # In AIX, when a pipe is readable, select(2) returns the write side
+ # of the pipe as "readable", even though you cannot actually read
+ # anything from the write side.
+ result = IO.select [@wr, @rd], nil, nil, nil
+ result.should == [[@wr, @rd], [], []]
+ end
+ platform_is_not :aix do
+ # Order matters here. We want to see that @wr doesn't expand the size
+ # of the returned array, so it must be 1st.
+ result = IO.select [@wr, @rd], nil, nil, nil
+ result.should == [[@rd], [], []]
+ end
+ end
+
+ it "returns supplied objects correctly when monitoring the same object in different arrays" do
+ filename = tmp("IO_select_pipe_file")
+ io = File.open(filename, 'w+')
+ result = IO.select [io], [io], nil, 0
+ result.should == [[io], [io], []]
+ io.close
+ rm_r filename
+ end
+
+ it "returns the pipe read end in read set if the pipe write end is closed concurrently" do
+ main = Thread.current
+ t = Thread.new {
+ Thread.pass until main.stop?
+ @wr.close
+ }
+ IO.select([@rd]).should == [[@rd], [], []]
+ ensure
+ t.join
+ end
+
+ it "invokes to_io on supplied objects that are not IO and returns the supplied objects" do
+ # make some data available
+ @wr.write("foobar")
+
+ obj = mock("read_io")
+ obj.should_receive(:to_io).at_least(1).and_return(@rd)
+ IO.select([obj]).should == [[obj], [], []]
+
+ IO.pipe do |_, wr|
+ obj = mock("write_io")
+ obj.should_receive(:to_io).at_least(1).and_return(wr)
+ IO.select(nil, [obj]).should == [[], [obj], []]
+ end
+ end
+
+ it "raises TypeError if supplied objects are not IO" do
+ -> { IO.select([Object.new]) }.should.raise(TypeError)
+ -> { IO.select(nil, [Object.new]) }.should.raise(TypeError)
+
+ obj = mock("io")
+ obj.should_receive(:to_io).any_number_of_times.and_return(nil)
+
+ -> { IO.select([obj]) }.should.raise(TypeError)
+ -> { IO.select(nil, [obj]) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if the specified timeout value is not Numeric" do
+ -> { IO.select([@rd], nil, nil, Object.new) }.should.raise(TypeError)
+ end
+
+ it "raises TypeError if the first three arguments are not Arrays" do
+ -> { IO.select(Object.new)}.should.raise(TypeError)
+ -> { IO.select(nil, Object.new)}.should.raise(TypeError)
+ -> { IO.select(nil, nil, Object.new)}.should.raise(TypeError)
+ end
+
+ it "raises an ArgumentError when passed a negative timeout" do
+ -> { IO.select(nil, nil, nil, -5)}.should.raise(ArgumentError, "time interval must not be negative")
+ end
+
+ ruby_version_is "4.0" do
+ it "raises an ArgumentError when passed negative infinity as timeout" do
+ -> { IO.select(nil, nil, nil, -Float::INFINITY)}.should.raise(ArgumentError, "time interval must not be negative")
+ end
+ end
+
+ it "raises an RangeError when passed NaN as timeout" do
+ -> { IO.select(nil, nil, nil, Float::NAN)}.should.raise(RangeError, "NaN out of Time range")
+ end
+
+ describe "returns the available descriptors when the file descriptor" do
+ it "is in both read and error arrays" do
+ @wr.write("foobar")
+ result = IO.select([@rd], nil, [@rd])
+ result.should == [[@rd], [], []]
+ end
+
+ it "is in both write and error arrays" do
+ result = IO.select(nil, [@wr], [@wr])
+ result.should == [[], [@wr], []]
+ end
+
+ it "is in both read and write arrays" do
+ filename = tmp("IO_select_read_write_file")
+ w = File.open(filename, 'w+')
+ begin
+ IO.select([w], [w], []).should == [[w], [w], []]
+ ensure
+ w.close
+ rm_r filename
+ end
+
+ IO.select([@wr], [@wr], []).should == [[], [@wr], []]
+
+ @wr.write("foobar")
+ # CRuby on macOS returns [[@rd], [@rd], []], weird but we accept it here, probably only for pipe read-end
+ [
+ [[@rd], [], []],
+ [[@rd], [@rd], []]
+ ].should.include? IO.select([@rd], [@rd], [])
+ end
+ end
+end
+
+describe "IO.select with infinite timeout" do
+ describe :io_select_infinite_timeout, shared: true do
+ it "sleeps forever and sets the thread status to 'sleep'" do
+ t = Thread.new do
+ IO.select(nil, nil, nil, @method)
+ end
+
+ Thread.pass while t.status && t.status != "sleep"
+ t.join unless t.status
+ t.status.should == "sleep"
+ t.kill
+ t.join
+ end
+ end
+
+ describe "IO.select when passed nil for timeout" do
+ it_behaves_like :io_select_infinite_timeout, nil
+ end
+
+ ruby_version_is "4.0" do
+ describe "IO.select when passed Float::INFINITY for timeout" do
+ it_behaves_like :io_select_infinite_timeout, Float::INFINITY
+ end
+ end
+end
diff --git a/spec/ruby/core/io/set_encoding_by_bom_spec.rb b/spec/ruby/core/io/set_encoding_by_bom_spec.rb
new file mode 100644
index 0000000000..5436879f11
--- /dev/null
+++ b/spec/ruby/core/io/set_encoding_by_bom_spec.rb
@@ -0,0 +1,262 @@
+require_relative '../../spec_helper'
+
+describe "IO#set_encoding_by_bom" do
+ before :each do
+ @name = tmp('io_set_encoding_by_bom.txt')
+ touch(@name)
+ @io = new_io(@name, 'rb')
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "returns nil if not readable" do
+ not_readable_io = new_io(@name, 'wb')
+
+ not_readable_io.set_encoding_by_bom.should == nil
+ not_readable_io.external_encoding.should == Encoding::ASCII_8BIT
+ ensure
+ not_readable_io.close
+ end
+
+ it "returns the result encoding if found BOM UTF-8 sequence" do
+ File.binwrite(@name, "\u{FEFF}")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_8
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.binwrite(@name, "\u{FEFF}abc")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_8
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.read.b.should == "abc".b
+ end
+
+ it "returns the result encoding if found BOM UTF_16LE sequence" do
+ File.binwrite(@name, "\xFF\xFE")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.binwrite(@name, "\xFF\xFEabc")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "abc".b
+ end
+
+ it "returns the result encoding if found BOM UTF_16BE sequence" do
+ File.binwrite(@name, "\xFE\xFF")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16BE
+ @io.external_encoding.should == Encoding::UTF_16BE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ 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 == "abcd".b
+ end
+
+ it "returns the result encoding if found BOM UTF_32LE sequence" do
+ File.binwrite(@name, "\xFF\xFE\x00\x00")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_32LE
+ @io.external_encoding.should == Encoding::UTF_32LE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.binwrite(@name, "\xFF\xFE\x00\x00abc")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_32LE
+ @io.external_encoding.should == Encoding::UTF_32LE
+ @io.read.b.should == "abc".b
+ end
+
+ it "returns the result encoding if found BOM UTF_32BE sequence" do
+ File.binwrite(@name, "\x00\x00\xFE\xFF")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_32BE
+ @io.external_encoding.should == Encoding::UTF_32BE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ 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 == "abcd".b
+ end
+
+ it "returns nil if io is empty" do
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ end
+
+ it "returns nil if UTF-8 BOM sequence is incomplete" do
+ File.write(@name, "\xEF")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xEF".b
+ @io.rewind
+
+ File.write(@name, "\xEFa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xEFa".b
+ @io.rewind
+
+ File.write(@name, "\xEF\xBB")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xEF\xBB".b
+ @io.rewind
+
+ File.write(@name, "\xEF\xBBa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xEF\xBBa".b
+ end
+
+ it "returns nil if UTF-16BE BOM sequence is incomplete" do
+ File.write(@name, "\xFE")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xFE".b
+ @io.rewind
+
+ File.write(@name, "\xFEa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xFEa".b
+ end
+
+ it "returns nil if UTF-16LE/UTF-32LE BOM sequence is incomplete" do
+ File.write(@name, "\xFF")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xFF".b
+ @io.rewind
+
+ File.write(@name, "\xFFa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\xFFa".b
+ end
+
+ it "returns UTF-16LE if UTF-32LE BOM sequence is incomplete" do
+ File.write(@name, "\xFF\xFE")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.write(@name, "\xFF\xFE\x00")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "\x00".b
+ @io.rewind
+ @io.set_encoding(Encoding::ASCII_8BIT)
+
+ File.write(@name, "\xFF\xFE\x00a")
+
+ @io.set_encoding_by_bom.should == Encoding::UTF_16LE
+ @io.external_encoding.should == Encoding::UTF_16LE
+ @io.read.b.should == "\x00a".b
+ end
+
+ it "returns nil if UTF-32BE BOM sequence is incomplete" do
+ File.write(@name, "\x00")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00".b
+ @io.rewind
+
+ File.write(@name, "\x00a")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00a".b
+ @io.rewind
+
+ File.write(@name, "\x00\x00")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00\x00".b
+ @io.rewind
+
+ File.write(@name, "\x00\x00a")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00\x00a".b
+ @io.rewind
+
+ File.write(@name, "\x00\x00\xFE")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00\x00\xFE".b
+ @io.rewind
+
+ File.write(@name, "\x00\x00\xFEa")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read.b.should == "\x00\x00\xFEa".b
+ end
+
+ it "returns nil if found BOM sequence not provided" do
+ File.write(@name, "abc")
+
+ @io.set_encoding_by_bom.should == nil
+ @io.external_encoding.should == Encoding::ASCII_8BIT
+ @io.read(3).should == "abc".b
+ end
+
+ it 'returns exception if io not in binary mode' do
+ not_binary_io = new_io(@name, 'r')
+
+ -> { not_binary_io.set_encoding_by_bom }.should.raise(ArgumentError, 'ASCII incompatible encoding needs binmode')
+ ensure
+ not_binary_io.close
+ end
+
+ it 'returns exception if encoding already set' do
+ @io.set_encoding("utf-8")
+
+ -> { @io.set_encoding_by_bom }.should.raise(ArgumentError, 'encoding is set to UTF-8 already')
+ end
+
+ it 'returns exception if encoding conversion is already set' do
+ @io.set_encoding(Encoding::UTF_8, Encoding::UTF_16BE)
+
+ -> { @io.set_encoding_by_bom }.should.raise(ArgumentError, 'encoding conversion is set')
+ end
+end
diff --git a/spec/ruby/core/io/set_encoding_spec.rb b/spec/ruby/core/io/set_encoding_spec.rb
new file mode 100644
index 0000000000..237251de5b
--- /dev/null
+++ b/spec/ruby/core/io/set_encoding_spec.rb
@@ -0,0 +1,238 @@
+require_relative '../../spec_helper'
+
+describe :io_set_encoding_write, shared: true do
+ it "sets the encodings to nil when they were set previously" do
+ @io = new_io @name, "#{@object}:ibm437:ibm866"
+ @io.set_encoding nil, nil
+
+ @io.external_encoding.should == nil
+ @io.internal_encoding.should == nil
+ end
+
+ it "sets the encodings to nil when the IO is built with no explicit encoding" do
+ @io = new_io @name, @object
+
+ # Checking our assumptions first
+ @io.external_encoding.should == nil
+ @io.internal_encoding.should == nil
+
+ @io.set_encoding nil, nil
+
+ @io.external_encoding.should == nil
+ @io.internal_encoding.should == nil
+ end
+
+ it "prevents the encodings from changing when Encoding defaults are changed" do
+ @io = new_io @name, "#{@object}:utf-8:us-ascii"
+ @io.set_encoding nil, nil
+
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+
+ @io.external_encoding.should == nil
+ @io.internal_encoding.should == nil
+ end
+
+ it "sets the encodings to the current Encoding defaults" do
+ @io = new_io @name, @object
+
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+
+ @io.set_encoding nil, nil
+
+ @io.external_encoding.should == Encoding::IBM437
+ @io.internal_encoding.should == Encoding::IBM866
+ end
+end
+
+describe "IO#set_encoding when passed nil, nil" do
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ # The defaults
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = nil
+
+ @name = tmp('io_set_encoding.txt')
+ touch(@name)
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+
+ @io.close if @io and not @io.closed?
+ rm_r @name
+ end
+
+ describe "with 'r' mode" do
+ it "sets the encodings to the current Encoding defaults" do
+ @io = new_io @name, "r"
+
+ Encoding.default_external = Encoding::IBM437
+ Encoding.default_internal = Encoding::IBM866
+
+ @io.set_encoding nil, nil
+ @io.external_encoding.should.equal?(Encoding::IBM437)
+ @io.internal_encoding.should.equal?(Encoding::IBM866)
+ end
+
+ it "prevents the #internal_encoding from changing when Encoding.default_internal is changed" do
+ @io = new_io @name, "r"
+ @io.set_encoding nil, nil
+
+ Encoding.default_internal = Encoding::IBM437
+
+ @io.internal_encoding.should == nil
+ end
+
+ it "allows the #external_encoding to change when Encoding.default_external is changed" do
+ @io = new_io @name, "r"
+ @io.set_encoding nil, nil
+
+ Encoding.default_external = Encoding::IBM437
+
+ @io.external_encoding.should.equal?(Encoding::IBM437)
+ end
+ end
+
+ describe "with 'rb' mode" do
+ it "returns Encoding.default_external" do
+ @io = new_io @name, "rb"
+ @io.external_encoding.should.equal?(Encoding::BINARY)
+
+ @io.set_encoding nil, nil
+ @io.external_encoding.should.equal?(Encoding.default_external)
+ end
+ end
+
+ describe "with 'r+' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "r+"
+ end
+
+ describe "with 'w' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "w"
+ end
+
+ describe "with 'w+' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "w+"
+ end
+
+ describe "with 'a' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "a"
+ end
+
+ describe "with 'a+' mode" do
+ it_behaves_like :io_set_encoding_write, nil, "a+"
+ end
+
+ describe "with standard IOs" do
+ it "correctly resets them" do
+ STDOUT.external_encoding.should == nil
+ STDOUT.internal_encoding.should == nil
+
+ begin
+ STDOUT.set_encoding(Encoding::US_ASCII, Encoding::ISO_8859_1)
+ ensure
+ STDOUT.set_encoding(nil, nil)
+ end
+
+ STDOUT.external_encoding.should == nil
+ STDOUT.internal_encoding.should == nil
+ end
+ end
+end
+
+describe "IO#set_encoding" do
+ before :each do
+ @name = tmp('io_set_encoding.txt')
+ touch(@name)
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "returns self" do
+ @io.set_encoding(Encoding::UTF_8).should.equal?(@io)
+ end
+
+ it "sets the external encoding when passed an Encoding argument" do
+ @io.set_encoding(Encoding::UTF_8)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == nil
+ end
+
+ it "sets the external and internal encoding when passed two Encoding arguments" do
+ @io.set_encoding(Encoding::UTF_8, Encoding::UTF_16BE)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "sets the external encoding when passed the name of an Encoding" do
+ @io.set_encoding("utf-8")
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == nil
+ end
+
+ it "ignores the internal encoding if the same as external when passed Encoding objects" do
+ @io.set_encoding(Encoding::UTF_8, Encoding::UTF_8)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == nil
+ end
+
+ it "ignores the internal encoding if the same as external when passed encoding names separated by ':'" do
+ @io.set_encoding("utf-8:utf-8")
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == nil
+ end
+
+ it "sets the external and internal encoding when passed the names of Encodings separated by ':'" do
+ @io.set_encoding("utf-8:utf-16be")
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "sets the external and internal encoding when passed two String arguments" do
+ @io.set_encoding("utf-8", "utf-16be")
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "calls #to_str to convert an abject to a String" do
+ obj = mock("io_set_encoding")
+ obj.should_receive(:to_str).and_return("utf-8:utf-16be")
+ @io.set_encoding(obj)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "calls #to_str to convert the second argument to a String" do
+ obj = mock("io_set_encoding")
+ obj.should_receive(:to_str).at_least(1).times.and_return("utf-16be")
+ @io.set_encoding(Encoding::UTF_8, obj)
+ @io.external_encoding.should == Encoding::UTF_8
+ @io.internal_encoding.should == Encoding::UTF_16BE
+ end
+
+ it "saves encoding options passed as a hash in the last argument" do
+ File.write(@name, "\xff")
+ io = File.open(@name)
+ io.set_encoding(Encoding::EUC_JP, Encoding::SHIFT_JIS, invalid: :replace, replace: ".")
+ io.read.should == "."
+ ensure
+ io.close
+ end
+
+ it "raises ArgumentError when no arguments are given" do
+ -> { @io.set_encoding() }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError when too many arguments are given" do
+ -> { @io.set_encoding(1, 2, 3) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/binwrite.rb b/spec/ruby/core/io/shared/binwrite.rb
new file mode 100644
index 0000000000..64793b1936
--- /dev/null
+++ b/spec/ruby/core/io/shared/binwrite.rb
@@ -0,0 +1,91 @@
+require_relative '../fixtures/classes'
+
+describe :io_binwrite, shared: true do
+ before :each do
+ @filename = tmp("IO_binwrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file << "012345678901234567890123456789"
+ end
+ end
+
+ after :each do
+ rm_r @filename
+ end
+
+ it "coerces the argument to a string using to_s" do
+ (obj = mock('test')).should_receive(:to_s).and_return('a string')
+ IO.send(@method, @filename, obj)
+ end
+
+ it "returns the number of bytes written" do
+ IO.send(@method, @filename, "abcde").should == 5
+ end
+
+ it "accepts options as a keyword argument" do
+ IO.send(@method, @filename, "hi", 0, flags: File::CREAT).should == 2
+
+ -> {
+ IO.send(@method, @filename, "hi", 0, {flags: File::CREAT})
+ }.should.raise(ArgumentError, "wrong number of arguments (given 4, expected 2..3)")
+ end
+
+ it "creates a file if missing" do
+ fn = @filename + "xxx"
+ begin
+ File.should_not.exist?(fn)
+ IO.send(@method, fn, "test")
+ File.should.exist?(fn)
+ ensure
+ rm_r fn
+ end
+ end
+
+ it "creates file if missing even if offset given" do
+ fn = @filename + "xxx"
+ begin
+ File.should_not.exist?(fn)
+ IO.send(@method, fn, "test", 0)
+ File.should.exist?(fn)
+ ensure
+ rm_r fn
+ end
+ end
+
+ it "truncates the file and writes the given string" do
+ IO.send(@method, @filename, "hello, world!")
+ File.read(@filename).should == "hello, world!"
+ end
+
+ it "doesn't truncate the file and writes the given string if an offset is given" do
+ IO.send(@method, @filename, "hello, world!", 0)
+ File.read(@filename).should == "hello, world!34567890123456789"
+ IO.send(@method, @filename, "hello, world!", 20)
+ File.read(@filename).should == "hello, world!3456789hello, world!"
+ end
+
+ it "doesn't truncate and writes at the given offset after passing empty opts" do
+ IO.send(@method, @filename, "hello world!", 1, **{})
+ File.read(@filename).should == "0hello world!34567890123456789"
+ end
+
+ it "accepts a :mode option" do
+ IO.send(@method, @filename, "hello, world!", mode: 'a')
+ File.read(@filename).should == "012345678901234567890123456789hello, world!"
+ IO.send(@method, @filename, "foo", 2, mode: 'w')
+ File.read(@filename).should == "\0\0foo"
+ end
+
+ it "accepts a :flags option without :mode one" do
+ IO.send(@method, @filename, "hello, world!", flags: File::CREAT)
+ File.read(@filename).should == "hello, world!"
+ end
+
+ it "raises an error if readonly mode is specified" do
+ -> { IO.send(@method, @filename, "abcde", mode: "r") }.should.raise(IOError)
+ end
+
+ it "truncates if empty :opts provided and offset skipped" do
+ IO.send(@method, @filename, "hello, world!", **{})
+ File.read(@filename).should == "hello, world!"
+ end
+end
diff --git a/spec/ruby/core/io/shared/chars.rb b/spec/ruby/core/io/shared/chars.rb
new file mode 100644
index 0000000000..efd4d5ee10
--- /dev/null
+++ b/spec/ruby/core/io/shared/chars.rb
@@ -0,0 +1,73 @@
+# -*- encoding: utf-8 -*-
+describe :io_chars, shared: true do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ ScratchPad.record []
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "yields each character" do
+ @io.readline.should == "Voici la ligne une.\n"
+
+ count = 0
+ @io.send(@method) do |c|
+ ScratchPad << c
+ break if 4 < count += 1
+ end
+
+ ScratchPad.recorded.should == ["Q", "u", "i", " ", "è"]
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ enum = @io.send(@method)
+ enum.should.instance_of?(Enumerator)
+ enum.first(5).should == ["V", "o", "i", "c", "i"]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.send(@method).size.should == nil
+ end
+ end
+ end
+ end
+
+ it "returns itself" do
+ @io.send(@method) { |c| }.should.equal?(@io)
+ end
+
+ it "returns an enumerator for a closed stream" do
+ IOSpecs.closed_io.send(@method).should.instance_of?(Enumerator)
+ end
+
+ it "raises an IOError when an enumerator created on a closed stream is accessed" do
+ -> { IOSpecs.closed_io.send(@method).first }.should.raise(IOError)
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send(@method) {} }.should.raise(IOError)
+ end
+end
+
+describe :io_chars_empty, shared: true do
+ before :each do
+ @name = tmp("io_each_char")
+ @io = new_io @name, "w+:utf-8"
+ ScratchPad.record []
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "does not yield any characters on an empty stream" do
+ @io.send(@method) { |c| ScratchPad << c }
+ ScratchPad.recorded.should == []
+ end
+end
diff --git a/spec/ruby/core/io/shared/codepoints.rb b/spec/ruby/core/io/shared/codepoints.rb
new file mode 100644
index 0000000000..21c756986f
--- /dev/null
+++ b/spec/ruby/core/io/shared/codepoints.rb
@@ -0,0 +1,54 @@
+# -*- encoding: utf-8 -*-
+require_relative '../fixtures/classes'
+
+describe :io_codepoints, shared: true do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ @enum = @io.send(@method)
+ end
+
+ after :each do
+ @io.close
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ @enum.should.instance_of?(Enumerator)
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @enum.size.should == nil
+ end
+ end
+ end
+ end
+
+ it "yields each codepoint" do
+ @enum.first(25).should == [
+ 86, 111, 105, 99, 105, 32, 108, 97, 32, 108, 105, 103, 110,
+ 101, 32, 117, 110, 101, 46, 10, 81, 117, 105, 32, 232
+ ]
+ end
+
+ it "yields each codepoint starting from the current position" do
+ @io.pos = 130
+ @enum.to_a.should == [101, 32, 115, 105, 120, 46, 10]
+ end
+
+ it "raises an error if reading invalid sequence" do
+ @io.pos = 60 # inside of a multibyte sequence
+ -> { @enum.first }.should.raise(ArgumentError)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @enum.to_a
+ $_.should == "test"
+ end
+
+ it "raises an IOError when self is not readable" do
+ -> { IOSpecs.closed_io.send(@method).to_a }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/each.rb b/spec/ruby/core/io/shared/each.rb
new file mode 100644
index 0000000000..ae60c3506a
--- /dev/null
+++ b/spec/ruby/core/io/shared/each.rb
@@ -0,0 +1,251 @@
+# -*- encoding: utf-8 -*-
+require_relative '../fixtures/classes'
+
+describe :io_each, shared: true do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ ScratchPad.record []
+ end
+
+ after :each do
+ @io.close if @io
+ end
+
+ describe "with no separator" do
+ it "yields each line to the passed block" do
+ @io.send(@method) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines
+ end
+
+ it "yields each line starting from the current position" do
+ @io.pos = 41
+ @io.send(@method) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines[2..-1]
+ end
+
+ it "returns self" do
+ @io.send(@method) { |l| l }.should.equal?(@io)
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.send(@method) { |s| s }
+ $_.should == "test"
+ end
+
+ it "raises an IOError when self is not readable" do
+ -> { IOSpecs.closed_io.send(@method) {} }.should.raise(IOError)
+ end
+
+ it "makes line count accessible via lineno" do
+ @io.send(@method) { ScratchPad << @io.lineno }
+ ScratchPad.recorded.should == [ 1,2,3,4,5,6,7,8,9 ]
+ end
+
+ it "makes line count accessible via $." do
+ @io.send(@method) { ScratchPad << $. }
+ ScratchPad.recorded.should == [ 1,2,3,4,5,6,7,8,9 ]
+ end
+
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ enum = @io.send(@method)
+ enum.should.instance_of?(Enumerator)
+
+ enum.each { |l| ScratchPad << l }
+ ScratchPad.recorded.should == IOSpecs.lines
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.send(@method).size.should == nil
+ end
+ end
+ end
+ end
+ end
+
+ describe "with limit" do
+ describe "when limit is 0" do
+ it "raises an ArgumentError" do
+ # must pass block so Enumerator is evaluated and raises
+ -> { @io.send(@method, 0){} }.should.raise(ArgumentError)
+ end
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ -> { @io.send(@method, 2**128){} }.should.raise(RangeError)
+ end
+ end
+
+ describe "when passed a String containing one space as a separator" do
+ it "uses the passed argument as the line separator" do
+ @io.send(@method, " ") { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines_space_separator
+ end
+
+ it "does not change $_" do
+ $_ = "test"
+ @io.send(@method, " ") { |s| }
+ $_.should == "test"
+ end
+
+ it "tries to convert the passed separator to a String using #to_str" do
+ obj = mock("to_str")
+ obj.stub!(:to_str).and_return(" ")
+
+ @io.send(@method, obj) { |l| ScratchPad << l }
+ ScratchPad.recorded.should == IOSpecs.lines_space_separator
+ end
+ end
+
+ describe "when passed nil as a separator" do
+ it "yields self's content starting from the current position when the passed separator is nil" do
+ @io.pos = 100
+ @io.send(@method, nil) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"]
+ end
+ end
+
+ describe "when passed an empty String as a separator" do
+ it "yields each paragraph" do
+ @io.send(@method, "") { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs
+ end
+
+ it "discards leading newlines" do
+ @io.readline
+ @io.readline
+ @io.send(@method, "") { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1]
+ end
+ end
+
+ describe "with both separator and limit" do
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ enum = @io.send(@method, nil, 1024)
+ enum.should.instance_of?(Enumerator)
+
+ enum.each { |l| ScratchPad << l }
+ ScratchPad.recorded.should == [IOSpecs.lines.join]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ @io.send(@method, nil, 1024).size.should == nil
+ end
+ end
+ end
+ end
+
+ describe "when a block is given" do
+ it "accepts an empty block" do
+ @io.send(@method, nil, 1024) {}.should.equal?(@io)
+ end
+
+ describe "when passed nil as a separator" do
+ it "yields self's content starting from the current position when the passed separator is nil" do
+ @io.pos = 100
+ @io.send(@method, nil, 1024) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == ["qui a linha cinco.\nHere is line six.\n"]
+ end
+ end
+
+ describe "when passed an empty String as a separator" do
+ it "yields each paragraph" do
+ @io.send(@method, "", 1024) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs
+ end
+
+ it "discards leading newlines" do
+ @io.readline
+ @io.readline
+ @io.send(@method, "", 1024) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs[1..-1]
+ end
+ end
+ end
+ end
+
+ describe "when passed chomp" do
+ it "yields each line without trailing newline characters to the passed block" do
+ @io.send(@method, chomp: true) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines_without_newline_characters
+ end
+
+ it "raises exception when options passed as Hash" do
+ -> {
+ @io.send(@method, { chomp: true }) { |s| }
+ }.should.raise(TypeError)
+
+ -> {
+ @io.send(@method, "\n", 1, { chomp: true }) { |s| }
+ }.should.raise(ArgumentError, "wrong number of arguments (given 3, expected 0..2)")
+ end
+ end
+
+ describe "when passed chomp and a separator" do
+ it "yields each line without separator to the passed block" do
+ @io.send(@method, " ", chomp: true) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines_space_separator_without_trailing_spaces
+ end
+ end
+
+ describe "when passed chomp and empty line as a separator" do
+ it "yields each paragraph without trailing new line characters" do
+ @io.send(@method, "", 1024, chomp: true) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.paragraphs_without_trailing_new_line_characters
+ end
+ end
+
+ describe "when passed chomp and nil as a separator" 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
+
+ describe "when passed chomp, nil as a separator, and a limit" do
+ it "yields each line of limit size without truncating trailing new line character" do
+ # 43 - is a size of the 1st paragraph in the file
+ @io.send(@method, nil, 43, chomp: true) { |s| ScratchPad << s }
+
+ ScratchPad.recorded.should == [
+ "Voici la ligne une.\nQui è la linea due.\n\n\n",
+ "Aquí está la línea tres.\n" + "Hier ist Zeile ",
+ "vier.\n\nEstá aqui a linha cinco.\nHere is li",
+ "ne six.\n"
+ ]
+ end
+ end
+
+ describe "when passed too many arguments" do
+ it "raises ArgumentError" do
+ -> {
+ @io.send(@method, "", 1, "excess argument", chomp: true) {}
+ }.should.raise(ArgumentError)
+ end
+ end
+end
+
+describe :io_each_default_separator, shared: true do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ ScratchPad.record []
+ suppress_warning {@sep, $/ = $/, " "}
+ end
+
+ after :each do
+ @io.close if @io
+ suppress_warning {$/ = @sep}
+ end
+
+ it "uses $/ as the default line separator" do
+ @io.send(@method) { |s| ScratchPad << s }
+ ScratchPad.recorded.should == IOSpecs.lines_space_separator
+ end
+end
diff --git a/spec/ruby/core/io/shared/gets_ascii.rb b/spec/ruby/core/io/shared/gets_ascii.rb
new file mode 100644
index 0000000000..2bd5470d99
--- /dev/null
+++ b/spec/ruby/core/io/shared/gets_ascii.rb
@@ -0,0 +1,19 @@
+# encoding: binary
+describe :io_gets_ascii, shared: true do
+ describe "with ASCII separator" do
+ before :each do
+ @name = tmp("gets_specs.txt")
+ touch(@name, "wb") { |f| f.print "this is a test\xFFtesty\ntestier" }
+
+ File.open(@name, "rb") { |f| @data = f.send(@method, "\xFF") }
+ end
+
+ after :each do
+ rm_r @name
+ end
+
+ it "returns the separator's character representation" do
+ @data.should == "this is a test\xFF"
+ end
+ end
+end
diff --git a/spec/ruby/core/io/shared/new.rb b/spec/ruby/core/io/shared/new.rb
new file mode 100644
index 0000000000..6f318ddee5
--- /dev/null
+++ b/spec/ruby/core/io/shared/new.rb
@@ -0,0 +1,413 @@
+require_relative '../fixtures/classes'
+
+# NOTE: should be synchronized with library/stringio/initialize_spec.rb
+
+# This group of specs may ONLY contain specs that do successfully create
+# an IO instance from the file descriptor returned by #new_fd helper.
+describe :io_new, shared: true do
+ before :each do
+ @name = tmp("io_new.txt")
+ @fd = new_fd @name
+ @io = nil
+ end
+
+ after :each do
+ if @io
+ @io.close
+ elsif @fd
+ IO.new(@fd, "w").close
+ end
+ rm_r @name
+ end
+
+ it "creates an IO instance from an Integer argument" do
+ @io = IO.send(@method, @fd, "w")
+ @io.should.instance_of?(IO)
+ end
+
+ it "creates an IO instance when STDOUT is closed" do
+ suppress_warning do
+ stdout = STDOUT
+ stdout_file = tmp("stdout.txt")
+
+ begin
+ @io = IO.send(@method, @fd, "w")
+ @io.should.instance_of?(IO)
+ ensure
+ STDOUT = stdout
+ rm_r stdout_file
+ end
+ end
+ end
+
+ it "creates an IO instance when STDERR is closed" do
+ suppress_warning do
+ stderr = STDERR
+ stderr_file = tmp("stderr.txt")
+ STDERR = new_io stderr_file
+ STDERR.close
+
+ begin
+ @io = IO.send(@method, @fd, "w")
+ @io.should.instance_of?(IO)
+ ensure
+ STDERR = stderr
+ rm_r stderr_file
+ end
+ end
+ end
+
+ it "calls #to_int on an object to convert to an Integer" do
+ obj = mock("file descriptor")
+ obj.should_receive(:to_int).and_return(@fd)
+ @io = IO.send(@method, obj, "w")
+ @io.should.instance_of?(IO)
+ end
+
+ it "accepts options as keyword arguments" do
+ @io = IO.send(@method, @fd, "w", flags: File::CREAT)
+ @io.write("foo").should == 3
+
+ -> {
+ IO.send(@method, @fd, "w", {flags: File::CREAT})
+ }.should.raise(ArgumentError, "wrong number of arguments (given 3, expected 1..2)")
+ end
+
+ it "accepts a :mode option" do
+ @io = IO.send(@method, @fd, mode: "w")
+ @io.write("foo").should == 3
+ end
+
+ it "accepts a mode argument set to nil with a valid :mode option" do
+ @io = IO.send(@method, @fd, nil, mode: "w")
+ @io.write("foo").should == 3
+ end
+
+ it "accepts a mode argument with a :mode option set to nil" do
+ @io = IO.send(@method, @fd, "w", mode: nil)
+ @io.write("foo").should == 3
+ end
+
+ it "uses the external encoding specified in the mode argument" do
+ @io = IO.send(@method, @fd, 'w:utf-8')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ end
+
+ it "uses the external and the internal encoding specified in the mode argument" do
+ @io = IO.send(@method, @fd, 'w:utf-8:ISO-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "uses the external encoding specified via the :external_encoding option" do
+ @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ end
+
+ it "uses the internal encoding specified via the :internal_encoding option" do
+ @io = IO.send(@method, @fd, 'w', internal_encoding: 'ibm866')
+ @io.internal_encoding.to_s.should == 'IBM866'
+ end
+
+ it "uses the colon-separated encodings specified via the :encoding option" do
+ @io = IO.send(@method, @fd, 'w', encoding: 'utf-8:ISO-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "uses the :encoding option as the external encoding when only one is given" do
+ @io = IO.send(@method, @fd, 'w', encoding: 'ISO-8859-1')
+ @io.external_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "uses the :encoding options as the external encoding when it's an Encoding object" do
+ @io = IO.send(@method, @fd, 'w', encoding: Encoding::ISO_8859_1)
+ @io.external_encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "ignores the :encoding option when the :external_encoding option is present" do
+ -> {
+ @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8', encoding: 'iso-8859-1:iso-8859-1')
+ }.should complain(/Ignoring encoding parameter/)
+ @io.external_encoding.to_s.should == 'UTF-8'
+ end
+
+ it "ignores the :encoding option when the :internal_encoding option is present" do
+ -> {
+ @io = IO.send(@method, @fd, 'w', internal_encoding: 'ibm866', encoding: 'iso-8859-1:iso-8859-1')
+ }.should complain(/Ignoring encoding parameter/)
+ @io.internal_encoding.to_s.should == 'IBM866'
+ end
+
+ it "uses the encoding specified via the :mode option hash" do
+ @io = IO.send(@method, @fd, mode: 'w:utf-8:ISO-8859-1')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "ignores the :internal_encoding option when the same as the external encoding" do
+ @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8', internal_encoding: 'utf-8')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == ''
+ end
+
+ it "sets internal encoding to nil when passed '-'" do
+ @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8', internal_encoding: '-')
+ @io.external_encoding.to_s.should == 'UTF-8'
+ @io.internal_encoding.to_s.should == ''
+ end
+
+ it "sets binmode from mode string" do
+ @io = IO.send(@method, @fd, 'wb')
+ @io.should.binmode?
+ end
+
+ it "does not set binmode without being asked" do
+ @io = IO.send(@method, @fd, 'w')
+ @io.should_not.binmode?
+ end
+
+ it "sets binmode from :binmode option" do
+ @io = IO.send(@method, @fd, 'w', binmode: true)
+ @io.should.binmode?
+ end
+
+ it "does not set binmode from false :binmode" do
+ @io = IO.send(@method, @fd, 'w', binmode: false)
+ @io.should_not.binmode?
+ end
+
+ it "sets external encoding to binary with binmode in mode string" do
+ @io = IO.send(@method, @fd, 'wb')
+ @io.external_encoding.should == Encoding::BINARY
+ end
+
+ # #5917
+ it "sets external encoding to binary with :binmode option" do
+ @io = IO.send(@method, @fd, 'w', binmode: true)
+ @io.external_encoding.should == Encoding::BINARY
+ end
+
+ it "does not use binary encoding when mode encoding is specified" do
+ @io = IO.send(@method, @fd, 'wb:iso-8859-1')
+ @io.external_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "does not use binary encoding when :encoding option is specified" do
+ @io = IO.send(@method, @fd, 'wb', encoding: "iso-8859-1")
+ @io.external_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "does not use binary encoding when :external_encoding option is specified" do
+ @io = IO.send(@method, @fd, 'wb', external_encoding: "iso-8859-1")
+ @io.external_encoding.to_s.should == 'ISO-8859-1'
+ end
+
+ it "does not use binary encoding when :internal_encoding option is specified" do
+ @io = IO.send(@method, @fd, 'wb', internal_encoding: "ibm866")
+ @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)
+ }.should.raise(ArgumentError)
+ end
+
+ it "coerces mode with #to_str" do
+ mode = mock("mode")
+ mode.should_receive(:to_str).and_return('w')
+ @io = IO.send(@method, @fd, mode)
+ end
+
+ it "coerces mode with #to_int" do
+ mode = mock("mode")
+ mode.should_receive(:to_int).and_return(File::WRONLY)
+ @io = IO.send(@method, @fd, mode)
+ end
+
+ it "coerces mode with #to_str when passed in options" do
+ mode = mock("mode")
+ mode.should_receive(:to_str).and_return('w')
+ @io = IO.send(@method, @fd, mode: mode)
+ end
+
+ it "coerces mode with #to_int when passed in options" do
+ mode = mock("mode")
+ mode.should_receive(:to_int).and_return(File::WRONLY)
+ @io = IO.send(@method, @fd, mode: mode)
+ end
+
+ it "coerces :encoding option with #to_str" do
+ encoding = mock("encoding")
+ encoding.should_receive(:to_str).and_return('utf-8')
+ @io = IO.send(@method, @fd, 'w', encoding: encoding)
+ end
+
+ it "coerces :external_encoding option with #to_str" do
+ encoding = mock("encoding")
+ encoding.should_receive(:to_str).and_return('utf-8')
+ @io = IO.send(@method, @fd, 'w', external_encoding: encoding)
+ end
+
+ it "coerces :internal_encoding option with #to_str" do
+ encoding = mock("encoding")
+ encoding.should_receive(:to_str).at_least(:once).and_return('utf-8')
+ @io = IO.send(@method, @fd, 'w', internal_encoding: encoding)
+ end
+
+ it "coerces options as third argument with #to_hash" do
+ options = mock("options")
+ options.should_receive(:to_hash).and_return({})
+ @io = IO.send(@method, @fd, 'w', **options)
+ end
+
+ it "coerces options as second argument with #to_hash" do
+ options = mock("options")
+ options.should_receive(:to_hash).and_return({})
+ @io = IO.send(@method, @fd, **options)
+ end
+
+ it "accepts an :autoclose option" do
+ @io = IO.send(@method, @fd, 'w', autoclose: false)
+ @io.should_not.autoclose?
+ @io.autoclose = true
+ end
+
+ it "accepts any truthy option :autoclose" do
+ @io = IO.send(@method, @fd, 'w', autoclose: 42)
+ @io.should.autoclose?
+ end
+end
+
+# This group of specs may ONLY contain specs that do not actually create
+# an IO instance from the file descriptor returned by #new_fd helper.
+describe :io_new_errors, shared: true do
+ before :each do
+ @name = tmp("io_new.txt")
+ @fd = new_fd @name
+ end
+
+ after :each do
+ IO.new(@fd, "w").close if @fd
+ rm_r @name
+ end
+
+ it "raises an Errno::EBADF if the file descriptor is not valid" do
+ -> { IO.send(@method, -1, "w") }.should.raise(Errno::EBADF)
+ end
+
+ it "raises an IOError if passed a closed stream" do
+ -> { IO.send(@method, IOSpecs.closed_io.fileno, 'w') }.should.raise(IOError)
+ end
+
+ platform_is_not :windows do
+ it "raises an Errno::EINVAL if the new mode is not compatible with the descriptor's current mode" do
+ -> { IO.send(@method, @fd, "r") }.should.raise(Errno::EINVAL)
+ end
+ end
+
+ it "raises ArgumentError if passed an empty mode string" do
+ -> { IO.send(@method, @fd, "") }.should.raise(ArgumentError)
+ end
+
+ it "raises an error if passed modes two ways" do
+ -> {
+ IO.send(@method, @fd, "w", mode: "w")
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises an error if passed encodings two ways" do
+ -> {
+ @io = IO.send(@method, @fd, 'w:ISO-8859-1', encoding: 'ISO-8859-1')
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, 'w:ISO-8859-1', external_encoding: 'ISO-8859-1')
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, 'w:ISO-8859-1', internal_encoding: 'ISO-8859-1')
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, 'w:ISO-8859-1:UTF-8', internal_encoding: 'ISO-8859-1')
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises an error if passed matching binary/text mode two ways" do
+ -> {
+ @io = IO.send(@method, @fd, "wb", binmode: true)
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, "wt", textmode: true)
+ }.should.raise(ArgumentError)
+
+ -> {
+ @io = IO.send(@method, @fd, "wb", textmode: false)
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, "wt", binmode: false)
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises an error if passed conflicting binary/text mode two ways" do
+ -> {
+ @io = IO.send(@method, @fd, "wb", binmode: false)
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, "wt", textmode: false)
+ }.should.raise(ArgumentError)
+
+ -> {
+ @io = IO.send(@method, @fd, "wb", textmode: true)
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, "wt", binmode: true)
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises an error when trying to set both binmode and textmode" do
+ -> {
+ @io = IO.send(@method, @fd, "w", textmode: true, binmode: true)
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, File::Constants::WRONLY, textmode: true, binmode: true)
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError if not passed a hash or nil for options" do
+ -> {
+ @io = IO.send(@method, @fd, 'w', false)
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, false, false)
+ }.should.raise(ArgumentError)
+ -> {
+ @io = IO.send(@method, @fd, nil, false)
+ }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError if passed a hash for mode and nil for options" do
+ -> {
+ @io = IO.send(@method, @fd, {mode: 'w'}, nil)
+ }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/pos.rb b/spec/ruby/core/io/shared/pos.rb
new file mode 100644
index 0000000000..f4d0405863
--- /dev/null
+++ b/spec/ruby/core/io/shared/pos.rb
@@ -0,0 +1,78 @@
+describe :io_pos, shared: true do
+ before :each do
+ @fname = tmp('test.txt')
+ File.open(@fname, 'w') { |f| f.write "123" }
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "gets the offset" do
+ File.open @fname do |f|
+ f.send(@method).should == 0
+ f.read 1
+ f.send(@method).should == 1
+ f.read 2
+ f.send(@method).should == 3
+ end
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send(@method) }.should.raise(IOError)
+ end
+
+ it "resets #eof?" do
+ open @fname do |io|
+ io.read 1
+ io.read 1
+ io.send(@method)
+ io.should_not.eof?
+ end
+ end
+end
+
+describe :io_set_pos, shared: true do
+ before :each do
+ @fname = tmp('test.txt')
+ File.open(@fname, 'w') { |f| f.write "123" }
+ end
+
+ after :each do
+ rm_r @fname
+ end
+
+ it "sets the offset" do
+ File.open @fname do |f|
+ val1 = f.read 1
+ f.send @method, 0
+ f.read(1).should == val1
+ end
+ end
+
+ it "converts arguments to Integers" do
+ File.open @fname do |io|
+ o = mock("o")
+ o.should_receive(:to_int).and_return(1)
+
+ io.send @method, o
+ io.pos.should == 1
+ end
+ end
+
+ it "raises TypeError when cannot convert implicitly argument to Integer" do
+ File.open @fname do |io|
+ -> { io.send @method, Object.new }.should.raise(TypeError, "no implicit conversion of Object into Integer")
+ end
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ File.open @fname do |io|
+ -> { io.send @method, 2**128 }.should.raise(RangeError)
+ end
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send @method, 0 }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/readlines.rb b/spec/ruby/core/io/shared/readlines.rb
new file mode 100644
index 0000000000..f54fccc2e3
--- /dev/null
+++ b/spec/ruby/core/io/shared/readlines.rb
@@ -0,0 +1,257 @@
+describe :io_readlines, shared: true do
+ it "raises TypeError if the first parameter is nil" do
+ -> { IO.send(@method, nil, &@object) }.should.raise(TypeError)
+ end
+
+ it "raises an Errno::ENOENT if the file does not exist" do
+ name = tmp("nonexistent.txt")
+ -> { IO.send(@method, name, &@object) }.should.raise(Errno::ENOENT)
+ end
+
+ it "yields a single string with entire content when the separator is nil" do
+ result = IO.send(@method, @name, nil, &@object)
+ (result ? result : ScratchPad.recorded).should == [IO.read(@name)]
+ end
+
+ it "yields a sequence of paragraphs when the separator is an empty string" do
+ result = IO.send(@method, @name, "", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_empty_separator
+ end
+
+ it "yields a sequence of lines without trailing newline characters when chomp is passed" do
+ result = IO.send(@method, @name, chomp: true, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_without_newline_characters
+ end
+end
+
+describe :io_readlines_options_19, shared: true do
+ before :each do
+ @filename = tmp("io readlines options")
+ end
+
+ after :each do
+ rm_r @filename
+ end
+
+ describe "when passed name" do
+ it "calls #to_path to convert the name" do
+ name = mock("io name to_path")
+ name.should_receive(:to_path).and_return(@name)
+ IO.send(@method, name, &@object)
+ end
+
+ it "defaults to $/ as the separator" do
+ result = IO.send(@method, @name, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines
+ end
+ end
+
+ describe "when passed name, object" do
+ it "calls #to_str to convert the object to a separator" do
+ sep = mock("io readlines separator")
+ sep.should_receive(:to_str).at_least(1).and_return(" ")
+ result = IO.send(@method, @name, sep, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator
+ end
+
+ describe "when the object is an Integer" do
+ before :each do
+ @sep = $/
+ end
+
+ after :each do
+ suppress_warning {$/ = @sep}
+ end
+
+ it "defaults to $/ as the separator" do
+ suppress_warning {$/ = " "}
+ result = IO.send(@method, @name, 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "uses the object as a limit if it is an Integer" do
+ result = IO.send(@method, @name, 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_limit
+ end
+
+ it "ignores the object as a limit if it is negative" do
+ result = IO.send(@method, @name, -2, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines
+ end
+
+ it "does not accept Integers that don't fit in a C off_t" do
+ -> { IO.send(@method, @name, 2**128, &@object) }.should.raise(RangeError)
+ end
+
+ describe "when passed limit" do
+ it "raises ArgumentError when passed 0 as a limit" do
+ -> { IO.send(@method, @name, 0, &@object) }.should.raise(ArgumentError)
+ end
+ end
+ end
+
+ describe "when the object is a String" do
+ it "uses the value as the separator" do
+ result = IO.send(@method, @name, " ", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator
+ end
+
+ it "accepts non-ASCII data as separator" do
+ result = IO.send(@method, @name, "\303\250".dup.force_encoding("utf-8"), &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_arbitrary_separator
+ end
+ end
+
+ describe "when the object is an options Hash" do
+ it "raises TypeError exception" do
+ -> {
+ IO.send(@method, @name, { chomp: true }, &@object)
+ }.should.raise(TypeError)
+ end
+ end
+
+ describe "when the object is neither Integer nor String" do
+ it "raises TypeError exception" do
+ obj = mock("not io readlines limit")
+
+ -> {
+ IO.send(@method, @name, obj, &@object)
+ }.should.raise(TypeError)
+ end
+ end
+ end
+
+ describe "when passed name, keyword arguments" do
+ it "uses the keyword arguments as options" do
+ result = IO.send(@method, @name, mode: "r", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines
+ end
+ end
+
+ describe "when passed name, object, object" do
+ describe "when the first object is a String" do
+ it "uses the second object as a limit if it is an Integer" do
+ result = IO.send(@method, @name, " ", 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "calls #to_int to convert the second object" do
+ limit = mock("io readlines limit")
+ limit.should_receive(:to_int).at_least(1).and_return(10)
+ result = IO.send(@method, @name, " ", limit, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+ end
+
+ describe "when the first object is not a String or Integer" do
+ it "calls #to_str to convert the object to a String" do
+ sep = mock("io readlines separator")
+ sep.should_receive(:to_str).at_least(1).and_return(" ")
+ result = IO.send(@method, @name, sep, 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "uses the second object as a limit if it is an Integer" do
+ result = IO.send(@method, @name, " ", 10, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "calls #to_int to convert the second object" do
+ limit = mock("io readlines limit")
+ limit.should_receive(:to_int).at_least(1).and_return(10)
+ result = IO.send(@method, @name, " ", limit, &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+ end
+
+ describe "when the second object is neither Integer nor String" do
+ it "raises TypeError exception" do
+ obj = mock("not io readlines limit")
+
+ -> {
+ IO.send(@method, @name, " ", obj, &@object)
+ }.should.raise(TypeError)
+ end
+ end
+
+ describe "when the second object is an options Hash" do
+ it "raises TypeError exception" do
+ -> {
+ IO.send(@method, @name, "", { chomp: true }, &@object)
+ }.should.raise(TypeError)
+ end
+ end
+ end
+
+ describe "when passed name, object, keyword arguments" do
+ describe "when the first object is an Integer" do
+ it "uses the keyword arguments as options" do
+ -> do
+ IO.send(@method, @filename, 10, mode: "w", &@object)
+ end.should.raise(IOError)
+ end
+ end
+
+ describe "when the first object is a String" do
+ it "uses the keyword arguments as options" do
+ -> do
+ IO.send(@method, @filename, " ", mode: "w", &@object)
+ end.should.raise(IOError)
+ end
+ end
+
+ describe "when the first object is not a String or Integer" do
+ it "uses the keyword arguments as options" do
+ sep = mock("io readlines separator")
+ sep.should_receive(:to_str).at_least(1).and_return(" ")
+
+ -> do
+ IO.send(@method, @filename, sep, mode: "w", &@object)
+ end.should.raise(IOError)
+ end
+ end
+ end
+
+ describe "when passed name, separator, limit, keyword arguments" do
+ it "calls #to_path to convert the name object" do
+ name = mock("io name to_path")
+ name.should_receive(:to_path).and_return(@name)
+ result = IO.send(@method, name, " ", 10, mode: "r", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "calls #to_str to convert the separator object" do
+ sep = mock("io readlines separator")
+ sep.should_receive(:to_str).at_least(1).and_return(" ")
+ result = IO.send(@method, @name, sep, 10, mode: "r", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "calls #to_int to convert the limit argument" do
+ limit = mock("io readlines limit")
+ limit.should_receive(:to_int).at_least(1).and_return(10)
+ result = IO.send(@method, @name, " ", limit, mode: "r", &@object)
+ (result ? result : ScratchPad.recorded).should == IOSpecs.lines_space_separator_limit
+ end
+
+ it "uses the keyword arguments as options" do
+ -> do
+ IO.send(@method, @filename, " ", 10, mode: "w", &@object)
+ end.should.raise(IOError)
+ end
+
+ describe "when passed chomp, nil as a separator, and a limit" do
+ it "yields each line of limit size without truncating trailing new line character" do
+ # 43 - is a size of the 1st paragraph in the file
+ result = IO.send(@method, @name, nil, 43, chomp: true, &@object)
+
+ (result ? result : ScratchPad.recorded).should == [
+ "Voici la ligne une.\nQui è la linea due.\n\n\n",
+ "Aquí está la línea tres.\n" + "Hier ist Zeile ",
+ "vier.\n\nEstá aqui a linha cinco.\nHere is li",
+ "ne six.\n"
+ ]
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/io/shared/tty.rb b/spec/ruby/core/io/shared/tty.rb
new file mode 100644
index 0000000000..1dc0e95739
--- /dev/null
+++ b/spec/ruby/core/io/shared/tty.rb
@@ -0,0 +1,24 @@
+require_relative '../fixtures/classes'
+
+describe :io_tty, shared: true do
+ platform_is_not :windows do
+ it "returns true if this stream is a terminal device (TTY)" do
+ begin
+ # check to enabled tty
+ File.open('/dev/tty') {}
+ rescue Errno::ENXIO
+ skip "workaround for not configured environment like OS X"
+ else
+ File.open('/dev/tty') { |f| f.send(@method) }.should == true
+ end
+ end
+ end
+
+ it "returns false if this stream is not a terminal device (TTY)" do
+ File.open(__FILE__) { |f| f.send(@method) }.should == false
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send @method }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/shared/write.rb b/spec/ruby/core/io/shared/write.rb
new file mode 100644
index 0000000000..5de9fe4335
--- /dev/null
+++ b/spec/ruby/core/io/shared/write.rb
@@ -0,0 +1,154 @@
+# encoding: utf-8
+require_relative '../fixtures/classes'
+
+describe :io_write, shared: true do
+ before :each do
+ @filename = tmp("IO_syswrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file.send(@method, "012345678901234567890123456789")
+ end
+ @file = File.open(@filename, "r+")
+ @readonly_file = File.open(@filename)
+ end
+
+ after :each do
+ @readonly_file.close if @readonly_file
+ @file.close if @file
+ rm_r @filename
+ end
+
+ it "coerces the argument to a string using to_s" do
+ (obj = mock('test')).should_receive(:to_s).and_return('a string')
+ @file.send(@method, obj)
+ end
+
+ it "checks if the file is writable if writing more than zero bytes" do
+ -> { @readonly_file.send(@method, "abcde") }.should.raise(IOError)
+ end
+
+ it "returns the number of bytes written" do
+ written = @file.send(@method, "abcde")
+ written.should == 5
+ end
+
+ it "invokes to_s on non-String argument" do
+ data = "abcdefgh9876"
+ (obj = mock(data)).should_receive(:to_s).and_return(data)
+ @file.send(@method, obj)
+ @file.seek(0)
+ @file.read(data.size).should == data
+ end
+
+ it "writes all of the string's bytes without buffering if mode is sync" do
+ @file.sync = true
+ written = @file.send(@method, "abcde")
+ written.should == 5
+ File.open(@filename) do |file|
+ file.read(10).should == "abcde56789"
+ end
+ end
+
+ it "does not warn if called after IO#read" do
+ @file.read(5)
+ -> { @file.send(@method, "fghij") }.should_not complain
+ end
+
+ it "writes to the current position after IO#read" do
+ @file.read(5)
+ @file.send(@method, "abcd")
+ @file.rewind
+ @file.read.should == "01234abcd901234567890123456789"
+ end
+
+ it "advances the file position by the count of given bytes" do
+ @file.send(@method, "abcde")
+ @file.read(10).should == "5678901234"
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.send(@method, "hello") }.should.raise(IOError)
+ end
+
+ describe "on a pipe" do
+ before :each do
+ @r, @w = IO.pipe
+ end
+
+ after :each do
+ @r.close
+ @w.close
+ end
+
+ it "writes the given String to the pipe" do
+ @w.send(@method, "foo")
+ @w.close
+ @r.read.should == "foo"
+ end
+
+ # [ruby-core:90895] RJIT worker may leave fd open in a forked child.
+ # For instance, RJIT creates a worker before @r.close with fork(), @r.close happens,
+ # and the RJIT worker keeps the pipe open until the worker execve().
+ # TODO: consider acquiring GVL from RJIT worker.
+ guard_not -> { defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? } do
+ it "raises Errno::EPIPE if the read end is closed and does not die from SIGPIPE" do
+ @r.close
+ -> { @w.send(@method, "foo") }.should.raise(Errno::EPIPE, /Broken pipe/)
+ end
+ end
+ end
+end
+
+describe :io_write_transcode, shared: true do
+ before :each do
+ @transcode_filename = tmp("io_write_transcode")
+ end
+
+ after :each do
+ rm_r @transcode_filename
+ end
+
+ it "transcodes the given string when the external encoding is set and neither is BINARY" do
+ utf8_str = "hello"
+
+ File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file|
+ file.external_encoding.should == Encoding::UTF_16BE
+ file.send(@method, utf8_str)
+ end
+
+ result = File.binread(@transcode_filename)
+ expected = [0, 104, 0, 101, 0, 108, 0, 108, 0, 111] # UTF-16BE bytes for "hello"
+
+ result.bytes.should == expected
+ end
+
+ it "transcodes the given string when the external encoding is set and the string encoding is BINARY" do
+ str = "été".b
+
+ File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file|
+ file.external_encoding.should == Encoding::UTF_16BE
+ -> { file.send(@method, str) }.should.raise(Encoding::UndefinedConversionError)
+ end
+ end
+end
+
+describe :io_write_no_transcode, shared: true do
+ before :each do
+ @transcode_filename = tmp("io_write_no_transcode")
+ end
+
+ after :each do
+ rm_r @transcode_filename
+ end
+
+ it "does not transcode the given string even when the external encoding is set" do
+ utf8_str = "hello"
+
+ File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file|
+ file.external_encoding.should == Encoding::UTF_16BE
+ file.send(@method, utf8_str)
+ end
+
+ result = File.binread(@transcode_filename)
+ result.bytes.should == utf8_str.bytes
+ end
+end
diff --git a/spec/ruby/core/io/stat_spec.rb b/spec/ruby/core/io/stat_spec.rb
new file mode 100644
index 0000000000..f9fc232ee0
--- /dev/null
+++ b/spec/ruby/core/io/stat_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#stat" do
+ before :each do
+ cmd = platform_is(:windows) ? 'rem' : 'cat'
+ @io = IO.popen cmd, "r+"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.stat }.should.raise(IOError)
+ end
+
+ it "returns a File::Stat object for the stream" do
+ STDOUT.stat.should.instance_of?(File::Stat)
+ end
+
+ it "can stat pipes" do
+ @io.stat.should.instance_of?(File::Stat)
+ end
+end
diff --git a/spec/ruby/core/io/sync_spec.rb b/spec/ruby/core/io/sync_spec.rb
new file mode 100644
index 0000000000..b537db335b
--- /dev/null
+++ b/spec/ruby/core/io/sync_spec.rb
@@ -0,0 +1,64 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#sync=" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "sets the sync mode to true or false" do
+ @io.sync = true
+ @io.sync.should == true
+ @io.sync = false
+ @io.sync.should == false
+ end
+
+ it "accepts non-boolean arguments" do
+ @io.sync = 10
+ @io.sync.should == true
+ @io.sync = nil
+ @io.sync.should == false
+ @io.sync = Object.new
+ @io.sync.should == true
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.sync = true }.should.raise(IOError)
+ end
+end
+
+describe "IO#sync" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns the current sync mode" do
+ @io.sync.should == false
+ end
+
+ it "raises an IOError on closed stream" do
+ -> { IOSpecs.closed_io.sync }.should.raise(IOError)
+ end
+end
+
+describe "IO#sync" do
+ it "is false by default for STDIN" do
+ STDIN.sync.should == false
+ end
+
+ it "is false by default for STDOUT" do
+ STDOUT.sync.should == false
+ end
+
+ it "is true by default for STDERR" do
+ STDERR.sync.should == true
+ end
+end
diff --git a/spec/ruby/core/io/sysopen_spec.rb b/spec/ruby/core/io/sysopen_spec.rb
new file mode 100644
index 0000000000..325d51ed23
--- /dev/null
+++ b/spec/ruby/core/io/sysopen_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+
+describe "IO.sysopen" do
+ before :each do
+ @filename = tmp("rubinius-spec-io-sysopen-#{$$}.txt")
+ @fd = nil
+ end
+
+ after :each do
+ IO.for_fd(@fd).close if @fd
+ rm_r @filename
+ end
+
+ it "returns the file descriptor for a given path" do
+ @fd = IO.sysopen(@filename, "w")
+ @fd.should.is_a?(Integer)
+ @fd.should_not.equal?(0)
+ end
+
+ # opening a directory is not supported on Windows
+ platform_is_not :windows do
+ it "works on directories" do
+ @fd = IO.sysopen(tmp("")) # /tmp
+ @fd.should.is_a?(Integer)
+ @fd.should_not.equal?(0)
+ end
+ end
+
+ it "calls #to_path to convert an object to a path" do
+ path = mock('sysopen to_path')
+ path.should_receive(:to_path).and_return(@filename)
+ @fd = IO.sysopen(path, 'w')
+ end
+
+ it "accepts a mode as second argument" do
+ -> { @fd = IO.sysopen(@filename, "w") }.should_not.raise
+ @fd.should_not.equal?(0)
+ end
+
+ it "accepts permissions as third argument" do
+ @fd = IO.sysopen(@filename, "w", 777)
+ @fd.should_not.equal?(0)
+ end
+
+ it "accepts mode & permission that are nil" do
+ touch @filename # create the file
+ @fd = IO.sysopen(@filename, nil, nil)
+ @fd.should_not.equal?(0)
+ end
+end
diff --git a/spec/ruby/core/io/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb
new file mode 100644
index 0000000000..2d58db2250
--- /dev/null
+++ b/spec/ruby/core/io/sysread_spec.rb
@@ -0,0 +1,137 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#sysread on a file" do
+ before :each do
+ @file_name = tmp("IO_sysread_file") + $$.to_s
+ File.open(@file_name, "w") do |f|
+ # write some stuff
+ f.write("012345678901234567890123456789\nabcdef")
+ end
+ @file = File.open(@file_name, "r+")
+ end
+
+ after :each do
+ @file.close
+ rm_r @file_name
+ end
+
+ it "reads the specified number of bytes from the file" do
+ @file.sysread(15).should == "012345678901234"
+ end
+
+ it "reads the specified number of bytes from the file to the buffer" do
+ buf = +"" # empty buffer
+ @file.sysread(15, buf).should == buf
+ buf.should == "012345678901234"
+
+ @file.rewind
+
+ buf = +"ABCDE" # small buffer
+ @file.sysread(15, buf).should == buf
+ buf.should == "012345678901234"
+
+ @file.rewind
+
+ buf = +"ABCDE" * 5 # large buffer
+ @file.sysread(15, buf).should == buf
+ buf.should == "012345678901234"
+ end
+
+ it "coerces the second argument to string and uses it as a buffer" do
+ buf = +"ABCDE"
+ (obj = mock("buff")).should_receive(:to_str).any_number_of_times.and_return(buf)
+ @file.sysread(15, obj).should == buf
+ buf.should == "012345678901234"
+ end
+
+ it "advances the position of the file by the specified number of bytes" do
+ @file.sysread(15)
+ @file.sysread(5).should == "56789"
+ end
+
+ it "raises an error when called after buffered reads" do
+ @file.readline
+ -> { @file.sysread(5) }.should.raise(IOError)
+ end
+
+ it "reads normally even when called immediately after a buffered IO#read" do
+ @file.read(15)
+ @file.sysread(5).should == "56789"
+ end
+
+ it "does not raise error if called after IO#read followed by IO#write" do
+ @file.read(5)
+ @file.write("abcde")
+ -> { @file.sysread(5) }.should_not.raise(IOError)
+ end
+
+ it "does not raise error if called after IO#read followed by IO#syswrite" do
+ @file.read(5)
+ @file.syswrite("abcde")
+ -> { @file.sysread(5) }.should_not.raise(IOError)
+ end
+
+ it "reads updated content after the flushed buffered IO#write" do
+ @file.write("abcde")
+ @file.flush
+ @file.sysread(5).should == "56789"
+ File.open(@file_name) do |f|
+ f.sysread(10).should == "abcde56789"
+ end
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.sysread(5) }.should.raise(IOError)
+ end
+
+ it "immediately returns an empty string if the length argument is 0" do
+ @file.sysread(0).should == ""
+ end
+
+ it "immediately returns the given buffer if the length argument is 0" do
+ buffer = +"existing content"
+ @file.sysread(0, buffer).should == buffer
+ buffer.should == "existing content"
+ end
+
+ it "discards the existing buffer content upon successful read" do
+ buffer = +"existing content"
+ @file.sysread(11, buffer).should.equal?(buffer)
+ buffer.should == "01234567890"
+ end
+
+ it "discards the existing buffer content upon error" do
+ buffer = +"existing content"
+ @file.seek(0, :END)
+ -> { @file.sysread(1, buffer) }.should.raise(EOFError)
+ buffer.should.empty?
+ end
+
+ it "preserves the encoding of the given buffer" do
+ buffer = ''.encode(Encoding::ISO_8859_1)
+ string = @file.sysread(10, buffer)
+
+ buffer.encoding.should == Encoding::ISO_8859_1
+ end
+end
+
+describe "IO#sysread" do
+ before do
+ @read, @write = IO.pipe
+ end
+
+ after do
+ @read.close
+ @write.close
+ end
+
+ it "returns a smaller string if less than size bytes are available" do
+ @write.syswrite "ab"
+ @read.sysread(3).should == "ab"
+ end
+
+ it "raises ArgumentError when length is less than 0" do
+ -> { @read.sysread(-1) }.should.raise(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/io/sysseek_spec.rb b/spec/ruby/core/io/sysseek_spec.rb
new file mode 100644
index 0000000000..2384895dc5
--- /dev/null
+++ b/spec/ruby/core/io/sysseek_spec.rb
@@ -0,0 +1,49 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/pos'
+
+describe "IO#sysseek" do
+ it_behaves_like :io_set_pos, :sysseek
+end
+
+describe "IO#sysseek" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "moves the read position relative to the current position with SEEK_CUR" do
+ @io.sysseek(10, IO::SEEK_CUR)
+ @io.readline.should == "igne une.\n"
+ end
+
+ it "raises an error when called after buffered reads" do
+ @io.readline
+ -> { @io.sysseek(-5, IO::SEEK_CUR) }.should.raise(IOError)
+ end
+
+ it "seeks normally even when called immediately after a buffered IO#read" do
+ @io.read(15)
+ @io.sysseek(-5, IO::SEEK_CUR).should == 10
+ end
+
+ it "moves the read position relative to the start with SEEK_SET" do
+ @io.sysseek(43, IO::SEEK_SET)
+ @io.readline.should == "Aquí está la línea tres.\n"
+ end
+
+ it "moves the read position relative to the end with SEEK_END" do
+ @io.sysseek(1, IO::SEEK_END)
+
+ # this is the safest way of checking the EOF when
+ # sys-* methods are invoked
+ -> { @io.sysread(1) }.should.raise(EOFError)
+
+ @io.sysseek(-25, IO::SEEK_END)
+ @io.sysread(7).should == "cinco.\n"
+ end
+end
diff --git a/spec/ruby/core/io/syswrite_spec.rb b/spec/ruby/core/io/syswrite_spec.rb
new file mode 100644
index 0000000000..8bf61a27c3
--- /dev/null
+++ b/spec/ruby/core/io/syswrite_spec.rb
@@ -0,0 +1,82 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+
+describe "IO#syswrite on a file" do
+ before :each do
+ @filename = tmp("IO_syswrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file.syswrite("012345678901234567890123456789")
+ end
+ @file = File.open(@filename, "r+")
+ @readonly_file = File.open(@filename)
+ end
+
+ after :each do
+ @file.close
+ @readonly_file.close
+ rm_r @filename
+ end
+
+ it "writes all of the string's bytes but does not buffer them" do
+ written = @file.syswrite("abcde")
+ written.should == 5
+ File.open(@filename) do |file|
+ file.sysread(10).should == "abcde56789"
+ file.seek(0)
+ @file.fsync
+ file.sysread(10).should == "abcde56789"
+ end
+ end
+
+ it "does not modify the passed argument" do
+ File.open(@filename, "w") do |f|
+ f.set_encoding(Encoding::IBM437)
+ # A character whose codepoint differs between UTF-8 and IBM437
+ f.syswrite("ƒ".freeze)
+ end
+
+ File.binread(@filename).bytes.should == [198, 146]
+ end
+
+ it "warns if called immediately after a buffered IO#write" do
+ @file.write("abcde")
+ -> { @file.syswrite("fghij") }.should complain(/syswrite/)
+ end
+
+ it "does not warn if called after IO#write with intervening IO#sysread" do
+ @file.syswrite("abcde")
+ @file.sysread(5)
+ -> { @file.syswrite("fghij") }.should_not complain
+ end
+
+ it "writes to the actual file position when called after buffered IO#read" do
+ @file.read(5)
+ @file.syswrite("abcde")
+ File.open(@filename) do |file|
+ file.sysread(10).should == "01234abcde"
+ end
+ end
+end
+
+describe "IO#syswrite on a pipe" do
+ it "returns the written bytes if the fd is in nonblock mode and write would block" do
+ require 'io/nonblock'
+ r, w = IO.pipe
+ begin
+ w.nonblock = true
+ larger_than_pipe_capacity = 2 * 1024 * 1024
+ written = w.syswrite("a"*larger_than_pipe_capacity)
+ written.should > 0
+ written.should < larger_than_pipe_capacity
+ ensure
+ w.close
+ r.close
+ end
+ end
+end
+
+describe "IO#syswrite" do
+ it_behaves_like :io_write, :syswrite
+ it_behaves_like :io_write_no_transcode, :syswrite
+end
diff --git a/spec/ruby/core/io/tell_spec.rb b/spec/ruby/core/io/tell_spec.rb
new file mode 100644
index 0000000000..0d6c6b02d3
--- /dev/null
+++ b/spec/ruby/core/io/tell_spec.rb
@@ -0,0 +1,7 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/pos'
+
+describe "IO#tell" do
+ it_behaves_like :io_pos, :tell
+end
diff --git a/spec/ruby/core/io/to_i_spec.rb b/spec/ruby/core/io/to_i_spec.rb
new file mode 100644
index 0000000000..1d0cf2a1f6
--- /dev/null
+++ b/spec/ruby/core/io/to_i_spec.rb
@@ -0,0 +1,12 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#to_i" do
+ it "returns the numeric file descriptor of the given IO object" do
+ $stdout.to_i.should == 1
+ end
+
+ it "raises IOError on closed stream" do
+ -> { IOSpecs.closed_io.to_i }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/to_io_spec.rb b/spec/ruby/core/io/to_io_spec.rb
new file mode 100644
index 0000000000..0b1809dffa
--- /dev/null
+++ b/spec/ruby/core/io/to_io_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#to_io" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ end
+
+ it "returns self for open stream" do
+ @io.to_io.should.equal?(@io)
+ end
+
+ it "returns self for closed stream" do
+ io = IOSpecs.closed_io
+ io.to_io.should.equal?(io)
+ end
+end
diff --git a/spec/ruby/core/io/try_convert_spec.rb b/spec/ruby/core/io/try_convert_spec.rb
new file mode 100644
index 0000000000..7600b01b75
--- /dev/null
+++ b/spec/ruby/core/io/try_convert_spec.rb
@@ -0,0 +1,49 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO.try_convert" do
+ before :each do
+ @name = tmp("io_try_convert.txt")
+ @io = new_io @name
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "returns the passed IO object" do
+ IO.try_convert(@io).should.equal?(@io)
+ end
+
+ it "does not call #to_io on an IO instance" do
+ @io.should_not_receive(:to_io)
+ IO.try_convert(@io)
+ end
+
+ it "calls #to_io to coerce an object" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return(@io)
+ IO.try_convert(obj).should.equal?(@io)
+ end
+
+ it "returns nil when the passed object does not respond to #to_io" do
+ IO.try_convert(mock("io")).should == nil
+ end
+
+ it "return nil when BasicObject is passed" do
+ IO.try_convert(BasicObject.new).should == nil
+ end
+
+ it "raises a TypeError if the object does not return an IO from #to_io" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_return("io")
+ -> { IO.try_convert(obj) }.should raise_consistent_error(TypeError, "can't convert MockObject into IO (MockObject#to_io gives String)")
+ end
+
+ it "propagates an exception raised by #to_io" do
+ obj = mock("io")
+ obj.should_receive(:to_io).and_raise(TypeError.new)
+ ->{ IO.try_convert(obj) }.should.raise(TypeError)
+ end
+end
diff --git a/spec/ruby/core/io/tty_spec.rb b/spec/ruby/core/io/tty_spec.rb
new file mode 100644
index 0000000000..3b76c6d2b8
--- /dev/null
+++ b/spec/ruby/core/io/tty_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/tty'
+
+describe "IO#tty?" do
+ it_behaves_like :io_tty, :tty?
+end
diff --git a/spec/ruby/core/io/ungetbyte_spec.rb b/spec/ruby/core/io/ungetbyte_spec.rb
new file mode 100644
index 0000000000..e0615cd76c
--- /dev/null
+++ b/spec/ruby/core/io/ungetbyte_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+describe "IO#ungetbyte" do
+ before :each do
+ @name = tmp("io_ungetbyte")
+ touch(@name) { |f| f.write "a" }
+ @io = new_io @name, "r"
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @name
+ end
+
+ it "does nothing when passed nil" do
+ @io.ungetbyte(nil).should == nil
+ @io.getbyte.should == 97
+ end
+
+ it "puts back each byte in a String argument" do
+ @io.ungetbyte("cat").should == nil
+ @io.getbyte.should == 99
+ @io.getbyte.should == 97
+ @io.getbyte.should == 116
+ @io.getbyte.should == 97
+ end
+
+ it "calls #to_str to convert the argument" do
+ str = mock("io ungetbyte")
+ str.should_receive(:to_str).and_return("dog")
+
+ @io.ungetbyte(str).should == nil
+ @io.getbyte.should == 100
+ @io.getbyte.should == 111
+ @io.getbyte.should == 103
+ @io.getbyte.should == 97
+ end
+
+ it "never raises RangeError" do
+ for i in [4095, 0x4f7574206f6620636861722072616e67ff] do
+ @io.ungetbyte(i).should == nil
+ @io.getbyte.should == 255
+ end
+ end
+
+ it "raises IOError on stream not opened for reading" do
+ -> { STDOUT.ungetbyte(42) }.should.raise(IOError, "not opened for reading")
+ end
+
+ it "raises an IOError if the IO is closed" do
+ @io.close
+ -> { @io.ungetbyte(42) }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/ungetc_spec.rb b/spec/ruby/core/io/ungetc_spec.rb
new file mode 100644
index 0000000000..4a9e67f126
--- /dev/null
+++ b/spec/ruby/core/io/ungetc_spec.rb
@@ -0,0 +1,138 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "IO#ungetc" do
+ before :each do
+ @io = IOSpecs.io_fixture "lines.txt"
+
+ @empty = tmp('empty.txt')
+ end
+
+ after :each do
+ @io.close unless @io.closed?
+ rm_r @empty
+ end
+
+ it "pushes back one character onto stream" do
+ @io.getc.should == ?V
+ @io.ungetc(86)
+ @io.getc.should == ?V
+
+ @io.ungetc(10)
+ @io.getc.should == ?\n
+
+ @io.getc.should == ?o
+ @io.getc.should == ?i
+ # read the rest of line
+ @io.readline.should == "ci la ligne une.\n"
+ @io.getc.should == ?Q
+ @io.ungetc(99)
+ @io.getc.should == ?c
+ end
+
+ it "interprets the codepoint in the external encoding" do
+ @io.set_encoding(Encoding::UTF_8)
+ @io.ungetc(233)
+ c = @io.getc
+ c.encoding.should == Encoding::UTF_8
+ c.should == "é"
+ c.bytes.should == [195, 169]
+
+ @io.set_encoding(Encoding::IBM437)
+ @io.ungetc(130)
+ c = @io.getc
+ c.encoding.should == Encoding::IBM437
+ c.bytes.should == [130]
+ c.encode(Encoding::UTF_8).should == "é"
+ end
+
+ it "pushes back one character when invoked at the end of the stream" do
+ # read entire content
+ @io.read
+ @io.ungetc(100)
+ @io.getc.should == ?d
+ end
+
+ it "pushes back one character when invoked at the start of the stream" do
+ @io.read(0)
+ @io.ungetc(100)
+ @io.getc.should == ?d
+ end
+
+ it "pushes back one character when invoked on empty stream" do
+ touch(@empty)
+
+ File.open(@empty) { |empty|
+ empty.getc().should == nil
+ empty.ungetc(10)
+ empty.getc.should == ?\n
+ }
+ end
+
+ it "affects EOF state" do
+ touch(@empty)
+
+ File.open(@empty) { |empty|
+ empty.should.eof?
+ empty.getc.should == nil
+ empty.ungetc(100)
+ empty.should_not.eof?
+ }
+ end
+
+ it "adjusts the stream position" do
+ @io.pos.should == 0
+
+ # read one char
+ c = @io.getc
+ @io.pos.should == 1
+ @io.ungetc(c)
+ @io.pos.should == 0
+
+ # read all
+ @io.read
+ pos = @io.pos
+ @io.ungetc(98)
+ @io.pos.should == pos - 1
+ end
+
+ it "makes subsequent unbuffered operations to raise IOError" do
+ @io.getc
+ @io.ungetc(100)
+ -> { @io.sysread(1) }.should.raise(IOError)
+ end
+
+ it "raises TypeError if passed nil" do
+ @io.getc.should == ?V
+ proc{@io.ungetc(nil)}.should.raise(TypeError)
+ end
+
+ it "puts one or more characters back in the stream" do
+ @io.gets
+ @io.ungetc("Aquí ").should == nil
+ @io.gets.chomp.should == "Aquí Qui è la linea due."
+ end
+
+ it "calls #to_str to convert the argument if it is not an Integer" do
+ chars = mock("io ungetc")
+ chars.should_receive(:to_str).and_return("Aquí ")
+
+ @io.ungetc(chars).should == nil
+ @io.gets.chomp.should == "Aquí Voici la ligne une."
+ end
+
+ it "returns nil when invoked on stream that was not yet read" do
+ @io.ungetc(100).should == nil
+ end
+
+ it "raises IOError on stream not opened for reading" do
+ -> { STDOUT.ungetc(100) }.should.raise(IOError, "not opened for reading")
+ end
+
+ it "raises IOError on closed stream" do
+ @io.getc
+ @io.close
+ -> { @io.ungetc(100) }.should.raise(IOError)
+ end
+end
diff --git a/spec/ruby/core/io/write_nonblock_spec.rb b/spec/ruby/core/io/write_nonblock_spec.rb
new file mode 100644
index 0000000000..a6bd43c058
--- /dev/null
+++ b/spec/ruby/core/io/write_nonblock_spec.rb
@@ -0,0 +1,96 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+
+# See https://bugs.ruby-lang.org/issues/5954#note-5
+platform_is_not :windows do
+ describe "IO#write_nonblock on a file" do
+ before :each do
+ @filename = tmp("IO_syswrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file.write_nonblock("012345678901234567890123456789")
+ end
+ @file = File.open(@filename, "r+")
+ @readonly_file = File.open(@filename)
+ end
+
+ after :each do
+ @file.close if @file
+ @readonly_file.close if @readonly_file
+ rm_r @filename
+ end
+
+ it "writes all of the string's bytes but does not buffer them" do
+ written = @file.write_nonblock("abcde")
+ written.should == 5
+ File.open(@filename) do |file|
+ file.sysread(10).should == "abcde56789"
+ file.seek(0)
+ @file.fsync
+ file.sysread(10).should == "abcde56789"
+ end
+ end
+
+ it "does not modify the passed argument" do
+ File.open(@filename, "w") do |f|
+ f.set_encoding(Encoding::IBM437)
+ # A character whose codepoint differs between UTF-8 and IBM437
+ f.write_nonblock("ƒ".freeze)
+ end
+
+ File.binread(@filename).bytes.should == [198, 146]
+ end
+
+ it "checks if the file is writable if writing zero bytes" do
+ -> {
+ @readonly_file.write_nonblock("")
+ }.should.raise(IOError)
+ end
+ end
+
+ describe "IO#write_nonblock" do
+ it_behaves_like :io_write, :write_nonblock
+ it_behaves_like :io_write_no_transcode, :write_nonblock
+ end
+end
+
+describe 'IO#write_nonblock' do
+ before do
+ @read, @write = IO.pipe
+ end
+
+ after do
+ @read.close
+ @write.close
+ end
+
+ it "raises an exception extending IO::WaitWritable when the write would block" do
+ -> {
+ loop { @write.write_nonblock('a' * 10_000) }
+ }.should.raise(IO::WaitWritable) { |e|
+ platform_is_not :windows do
+ e.should.is_a?(Errno::EAGAIN)
+ end
+ platform_is :windows do
+ e.should.is_a?(Errno::EWOULDBLOCK)
+ end
+ }
+ end
+
+ context "when exception option is set to false" do
+ it "returns :wait_writable when the operation would block" do
+ loop {
+ break if @write.write_nonblock("a" * 10_000, exception: false) == :wait_writable
+ }
+ @write.write_nonblock("a" * 10_000, exception: false).should == :wait_writable
+ end
+ end
+
+ platform_is_not :windows do
+ it 'sets the IO in nonblock mode' do
+ require 'io/nonblock'
+ @write.write_nonblock('a')
+ @write.should.nonblock?
+ end
+ end
+end
diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb
new file mode 100644
index 0000000000..1a745ba012
--- /dev/null
+++ b/spec/ruby/core/io/write_spec.rb
@@ -0,0 +1,304 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/write'
+require_relative 'shared/binwrite'
+
+describe "IO#write on a file" do
+ before :each do
+ @filename = tmp("IO_syswrite_file") + $$.to_s
+ File.open(@filename, "w") do |file|
+ file.write("012345678901234567890123456789")
+ end
+ @file = File.open(@filename, "r+")
+ @readonly_file = File.open(@filename)
+ end
+
+ after :each do
+ @file.close
+ @readonly_file.close
+ rm_r @filename
+ end
+
+ it "does not check if the file is writable if writing zero bytes" do
+ -> { @readonly_file.write("") }.should_not.raise
+ end
+
+ before :each do
+ @external = Encoding.default_external
+ @internal = Encoding.default_internal
+
+ Encoding.default_external = Encoding::UTF_8
+ end
+
+ after :each do
+ Encoding.default_external = @external
+ Encoding.default_internal = @internal
+ end
+
+ it "returns a length of 0 when writing a blank string" do
+ @file.write('').should == 0
+ end
+
+ it "returns a length of 0 when writing blank strings" do
+ @file.write('', '', '').should == 0
+ end
+
+ it "returns a length of 0 when passed no arguments" do
+ @file.write().should == 0
+ end
+
+ it "returns the number of bytes written" do
+ @file.write("hellø").should == 6
+ end
+
+ it "does not modify the passed argument" do
+ File.open(@filename, "w") do |f|
+ f.set_encoding(Encoding::IBM437)
+ # A character whose codepoint differs between UTF-8 and IBM437
+ f.write("ƒ".freeze)
+ end
+
+ File.binread(@filename).bytes.should == [159]
+ end
+
+ it "does not modify arguments when passed multiple arguments and external encoding not set" do
+ a, b = "a".freeze, "b".freeze
+
+ File.open(@filename, "w") do |f|
+ f.write(a, b)
+ end
+
+ File.binread(@filename).bytes.should == [97, 98]
+ a.encoding.should == Encoding::UTF_8
+ b.encoding.should == Encoding::UTF_8
+ end
+
+ it "uses the encoding from the given option for non-ascii encoding" do
+ File.open(@filename, "w", encoding: Encoding::UTF_32LE) do |file|
+ file.write("hi").should == 8
+ end
+ File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000"
+ end
+
+ it "uses the encoding from the given option for non-ascii encoding even if in binary mode" do
+ File.open(@filename, "w", encoding: Encoding::UTF_32LE, binmode: true) do |file|
+ file.should.binmode?
+ file.write("hi").should == 8
+ end
+ File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000"
+
+ File.open(@filename, "wb", encoding: Encoding::UTF_32LE) do |file|
+ file.should.binmode?
+ file.write("hi").should == 8
+ end
+ File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000"
+ end
+
+ it "uses the encoding from the given option for non-ascii encoding when multiple arguments passes" do
+ File.open(@filename, "w", encoding: Encoding::UTF_32LE) do |file|
+ file.write("h", "i").should == 8
+ end
+ File.binread(@filename).should == "h\u0000\u0000\u0000i\u0000\u0000\u0000"
+ end
+
+ it "ignores the 'bom|' prefix" do
+ File.open(@filename, "w", encoding: 'bom|utf-8') do |file|
+ file.write("hi")
+ end
+ File.binread(@filename).should == "hi"
+ end
+
+ it "raises a invalid byte sequence error if invalid bytes are being written" do
+ # pack "\xFEhi" to avoid utf-8 conflict
+ xFEhi = ([254].pack('C*') + 'hi').force_encoding('utf-8')
+ File.open(@filename, "w", encoding: Encoding::US_ASCII) do |file|
+ -> { file.write(xFEhi) }.should.raise(Encoding::InvalidByteSequenceError)
+ end
+ end
+
+ it "writes binary data if no encoding is given" do
+ File.open(@filename, "w") do |file|
+ file.write('Hëllö'.encode('ISO-8859-1'))
+ end
+ ë = ([235].pack('U')).encode('ISO-8859-1')
+ ö = ([246].pack('U')).encode('ISO-8859-1')
+ res = "H#{ë}ll#{ö}"
+ File.binread(@filename).should == res.force_encoding(Encoding::BINARY)
+ end
+
+ platform_is_not :windows do
+ it "writes binary data if no encoding is given and multiple arguments passed" do
+ File.open(@filename, "w") do |file|
+ file.write("\x87".b, "ą") # 0x87 isn't a valid UTF-8 binary representation of a character
+ end
+ File.binread(@filename).bytes.should == [0x87, 0xC4, 0x85]
+
+ File.open(@filename, "w") do |file|
+ file.write("\x61".encode("utf-32le"), "ą")
+ end
+ File.binread(@filename).bytes.should == [0x61, 0x00, 0x00, 0x00, 0xC4, 0x85]
+ end
+ end
+end
+
+describe "IO.write" do
+ it_behaves_like :io_binwrite, :write
+
+ it "uses an :open_args option" do
+ IO.write(@filename, 'hi', open_args: ["w", nil, {encoding: Encoding::UTF_32LE}]).should == 8
+ end
+
+ it "disregards other options if :open_args is given" do
+ IO.write(@filename, 'hi', 2, mode: "r", encoding: Encoding::UTF_32LE, open_args: ["w"]).should == 2
+ File.read(@filename).should == "\0\0hi"
+ end
+
+ it "requires mode to be specified in :open_args" do
+ -> {
+ IO.write(@filename, 'hi', open_args: [{encoding: Encoding::UTF_32LE, binmode: true}])
+ }.should.raise(IOError, "not opened for writing")
+
+ IO.write(@filename, 'hi', open_args: ["w", {encoding: Encoding::UTF_32LE, binmode: true}]).should == 8
+ IO.write(@filename, 'hi', open_args: [{encoding: Encoding::UTF_32LE, binmode: true, mode: "w"}]).should == 8
+ end
+
+ it "requires mode to be specified in :open_args even if flags option passed" do
+ -> {
+ IO.write(@filename, 'hi', open_args: [{encoding: Encoding::UTF_32LE, binmode: true, flags: File::CREAT}])
+ }.should.raise(IOError, "not opened for writing")
+
+ IO.write(@filename, 'hi', open_args: ["w", {encoding: Encoding::UTF_32LE, binmode: true, flags: File::CREAT}]).should == 8
+ IO.write(@filename, 'hi', open_args: [{encoding: Encoding::UTF_32LE, binmode: true, flags: File::CREAT, mode: "w"}]).should == 8
+ end
+
+ it "uses the given encoding and returns the number of bytes written" do
+ IO.write(@filename, 'hi', mode: "w", encoding: Encoding::UTF_32LE).should == 8
+ end
+
+ it "raises ArgumentError if encoding is specified in mode parameter and is given as :encoding option" do
+ -> {
+ IO.write(@filename, 'hi', mode: "w:UTF-16LE:UTF-16BE", encoding: Encoding::UTF_32LE)
+ }.should.raise(ArgumentError, "encoding specified twice")
+
+ -> {
+ IO.write(@filename, 'hi', mode: "w:UTF-16BE", encoding: Encoding::UTF_32LE)
+ }.should.raise(ArgumentError, "encoding specified twice")
+ end
+
+ it "writes the file with the permissions in the :perm parameter" do
+ rm_r @filename
+ IO.write(@filename, 'write :perm spec', mode: "w", perm: 0o755).should == 16
+ (File.stat(@filename).mode & 0o777) == 0o755
+ end
+
+ it "writes binary data if no encoding is given" do
+ IO.write(@filename, 'Hëllö'.encode('ISO-8859-1'))
+ xEB = [235].pack('C*')
+ xF6 = [246].pack('C*')
+ File.binread(@filename).should == ("H" + xEB + "ll" + xF6).force_encoding(Encoding::BINARY)
+ end
+
+ platform_is_not :windows do
+ describe "on a FIFO" do
+ before :each do
+ @fifo = tmp("File_open_fifo")
+ File.mkfifo(@fifo)
+ end
+
+ after :each do
+ rm_r @fifo
+ end
+
+ # rb_cloexec_open() is currently missing a retry on EINTR.
+ # @ioquatix is looking into fixing it. Quarantined until it's done.
+ quarantine! do
+ it "writes correctly" do
+ thr = Thread.new do
+ IO.read(@fifo)
+ end
+ begin
+ string = "hi"
+ IO.write(@fifo, string).should == string.length
+ ensure
+ thr.join
+ end
+ end
+ end
+ end
+
+ ruby_version_is ""..."4.0" do
+ # https://bugs.ruby-lang.org/issues/19630
+ it "warns about deprecation given a path with a pipe" do
+ -> {
+ -> {
+ IO.write("|cat", "xxx")
+ }.should output_to_fd("xxx")
+ }.should complain(/IO process creation with a leading '\|'/)
+ end
+ end
+ end
+end
+
+describe "IO#write" do
+ it_behaves_like :io_write, :write
+ it_behaves_like :io_write_transcode, :write
+
+ it "accepts multiple arguments" do
+ IO.pipe do |r, w|
+ w.write("foo", "bar")
+ w.close
+
+ r.read.should == "foobar"
+ end
+ end
+end
+
+platform_is :windows do
+ describe "IO#write on Windows" do
+ before :each do
+ @fname = tmp("io_write.txt")
+ end
+
+ after :each do
+ rm_r @fname
+ @io.close if @io and !@io.closed?
+ end
+
+ it "normalizes line endings in text mode" do
+ @io = new_io(@fname, "wt")
+ @io.write "a\nb\nc"
+ @io.close
+ File.binread(@fname).should == "a\r\nb\r\nc"
+ end
+
+ it "does not normalize line endings in binary mode" do
+ @io = new_io(@fname, "wb")
+ @io.write "a\r\nb\r\nc"
+ @io.close
+ File.binread(@fname).should == "a\r\nb\r\nc"
+ end
+ end
+end
+
+describe "IO#write on STDOUT" do
+ # https://bugs.ruby-lang.org/issues/14413
+ platform_is_not :windows do
+ it "raises SignalException SIGPIPE if the stream is closed instead of Errno::EPIPE like other IOs" do
+ stderr_file = tmp("stderr")
+ begin
+ IO.popen([*ruby_exe, "-e", "loop { puts :ok }"], "r", err: stderr_file) do |io|
+ io.gets.should == "ok\n"
+ io.close
+ end
+ status = $?
+ status.should_not.success?
+ status.should.signaled?
+ Signal.signame(status.termsig).should == 'PIPE'
+ File.read(stderr_file).should.empty?
+ ensure
+ rm_r stderr_file
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/kernel/Array_spec.rb b/spec/ruby/core/kernel/Array_spec.rb
new file mode 100644
index 0000000000..063faf7097
--- /dev/null
+++ b/spec/ruby/core/kernel/Array_spec.rb
@@ -0,0 +1,97 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+
+describe "Kernel" do
+ it "has private instance method Array()" do
+ Kernel.private_instance_methods(false).should.include?(:Array)
+ end
+end
+
+describe :kernel_Array, shared: true do
+ before :each do
+ @array = [1, 2, 3]
+ end
+
+ it "does not call #to_ary on an Array" do
+ @array.should_not_receive(:to_ary)
+ @object.send(@method, @array).should == @array
+ end
+
+ it "calls #to_ary to convert the argument to an Array" do
+ obj = mock("Array([1,2,3])")
+ obj.should_receive(:to_ary).and_return(@array)
+ obj.should_not_receive(:to_a)
+
+ @object.send(@method, obj).should == @array
+ end
+
+ it "does not call #to_a on an Array" do
+ @array.should_not_receive(:to_a)
+ @object.send(@method, @array).should == @array
+ end
+
+ it "calls #to_a if the argument does not respond to #to_ary" do
+ obj = mock("Array([1,2,3])")
+ obj.should_receive(:to_a).and_return(@array)
+
+ @object.send(@method, obj).should == @array
+ end
+
+ it "calls #to_a if #to_ary returns nil" do
+ obj = mock("Array([1,2,3])")
+ obj.should_receive(:to_ary).and_return(nil)
+ obj.should_receive(:to_a).and_return(@array)
+
+ @object.send(@method, obj).should == @array
+ end
+
+ it "returns an Array containing the argument if #to_a returns nil" do
+ obj = mock("Array([1,2,3])")
+ obj.should_receive(:to_a).and_return(nil)
+
+ @object.send(@method, obj).should == [obj]
+ end
+
+ it "calls #to_ary first, even if it's private" do
+ obj = KernelSpecs::PrivateToAry.new
+
+ @object.send(@method, obj).should == [1, 2]
+ end
+
+ it "calls #to_a if #to_ary is not defined, even if it's private" do
+ obj = KernelSpecs::PrivateToA.new
+
+ @object.send(@method, obj).should == [3, 4]
+ end
+
+ it "returns an Array containing the argument if it responds to neither #to_ary nor #to_a" do
+ obj = mock("Array(x)")
+ @object.send(@method, obj).should == [obj]
+ end
+
+ it "returns an empty Array when passed nil" do
+ @object.send(@method, nil).should == []
+ end
+
+ it "raises a TypeError if #to_ary does not return an Array" do
+ obj = mock("Array() string")
+ obj.should_receive(:to_ary).and_return("string")
+
+ -> { @object.send(@method, obj) }.should.raise(TypeError)
+ end
+
+ it "raises a TypeError if #to_a does not return an Array" do
+ obj = mock("Array() string")
+ obj.should_receive(:to_a).and_return("string")
+
+ -> { @object.send(@method, obj) }.should.raise(TypeError)
+ end
+end
+
+describe "Kernel.Array" do
+ it_behaves_like :kernel_Array, :Array_method, KernelSpecs
+end
+
+describe "Kernel#Array" do
+ it_behaves_like :kernel_Array, :Array_function, KernelSpecs
+end
diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb
new file mode 100644
index 0000000000..92ce183cc8
--- /dev/null
+++ b/spec/ruby/core/kernel/Complex_spec.rb
@@ -0,0 +1,276 @@
+require_relative '../../spec_helper'
+require_relative '../../shared/kernel/complex'
+require_relative 'fixtures/Complex'
+
+describe "Kernel.Complex()" do
+ describe "when passed [Complex, Complex]" do
+ it "returns a new Complex number based on the two given numbers" do
+ Complex(Complex(3, 4), Complex(5, 6)).should == Complex(3 - 6, 4 + 5)
+ Complex(Complex(1.5, 2), Complex(-5, 6.3)).should == Complex(1.5 - 6.3, 2 - 5)
+ end
+ end
+
+ describe "when passed [Complex]" do
+ it "returns the passed Complex number" do
+ Complex(Complex(1, 2)).should == Complex(1, 2)
+ Complex(Complex(-3.4, bignum_value)).should == Complex(-3.4, bignum_value)
+ end
+ end
+
+ describe "when passed [Integer, Integer]" do
+ it "returns a new Complex number" do
+ Complex(1, 2).should.instance_of?(Complex)
+ Complex(1, 2).real.should == 1
+ Complex(1, 2).imag.should == 2
+
+ Complex(-3, -5).should.instance_of?(Complex)
+ Complex(-3, -5).real.should == -3
+ Complex(-3, -5).imag.should == -5
+
+ Complex(3.5, -4.5).should.instance_of?(Complex)
+ Complex(3.5, -4.5).real.should == 3.5
+ Complex(3.5, -4.5).imag.should == -4.5
+
+ Complex(bignum_value, 30).should.instance_of?(Complex)
+ Complex(bignum_value, 30).real.should == bignum_value
+ Complex(bignum_value, 30).imag.should == 30
+ end
+ end
+
+ describe "when passed [Integer/Float]" do
+ it "returns a new Complex number with 0 as the imaginary component" do
+ # Guard against the Mathn library
+ guard -> { !defined?(Math.rsqrt) } do
+ Complex(1).should.instance_of?(Complex)
+ Complex(1).imag.should == 0
+ Complex(1).real.should == 1
+
+ Complex(-3).should.instance_of?(Complex)
+ Complex(-3).imag.should == 0
+ Complex(-3).real.should == -3
+
+ Complex(-4.5).should.instance_of?(Complex)
+ Complex(-4.5).imag.should == 0
+ Complex(-4.5).real.should == -4.5
+
+ Complex(bignum_value).should.instance_of?(Complex)
+ Complex(bignum_value).imag.should == 0
+ Complex(bignum_value).real.should == bignum_value
+ end
+ end
+ end
+
+ describe "when passed [String]" do
+ it_behaves_like :kernel_complex, :Complex_method, KernelSpecs
+
+ context "invalid argument" do
+ it "raises Encoding::CompatibilityError if String is in not ASCII-compatible encoding" do
+ -> {
+ Complex("79+4i".encode("UTF-16"))
+ }.should.raise(Encoding::CompatibilityError, "ASCII incompatible encoding: UTF-16")
+ end
+
+ it "raises ArgumentError for unrecognised Strings" do
+ -> {
+ Complex("ruby")
+ }.should.raise(ArgumentError, 'invalid value for convert(): "ruby"')
+ end
+
+ it "raises ArgumentError for trailing garbage" do
+ -> {
+ Complex("79+4iruby")
+ }.should.raise(ArgumentError, 'invalid value for convert(): "79+4iruby"')
+ end
+
+ it "does not understand Float::INFINITY" do
+ -> {
+ Complex("Infinity")
+ }.should.raise(ArgumentError, 'invalid value for convert(): "Infinity"')
+
+ -> {
+ Complex("-Infinity")
+ }.should.raise(ArgumentError, 'invalid value for convert(): "-Infinity"')
+ end
+
+ it "does not understand Float::NAN" do
+ -> {
+ Complex("NaN")
+ }.should.raise(ArgumentError, 'invalid value for convert(): "NaN"')
+ end
+
+ it "does not understand a sequence of _" do
+ ->