summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/-ext-/box/test_load_ext.rb97
-rw-r--r--test/-ext-/bug_reporter/test_bug_reporter.rb10
-rw-r--r--test/-ext-/debug/test_debug.rb54
-rw-r--r--test/-ext-/gvl/test_last_thread.rb3
-rw-r--r--test/-ext-/marshal/test_internal_ivar.rb10
-rw-r--r--test/-ext-/postponed_job/test_postponed_job.rb35
-rw-r--r--test/-ext-/scheduler/test_interrupt_with_scheduler.rb54
-rw-r--r--test/-ext-/stack/test_stack_overflow.rb55
-rw-r--r--test/-ext-/string/test_capacity.rb13
-rw-r--r--test/-ext-/string/test_interned_str.rb5
-rw-r--r--test/-ext-/string/test_set_len.rb2
-rw-r--r--test/-ext-/symbol/test_type.rb16
-rw-r--r--test/-ext-/test_abi.rb12
-rw-r--r--test/-ext-/thread/test_instrumentation_api.rb6
-rw-r--r--test/-ext-/thread/test_lock_native_thread.rb4
-rw-r--r--test/-ext-/thread_fd/test_thread_fd_close.rb24
-rw-r--r--test/-ext-/tracepoint/test_tracepoint.rb2
-rw-r--r--test/.excludes-mmtk/TestEtc.rb1
-rw-r--r--test/.excludes-mmtk/TestGc.rb1
-rw-r--r--test/.excludes-mmtk/TestObjSpace.rb3
-rw-r--r--test/.excludes-mmtk/TestObjectSpace.rb1
-rw-r--r--test/.excludes-zjit/TestResolvDNS.rb1
-rw-r--r--test/.excludes/JSONGenericObjectTest.rb4
-rw-r--r--test/.excludes/TestPatternMatching.rb1
-rw-r--r--test/.excludes/TestThread.rb2
-rw-r--r--test/.excludes/URI/TestMailTo.rb1
-rw-r--r--test/.excludes/_appveyor/TestArray.rb7
-rw-r--r--test/cgi/test_cgi_cookie.rb211
-rw-r--r--test/cgi/test_cgi_core.rb307
-rw-r--r--test/cgi/test_cgi_escape.rb (renamed from test/cgi/test_cgi_util.rb)45
-rw-r--r--test/cgi/test_cgi_header.rb192
-rw-r--r--test/cgi/test_cgi_modruby.rb149
-rw-r--r--test/cgi/test_cgi_multipart.rb385
-rw-r--r--test/cgi/test_cgi_session.rb169
-rw-r--r--test/cgi/test_cgi_tag_helper.rb355
-rw-r--r--test/cgi/testdata/file1.html10
-rw-r--r--test/cgi/testdata/large.pngbin156414 -> 0 bytes
-rw-r--r--test/cgi/testdata/small.pngbin82 -> 0 bytes
-rw-r--r--test/coverage/test_coverage.rb81
-rw-r--r--test/date/test_date.rb4
-rw-r--r--test/date/test_date_conv.rb20
-rw-r--r--test/date/test_date_parse.rb26
-rw-r--r--test/date/test_date_ractor.rb2
-rw-r--r--test/date/test_date_strptime.rb15
-rw-r--r--test/date/test_switch_hitter.rb5
-rw-r--r--test/did_you_mean/spell_checking/test_method_name_check.rb4
-rw-r--r--test/did_you_mean/test_ractor_compatibility.rb12
-rw-r--r--test/digest/test_ractor.rb6
-rw-r--r--test/dtrace/helper.rb6
-rw-r--r--test/erb/test_erb.rb111
-rw-r--r--test/error_highlight/test_error_highlight.rb302
-rw-r--r--test/etc/test_etc.rb75
-rw-r--r--test/fiber/scheduler.rb158
-rw-r--r--test/fiber/test_io.rb59
-rw-r--r--test/fiber/test_io_close.rb107
-rw-r--r--test/fiber/test_ractor.rb2
-rw-r--r--test/fiber/test_scheduler.rb173
-rw-r--r--test/fiber/test_sleep.rb4
-rw-r--r--test/fiber/test_thread.rb47
-rw-r--r--test/fiddle/helper.rb202
-rw-r--r--test/fiddle/test_c_struct_builder.rb69
-rw-r--r--test/fiddle/test_c_struct_entry.rb171
-rw-r--r--test/fiddle/test_c_union_entity.rb36
-rw-r--r--test/fiddle/test_closure.rb173
-rw-r--r--test/fiddle/test_cparser.rb419
-rw-r--r--test/fiddle/test_fiddle.rb91
-rw-r--r--test/fiddle/test_func.rb180
-rw-r--r--test/fiddle/test_function.rb310
-rw-r--r--test/fiddle/test_handle.rb244
-rw-r--r--test/fiddle/test_import.rb499
-rw-r--r--test/fiddle/test_memory_view.rb175
-rw-r--r--test/fiddle/test_pack.rb37
-rw-r--r--test/fiddle/test_pinned.rb34
-rw-r--r--test/fiddle/test_pointer.rb319
-rw-r--r--test/fileutils/test_fileutils.rb95
-rw-r--r--test/io/console/test_io_console.rb30
-rw-r--r--test/io/console/test_ractor.rb12
-rw-r--r--test/io/wait/test_io_wait.rb38
-rw-r--r--test/io/wait/test_io_wait_uncommon.rb15
-rw-r--r--test/io/wait/test_ractor.rb6
-rw-r--r--test/irb/command/test_cd.rb84
-rw-r--r--test/irb/command/test_command_aliasing.rb50
-rw-r--r--test/irb/command/test_custom_command.rb194
-rw-r--r--test/irb/command/test_disable_irb.rb28
-rw-r--r--test/irb/command/test_force_exit.rb51
-rw-r--r--test/irb/command/test_help.rb75
-rw-r--r--test/irb/command/test_multi_irb_commands.rb50
-rw-r--r--test/irb/command/test_show_source.rb410
-rw-r--r--test/irb/helper.rb234
-rw-r--r--test/irb/test_color.rb275
-rw-r--r--test/irb/test_color_printer.rb69
-rw-r--r--test/irb/test_command.rb1001
-rw-r--r--test/irb/test_completion.rb317
-rw-r--r--test/irb/test_context.rb737
-rw-r--r--test/irb/test_debugger_integration.rb513
-rw-r--r--test/irb/test_eval_history.rb69
-rw-r--r--test/irb/test_evaluation.rb44
-rw-r--r--test/irb/test_helper_method.rb135
-rw-r--r--test/irb/test_history.rb573
-rw-r--r--test/irb/test_init.rb388
-rw-r--r--test/irb/test_input_method.rb195
-rw-r--r--test/irb/test_irb.rb936
-rw-r--r--test/irb/test_locale.rb118
-rw-r--r--test/irb/test_nesting_parser.rb339
-rw-r--r--test/irb/test_option.rb13
-rw-r--r--test/irb/test_raise_exception.rb74
-rw-r--r--test/irb/test_ruby_lex.rb242
-rw-r--r--test/irb/test_tracer.rb90
-rw-r--r--test/irb/test_type_completor.rb109
-rw-r--r--test/irb/test_workspace.rb126
-rw-r--r--test/irb/yamatanooroti/test_rendering.rb478
-rw-r--r--test/json/fixtures/fail15.json (renamed from test/json/fixtures/pass15.json)0
-rw-r--r--test/json/fixtures/fail16.json (renamed from test/json/fixtures/pass16.json)0
-rw-r--r--test/json/fixtures/fail17.json (renamed from test/json/fixtures/pass17.json)0
-rw-r--r--test/json/fixtures/fail26.json (renamed from test/json/fixtures/pass26.json)0
-rw-r--r--test/json/fixtures/pass1.json2
-rw-r--r--test/json/json_addition_test.rb14
-rwxr-xr-xtest/json/json_coder_test.rb154
-rw-r--r--test/json/json_common_interface_test.rb138
-rw-r--r--test/json/json_encoding_test.rb188
-rw-r--r--test/json/json_ext_parser_test.rb31
-rw-r--r--test/json/json_fixtures_test.rb2
-rwxr-xr-xtest/json/json_generator_test.rb669
-rw-r--r--test/json/json_generic_object_test.rb24
-rw-r--r--test/json/json_parser_test.rb304
-rw-r--r--test/json/json_ryu_fallback_test.rb191
-rw-r--r--test/json/ractor_test.rb74
-rw-r--r--test/json/test_helper.rb25
-rw-r--r--test/lib/jit_support.rb16
-rw-r--r--test/mkmf/test_egrep_cpp.rb14
-rw-r--r--test/mkmf/test_pkg_config.rb17
-rw-r--r--test/mmtk/helper.rb11
-rw-r--r--test/mmtk/test_configuration.rb20
-rw-r--r--test/monitor/test_monitor.rb2
-rw-r--r--test/net/http/test_http.rb47
-rw-r--r--test/net/http/test_http_request.rb34
-rw-r--r--test/net/http/test_https.rb84
-rw-r--r--test/net/http/test_https_proxy.rb16
-rw-r--r--test/net/http/utils.rb16
-rw-r--r--test/objspace/test_objspace.rb160
-rw-r--r--test/objspace/test_ractor.rb74
-rw-r--r--test/open-uri/test_open-uri.rb2
-rw-r--r--test/openssl/fixtures/pkey/dsa1024.pem12
-rw-r--r--test/openssl/fixtures/pkey/dsa256.pem8
-rw-r--r--test/openssl/fixtures/pkey/dsa512.pem8
-rw-r--r--test/openssl/fixtures/pkey/mldsa65-1.pem88
-rw-r--r--test/openssl/fixtures/pkey/mldsa65-2.pem88
-rw-r--r--test/openssl/fixtures/pkey/rsa1024.pem15
-rw-r--r--test/openssl/test_asn1.rb106
-rw-r--r--test/openssl/test_bn.rb52
-rw-r--r--test/openssl/test_cipher.rb103
-rw-r--r--test/openssl/test_config.rb13
-rw-r--r--test/openssl/test_digest.rb95
-rw-r--r--test/openssl/test_fips.rb9
-rw-r--r--test/openssl/test_hmac.rb34
-rw-r--r--test/openssl/test_kdf.rb138
-rw-r--r--test/openssl/test_ns_spki.rb4
-rw-r--r--test/openssl/test_ocsp.rb43
-rw-r--r--test/openssl/test_ossl.rb85
-rw-r--r--test/openssl/test_pkcs12.rb40
-rw-r--r--test/openssl/test_pkcs7.rb380
-rw-r--r--test/openssl/test_pkey.rb166
-rw-r--r--test/openssl/test_pkey_dh.rb145
-rw-r--r--test/openssl/test_pkey_dsa.rb149
-rw-r--r--test/openssl/test_pkey_ec.rb109
-rw-r--r--test/openssl/test_pkey_rsa.rb353
-rw-r--r--test/openssl/test_provider.rb1
-rw-r--r--test/openssl/test_ssl.rb839
-rw-r--r--test/openssl/test_ssl_session.rb45
-rw-r--r--test/openssl/test_ts.rb84
-rw-r--r--test/openssl/test_x509cert.rb177
-rw-r--r--test/openssl/test_x509crl.rb79
-rw-r--r--test/openssl/test_x509name.rb16
-rw-r--r--test/openssl/test_x509req.rb95
-rw-r--r--test/openssl/test_x509store.rb19
-rw-r--r--test/openssl/utils.rb66
-rw-r--r--test/optparse/test_load.rb61
-rw-r--r--test/optparse/test_optparse.rb17
-rw-r--r--test/optparse/test_placearg.rb25
-rw-r--r--test/optparse/test_switch.rb50
-rw-r--r--test/pathname/test_pathname.rb69
-rw-r--r--test/pathname/test_ractor.rb12
-rw-r--r--test/prism/api/freeze_test.rb5
-rw-r--r--test/prism/api/parse_stream_test.rb51
-rw-r--r--test/prism/api/parse_test.rb18
-rw-r--r--test/prism/bom_test.rb3
-rw-r--r--test/prism/encoding/encodings_test.rb18
-rw-r--r--test/prism/encoding/regular_expression_encoding_test.rb34
-rw-r--r--test/prism/errors/3.3-3.3/circular_parameters.txt12
-rw-r--r--test/prism/errors/3.3-3.4/leading_logical.txt34
-rw-r--r--test/prism/errors/3.3-3.4/private_endless_method.txt3
-rw-r--r--test/prism/errors/3.3-4.0/do_not_allow_trailing_commas_in_method_parameters.txt (renamed from test/prism/errors/do_not_allow_trailing_commas_in_method_parameters.txt)0
-rw-r--r--test/prism/errors/3.3-4.0/noblock.txt6
-rw-r--r--test/prism/errors/3.3-4.0/singleton_method_with_void_value.txt3
-rw-r--r--test/prism/errors/3.4-4.0/void_value.txt18
-rw-r--r--test/prism/errors/3.4/block_args_in_array_assignment.txt (renamed from test/prism/errors/block_args_in_array_assignment.txt)0
-rw-r--r--test/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt (renamed from test/prism/errors/dont_allow_return_inside_sclass_body.txt)0
-rw-r--r--test/prism/errors/3.4/it_with_ordinary_parameter.txt (renamed from test/prism/errors/it_with_ordinary_parameter.txt)0
-rw-r--r--test/prism/errors/3.4/keyword_args_in_array_assignment.txt (renamed from test/prism/errors/keyword_args_in_array_assignment.txt)0
-rw-r--r--test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt6
-rw-r--r--test/prism/errors/4.1/end_block_exit.txt10
-rw-r--r--test/prism/errors/4.1/multiple_blocks.txt12
-rw-r--r--test/prism/errors/4.1/singleton_method_with_void_value.txt4
-rw-r--r--test/prism/errors/4.1/void_value.txt44
-rw-r--r--test/prism/errors/block_args_with_endless_def.txt5
-rw-r--r--test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt3
-rw-r--r--test/prism/errors/block_pass_return_value.txt33
-rw-r--r--test/prism/errors/command_call_in.txt1
-rw-r--r--test/prism/errors/command_call_in_2.txt4
-rw-r--r--test/prism/errors/command_call_in_3.txt4
-rw-r--r--test/prism/errors/command_call_in_4.txt4
-rw-r--r--test/prism/errors/command_call_in_5.txt4
-rw-r--r--test/prism/errors/command_call_in_6.txt4
-rw-r--r--test/prism/errors/command_call_in_7.txt4
-rw-r--r--test/prism/errors/command_call_value_and.txt3
-rw-r--r--test/prism/errors/command_call_value_or.txt3
-rw-r--r--test/prism/errors/command_calls.txt7
-rw-r--r--test/prism/errors/command_calls_2.txt2
-rw-r--r--test/prism/errors/command_calls_24.txt2
-rw-r--r--test/prism/errors/command_calls_25.txt2
-rw-r--r--test/prism/errors/command_calls_31.txt17
-rw-r--r--test/prism/errors/command_calls_32.txt19
-rw-r--r--test/prism/errors/command_calls_33.txt6
-rw-r--r--test/prism/errors/command_calls_34.txt31
-rw-r--r--test/prism/errors/command_calls_35.txt50
-rw-r--r--test/prism/errors/def_endless_do.txt6
-rw-r--r--test/prism/errors/def_with_optional_splat.txt6
-rw-r--r--test/prism/errors/defined_empty.txt3
-rw-r--r--test/prism/errors/destroy_call_operator_write_arguments.txt11
-rw-r--r--test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt12
-rw-r--r--test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt12
-rw-r--r--test/prism/errors/endless_method_command_call.txt3
-rw-r--r--test/prism/errors/endless_method_command_call_parameters.txt27
-rw-r--r--test/prism/errors/escape_unicode_curly_whitespace.txt5
-rw-r--r--test/prism/errors/heredoc_percent_q_newline_delimiter.txt11
-rw-r--r--test/prism/errors/heredoc_unterminated.txt2
-rw-r--r--test/prism/errors/infix_after_label.txt2
-rw-r--r--test/prism/errors/interpolated_symbol_pattern_hash_key.txt3
-rw-r--r--test/prism/errors/label_in_interpolated_string.txt14
-rw-r--r--test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt1
-rw-r--r--test/prism/errors/match_predicate_after_rescue_with_opreator.txt1
-rw-r--r--test/prism/errors/match_required_after_rescue_with_dot_method_call.txt1
-rw-r--r--test/prism/errors/match_required_after_rescue_with_opreator.txt1
-rw-r--r--test/prism/errors/modifier_conditional_in_predicate.txt12
-rw-r--r--test/prism/errors/not_without_parens_assignment.txt4
-rw-r--r--test/prism/errors/not_without_parens_call.txt7
-rw-r--r--test/prism/errors/not_without_parens_command.txt4
-rw-r--r--test/prism/errors/not_without_parens_command_call.txt4
-rw-r--r--test/prism/errors/not_without_parens_return.txt4
-rw-r--r--test/prism/errors/numbered_and_write.txt3
-rw-r--r--test/prism/errors/numbered_operator_write.txt3
-rw-r--r--test/prism/errors/numbered_or_write.txt3
-rw-r--r--test/prism/errors/pattern-capture-in-alt-array.txt4
-rw-r--r--test/prism/errors/pattern-capture-in-alt-hash.txt3
-rw-r--r--test/prism/errors/pattern-capture-in-alt-name.txt3
-rw-r--r--test/prism/errors/pattern-capture-in-alt-top.txt4
-rw-r--r--test/prism/errors/pattern_arithmetic_expressions.txt3
-rw-r--r--test/prism/errors/pattern_match_implicit_rest.txt3
-rw-r--r--test/prism/errors/pattern_string_key.txt8
-rw-r--r--test/prism/errors/rescue_pattern.txt4
-rw-r--r--test/prism/errors/shadow_args_in_lambda.txt2
-rw-r--r--test/prism/errors/singleton_method_for_literals.txt2
-rw-r--r--test/prism/errors/unterminated_begin.txt4
-rw-r--r--test/prism/errors/unterminated_begin_upcase.txt4
-rw-r--r--test/prism/errors/unterminated_block.txt2
-rw-r--r--test/prism/errors/unterminated_block_do_end.txt4
-rw-r--r--test/prism/errors/unterminated_class.txt4
-rw-r--r--test/prism/errors/unterminated_def.txt5
-rw-r--r--test/prism/errors/unterminated_end_upcase.txt4
-rw-r--r--test/prism/errors/unterminated_for.txt5
-rw-r--r--test/prism/errors/unterminated_heredoc_and_embexpr.txt11
-rw-r--r--test/prism/errors/unterminated_heredoc_and_embexpr_2.txt9
-rw-r--r--test/prism/errors/unterminated_if.txt5
-rw-r--r--test/prism/errors/unterminated_if_else.txt5
-rw-r--r--test/prism/errors/unterminated_lambda_brace.txt4
-rw-r--r--test/prism/errors/unterminated_module.txt4
-rw-r--r--test/prism/errors/unterminated_pattern_bracket.txt7
-rw-r--r--test/prism/errors/unterminated_pattern_paren.txt7
-rw-r--r--test/prism/errors/unterminated_until.txt5
-rw-r--r--test/prism/errors/void_value_expression_in_begin_statement.txt2
-rw-r--r--test/prism/errors/while_endless_method.txt2
-rw-r--r--test/prism/errors/xstring_concat.txt5
-rw-r--r--test/prism/errors_test.rb107
-rw-r--r--test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt1
-rw-r--r--test/prism/fixtures/3.3-3.3/it.txt (renamed from test/prism/fixtures/it.txt)2
-rw-r--r--test/prism/fixtures/3.3-3.3/it_indirect_writes.txt23
-rw-r--r--test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt1
-rw-r--r--test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt1
-rw-r--r--test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt1
-rw-r--r--test/prism/fixtures/3.3-3.3/return_in_sclass.txt1
-rw-r--r--test/prism/fixtures/3.3-4.0/end_block_exit.txt11
-rw-r--r--test/prism/fixtures/3.3-4.0/void_value.txt29
-rw-r--r--test/prism/fixtures/3.4/circular_parameters.txt4
-rw-r--r--test/prism/fixtures/3.4/it.txt5
-rw-r--r--test/prism/fixtures/3.4/it_indirect_writes.txt23
-rw-r--r--test/prism/fixtures/3.4/it_read_and_assignment.txt1
-rw-r--r--test/prism/fixtures/4.0/endless_methods_command_call.txt11
-rw-r--r--test/prism/fixtures/4.0/leading_logical.txt16
-rw-r--r--test/prism/fixtures/4.1/noblock.txt4
-rw-r--r--test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt15
-rw-r--r--test/prism/fixtures/4.1/void_value.txt7
-rw-r--r--test/prism/fixtures/__END__.txt3
-rw-r--r--test/prism/fixtures/and_or_with_suffix.txt17
-rw-r--r--test/prism/fixtures/begin_rescue.txt6
-rw-r--r--test/prism/fixtures/blocks.txt8
-rw-r--r--test/prism/fixtures/bom_leading_space.txt1
-rw-r--r--test/prism/fixtures/bom_spaces.txt1
-rw-r--r--test/prism/fixtures/break.txt4
-rw-r--r--test/prism/fixtures/case_in_hash_key.txt6
-rw-r--r--test/prism/fixtures/case_in_in.txt4
-rw-r--r--test/prism/fixtures/character_literal.txt2
-rw-r--r--test/prism/fixtures/command_method_call_2.txt1
-rw-r--r--test/prism/fixtures/command_method_call_3.txt19
-rw-r--r--test/prism/fixtures/defined.txt9
-rw-r--r--test/prism/fixtures/dstring.txt4
-rw-r--r--test/prism/fixtures/endless_method_as_default_arg.txt11
-rw-r--r--test/prism/fixtures/endless_methods.txt6
-rw-r--r--test/prism/fixtures/escaped_newline_with_trailing_content.txt2
-rw-r--r--test/prism/fixtures/heredoc_dedent_line_continuation.txt5
-rw-r--r--test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt22
-rw-r--r--test/prism/fixtures/heredocs_with_fake_newlines.txt55
-rw-r--r--test/prism/fixtures/it_assignment.txt1
-rw-r--r--test/prism/fixtures/keyword_method_names.txt9
-rw-r--r--test/prism/fixtures/next.txt4
-rw-r--r--test/prism/fixtures/non_void_value.txt31
-rw-r--r--test/prism/fixtures/patterns.txt5
-rw-r--r--test/prism/fixtures/regex_with_fake_newlines.txt41
-rw-r--r--test/prism/fixtures/rescue.txt4
-rw-r--r--test/prism/fixtures/rescue_modifier.txt7
-rw-r--r--test/prism/fixtures/return.txt3
-rw-r--r--test/prism/fixtures/string_concatination_frozen_false.txt5
-rw-r--r--test/prism/fixtures/string_concatination_frozen_true.txt5
-rw-r--r--test/prism/fixtures/strings.txt68
-rw-r--r--test/prism/fixtures/symbols.txt4
-rw-r--r--test/prism/fixtures/unary_method_calls.txt8
-rw-r--r--test/prism/fixtures/variables.txt2
-rw-r--r--test/prism/fixtures/write_command_operator.txt3
-rw-r--r--test/prism/fixtures_test.rb10
-rw-r--r--test/prism/lex_test.rb113
-rw-r--r--test/prism/locals_test.rb22
-rw-r--r--test/prism/magic_comment_test.rb5
-rw-r--r--test/prism/newline_offsets_test.rb27
-rw-r--r--test/prism/newline_test.rb3
-rw-r--r--test/prism/ractor_test.rb74
-rw-r--r--test/prism/result/breadth_first_search_test.rb11
-rw-r--r--test/prism/result/continuable_test.rb124
-rw-r--r--test/prism/result/error_recovery_test.rb237
-rw-r--r--test/prism/result/numeric_value_test.rb11
-rw-r--r--test/prism/result/overlap_test.rb9
-rw-r--r--test/prism/result/source_location_test.rb16
-rw-r--r--test/prism/result/warnings_test.rb21
-rw-r--r--test/prism/ruby/dispatcher_test.rb19
-rw-r--r--test/prism/ruby/find_fixtures.rb69
-rw-r--r--test/prism/ruby/find_test.rb242
-rw-r--r--test/prism/ruby/location_test.rb49
-rw-r--r--test/prism/ruby/parameters_signature_test.rb22
-rw-r--r--test/prism/ruby/parser_test.rb130
-rw-r--r--test/prism/ruby/ripper_test.rb278
-rw-r--r--test/prism/ruby/ruby_parser_test.rb48
-rw-r--r--test/prism/ruby/source_test.rb51
-rw-r--r--test/prism/snippets_test.rb12
-rw-r--r--test/prism/test_helper.rb91
-rw-r--r--test/psych/test_data.rb93
-rw-r--r--test/psych/test_date_time.rb15
-rw-r--r--test/psych/test_exception.rb13
-rw-r--r--test/psych/test_object_references.rb5
-rw-r--r--test/psych/test_parser.rb42
-rw-r--r--test/psych/test_psych.rb11
-rw-r--r--test/psych/test_psych_set.rb57
-rw-r--r--test/psych/test_ractor.rb6
-rw-r--r--test/psych/test_safe_load.rb32
-rw-r--r--test/psych/test_scalar_scanner.rb5
-rw-r--r--test/psych/test_serialize_subclasses.rb18
-rw-r--r--test/psych/test_set.rb61
-rw-r--r--test/psych/test_stream.rb8
-rw-r--r--test/psych/test_stringio.rb14
-rw-r--r--test/psych/test_yaml.rb47
-rw-r--r--test/psych/test_yaml_special_cases.rb12
-rw-r--r--test/psych/visitors/test_to_ruby.rb6
-rw-r--r--test/psych/visitors/test_yaml_tree.rb21
-rw-r--r--test/reline/helper.rb158
-rw-r--r--test/reline/test_ansi.rb72
-rw-r--r--test/reline/test_config.rb616
-rw-r--r--test/reline/test_face.rb257
-rw-r--r--test/reline/test_history.rb317
-rw-r--r--test/reline/test_key_actor_emacs.rb1743
-rw-r--r--test/reline/test_key_actor_vi.rb967
-rw-r--r--test/reline/test_key_stroke.rb111
-rw-r--r--test/reline/test_kill_ring.rb268
-rw-r--r--test/reline/test_line_editor.rb271
-rw-r--r--test/reline/test_macro.rb40
-rw-r--r--test/reline/test_reline.rb487
-rw-r--r--test/reline/test_reline_key.rb10
-rw-r--r--test/reline/test_string_processing.rb46
-rw-r--r--test/reline/test_unicode.rb286
-rw-r--r--test/reline/test_within_pipe.rb77
-rw-r--r--test/reline/windows/test_key_event_record.rb41
-rwxr-xr-xtest/reline/yamatanooroti/multiline_repl257
-rw-r--r--test/reline/yamatanooroti/termination_checker.rb26
-rw-r--r--test/reline/yamatanooroti/test_rendering.rb1915
-rw-r--r--test/resolv/test_dns.rb134
-rw-r--r--test/resolv/test_resource.rb74
-rw-r--r--test/resolv/test_win32_config.rb26
-rw-r--r--test/ripper/assert_parse_files.rb1
-rw-r--r--test/ripper/test_lexer.rb93
-rw-r--r--test/ripper/test_parser_events.rb7
-rw-r--r--test/ruby/box/a.1_1_0.rb17
-rw-r--r--test/ruby/box/a.1_2_0.rb17
-rw-r--r--test/ruby/box/a.rb15
-rw-r--r--test/ruby/box/autoloading.rb8
-rw-r--r--test/ruby/box/blank.rb2
-rw-r--r--test/ruby/box/blank1.rb2
-rw-r--r--test/ruby/box/blank2.rb2
-rw-r--r--test/ruby/box/box.rb10
-rw-r--r--test/ruby/box/call_proc.rb5
-rw-r--r--test/ruby/box/call_toplevel.rb8
-rw-r--r--test/ruby/box/consts.rb148
-rw-r--r--test/ruby/box/define_toplevel.rb5
-rw-r--r--test/ruby/box/global_vars.rb37
-rw-r--r--test/ruby/box/instance_variables.rb21
-rw-r--r--test/ruby/box/line_splitter.rb9
-rw-r--r--test/ruby/box/load_path.rb26
-rw-r--r--test/ruby/box/open_class_with_include.rb31
-rw-r--r--test/ruby/box/proc_callee.rb14
-rw-r--r--test/ruby/box/proc_caller.rb5
-rw-r--r--test/ruby/box/procs.rb64
-rw-r--r--test/ruby/box/raise.rb3
-rw-r--r--test/ruby/box/returns_proc.rb12
-rw-r--r--test/ruby/box/singleton_methods.rb65
-rw-r--r--test/ruby/box/string_ext.rb13
-rw-r--r--test/ruby/box/string_ext_caller.rb5
-rw-r--r--test/ruby/box/string_ext_calling.rb1
-rw-r--r--test/ruby/box/string_ext_eval_caller.rb12
-rw-r--r--test/ruby/box/top_level.rb33
-rw-r--r--test/ruby/enc/test_emoji_breaks.rb2
-rw-r--r--test/ruby/rjit/test_assembler.rb368
-rw-r--r--test/ruby/sentence.rb2
-rw-r--r--test/ruby/test_alias.rb13
-rw-r--r--test/ruby/test_allocation.rb129
-rw-r--r--test/ruby/test_array.rb159
-rw-r--r--test/ruby/test_ast.rb179
-rw-r--r--test/ruby/test_autoload.rb119
-rw-r--r--test/ruby/test_backtrace.rb22
-rw-r--r--test/ruby/test_beginendblock.rb3
-rw-r--r--test/ruby/test_bignum.rb46
-rw-r--r--test/ruby/test_box.rb1219
-rw-r--r--test/ruby/test_call.rb97
-rw-r--r--test/ruby/test_class.rb164
-rw-r--r--test/ruby/test_compile_prism.rb74
-rw-r--r--test/ruby/test_data.rb35
-rw-r--r--test/ruby/test_defined.rb48
-rw-r--r--test/ruby/test_dir.rb15
-rw-r--r--test/ruby/test_encoding.rb44
-rw-r--r--test/ruby/test_enum.rb10
-rw-r--r--test/ruby/test_enumerator.rb28
-rw-r--r--test/ruby/test_env.rb532
-rw-r--r--test/ruby/test_exception.rb29
-rw-r--r--test/ruby/test_fiber.rb46
-rw-r--r--test/ruby/test_file.rb19
-rw-r--r--test/ruby/test_file_exhaustive.rb53
-rw-r--r--test/ruby/test_float.rb47
-rw-r--r--test/ruby/test_frozen.rb16
-rw-r--r--test/ruby/test_gc.rb197
-rw-r--r--test/ruby/test_gc_compact.rb73
-rw-r--r--test/ruby/test_hash.rb72
-rw-r--r--test/ruby/test_integer.rb10
-rw-r--r--test/ruby/test_io.rb184
-rw-r--r--test/ruby/test_io_buffer.rb410
-rw-r--r--test/ruby/test_io_m17n.rb41
-rw-r--r--test/ruby/test_iseq.rb191
-rw-r--r--test/ruby/test_keyword.rb34
-rw-r--r--test/ruby/test_lambda.rb14
-rw-r--r--test/ruby/test_lazy_enumerator.rb21
-rw-r--r--test/ruby/test_literal.rb5
-rw-r--r--test/ruby/test_m17n.rb118
-rw-r--r--test/ruby/test_marshal.rb113
-rw-r--r--test/ruby/test_math.rb32
-rw-r--r--test/ruby/test_memory_view.rb2
-rw-r--r--test/ruby/test_metaclass.rb2
-rw-r--r--test/ruby/test_method.rb149
-rw-r--r--test/ruby/test_module.rb147
-rw-r--r--test/ruby/test_nomethod_error.rb30
-rw-r--r--test/ruby/test_numeric.rb42
-rw-r--r--test/ruby/test_object.rb204
-rw-r--r--test/ruby/test_object_id.rb303
-rw-r--r--test/ruby/test_objectspace.rb54
-rw-r--r--test/ruby/test_optimization.rb73
-rw-r--r--test/ruby/test_pack.rb167
-rw-r--r--test/ruby/test_parse.rb36
-rw-r--r--test/ruby/test_pattern_matching.rb42
-rw-r--r--test/ruby/test_proc.rb290
-rw-r--r--test/ruby/test_process.rb66
-rw-r--r--test/ruby/test_ractor.rb377
-rw-r--r--test/ruby/test_range.rb16
-rw-r--r--test/ruby/test_rational.rb48
-rw-r--r--test/ruby/test_refinement.rb950
-rw-r--r--test/ruby/test_regexp.rb276
-rw-r--r--test/ruby/test_require.rb48
-rw-r--r--test/ruby/test_require_lib.rb4
-rw-r--r--test/ruby/test_rubyoptions.rb192
-rw-r--r--test/ruby/test_set.rb (renamed from test/set/test_set.rb)217
-rw-r--r--test/ruby/test_settracefunc.rb220
-rw-r--r--test/ruby/test_shapes.rb419
-rw-r--r--test/ruby/test_signal.rb22
-rw-r--r--test/ruby/test_sleep.rb18
-rw-r--r--test/ruby/test_string.rb399
-rw-r--r--test/ruby/test_struct.rb20
-rw-r--r--test/ruby/test_super.rb29
-rw-r--r--test/ruby/test_symbol.rb5
-rw-r--r--test/ruby/test_syntax.rb287
-rw-r--r--test/ruby/test_thread.rb149
-rw-r--r--test/ruby/test_thread_cv.rb4
-rw-r--r--test/ruby/test_thread_queue.rb16
-rw-r--r--test/ruby/test_time.rb7
-rw-r--r--test/ruby/test_time_tz.rb1
-rw-r--r--test/ruby/test_transcode.rb87
-rw-r--r--test/ruby/test_variable.rb156
-rw-r--r--test/ruby/test_vm_dump.rb5
-rw-r--r--test/ruby/test_weakmap.rb34
-rw-r--r--test/ruby/test_yield.rb2
-rw-r--r--test/ruby/test_yjit.rb192
-rw-r--r--test/ruby/test_zjit.rb556
-rw-r--r--test/rubygems/coverage_setup.rb9
-rw-r--r--test/rubygems/helper.rb147
-rw-r--r--test/rubygems/installer_test_case.rb30
-rw-r--r--test/rubygems/mock_gem_ui.rb2
-rw-r--r--test/rubygems/package/tar_test_case.rb38
-rw-r--r--test/rubygems/test_bundled_ca.rb2
-rw-r--r--test/rubygems/test_config.rb7
-rw-r--r--test/rubygems/test_gem.rb102
-rw-r--r--test/rubygems/test_gem_bundler_version_finder.rb159
-rw-r--r--test/rubygems/test_gem_command_manager.rb45
-rw-r--r--test/rubygems/test_gem_commands_build_command.rb10
-rw-r--r--test/rubygems/test_gem_commands_cert_command.rb22
-rw-r--r--test/rubygems/test_gem_commands_environment_command.rb4
-rw-r--r--test/rubygems/test_gem_commands_exec_command.rb36
-rw-r--r--test/rubygems/test_gem_commands_fetch_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_help_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_info_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_install_command.rb156
-rw-r--r--test/rubygems/test_gem_commands_open_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_owner_command.rb30
-rw-r--r--test/rubygems/test_gem_commands_pristine_command.rb62
-rw-r--r--test/rubygems/test_gem_commands_push_command.rb172
-rw-r--r--test/rubygems/test_gem_commands_query_command.rb830
-rw-r--r--test/rubygems/test_gem_commands_setup_command.rb31
-rw-r--r--test/rubygems/test_gem_commands_signin_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_sources_command.rb685
-rw-r--r--test/rubygems/test_gem_commands_uninstall_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_update_command.rb32
-rw-r--r--test/rubygems/test_gem_commands_which_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_yank_command.rb15
-rw-r--r--test/rubygems/test_gem_config_file.rb56
-rw-r--r--test/rubygems/test_gem_dependency_installer.rb200
-rw-r--r--test/rubygems/test_gem_dependency_resolution_error.rb23
-rw-r--r--test/rubygems/test_gem_ext_builder.rb69
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder.rb62
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock53
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml2
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock53
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml2
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb2
-rw-r--r--test/rubygems/test_gem_ext_cmake_builder.rb97
-rw-r--r--test/rubygems/test_gem_ext_ext_conf_builder.rb33
-rw-r--r--test/rubygems/test_gem_ext_rake_builder.rb2
-rw-r--r--test/rubygems/test_gem_gem_runner.rb11
-rw-r--r--test/rubygems/test_gem_gemcutter_utilities.rb16
-rw-r--r--test/rubygems/test_gem_impossible_dependencies_error.rb60
-rw-r--r--test/rubygems/test_gem_install_update_options.rb12
-rw-r--r--test/rubygems/test_gem_installer.rb398
-rw-r--r--test/rubygems/test_gem_name_tuple.rb37
-rw-r--r--test/rubygems/test_gem_package.rb187
-rw-r--r--test/rubygems/test_gem_package_old.rb2
-rw-r--r--test/rubygems/test_gem_package_tar_header_ractor.rb61
-rw-r--r--test/rubygems/test_gem_package_tar_writer.rb33
-rw-r--r--test/rubygems/test_gem_path_support.rb8
-rw-r--r--test/rubygems/test_gem_platform.rb305
-rw-r--r--test/rubygems/test_gem_rdoc.rb14
-rw-r--r--test/rubygems/test_gem_remote_fetcher.rb239
-rw-r--r--test/rubygems/test_gem_remote_fetcher_s3.rb296
-rw-r--r--test/rubygems/test_gem_request.rb12
-rw-r--r--test/rubygems/test_gem_request_connection_pools.rb12
-rw-r--r--test/rubygems/test_gem_request_set.rb132
-rw-r--r--test/rubygems/test_gem_request_set_lockfile_parser.rb544
-rw-r--r--test/rubygems/test_gem_request_set_lockfile_tokenizer.rb307
-rw-r--r--test/rubygems/test_gem_requirement.rb22
-rw-r--r--test/rubygems/test_gem_resolver.rb496
-rw-r--r--test/rubygems/test_gem_resolver_best_set.rb14
-rw-r--r--test/rubygems/test_gem_resolver_conflict.rb80
-rw-r--r--test/rubygems/test_gem_resolver_git_specification.rb38
-rw-r--r--test/rubygems/test_gem_resolver_strategy.rb163
-rw-r--r--test/rubygems/test_gem_safe_marshal.rb4
-rw-r--r--test/rubygems/test_gem_safe_yaml.rb1302
-rw-r--r--test/rubygems/test_gem_security_trust_dir.rb6
-rw-r--r--test/rubygems/test_gem_source_git.rb2
-rw-r--r--test/rubygems/test_gem_source_list.rb127
-rw-r--r--test/rubygems/test_gem_source_local.rb24
-rw-r--r--test/rubygems/test_gem_specification.rb247
-rw-r--r--test/rubygems/test_gem_stub_specification.rb26
-rw-r--r--test/rubygems/test_gem_uri.rb2
-rw-r--r--test/rubygems/test_gem_util.rb11
-rw-r--r--test/rubygems/test_gem_util_atomic_file_writer.rb12
-rw-r--r--test/rubygems/test_gem_version.rb92
-rw-r--r--test/rubygems/test_project_sanity.rb3
-rw-r--r--test/rubygems/test_require.rb19
-rw-r--r--test/rubygems/test_rubygems.rb1
-rw-r--r--test/rubygems/test_webauthn_listener.rb2
-rw-r--r--test/set/fixtures/fake_sorted_set_gem/sorted_set.rb9
-rw-r--r--test/set/test_sorted_set.rb45
-rw-r--r--test/socket/test_addrinfo.rb6
-rw-r--r--test/socket/test_nonblock.rb4
-rw-r--r--test/socket/test_socket.rb147
-rw-r--r--test/socket/test_tcp.rb44
-rw-r--r--test/socket/test_unix.rb21
-rw-r--r--test/stringio/test_ractor.rb6
-rw-r--r--test/stringio/test_stringio.rb168
-rw-r--r--test/strscan/test_ractor.rb6
-rw-r--r--test/strscan/test_stringscanner.rb274
-rw-r--r--test/test_bundled_gems.rb38
-rw-r--r--test/test_delegate.rb57
-rw-r--r--test/test_extlibs.rb3
-rw-r--r--test/test_ipaddr.rb100
-rw-r--r--test/test_pp.rb74
-rw-r--r--test/test_prettyprint.rb71
-rw-r--r--test/test_pty.rb8
-rw-r--r--test/test_rbconfig.rb15
-rw-r--r--test/test_time.rb2
-rw-r--r--test/test_timeout.rb264
-rw-r--r--test/test_tmpdir.rb33
-rw-r--r--test/test_tsort.rb115
-rw-r--r--test/test_unicode_normalize.rb28
-rw-r--r--test/uri/test_common.rb26
-rw-r--r--test/uri/test_ftp.rb10
-rw-r--r--test/uri/test_generic.rb92
-rw-r--r--test/uri/test_http.rb20
-rw-r--r--test/uri/test_mailto.rb72
-rw-r--r--test/uri/test_parser.rb26
-rw-r--r--test/uri/test_ws.rb16
-rw-r--r--test/uri/test_wss.rb16
-rw-r--r--test/win32/test_registry.rb256
-rw-r--r--test/zlib/test_zlib.rb85
641 files changed, 26855 insertions, 28723 deletions
diff --git a/test/-ext-/box/test_load_ext.rb b/test/-ext-/box/test_load_ext.rb
new file mode 100644
index 0000000000..ea3744375e
--- /dev/null
+++ b/test/-ext-/box/test_load_ext.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class Test_Load_Extensions < Test::Unit::TestCase
+ ENV_ENABLE_BOX = {'RUBY_BOX' => '1'}
+
+ def test_load_extension
+ pend
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ require '-test-/box/yay1'
+ assert_equal "1.0.0", Yay.version
+ assert_equal "yay", Yay.yay
+ end;
+ end
+
+ def test_extension_contamination_in_global
+ pend
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ require '-test-/box/yay1'
+ yay1 = Yay
+ assert_equal "1.0.0", Yay.version
+ assert_equal "yay", Yay.yay
+
+ require '-test-/box/yay2'
+ assert_equal "2.0.0", Yay.version
+ v = Yay.yay
+ assert(v == "yay" || v == "yaaay") # "yay" on Linux, "yaaay" on macOS, Win32
+ end;
+ end
+
+ def test_load_extension_in_box
+ pend
+ assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ns = Ruby::Box.new
+ ns.require '-test-/box/yay1'
+ assert_equal "1.0.0", ns::Yay.version
+ assert_raise(NameError) { Yay }
+ end;
+ end
+
+ def test_different_version_extensions
+ pend
+ assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ns1 = Ruby::Box.new
+ ns2 = Ruby::Box.new
+ ns1.require('-test-/box/yay1')
+ ns2.require('-test-/box/yay2')
+
+ assert_raise(NameError) { Yay }
+ assert_not_nil ns1::Yay
+ assert_not_nil ns2::Yay
+ assert_equal "1.0.0", ns1::Yay::VERSION
+ assert_equal "2.0.0", ns2::Yay::VERSION
+ assert_equal "1.0.0", ns1::Yay.version
+ assert_equal "2.0.0", ns2::Yay.version
+ end;
+ end
+
+ def test_loading_extensions_from_global_to_local
+ pend
+ assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ require '-test-/box/yay1'
+ assert_equal "1.0.0", Yay.version
+ assert_equal "yay", Yay.yay
+
+ ns = Ruby::Box.new
+ ns.require '-test-/box/yay2'
+ assert_equal "2.0.0", ns::Yay.version
+ assert_equal "yaaay", ns::Yay.yay
+
+ assert_equal "yay", Yay.yay
+ end;
+ end
+
+ def test_loading_extensions_from_local_to_global
+ pend
+ assert_separately([ENV_ENABLE_BOX], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ ns = Ruby::Box.new
+ ns.require '-test-/box/yay1'
+ assert_equal "1.0.0", ns::Yay.version
+ assert_equal "yay", ns::Yay.yay
+
+
+ require '-test-/box/yay2'
+ assert_equal "2.0.0", Yay.version
+ assert_equal "yaaay", Yay.yay
+
+ assert_equal "yay", ns::Yay.yay
+ end;
+ end
+end
diff --git a/test/-ext-/bug_reporter/test_bug_reporter.rb b/test/-ext-/bug_reporter/test_bug_reporter.rb
index 0036137f02..83fdba2282 100644
--- a/test/-ext-/bug_reporter/test_bug_reporter.rb
+++ b/test/-ext-/bug_reporter/test_bug_reporter.rb
@@ -6,12 +6,8 @@ require_relative '../../lib/parser_support'
class TestBugReporter < Test::Unit::TestCase
def test_bug_reporter_add
- pend "macOS 15 is not working with this test" if macos?(15)
-
- omit "flaky with RJIT" if JITSupport.rjit_enabled?
description = RUBY_DESCRIPTION
description = description.sub(/\+PRISM /, '') unless ParserSupport.prism_enabled_in_subprocess?
- description = description.sub(/\+RJIT /, '') unless JITSupport.rjit_force_enabled?
expected_stderr = [
:*,
/\[BUG\]\sSegmentation\sfault.*\n/,
@@ -24,8 +20,10 @@ class TestBugReporter < Test::Unit::TestCase
no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE)
args = ["-r-test-/bug_reporter", "-C", tmpdir]
- args.push("--yjit") if JITSupport.yjit_enabled? # We want the printed description to match this process's RUBY_DESCRIPTION
- args.unshift({"RUBY_ON_BUG" => nil})
+ # We want the printed description to match this process's RUBY_DESCRIPTION
+ args.push("--yjit") if JITSupport.yjit_enabled?
+ args.push("--zjit") if JITSupport.zjit_enabled?
+ args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil})
stdin = "#{no_core}register_sample_bug_reporter(12345); Process.kill :SEGV, $$"
assert_in_out_err(args, stdin, [], expected_stderr, encoding: "ASCII-8BIT")
ensure
diff --git a/test/-ext-/debug/test_debug.rb b/test/-ext-/debug/test_debug.rb
index 98e178e34f..c9263d76fa 100644
--- a/test/-ext-/debug/test_debug.rb
+++ b/test/-ext-/debug/test_debug.rb
@@ -76,3 +76,57 @@ class TestDebug < Test::Unit::TestCase
assert_equal true, x, '[Bug #15105]'
end
end
+
+# This is a YJIT test, but we can't test this without a C extension that calls
+# rb_debug_inspector_open(), so we're testing it using "-test-/debug" here.
+class TestDebugWithYJIT < Test::Unit::TestCase
+ class LocalSetArray
+ def to_a
+ Bug::Debug.inspector.each do |_, binding,|
+ binding.local_variable_set(:local, :ok) if binding
+ end
+ [:ok]
+ end
+ end
+
+ class DebugArray
+ def to_a
+ Bug::Debug.inspector
+ [:ok]
+ end
+ end
+
+ def test_yjit_invalidates_getlocal_after_splatarray
+ val = getlocal_after_splatarray(LocalSetArray.new)
+ assert_equal [:ok, :ok], val
+ end
+
+ def test_yjit_invalidates_setlocal_after_splatarray
+ val = setlocal_after_splatarray(DebugArray.new)
+ assert_equal [:ok], val
+ end
+
+ def test_yjit_invalidates_setlocal_after_proc_call
+ val = setlocal_after_proc_call(proc { Bug::Debug.inspector; :ok })
+ assert_equal :ok, val
+ end
+
+ private
+
+ def getlocal_after_splatarray(array)
+ local = 1
+ [*array, local]
+ end
+
+ def setlocal_after_splatarray(array)
+ local = *array # setlocal followed by splatarray
+ itself # split a block using a C call
+ local # getlocal
+ end
+
+ def setlocal_after_proc_call(block)
+ local = block.call # setlocal followed by OPTIMIZED_METHOD_TYPE_CALL
+ itself # split a block using a C call
+ local # getlocal
+ end
+end if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
diff --git a/test/-ext-/gvl/test_last_thread.rb b/test/-ext-/gvl/test_last_thread.rb
index f1bebafeea..bcda0e3385 100644
--- a/test/-ext-/gvl/test_last_thread.rb
+++ b/test/-ext-/gvl/test_last_thread.rb
@@ -15,8 +15,7 @@ class TestLastThread < Test::Unit::TestCase
t1 = Time.now
t = t1 - t0
- assert_in_delta(1.0, t, 0.16)
+ assert_in_delta(1.0, t, 0.8)
end;
end
end
-
diff --git a/test/-ext-/marshal/test_internal_ivar.rb b/test/-ext-/marshal/test_internal_ivar.rb
index faabe14ab2..8b4667fdf9 100644
--- a/test/-ext-/marshal/test_internal_ivar.rb
+++ b/test/-ext-/marshal/test_internal_ivar.rb
@@ -7,11 +7,16 @@ module Bug end
module Bug::Marshal
class TestInternalIVar < Test::Unit::TestCase
def test_marshal
- v = InternalIVar.new("hello", "world", "bye")
+ v = InternalIVar.new("hello", "world", "bye", "hi")
assert_equal("hello", v.normal)
assert_equal("world", v.internal)
assert_equal("bye", v.encoding_short)
- dump = assert_warn(/instance variable 'E' on class \S+ is not dumped/) {
+ assert_equal("hi", v.encoding_long)
+ warnings = ->(s) {
+ w = s.scan(/instance variable '(.+?)' on class \S+ is not dumped/)
+ assert_equal(%w[E K encoding], w.flatten.sort)
+ }
+ dump = assert_warn(warnings) {
::Marshal.dump(v)
}
v = assert_nothing_raised {break ::Marshal.load(dump)}
@@ -19,6 +24,7 @@ module Bug::Marshal
assert_equal("hello", v.normal)
assert_nil(v.internal)
assert_nil(v.encoding_short)
+ assert_nil(v.encoding_long)
end
end
end
diff --git a/test/-ext-/postponed_job/test_postponed_job.rb b/test/-ext-/postponed_job/test_postponed_job.rb
index 8c2b3e95d1..01d6015de1 100644
--- a/test/-ext-/postponed_job/test_postponed_job.rb
+++ b/test/-ext-/postponed_job/test_postponed_job.rb
@@ -33,39 +33,4 @@ class TestPostponed_job < Test::Unit::TestCase
assert_equal [3, 4], values
RUBY
end
-
- def test_legacy_register
- assert_separately([], __FILE__, __LINE__, <<-'RUBY')
- require '-test-/postponed_job'
- direct, registered = [], []
-
- Bug.postponed_job_call_direct(direct)
- Bug.postponed_job_register(registered)
-
- assert_equal([0], direct)
- assert_equal([3], registered)
-
- Bug.postponed_job_register_one(ary = [])
- assert_equal [1], ary
- RUBY
- end
-
- def test_legacy_register_one_same
- assert_separately([], __FILE__, __LINE__, <<-'RUBY')
- require '-test-/postponed_job'
- # Registering the same job three times should result in three of the same handle
- handles = Bug.postponed_job_register_one_same
- assert_equal [handles[0]], handles.uniq
- RUBY
- end
-
- if Bug.respond_to?(:postponed_job_register_in_c_thread)
- def test_legacy_register_in_c_thread
- assert_separately([], __FILE__, __LINE__, <<-'RUBY')
- require '-test-/postponed_job'
- assert Bug.postponed_job_register_in_c_thread(ary = [])
- assert_equal [1], ary
- RUBY
- end
- end
end
diff --git a/test/-ext-/scheduler/test_interrupt_with_scheduler.rb b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb
new file mode 100644
index 0000000000..eb7a0647e5
--- /dev/null
+++ b/test/-ext-/scheduler/test_interrupt_with_scheduler.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+require 'test/unit'
+require 'timeout'
+require_relative '../../fiber/scheduler'
+
+class TestSchedulerInterruptHandling < Test::Unit::TestCase
+ def setup
+ pend("No fork support") unless Process.respond_to?(:fork)
+ require '-test-/scheduler'
+ end
+
+ # Test without Thread.handle_interrupt - should work regardless of fix
+ def test_without_handle_interrupt_signal_works
+ IO.pipe do |input, output|
+ pid = fork do
+ STDERR.reopen(output)
+
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ Signal.trap(:INT) do
+ ::Thread.current.raise(Interrupt)
+ end
+
+ Fiber.schedule do
+ # Yield to the scheduler:
+ sleep(0)
+
+ Bug::Scheduler.blocking_loop(output)
+ end
+ end
+
+ output.close
+ assert_equal "x", input.read(1)
+
+ Process.kill(:INT, pid)
+
+ reaper = Thread.new do
+ Process.waitpid2(pid)
+ end
+
+ unless reaper.join(10)
+ Process.kill(:KILL, pid)
+ end
+
+ _, status = reaper.value
+
+ # It should be interrupted (not killed):
+ assert_not_equal 0, status.exitstatus
+ assert_equal true, status.signaled?
+ assert_equal Signal.list["INT"], status.termsig
+ end
+ end
+end
diff --git a/test/-ext-/stack/test_stack_overflow.rb b/test/-ext-/stack/test_stack_overflow.rb
new file mode 100644
index 0000000000..3d7f00331d
--- /dev/null
+++ b/test/-ext-/stack/test_stack_overflow.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+require 'test/unit'
+
+class Test_StackOverflow < Test::Unit::TestCase
+ def setup
+ omit "Stack overflow tests are not supported on this platform: #{RUBY_PLATFORM.inspect}" unless RUBY_PLATFORM =~ /x86_64-linux|darwin/
+
+ require '-test-/stack'
+
+ omit "Stack overflow tests are not supported with ASAN" if Thread.asan?
+ end
+
+ def test_overflow
+ assert_separately([], <<~RUBY)
+ # GC may try to scan the top of the stack and cause a SEGV.
+ GC.disable
+ require '-test-/stack'
+
+ assert_raise(SystemStackError) do
+ Thread.stack_overflow
+ end
+ RUBY
+ end
+
+ def test_thread_stack_overflow
+ assert_separately([], <<~RUBY)
+ require '-test-/stack'
+ GC.disable
+
+ thread = Thread.new do
+ Thread.current.report_on_exception = false
+ Thread.stack_overflow
+ end
+
+ assert_raise(SystemStackError) do
+ thread.join
+ end
+ RUBY
+ end
+
+ def test_fiber_stack_overflow
+ assert_separately([], <<~RUBY)
+ require '-test-/stack'
+ GC.disable
+
+ fiber = Fiber.new do
+ Thread.stack_overflow
+ end
+
+ assert_raise(SystemStackError) do
+ fiber.resume
+ end
+ RUBY
+ end
+end
diff --git a/test/-ext-/string/test_capacity.rb b/test/-ext-/string/test_capacity.rb
index bcca64d85a..a23892142a 100644
--- a/test/-ext-/string/test_capacity.rb
+++ b/test/-ext-/string/test_capacity.rb
@@ -2,16 +2,17 @@
require 'test/unit'
require '-test-/string'
require 'rbconfig/sizeof'
+require 'objspace'
class Test_StringCapacity < Test::Unit::TestCase
def test_capacity_embedded
- assert_equal GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] - embed_header_size - 1, capa('foo')
+ assert_equal pool_slot_size(0) - embed_header_size - 1, capa('foo')
assert_equal max_embed_len, capa('1' * max_embed_len)
assert_equal max_embed_len, capa('1' * (max_embed_len - 1))
end
def test_capacity_shared
- sym = ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]).to_sym
+ sym = ("a" * pool_slot_size(0)).to_sym
assert_equal 0, capa(sym.to_s)
end
@@ -47,7 +48,7 @@ class Test_StringCapacity < Test::Unit::TestCase
def test_capacity_frozen
s = String.new("I am testing", capacity: 1000)
- s << "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
+ s << "a" * pool_slot_size(0)
s.freeze
assert_equal(s.length, capa(s))
end
@@ -66,7 +67,11 @@ class Test_StringCapacity < Test::Unit::TestCase
end
def embed_header_size
- 3 * RbConfig::SIZEOF['void*']
+ GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*']
+ end
+
+ def pool_slot_size(_idx = 0)
+ Integer(ObjectSpace.dump("")[/"slot_size":(\d+)/, 1])
end
def max_embed_len
diff --git a/test/-ext-/string/test_interned_str.rb b/test/-ext-/string/test_interned_str.rb
index 340dba41e8..a81cb59aa5 100644
--- a/test/-ext-/string/test_interned_str.rb
+++ b/test/-ext-/string/test_interned_str.rb
@@ -9,4 +9,9 @@ class Test_RbInternedStr < Test::Unit::TestCase
src << "b" * 20
assert_equal "a" * 20, interned_str
end
+
+ def test_interned_str_encoding
+ src = :ascii.name
+ assert_equal Encoding::US_ASCII, Bug::String.rb_interned_str_dup(src).encoding
+ end
end
diff --git a/test/-ext-/string/test_set_len.rb b/test/-ext-/string/test_set_len.rb
index 1531d76167..41e14a293a 100644
--- a/test/-ext-/string/test_set_len.rb
+++ b/test/-ext-/string/test_set_len.rb
@@ -5,7 +5,7 @@ require "-test-/string"
class Test_StrSetLen < Test::Unit::TestCase
def setup
# Make string long enough so that it is not embedded
- @range_end = ("0".ord + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]).chr
+ @range_end = ("0".ord + GC.stat_heap(0, :slot_size)).chr
@s0 = [*"0"..@range_end].join("").freeze
@s1 = Bug::String.new(@s0)
end
diff --git a/test/-ext-/symbol/test_type.rb b/test/-ext-/symbol/test_type.rb
index 2b0fbe5b79..ed019062fa 100644
--- a/test/-ext-/symbol/test_type.rb
+++ b/test/-ext-/symbol/test_type.rb
@@ -123,16 +123,20 @@ module Test_Symbol
def test_check_id_invalid_type
cx = EnvUtil.labeled_class("X\u{1f431}")
- assert_raise_with_message(TypeError, /X\u{1F431}/) {
- Bug::Symbol.pinneddown?(cx)
- }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{1F431}/) {
+ Bug::Symbol.pinneddown?(cx)
+ }
+ end
end
def test_check_symbol_invalid_type
cx = EnvUtil.labeled_class("X\u{1f431}")
- assert_raise_with_message(TypeError, /X\u{1F431}/) {
- Bug::Symbol.find(cx)
- }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{1F431}/) {
+ Bug::Symbol.find(cx)
+ }
+ end
end
def test_const_name_type
diff --git a/test/-ext-/test_abi.rb b/test/-ext-/test_abi.rb
index d3ea6bb9b1..7f30feb944 100644
--- a/test/-ext-/test_abi.rb
+++ b/test/-ext-/test_abi.rb
@@ -9,7 +9,11 @@ class TestABI < Test::Unit::TestCase
assert_separately [], <<~RUBY
err = assert_raise(LoadError) { require "-test-/abi" }
assert_match(/incompatible ABI version/, err.message)
- assert_include err.message, "/-test-/abi."
+ if Ruby::Box.enabled?
+ assert_include err.message, "_-test-+abi."
+ else
+ assert_include err.message, "/-test-/abi."
+ end
RUBY
end
@@ -27,7 +31,11 @@ class TestABI < Test::Unit::TestCase
assert_separately [{ "RUBY_ABI_CHECK" => "1" }], <<~RUBY
err = assert_raise(LoadError) { require "-test-/abi" }
assert_match(/incompatible ABI version/, err.message)
- assert_include err.message, "/-test-/abi."
+ if Ruby::Box.enabled?
+ assert_include err.message, "_-test-+abi."
+ else
+ assert_include err.message, "/-test-/abi."
+ end
RUBY
end
diff --git a/test/-ext-/thread/test_instrumentation_api.rb b/test/-ext-/thread/test_instrumentation_api.rb
index 663e41be53..ba41069304 100644
--- a/test/-ext-/thread/test_instrumentation_api.rb
+++ b/test/-ext-/thread/test_instrumentation_api.rb
@@ -151,7 +151,7 @@ class TestThreadInstrumentation < Test::Unit::TestCase
end
full_timeline = record do
- ractor.take
+ ractor.value
end
timeline = timeline_for(Thread.current, full_timeline)
@@ -161,6 +161,8 @@ class TestThreadInstrumentation < Test::Unit::TestCase
end
def test_sleeping_inside_ractor
+ omit "This test is flaky and intermittently failing now on ModGC workflow" if ENV['GITHUB_WORKFLOW'] == 'ModGC'
+
assert_ractor(<<-"RUBY", require_relative: "helper", require: "-test-/thread/instrumentation")
include ThreadInstrumentation::TestHelper
@@ -170,7 +172,7 @@ class TestThreadInstrumentation < Test::Unit::TestCase
thread = Ractor.new{
sleep 0.1
Thread.current
- }.take
+ }.value
sleep 0.1
end
diff --git a/test/-ext-/thread/test_lock_native_thread.rb b/test/-ext-/thread/test_lock_native_thread.rb
index 8a5ba78838..b4044b2b93 100644
--- a/test/-ext-/thread/test_lock_native_thread.rb
+++ b/test/-ext-/thread/test_lock_native_thread.rb
@@ -15,6 +15,8 @@ end
class TestThreadLockNativeThread < Test::Unit::TestCase
def test_lock_native_thread
+ omit "LSAN reports memory leak because NT is not freed for MN thread" if Test::Sanitizers.lsan_enabled?
+
assert_separately([{'RUBY_MN_THREADS' => '1'}], <<-RUBY)
require '-test-/thread/lock_native_thread'
@@ -28,6 +30,8 @@ class TestThreadLockNativeThread < Test::Unit::TestCase
end
def test_lock_native_thread_tls
+ omit "LSAN reports memory leak because NT is not freed for MN thread" if Test::Sanitizers.lsan_enabled?
+
assert_separately([{'RUBY_MN_THREADS' => '1'}], <<-RUBY)
require '-test-/thread/lock_native_thread'
tn = 10
diff --git a/test/-ext-/thread_fd/test_thread_fd_close.rb b/test/-ext-/thread_fd/test_thread_fd_close.rb
deleted file mode 100644
index 1d2ef63635..0000000000
--- a/test/-ext-/thread_fd/test_thread_fd_close.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require '-test-/thread_fd'
-
-class TestThreadFdClose < Test::Unit::TestCase
-
- def test_thread_fd_close
- IO.pipe do |r, w|
- th = Thread.new do
- begin
- assert_raise(IOError) {
- r.read(4)
- }
- ensure
- w.syswrite('done')
- end
- end
- Thread.pass until th.stop?
- IO.thread_fd_close(r.fileno)
- assert_equal 'done', r.read(4)
- th.join
- end
- end
-end
diff --git a/test/-ext-/tracepoint/test_tracepoint.rb b/test/-ext-/tracepoint/test_tracepoint.rb
index bf66d8f105..603fd01fd5 100644
--- a/test/-ext-/tracepoint/test_tracepoint.rb
+++ b/test/-ext-/tracepoint/test_tracepoint.rb
@@ -83,7 +83,7 @@ class TestTracepointObj < Test::Unit::TestCase
end
def test_teardown_with_active_GC_end_hook
- assert_separately([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}')
+ assert_ruby_status([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}; GC.start')
end
end
diff --git a/test/.excludes-mmtk/TestEtc.rb b/test/.excludes-mmtk/TestEtc.rb
new file mode 100644
index 0000000000..746f5ba321
--- /dev/null
+++ b/test/.excludes-mmtk/TestEtc.rb
@@ -0,0 +1 @@
+exclude(:test_ractor_parallel, "glibc error: Mutex lock with MarkSweep debug")
diff --git a/test/.excludes-mmtk/TestGc.rb b/test/.excludes-mmtk/TestGc.rb
index 228a344aa5..cbab458b90 100644
--- a/test/.excludes-mmtk/TestGc.rb
+++ b/test/.excludes-mmtk/TestGc.rb
@@ -7,6 +7,7 @@ exclude(:test_gc_config_setting_returns_updated_config_hash, "testing behaviour
exclude(:test_gc_internals, "testing behaviour specific to default GC")
exclude(:test_gc_parameter, "testing behaviour specific to default GC")
exclude(:test_gc_parameter_init_slots, "testing behaviour specific to default GC")
+exclude(:test_heaps_grow_independently, "testing behaviour specific to default GC")
exclude(:test_latest_gc_info, "testing behaviour specific to default GC")
exclude(:test_latest_gc_info_argument, "testing behaviour specific to default GC")
exclude(:test_latest_gc_info_need_major_by, "testing behaviour specific to default GC")
diff --git a/test/.excludes-mmtk/TestObjSpace.rb b/test/.excludes-mmtk/TestObjSpace.rb
index eedbc308d4..94eb2c436d 100644
--- a/test/.excludes-mmtk/TestObjSpace.rb
+++ b/test/.excludes-mmtk/TestObjSpace.rb
@@ -1,3 +1,4 @@
exclude(:test_dump_all_full, "testing behaviour specific to default GC")
+exclude(:test_dump_flag_age, "testing behaviour specific to default GC")
exclude(:test_dump_flags, "testing behaviour specific to default GC")
-exclude(:test_finalizer, "times out in debug mode on Ubuntu")
+exclude(:test_dump_objects_dumps_page_slot_sizes, "testing behaviour specific to default GC")
diff --git a/test/.excludes-mmtk/TestObjectSpace.rb b/test/.excludes-mmtk/TestObjectSpace.rb
new file mode 100644
index 0000000000..a92be8090c
--- /dev/null
+++ b/test/.excludes-mmtk/TestObjectSpace.rb
@@ -0,0 +1 @@
+exclude(:test_finalizer, "times out in debug mode on Ubuntu")
diff --git a/test/.excludes-zjit/TestResolvDNS.rb b/test/.excludes-zjit/TestResolvDNS.rb
new file mode 100644
index 0000000000..1a85ea90be
--- /dev/null
+++ b/test/.excludes-zjit/TestResolvDNS.rb
@@ -0,0 +1 @@
+exclude(:test_multiple_servers_with_timeout_and_truncated_tcp_fallback, 'randomly crashes on Thread#value, showing up as Timeout') # https://github.com/Shopify/ruby/issues/852
diff --git a/test/.excludes/JSONGenericObjectTest.rb b/test/.excludes/JSONGenericObjectTest.rb
new file mode 100644
index 0000000000..820a6a0120
--- /dev/null
+++ b/test/.excludes/JSONGenericObjectTest.rb
@@ -0,0 +1,4 @@
+# ostruct will be loaded when JSON::GenericObject is autoloaded. By
+# removing all test methods, the autoload in `setup` is not triggered.
+
+exclude /test_/, 'JSON::GenericObject needs ostruct gem'
diff --git a/test/.excludes/TestPatternMatching.rb b/test/.excludes/TestPatternMatching.rb
new file mode 100644
index 0000000000..04b2f16de8
--- /dev/null
+++ b/test/.excludes/TestPatternMatching.rb
@@ -0,0 +1 @@
+exclude(:test_alternative_pattern_nested, "Changes here for syntax errors") if RUBY_DESCRIPTION.include?("+GC")
diff --git a/test/.excludes/TestThread.rb b/test/.excludes/TestThread.rb
index f26ea420a6..63f193e484 100644
--- a/test/.excludes/TestThread.rb
+++ b/test/.excludes/TestThread.rb
@@ -15,4 +15,6 @@ end
if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS')
# to avoid "`failed to allocate memory (NoMemoryError)" error
exclude(:test_thread_interrupt_for_killed_thread, 'TODO')
+ # timeout only on mswin, not mingw
+ exclude(:test_thread_join_during_finalizers, 'Timeout')
end
diff --git a/test/.excludes/URI/TestMailTo.rb b/test/.excludes/URI/TestMailTo.rb
new file mode 100644
index 0000000000..c9b1f94fe2
--- /dev/null
+++ b/test/.excludes/URI/TestMailTo.rb
@@ -0,0 +1 @@
+exclude :test_email_regexp, "still flaky with --repeat-count option"
diff --git a/test/.excludes/_appveyor/TestArray.rb b/test/.excludes/_appveyor/TestArray.rb
deleted file mode 100644
index 7d03833f07..0000000000
--- a/test/.excludes/_appveyor/TestArray.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# https://ci.appveyor.com/project/ruby/ruby/builds/20339189/job/ltdpffep976xtj85
-# `test_push_over_ary_max': failed to allocate memory (NoMemoryError)
-exclude(:test_push_over_ary_max, 'Sometimes AppVeyor has insufficient memory to run this test')
-# https://ci.appveyor.com/project/ruby/ruby/builds/20728419/job/o73q9fy1ojfibg5v
-exclude(:test_unshift_over_ary_max, 'Sometimes AppVeyor has insufficient memory to run this test')
-# https://ci.appveyor.com/project/ruby/ruby/builds/20427662/job/prq9i2lkfxv2j0uy
-exclude(:test_splice_over_ary_max, 'Sometimes AppVeyor has insufficient memory to run this test')
diff --git a/test/cgi/test_cgi_cookie.rb b/test/cgi/test_cgi_cookie.rb
deleted file mode 100644
index eadae45313..0000000000
--- a/test/cgi/test_cgi_cookie.rb
+++ /dev/null
@@ -1,211 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require 'cgi'
-require 'stringio'
-require_relative 'update_env'
-
-
-class CGICookieTest < Test::Unit::TestCase
- include UpdateEnv
-
-
- def setup
- @environ = {}
- update_env(
- 'REQUEST_METHOD' => 'GET',
- 'SCRIPT_NAME' => nil,
- )
- @str1="\xE3\x82\x86\xE3\x82\x93\xE3\x82\x86\xE3\x82\x93".dup
- @str1.force_encoding("UTF-8") if defined?(::Encoding)
- end
-
- def teardown
- ENV.update(@environ)
- end
-
-
- def test_cgi_cookie_new_simple
- cookie = CGI::Cookie.new('name1', 'val1', '&<>"', @str1)
- assert_equal('name1', cookie.name)
- assert_equal(['val1', '&<>"', @str1], cookie.value)
- assert_nil(cookie.domain)
- assert_nil(cookie.expires)
- assert_equal('', cookie.path)
- assert_equal(false, cookie.secure)
- assert_equal(false, cookie.httponly)
- assert_equal("name1=val1&%26%3C%3E%22&%E3%82%86%E3%82%93%E3%82%86%E3%82%93; path=", cookie.to_s)
- end
-
-
- def test_cgi_cookie_new_complex
- t = Time.gm(2030, 12, 31, 23, 59, 59)
- value = ['val1', '&<>"', "\xA5\xE0\xA5\xB9\xA5\xAB".dup]
- value[2].force_encoding("EUC-JP") if defined?(::Encoding)
- cookie = CGI::Cookie.new('name'=>'name1',
- 'value'=>value,
- 'path'=>'/cgi-bin/myapp/',
- 'domain'=>'www.example.com',
- 'expires'=>t,
- 'secure'=>true,
- 'httponly'=>true
- )
- assert_equal('name1', cookie.name)
- assert_equal(value, cookie.value)
- assert_equal('www.example.com', cookie.domain)
- assert_equal(t, cookie.expires)
- assert_equal('/cgi-bin/myapp/', cookie.path)
- assert_equal(true, cookie.secure)
- assert_equal(true, cookie.httponly)
- assert_equal('name1=val1&%26%3C%3E%22&%A5%E0%A5%B9%A5%AB; domain=www.example.com; path=/cgi-bin/myapp/; expires=Tue, 31 Dec 2030 23:59:59 GMT; secure; HttpOnly', cookie.to_s)
- end
-
-
- def test_cgi_cookie_new_with_domain
- h = {'name'=>'name1', 'value'=>'value1'}
- cookie = CGI::Cookie.new(h.merge('domain'=>'a.example.com'))
- assert_equal('a.example.com', cookie.domain)
-
- cookie = CGI::Cookie.new(h.merge('domain'=>'.example.com'))
- assert_equal('.example.com', cookie.domain)
-
- cookie = CGI::Cookie.new(h.merge('domain'=>'1.example.com'))
- assert_equal('1.example.com', cookie.domain, 'enhanced by RFC 1123')
-
- assert_raise(ArgumentError) {
- CGI::Cookie.new(h.merge('domain'=>'-a.example.com'))
- }
-
- assert_raise(ArgumentError) {
- CGI::Cookie.new(h.merge('domain'=>'a-.example.com'))
- }
- end
-
-
- def test_cgi_cookie_scriptname
- cookie = CGI::Cookie.new('name1', 'value1')
- assert_equal('', cookie.path)
- cookie = CGI::Cookie.new('name'=>'name1', 'value'=>'value1')
- assert_equal('', cookie.path)
- ## when ENV['SCRIPT_NAME'] is set, cookie.path is set automatically
- ENV['SCRIPT_NAME'] = '/cgi-bin/app/example.cgi'
- cookie = CGI::Cookie.new('name1', 'value1')
- assert_equal('/cgi-bin/app/', cookie.path)
- cookie = CGI::Cookie.new('name'=>'name1', 'value'=>'value1')
- assert_equal('/cgi-bin/app/', cookie.path)
- end
-
-
- def test_cgi_cookie_parse
- ## ';' separator
- cookie_str = 'name1=val1&val2; name2=val2&%26%3C%3E%22&%E3%82%86%E3%82%93%E3%82%86%E3%82%93;_session_id=12345'
- cookies = CGI::Cookie.parse(cookie_str)
- list = [
- ['name1', ['val1', 'val2']],
- ['name2', ['val2', '&<>"',@str1]],
- ['_session_id', ['12345']],
- ]
- list.each do |name, value|
- cookie = cookies[name]
- assert_equal(name, cookie.name)
- assert_equal(value, cookie.value)
- end
- ## don't allow ',' separator
- cookie_str = 'name1=val1&val2, name2=val2'
- cookies = CGI::Cookie.parse(cookie_str)
- list = [
- ['name1', ['val1', 'val2, name2=val2']],
- ]
- list.each do |name, value|
- cookie = cookies[name]
- assert_equal(name, cookie.name)
- assert_equal(value, cookie.value)
- end
- end
-
- def test_cgi_cookie_parse_not_decode_name
- cookie_str = "%66oo=baz;foo=bar"
- cookies = CGI::Cookie.parse(cookie_str)
- assert_equal({"%66oo" => ["baz"], "foo" => ["bar"]}, cookies)
- end
-
- def test_cgi_cookie_arrayinterface
- cookie = CGI::Cookie.new('name1', 'a', 'b', 'c')
- assert_equal('a', cookie[0])
- assert_equal('c', cookie[2])
- assert_nil(cookie[3])
- assert_equal('a', cookie.first)
- assert_equal('c', cookie.last)
- assert_equal(['A', 'B', 'C'], cookie.collect{|e| e.upcase})
- end
-
-
- def test_cgi_cookie_domain_injection_into_name
- name = "a=b; domain=example.com;"
- path = "/"
- domain = "example.jp"
- assert_raise(ArgumentError) do
- CGI::Cookie.new('name' => name,
- 'value' => "value",
- 'domain' => domain,
- 'path' => path)
- end
- end
-
-
- def test_cgi_cookie_newline_injection_into_name
- name = "a=b;\r\nLocation: http://example.com#"
- path = "/"
- domain = "example.jp"
- assert_raise(ArgumentError) do
- CGI::Cookie.new('name' => name,
- 'value' => "value",
- 'domain' => domain,
- 'path' => path)
- end
- end
-
-
- def test_cgi_cookie_multibyte_injection_into_name
- name = "a=b;\u3042"
- path = "/"
- domain = "example.jp"
- assert_raise(ArgumentError) do
- CGI::Cookie.new('name' => name,
- 'value' => "value",
- 'domain' => domain,
- 'path' => path)
- end
- end
-
-
- def test_cgi_cookie_injection_into_path
- name = "name"
- path = "/; samesite=none"
- domain = "example.jp"
- assert_raise(ArgumentError) do
- CGI::Cookie.new('name' => name,
- 'value' => "value",
- 'domain' => domain,
- 'path' => path)
- end
- end
-
-
- def test_cgi_cookie_injection_into_domain
- name = "name"
- path = "/"
- domain = "example.jp; samesite=none"
- assert_raise(ArgumentError) do
- CGI::Cookie.new('name' => name,
- 'value' => "value",
- 'domain' => domain,
- 'path' => path)
- end
- end
-
-
- instance_methods.each do |method|
- private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
- end if ENV['TEST']
-
-end
diff --git a/test/cgi/test_cgi_core.rb b/test/cgi/test_cgi_core.rb
deleted file mode 100644
index f7adb7e99f..0000000000
--- a/test/cgi/test_cgi_core.rb
+++ /dev/null
@@ -1,307 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require 'cgi'
-require 'stringio'
-require_relative 'update_env'
-
-
-class CGICoreTest < Test::Unit::TestCase
- include UpdateEnv
-
- def setup
- @environ = {}
- #@environ = {
- # 'SERVER_PROTOCOL' => 'HTTP/1.1',
- # 'REQUEST_METHOD' => 'GET',
- # 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- #}
- #ENV.update(@environ)
- end
-
- def teardown
- ENV.update(@environ)
- $stdout = STDOUT
- end
-
- def test_cgi_parse_illegal_query
- update_env(
- 'REQUEST_METHOD' => 'GET',
- 'QUERY_STRING' => 'a=111&&b=222&c&d=',
- 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- cgi = CGI.new
- assert_equal(["a","b","c","d"],cgi.keys.sort)
- assert_equal("",cgi["d"])
- end
-
- def test_cgi_core_params_GET
- update_env(
- 'REQUEST_METHOD' => 'GET',
- 'QUERY_STRING' => 'id=123&id=456&id=&id&str=%40h+%3D%7E+%2F%5E%24%2F',
- 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- cgi = CGI.new
- ## cgi[]
- assert_equal('123', cgi['id'])
- assert_equal('@h =~ /^$/', cgi['str'])
- ## cgi.params
- assert_equal(['123', '456', ''], cgi.params['id'])
- assert_equal(['@h =~ /^$/'], cgi.params['str'])
- ## cgi.keys
- assert_equal(['id', 'str'], cgi.keys.sort)
- ## cgi.key?, cgi.has_key?, cgi.include?
- assert_equal(true, cgi.key?('id'))
- assert_equal(true, cgi.has_key?('id'))
- assert_equal(true, cgi.include?('id'))
- assert_equal(false, cgi.key?('foo'))
- assert_equal(false, cgi.has_key?('foo'))
- assert_equal(false, cgi.include?('foo'))
- ## invalid parameter name
- assert_equal('', cgi['*notfound*']) # [ruby-dev:30740]
- assert_equal([], cgi.params['*notfound*'])
- end
-
-
- def test_cgi_core_params_POST
- query_str = 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F'
- update_env(
- 'REQUEST_METHOD' => 'POST',
- 'CONTENT_LENGTH' => query_str.length.to_s,
- 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- $stdin = StringIO.new
- $stdin << query_str
- $stdin.rewind
- cgi = CGI.new
- ## cgi[]
- assert_equal('123', cgi['id'])
- assert_equal('@h =~ /^$/', cgi['str'])
- ## cgi.params
- assert_equal(['123', '456', ''], cgi.params['id'])
- assert_equal(['@h =~ /^$/'], cgi.params['str'])
- ## invalid parameter name
- assert_equal('', cgi['*notfound*'])
- assert_equal([], cgi.params['*notfound*'])
- ensure
- $stdin = STDIN
- end
-
- def test_cgi_core_params_encoding_check
- query_str = 'str=%BE%BE%B9%BE'
- update_env(
- 'REQUEST_METHOD' => 'POST',
- 'CONTENT_LENGTH' => query_str.length.to_s,
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- $stdin = StringIO.new
- $stdin << query_str
- $stdin.rewind
- if defined?(::Encoding)
- hash={}
- cgi = CGI.new(:accept_charset=>"UTF-8"){|key,val|hash[key]=val}
- ## cgi[]
- assert_equal("\xBE\xBE\xB9\xBE".dup.force_encoding("UTF-8"), cgi['str'])
- ## cgi.params
- assert_equal(["\xBE\xBE\xB9\xBE".dup.force_encoding("UTF-8")], cgi.params['str'])
- ## accept-charset error
- assert_equal({"str"=>"\xBE\xBE\xB9\xBE".dup.force_encoding("UTF-8")},hash)
-
- $stdin.rewind
- assert_raise(CGI::InvalidEncoding) do
- cgi = CGI.new(:accept_charset=>"UTF-8")
- end
-
- $stdin.rewind
- cgi = CGI.new(:accept_charset=>"EUC-JP")
- ## cgi[]
- assert_equal("\xBE\xBE\xB9\xBE".dup.force_encoding("EUC-JP"), cgi['str'])
- ## cgi.params
- assert_equal(["\xBE\xBE\xB9\xBE".dup.force_encoding("EUC-JP")], cgi.params['str'])
- else
- assert(true)
- end
- ensure
- $stdin = STDIN
- end
-
-
- def test_cgi_core_cookie
- update_env(
- 'REQUEST_METHOD' => 'GET',
- 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
- 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- cgi = CGI.new
- assert_not_equal(nil,cgi.cookies)
- [ ['_session_id', ['12345'], ],
- ['name1', ['val1', 'val2'], ],
- ].each do |key, expected|
- cookie = cgi.cookies[key]
- assert_kind_of(CGI::Cookie, cookie)
- assert_equal(expected, cookie.value)
- assert_equal(false, cookie.secure)
- assert_nil(cookie.expires)
- assert_nil(cookie.domain)
- assert_equal('', cookie.path)
- end
- end
-
-
- def test_cgi_core_maxcontentlength
- update_env(
- 'REQUEST_METHOD' => 'POST',
- 'CONTENT_LENGTH' => (64 * 1024 * 1024).to_s
- )
- ex = assert_raise(StandardError) do
- CGI.new
- end
- assert_equal("too large post data.", ex.message)
- end if CGI.const_defined?(:MAX_CONTENT_LENGTH)
-
-
- def test_cgi_core_out
- update_env(
- 'REQUEST_METHOD' => 'GET',
- 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
- 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- cgi = CGI.new
- ## euc string
- euc_str = "\270\253\244\355\241\242\277\315\244\254\245\264\245\337\244\316\244\350\244\246\244\300"
- ## utf8 (not converted)
- options = { 'charset'=>'utf8' }
- $stdout = StringIO.new
- cgi.out(options) { euc_str }
- assert_nil(options['language'])
- actual = $stdout.string
- expected = "Content-Type: text/html; charset=utf8\r\n" +
- "Content-Length: 22\r\n" +
- "\r\n" +
- euc_str
- if defined?(::Encoding)
- actual.force_encoding("ASCII-8BIT")
- expected.force_encoding("ASCII-8BIT")
- end
- assert_equal(expected, actual)
- ## language is keeped
- options = { 'charset'=>'Shift_JIS', 'language'=>'en' }
- $stdout = StringIO.new
- cgi.out(options) { euc_str }
- assert_equal('en', options['language'])
- ## HEAD method
- update_env('REQUEST_METHOD' => 'HEAD')
- options = { 'charset'=>'utf8' }
- $stdout = StringIO.new
- cgi.out(options) { euc_str }
- actual = $stdout.string
- expected = "Content-Type: text/html; charset=utf8\r\n" +
- "Content-Length: 22\r\n" +
- "\r\n"
- assert_equal(expected, actual)
- end
-
-
- def test_cgi_core_print
- update_env(
- 'REQUEST_METHOD' => 'GET',
- )
- cgi = CGI.new
- $stdout = StringIO.new
- str = "foobar"
- cgi.print(str)
- expected = str
- actual = $stdout.string
- assert_equal(expected, actual)
- end
-
-
- def test_cgi_core_environs
- update_env(
- 'REQUEST_METHOD' => 'GET',
- )
- cgi = CGI.new
- ##
- list1 = %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
- PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST
- REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME
- SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE
- HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
- HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
- HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT
- ]
- # list2 = %w[ CONTENT_LENGTH SERVER_PORT ]
- ## string expected
- list1.each do |name|
- update_env(name => "**#{name}**")
- end
- list1.each do |name|
- method = name.sub(/\AHTTP_/, '').downcase
- actual = cgi.__send__ method
- expected = "**#{name}**"
- assert_equal(expected, actual)
- end
- ## integer expected
- update_env('CONTENT_LENGTH' => '123')
- update_env('SERVER_PORT' => '8080')
- assert_equal(123, cgi.content_length)
- assert_equal(8080, cgi.server_port)
- ## raw cookie
- update_env('HTTP_COOKIE' => 'name1=val1')
- update_env('HTTP_COOKIE2' => 'name2=val2')
- assert_equal('name1=val1', cgi.raw_cookie)
- assert_equal('name2=val2', cgi.raw_cookie2)
- end
-
-
- def test_cgi_core_htmltype_header
- update_env(
- 'REQUEST_METHOD' => 'GET',
- )
- ## no htmltype
- cgi = CGI.new
- assert_raise(NoMethodError) do cgi.doctype end
- assert_equal("Content-Type: text/html\r\n\r\n",cgi.header)
- ## html3
- cgi = CGI.new('html3')
- expected = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'
- assert_equal(expected, cgi.doctype)
- assert_equal("Content-Type: text/html\r\n\r\n",cgi.header)
- ## html4
- cgi = CGI.new('html4')
- expected = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
- assert_equal(expected, cgi.doctype)
- assert_equal("Content-Type: text/html\r\n\r\n",cgi.header)
- ## html4 transitional
- cgi = CGI.new('html4Tr')
- expected = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
- assert_equal(expected, cgi.doctype)
- assert_equal("Content-Type: text/html\r\n\r\n",cgi.header)
- ## html4 frameset
- cgi = CGI.new('html4Fr')
- expected = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
- assert_equal(expected, cgi.doctype)
- assert_equal("Content-Type: text/html\r\n\r\n",cgi.header)
- ## html5
- cgi = CGI.new('html5')
- expected = '<!DOCTYPE HTML>'
- assert_equal(expected, cgi.doctype)
- assert_match(/^<HEADER><\/HEADER>$/i,cgi.header)
- end
-
-
- instance_methods.each do |method|
- private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
- end if ENV['TEST']
-
-end
diff --git a/test/cgi/test_cgi_util.rb b/test/cgi/test_cgi_escape.rb
index b0612fc87d..73d99e8aac 100644
--- a/test/cgi/test_cgi_util.rb
+++ b/test/cgi/test_cgi_escape.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
require 'test/unit'
-require 'cgi'
+require 'cgi/escape'
require 'stringio'
require_relative 'update_env'
-class CGIUtilTest < Test::Unit::TestCase
- include CGI::Util
+class CGIEscapeTest < Test::Unit::TestCase
+ include CGI::Escape
include UpdateEnv
def setup
@@ -63,7 +63,7 @@ class CGIUtilTest < Test::Unit::TestCase
return unless defined?(::Encoding)
assert_raise(TypeError) {CGI.unescape('', nil)}
- assert_separately(%w[-rcgi/util], "#{<<-"begin;"}\n#{<<-"end;"}")
+ assert_separately(%w[-rcgi/escape], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
assert_equal("", CGI.unescape(''))
end;
@@ -120,17 +120,12 @@ class CGIUtilTest < Test::Unit::TestCase
return unless defined?(::Encoding)
assert_raise(TypeError) {CGI.unescapeURIComponent('', nil)}
- assert_separately(%w[-rcgi/util], "#{<<-"begin;"}\n#{<<-"end;"}")
+ assert_separately(%w[-rcgi/escape], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
assert_equal("", CGI.unescapeURIComponent(''))
end;
end
- def test_cgi_pretty
- assert_equal("<HTML>\n <BODY>\n </BODY>\n</HTML>\n",CGI.pretty("<HTML><BODY></BODY></HTML>"))
- assert_equal("<HTML>\n\t<BODY>\n\t</BODY>\n</HTML>\n",CGI.pretty("<HTML><BODY></BODY></HTML>","\t"))
- end
-
def test_cgi_escapeHTML
assert_equal("&#39;&amp;&quot;&gt;&lt;", CGI.escapeHTML("'&\"><"))
end
@@ -269,6 +264,14 @@ class CGIUtilTest < Test::Unit::TestCase
assert_equal("<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]))
assert_equal("<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escape_element('<BR><A HREF="url"></A>', "A", "IMG"))
assert_equal("<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escape_element('<BR><A HREF="url"></A>', ["A", "IMG"]))
+
+ assert_equal("&lt;A &lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escapeElement('<A <A HREF="url"></A>', "A", "IMG"))
+ assert_equal("&lt;A &lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escapeElement('<A <A HREF="url"></A>', ["A", "IMG"]))
+ assert_equal("&lt;A &lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escape_element('<A <A HREF="url"></A>', "A", "IMG"))
+ assert_equal("&lt;A &lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;", escape_element('<A <A HREF="url"></A>', ["A", "IMG"]))
+
+ assert_equal("&lt;A &lt;A ", escapeElement('<A <A ', "A", "IMG"))
+ assert_equal("&lt;A &lt;A ", escapeElement('<A <A ', ["A", "IMG"]))
end
@@ -277,29 +280,39 @@ class CGIUtilTest < Test::Unit::TestCase
assert_equal('&lt;BR&gt;<A HREF="url"></A>', unescapeElement(escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]))
assert_equal('&lt;BR&gt;<A HREF="url"></A>', unescape_element(escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG"))
assert_equal('&lt;BR&gt;<A HREF="url"></A>', unescape_element(escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]))
+
+ assert_equal('<A <A HREF="url"></A>', unescapeElement(escapeHTML('<A <A HREF="url"></A>'), "A", "IMG"))
+ assert_equal('<A <A HREF="url"></A>', unescapeElement(escapeHTML('<A <A HREF="url"></A>'), ["A", "IMG"]))
+ assert_equal('<A <A HREF="url"></A>', unescape_element(escapeHTML('<A <A HREF="url"></A>'), "A", "IMG"))
+ assert_equal('<A <A HREF="url"></A>', unescape_element(escapeHTML('<A <A HREF="url"></A>'), ["A", "IMG"]))
+
+ assert_equal('<A <A ', unescapeElement(escapeHTML('<A <A '), "A", "IMG"))
+ assert_equal('<A <A ', unescapeElement(escapeHTML('<A <A '), ["A", "IMG"]))
+ assert_equal('<A <A ', unescape_element(escapeHTML('<A <A '), "A", "IMG"))
+ assert_equal('<A <A ', unescape_element(escapeHTML('<A <A '), ["A", "IMG"]))
end
end
-class CGIUtilPureRubyTest < Test::Unit::TestCase
+class CGIEscapePureRubyTest < Test::Unit::TestCase
def setup
- CGI::Escape.module_eval do
+ CGI::EscapeExt.module_eval do
alias _escapeHTML escapeHTML
remove_method :escapeHTML
alias _unescapeHTML unescapeHTML
remove_method :unescapeHTML
- end if defined?(CGI::Escape)
+ end if defined?(CGI::EscapeExt) and CGI::EscapeExt.method_defined?(:escapeHTML)
end
def teardown
- CGI::Escape.module_eval do
+ CGI::EscapeExt.module_eval do
alias escapeHTML _escapeHTML
remove_method :_escapeHTML
alias unescapeHTML _unescapeHTML
remove_method :_unescapeHTML
- end if defined?(CGI::Escape)
+ end if defined?(CGI::EscapeExt) and CGI::EscapeExt.method_defined?(:_escapeHTML)
end
- include CGIUtilTest::UnescapeHTMLTests
+ include CGIEscapeTest::UnescapeHTMLTests
def test_cgi_escapeHTML_with_invalid_byte_sequence
assert_equal("&lt;\xA4??&gt;", CGI.escapeHTML(%[<\xA4??>]))
diff --git a/test/cgi/test_cgi_header.rb b/test/cgi/test_cgi_header.rb
deleted file mode 100644
index ec2f4deb72..0000000000
--- a/test/cgi/test_cgi_header.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require 'cgi'
-require 'time'
-require_relative 'update_env'
-
-
-class CGIHeaderTest < Test::Unit::TestCase
- include UpdateEnv
-
-
- def setup
- @environ = {}
- update_env(
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- 'REQUEST_METHOD' => 'GET',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- )
- end
-
-
- def teardown
- ENV.update(@environ)
- end
-
-
- def test_cgi_http_header_simple
- cgi = CGI.new
- ## default content type
- expected = "Content-Type: text/html\r\n\r\n"
- actual = cgi.http_header
- assert_equal(expected, actual)
- ## content type specified as string
- expected = "Content-Type: text/xhtml; charset=utf8\r\n\r\n"
- actual = cgi.http_header('text/xhtml; charset=utf8')
- assert_equal(expected, actual)
- ## content type specified as hash
- expected = "Content-Type: image/png\r\n\r\n"
- actual = cgi.http_header('type'=>'image/png')
- assert_equal(expected, actual)
- ## charset specified
- expected = "Content-Type: text/html; charset=utf8\r\n\r\n"
- actual = cgi.http_header('charset'=>'utf8')
- assert_equal(expected, actual)
- end
-
-
- def test_cgi_http_header_complex
- cgi = CGI.new
- options = {
- 'type' => 'text/xhtml',
- 'charset' => 'utf8',
- 'status' => 'REDIRECT',
- 'server' => 'webrick',
- 'connection' => 'close',
- 'length' => 123,
- 'language' => 'ja',
- 'expires' => Time.gm(2000, 1, 23, 12, 34, 56),
- 'location' => 'http://www.ruby-lang.org/',
- }
- expected = "Status: 302 Found\r\n".dup
- expected << "Server: webrick\r\n"
- expected << "Connection: close\r\n"
- expected << "Content-Type: text/xhtml; charset=utf8\r\n"
- expected << "Content-Length: 123\r\n"
- expected << "Content-Language: ja\r\n"
- expected << "Expires: Sun, 23 Jan 2000 12:34:56 GMT\r\n"
- expected << "location: http://www.ruby-lang.org/\r\n"
- expected << "\r\n"
- actual = cgi.http_header(options)
- assert_equal(expected, actual)
- end
-
-
- def test_cgi_http_header_argerr
- cgi = CGI.new
- expected = ArgumentError
-
- assert_raise(expected) do
- cgi.http_header(nil)
- end
- end
-
-
- def test_cgi_http_header_cookie
- cgi = CGI.new
- cookie1 = CGI::Cookie.new('name1', 'abc', '123')
- cookie2 = CGI::Cookie.new('name'=>'name2', 'value'=>'value2', 'secure'=>true)
- ctype = "Content-Type: text/html\r\n"
- sep = "\r\n"
- c1 = "Set-Cookie: name1=abc&123; path=\r\n"
- c2 = "Set-Cookie: name2=value2; path=; secure\r\n"
- ## CGI::Cookie object
- actual = cgi.http_header('cookie'=>cookie1)
- expected = ctype + c1 + sep
- assert_equal(expected, actual)
- ## String
- actual = cgi.http_header('cookie'=>cookie2.to_s)
- expected = ctype + c2 + sep
- assert_equal(expected, actual)
- ## Array
- actual = cgi.http_header('cookie'=>[cookie1, cookie2])
- expected = ctype + c1 + c2 + sep
- assert_equal(expected, actual)
- ## Hash
- actual = cgi.http_header('cookie'=>{'name1'=>cookie1, 'name2'=>cookie2})
- expected = ctype + c1 + c2 + sep
- assert_equal(expected, actual)
- end
-
-
- def test_cgi_http_header_output_cookies
- cgi = CGI.new
- ## output cookies
- cookies = [ CGI::Cookie.new('name1', 'abc', '123'),
- CGI::Cookie.new('name'=>'name2', 'value'=>'value2', 'secure'=>true),
- ]
- cgi.instance_variable_set('@output_cookies', cookies)
- expected = "Content-Type: text/html; charset=utf8\r\n".dup
- expected << "Set-Cookie: name1=abc&123; path=\r\n"
- expected << "Set-Cookie: name2=value2; path=; secure\r\n"
- expected << "\r\n"
- ## header when string
- actual = cgi.http_header('text/html; charset=utf8')
- assert_equal(expected, actual)
- ## _header_for_string
- actual = cgi.http_header('type'=>'text/html', 'charset'=>'utf8')
- assert_equal(expected, actual)
- end
-
-
- def test_cgi_http_header_nph
- time_start = Time.now.to_i
- cgi = CGI.new
- ## 'nph' is true
- ENV['SERVER_SOFTWARE'] = 'Apache 2.2.0'
- actual1 = cgi.http_header('nph'=>true)
- ## when old IIS, NPH-mode is forced
- ENV['SERVER_SOFTWARE'] = 'IIS/4.0'
- actual2 = cgi.http_header
- actual3 = cgi.http_header('status'=>'REDIRECT', 'location'=>'http://www.example.com/')
- ## newer IIS doesn't require NPH-mode ## [ruby-dev:30537]
- ENV['SERVER_SOFTWARE'] = 'IIS/5.0'
- actual4 = cgi.http_header
- actual5 = cgi.http_header('status'=>'REDIRECT', 'location'=>'http://www.example.com/')
- time_end = Time.now.to_i
- date = /^Date: ([A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d\d:\d\d:\d\d GMT)\r\n/
- [actual1, actual2, actual3].each do |actual|
- assert_match(date, actual)
- assert_include(time_start..time_end, date =~ actual && Time.parse($1).to_i)
- actual.sub!(date, "Date: DATE_IS_REMOVED\r\n")
- end
- ## assertion
- expected = "HTTP/1.1 200 OK\r\n".dup
- expected << "Date: DATE_IS_REMOVED\r\n"
- expected << "Server: Apache 2.2.0\r\n"
- expected << "Connection: close\r\n"
- expected << "Content-Type: text/html\r\n"
- expected << "\r\n"
- assert_equal(expected, actual1)
- expected.sub!(/^Server: .*?\r\n/, "Server: IIS/4.0\r\n")
- assert_equal(expected, actual2)
- expected.sub!(/^HTTP\/1.1 200 OK\r\n/, "HTTP/1.1 302 Found\r\n")
- expected.sub!(/\r\n\r\n/, "\r\nlocation: http://www.example.com/\r\n\r\n")
- assert_equal(expected, actual3)
- expected = "Content-Type: text/html\r\n".dup
- expected << "\r\n"
- assert_equal(expected, actual4)
- expected = "Status: 302 Found\r\n".dup
- expected << "Content-Type: text/html\r\n"
- expected << "location: http://www.example.com/\r\n"
- expected << "\r\n"
- assert_equal(expected, actual5)
- ensure
- ENV.delete('SERVER_SOFTWARE')
- end
-
-
- def test_cgi_http_header_crlf_injection
- cgi = CGI.new
- assert_raise(RuntimeError) { cgi.http_header("text/xhtml\r\nBOO") }
- assert_raise(RuntimeError) { cgi.http_header("type" => "text/xhtml\r\nBOO") }
- assert_raise(RuntimeError) { cgi.http_header("status" => "200 OK\r\nBOO") }
- assert_raise(RuntimeError) { cgi.http_header("location" => "text/xhtml\r\nBOO") }
- end
-
-
- instance_methods.each do |method|
- private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
- end if ENV['TEST']
-
-end
diff --git a/test/cgi/test_cgi_modruby.rb b/test/cgi/test_cgi_modruby.rb
deleted file mode 100644
index 90132962b5..0000000000
--- a/test/cgi/test_cgi_modruby.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require 'cgi'
-require_relative 'update_env'
-
-
-class CGIModrubyTest < Test::Unit::TestCase
- include UpdateEnv
-
-
- def setup
- @environ = {}
- update_env(
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- 'REQUEST_METHOD' => 'GET',
- #'QUERY_STRING' => 'a=foo&b=bar',
- )
- CGI.class_eval { const_set(:MOD_RUBY, true) }
- Apache._reset()
- #@cgi = CGI.new
- #@req = Apache.request
- end
-
-
- def teardown
- ENV.update(@environ)
- CGI.class_eval { remove_const(:MOD_RUBY) }
- end
-
-
- def test_cgi_modruby_simple
- req = Apache.request
- cgi = CGI.new
- assert(req._setup_cgi_env_invoked?)
- assert(! req._send_http_header_invoked?)
- actual = cgi.http_header
- assert_equal('', actual)
- assert_equal('text/html', req.content_type)
- assert(req._send_http_header_invoked?)
- end
-
-
- def test_cgi_modruby_complex
- req = Apache.request
- cgi = CGI.new
- options = {
- 'status' => 'FORBIDDEN',
- 'location' => 'http://www.example.com/',
- 'type' => 'image/gif',
- 'content-encoding' => 'deflate',
- 'cookie' => [ CGI::Cookie.new('name1', 'abc', '123'),
- CGI::Cookie.new('name'=>'name2', 'value'=>'value2', 'secure'=>true),
- ],
- }
- assert(req._setup_cgi_env_invoked?)
- assert(! req._send_http_header_invoked?)
- actual = cgi.http_header(options)
- assert_equal('', actual)
- assert_equal('image/gif', req.content_type)
- assert_equal('403 Forbidden', req.status_line)
- assert_equal(403, req.status)
- assert_equal('deflate', req.content_encoding)
- assert_equal('http://www.example.com/', req.headers_out['location'])
- assert_equal(["name1=abc&123; path=", "name2=value2; path=; secure"],
- req.headers_out['Set-Cookie'])
- assert(req._send_http_header_invoked?)
- end
-
-
- def test_cgi_modruby_location
- req = Apache.request
- cgi = CGI.new
- options = {
- 'status' => '200 OK',
- 'location' => 'http://www.example.com/',
- }
- cgi.http_header(options)
- assert_equal('200 OK', req.status_line) # should be '302 Found' ?
- assert_equal(302, req.status)
- assert_equal('http://www.example.com/', req.headers_out['location'])
- end
-
-
- def test_cgi_modruby_requestparams
- req = Apache.request
- req.args = 'a=foo&b=bar'
- cgi = CGI.new
- assert_equal('foo', cgi['a'])
- assert_equal('bar', cgi['b'])
- end
-
-
- instance_methods.each do |method|
- private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
- end if ENV['TEST']
-
-end
-
-
-
-## dummy class for mod_ruby
-class Apache #:nodoc:
-
- def self._reset
- @request = Request.new
- end
-
- def self.request
- return @request
- end
-
- class Request
-
- def initialize
- hash = {}
- def hash.add(name, value)
- (self[name] ||= []) << value
- end
- @http_header = nil
- @headers_out = hash
- @status_line = nil
- @status = nil
- @content_type = nil
- @content_encoding = nil
- end
- attr_accessor :headers_out, :status_line, :status, :content_type, :content_encoding
-
- attr_accessor :args
- #def args
- # return ENV['QUERY_STRING']
- #end
-
- def send_http_header
- @http_header = '*invoked*'
- end
- def _send_http_header_invoked?
- @http_header ? true : false
- end
-
- def setup_cgi_env
- @cgi_env = '*invoked*'
- end
- def _setup_cgi_env_invoked?
- @cgi_env ? true : false
- end
-
- end
-
-end
diff --git a/test/cgi/test_cgi_multipart.rb b/test/cgi/test_cgi_multipart.rb
deleted file mode 100644
index 5e8ec25390..0000000000
--- a/test/cgi/test_cgi_multipart.rb
+++ /dev/null
@@ -1,385 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require 'cgi'
-require 'tempfile'
-require 'stringio'
-require_relative 'update_env'
-
-
-##
-## usage:
-## boundary = 'foobar1234' # or nil
-## multipart = MultiPart.new(boundary)
-## multipart.append('name1', 'value1')
-## multipart.append('file1', File.read('file1.html'), 'file1.html')
-## str = multipart.close()
-## str.each_line {|line| p line }
-## ## output:
-## # "--foobar1234\r\n"
-## # "Content-Disposition: form-data: name=\"name1\"\r\n"
-## # "\r\n"
-## # "value1\r\n"
-## # "--foobar1234\r\n"
-## # "Content-Disposition: form-data: name=\"file1\"; filename=\"file1.html\"\r\n"
-## # "Content-Type: text/html\r\n"
-## # "\r\n"
-## # "<html>\n"
-## # "<body><p>Hello</p></body>\n"
-## # "</html>\n"
-## # "\r\n"
-## # "--foobar1234--\r\n"
-##
-class MultiPart
-
- def initialize(boundary=nil)
- @boundary = boundary || create_boundary()
- @buf = ''.dup
- @buf.force_encoding(::Encoding::ASCII_8BIT) if defined?(::Encoding)
- end
- attr_reader :boundary
-
- def append(name, value, filename=nil, content_type=nil)
- content_type = detect_content_type(filename) if filename && content_type.nil?
- s = filename ? "; filename=\"#{filename}\"" : ''
- buf = @buf
- buf << "--#{boundary}\r\n"
- buf << "Content-Disposition: form-data: name=\"#{name}\"#{s}\r\n"
- buf << "Content-Type: #{content_type}\r\n" if content_type
- buf << "\r\n"
- buf << value.b
- buf << "\r\n"
- return self
- end
-
- def close
- buf = @buf
- @buf = ''.dup
- return buf << "--#{boundary}--\r\n"
- end
-
- def create_boundary() #:nodoc:
- return "--boundary#{rand().to_s[2..-1]}"
- end
-
- def detect_content_type(filename) #:nodoc:
- filename =~ /\.(\w+)\z/
- return MIME_TYPES[$1] || 'application/octet-stream'
- end
-
- MIME_TYPES = {
- 'gif' => 'image/gif',
- 'jpg' => 'image/jpeg',
- 'jpeg' => 'image/jpeg',
- 'png' => 'image/png',
- 'bmp' => 'image/bmp',
- 'tif' => 'image/tiff',
- 'tiff' => 'image/tiff',
- 'htm' => 'text/html',
- 'html' => 'text/html',
- 'xml' => 'text/xml',
- 'txt' => 'text/plain',
- 'text' => 'text/plain',
- 'css' => 'text/css',
- 'mpg' => 'video/mpeg',
- 'mpeg' => 'video/mpeg',
- 'mov' => 'video/quicktime',
- 'avi' => 'video/x-msvideo',
- 'mp3' => 'audio/mpeg',
- 'mid' => 'audio/midi',
- 'wav' => 'audio/x-wav',
- 'zip' => 'application/zip',
- #'tar.gz' => 'application/gtar',
- 'gz' => 'application/gzip',
- 'bz2' => 'application/bzip2',
- 'rtf' => 'application/rtf',
- 'pdf' => 'application/pdf',
- 'ps' => 'application/postscript',
- 'js' => 'application/x-javascript',
- 'xls' => 'application/vnd.ms-excel',
- 'doc' => 'application/msword',
- 'ppt' => 'application/vnd.ms-powerpoint',
- }
-
-end
-
-
-
-class CGIMultipartTest < Test::Unit::TestCase
- include UpdateEnv
-
-
- def setup
- @environ = {}
- update_env(
- 'REQUEST_METHOD' => 'POST',
- 'CONTENT_TYPE' => nil,
- 'CONTENT_LENGTH' => nil,
- )
- @tempfiles = []
- end
-
- def teardown
- ENV.update(@environ)
- $stdin.close() if $stdin.is_a?(Tempfile)
- $stdin = STDIN
- @tempfiles.each {|t|
- t.close!
- }
- end
-
- def _prepare(data)
- ## create multipart input
- multipart = MultiPart.new(defined?(@boundary) ? @boundary : nil)
- data.each do |hash|
- multipart.append(hash[:name], hash[:value], hash[:filename])
- end
- input = multipart.close()
- input = yield(input) if block_given?
- #$stderr.puts "*** debug: input=\n#{input.collect{|line| line.inspect}.join("\n")}"
- @boundary ||= multipart.boundary
- ## set environment
- ENV['CONTENT_TYPE'] = "multipart/form-data; boundary=#{@boundary}"
- ENV['CONTENT_LENGTH'] = input.length.to_s
- ENV['REQUEST_METHOD'] = 'POST'
- ## set $stdin
- tmpfile = Tempfile.new('test_cgi_multipart')
- @tempfiles << tmpfile
- tmpfile.binmode
- tmpfile << input
- tmpfile.rewind()
- $stdin = tmpfile
- end
-
- def _test_multipart(cgi_options={})
- caller(0).find {|s| s =~ /in `test_(.*?)'/ }
- #testname = $1
- #$stderr.puts "*** debug: testname=#{testname.inspect}"
- _prepare(@data)
- options = {:accept_charset=>"UTF-8"}
- options.merge! cgi_options
- cgi = CGI.new(options)
- expected_names = @data.collect{|hash| hash[:name] }.sort
- assert_equal(expected_names, cgi.params.keys.sort)
- threshold = 1024*10
- @data.each do |hash|
- name = hash[:name]
- expected = hash[:value]
- if hash[:filename] #if file
- expected_class = @expected_class || (hash[:value].length < threshold ? StringIO : Tempfile)
- assert(cgi.files.keys.member?(hash[:name]))
- else
- expected_class = String
- assert_equal(expected, cgi[name])
- assert_equal(false,cgi.files.keys.member?(hash[:name]))
- end
- assert_kind_of(expected_class, cgi[name])
- assert_equal(expected, cgi[name].read())
- assert_equal(hash[:filename] || '', cgi[name].original_filename) #if hash[:filename]
- assert_equal(hash[:content_type] || '', cgi[name].content_type) #if hash[:content_type]
- end
- ensure
- if cgi
- cgi.params.each {|name, vals|
- vals.each {|val|
- if val.kind_of?(Tempfile) && val.path
- val.close!
- end
- }
- }
- end
- end
-
-
- def _read(basename)
- filename = File.join(File.dirname(__FILE__), 'testdata', basename)
- s = File.open(filename, 'rb') {|f| f.read() }
-
- return s
- end
-
-
- def test_cgi_multipart_stringio
- @boundary = '----WebKitFormBoundaryAAfvAII+YL9102cX'
- @data = [
- {:name=>'hidden1', :value=>'foobar'},
- {:name=>'text1', :value=>"\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A".dup},
- {:name=>'file1', :value=>_read('file1.html'),
- :filename=>'file1.html', :content_type=>'text/html'},
- {:name=>'image1', :value=>_read('small.png'),
- :filename=>'small.png', :content_type=>'image/png'}, # small image
- ]
- @data[1][:value].force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
- @expected_class = StringIO
- _test_multipart()
- end
-
-
- def test_cgi_multipart_tempfile
- @boundary = '----WebKitFormBoundaryAAfvAII+YL9102cX'
- @data = [
- {:name=>'hidden1', :value=>'foobar'},
- {:name=>'text1', :value=>"\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A".dup},
- {:name=>'file1', :value=>_read('file1.html'),
- :filename=>'file1.html', :content_type=>'text/html'},
- {:name=>'image1', :value=>_read('large.png'),
- :filename=>'large.png', :content_type=>'image/png'}, # large image
- ]
- @data[1][:value].force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
- @expected_class = Tempfile
- _test_multipart()
- end
-
-
- def _set_const(klass, name, value)
- old = nil
- klass.class_eval do
- old = const_get(name)
- remove_const(name)
- const_set(name, value)
- end
- return old
- end
-
-
- def test_cgi_multipart_maxmultipartlength
- @data = [
- {:name=>'image1', :value=>_read('large.png'),
- :filename=>'large.png', :content_type=>'image/png'}, # large image
- ]
- begin
- ex = assert_raise(StandardError) do
- _test_multipart(:max_multipart_length=>2 * 1024) # set via simple scalar
- end
- assert_equal("too large multipart data.", ex.message)
- ensure
- end
- end
-
-
- def test_cgi_multipart_maxmultipartlength_lambda
- @data = [
- {:name=>'image1', :value=>_read('large.png'),
- :filename=>'large.png', :content_type=>'image/png'}, # large image
- ]
- begin
- ex = assert_raise(StandardError) do
- _test_multipart(:max_multipart_length=>lambda{2*1024}) # set via lambda
- end
- assert_equal("too large multipart data.", ex.message)
- ensure
- end
- end
-
-
- def test_cgi_multipart_maxmultipartcount
- @data = [
- {:name=>'file1', :value=>_read('file1.html'),
- :filename=>'file1.html', :content_type=>'text/html'},
- ]
- item = @data.first
- 500.times { @data << item }
- #original = _set_const(CGI, :MAX_MULTIPART_COUNT, 128)
- begin
- ex = assert_raise(StandardError) do
- _test_multipart()
- end
- assert_equal("too many parameters.", ex.message)
- ensure
- #_set_const(CGI, :MAX_MULTIPART_COUNT, original)
- end
- end if CGI.const_defined?(:MAX_MULTIPART_COUNT)
-
-
- def test_cgi_multipart_badbody ## [ruby-dev:28470]
- @data = [
- {:name=>'file1', :value=>_read('file1.html'),
- :filename=>'file1.html', :content_type=>'text/html'},
- ]
- _prepare(@data) do |input|
- input2 = input.sub(/--(\r\n)?\z/, "\r\n")
- assert input2 != input
- #p input2
- input2
- end
- ex = assert_raise(EOFError) do
- CGI.new(:accept_charset=>"UTF-8")
- end
- assert_equal("bad content body", ex.message)
- #
- _prepare(@data) do |input|
- input2 = input.sub(/--(\r\n)?\z/, "")
- assert input2 != input
- #p input2
- input2
- end
- ex = assert_raise(EOFError) do
- CGI.new(:accept_charset=>"UTF-8")
- end
- assert_equal("bad content body", ex.message)
- end
-
-
- def test_cgi_multipart_quoteboundary ## [JVN#84798830]
- @boundary = '(.|\n)*'
- @data = [
- {:name=>'hidden1', :value=>'foobar'},
- {:name=>'text1', :value=>"\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A".dup},
- {:name=>'file1', :value=>_read('file1.html'),
- :filename=>'file1.html', :content_type=>'text/html'},
- {:name=>'image1', :value=>_read('small.png'),
- :filename=>'small.png', :content_type=>'image/png'}, # small image
- ]
- @data[1][:value].force_encoding("UTF-8")
- _prepare(@data)
- cgi = CGI.new(:accept_charset=>"UTF-8")
- assert_equal('file1.html', cgi['file1'].original_filename)
- end
-
- def test_cgi_multipart_boundary_10240 # [Bug #3866]
- @boundary = 'AaB03x'
- @data = [
- {:name=>'file', :value=>"b"*10134,
- :filename=>'file.txt', :content_type=>'text/plain'},
- {:name=>'foo', :value=>"bar"},
- ]
- _prepare(@data)
- cgi = CGI.new(:accept_charset=>"UTF-8")
- assert_equal(cgi['foo'], 'bar')
- assert_equal(cgi['file'].read, 'b'*10134)
- cgi['file'].close! if cgi['file'].kind_of? Tempfile
- end
-
- def test_cgi_multipart_without_tempfile
- assert_in_out_err([], <<-'EOM')
- require 'cgi'
- require 'stringio'
- ENV['REQUEST_METHOD'] = 'POST'
- ENV['CONTENT_TYPE'] = 'multipart/form-data; boundary=foobar1234'
- body = <<-BODY.gsub(/\n/, "\r\n")
---foobar1234
-Content-Disposition: form-data: name=\"name1\"
-
-value1
---foobar1234
-Content-Disposition: form-data: name=\"file1\"; filename=\"file1.html\"
-Content-Type: text/html
-
-<html>
-<body><p>Hello</p></body>
-</html>
-
---foobar1234--
-BODY
- ENV['CONTENT_LENGTH'] = body.size.to_s
- $stdin = StringIO.new(body)
- CGI.new
- EOM
- end
-
- ###
-
- self.instance_methods.each do |method|
- private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
- end if ENV['TEST']
-
-end
diff --git a/test/cgi/test_cgi_session.rb b/test/cgi/test_cgi_session.rb
deleted file mode 100644
index 32b907d741..0000000000
--- a/test/cgi/test_cgi_session.rb
+++ /dev/null
@@ -1,169 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require 'cgi'
-require 'cgi/session'
-require 'cgi/session/pstore'
-require 'stringio'
-require 'tmpdir'
-require_relative 'update_env'
-
-class CGISessionTest < Test::Unit::TestCase
- include UpdateEnv
-
- def setup
- @environ = {}
- @session_dir = Dir.mktmpdir(%w'session dir')
- end
-
- def teardown
- ENV.update(@environ)
- $stdout = STDOUT
- FileUtils.rm_rf(@session_dir)
- end
-
- def test_cgi_session_filestore
- update_env(
- 'REQUEST_METHOD' => 'GET',
- # 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
- # 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- value1="value1"
- value2="\x8F\xBC\x8D]".dup
- value2.force_encoding("SJIS") if defined?(::Encoding)
- cgi = CGI.new
- session = CGI::Session.new(cgi,"tmpdir"=>@session_dir)
- session["key1"]=value1
- session["key2"]=value2
- assert_equal(value1,session["key1"])
- assert_equal(value2,session["key2"])
- session.close
- $stdout = StringIO.new
- cgi.out{""}
-
- update_env(
- 'REQUEST_METHOD' => 'GET',
- # 'HTTP_COOKIE' => "_session_id=#{session_id}",
- 'QUERY_STRING' => "_session_id=#{session.session_id}",
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- cgi = CGI.new
- session = CGI::Session.new(cgi,"tmpdir"=>@session_dir)
- $stdout = StringIO.new
- assert_equal(value1,session["key1"])
- assert_equal(value2,session["key2"])
- session.close
-
- end
- def test_cgi_session_pstore
- update_env(
- 'REQUEST_METHOD' => 'GET',
- # 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
- # 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- value1="value1"
- value2="\x8F\xBC\x8D]".dup
- value2.force_encoding("SJIS") if defined?(::Encoding)
- cgi = CGI.new
- session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"database_manager"=>CGI::Session::PStore)
- session["key1"]=value1
- session["key2"]=value2
- assert_equal(value1,session["key1"])
- assert_equal(value2,session["key2"])
- session.close
- $stdout = StringIO.new
- cgi.out{""}
-
- update_env(
- 'REQUEST_METHOD' => 'GET',
- # 'HTTP_COOKIE' => "_session_id=#{session_id}",
- 'QUERY_STRING' => "_session_id=#{session.session_id}",
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- cgi = CGI.new
- session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"database_manager"=>CGI::Session::PStore)
- $stdout = StringIO.new
- assert_equal(value1,session["key1"])
- assert_equal(value2,session["key2"])
- session.close
- end if defined?(::PStore)
- def test_cgi_session_specify_session_id
- update_env(
- 'REQUEST_METHOD' => 'GET',
- # 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
- # 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- value1="value1"
- value2="\x8F\xBC\x8D]".dup
- value2.force_encoding("SJIS") if defined?(::Encoding)
- cgi = CGI.new
- session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"session_id"=>"foo")
- session["key1"]=value1
- session["key2"]=value2
- assert_equal(value1,session["key1"])
- assert_equal(value2,session["key2"])
- assert_equal("foo",session.session_id)
- #session_id=session.session_id
- session.close
- $stdout = StringIO.new
- cgi.out{""}
-
- update_env(
- 'REQUEST_METHOD' => 'GET',
- # 'HTTP_COOKIE' => "_session_id=#{session_id}",
- 'QUERY_STRING' => "_session_id=#{session.session_id}",
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- cgi = CGI.new
- session = CGI::Session.new(cgi,"tmpdir"=>@session_dir)
- $stdout = StringIO.new
- assert_equal(value1,session["key1"])
- assert_equal(value2,session["key2"])
- assert_equal("foo",session.session_id)
- session.close
- end
- def test_cgi_session_specify_session_key
- update_env(
- 'REQUEST_METHOD' => 'GET',
- # 'QUERY_STRING' => 'id=123&id=456&id=&str=%40h+%3D%7E+%2F%5E%24%2F',
- # 'HTTP_COOKIE' => '_session_id=12345; name1=val1&val2;',
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- value1="value1"
- value2="\x8F\xBC\x8D]".dup
- value2.force_encoding("SJIS") if defined?(::Encoding)
- cgi = CGI.new
- session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"session_key"=>"bar")
- session["key1"]=value1
- session["key2"]=value2
- assert_equal(value1,session["key1"])
- assert_equal(value2,session["key2"])
- session_id=session.session_id
- session.close
- $stdout = StringIO.new
- cgi.out{""}
-
- update_env(
- 'REQUEST_METHOD' => 'GET',
- 'HTTP_COOKIE' => "bar=#{session_id}",
- # 'QUERY_STRING' => "bar=#{session.session_id}",
- 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- )
- cgi = CGI.new
- session = CGI::Session.new(cgi,"tmpdir"=>@session_dir,"session_key"=>"bar")
- $stdout = StringIO.new
- assert_equal(value1,session["key1"])
- assert_equal(value2,session["key2"])
- session.close
- end
-end
diff --git a/test/cgi/test_cgi_tag_helper.rb b/test/cgi/test_cgi_tag_helper.rb
deleted file mode 100644
index 0b99dfc1bc..0000000000
--- a/test/cgi/test_cgi_tag_helper.rb
+++ /dev/null
@@ -1,355 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require 'cgi'
-require 'stringio'
-require_relative 'update_env'
-
-
-class CGITagHelperTest < Test::Unit::TestCase
- include UpdateEnv
-
-
- def setup
- @environ = {}
- #@environ = {
- # 'SERVER_PROTOCOL' => 'HTTP/1.1',
- # 'REQUEST_METHOD' => 'GET',
- # 'SERVER_SOFTWARE' => 'Apache 2.2.0',
- #}
- #ENV.update(@environ)
- end
-
-
- def teardown
- ENV.update(@environ)
- $stdout = STDOUT
- end
-
-
- def test_cgi_tag_helper_html3
- update_env(
- 'REQUEST_METHOD' => 'GET',
- )
- ## html3
- cgi = CGI.new('html3')
- assert_equal('<A HREF=""></A>',cgi.a)
- assert_equal('<A HREF="bar"></A>',cgi.a('bar'))
- assert_equal('<A HREF="">foo</A>',cgi.a{'foo'})
- assert_equal('<A HREF="bar">foo</A>',cgi.a('bar'){'foo'})
- assert_equal('<TT></TT>',cgi.tt)
- assert_equal('<TT></TT>',cgi.tt('bar'))
- assert_equal('<TT>foo</TT>',cgi.tt{'foo'})
- assert_equal('<TT>foo</TT>',cgi.tt('bar'){'foo'})
- assert_equal('<I></I>',cgi.i)
- assert_equal('<I></I>',cgi.i('bar'))
- assert_equal('<I>foo</I>',cgi.i{'foo'})
- assert_equal('<I>foo</I>',cgi.i('bar'){'foo'})
- assert_equal('<B></B>',cgi.b)
- assert_equal('<B></B>',cgi.b('bar'))
- assert_equal('<B>foo</B>',cgi.b{'foo'})
- assert_equal('<B>foo</B>',cgi.b('bar'){'foo'})
- assert_equal('<U></U>',cgi.u)
- assert_equal('<U></U>',cgi.u('bar'))
- assert_equal('<U>foo</U>',cgi.u{'foo'})
- assert_equal('<U>foo</U>',cgi.u('bar'){'foo'})
- assert_equal('<STRIKE></STRIKE>',cgi.strike)
- assert_equal('<STRIKE></STRIKE>',cgi.strike('bar'))
- assert_equal('<STRIKE>foo</STRIKE>',cgi.strike{'foo'})
- assert_equal('<STRIKE>foo</STRIKE>',cgi.strike('bar'){'foo'})
- assert_equal('<BIG></BIG>',cgi.big)
- assert_equal('<BIG></BIG>',cgi.big('bar'))
- assert_equal('<BIG>foo</BIG>',cgi.big{'foo'})
- assert_equal('<BIG>foo</BIG>',cgi.big('bar'){'foo'})
- assert_equal('<SMALL></SMALL>',cgi.small)
- assert_equal('<SMALL></SMALL>',cgi.small('bar'))
- assert_equal('<SMALL>foo</SMALL>',cgi.small{'foo'})
- assert_equal('<SMALL>foo</SMALL>',cgi.small('bar'){'foo'})
- assert_equal('<SUB></SUB>',cgi.sub)
- assert_equal('<SUB></SUB>',cgi.sub('bar'))
- assert_equal('<SUB>foo</SUB>',cgi.sub{'foo'})
- assert_equal('<SUB>foo</SUB>',cgi.sub('bar'){'foo'})
- assert_equal('<SUP></SUP>',cgi.sup)
- assert_equal('<SUP></SUP>',cgi.sup('bar'))
- assert_equal('<SUP>foo</SUP>',cgi.sup{'foo'})
- assert_equal('<SUP>foo</SUP>',cgi.sup('bar'){'foo'})
- assert_equal('<EM></EM>',cgi.em)
- assert_equal('<EM></EM>',cgi.em('bar'))
- assert_equal('<EM>foo</EM>',cgi.em{'foo'})
- assert_equal('<EM>foo</EM>',cgi.em('bar'){'foo'})
- assert_equal('<STRONG></STRONG>',cgi.strong)
- assert_equal('<STRONG></STRONG>',cgi.strong('bar'))
- assert_equal('<STRONG>foo</STRONG>',cgi.strong{'foo'})
- assert_equal('<STRONG>foo</STRONG>',cgi.strong('bar'){'foo'})
- assert_equal('<DFN></DFN>',cgi.dfn)
- assert_equal('<DFN></DFN>',cgi.dfn('bar'))
- assert_equal('<DFN>foo</DFN>',cgi.dfn{'foo'})
- assert_equal('<DFN>foo</DFN>',cgi.dfn('bar'){'foo'})
- assert_equal('<CODE></CODE>',cgi.code)
- assert_equal('<CODE></CODE>',cgi.code('bar'))
- assert_equal('<CODE>foo</CODE>',cgi.code{'foo'})
- assert_equal('<CODE>foo</CODE>',cgi.code('bar'){'foo'})
- assert_equal('<SAMP></SAMP>',cgi.samp)
- assert_equal('<SAMP></SAMP>',cgi.samp('bar'))
- assert_equal('<SAMP>foo</SAMP>',cgi.samp{'foo'})
- assert_equal('<SAMP>foo</SAMP>',cgi.samp('bar'){'foo'})
- assert_equal('<KBD></KBD>',cgi.kbd)
- assert_equal('<KBD></KBD>',cgi.kbd('bar'))
- assert_equal('<KBD>foo</KBD>',cgi.kbd{'foo'})
- assert_equal('<KBD>foo</KBD>',cgi.kbd('bar'){'foo'})
- assert_equal('<VAR></VAR>',cgi.var)
- assert_equal('<VAR></VAR>',cgi.var('bar'))
- assert_equal('<VAR>foo</VAR>',cgi.var{'foo'})
- assert_equal('<VAR>foo</VAR>',cgi.var('bar'){'foo'})
- assert_equal('<CITE></CITE>',cgi.cite)
- assert_equal('<CITE></CITE>',cgi.cite('bar'))
- assert_equal('<CITE>foo</CITE>',cgi.cite{'foo'})
- assert_equal('<CITE>foo</CITE>',cgi.cite('bar'){'foo'})
- assert_equal('<FONT></FONT>',cgi.font)
- assert_equal('<FONT></FONT>',cgi.font('bar'))
- assert_equal('<FONT>foo</FONT>',cgi.font{'foo'})
- assert_equal('<FONT>foo</FONT>',cgi.font('bar'){'foo'})
- assert_equal('<ADDRESS></ADDRESS>',cgi.address)
- assert_equal('<ADDRESS></ADDRESS>',cgi.address('bar'))
- assert_equal('<ADDRESS>foo</ADDRESS>',cgi.address{'foo'})
- assert_equal('<ADDRESS>foo</ADDRESS>',cgi.address('bar'){'foo'})
- assert_equal('<DIV></DIV>',cgi.div)
- assert_equal('<DIV></DIV>',cgi.div('bar'))
- assert_equal('<DIV>foo</DIV>',cgi.div{'foo'})
- assert_equal('<DIV>foo</DIV>',cgi.div('bar'){'foo'})
- assert_equal('<CENTER></CENTER>',cgi.center)
- assert_equal('<CENTER></CENTER>',cgi.center('bar'))
- assert_equal('<CENTER>foo</CENTER>',cgi.center{'foo'})
- assert_equal('<CENTER>foo</CENTER>',cgi.center('bar'){'foo'})
- assert_equal('<MAP></MAP>',cgi.map)
- assert_equal('<MAP></MAP>',cgi.map('bar'))
- assert_equal('<MAP>foo</MAP>',cgi.map{'foo'})
- assert_equal('<MAP>foo</MAP>',cgi.map('bar'){'foo'})
- assert_equal('<APPLET></APPLET>',cgi.applet)
- assert_equal('<APPLET></APPLET>',cgi.applet('bar'))
- assert_equal('<APPLET>foo</APPLET>',cgi.applet{'foo'})
- assert_equal('<APPLET>foo</APPLET>',cgi.applet('bar'){'foo'})
- assert_equal('<PRE></PRE>',cgi.pre)
- assert_equal('<PRE></PRE>',cgi.pre('bar'))
- assert_equal('<PRE>foo</PRE>',cgi.pre{'foo'})
- assert_equal('<PRE>foo</PRE>',cgi.pre('bar'){'foo'})
- assert_equal('<XMP></XMP>',cgi.xmp)
- assert_equal('<XMP></XMP>',cgi.xmp('bar'))
- assert_equal('<XMP>foo</XMP>',cgi.xmp{'foo'})
- assert_equal('<XMP>foo</XMP>',cgi.xmp('bar'){'foo'})
- assert_equal('<LISTING></LISTING>',cgi.listing)
- assert_equal('<LISTING></LISTING>',cgi.listing('bar'))
- assert_equal('<LISTING>foo</LISTING>',cgi.listing{'foo'})
- assert_equal('<LISTING>foo</LISTING>',cgi.listing('bar'){'foo'})
- assert_equal('<DL></DL>',cgi.dl)
- assert_equal('<DL></DL>',cgi.dl('bar'))
- assert_equal('<DL>foo</DL>',cgi.dl{'foo'})
- assert_equal('<DL>foo</DL>',cgi.dl('bar'){'foo'})
- assert_equal('<OL></OL>',cgi.ol)
- assert_equal('<OL></OL>',cgi.ol('bar'))
- assert_equal('<OL>foo</OL>',cgi.ol{'foo'})
- assert_equal('<OL>foo</OL>',cgi.ol('bar'){'foo'})
- assert_equal('<UL></UL>',cgi.ul)
- assert_equal('<UL></UL>',cgi.ul('bar'))
- assert_equal('<UL>foo</UL>',cgi.ul{'foo'})
- assert_equal('<UL>foo</UL>',cgi.ul('bar'){'foo'})
- assert_equal('<DIR></DIR>',cgi.dir)
- assert_equal('<DIR></DIR>',cgi.dir('bar'))
- assert_equal('<DIR>foo</DIR>',cgi.dir{'foo'})
- assert_equal('<DIR>foo</DIR>',cgi.dir('bar'){'foo'})
- assert_equal('<MENU></MENU>',cgi.menu)
- assert_equal('<MENU></MENU>',cgi.menu('bar'))
- assert_equal('<MENU>foo</MENU>',cgi.menu{'foo'})
- assert_equal('<MENU>foo</MENU>',cgi.menu('bar'){'foo'})
- assert_equal('<SELECT></SELECT>',cgi.select)
- assert_equal('<SELECT></SELECT>',cgi.select('bar'))
- assert_equal('<SELECT>foo</SELECT>',cgi.select{'foo'})
- assert_equal('<SELECT>foo</SELECT>',cgi.select('bar'){'foo'})
- assert_equal('<TABLE></TABLE>',cgi.table)
- assert_equal('<TABLE></TABLE>',cgi.table('bar'))
- assert_equal('<TABLE>foo</TABLE>',cgi.table{'foo'})
- assert_equal('<TABLE>foo</TABLE>',cgi.table('bar'){'foo'})
- assert_equal('<TITLE></TITLE>',cgi.title)
- assert_equal('<TITLE></TITLE>',cgi.title('bar'))
- assert_equal('<TITLE>foo</TITLE>',cgi.title{'foo'})
- assert_equal('<TITLE>foo</TITLE>',cgi.title('bar'){'foo'})
- assert_equal('<STYLE></STYLE>',cgi.style)
- assert_equal('<STYLE></STYLE>',cgi.style('bar'))
- assert_equal('<STYLE>foo</STYLE>',cgi.style{'foo'})
- assert_equal('<STYLE>foo</STYLE>',cgi.style('bar'){'foo'})
- assert_equal('<SCRIPT></SCRIPT>',cgi.script)
- assert_equal('<SCRIPT></SCRIPT>',cgi.script('bar'))
- assert_equal('<SCRIPT>foo</SCRIPT>',cgi.script{'foo'})
- assert_equal('<SCRIPT>foo</SCRIPT>',cgi.script('bar'){'foo'})
- assert_equal('<H1></H1>',cgi.h1)
- assert_equal('<H1></H1>',cgi.h1('bar'))
- assert_equal('<H1>foo</H1>',cgi.h1{'foo'})
- assert_equal('<H1>foo</H1>',cgi.h1('bar'){'foo'})
- assert_equal('<H2></H2>',cgi.h2)
- assert_equal('<H2></H2>',cgi.h2('bar'))
- assert_equal('<H2>foo</H2>',cgi.h2{'foo'})
- assert_equal('<H2>foo</H2>',cgi.h2('bar'){'foo'})
- assert_equal('<H3></H3>',cgi.h3)
- assert_equal('<H3></H3>',cgi.h3('bar'))
- assert_equal('<H3>foo</H3>',cgi.h3{'foo'})
- assert_equal('<H3>foo</H3>',cgi.h3('bar'){'foo'})
- assert_equal('<H4></H4>',cgi.h4)
- assert_equal('<H4></H4>',cgi.h4('bar'))
- assert_equal('<H4>foo</H4>',cgi.h4{'foo'})
- assert_equal('<H4>foo</H4>',cgi.h4('bar'){'foo'})
- assert_equal('<H5></H5>',cgi.h5)
- assert_equal('<H5></H5>',cgi.h5('bar'))
- assert_equal('<H5>foo</H5>',cgi.h5{'foo'})
- assert_equal('<H5>foo</H5>',cgi.h5('bar'){'foo'})
- assert_equal('<H6></H6>',cgi.h6)
- assert_equal('<H6></H6>',cgi.h6('bar'))
- assert_equal('<H6>foo</H6>',cgi.h6{'foo'})
- assert_equal('<H6>foo</H6>',cgi.h6('bar'){'foo'})
- assert_match(/^<TEXTAREA .*><\/TEXTAREA>$/,cgi.textarea)
- assert_match(/COLS="70"/,cgi.textarea)
- assert_match(/ROWS="10"/,cgi.textarea)
- assert_match(/NAME=""/,cgi.textarea)
- assert_match(/^<TEXTAREA .*><\/TEXTAREA>$/,cgi.textarea("bar"))
- assert_match(/COLS="70"/,cgi.textarea("bar"))
- assert_match(/ROWS="10"/,cgi.textarea("bar"))
- assert_match(/NAME="bar"/,cgi.textarea("bar"))
- assert_match(/^<TEXTAREA .*>foo<\/TEXTAREA>$/,cgi.textarea{"foo"})
- assert_match(/COLS="70"/,cgi.textarea{"foo"})
- assert_match(/ROWS="10"/,cgi.textarea{"foo"})
- assert_match(/NAME=""/,cgi.textarea{"foo"})
- assert_match(/^<TEXTAREA .*>foo<\/TEXTAREA>$/,cgi.textarea("bar"){"foo"})
- assert_match(/COLS="70"/,cgi.textarea("bar"){"foo"})
- assert_match(/ROWS="10"/,cgi.textarea("bar"){"foo"})
- assert_match(/NAME="bar"/,cgi.textarea("bar"){"foo"})
- assert_match(/^<FORM .*><\/FORM>$/,cgi.form)
- assert_match(/METHOD="post"/,cgi.form)
- assert_match(/ENCTYPE="application\/x-www-form-urlencoded"/,cgi.form)
- assert_match(/^<FORM .*><\/FORM>$/,cgi.form("bar"))
- assert_match(/METHOD="bar"/,cgi.form("bar"))
- assert_match(/ENCTYPE="application\/x-www-form-urlencoded"/,cgi.form("bar"))
- assert_match(/^<FORM .*>foo<\/FORM>$/,cgi.form{"foo"})
- assert_match(/METHOD="post"/,cgi.form{"foo"})
- assert_match(/ENCTYPE="application\/x-www-form-urlencoded"/,cgi.form{"foo"})
- assert_match(/^<FORM .*>foo<\/FORM>$/,cgi.form("bar"){"foo"})
- assert_match(/METHOD="bar"/,cgi.form("bar"){"foo"})
- assert_match(/ENCTYPE="application\/x-www-form-urlencoded"/,cgi.form("bar"){"foo"})
- assert_equal('<BLOCKQUOTE></BLOCKQUOTE>',cgi.blockquote)
- assert_equal('<BLOCKQUOTE CITE="bar"></BLOCKQUOTE>',cgi.blockquote('bar'))
- assert_equal('<BLOCKQUOTE>foo</BLOCKQUOTE>',cgi.blockquote{'foo'})
- assert_equal('<BLOCKQUOTE CITE="bar">foo</BLOCKQUOTE>',cgi.blockquote('bar'){'foo'})
- assert_equal('<CAPTION></CAPTION>',cgi.caption)
- assert_equal('<CAPTION ALIGN="bar"></CAPTION>',cgi.caption('bar'))
- assert_equal('<CAPTION>foo</CAPTION>',cgi.caption{'foo'})
- assert_equal('<CAPTION ALIGN="bar">foo</CAPTION>',cgi.caption('bar'){'foo'})
- assert_equal('<IMG SRC="" ALT="">',cgi.img)
- assert_equal('<IMG SRC="bar" ALT="">',cgi.img('bar'))
- assert_equal('<IMG SRC="" ALT="">',cgi.img{'foo'})
- assert_equal('<IMG SRC="bar" ALT="">',cgi.img('bar'){'foo'})
- assert_equal('<BASE HREF="">',cgi.base)
- assert_equal('<BASE HREF="bar">',cgi.base('bar'))
- assert_equal('<BASE HREF="">',cgi.base{'foo'})
- assert_equal('<BASE HREF="bar">',cgi.base('bar'){'foo'})
- assert_equal('<BASEFONT>',cgi.basefont)
- assert_equal('<BASEFONT>',cgi.basefont('bar'))
- assert_equal('<BASEFONT>',cgi.basefont{'foo'})
- assert_equal('<BASEFONT>',cgi.basefont('bar'){'foo'})
- assert_equal('<BR>',cgi.br)
- assert_equal('<BR>',cgi.br('bar'))
- assert_equal('<BR>',cgi.br{'foo'})
- assert_equal('<BR>',cgi.br('bar'){'foo'})
- assert_equal('<AREA>',cgi.area)
- assert_equal('<AREA>',cgi.area('bar'))
- assert_equal('<AREA>',cgi.area{'foo'})
- assert_equal('<AREA>',cgi.area('bar'){'foo'})
- assert_equal('<LINK>',cgi.link)
- assert_equal('<LINK>',cgi.link('bar'))
- assert_equal('<LINK>',cgi.link{'foo'})
- assert_equal('<LINK>',cgi.link('bar'){'foo'})
- assert_equal('<PARAM>',cgi.param)
- assert_equal('<PARAM>',cgi.param('bar'))
- assert_equal('<PARAM>',cgi.param{'foo'})
- assert_equal('<PARAM>',cgi.param('bar'){'foo'})
- assert_equal('<HR>',cgi.hr)
- assert_equal('<HR>',cgi.hr('bar'))
- assert_equal('<HR>',cgi.hr{'foo'})
- assert_equal('<HR>',cgi.hr('bar'){'foo'})
- assert_equal('<INPUT>',cgi.input)
- assert_equal('<INPUT>',cgi.input('bar'))
- assert_equal('<INPUT>',cgi.input{'foo'})
- assert_equal('<INPUT>',cgi.input('bar'){'foo'})
- assert_equal('<ISINDEX>',cgi.isindex)
- assert_equal('<ISINDEX>',cgi.isindex('bar'))
- assert_equal('<ISINDEX>',cgi.isindex{'foo'})
- assert_equal('<ISINDEX>',cgi.isindex('bar'){'foo'})
- assert_equal('<META>',cgi.meta)
- assert_equal('<META>',cgi.meta('bar'))
- assert_equal('<META>',cgi.meta{'foo'})
- assert_equal('<META>',cgi.meta('bar'){'foo'})
- assert_equal('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>',cgi.html)
- assert_equal('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>foo</HTML>',cgi.html{'foo'})
- assert_equal('<HEAD>',cgi.head)
- assert_equal('<HEAD>foo</HEAD>',cgi.head{'foo'})
- assert_equal('<BODY>',cgi.body)
- assert_equal('<BODY>foo</BODY>',cgi.body{'foo'})
- assert_equal('<P>',cgi.p)
- assert_equal('<P>foo</P>',cgi.p{'foo'})
- assert_equal('<PLAINTEXT>',cgi.plaintext)
- assert_equal('<PLAINTEXT>foo</PLAINTEXT>',cgi.plaintext{'foo'})
- assert_equal('<DT>',cgi.dt)
- assert_equal('<DT>foo</DT>',cgi.dt{'foo'})
- assert_equal('<DD>',cgi.dd)
- assert_equal('<DD>foo</DD>',cgi.dd{'foo'})
- assert_equal('<LI>',cgi.li)
- assert_equal('<LI>foo</LI>',cgi.li{'foo'})
- assert_equal('<OPTION>',cgi.option)
- assert_equal('<OPTION>foo</OPTION>',cgi.option{'foo'})
- assert_equal('<TR>',cgi.tr)
- assert_equal('<TR>foo</TR>',cgi.tr{'foo'})
- assert_equal('<TH>',cgi.th)
- assert_equal('<TH>foo</TH>',cgi.th{'foo'})
- assert_equal('<TD>',cgi.td)
- assert_equal('<TD>foo</TD>',cgi.td{'foo'})
- str=cgi.checkbox_group("foo",["aa","bb"],["cc","dd"])
- assert_match(/^<INPUT .*VALUE="aa".*>bb<INPUT .*VALUE="cc".*>dd$/,str)
- assert_match(/^<INPUT .*TYPE="checkbox".*>bb<INPUT .*TYPE="checkbox".*>dd$/,str)
- assert_match(/^<INPUT .*NAME="foo".*>bb<INPUT .*NAME="foo".*>dd$/,str)
- str=cgi.radio_group("foo",["aa","bb"],["cc","dd"])
- assert_match(/^<INPUT .*VALUE="aa".*>bb<INPUT .*VALUE="cc".*>dd$/,str)
- assert_match(/^<INPUT .*TYPE="radio".*>bb<INPUT .*TYPE="radio".*>dd$/,str)
- assert_match(/^<INPUT .*NAME="foo".*>bb<INPUT .*NAME="foo".*>dd$/,str)
- str=cgi.checkbox_group("foo",["aa","bb"],["cc","dd",true])
- assert_match(/^<INPUT .*VALUE="aa".*>bb<INPUT .*VALUE="cc".*>dd$/,str)
- assert_match(/^<INPUT .*TYPE="checkbox".*>bb<INPUT .*TYPE="checkbox".*>dd$/,str)
- assert_match(/^<INPUT .*NAME="foo".*>bb<INPUT .*NAME="foo".*>dd$/,str)
- assert_match(/^<INPUT .*>bb<INPUT .*CHECKED.*>dd$/,str)
- assert_match(/<INPUT .*TYPE="text".*>/,cgi.text_field(:name=>"name",:value=>"value"))
- str=cgi.radio_group("foo",["aa","bb"],["cc","dd",false])
- assert_match(/^<INPUT .*VALUE="aa".*>bb<INPUT .*VALUE="cc".*>dd$/,str)
- assert_match(/^<INPUT .*TYPE="radio".*>bb<INPUT .*TYPE="radio".*>dd$/,str)
- assert_match(/^<INPUT .*NAME="foo".*>bb<INPUT .*NAME="foo".*>dd$/,str)
- end
-
-=begin
- def test_cgi_tag_helper_html4
- ## html4
- cgi = CGI.new('html4')
- ## html4 transitional
- cgi = CGI.new('html4Tr')
- ## html4 frameset
- cgi = CGI.new('html4Fr')
- end
-=end
-
- def test_cgi_tag_helper_html5
- update_env(
- 'REQUEST_METHOD' => 'GET',
- )
- ## html5
- cgi = CGI.new('html5')
- assert_equal('<HEADER></HEADER>',cgi.header)
- assert_equal('<FOOTER></FOOTER>',cgi.footer)
- assert_equal('<ARTICLE></ARTICLE>',cgi.article)
- assert_equal('<SECTION></SECTION>',cgi.section)
- assert_equal('<!DOCTYPE HTML><HTML BLA="TEST"></HTML>',cgi.html("BLA"=>"TEST"){})
- end
-
-end
diff --git a/test/cgi/testdata/file1.html b/test/cgi/testdata/file1.html
deleted file mode 100644
index 2ceaf6bc39..0000000000
--- a/test/cgi/testdata/file1.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-<html>
- <head>
- <title>ムスカ大佐のひとりごと</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF8">
- </head>
- <body>
- <p>バカどもにはちょうどいい目くらましだ。</p>
- </body>
-</html>
diff --git a/test/cgi/testdata/large.png b/test/cgi/testdata/large.png
deleted file mode 100644
index d716396fa3..0000000000
--- a/test/cgi/testdata/large.png
+++ /dev/null
Binary files differ
diff --git a/test/cgi/testdata/small.png b/test/cgi/testdata/small.png
deleted file mode 100644
index 753d58e3cb..0000000000
--- a/test/cgi/testdata/small.png
+++ /dev/null
Binary files differ
diff --git a/test/coverage/test_coverage.rb b/test/coverage/test_coverage.rb
index 9db1f8f253..adcd4a946c 100644
--- a/test/coverage/test_coverage.rb
+++ b/test/coverage/test_coverage.rb
@@ -82,6 +82,70 @@ class TestCoverage < Test::Unit::TestCase
}
end
+ def test_coverage_snapshot_iseq_compile
+ Dir.mktmpdir {|tmp|
+ Dir.chdir(tmp) {
+ File.open("test.rb", "w") do |f|
+ f.puts <<-EOS
+ def coverage_test_snapshot
+ :ok
+ end
+ EOS
+ end
+
+ assert_in_out_err(ARGV, <<-"end;", ["[1, 0, nil]", "[1, 1, nil]", "[1, 1, nil]"], [])
+ class RubyVM::InstructionSequence
+ def self.load_iseq(path)
+ compile(File.read(path), path, path)
+ end
+ end
+
+ Coverage.start
+ tmp = Dir.pwd
+ require tmp + "/test.rb"
+ cov = Coverage.peek_result[tmp + "/test.rb"]
+ coverage_test_snapshot
+ cov2 = Coverage.peek_result[tmp + "/test.rb"]
+ p cov
+ p cov2
+ p Coverage.result[tmp + "/test.rb"]
+ end;
+ }
+ }
+ end
+
+ def test_coverage_snapshot_iseq_compile_file
+ Dir.mktmpdir {|tmp|
+ Dir.chdir(tmp) {
+ File.open("test.rb", "w") do |f|
+ f.puts <<-EOS
+ def coverage_test_snapshot
+ :ok
+ end
+ EOS
+ end
+
+ assert_in_out_err(ARGV, <<-"end;", ["[1, 0, nil]", "[1, 1, nil]", "[1, 1, nil]"], [])
+ class RubyVM::InstructionSequence
+ def self.load_iseq(path)
+ compile_file(path)
+ end
+ end
+
+ Coverage.start
+ tmp = Dir.pwd
+ require tmp + "/test.rb"
+ cov = Coverage.peek_result[tmp + "/test.rb"]
+ coverage_test_snapshot
+ cov2 = Coverage.peek_result[tmp + "/test.rb"]
+ p cov
+ p cov2
+ p Coverage.result[tmp + "/test.rb"]
+ end;
+ }
+ }
+ end
+
def test_restarting_coverage
Dir.mktmpdir {|tmp|
Dir.chdir(tmp) {
@@ -192,6 +256,23 @@ class TestCoverage < Test::Unit::TestCase
end;
end
+ def test_eval_negative_lineno
+ assert_in_out_err(ARGV, <<-"end;", ["[1, 1, 1]"], [])
+ Coverage.start(eval: true, lines: true)
+
+ eval(<<-RUBY, TOPLEVEL_BINDING, "test.rb", -2)
+ p # -2 # Not subject to measurement
+ p # -1 # Not subject to measurement
+ p # 0 # Not subject to measurement
+ p # 1 # Subject to measurement
+ p # 2 # Subject to measurement
+ p # 3 # Subject to measurement
+ RUBY
+
+ p Coverage.result["test.rb"][:lines]
+ end;
+ end
+
def test_coverage_supported
assert Coverage.supported?(:lines)
assert Coverage.supported?(:oneshot_lines)
diff --git a/test/date/test_date.rb b/test/date/test_date.rb
index 3f9c893efa..7e37fc94d2 100644
--- a/test/date/test_date.rb
+++ b/test/date/test_date.rb
@@ -135,6 +135,10 @@ class TestDate < Test::Unit::TestCase
assert_equal(9, h[DateTime.new(1999,5,25)])
h = {}
+ h[Date.new(3171505571716611468830131104691,2,19)] = 0
+ assert_equal(true, h.key?(Date.new(3171505571716611468830131104691,2,19)))
+
+ h = {}
h[DateTime.new(1999,5,23)] = 0
h[DateTime.new(1999,5,24)] = 1
h[DateTime.new(1999,5,25)] = 2
diff --git a/test/date/test_date_conv.rb b/test/date/test_date_conv.rb
index 772fbee9a4..8d81084435 100644
--- a/test/date/test_date_conv.rb
+++ b/test/date/test_date_conv.rb
@@ -82,21 +82,17 @@ class TestDateConv < Test::Unit::TestCase
assert_equal([1582, 10, 13, 1, 2, 3, 456789],
[t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.usec])
- if Time.allocate.respond_to?(:nsec)
- d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123.to_r/86400000000000
- t = d.to_time.utc
- assert_equal([2004, 9, 19, 1, 2, 3, 456789123],
- [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.nsec])
- end
+ d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123.to_r/86400000000000
+ t = d.to_time.utc
+ assert_equal([2004, 9, 19, 1, 2, 3, 456789123],
+ [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.nsec])
# TruffleRuby does not support more than nanoseconds
unless RUBY_ENGINE == 'truffleruby'
- if Time.allocate.respond_to?(:subsec)
- d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123456789123.to_r/86400000000000000000000
- t = d.to_time.utc
- assert_equal([2004, 9, 19, 1, 2, 3, Rational(456789123456789123,1000000000000000000)],
- [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.subsec])
- end
+ d = DateTime.new(2004, 9, 19, 1, 2, 3, 0) + 456789123456789123.to_r/86400000000000000000000
+ t = d.to_time.utc
+ assert_equal([2004, 9, 19, 1, 2, 3, Rational(456789123456789123,1000000000000000000)],
+ [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.subsec])
end
end
diff --git a/test/date/test_date_parse.rb b/test/date/test_date_parse.rb
index cc29771cf8..8308f258d5 100644
--- a/test/date/test_date_parse.rb
+++ b/test/date/test_date_parse.rb
@@ -544,6 +544,8 @@ class TestDateParse < Test::Unit::TestCase
h = Date._parse('')
assert_equal({}, h)
+
+ assert_raise(TypeError) {Date._parse(nil)}
end
def test_parse
@@ -589,13 +591,7 @@ class TestDateParse < Test::Unit::TestCase
end
def test__parse_too_long_year
- # Math.log10 does not support so big numbers like 10^100_000 on TruffleRuby
- unless RUBY_ENGINE == 'truffleruby'
- str = "Jan 1" + "0" * 100_000
- h = EnvUtil.timeout(3) {Date._parse(str, limit: 100_010)}
- assert_equal(100_000, Math.log10(h[:year]))
- assert_equal(1, h[:mon])
- end
+ omit 'transient' if RUBY_ENGINE == 'truffleruby'
str = "Jan - 1" + "0" * 100_000
h = EnvUtil.timeout(3) {Date._parse(str, limit: 100_010)}
@@ -1304,4 +1300,20 @@ class TestDateParse < Test::Unit::TestCase
assert_raise(ArgumentError) { Date._parse("Jan " + "9" * 1000000) }
end
+
+ def test_string_argument
+ s = '2001-02-03T04:05:06Z'
+ obj = Class.new(Struct.new(:to_str, :count)) do
+ def to_str
+ self.count +=1
+ super
+ end
+ end.new(s, 0)
+
+ all_assertions_foreach(nil, :_parse, :_iso8601, :_rfc3339, :_xmlschema) do |m|
+ obj.count = 0
+ assert_not_equal({}, Date.__send__(m, obj))
+ assert_equal(1, obj.count)
+ end
+ end
end
diff --git a/test/date/test_date_ractor.rb b/test/date/test_date_ractor.rb
index 7ec953d87a..91ea38bb93 100644
--- a/test/date/test_date_ractor.rb
+++ b/test/date/test_date_ractor.rb
@@ -8,7 +8,7 @@ class TestDateParseRactor < Test::Unit::TestCase
share = #{share}
d = Date.parse('Aug 23:55')
Ractor.make_shareable(d) if share
- d2, d3 = Ractor.new(d) { |d| [d, Date.parse(d.to_s)] }.take
+ d2, d3 = Ractor.new(d) { |d| [d, Date.parse(d.to_s)] }.value
if share
assert_same d, d2
else
diff --git a/test/date/test_date_strptime.rb b/test/date/test_date_strptime.rb
index 4efe1a47d0..6aa7db292d 100644
--- a/test/date/test_date_strptime.rb
+++ b/test/date/test_date_strptime.rb
@@ -517,7 +517,20 @@ class TestDateStrptime < Test::Unit::TestCase
d = DateTime.strptime('9000 +0200', '%Q %z')
assert_equal([1970, 1, 1, 2, 0, 9], [d.year, d.mon, d.mday, d.hour, d.min, d.sec])
assert_equal(Rational(2, 24), d.offset)
-
end
+ def test_format_modified
+ str = " " * 100
+ fmt = Struct.new(:str) {
+ def to_str
+ str << "2026-06-01" << " "*100
+ " %F "
+ end
+ }.new(str)
+ d = Date._strptime(str, fmt)
+ assert_not_nil(d)
+ assert_equal(2026, d[:year])
+ assert_equal(6, d[:mon])
+ assert_equal(1, d[:mday])
+ end
end
diff --git a/test/date/test_switch_hitter.rb b/test/date/test_switch_hitter.rb
index bdf299e030..cc75782537 100644
--- a/test/date/test_switch_hitter.rb
+++ b/test/date/test_switch_hitter.rb
@@ -97,6 +97,11 @@ class TestSH < Test::Unit::TestCase
[d.year, d.mon, d.mday, d.hour, d.min, d.sec, d.offset])
end
+ def test_ajd
+ assert_equal(Date.civil(2008, 1, 16).ajd, 4908963r/2)
+ assert_equal(Date.civil(-11082381539297990, 2, 19).ajd, -8095679714453739481r/2)
+ end
+
def test_ordinal
d = Date.ordinal
assert_equal([-4712, 1], [d.year, d.yday])
diff --git a/test/did_you_mean/spell_checking/test_method_name_check.rb b/test/did_you_mean/spell_checking/test_method_name_check.rb
index 4daaf7cec7..2ae5fa7d03 100644
--- a/test/did_you_mean/spell_checking/test_method_name_check.rb
+++ b/test/did_you_mean/spell_checking/test_method_name_check.rb
@@ -98,6 +98,8 @@ class MethodNameCheckTest < Test::Unit::TestCase
end
def test_does_not_append_suggestions_twice
+ omit "This test is not working with JRuby" if RUBY_ENGINE == "jruby"
+
error = assert_raise NoMethodError do
begin
@user.firstname
@@ -110,6 +112,8 @@ class MethodNameCheckTest < Test::Unit::TestCase
end
def test_does_not_append_suggestions_three_times
+ omit "This test is not working with JRuby" if RUBY_ENGINE == "jruby"
+
error = assert_raise NoMethodError do
begin
@user.raise_no_method_error
diff --git a/test/did_you_mean/test_ractor_compatibility.rb b/test/did_you_mean/test_ractor_compatibility.rb
index 7385f10612..3166d0b6c5 100644
--- a/test/did_you_mean/test_ractor_compatibility.rb
+++ b/test/did_you_mean/test_ractor_compatibility.rb
@@ -14,7 +14,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase
e.corrections # It is important to call the #corrections method within Ractor.
e
end
- }.take
+ }.value
assert_correction "Book", error.corrections
CODE
@@ -32,7 +32,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase
e.corrections # It is important to call the #corrections method within Ractor.
e
end
- }.take
+ }.value
assert_correction ":bar", error.corrections
assert_match "Did you mean? :bar", get_message(error)
@@ -49,7 +49,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase
e.corrections # It is important to call the #corrections method within Ractor.
e
end
- }.take
+ }.value
assert_correction :to_s, error.corrections
assert_match "Did you mean? to_s", get_message(error)
@@ -71,7 +71,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase
e.corrections # It is important to call the #corrections method within Ractor.
e
end
- }.take
+ }.value
assert_correction ":foo", error.corrections
assert_match "Did you mean? :foo", get_message(error)
@@ -90,7 +90,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase
e.corrections # It is important to call the #corrections method within Ractor.
e
end
- }.take
+ }.value
assert_not_match(/Did you mean\?/, error.message)
CODE
@@ -108,7 +108,7 @@ class RactorCompatibilityTest < Test::Unit::TestCase
e.corrections # It is important to call the #corrections method within Ractor.
e
end
- }.take
+ }.value
assert_correction :in_ractor, error.corrections
assert_match "Did you mean? in_ractor", get_message(error)
diff --git a/test/digest/test_ractor.rb b/test/digest/test_ractor.rb
index b34a3653b4..d7b03eaeba 100644
--- a/test/digest/test_ractor.rb
+++ b/test/digest/test_ractor.rb
@@ -15,6 +15,10 @@ module TestDigestRactor
def test_s_hexdigest
assert_in_out_err([], <<-"end;", ["true", "true"], [])
+ class Ractor
+ alias value take
+ end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders
+
$VERBOSE = nil
require "digest"
require "#{self.class::LIB}"
@@ -26,7 +30,7 @@ module TestDigestRactor
[r, hexdigest]
end
rs.each do |r, hexdigest|
- puts r.take == hexdigest
+ puts r.value == hexdigest
end
end;
end
diff --git a/test/dtrace/helper.rb b/test/dtrace/helper.rb
index 7fa16965f1..9e8c7ecd52 100644
--- a/test/dtrace/helper.rb
+++ b/test/dtrace/helper.rb
@@ -65,11 +65,7 @@ module DTrace
class TestCase < Test::Unit::TestCase
INCLUDE = File.expand_path('..', File.dirname(__FILE__))
- case RUBY_PLATFORM
- when /solaris/i
- # increase bufsize to 8m (default 4m on Solaris)
- DTRACE_CMD = %w[dtrace -b 8m]
- when /darwin/i
+ if RUBY_PLATFORM =~ /darwin/i
READ_PROBES = proc do |cmd|
lines = nil
PTY.spawn(*cmd) do |io, _, pid|
diff --git a/test/erb/test_erb.rb b/test/erb/test_erb.rb
index 555345a140..b2eaa4e5c4 100644
--- a/test/erb/test_erb.rb
+++ b/test/erb/test_erb.rb
@@ -24,29 +24,6 @@ class TestERB < Test::Unit::TestCase
assert_match(/\Atest filename:1\b/, e.backtrace[0])
end
- # [deprecated] This will be removed later
- def test_without_filename_with_safe_level
- erb = EnvUtil.suppress_warning do
- ERB.new("<% raise ::TestERB::MyError %>", 1)
- end
- e = assert_raise(MyError) {
- erb.result
- }
- assert_match(/\A\(erb\):1\b/, e.backtrace[0])
- end
-
- # [deprecated] This will be removed later
- def test_with_filename_and_safe_level
- erb = EnvUtil.suppress_warning do
- ERB.new("<% raise ::TestERB::MyError %>", 1)
- end
- erb.filename = "test filename"
- e = assert_raise(MyError) {
- erb.result
- }
- assert_match(/\Atest filename:1\b/, e.backtrace[0])
- end
-
def test_with_filename_lineno
erb = ERB.new("<% raise ::TestERB::MyError %>")
erb.filename = "test filename"
@@ -77,6 +54,9 @@ class TestERB < Test::Unit::TestCase
assert_equal("", ERB::Util.html_escape(nil))
assert_equal("123", ERB::Util.html_escape(123))
+
+ assert_equal(65536+5, ERB::Util.html_escape("x"*65536 + "&").size)
+ assert_equal(65536+5, ERB::Util.html_escape("&" + "x"*65536).size)
end
def test_html_escape_to_s
@@ -114,25 +94,16 @@ class TestERBCore < Test::Unit::TestCase
end
def test_core
- # [deprecated] Fix initializer later
- EnvUtil.suppress_warning do
- _test_core(nil)
- _test_core(0)
- _test_core(1)
- end
- end
-
- def _test_core(safe)
erb = @erb.new("hello")
assert_equal("hello", erb.result)
- erb = @erb.new("hello", safe, 0)
+ erb = @erb.new("hello", trim_mode: 0)
assert_equal("hello", erb.result)
- erb = @erb.new("hello", safe, 1)
+ erb = @erb.new("hello", trim_mode: 1)
assert_equal("hello", erb.result)
- erb = @erb.new("hello", safe, 2)
+ erb = @erb.new("hello", trim_mode: 2)
assert_equal("hello", erb.result)
src = <<EOS
@@ -160,9 +131,9 @@ EOS
EOS
erb = @erb.new(src)
assert_equal(ans, erb.result)
- erb = @erb.new(src, safe, 0)
+ erb = @erb.new(src, trim_mode: 0)
assert_equal(ans, erb.result)
- erb = @erb.new(src, safe, '')
+ erb = EnvUtil.suppress_warning { @erb.new(src, trim_mode: '') }
assert_equal(ans, erb.result)
ans = <<EOS
@@ -173,9 +144,9 @@ EOS
* 1% n=0
* 2
EOS
- erb = @erb.new(src, safe, 1)
+ erb = @erb.new(src, trim_mode: 1)
assert_equal(ans.chomp, erb.result)
- erb = @erb.new(src, safe, '>')
+ erb = @erb.new(src, trim_mode: '>')
assert_equal(ans.chomp, erb.result)
ans = <<EOS
@@ -189,9 +160,9 @@ EOS
* 2
EOS
- erb = @erb.new(src, safe, 2)
+ erb = @erb.new(src, trim_mode: 2)
assert_equal(ans, erb.result)
- erb = @erb.new(src, safe, '<>')
+ erb = @erb.new(src, trim_mode: '<>')
assert_equal(ans, erb.result)
ans = <<EOS
@@ -205,7 +176,7 @@ EOS
* 0
EOS
- erb = @erb.new(src, safe, '%')
+ erb = @erb.new(src, trim_mode: '%')
assert_equal(ans, erb.result)
ans = <<EOS
@@ -213,7 +184,7 @@ EOS
= hello
* 0* 0* 0
EOS
- erb = @erb.new(src, safe, '%>')
+ erb = @erb.new(src, trim_mode: '%>')
assert_equal(ans.chomp, erb.result)
ans = <<EOS
@@ -223,7 +194,7 @@ EOS
* 0
* 0
EOS
- erb = @erb.new(src, safe, '%<>')
+ erb = @erb.new(src, trim_mode: '%<>')
assert_equal(ans, erb.result)
end
@@ -627,10 +598,10 @@ EOS
def test_frozen_string_literal
bug12031 = '[ruby-core:73561] [Bug #12031]'
e = @erb.new("<%#encoding: us-ascii%>a")
- e.src.sub!(/\A#(?:-\*-)?(.*)(?:-\*-)?/) {
+ src = e.src.sub(/\A#(?:-\*-)?(.*)(?:-\*-)?/) {
'# -*- \1; frozen-string-literal: true -*-'
}
- assert_equal("a", e.result, bug12031)
+ assert_equal("a", eval(src), bug12031)
%w(false true).each do |flag|
erb = @erb.new("<%#frozen-string-literal: #{flag}%><%=''.frozen?%>")
@@ -679,27 +650,6 @@ EOS
end
end
- # [deprecated] These interfaces will be removed later
- def test_deprecated_interface_warnings
- [nil, 0, 1, 2].each do |safe|
- assert_warn(/2nd argument of ERB.new is deprecated/) do
- ERB.new('', safe)
- end
- end
-
- [nil, '', '%', '%<>'].each do |trim|
- assert_warn(/3rd argument of ERB.new is deprecated/) do
- ERB.new('', nil, trim)
- end
- end
-
- [nil, '_erbout', '_hamlout'].each do |eoutvar|
- assert_warn(/4th argument of ERB.new is deprecated/) do
- ERB.new('', nil, nil, eoutvar)
- end
- end
- end
-
def test_prohibited_marshal_dump
erb = ERB.new("")
assert_raise(TypeError) {Marshal.dump(erb)}
@@ -714,6 +664,33 @@ EOS
assert_raise(ArgumentError) {erb.result}
end
+ def test_prohibited_marshal_load_def_method
+ erb = ERB.allocate
+ erb.instance_variable_set(:@src, "")
+ erb.instance_variable_set(:@lineno, 1)
+ erb.instance_variable_set(:@_init, true)
+ erb = Marshal.load(Marshal.dump(erb))
+ assert_raise(ArgumentError) {erb.def_method(Class.new, 'render')}
+ end
+
+ def test_prohibited_marshal_load_def_module
+ erb = ERB.allocate
+ erb.instance_variable_set(:@src, "")
+ erb.instance_variable_set(:@lineno, 1)
+ erb.instance_variable_set(:@_init, true)
+ erb = Marshal.load(Marshal.dump(erb))
+ assert_raise(ArgumentError) {erb.def_module}
+ end
+
+ def test_prohibited_marshal_load_def_class
+ erb = ERB.allocate
+ erb.instance_variable_set(:@src, "")
+ erb.instance_variable_set(:@lineno, 1)
+ erb.instance_variable_set(:@_init, true)
+ erb = Marshal.load(Marshal.dump(erb))
+ assert_raise(ArgumentError) {erb.def_class}
+ end
+
def test_multi_line_comment_lineno
erb = ERB.new(<<~EOS)
<%= __LINE__ %>
diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb
index b75bf6d06c..5f664de502 100644
--- a/test/error_highlight/test_error_highlight.rb
+++ b/test/error_highlight/test_error_highlight.rb
@@ -44,6 +44,17 @@ class ErrorHighlightTest < Test::Unit::TestCase
def assert_error_message(klass, expected_msg, &blk)
omit unless klass < ErrorHighlight::CoreExt
err = assert_raise(klass, &blk)
+ unless klass == ArgumentError && err.message =~ /\A(?:wrong number of arguments|missing keyword[s]?|unknown keyword[s]?|no keywords accepted)\b/
+ spot = ErrorHighlight.spot(err)
+ if spot
+ assert_kind_of(Integer, spot[:first_lineno])
+ assert_kind_of(Integer, spot[:first_column])
+ assert_kind_of(Integer, spot[:last_lineno])
+ assert_kind_of(Integer, spot[:last_column])
+ assert_kind_of(String, spot[:snippet])
+ assert_kind_of(Array, spot[:script_lines])
+ end
+ end
assert_equal(preprocess(expected_msg).chomp, err.detailed_message(highlight: false).sub(/ \((?:NoMethod|Name)Error\)/, ""))
end
else
@@ -880,27 +891,13 @@ uninitialized constant ErrorHighlightTest::NotDefined
end
end
- if ErrorHighlight.const_get(:Spotter).const_get(:OPT_GETCONSTANT_PATH)
- def test_COLON2_5
- # Unfortunately, we cannot identify which `NotDefined` caused the NameError
- assert_error_message(NameError, <<~END) do
- uninitialized constant ErrorHighlightTest::NotDefined
- END
-
- ErrorHighlightTest::NotDefined::NotDefined
- end
- end
- else
- def test_COLON2_5
- assert_error_message(NameError, <<~END) do
+ def test_COLON2_5
+ # Unfortunately, we cannot identify which `NotDefined` caused the NameError
+ assert_error_message(NameError, <<~END) do
uninitialized constant ErrorHighlightTest::NotDefined
+ END
- ErrorHighlightTest::NotDefined::NotDefined
- ^^^^^^^^^^^^
- END
-
- ErrorHighlightTest::NotDefined::NotDefined
- end
+ ErrorHighlightTest::NotDefined::NotDefined
end
end
@@ -1088,10 +1085,11 @@ nil can't be coerced into Integer (TypeError)
end
end
+ OF_NIL_INTO_INTEGER = RUBY_VERSION < "4.1." ? "from nil to integer" : "of nil into Integer"
def test_args_CALL_2
v = []
assert_error_message(TypeError, <<~END) do
-no implicit conversion from nil to integer (TypeError)
+no implicit conversion #{OF_NIL_INTO_INTEGER} (TypeError)
v[nil]
^^^
@@ -1102,12 +1100,13 @@ no implicit conversion from nil to integer (TypeError)
end
def test_args_ATTRASGN_1
- v = []
- assert_error_message(ArgumentError, <<~END) do
-wrong number of arguments (given 1, expected 2..3) (ArgumentError)
+ v = method(:raise).to_proc
+ recv = NEW_MESSAGE_FORMAT ? "an instance of Proc" : v.inspect
+ assert_error_message(NoMethodError, <<~END) do
+undefined method `[]=' for #{ recv }
v [ ] = 1
- ^^^^^^
+ ^^^^^
END
v [ ] = 1
@@ -1117,7 +1116,7 @@ wrong number of arguments (given 1, expected 2..3) (ArgumentError)
def test_args_ATTRASGN_2
v = []
assert_error_message(TypeError, <<~END) do
-no implicit conversion from nil to integer (TypeError)
+no implicit conversion #{OF_NIL_INTO_INTEGER} (TypeError)
v [nil] = 1
^^^^^^^^
@@ -1179,7 +1178,7 @@ no implicit conversion of Symbol into String (TypeError)
v = []
assert_error_message(TypeError, <<~END) do
-no implicit conversion from nil to integer (TypeError)
+no implicit conversion #{OF_NIL_INTO_INTEGER} (TypeError)
v [nil] += 42
^^^^^^^^^^
@@ -1190,16 +1189,16 @@ no implicit conversion from nil to integer (TypeError)
end
def test_args_OP_ASGN1_aref_2
- v = []
+ v = method(:raise).to_proc
assert_error_message(ArgumentError, <<~END) do
-wrong number of arguments (given 0, expected 1..2) (ArgumentError)
+ArgumentError (ArgumentError)
- v [ ] += 42
- ^^^^^^^^
+ v [ArgumentError] += 42
+ ^^^^^^^^^^^^^^^^^^^^
END
- v [ ] += 42
+ v [ArgumentError] += 42
end
end
@@ -1444,6 +1443,199 @@ undefined method `foo' for #{ NIL_RECV_MESSAGE }
end
end
+ begin
+ ->{}.call(1)
+ rescue ArgumentError => exc
+ MethodDefLocationSupported =
+ RubyVM::AbstractSyntaxTree.respond_to?(:node_id_for_backtrace_location) &&
+ RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(exc.backtrace_locations.first)
+ end
+
+ def process_callee_snippet(str)
+ return str if MethodDefLocationSupported
+
+ str.sub(/\n +\|.*\n +\^+\n\z/, "")
+ end
+
+ WRONG_NUMBER_OF_ARGUMENTS_LINENO = __LINE__ + 1
+ def wrong_number_of_arguments_test(x, y)
+ x + y
+ end
+
+ def test_wrong_number_of_arguments_for_method
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+wrong number of arguments (given 1, expected 2) (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | wrong_number_of_arguments_test(1)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ callee: #{ __FILE__ }:#{ WRONG_NUMBER_OF_ARGUMENTS_LINENO }
+ | def wrong_number_of_arguments_test(x, y)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ END
+
+ wrong_number_of_arguments_test(1)
+ end
+ end
+
+ KEYWORD_TEST_LINENO = __LINE__ + 1
+ def keyword_test(kw1:, kw2:, kw3:)
+ kw1 + kw2 + kw3
+ end
+
+ def test_missing_keyword
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+missing keyword: :kw3 (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | keyword_test(kw1: 1, kw2: 2)
+ ^^^^^^^^^^^^
+ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO }
+ | def keyword_test(kw1:, kw2:, kw3:)
+ ^^^^^^^^^^^^
+ END
+
+ keyword_test(kw1: 1, kw2: 2)
+ end
+ end
+
+ def test_missing_keywords # multiple missing keywords
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+missing keywords: :kw2, :kw3 (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | keyword_test(kw1: 1)
+ ^^^^^^^^^^^^
+ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO }
+ | def keyword_test(kw1:, kw2:, kw3:)
+ ^^^^^^^^^^^^
+ END
+
+ keyword_test(kw1: 1)
+ end
+ end
+
+ def test_unknown_keyword
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+unknown keyword: :kw4 (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4)
+ ^^^^^^^^^^^^
+ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO }
+ | def keyword_test(kw1:, kw2:, kw3:)
+ ^^^^^^^^^^^^
+ END
+
+ keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4)
+ end
+ end
+
+ def test_unknown_keywords
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+unknown keywords: :kw4, :kw5 (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5)
+ ^^^^^^^^^^^^
+ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO }
+ | def keyword_test(kw1:, kw2:, kw3:)
+ ^^^^^^^^^^^^
+ END
+
+ keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5)
+ end
+ end
+
+ WRONG_NUBMER_OF_ARGUMENTS_TEST2_LINENO = __LINE__ + 1
+ def wrong_number_of_arguments_test2(
+ long_argument_name_x,
+ long_argument_name_y,
+ long_argument_name_z
+ )
+ long_argument_name_x + long_argument_name_y + long_argument_name_z
+ end
+
+ def test_wrong_number_of_arguments_for_method2
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+wrong number of arguments (given 1, expected 3) (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | wrong_number_of_arguments_test2(1)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ callee: #{ __FILE__ }:#{ WRONG_NUBMER_OF_ARGUMENTS_TEST2_LINENO }
+ | def wrong_number_of_arguments_test2(
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ END
+
+ wrong_number_of_arguments_test2(1)
+ end
+ end
+
+ def test_wrong_number_of_arguments_for_lambda_literal
+ v = -> {}
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+wrong number of arguments (given 1, expected 0) (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | v.call(1)
+ ^^^^^
+ callee: #{ __FILE__ }:#{ lineno - 1 }
+ | v = -> {}
+ ^^
+ END
+
+ v.call(1)
+ end
+ end
+
+ def test_wrong_number_of_arguments_for_lambda_method
+ v = lambda { }
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+wrong number of arguments (given 1, expected 0) (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | v.call(1)
+ ^^^^^
+ callee: #{ __FILE__ }:#{ lineno - 1 }
+ | v = lambda { }
+ ^
+ END
+
+ v.call(1)
+ end
+ end
+
+ DEFINE_METHOD_TEST_LINENO = __LINE__ + 1
+ define_method :define_method_test do |x, y|
+ x + y
+ end
+
+ def test_wrong_number_of_arguments_for_define_method
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+wrong number of arguments (given 1, expected 2) (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | define_method_test(1)
+ ^^^^^^^^^^^^^^^^^^
+ callee: #{ __FILE__ }:#{ DEFINE_METHOD_TEST_LINENO }
+ | define_method :define_method_test do |x, y|
+ ^^
+ END
+
+ define_method_test(1)
+ end
+ end
+
def test_spoofed_filename
Tempfile.create(["error_highlight_test", ".rb"], binmode: true) do |tmp|
tmp << "module Dummy\nend\n"
@@ -1512,6 +1704,54 @@ undefined method `foo' for #{ NIL_RECV_MESSAGE }
assert_equal expected_spot, actual_spot
end
+ module SingletonMethodWithSpacing
+ LINENO = __LINE__ + 1
+ def self . baz(x:)
+ x
+ end
+ end
+
+ def test_singleton_method_with_spacing_missing_keyword
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+missing keyword: :x (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | SingletonMethodWithSpacing.baz
+ ^^^^
+ callee: #{ __FILE__ }:#{ SingletonMethodWithSpacing::LINENO }
+ | def self . baz(x:)
+ ^^^^^
+ END
+
+ SingletonMethodWithSpacing.baz
+ end
+ end
+
+ module SingletonMethodMultipleKwargs
+ LINENO = __LINE__ + 1
+ def self.run(shop_id:, param1:)
+ shop_id + param1
+ end
+ end
+
+ def test_singleton_method_multiple_missing_keywords
+ lineno = __LINE__
+ assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do
+missing keywords: :shop_id, :param1 (ArgumentError)
+
+ caller: #{ __FILE__ }:#{ lineno + 12 }
+ | SingletonMethodMultipleKwargs.run
+ ^^^^
+ callee: #{ __FILE__ }:#{ SingletonMethodMultipleKwargs::LINENO }
+ | def self.run(shop_id:, param1:)
+ ^^^^
+ END
+
+ SingletonMethodMultipleKwargs.run
+ end
+ end
+
private
def find_node_by_id(node, node_id)
diff --git a/test/etc/test_etc.rb b/test/etc/test_etc.rb
index c15c4f6e1e..c2e3af6317 100644
--- a/test/etc/test_etc.rb
+++ b/test/etc/test_etc.rb
@@ -21,7 +21,7 @@ class TestEtc < Test::Unit::TestCase
assert_instance_of(String, s.shell)
assert_kind_of(Integer, s.change) if s.respond_to?(:change)
assert_kind_of(Integer, s.quota) if s.respond_to?(:quota)
- assert(s.age.is_a?(Integer) || s.age.is_a?(String)) if s.respond_to?(:age)
+ assert(s.age.is_a?(Integer) || s.age.is_a?(String), s.age) if s.respond_to?(:age)
assert_instance_of(String, s.uclass) if s.respond_to?(:uclass)
assert_instance_of(String, s.comment) if s.respond_to?(:comment)
assert_kind_of(Integer, s.expire) if s.respond_to?(:expire)
@@ -160,7 +160,7 @@ class TestEtc < Test::Unit::TestCase
end
IO.pipe {|r, w|
val = w.pathconf(Etc::PC_PIPE_BUF)
- assert(val.nil? || val.kind_of?(Integer))
+ assert_kind_of(Integer, val) if val
}
end if defined?(Etc::PC_PIPE_BUF)
@@ -173,28 +173,85 @@ class TestEtc < Test::Unit::TestCase
assert_operator(File, :absolute_path?, Etc.sysconfdir)
end if File.method_defined?(:absolute_path?)
- def test_ractor
+ # All Ractor-safe methods should be tested here
+ def test_ractor_parallel
+ omit "This test is flaky and intermittently failing now on ModGC workflow" if ENV['GITHUB_WORKFLOW'] == 'ModGC'
+
+ assert_ractor(<<~RUBY, require: 'etc', timeout: 60)
+ 10.times.map do
+ Ractor.new do
+ 100.times do
+ raise unless String === Etc.systmpdir
+ raise unless Hash === Etc.uname
+ if defined?(Etc::SC_CLK_TCK)
+ raise unless Integer === Etc.sysconf(Etc::SC_CLK_TCK)
+ end
+ if defined?(Etc::CS_PATH)
+ raise unless String === Etc.confstr(Etc::CS_PATH)
+ end
+ if defined?(Etc::PC_PIPE_BUF)
+ IO.pipe { |r, w|
+ val = w.pathconf(Etc::PC_PIPE_BUF)
+ raise unless val.nil? || val.kind_of?(Integer)
+ }
+ end
+ raise unless Integer === Etc.nprocessors
+ end
+ end
+ end.each(&:join)
+ RUBY
+ end
+
+ def test_ractor_unsafe
+ assert_ractor(<<~RUBY, require: 'etc')
+ r = Ractor.new do
+ begin
+ Etc.passwd
+ rescue => e
+ e.class
+ end
+ end.value
+ assert_equal Ractor::UnsafeError, r
+ RUBY
+ end
+
+ def test_ractor_passwd
+ omit("https://bugs.ruby-lang.org/issues/21115")
return unless Etc.passwd # => skip test if no platform support
Etc.endpwent
assert_ractor(<<~RUBY, require: 'etc')
- ractor = Ractor.new do
+ ractor = Ractor.new port = Ractor::Port.new do |port|
Etc.passwd do |s|
- Ractor.yield :sync
- Ractor.yield s.name
+ port << :sync
+ port << s.name
break :done
end
end
- ractor.take # => :sync
+ port.receive # => :sync
assert_raise RuntimeError, /parallel/ do
Etc.passwd {}
end
- name = ractor.take # => first name
- ractor.take # => :done
+ name = port.receive # => first name
+ ractor.join # => :done
name2 = Etc.passwd do |s|
break s.name
end
assert_equal(name2, name)
RUBY
end
+
+ def test_ractor_getgrgid
+ omit("https://bugs.ruby-lang.org/issues/21115")
+
+ assert_ractor(<<~RUBY, require: 'etc')
+ 20.times.map do
+ Ractor.new do
+ 1000.times do
+ raise unless Etc.getgrgid(Process.gid).gid == Process.gid
+ end
+ end
+ end.each(&:join)
+ RUBY
+ end
end
diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb
index ac19bba7a2..8f1ce4376b 100644
--- a/test/fiber/scheduler.rb
+++ b/test/fiber/scheduler.rb
@@ -65,63 +65,79 @@ class Scheduler
end
end
- def run
- # $stderr.puts [__method__, Fiber.current].inspect
+ def run_once
+ readable = writable = nil
- while @readable.any? or @writable.any? or @waiting.any? or @blocking.any?
- # May only handle file descriptors up to 1024...
+ begin
readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout)
+ rescue IOError
+ # Ignore - this can happen if the IO is closed while we are waiting.
+ end
- # puts "readable: #{readable}" if readable&.any?
- # puts "writable: #{writable}" if writable&.any?
+ # puts "readable: #{readable}" if readable&.any?
+ # puts "writable: #{writable}" if writable&.any?
- selected = {}
+ selected = {}
- readable&.each do |io|
- if fiber = @readable.delete(io)
- @writable.delete(io) if @writable[io] == fiber
- selected[fiber] = IO::READABLE
- elsif io == @urgent.first
- @urgent.first.read_nonblock(1024)
- end
+ readable&.each do |io|
+ if fiber = @readable.delete(io)
+ @writable.delete(io) if @writable[io] == fiber
+ selected[fiber] = IO::READABLE
+ elsif io == @urgent.first
+ @urgent.first.read_nonblock(1024)
end
+ end
- writable&.each do |io|
- if fiber = @writable.delete(io)
- @readable.delete(io) if @readable[io] == fiber
- selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE
- end
+ writable&.each do |io|
+ if fiber = @writable.delete(io)
+ @readable.delete(io) if @readable[io] == fiber
+ selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE
end
+ end
- selected.each do |fiber, events|
- fiber.transfer(events)
- end
+ selected.each do |fiber, events|
+ fiber.transfer(events)
+ end
+
+ if @waiting.any?
+ time = current_time
+ waiting, @waiting = @waiting, {}
- if @waiting.any?
- time = current_time
- waiting, @waiting = @waiting, {}
-
- waiting.each do |fiber, timeout|
- if fiber.alive?
- if timeout <= time
- fiber.transfer
- else
- @waiting[fiber] = timeout
- end
+ waiting.each do |fiber, timeout|
+ if fiber.alive?
+ if timeout <= time
+ fiber.transfer
+ else
+ @waiting[fiber] = timeout
end
end
end
+ end
- if @ready.any?
- ready = nil
+ if @ready.any?
+ ready = nil
- @lock.synchronize do
- ready, @ready = @ready, []
- end
+ @lock.synchronize do
+ ready, @ready = @ready, []
+ end
- ready.each do |fiber|
- fiber.transfer
- end
+ ready.each do |fiber|
+ fiber.transfer if fiber.alive?
+ end
+ end
+ end
+
+ def run
+ # $stderr.puts [__method__, Fiber.current].inspect
+
+ # Use Thread.handle_interrupt like Async::Scheduler does
+ # This defers signal processing, which is the root cause of the gRPC bug
+ # See: https://github.com/socketry/async/blob/main/lib/async/scheduler.rb
+ Thread.handle_interrupt(::SignalException => :never) do
+ while @readable.any? or @writable.any? or @waiting.any? or @blocking.any?
+ run_once
+
+ break if Thread.pending_interrupt?
end
end
end
@@ -239,6 +255,13 @@ class Scheduler
end.value
end
+ # This hook is invoked by `IO#close`. Using a separate IO object
+ # demonstrates that the close operation is asynchronous.
+ def io_close(descriptor)
+ Fiber.blocking{IO.for_fd(descriptor.to_i).close}
+ return true
+ end
+
# This hook is invoked by `Kernel#sleep` and `Thread::Mutex#sleep`.
def kernel_sleep(duration = nil)
# $stderr.puts [__method__, duration, Fiber.current].inspect
@@ -290,6 +313,30 @@ class Scheduler
io.write_nonblock('.')
end
+ class FiberInterrupt
+ def initialize(fiber, exception)
+ @fiber = fiber
+ @exception = exception
+ end
+
+ def alive?
+ @fiber.alive?
+ end
+
+ def transfer
+ @fiber.raise(@exception)
+ end
+ end
+
+ def fiber_interrupt(fiber, exception)
+ @lock.synchronize do
+ @ready << FiberInterrupt.new(fiber, exception)
+ end
+
+ io = @urgent.last
+ io.write_nonblock('.')
+ end
+
# This hook is invoked by `Fiber.schedule`. Strictly speaking, you should use
# it to create scheduled fibers, but it is not required in practice;
# `Fiber.new` is usually sufficient.
@@ -311,7 +358,7 @@ class Scheduler
end
def blocking_operation_wait(work)
- thread = Thread.new(&work)
+ thread = Thread.new{work.call}
thread.join
@@ -441,6 +488,33 @@ class IOBufferScheduler < Scheduler
end
end
+class IOScheduler < Scheduler
+ def operations
+ @operations ||= []
+ end
+
+ def io_write(io, buffer, length, offset)
+ descriptor = io.fileno
+ string = buffer.get_string
+
+ self.operations << [:io_write, descriptor, string]
+
+ Fiber.blocking do
+ buffer.write(io, 0, offset)
+ end
+ end
+end
+
+class IOErrorScheduler < Scheduler
+ def io_read(io, buffer, length, offset)
+ return -Errno::EBADF::Errno
+ end
+
+ def io_write(io, buffer, length, offset)
+ return -Errno::EINVAL::Errno
+ end
+end
+
# This scheduler has a broken implementation of `unblock`` in the sense that it
# raises an exception. This is used to test the behavior of the scheduler when
# unblock raises an exception.
diff --git a/test/fiber/test_io.rb b/test/fiber/test_io.rb
index 39e32c5987..eea06f97c8 100644
--- a/test/fiber/test_io.rb
+++ b/test/fiber/test_io.rb
@@ -9,7 +9,7 @@ class TestFiberIO < Test::Unit::TestCase
omit unless defined?(UNIXSocket)
i, o = UNIXSocket.pair
- if RUBY_PLATFORM=~/mswin|mingw/
+ if RUBY_PLATFORM =~ /mswin|mingw/
i.nonblock = true
o.nonblock = true
end
@@ -44,7 +44,7 @@ class TestFiberIO < Test::Unit::TestCase
16.times.map do
Thread.new do
i, o = UNIXSocket.pair
- if RUBY_PLATFORM=~/mswin|mingw/
+ if RUBY_PLATFORM =~ /mswin|mingw/
i.nonblock = true
o.nonblock = true
end
@@ -67,7 +67,7 @@ class TestFiberIO < Test::Unit::TestCase
def test_epipe_on_read
omit unless defined?(UNIXSocket)
- omit "nonblock=true isn't properly supported on Windows" if RUBY_PLATFORM=~/mswin|mingw/
+ omit "nonblock=true isn't properly supported on Windows" if RUBY_PLATFORM =~ /mswin|mingw/
i, o = UNIXSocket.pair
@@ -242,38 +242,37 @@ class TestFiberIO < Test::Unit::TestCase
# Windows has UNIXSocket, but only with VS 2019+
omit "UNIXSocket is not defined!" unless defined?(UNIXSocket)
- i, o = Socket.pair(:UNIX, :STREAM)
- if RUBY_PLATFORM=~/mswin|mingw/
- i.nonblock = true
- o.nonblock = true
- end
+ Socket.pair(:UNIX, :STREAM) do |i, o|
+ if RUBY_PLATFORM =~ /mswin|mingw/
+ i.nonblock = true
+ o.nonblock = true
+ end
- reading_thread = Thread.new do
- Thread.current.report_on_exception = false
- i.wait_readable
- end
+ reading_thread = Thread.new do
+ Thread.current.report_on_exception = false
+ i.wait_readable
+ end
- fs_thread = Thread.new do
- # Wait until the reading thread is blocked on read:
- Thread.pass until reading_thread.status == "sleep"
+ scheduler_thread = Thread.new do
+ # Wait until the reading thread is blocked on read:
+ Thread.pass until reading_thread.status == "sleep"
- scheduler = Scheduler.new
- Fiber.set_scheduler scheduler
- Fiber.schedule do
- i.close
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+ Fiber.schedule do
+ i.close
+ end
end
- end
- assert_raise(IOError) { reading_thread.join }
- refute_nil fs_thread.join(5), "expected thread to terminate within 5 seconds"
+ assert_raise(IOError) { reading_thread.join }
+ refute_nil scheduler_thread.join(5), "expected thread to terminate within 5 seconds"
- assert_predicate(i, :closed?)
- ensure
- fs_thread&.kill
- fs_thread&.join rescue nil
- reading_thread&.kill
- reading_thread&.join rescue nil
- i&.close
- o&.close
+ assert_predicate(i, :closed?)
+ ensure
+ scheduler_thread&.kill
+ scheduler_thread&.join rescue nil
+ reading_thread&.kill
+ reading_thread&.join rescue nil
+ end
end
end
diff --git a/test/fiber/test_io_close.rb b/test/fiber/test_io_close.rb
new file mode 100644
index 0000000000..742b40841d
--- /dev/null
+++ b/test/fiber/test_io_close.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+require 'test/unit'
+require_relative 'scheduler'
+
+class TestFiberIOClose < Test::Unit::TestCase
+ def with_socket_pair(&block)
+ omit "UNIXSocket is not defined!" unless defined?(UNIXSocket)
+
+ UNIXSocket.pair do |i, o|
+ if RUBY_PLATFORM =~ /mswin|mingw/
+ i.nonblock = true
+ o.nonblock = true
+ end
+
+ yield i, o
+ end
+ end
+
+ def test_io_close_across_fibers
+ # omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/
+
+ with_socket_pair do |i, o|
+ error = nil
+
+ thread = Thread.new do
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ Fiber.schedule do
+ i.read
+ rescue => error
+ # Ignore.
+ end
+
+ Fiber.schedule do
+ i.close
+ end
+ end
+
+ thread.join
+
+ assert_instance_of IOError, error
+ assert_match(/closed/, error.message)
+ end
+ end
+
+ def test_io_close_blocking_thread
+ omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/
+
+ with_socket_pair do |i, o|
+ error = nil
+
+ reading_thread = Thread.new do
+ i.read
+ rescue => error
+ # Ignore.
+ end
+
+ Thread.pass until reading_thread.status == 'sleep'
+
+ thread = Thread.new do
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ Fiber.schedule do
+ i.close
+ end
+ end
+
+ thread.join
+ reading_thread.join
+
+ assert_instance_of IOError, error
+ assert_match(/closed/, error.message)
+ end
+ end
+
+ def test_io_close_blocking_fiber
+ # omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/
+
+ with_socket_pair do |i, o|
+ error = nil
+
+ thread = Thread.new do
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ Fiber.schedule do
+ begin
+ i.read
+ rescue => error
+ # Ignore.
+ end
+ end
+ end
+
+ Thread.pass until thread.status == 'sleep'
+
+ i.close
+
+ thread.join
+
+ assert_instance_of IOError, error
+ assert_match(/closed/, error.message)
+ end
+ end
+end
diff --git a/test/fiber/test_ractor.rb b/test/fiber/test_ractor.rb
index 3c4ccbd8e5..7dd82eda62 100644
--- a/test/fiber/test_ractor.rb
+++ b/test/fiber/test_ractor.rb
@@ -17,7 +17,7 @@ class TestFiberCurrentRactor < Test::Unit::TestCase
Fiber.current.class
end.resume
end
- assert_equal(Fiber, r.take)
+ assert_equal(Fiber, r.value)
end;
end
end
diff --git a/test/fiber/test_scheduler.rb b/test/fiber/test_scheduler.rb
index 62424fc489..d3696267f7 100644
--- a/test/fiber/test_scheduler.rb
+++ b/test/fiber/test_scheduler.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'test/unit'
+require 'securerandom'
+require 'fileutils'
require_relative 'scheduler'
class TestFiberScheduler < Test::Unit::TestCase
@@ -94,6 +96,9 @@ class TestFiberScheduler < Test::Unit::TestCase
def scheduler.kernel_sleep
end
+ def scheduler.fiber_interrupt(_fiber, _exception)
+ end
+
thread = Thread.new do
Fiber.set_scheduler scheduler
end
@@ -139,6 +144,19 @@ class TestFiberScheduler < Test::Unit::TestCase
end
end
+ def test_iseq_compile_under_gc_stress_bug_21180
+ Thread.new do
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ Fiber.schedule do
+ EnvUtil.under_gc_stress do
+ RubyVM::InstructionSequence.compile_file(File::NULL)
+ end
+ end
+ end.join
+ end
+
def test_deadlock
mutex = Thread::Mutex.new
condition = Thread::ConditionVariable.new
@@ -210,4 +228,159 @@ class TestFiberScheduler < Test::Unit::TestCase
thread.join
assert_kind_of RuntimeError, error
end
+
+ def test_post_fork_scheduler_reset
+ omit 'fork not supported' unless Process.respond_to?(:fork)
+
+ forked_scheduler_state = nil
+ thread = Thread.new do
+ r, w = IO.pipe
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ forked_pid = fork do
+ r.close
+ w << (Fiber.scheduler ? 'set' : 'reset')
+ w.close
+ end
+ w.close
+ forked_scheduler_state = r.read
+ Process.wait(forked_pid)
+ ensure
+ r.close rescue nil
+ w.close rescue nil
+ end
+ thread.join
+ assert_equal 'reset', forked_scheduler_state
+ ensure
+ thread.kill rescue nil
+ end
+
+ def test_post_fork_fiber_blocking
+ omit 'fork not supported' unless Process.respond_to?(:fork)
+
+ fiber_blocking_state = nil
+ thread = Thread.new do
+ r, w = IO.pipe
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ forked_pid = nil
+ Fiber.schedule do
+ forked_pid = fork do
+ r.close
+ w << (Fiber.current.blocking? ? 'blocking' : 'nonblocking')
+ w.close
+ end
+ end
+ w.close
+ fiber_blocking_state = r.read
+ Process.wait(forked_pid)
+ ensure
+ r.close rescue nil
+ w.close rescue nil
+ end
+ thread.join
+ assert_equal 'blocking', fiber_blocking_state
+ ensure
+ thread.kill rescue nil
+ end
+
+ def test_io_write_on_flush
+ begin
+ path = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}")
+ descriptor = nil
+ operations = nil
+
+ thread = Thread.new do
+ scheduler = IOScheduler.new
+ Fiber.set_scheduler scheduler
+
+ Fiber.schedule do
+ File.open(path, 'w+') do |file|
+ descriptor = file.fileno
+ file << 'foo'
+ file.flush
+ file << 'bar'
+ end
+ end
+
+ operations = scheduler.operations
+ end
+
+ thread.join
+ assert_equal [
+ [:io_write, descriptor, 'foo'],
+ [:io_write, descriptor, 'bar']
+ ], operations
+
+ assert_equal 'foobar', IO.read(path)
+ ensure
+ thread.kill rescue nil
+ FileUtils.rm_f(path)
+ end
+ end
+
+ def test_io_read_error
+ path = File.join(Dir.tmpdir, "ruby_test_io_read_error_#{SecureRandom.hex}")
+ error = nil
+
+ thread = Thread.new do
+ scheduler = IOErrorScheduler.new
+ Fiber.set_scheduler scheduler
+ Fiber.schedule do
+ File.open(path, 'w+') { it.read }
+ rescue => error
+ # Ignore.
+ end
+ end
+
+ thread.join
+ assert_kind_of Errno::EBADF, error
+ ensure
+ thread.kill rescue nil
+ FileUtils.rm_f(path)
+ end
+
+ def test_io_write_error
+ path = File.join(Dir.tmpdir, "ruby_test_io_write_error_#{SecureRandom.hex}")
+ error = nil
+
+ thread = Thread.new do
+ scheduler = IOErrorScheduler.new
+ Fiber.set_scheduler scheduler
+ Fiber.schedule do
+ File.open(path, 'w+') { it.sync = true; it << 'foo' }
+ rescue => error
+ # Ignore.
+ end
+ end
+
+ thread.join
+ assert_kind_of Errno::EINVAL, error
+ ensure
+ thread.kill rescue nil
+ FileUtils.rm_f(path)
+ end
+
+ def test_io_write_flush_error
+ path = File.join(Dir.tmpdir, "ruby_test_io_write_flush_error_#{SecureRandom.hex}")
+ error = nil
+
+ thread = Thread.new do
+ scheduler = IOErrorScheduler.new
+ Fiber.set_scheduler scheduler
+ Fiber.schedule do
+ File.open(path, 'w+') { it << 'foo' }
+ rescue => error
+ # Ignore.
+ end
+ end
+
+ thread.join
+ assert_kind_of Errno::EINVAL, error
+ ensure
+ thread.kill rescue nil
+ FileUtils.rm_f(path)
+ end
end
diff --git a/test/fiber/test_sleep.rb b/test/fiber/test_sleep.rb
index a7e88c0367..187f59dbd4 100644
--- a/test/fiber/test_sleep.rb
+++ b/test/fiber/test_sleep.rb
@@ -35,13 +35,13 @@ class TestFiberSleep < Test::Unit::TestCase
scheduler = Scheduler.new
Fiber.set_scheduler scheduler
Fiber.schedule do
- seconds = sleep(2)
+ seconds = sleep(1.1)
end
end
thread.join
- assert_operator seconds, :>=, 2, "actual: %p" % seconds
+ assert_operator seconds, :>=, 1, "actual: %p" % seconds
end
def test_broken_sleep
diff --git a/test/fiber/test_thread.rb b/test/fiber/test_thread.rb
index 5e3cc6d0e1..4d2fbde9ed 100644
--- a/test/fiber/test_thread.rb
+++ b/test/fiber/test_thread.rb
@@ -90,6 +90,47 @@ class TestFiberThread < Test::Unit::TestCase
assert_equal :done, thread.value
end
+ def test_spurious_unblock_during_thread_join
+ ready = Thread::Queue.new
+
+ target_thread = Thread.new do
+ ready.pop
+ :success
+ end
+
+ Thread.pass until target_thread.status == "sleep"
+
+ result = nil
+
+ thread = Thread.new do
+ scheduler = Scheduler.new
+ Fiber.set_scheduler scheduler
+
+ # Create a fiber that will join a long-running thread:
+ joining_fiber = Fiber.schedule do
+ result = target_thread.value
+ end
+
+ # Create another fiber that spuriously unblocks the joining fiber:
+ Fiber.schedule do
+ # This interrupts the join in joining_fiber:
+ scheduler.unblock(:spurious_wakeup, joining_fiber)
+
+ # This allows the unblock to be processed:
+ sleep(0)
+
+ # This allows the target thread to finish:
+ ready.push(:done)
+ end
+
+ scheduler.run
+ end
+
+ thread.join
+
+ assert_equal :success, result
+ end
+
def test_broken_unblock
thread = Thread.new do
Thread.current.report_on_exception = false
@@ -115,16 +156,20 @@ class TestFiberThread < Test::Unit::TestCase
end
def test_thread_join_hang
+ inner = nil
thread = Thread.new do
scheduler = SleepingUnblockScheduler.new
Fiber.set_scheduler scheduler
Fiber.schedule do
- Thread.new{sleep(0.01)}.value
+ inner = Thread.new{sleep(0.01)}
+ inner.value
end
end
thread.join
+ ensure
+ inner&.join
end
end
diff --git a/test/fiddle/helper.rb b/test/fiddle/helper.rb
deleted file mode 100644
index 457e02f595..0000000000
--- a/test/fiddle/helper.rb
+++ /dev/null
@@ -1,202 +0,0 @@
-# frozen_string_literal: true
-
-require 'rbconfig/sizeof'
-require 'test/unit'
-require 'fiddle'
-
-puts("Fiddle::VERSION: #{Fiddle::VERSION}") if $VERBOSE
-
-# FIXME: this is stolen from DL and needs to be refactored.
-
-libc_so = libm_so = nil
-
-if RUBY_ENGINE == "jruby"
- # "jruby ... [x86_64-linux]" -> "x86_64-linux"
- ruby_platform = RUBY_DESCRIPTION.split(" ").last[1..-2]
-else
- ruby_platform = RUBY_PLATFORM
-end
-case ruby_platform
-when /cygwin/
- libc_so = "cygwin1.dll"
- libm_so = "cygwin1.dll"
-when /android/
- libdir = '/system/lib'
- if [0].pack('L!').size == 8
- libdir = '/system/lib64'
- end
- libc_so = File.join(libdir, "libc.so")
- libm_so = File.join(libdir, "libm.so")
-when /linux-musl/
- Dir.glob('/lib/ld-musl-*.so.1') do |ld|
- libc_so = libm_so = ld
- end
-when /linux/
- libdir = '/lib'
- case RbConfig::SIZEOF['void*']
- when 4
- # 32-bit ruby
- case RUBY_PLATFORM
- when /armv\w+-linux/
- # In the ARM 32-bit libc package such as libc6:armhf libc6:armel,
- # libc.so and libm.so are installed to /lib/arm-linux-gnu*.
- # It's not installed to /lib32.
- dir, = Dir.glob('/lib/arm-linux-gnu*')
- libdir = dir if dir && File.directory?(dir)
- else
- libdir = '/lib32' if File.directory? '/lib32'
- end
- when 8
- # 64-bit ruby
- libdir = '/lib64' if File.directory? '/lib64'
- end
-
- # Handle musl libc
- libc_so, = Dir.glob(File.join(libdir, "libc.musl*.so*"))
- if libc_so
- libm_so = libc_so
- else
- # glibc
- case RUBY_PLATFORM
- when /alpha-linux/, /ia64-linux/
- libc_so = "libc.so.6.1"
- libm_so = "libm.so.6.1"
- else
- libc_so = "libc.so.6"
- libm_so = "libm.so.6"
- end
- end
-when /mingw/, /mswin/
- require "rbconfig"
- crtname = RbConfig::CONFIG["RUBY_SO_NAME"][/msvc\w+/] || 'ucrtbase'
- libc_so = libm_so = "#{crtname}.dll"
-when /darwin/
- libc_so = libm_so = "/usr/lib/libSystem.B.dylib"
- # macOS 11.0+ removed libSystem.B.dylib from /usr/lib. But It works with dlopen.
- rigid_path = true
-when /kfreebsd/
- libc_so = "/lib/libc.so.0.1"
- libm_so = "/lib/libm.so.1"
-when /gnu/ #GNU/Hurd
- libc_so = "/lib/libc.so.0.3"
- libm_so = "/lib/libm.so.6"
-when /mirbsd/
- libc_so = "/usr/lib/libc.so.41.10"
- libm_so = "/usr/lib/libm.so.7.0"
-when /freebsd/
- libc_so = "/lib/libc.so.7"
- libm_so = "/lib/libm.so.5"
-when /bsd|dragonfly/
- libc_so = "/usr/lib/libc.so"
- libm_so = "/usr/lib/libm.so"
-when /solaris/
- libdir = '/lib'
- case RbConfig::SIZEOF['void*']
- when 4
- # 32-bit ruby
- libdir = '/lib' if File.directory? '/lib'
- when 8
- # 64-bit ruby
- libdir = '/lib/64' if File.directory? '/lib/64'
- end
- libc_so = File.join(libdir, "libc.so")
- libm_so = File.join(libdir, "libm.so")
-when /aix/
- pwd=Dir.pwd
- libc_so = libm_so = "#{pwd}/libaixdltest.so"
- unless File.exist? libc_so
- cobjs=%w!strcpy.o!
- mobjs=%w!floats.o sin.o!
- funcs=%w!sin sinf strcpy strncpy!
- expfile='dltest.exp'
- require 'tmpdir'
- Dir.mktmpdir do |_dir|
- begin
- Dir.chdir _dir
- %x!/usr/bin/ar x /usr/lib/libc.a #{cobjs.join(' ')}!
- %x!/usr/bin/ar x /usr/lib/libm.a #{mobjs.join(' ')}!
- %x!echo "#{funcs.join("\n")}\n" > #{expfile}!
- require 'rbconfig'
- if RbConfig::CONFIG["GCC"] = 'yes'
- lflag='-Wl,'
- else
- lflag=''
- end
- flags="#{lflag}-bE:#{expfile} #{lflag}-bnoentry -lm"
- %x!#{RbConfig::CONFIG["LDSHARED"]} -o #{libc_so} #{(cobjs+mobjs).join(' ')} #{flags}!
- ensure
- Dir.chdir pwd
- end
- end
- end
-when /haiku/
- libdir = '/system/lib'
- case [0].pack('L!').size
- when 4
- # 32-bit ruby
- libdir = '/system/lib/x86' if File.directory? '/system/lib/x86'
- when 8
- # 64-bit ruby
- libdir = '/system/lib/' if File.directory? '/system/lib/'
- end
- libc_so = File.join(libdir, "libroot.so")
- libm_so = File.join(libdir, "libroot.so")
-else
- libc_so = ARGV[0] if ARGV[0] && ARGV[0][0] == ?/
- libm_so = ARGV[1] if ARGV[1] && ARGV[1][0] == ?/
- if( !(libc_so && libm_so) )
- $stderr.puts("libc and libm not found: #{$0} <libc> <libm>")
- end
-end
-
-unless rigid_path
- libc_so = nil if libc_so && libc_so[0] == ?/ && !File.file?(libc_so)
- libm_so = nil if libm_so && libm_so[0] == ?/ && !File.file?(libm_so)
-end
-
-if !libc_so || !libm_so
- require "envutil"
- ruby = EnvUtil.rubybin
- # When the ruby binary is 32-bit and the host is 64-bit,
- # `ldd ruby` outputs "not a dynamic executable" message.
- # libc_so and libm_so are not set.
- ldd = `ldd #{ruby}`
- #puts ldd
- libc_so = $& if !libc_so && %r{/\S*/libc\.so\S*} =~ ldd
- libm_so = $& if !libm_so && %r{/\S*/libm\.so\S*} =~ ldd
- #p [libc_so, libm_so]
-end
-
-Fiddle::LIBC_SO = libc_so
-Fiddle::LIBM_SO = libm_so
-
-module Fiddle
- class TestCase < Test::Unit::TestCase
- def setup
- @libc = Fiddle.dlopen(LIBC_SO)
- @libm = Fiddle.dlopen(LIBM_SO)
- end
-
- def teardown
- if /linux/ =~ RUBY_PLATFORM
- GC.start
- end
- end
-
- def ffi_backend?
- RUBY_ENGINE != 'ruby'
- end
-
- def under_gc_stress
- stress, GC.stress = GC.stress, true
- yield
- ensure
- GC.stress = stress
- end
-
- def assert_ractor_shareable(object)
- Ractor.make_shareable(object)
- assert_operator(Ractor, :shareable?, object)
- end
- end
-end
diff --git a/test/fiddle/test_c_struct_builder.rb b/test/fiddle/test_c_struct_builder.rb
deleted file mode 100644
index ca44c6cf7a..0000000000
--- a/test/fiddle/test_c_struct_builder.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
- require 'fiddle/struct'
- require 'fiddle/cparser'
- require 'fiddle/import'
-rescue LoadError
-end
-
-module Fiddle
- class TestCStructBuilder < TestCase
- include Fiddle::CParser
- extend Fiddle::Importer
-
- RBasic = struct ['void * flags',
- 'void * klass' ]
-
-
- RObject = struct [
- { 'basic' => RBasic },
- { 'as' => union([
- { 'heap'=> struct([ 'uint32_t numiv',
- 'void * ivptr',
- 'void * iv_index_tbl' ]) },
- 'void *ary[3]' ])}
- ]
-
-
- def test_basic_embedded_members
- assert_equal 0, RObject.offsetof("basic.flags")
- assert_equal Fiddle::SIZEOF_VOIDP, RObject.offsetof("basic.klass")
- end
-
- def test_embedded_union_members
- assert_equal 2 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as")
- assert_equal 2 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.heap")
- assert_equal 2 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.heap.numiv")
- assert_equal 3 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.heap.ivptr")
- assert_equal 4 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.heap.iv_index_tbl")
- end
-
- def test_as_ary
- assert_equal 2 * Fiddle::SIZEOF_VOIDP, RObject.offsetof("as.ary")
- end
-
- def test_offsetof
- types, members = parse_struct_signature(['int64_t i','char c'])
- my_struct = Fiddle::CStructBuilder.create(Fiddle::CStruct, types, members)
- assert_equal 0, my_struct.offsetof("i")
- assert_equal Fiddle::SIZEOF_INT64_T, my_struct.offsetof("c")
- end
-
- def test_offset_with_gap
- types, members = parse_struct_signature(['void *p', 'char c', 'long x'])
- my_struct = Fiddle::CStructBuilder.create(Fiddle::CStruct, types, members)
-
- assert_equal PackInfo.align(0, ALIGN_VOIDP), my_struct.offsetof("p")
- assert_equal PackInfo.align(SIZEOF_VOIDP, ALIGN_CHAR), my_struct.offsetof("c")
- assert_equal SIZEOF_VOIDP + PackInfo.align(SIZEOF_CHAR, ALIGN_LONG), my_struct.offsetof("x")
- end
-
- def test_union_offsetof
- types, members = parse_struct_signature(['int64_t i','char c'])
- my_struct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members)
- assert_equal 0, my_struct.offsetof("i")
- assert_equal 0, my_struct.offsetof("c")
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_c_struct_entry.rb b/test/fiddle/test_c_struct_entry.rb
deleted file mode 100644
index 45de2efe21..0000000000
--- a/test/fiddle/test_c_struct_entry.rb
+++ /dev/null
@@ -1,171 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
- require 'fiddle/struct'
-rescue LoadError
-end
-
-module Fiddle
- class TestCStructEntity < TestCase
- def test_class_size
- types = [TYPE_DOUBLE, TYPE_CHAR, TYPE_DOUBLE, TYPE_BOOL]
-
- size = CStructEntity.size types
-
- alignments = types.map { |type| PackInfo::ALIGN_MAP[type] }
-
- expected = PackInfo.align 0, alignments[0]
- expected += PackInfo::SIZE_MAP[TYPE_DOUBLE]
-
- expected = PackInfo.align expected, alignments[1]
- expected += PackInfo::SIZE_MAP[TYPE_CHAR]
-
- expected = PackInfo.align expected, alignments[2]
- expected += PackInfo::SIZE_MAP[TYPE_DOUBLE]
-
- expected = PackInfo.align expected, alignments[3]
- expected += PackInfo::SIZE_MAP[TYPE_BOOL]
-
- expected = PackInfo.align expected, alignments.max
-
- assert_equal expected, size
- end
-
- def test_class_size_with_count
- size = CStructEntity.size([[TYPE_DOUBLE, 2], [TYPE_CHAR, 20]])
-
- types = [TYPE_DOUBLE, TYPE_CHAR]
- alignments = types.map { |type| PackInfo::ALIGN_MAP[type] }
-
- expected = PackInfo.align 0, alignments[0]
- expected += PackInfo::SIZE_MAP[TYPE_DOUBLE] * 2
-
- expected = PackInfo.align expected, alignments[1]
- expected += PackInfo::SIZE_MAP[TYPE_CHAR] * 20
-
- expected = PackInfo.align expected, alignments.max
-
- assert_equal expected, size
- end
-
- def test_set_ctypes
- CStructEntity.malloc([TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE) do |struct|
- struct.assign_names %w[int long]
-
- # this test is roundabout because the stored ctypes are not accessible
- struct['long'] = 1
- struct['int'] = 2
-
- assert_equal 1, struct['long']
- assert_equal 2, struct['int']
- end
- end
-
- def test_aref_pointer_array
- CStructEntity.malloc([[TYPE_VOIDP, 2]], Fiddle::RUBY_FREE) do |team|
- team.assign_names(["names"])
- Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice|
- alice[0, 6] = "Alice\0"
- Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE) do |bob|
- bob[0, 4] = "Bob\0"
- team["names"] = [alice, bob]
- assert_equal(["Alice", "Bob"], team["names"].map(&:to_s))
- end
- end
- end
- end
-
- def test_aref_pointer
- CStructEntity.malloc([TYPE_VOIDP], Fiddle::RUBY_FREE) do |user|
- user.assign_names(["name"])
- Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice|
- alice[0, 6] = "Alice\0"
- user["name"] = alice
- assert_equal("Alice", user["name"].to_s)
- end
- end
- end
-
- def test_new_double_free
- types = [TYPE_INT]
- Pointer.malloc(CStructEntity.size(types), Fiddle::RUBY_FREE) do |pointer|
- assert_raise ArgumentError do
- CStructEntity.new(pointer, types, Fiddle::RUBY_FREE)
- end
- end
- end
-
- def test_malloc_block
- escaped_struct = nil
- returned = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct|
- assert_equal Fiddle::SIZEOF_INT, struct.size
- assert_equal Fiddle::RUBY_FREE, struct.free.to_i
- escaped_struct = struct
- :returned
- end
- assert_equal :returned, returned
- assert escaped_struct.freed?
- end
-
- def test_malloc_block_no_free
- assert_raise ArgumentError do
- CStructEntity.malloc([TYPE_INT]) { |struct| }
- end
- end
-
- def test_free
- struct = CStructEntity.malloc([TYPE_INT])
- begin
- assert_nil struct.free
- ensure
- Fiddle.free struct
- end
- end
-
- def test_free_with_func
- struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE)
- refute struct.freed?
- struct.call_free
- assert struct.freed?
- struct.call_free # you can safely run it again
- assert struct.freed?
- GC.start # you can safely run the GC routine
- assert struct.freed?
- end
-
- def test_free_with_no_func
- struct = CStructEntity.malloc([TYPE_INT])
- refute struct.freed?
- struct.call_free
- refute struct.freed?
- struct.call_free # you can safely run it again
- refute struct.freed?
- end
-
- def test_freed?
- struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE)
- refute struct.freed?
- struct.call_free
- assert struct.freed?
- end
-
- def test_null?
- struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE)
- refute struct.null?
- end
-
- def test_size
- CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct|
- assert_equal Fiddle::SIZEOF_INT, struct.size
- end
- end
-
- def test_size=
- CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct|
- assert_raise NoMethodError do
- struct.size = 1
- end
- end
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_c_union_entity.rb b/test/fiddle/test_c_union_entity.rb
deleted file mode 100644
index e0a3757562..0000000000
--- a/test/fiddle/test_c_union_entity.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
- require 'fiddle/struct'
-rescue LoadError
-end
-
-
-module Fiddle
- class TestCUnionEntity < TestCase
- def test_class_size
- size = CUnionEntity.size([TYPE_DOUBLE, TYPE_CHAR])
-
- assert_equal SIZEOF_DOUBLE, size
- end
-
- def test_class_size_with_count
- size = CUnionEntity.size([[TYPE_DOUBLE, 2], [TYPE_CHAR, 20]])
-
- assert_equal SIZEOF_CHAR * 20, size
- end
-
- def test_set_ctypes
- CUnionEntity.malloc([TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE) do |union|
- union.assign_names %w[int long]
-
- # this test is roundabout because the stored ctypes are not accessible
- union['long'] = 1
- assert_equal 1, union['long']
-
- union['int'] = 1
- assert_equal 1, union['int']
- end
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_closure.rb b/test/fiddle/test_closure.rb
deleted file mode 100644
index 26cff8e516..0000000000
--- a/test/fiddle/test_closure.rb
+++ /dev/null
@@ -1,173 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
-rescue LoadError
-end
-
-module Fiddle
- class TestClosure < Fiddle::TestCase
- def teardown
- super
- # We can't use ObjectSpace with JRuby.
- return if RUBY_ENGINE == "jruby"
- # Ensure freeing all closures.
- # See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
- not_freed_closures = []
- ObjectSpace.each_object(Fiddle::Closure) do |closure|
- not_freed_closures << closure unless closure.freed?
- end
- assert_equal([], not_freed_closures)
- end
-
- def test_argument_errors
- assert_raise(TypeError) do
- Closure.new(TYPE_INT, TYPE_INT)
- end
-
- assert_raise(TypeError) do
- Closure.new('foo', [TYPE_INT])
- end
-
- assert_raise(TypeError) do
- Closure.new(TYPE_INT, ['meow!'])
- end
- end
-
- def test_call
- closure_class = Class.new(Closure) do
- def call
- 10
- end
- end
- closure_class.create(TYPE_INT, []) do |closure|
- func = Function.new(closure, [], TYPE_INT)
- assert_equal 10, func.call
- end
- end
-
- def test_returner
- closure_class = Class.new(Closure) do
- def call thing
- thing
- end
- end
- closure_class.create(TYPE_INT, [TYPE_INT]) do |closure|
- func = Function.new(closure, [TYPE_INT], TYPE_INT)
- assert_equal 10, func.call(10)
- end
- end
-
- def test_const_string
- if ffi_backend?
- omit("Closure with :const_string works but " +
- "Function with :const_string doesn't work with FFI backend")
- end
-
- closure_class = Class.new(Closure) do
- def call(string)
- @return_string = "Hello! #{string}"
- @return_string
- end
- end
- closure_class.create(:const_string, [:const_string]) do |closure|
- func = Function.new(closure, [:const_string], :const_string)
- assert_equal("Hello! World!", func.call("World!"))
- end
- end
-
- def test_bool
- closure_class = Class.new(Closure) do
- def call(bool)
- not bool
- end
- end
- closure_class.create(:bool, [:bool]) do |closure|
- func = Function.new(closure, [:bool], :bool)
- assert_equal(false, func.call(true))
- end
- end
-
- def test_free
- closure_class = Class.new(Closure) do
- def call
- 10
- end
- end
- closure_class.create(:int, [:void]) do |closure|
- assert(!closure.freed?)
- closure.free
- assert(closure.freed?)
- closure.free
- end
- end
-
- def test_block_caller
- cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
- one
- end
- begin
- func = Function.new(cb, [TYPE_INT], TYPE_INT)
- assert_equal 11, func.call(11)
- ensure
- cb.free
- end
- end
-
- def test_memsize_ruby_dev_42480
- if RUBY_ENGINE == "jruby"
- omit("We can't use ObjectSpace with JRuby")
- end
-
- require 'objspace'
- closure_class = Class.new(Closure) do
- def call
- 10
- end
- end
- n = 10000
- n.times do
- closure_class.create(:int, [:void]) do |closure|
- ObjectSpace.memsize_of(closure)
- end
- end
- end
-
- %w[INT SHORT CHAR LONG LONG_LONG].each do |name|
- type = Fiddle.const_get("TYPE_#{name}") rescue next
- size = Fiddle.const_get("SIZEOF_#{name}")
- [[type, size-1, name], [-type, size, "unsigned_"+name]].each do |t, s, n|
- define_method("test_conversion_#{n.downcase}") do
- arg = nil
-
- closure_class = Class.new(Closure) do
- define_method(:call) {|x| arg = x}
- end
- closure_class.create(t, [t]) do |closure|
- v = ~(~0 << (8*s))
-
- arg = nil
- assert_equal(v, closure.call(v))
- assert_equal(arg, v, n)
-
- arg = nil
- func = Function.new(closure, [t], t)
- assert_equal(v, func.call(v))
- assert_equal(arg, v, n)
- end
- end
- end
- end
-
- def test_ractor_shareable
- omit("Need Ractor") unless defined?(Ractor)
- closure_class = Class.new(Closure) do
- def call
- 0
- end
- end
- closure_class.create(:int, [:void]) do |c|
- assert_ractor_shareable(c)
- end
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb
deleted file mode 100644
index 2052911507..0000000000
--- a/test/fiddle/test_cparser.rb
+++ /dev/null
@@ -1,419 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
- require 'fiddle/cparser'
- require 'fiddle/import'
-rescue LoadError
-end
-
-module Fiddle
- class TestCParser < TestCase
- include CParser
-
- def test_char_ctype
- assert_equal(TYPE_CHAR, parse_ctype('char'))
- assert_equal(TYPE_CHAR, parse_ctype('const char'))
- assert_equal(TYPE_CHAR, parse_ctype('signed char'))
- assert_equal(TYPE_CHAR, parse_ctype('const signed char'))
- assert_equal(-TYPE_CHAR, parse_ctype('unsigned char'))
- assert_equal(-TYPE_CHAR, parse_ctype('const unsigned char'))
- end
-
- def test_short_ctype
- assert_equal(TYPE_SHORT, parse_ctype('short'))
- assert_equal(TYPE_SHORT, parse_ctype('const short'))
- assert_equal(TYPE_SHORT, parse_ctype('short int'))
- assert_equal(TYPE_SHORT, parse_ctype('const short int'))
- assert_equal(TYPE_SHORT, parse_ctype('int short'))
- assert_equal(TYPE_SHORT, parse_ctype('const int short'))
- assert_equal(TYPE_SHORT, parse_ctype('signed short'))
- assert_equal(TYPE_SHORT, parse_ctype('const signed short'))
- assert_equal(TYPE_SHORT, parse_ctype('short signed'))
- assert_equal(TYPE_SHORT, parse_ctype('const short signed'))
- assert_equal(TYPE_SHORT, parse_ctype('signed short int'))
- assert_equal(TYPE_SHORT, parse_ctype('const signed short int'))
- assert_equal(TYPE_SHORT, parse_ctype('signed int short'))
- assert_equal(TYPE_SHORT, parse_ctype('const signed int short'))
- assert_equal(TYPE_SHORT, parse_ctype('int signed short'))
- assert_equal(TYPE_SHORT, parse_ctype('const int signed short'))
- assert_equal(TYPE_SHORT, parse_ctype('int short signed'))
- assert_equal(TYPE_SHORT, parse_ctype('const int short signed'))
- assert_equal(-TYPE_SHORT, parse_ctype('unsigned short'))
- assert_equal(-TYPE_SHORT, parse_ctype('const unsigned short'))
- assert_equal(-TYPE_SHORT, parse_ctype('unsigned short int'))
- assert_equal(-TYPE_SHORT, parse_ctype('const unsigned short int'))
- assert_equal(-TYPE_SHORT, parse_ctype('unsigned int short'))
- assert_equal(-TYPE_SHORT, parse_ctype('const unsigned int short'))
- assert_equal(-TYPE_SHORT, parse_ctype('short int unsigned'))
- assert_equal(-TYPE_SHORT, parse_ctype('const short int unsigned'))
- assert_equal(-TYPE_SHORT, parse_ctype('int unsigned short'))
- assert_equal(-TYPE_SHORT, parse_ctype('const int unsigned short'))
- assert_equal(-TYPE_SHORT, parse_ctype('int short unsigned'))
- assert_equal(-TYPE_SHORT, parse_ctype('const int short unsigned'))
- end
-
- def test_int_ctype
- assert_equal(TYPE_INT, parse_ctype('int'))
- assert_equal(TYPE_INT, parse_ctype('const int'))
- assert_equal(TYPE_INT, parse_ctype('signed int'))
- assert_equal(TYPE_INT, parse_ctype('const signed int'))
- assert_equal(-TYPE_INT, parse_ctype('uint'))
- assert_equal(-TYPE_INT, parse_ctype('const uint'))
- assert_equal(-TYPE_INT, parse_ctype('unsigned int'))
- assert_equal(-TYPE_INT, parse_ctype('const unsigned int'))
- end
-
- def test_long_ctype
- assert_equal(TYPE_LONG, parse_ctype('long'))
- assert_equal(TYPE_LONG, parse_ctype('const long'))
- assert_equal(TYPE_LONG, parse_ctype('long int'))
- assert_equal(TYPE_LONG, parse_ctype('const long int'))
- assert_equal(TYPE_LONG, parse_ctype('int long'))
- assert_equal(TYPE_LONG, parse_ctype('const int long'))
- assert_equal(TYPE_LONG, parse_ctype('signed long'))
- assert_equal(TYPE_LONG, parse_ctype('const signed long'))
- assert_equal(TYPE_LONG, parse_ctype('signed long int'))
- assert_equal(TYPE_LONG, parse_ctype('const signed long int'))
- assert_equal(TYPE_LONG, parse_ctype('signed int long'))
- assert_equal(TYPE_LONG, parse_ctype('const signed int long'))
- assert_equal(TYPE_LONG, parse_ctype('long signed'))
- assert_equal(TYPE_LONG, parse_ctype('const long signed'))
- assert_equal(TYPE_LONG, parse_ctype('long int signed'))
- assert_equal(TYPE_LONG, parse_ctype('const long int signed'))
- assert_equal(TYPE_LONG, parse_ctype('int long signed'))
- assert_equal(TYPE_LONG, parse_ctype('const int long signed'))
- assert_equal(-TYPE_LONG, parse_ctype('unsigned long'))
- assert_equal(-TYPE_LONG, parse_ctype('const unsigned long'))
- assert_equal(-TYPE_LONG, parse_ctype('unsigned long int'))
- assert_equal(-TYPE_LONG, parse_ctype('const unsigned long int'))
- assert_equal(-TYPE_LONG, parse_ctype('long int unsigned'))
- assert_equal(-TYPE_LONG, parse_ctype('const long int unsigned'))
- assert_equal(-TYPE_LONG, parse_ctype('unsigned int long'))
- assert_equal(-TYPE_LONG, parse_ctype('const unsigned int long'))
- assert_equal(-TYPE_LONG, parse_ctype('int unsigned long'))
- assert_equal(-TYPE_LONG, parse_ctype('const int unsigned long'))
- assert_equal(-TYPE_LONG, parse_ctype('int long unsigned'))
- assert_equal(-TYPE_LONG, parse_ctype('const int long unsigned'))
- end
-
- def test_size_t_ctype
- assert_equal(TYPE_SIZE_T, parse_ctype("size_t"))
- assert_equal(TYPE_SIZE_T, parse_ctype("const size_t"))
- end
-
- def test_ssize_t_ctype
- assert_equal(TYPE_SSIZE_T, parse_ctype("ssize_t"))
- assert_equal(TYPE_SSIZE_T, parse_ctype("const ssize_t"))
- end
-
- def test_ptrdiff_t_ctype
- assert_equal(TYPE_PTRDIFF_T, parse_ctype("ptrdiff_t"))
- assert_equal(TYPE_PTRDIFF_T, parse_ctype("const ptrdiff_t"))
- end
-
- def test_intptr_t_ctype
- assert_equal(TYPE_INTPTR_T, parse_ctype("intptr_t"))
- assert_equal(TYPE_INTPTR_T, parse_ctype("const intptr_t"))
- end
-
- def test_uintptr_t_ctype
- assert_equal(TYPE_UINTPTR_T, parse_ctype("uintptr_t"))
- assert_equal(TYPE_UINTPTR_T, parse_ctype("const uintptr_t"))
- end
-
- def test_bool_ctype
- assert_equal(TYPE_BOOL, parse_ctype('bool'))
- end
-
- def test_undefined_ctype
- assert_raise(DLError) { parse_ctype('DWORD') }
- end
-
- def test_undefined_ctype_with_type_alias
- assert_equal(-TYPE_LONG,
- parse_ctype('DWORD', {"DWORD" => "unsigned long"}))
- assert_equal(-TYPE_LONG,
- parse_ctype('const DWORD', {"DWORD" => "unsigned long"}))
- end
-
- def expand_struct_types(types)
- types.collect do |type|
- case type
- when Class
- [expand_struct_types(type.types)]
- when Array
- [expand_struct_types([type[0]])[0][0], type[1]]
- else
- type
- end
- end
- end
-
- def test_struct_basic
- assert_equal([[TYPE_INT, TYPE_CHAR], ['i', 'c']],
- parse_struct_signature(['int i', 'char c']))
- assert_equal([[TYPE_INT, TYPE_CHAR], ['i', 'c']],
- parse_struct_signature(['const int i', 'const char c']))
- end
-
- def test_struct_array
- assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]],
- ['buffer', 'x']],
- parse_struct_signature(['char buffer[80]',
- 'int[5] x']))
- assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]],
- ['buffer', 'x']],
- parse_struct_signature(['const char buffer[80]',
- 'const int[5] x']))
- end
-
- def test_struct_nested_struct
- types, members = parse_struct_signature([
- 'int x',
- {inner: ['int i', 'char c']},
- ])
- assert_equal([[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]],
- ['x', ['inner', ['i', 'c']]]],
- [expand_struct_types(types),
- members])
- end
-
- def test_struct_nested_defined_struct
- inner = Fiddle::Importer.struct(['int i', 'char c'])
- assert_equal([[TYPE_INT, inner],
- ['x', ['inner', ['i', 'c']]]],
- parse_struct_signature([
- 'int x',
- {inner: inner},
- ]))
- end
-
- def test_struct_double_nested_struct
- types, members = parse_struct_signature([
- 'int x',
- {
- outer: [
- 'int y',
- {inner: ['int i', 'char c']},
- ],
- },
- ])
- assert_equal([[TYPE_INT, [[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]]]],
- ['x', ['outer', ['y', ['inner', ['i', 'c']]]]]],
- [expand_struct_types(types),
- members])
- end
-
- def test_struct_nested_struct_array
- types, members = parse_struct_signature([
- 'int x',
- {
- 'inner[2]' => [
- 'int i',
- 'char c',
- ],
- },
- ])
- assert_equal([[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]],
- ['x', ['inner', ['i', 'c']]]],
- [expand_struct_types(types),
- members])
- end
-
- def test_struct_double_nested_struct_inner_array
- types, members = parse_struct_signature(outer: [
- 'int x',
- {
- 'inner[2]' => [
- 'int i',
- 'char c',
- ],
- },
- ])
- assert_equal([[[[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]]]],
- [['outer', ['x', ['inner', ['i', 'c']]]]]],
- [expand_struct_types(types),
- members])
- end
-
- def test_struct_double_nested_struct_outer_array
- types, members = parse_struct_signature([
- 'int x',
- {
- 'outer[2]' => {
- inner: [
- 'int i',
- 'char c',
- ],
- },
- },
- ])
- assert_equal([[TYPE_INT, [[[[TYPE_INT, TYPE_CHAR]]], 2]],
- ['x', ['outer', [['inner', ['i', 'c']]]]]],
- [expand_struct_types(types),
- members])
- end
-
- def test_struct_array_str
- assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]],
- ['buffer', 'x']],
- parse_struct_signature('char buffer[80], int[5] x'))
- assert_equal([[[TYPE_CHAR, 80], [TYPE_INT, 5]],
- ['buffer', 'x']],
- parse_struct_signature('const char buffer[80], const int[5] x'))
- end
-
- def test_struct_function_pointer
- assert_equal([[TYPE_VOIDP], ['cb']],
- parse_struct_signature(['void (*cb)(const char*)']))
- end
-
- def test_struct_function_pointer_str
- assert_equal([[TYPE_VOIDP, TYPE_VOIDP], ['cb', 'data']],
- parse_struct_signature('void (*cb)(const char*), const char* data'))
- end
-
- def test_struct_string
- assert_equal [[TYPE_INT,TYPE_VOIDP,TYPE_VOIDP], ['x', 'cb', 'name']], parse_struct_signature('int x; void (*cb)(); const char* name')
- end
-
- def test_struct_bool
- assert_equal([[TYPE_INT, TYPE_BOOL], ['x', 'toggle']],
- parse_struct_signature('int x; bool toggle'))
- end
-
- def test_struct_undefined
- assert_raise(DLError) { parse_struct_signature(['int i', 'DWORD cb']) }
- end
-
- def test_struct_undefined_with_type_alias
- assert_equal [[TYPE_INT,-TYPE_LONG], ['i', 'cb']], parse_struct_signature(['int i', 'DWORD cb'], {"DWORD" => "unsigned long"})
- end
-
- def test_signature_basic
- func, ret, args = parse_signature('void func()')
- assert_equal 'func', func
- assert_equal TYPE_VOID, ret
- assert_equal [], args
- end
-
- def test_signature_semi
- func, ret, args = parse_signature('void func();')
- assert_equal 'func', func
- assert_equal TYPE_VOID, ret
- assert_equal [], args
- end
-
- def test_signature_void_arg
- func, ret, args = parse_signature('void func(void)')
- assert_equal 'func', func
- assert_equal TYPE_VOID, ret
- assert_equal [], args
- end
-
- def test_signature_type_args
- types = [
- 'char', 'unsigned char',
- 'short', 'unsigned short',
- 'int', 'unsigned int',
- 'long', 'unsigned long',
- defined?(TYPE_LONG_LONG) && \
- [
- 'long long', 'unsigned long long',
- ],
- 'float', 'double',
- 'const char*', 'void*',
- ].flatten.compact
- func, ret, args = parse_signature("void func(#{types.join(',')})")
- assert_equal 'func', func
- assert_equal TYPE_VOID, ret
- assert_equal [
- TYPE_CHAR, -TYPE_CHAR,
- TYPE_SHORT, -TYPE_SHORT,
- TYPE_INT, -TYPE_INT,
- TYPE_LONG, -TYPE_LONG,
- defined?(TYPE_LONG_LONG) && \
- [
- TYPE_LONG_LONG, -TYPE_LONG_LONG,
- ],
- TYPE_FLOAT, TYPE_DOUBLE,
- TYPE_VOIDP, TYPE_VOIDP,
- ].flatten.compact, args
- end
-
- def test_signature_single_variable
- func, ret, args = parse_signature('void func(int x)')
- assert_equal 'func', func
- assert_equal TYPE_VOID, ret
- assert_equal [TYPE_INT], args
- end
-
- def test_signature_multiple_variables
- func, ret, args = parse_signature('void func(int x, const char* s)')
- assert_equal 'func', func
- assert_equal TYPE_VOID, ret
- assert_equal [TYPE_INT, TYPE_VOIDP], args
- end
-
- def test_signature_array_variable
- func, ret, args = parse_signature('void func(int x[], int y[40])')
- assert_equal 'func', func
- assert_equal TYPE_VOID, ret
- assert_equal [TYPE_VOIDP, TYPE_VOIDP], args
- end
-
- def test_signature_function_pointer
- func, ret, args = parse_signature('int func(int (*sum)(int x, int y), int x, int y)')
- assert_equal 'func', func
- assert_equal TYPE_INT, ret
- assert_equal [TYPE_VOIDP, TYPE_INT, TYPE_INT], args
- end
-
- def test_signature_variadic_arguments
- unless Fiddle.const_defined?("TYPE_VARIADIC")
- omit "libffi doesn't support variadic arguments"
- end
- assert_equal([
- "printf",
- TYPE_INT,
- [TYPE_VOIDP, TYPE_VARIADIC],
- ],
- parse_signature('int printf(const char *format, ...)'))
- end
-
- def test_signature_return_pointer
- func, ret, args = parse_signature('void* malloc(size_t)')
- assert_equal 'malloc', func
- assert_equal TYPE_VOIDP, ret
- assert_equal [TYPE_SIZE_T], args
- end
-
- def test_signature_return_array
- func, ret, args = parse_signature('int (*func())[32]')
- assert_equal 'func', func
- assert_equal TYPE_VOIDP, ret
- assert_equal [], args
- end
-
- def test_signature_return_array_with_args
- func, ret, args = parse_signature('int (*func(const char* s))[]')
- assert_equal 'func', func
- assert_equal TYPE_VOIDP, ret
- assert_equal [TYPE_VOIDP], args
- end
-
- def test_signature_return_function_pointer
- func, ret, args = parse_signature('int (*func())(int x, int y)')
- assert_equal 'func', func
- assert_equal TYPE_VOIDP, ret
- assert_equal [], args
- end
-
- def test_signature_return_function_pointer_with_args
- func, ret, args = parse_signature('int (*func(int z))(int x, int y)')
- assert_equal 'func', func
- assert_equal TYPE_VOIDP, ret
- assert_equal [TYPE_INT], args
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_fiddle.rb b/test/fiddle/test_fiddle.rb
deleted file mode 100644
index 69d0c3d179..0000000000
--- a/test/fiddle/test_fiddle.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
-rescue LoadError
-end
-
-class TestFiddle < Fiddle::TestCase
- def test_nil_true_etc
- if ffi_backend?
- omit("Fiddle::Q* aren't supported with FFI backend")
- end
-
- assert_equal Fiddle::Qtrue, Fiddle.dlwrap(true)
- assert_equal Fiddle::Qfalse, Fiddle.dlwrap(false)
- assert_equal Fiddle::Qnil, Fiddle.dlwrap(nil)
- assert Fiddle::Qundef
- end
-
- def test_windows_constant
- require 'rbconfig'
- if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
- assert Fiddle::WINDOWS, "Fiddle::WINDOWS should be 'true' on Windows platforms"
- else
- refute Fiddle::WINDOWS, "Fiddle::WINDOWS should be 'false' on non-Windows platforms"
- end
- end
-
- def test_dlopen_linker_script_input_linux
- omit("This is only for Linux") unless RUBY_PLATFORM.match?("linux")
- if Dir.glob("/usr/lib{,64}/**/libncurses.so").empty?
- omit("libncurses.so is needed")
- end
- if ffi_backend?
- omit("Fiddle::Handle#file_name doesn't exist in FFI backend")
- end
-
- # libncurses.so uses INPUT() on Debian GNU/Linux and Arch Linux:
- #
- # Debian GNU/Linux:
- #
- # $ cat /usr/lib/x86_64-linux-gnu/libncurses.so
- # INPUT(libncurses.so.6 -ltinfo)
- #
- # Arch Linux:
- # $ cat /usr/lib/libncurses.so
- # INPUT(-lncursesw)
- handle = Fiddle.dlopen("libncurses.so")
- begin
- # /usr/lib/x86_64-linux-gnu/libncurses.so.6 ->
- # libncurses.so.6
- normalized_file_name = File.basename(handle.file_name)
- # libncurses.so.6 ->
- # libncurses.so
- #
- # libncursesw.so ->
- # libncursesw.so
- normalized_file_name = normalized_file_name.sub(/\.so(\.\d+)+\z/, ".so")
- # libncurses.so ->
- # libncurses.so
- #
- # libncursesw.so ->
- # libncurses.so
- normalized_file_name = normalized_file_name.sub(/ncursesw/, "ncurses")
- assert_equal("libncurses.so", normalized_file_name)
- ensure
- handle.close
- end
- end
-
- def test_dlopen_linker_script_group_linux
- omit("This is only for Linux") unless RUBY_PLATFORM.match?("linux")
- if ffi_backend?
- omit("Fiddle::Handle#file_name doesn't exist in FFI backend")
- end
-
- # libc.so uses GROUP() on Debian GNU/Linux
- # $ cat /usr/lib/x86_64-linux-gnu/libc.so
- # /* GNU ld script
- # Use the shared library, but some functions are only in
- # the static library, so try that secondarily. */
- # OUTPUT_FORMAT(elf64-x86-64)
- # GROUP ( /lib/x86_64-linux-gnu/libc.so.6 /usr/lib/x86_64-linux-gnu/libc_nonshared.a AS_NEEDED ( /lib64/ld-linux-x86-64.so.2 ) )
- handle = Fiddle.dlopen("libc.so")
- begin
- assert_equal("libc.so",
- File.basename(handle.file_name, ".*"))
- ensure
- handle.close
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_func.rb b/test/fiddle/test_func.rb
deleted file mode 100644
index ca503f92ad..0000000000
--- a/test/fiddle/test_func.rb
+++ /dev/null
@@ -1,180 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
-rescue LoadError
-end
-
-module Fiddle
- class TestFunc < TestCase
- def test_random
- f = Function.new(@libc['srand'], [-TYPE_LONG], TYPE_VOID)
- assert_nil f.call(10)
- end
-
- def test_sinf
- begin
- f = Function.new(@libm['sinf'], [TYPE_FLOAT], TYPE_FLOAT)
- rescue Fiddle::DLError
- omit "libm may not have sinf()"
- end
- assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001
- end
-
- def test_sin
- f = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
- assert_in_delta 1.0, f.call(90 * Math::PI / 180), 0.0001
- end
-
- def test_string
- if RUBY_ENGINE == "jruby"
- omit("Function that returns string doesn't work with JRuby")
- end
-
- under_gc_stress do
- f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
- buff = +"000"
- str = f.call(buff, "123")
- assert_equal("123", buff)
- assert_equal("123", str.to_s)
- end
- end
-
- def test_isdigit
- f = Function.new(@libc['isdigit'], [TYPE_INT], TYPE_INT)
- r1 = f.call(?1.ord)
- r2 = f.call(?2.ord)
- rr = f.call(?r.ord)
- assert_operator r1, :>, 0
- assert_operator r2, :>, 0
- assert_equal 0, rr
- end
-
- def test_atof
- f = Function.new(@libc['atof'], [TYPE_VOIDP], TYPE_DOUBLE)
- r = f.call("12.34")
- assert_includes(12.00..13.00, r)
- end
-
- def test_strtod
- f = Function.new(@libc['strtod'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_DOUBLE)
- buff1 = Pointer["12.34"]
- buff2 = buff1 + 4
- r = f.call(buff1, - buff2)
- assert_in_delta(12.34, r, 0.001)
- end
-
- def test_qsort1
- if RUBY_ENGINE == "jruby"
- omit("The untouched sanity check is broken on JRuby: https://github.com/jruby/jruby/issues/8365")
- end
-
- closure_class = Class.new(Closure) do
- def call(x, y)
- Pointer.new(x)[0] <=> Pointer.new(y)[0]
- end
- end
-
- closure_class.create(TYPE_INT, [TYPE_VOIDP, TYPE_VOIDP]) do |callback|
- qsort = Function.new(@libc['qsort'],
- [TYPE_VOIDP, TYPE_SIZE_T, TYPE_SIZE_T, TYPE_VOIDP],
- TYPE_VOID)
- untouched = "9341"
- buff = +"9341"
- qsort.call(buff, buff.size, 1, callback)
- assert_equal("1349", buff)
-
- bug4929 = '[ruby-core:37395]'
- buff = +"9341"
- under_gc_stress do
- qsort.call(buff, buff.size, 1, callback)
- end
- assert_equal("1349", buff, bug4929)
-
- # Ensure the test didn't mutate String literals
- assert_equal("93" + "41", untouched)
- end
- ensure
- # We can't use ObjectSpace with JRuby.
- unless RUBY_ENGINE == "jruby"
- # Ensure freeing all closures.
- # See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
- not_freed_closures = []
- ObjectSpace.each_object(Fiddle::Closure) do |closure|
- not_freed_closures << closure unless closure.freed?
- end
- assert_equal([], not_freed_closures)
- end
- end
-
- def test_snprintf
- unless Fiddle.const_defined?("TYPE_VARIADIC")
- omit "libffi doesn't support variadic arguments"
- end
- if Fiddle::WINDOWS
- snprintf_name = "_snprintf"
- else
- snprintf_name = "snprintf"
- end
- begin
- snprintf_pointer = @libc[snprintf_name]
- rescue Fiddle::DLError
- omit "Can't find #{snprintf_name}: #{$!.message}"
- end
- snprintf = Function.new(snprintf_pointer,
- [
- :voidp,
- :size_t,
- :const_string,
- :variadic,
- ],
- :int)
- Pointer.malloc(1024, Fiddle::RUBY_FREE) do |output|
- written = snprintf.call(output,
- output.size,
- "int: %d, string: %.*s, const string: %s\n",
- :int, -29,
- :int, 4,
- :voidp, "Hello",
- :const_string, "World")
- assert_equal("int: -29, string: Hell, const string: World\n",
- output[0, written])
-
- string_like_class = Class.new do
- def initialize(string)
- @string = string
- end
-
- def to_str
- @string
- end
- end
- written = snprintf.call(output,
- output.size,
- "string: %.*s, const string: %s, uint: %u\n",
- :int, 2,
- :voidp, "Hello",
- :const_string, string_like_class.new("World"),
- :int, 29)
- assert_equal("string: He, const string: World, uint: 29\n",
- output[0, written])
- end
- end
-
- def test_rb_memory_view_available_p
- omit "MemoryView is unavailable" unless defined? Fiddle::MemoryView
- libruby = Fiddle.dlopen(nil)
- case Fiddle::SIZEOF_VOIDP
- when Fiddle::SIZEOF_LONG_LONG
- value_type = -Fiddle::TYPE_LONG_LONG
- else
- value_type = -Fiddle::TYPE_LONG
- end
- rb_memory_view_available_p =
- Function.new(libruby["rb_memory_view_available_p"],
- [value_type],
- :bool,
- need_gvl: true)
- assert_equal(false, rb_memory_view_available_p.call(Fiddle::Qnil))
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_function.rb b/test/fiddle/test_function.rb
deleted file mode 100644
index b408a14ccd..0000000000
--- a/test/fiddle/test_function.rb
+++ /dev/null
@@ -1,310 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
-rescue LoadError
-end
-
-module Fiddle
- class TestFunction < Fiddle::TestCase
- def setup
- super
- Fiddle.last_error = nil
- if WINDOWS
- Fiddle.win32_last_error = nil
- Fiddle.win32_last_socket_error = nil
- end
- end
-
- def teardown
- # We can't use ObjectSpace with JRuby.
- return if RUBY_ENGINE == "jruby"
- # Ensure freeing all closures.
- # See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
- not_freed_closures = []
- ObjectSpace.each_object(Fiddle::Closure) do |closure|
- not_freed_closures << closure unless closure.freed?
- end
- assert_equal([], not_freed_closures)
- end
-
- def test_default_abi
- func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
- assert_equal Function::DEFAULT, func.abi
- end
-
- def test_name
- func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE, name: 'sin')
- assert_equal 'sin', func.name
- end
-
- def test_name_symbol
- func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE, name: :sin)
- assert_equal :sin, func.name
- end
-
- def test_need_gvl?
- if RUBY_ENGINE == "jruby"
- omit("rb_str_dup() doesn't exist in JRuby")
- end
- if RUBY_ENGINE == "truffleruby"
- omit("rb_str_dup() doesn't work with TruffleRuby")
- end
-
- libruby = Fiddle.dlopen(nil)
- rb_str_dup = Function.new(libruby['rb_str_dup'],
- [:voidp],
- :voidp,
- need_gvl: true)
- assert(rb_str_dup.need_gvl?)
- assert_equal('Hello',
- Fiddle.dlunwrap(rb_str_dup.call(Fiddle.dlwrap('Hello'))))
- end
-
- def test_argument_errors
- assert_raise(TypeError) do
- Function.new(@libm['sin'], TYPE_DOUBLE, TYPE_DOUBLE)
- end
-
- assert_raise(TypeError) do
- Function.new(@libm['sin'], ['foo'], TYPE_DOUBLE)
- end
-
- assert_raise(TypeError) do
- Function.new(@libm['sin'], [TYPE_DOUBLE], 'foo')
- end
- end
-
- def test_argument_type_conversion
- type = Struct.new(:int, :call_count) do
- def initialize(int)
- super(int, 0)
- end
- def to_int
- raise "exhausted" if (self.call_count += 1) > 1
- self.int
- end
- end
- type_arg = type.new(TYPE_DOUBLE)
- type_result = type.new(TYPE_DOUBLE)
- assert_nothing_raised(RuntimeError) do
- Function.new(@libm['sin'], [type_arg], type_result)
- end
- assert_equal(1, type_arg.call_count)
- assert_equal(1, type_result.call_count)
- end
-
- def test_call
- func = Function.new(@libm['sin'], [TYPE_DOUBLE], TYPE_DOUBLE)
- assert_in_delta 1.0, func.call(90 * Math::PI / 180), 0.0001
- end
-
- def test_integer_pointer_conversion
- func = Function.new(@libc['memcpy'], [TYPE_VOIDP, TYPE_VOIDP, TYPE_SIZE_T], TYPE_VOIDP)
- str = 'hello'
- Pointer.malloc(str.bytesize, Fiddle::RUBY_FREE) do |dst|
- func.call(dst.to_i, str, dst.size)
- assert_equal(str, dst.to_str)
- end
- end
-
- def test_argument_count
- closure_class = Class.new(Closure) do
- def call one
- 10 + one
- end
- end
- closure_class.create(TYPE_INT, [TYPE_INT]) do |closure|
- func = Function.new(closure, [TYPE_INT], TYPE_INT)
-
- assert_raise(ArgumentError) do
- func.call(1,2,3)
- end
- assert_raise(ArgumentError) do
- func.call
- end
- end
- end
-
- def test_last_error
- if ffi_backend?
- omit("Fiddle.last_error doesn't work with FFI backend")
- end
-
- func = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
-
- assert_nil Fiddle.last_error
- func.call(+"000", "123")
- refute_nil Fiddle.last_error
- end
-
- if WINDOWS
- def test_win32_last_error
- kernel32 = Fiddle.dlopen("kernel32")
- args = [kernel32["SetLastError"], [-TYPE_LONG], TYPE_VOID]
- args << Function::STDCALL if Function.const_defined?(:STDCALL)
- set_last_error = Function.new(*args)
- assert_nil(Fiddle.win32_last_error)
- n = 1 << 29 | 1
- set_last_error.call(n)
- assert_equal(n, Fiddle.win32_last_error)
- end
-
- def test_win32_last_socket_error
- ws2_32 = Fiddle.dlopen("ws2_32")
- args = [ws2_32["WSASetLastError"], [TYPE_INT], TYPE_VOID]
- args << Function::STDCALL if Function.const_defined?(:STDCALL)
- wsa_set_last_error = Function.new(*args)
- assert_nil(Fiddle.win32_last_socket_error)
- n = 1 << 29 | 1
- wsa_set_last_error.call(n)
- assert_equal(n, Fiddle.win32_last_socket_error)
- end
- end
-
- def test_strcpy
- if RUBY_ENGINE == "jruby"
- omit("Function that returns string doesn't work with JRuby")
- end
-
- f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
- buff = +"000"
- str = f.call(buff, "123")
- assert_equal("123", buff)
- assert_equal("123", str.to_s)
- end
-
- def call_proc(string_to_copy)
- buff = +"000"
- str = yield(buff, string_to_copy)
- [buff, str]
- end
-
- def test_function_as_proc
- if RUBY_ENGINE == "jruby"
- omit("Function that returns string doesn't work with JRuby")
- end
-
- f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
- buff, str = call_proc("123", &f)
- assert_equal("123", buff)
- assert_equal("123", str.to_s)
- end
-
- def test_function_as_method
- if RUBY_ENGINE == "jruby"
- omit("Function that returns string doesn't work with JRuby")
- end
-
- f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
- klass = Class.new do
- define_singleton_method(:strcpy, &f)
- end
- buff = +"000"
- str = klass.strcpy(buff, "123")
- assert_equal("123", buff)
- assert_equal("123", str.to_s)
- end
-
- def test_nogvl_poll
- require "envutil" unless defined?(EnvUtil)
-
- # XXX hack to quiet down CI errors on EINTR from r64353
- # [ruby-core:88360] [Misc #14937]
- # Making pipes (and sockets) non-blocking by default would allow
- # us to get rid of POSIX timers / timer pthread
- # https://bugs.ruby-lang.org/issues/14968
- IO.pipe { |r,w| IO.select([r], [w]) }
- begin
- poll = @libc['poll']
- rescue Fiddle::DLError
- omit 'poll(2) not available'
- end
- f = Function.new(poll, [TYPE_VOIDP, TYPE_INT, TYPE_INT], TYPE_INT)
-
- msec = EnvUtil.apply_timeout_scale(1000)
- t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
- th = Thread.new { f.call(nil, 0, msec) }
- n1 = f.call(nil, 0, msec)
- n2 = th.value
- t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
- assert_in_delta(msec, t1 - t0, EnvUtil.apply_timeout_scale(500), 'slept amount of time')
- assert_equal(0, n1, perror("poll(2) in main-thread"))
- assert_equal(0, n2, perror("poll(2) in sub-thread"))
- end
-
- def test_no_memory_leak
- if RUBY_ENGINE == "jruby"
- omit("rb_obj_frozen_p() doesn't exist in JRuby")
- end
- if RUBY_ENGINE == "truffleruby"
- omit("memory leak detection is fragile with TruffleRuby")
- end
-
- if respond_to?(:assert_nothing_leaked_memory)
- rb_obj_frozen_p_symbol = Fiddle.dlopen(nil)["rb_obj_frozen_p"]
- rb_obj_frozen_p = Fiddle::Function.new(rb_obj_frozen_p_symbol,
- [Fiddle::TYPE_UINTPTR_T],
- Fiddle::TYPE_UINTPTR_T)
- a = "a"
- n_tries = 100_000
- n_tries.times do
- begin
- a + 1
- rescue TypeError
- end
- end
- n_arguments = 1
- sizeof_fiddle_generic = Fiddle::SIZEOF_VOIDP # Rough
- size_per_try =
- (sizeof_fiddle_generic * n_arguments) +
- (Fiddle::SIZEOF_VOIDP * (n_arguments + 1))
- assert_nothing_leaked_memory(size_per_try * n_tries) do
- n_tries.times do
- begin
- rb_obj_frozen_p.call(a)
- rescue TypeError
- end
- end
- end
- else
- prep = 'r = Fiddle::Function.new(Fiddle.dlopen(nil)["rb_obj_frozen_p"], [Fiddle::TYPE_UINTPTR_T], Fiddle::TYPE_UINTPTR_T); a = "a"'
- code = 'begin r.call(a); rescue TypeError; end'
- assert_no_memory_leak(%w[-W0 -rfiddle], "#{prep}\n1000.times{#{code}}", "10_000.times {#{code}}", limit: 1.2)
- end
- end
-
- def test_ractor_shareable
- omit("Need Ractor") unless defined?(Ractor)
- assert_ractor_shareable(Function.new(@libm["sin"],
- [TYPE_DOUBLE],
- TYPE_DOUBLE))
- end
-
- def test_ractor_shareable_name
- omit("Need Ractor") unless defined?(Ractor)
- assert_ractor_shareable(Function.new(@libm["sin"],
- [TYPE_DOUBLE],
- TYPE_DOUBLE,
- name: "sin"))
- end
-
- def test_ractor_shareable_name_symbol
- omit("Need Ractor") unless defined?(Ractor)
- assert_ractor_shareable(Function.new(@libm["sin"],
- [TYPE_DOUBLE],
- TYPE_DOUBLE,
- name: :sin))
- end
-
- private
-
- def perror(m)
- proc do
- if e = Fiddle.last_error
- m = "#{m}: #{SystemCallError.new(e).message}"
- end
- m
- end
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_handle.rb b/test/fiddle/test_handle.rb
deleted file mode 100644
index ad8c45a00a..0000000000
--- a/test/fiddle/test_handle.rb
+++ /dev/null
@@ -1,244 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
-rescue LoadError
-end
-
-module Fiddle
- class TestHandle < TestCase
- include Fiddle
-
- def test_library_unavailable
- assert_raise(DLError) do
- Fiddle::Handle.new("does-not-exist-library")
- end
- assert_raise(DLError) do
- Fiddle::Handle.new("/does/not/exist/library.#{RbConfig::CONFIG['SOEXT']}")
- end
- end
-
- def test_to_i
- if ffi_backend?
- omit("Fiddle::Handle#to_i is unavailable with FFI backend")
- end
-
- handle = Fiddle::Handle.new(LIBC_SO)
- assert_kind_of Integer, handle.to_i
- end
-
- def test_to_ptr
- if ffi_backend?
- omit("Fiddle::Handle#to_i is unavailable with FFI backend")
- end
-
- handle = Fiddle::Handle.new(LIBC_SO)
- ptr = handle.to_ptr
- assert_equal ptr.to_i, handle.to_i
- end
-
- def test_static_sym_unknown
- assert_raise(DLError) { Fiddle::Handle.sym('fooo') }
- assert_raise(DLError) { Fiddle::Handle['fooo'] }
- refute Fiddle::Handle.sym_defined?('fooo')
- end
-
- def test_static_sym
- if ffi_backend?
- omit("We can't assume static symbols with FFI backend")
- end
-
- begin
- # Linux / Darwin / FreeBSD
- refute_nil Fiddle::Handle.sym('dlopen')
- assert Fiddle::Handle.sym_defined?('dlopen')
- assert_equal Fiddle::Handle.sym('dlopen'), Fiddle::Handle['dlopen']
- return
- rescue
- end
-
- begin
- # NetBSD
- require '-test-/dln/empty'
- refute_nil Fiddle::Handle.sym('Init_empty')
- assert_equal Fiddle::Handle.sym('Init_empty'), Fiddle::Handle['Init_empty']
- return
- rescue
- end
- end unless /mswin|mingw/ =~ RUBY_PLATFORM
-
- def test_sym_closed_handle
- handle = Fiddle::Handle.new(LIBC_SO)
- handle.close
- assert_raise(DLError) { handle.sym("calloc") }
- assert_raise(DLError) { handle["calloc"] }
- end
-
- def test_sym_unknown
- handle = Fiddle::Handle.new(LIBC_SO)
- assert_raise(DLError) { handle.sym('fooo') }
- assert_raise(DLError) { handle['fooo'] }
- refute handle.sym_defined?('fooo')
- end
-
- def test_sym_with_bad_args
- handle = Handle.new(LIBC_SO)
- assert_raise(TypeError) { handle.sym(nil) }
- assert_raise(TypeError) { handle[nil] }
- end
-
- def test_sym
- handle = Handle.new(LIBC_SO)
- refute_nil handle.sym('calloc')
- refute_nil handle['calloc']
- assert handle.sym_defined?('calloc')
- end
-
- def test_handle_close
- handle = Handle.new(LIBC_SO)
- assert_equal 0, handle.close
- end
-
- def test_handle_close_twice
- handle = Handle.new(LIBC_SO)
- handle.close
- assert_raise(DLError) do
- handle.close
- end
- end
-
- def test_dlopen_returns_handle
- assert_instance_of Handle, dlopen(LIBC_SO)
- end
-
- def test_initialize_noargs
- if RUBY_ENGINE == "jruby"
- omit("rb_str_new() doesn't exist in JRuby")
- end
-
- handle = Handle.new
- refute_nil handle['rb_str_new']
- end
-
- def test_initialize_flags
- handle = Handle.new(LIBC_SO, RTLD_LAZY | RTLD_GLOBAL)
- refute_nil handle['calloc']
- end
-
- def test_enable_close
- handle = Handle.new(LIBC_SO)
- assert !handle.close_enabled?, 'close is enabled'
-
- handle.enable_close
- assert handle.close_enabled?, 'close is not enabled'
- end
-
- def test_disable_close
- handle = Handle.new(LIBC_SO)
-
- handle.enable_close
- assert handle.close_enabled?, 'close is enabled'
- handle.disable_close
- assert !handle.close_enabled?, 'close is enabled'
- end
-
- def test_file_name
- if ffi_backend?
- omit("Fiddle::Handle#file_name doesn't exist in FFI backend")
- end
-
- file_name = Handle.new(LIBC_SO).file_name
- if file_name
- assert_kind_of String, file_name
- expected = [File.basename(LIBC_SO)]
- begin
- expected << File.basename(File.realpath(LIBC_SO, File.dirname(file_name)))
- rescue Errno::ENOENT
- end
- basename = File.basename(file_name)
- unless File::FNM_SYSCASE.zero?
- basename.downcase!
- expected.each(&:downcase!)
- end
- assert_include expected, basename
- end
- end
-
- def test_NEXT
- if ffi_backend?
- omit("Fiddle::Handle::NEXT doesn't exist in FFI backend")
- end
-
- begin
- # Linux / Darwin
- #
- # There are two special pseudo-handles, RTLD_DEFAULT and RTLD_NEXT. The former will find
- # the first occurrence of the desired symbol using the default library search order. The
- # latter will find the next occurrence of a function in the search order after the current
- # library. This allows one to provide a wrapper around a function in another shared
- # library.
- # --- Ubuntu Linux 8.04 dlsym(3)
- handle = Handle::NEXT
- refute_nil handle['malloc']
- return
- rescue
- end
-
- begin
- # BSD
- #
- # If dlsym() is called with the special handle RTLD_NEXT, then the search
- # for the symbol is limited to the shared objects which were loaded after
- # the one issuing the call to dlsym(). Thus, if the function is called
- # from the main program, all the shared libraries are searched. If it is
- # called from a shared library, all subsequent shared libraries are
- # searched. RTLD_NEXT is useful for implementing wrappers around library
- # functions. For example, a wrapper function getpid() could access the
- # "real" getpid() with dlsym(RTLD_NEXT, "getpid"). (Actually, the dlfunc()
- # interface, below, should be used, since getpid() is a function and not a
- # data object.)
- # --- FreeBSD 8.0 dlsym(3)
- require '-test-/dln/empty'
- handle = Handle::NEXT
- refute_nil handle['Init_empty']
- return
- rescue
- end
- end unless /mswin|mingw/ =~ RUBY_PLATFORM
-
- def test_DEFAULT
- if Fiddle::WINDOWS
- omit("Fiddle::Handle::DEFAULT doesn't have malloc() on Windows")
- end
-
- handle = Handle::DEFAULT
- refute_nil handle['malloc']
- end
-
- def test_dlerror
- # FreeBSD (at least 7.2 to 7.2) calls nsdispatch(3) when it calls
- # getaddrinfo(3). And nsdispatch(3) doesn't call dlerror(3) even if
- # it calls _nss_cache_cycle_prevention_function with dlsym(3).
- # So our Fiddle::Handle#sym must call dlerror(3) before call dlsym.
- # In general uses of dlerror(3) should call it before use it.
- verbose, $VERBOSE = $VERBOSE, nil
- require 'socket'
- Socket.gethostbyname("localhost")
- Fiddle.dlopen("/lib/libc.so.7").sym('strcpy')
- ensure
- $VERBOSE = verbose
- end if /freebsd/=~ RUBY_PLATFORM
-
- if /cygwin|mingw|mswin/ =~ RUBY_PLATFORM
- def test_fallback_to_ansi
- k = Fiddle::Handle.new("kernel32.dll")
- ansi = k["GetFileAttributesA"]
- assert_equal(ansi, k["GetFileAttributes"], "should fallback to ANSI version")
- end
- end
-
- def test_ractor_shareable
- omit("Need Ractor") unless defined?(Ractor)
- assert_ractor_shareable(Fiddle::Handle.new(LIBC_SO))
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb
deleted file mode 100644
index a6a3b66655..0000000000
--- a/test/fiddle/test_import.rb
+++ /dev/null
@@ -1,499 +0,0 @@
-# coding: US-ASCII
-# frozen_string_literal: true
-begin
- require_relative 'helper'
- require 'fiddle/import'
-rescue LoadError
-end
-
-module Fiddle
- module LIBC
- extend Importer
- dlload LIBC_SO, LIBM_SO
-
- typealias 'string', 'char*'
- typealias 'FILE*', 'void*'
-
- extern "void *strcpy(char*, char*)"
- extern "int isdigit(int)"
- extern "double atof(string)"
- extern "unsigned long strtoul(char*, char **, int)"
- extern "int qsort(void*, unsigned long, unsigned long, void*)"
- extern "int fprintf(FILE*, char*)" rescue nil
- extern "int gettimeofday(timeval*, timezone*)" rescue nil
-
- Timeval = struct [
- "long tv_sec",
- "long tv_usec",
- ]
- Timezone = struct [
- "int tz_minuteswest",
- "int tz_dsttime",
- ]
- MyStruct = struct [
- "short num[5]",
- "char c",
- "unsigned char buff[7]",
- ]
- StructNestedStruct = struct [
- {
- "vertices[2]" => {
- position: ["float x", "float y", "float z"],
- texcoord: ["float u", "float v"]
- },
- object: ["int id", "void *user_data"],
- },
- "int id"
- ]
- UnionNestedStruct = union [
- {
- keyboard: [
- 'unsigned int state',
- 'char key'
- ],
- mouse: [
- 'unsigned int button',
- 'unsigned short x',
- 'unsigned short y'
- ]
- }
- ]
- end
-
- class TestImport < TestCase
- def test_ensure_call_dlload
- err = assert_raise(RuntimeError) do
- Class.new do
- extend Importer
- extern "void *strcpy(char*, char*)"
- end
- end
- assert_match(/call dlload before/, err.message)
- end
-
- def test_struct_memory_access()
- # check memory operations performed directly on struct
- Fiddle::Importer.struct(['int id']).malloc(Fiddle::RUBY_FREE) do |my_struct|
- my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
- assert_equal 0x01010101, my_struct.id
-
- my_struct.id = 0
- assert_equal "\x00".b * Fiddle::SIZEOF_INT, my_struct[0, Fiddle::SIZEOF_INT]
- end
- end
-
- def test_struct_ptr_array_subscript_multiarg()
- # check memory operations performed on struct#to_ptr
- Fiddle::Importer.struct([ 'int x' ]).malloc(Fiddle::RUBY_FREE) do |struct|
- ptr = struct.to_ptr
-
- struct.x = 0x02020202
- assert_equal("\x02".b * Fiddle::SIZEOF_INT, ptr[0, Fiddle::SIZEOF_INT])
-
- ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
- assert_equal 0x01010101, struct.x
- end
- end
-
- def test_malloc()
- LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s1|
- LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s2|
- refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i)
- end
- end
- end
-
- def test_sizeof()
- assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*"))
- assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct))
- LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |my_struct|
- assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(my_struct))
- end
- assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG)
- assert_equal(LIBC::StructNestedStruct.size(), LIBC.sizeof(LIBC::StructNestedStruct))
- end
-
- Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(.*)/) do
- type = $&
- const_type_name = $1
- size = Fiddle.const_get("SIZEOF_#{const_type_name}")
- if const_type_name == "CONST_STRING"
- name = "const_string"
- type_name = "const char*"
- else
- name = $1.sub(/P\z/,"*").gsub(/_(?!T\z)/, " ").downcase
- type_name = name
- end
- type_name = "unsigned #{$1}" if type_name =~ /\Au(long|short|char|int|long long)\z/
-
- define_method("test_sizeof_#{name}") do
- assert_equal(size, Fiddle::Importer.sizeof(type_name), type)
- end
- end
-
- # Assert that the unsigned constants are equal to the "negative" signed ones
- # for backwards compatibility
- def test_unsigned_equals_negative_signed
- Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(U.*)/) do |unsigned|
- assert_equal(-Fiddle.const_get(unsigned.to_s.sub(/U/, '')),
- Fiddle.const_get(unsigned))
- end
- end
-
- def test_type_constants
- Fiddle::Types.constants.each do |const|
- assert_equal Fiddle::Types.const_get(const), Fiddle.const_get("TYPE_#{const}")
- end
- end
-
- def test_unsigned_result()
- d = (2 ** 31) + 1
-
- r = LIBC.strtoul(d.to_s, nil, 0)
- assert_equal(d, r)
- end
-
- def test_io()
- if ffi_backend?
- omit("BUILD_RUBY_PLATFORM doesn't exist in FFI backend")
- end
-
- if( RUBY_PLATFORM != BUILD_RUBY_PLATFORM ) || !defined?(LIBC.fprintf)
- return
- end
- io_in,io_out = IO.pipe()
- LIBC.fprintf(io_out, "hello")
- io_out.flush()
- io_out.close()
- str = io_in.read()
- io_in.close()
- assert_equal("hello", str)
- end
-
- def test_value()
- i = LIBC.value('int', 2)
- assert_equal(2, i.value)
-
- d = LIBC.value('double', 2.0)
- assert_equal(2.0, d.value)
-
- ary = LIBC.value('int[3]', [0,1,2])
- assert_equal([0,1,2], ary.value)
- end
-
- def test_struct_array_assignment()
- Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc(Fiddle::RUBY_FREE) do |instance|
- instance.stages[0] = 1024
- instance.stages[1] = 10
- instance.stages[2] = 100
- assert_equal 1024, instance.stages[0]
- assert_equal 10, instance.stages[1]
- assert_equal 100, instance.stages[2]
- assert_equal [1024, 10, 100].pack(Fiddle::PackInfo::PACK_MAP[-Fiddle::TYPE_INT] * 3),
- instance.to_ptr[0, 3 * Fiddle::SIZEOF_INT]
- assert_raise(IndexError) { instance.stages[-1] = 5 }
- assert_raise(IndexError) { instance.stages[3] = 5 }
- end
- end
-
- def test_nested_struct_reusing_other_structs()
- position_struct = Fiddle::Importer.struct(['float x', 'float y', 'float z'])
- texcoord_struct = Fiddle::Importer.struct(['float u', 'float v'])
- vertex_struct = Fiddle::Importer.struct(position: position_struct, texcoord: texcoord_struct)
- mesh_struct = Fiddle::Importer.struct([
- {
- "vertices[2]" => vertex_struct,
- object: [
- "int id",
- "void *user_data",
- ],
- },
- "int id",
- ])
- assert_equal LIBC::StructNestedStruct.size, mesh_struct.size
-
-
- keyboard_event_struct = Fiddle::Importer.struct(['unsigned int state', 'char key'])
- mouse_event_struct = Fiddle::Importer.struct(['unsigned int button', 'unsigned short x', 'unsigned short y'])
- event_union = Fiddle::Importer.union([{ keyboard: keyboard_event_struct, mouse: mouse_event_struct}])
- assert_equal LIBC::UnionNestedStruct.size, event_union.size
- end
-
- def test_nested_struct_alignment_is_not_its_size()
- inner = Fiddle::Importer.struct(['int x', 'int y', 'int z', 'int w'])
- outer = Fiddle::Importer.struct(['char a', { 'nested' => inner }, 'char b'])
- outer.malloc(Fiddle::RUBY_FREE) do |instance|
- offset = instance.to_ptr.instance_variable_get(:"@offset")
- assert_equal Fiddle::SIZEOF_INT * 5, offset.last
- assert_equal Fiddle::SIZEOF_INT * 6, outer.size
- assert_equal instance.to_ptr.size, outer.size
- end
- end
-
- def test_struct_nested_struct_members()
- LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
- Fiddle::Pointer.malloc(24, Fiddle::RUBY_FREE) do |user_data|
- s.vertices[0].position.x = 1
- s.vertices[0].position.y = 2
- s.vertices[0].position.z = 3
- s.vertices[0].texcoord.u = 4
- s.vertices[0].texcoord.v = 5
- s.vertices[1].position.x = 6
- s.vertices[1].position.y = 7
- s.vertices[1].position.z = 8
- s.vertices[1].texcoord.u = 9
- s.vertices[1].texcoord.v = 10
- s.object.id = 100
- s.object.user_data = user_data
- s.id = 101
- assert_equal({
- "vertices" => [
- {
- "position" => {
- "x" => 1,
- "y" => 2,
- "z" => 3,
- },
- "texcoord" => {
- "u" => 4,
- "v" => 5,
- },
- },
- {
- "position" => {
- "x" => 6,
- "y" => 7,
- "z" => 8,
- },
- "texcoord" => {
- "u" => 9,
- "v" => 10,
- },
- },
- ],
- "object" => {
- "id" => 100,
- "user_data" => user_data,
- },
- "id" => 101,
- },
- s.to_h)
- end
- end
- end
-
- def test_union_nested_struct_members()
- LIBC::UnionNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
- s.keyboard.state = 100
- s.keyboard.key = 101
- assert_equal(100, s.mouse.button)
- refute_equal( 0, s.mouse.x)
- end
- end
-
- def test_struct_nested_struct_replace_array_element()
- LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
- s.vertices[0].position.x = 5
-
- vertex_struct = Fiddle::Importer.struct [{
- position: ["float x", "float y", "float z"],
- texcoord: ["float u", "float v"]
- }]
- vertex_struct.malloc(Fiddle::RUBY_FREE) do |vertex|
- vertex.position.x = 100
- s.vertices[0] = vertex
-
- # make sure element was copied by value, but things like memory address
- # should not be changed
- assert_equal(100, s.vertices[0].position.x)
- refute_equal(vertex.object_id, s.vertices[0].object_id)
- refute_equal(vertex.to_ptr, s.vertices[0].to_ptr)
- end
- end
- end
-
- def test_struct_nested_struct_replace_array_element_nil()
- LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
- s.vertices[0].position.x = 5
- s.vertices[0] = nil
- assert_equal({
- "position" => {
- "x" => 0.0,
- "y" => 0.0,
- "z" => 0.0,
- },
- "texcoord" => {
- "u" => 0.0,
- "v" => 0.0,
- },
- },
- s.vertices[0].to_h)
- end
- end
-
- def test_struct_nested_struct_replace_array_element_hash()
- LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
- s.vertices[0] = nil
- s.vertices[0] = {
- position: {
- x: 10,
- y: 100,
- },
- }
- assert_equal({
- "position" => {
- "x" => 10.0,
- "y" => 100.0,
- "z" => 0.0,
- },
- "texcoord" => {
- "u" => 0.0,
- "v" => 0.0,
- },
- },
- s.vertices[0].to_h)
- end
- end
-
- def test_struct_nested_struct_replace_entire_array()
- LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
- vertex_struct = Fiddle::Importer.struct [{
- position: ["float x", "float y", "float z"],
- texcoord: ["float u", "float v"]
- }]
-
- vertex_struct.malloc(Fiddle::RUBY_FREE) do |same0|
- vertex_struct.malloc(Fiddle::RUBY_FREE) do |same1|
- same = [same0, same1]
- same[0].position.x = 1; same[1].position.x = 6
- same[0].position.y = 2; same[1].position.y = 7
- same[0].position.z = 3; same[1].position.z = 8
- same[0].texcoord.u = 4; same[1].texcoord.u = 9
- same[0].texcoord.v = 5; same[1].texcoord.v = 10
- s.vertices = same
- assert_equal([
- {
- "position" => {
- "x" => 1.0,
- "y" => 2.0,
- "z" => 3.0,
- },
- "texcoord" => {
- "u" => 4.0,
- "v" => 5.0,
- },
- },
- {
- "position" => {
- "x" => 6.0,
- "y" => 7.0,
- "z" => 8.0,
- },
- "texcoord" => {
- "u" => 9.0,
- "v" => 10.0,
- },
- }
- ],
- s.vertices.collect(&:to_h))
- end
- end
- end
- end
-
- def test_struct_nested_struct_replace_entire_array_with_different_struct()
- LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
- different_struct_same_size = Fiddle::Importer.struct [{
- a: ['float i', 'float j', 'float k'],
- b: ['float l', 'float m']
- }]
-
- different_struct_same_size.malloc(Fiddle::RUBY_FREE) do |different0|
- different_struct_same_size.malloc(Fiddle::RUBY_FREE) do |different1|
- different = [different0, different1]
- different[0].a.i = 11; different[1].a.i = 16
- different[0].a.j = 12; different[1].a.j = 17
- different[0].a.k = 13; different[1].a.k = 18
- different[0].b.l = 14; different[1].b.l = 19
- different[0].b.m = 15; different[1].b.m = 20
- s.vertices[0][0, s.vertices[0].class.size] = different[0].to_ptr
- s.vertices[1][0, s.vertices[1].class.size] = different[1].to_ptr
- assert_equal([
- {
- "position" => {
- "x" => 11.0,
- "y" => 12.0,
- "z" => 13.0,
- },
- "texcoord" => {
- "u" => 14.0,
- "v" => 15.0,
- },
- },
- {
- "position" => {
- "x" => 16.0,
- "y" => 17.0,
- "z" => 18.0,
- },
- "texcoord" => {
- "u" => 19.0,
- "v" => 20.0,
- },
- }
- ],
- s.vertices.collect(&:to_h))
- end
- end
- end
- end
-
- def test_struct()
- LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |s|
- s.num = [0,1,2,3,4]
- s.c = ?a.ord
- s.buff = "012345\377"
- assert_equal([0,1,2,3,4], s.num)
- assert_equal(?a.ord, s.c)
- assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,"\xFF".ord], s.buff)
- end
- end
-
- def test_gettimeofday()
- if( defined?(LIBC.gettimeofday) )
- LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |timeval|
- LIBC::Timezone.malloc(Fiddle::RUBY_FREE) do |timezone|
- LIBC.gettimeofday(timeval, timezone)
- end
- cur = Time.now()
- assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i)
- end
- end
- end
-
- def test_strcpy()
- if RUBY_ENGINE == "jruby"
- omit("Function that returns string doesn't work with JRuby")
- end
-
- buff = +"000"
- str = LIBC.strcpy(buff, "123")
- assert_equal("123", buff)
- assert_equal("123", str.to_s)
- end
-
- def test_isdigit
- r1 = LIBC.isdigit(?1.ord)
- r2 = LIBC.isdigit(?2.ord)
- rr = LIBC.isdigit(?r.ord)
- assert_operator(r1, :>, 0)
- assert_operator(r2, :>, 0)
- assert_equal(0, rr)
- end
-
- def test_atof
- r = LIBC.atof("12.34")
- assert_includes(12.00..13.00, r)
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fiddle/test_memory_view.rb b/test/fiddle/test_memory_view.rb
deleted file mode 100644
index da00d66c91..0000000000
--- a/test/fiddle/test_memory_view.rb
+++ /dev/null
@@ -1,175 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
-rescue LoadError
- return
-end
-
-begin
- require '-test-/memory_view'
-rescue LoadError
- return
-end
-
-module Fiddle
- class TestMemoryView < TestCase
- def setup
- omit "MemoryView is unavailable" unless defined? Fiddle::MemoryView
- end
-
- def test_null_ptr
- assert_raise(ArgumentError) do
- MemoryView.new(Fiddle::NULL)
- end
- end
-
- def test_memory_view_from_unsupported_obj
- obj = Object.new
- assert_raise(ArgumentError) do
- MemoryView.new(obj)
- end
- end
-
- def test_memory_view_from_pointer
- str = Marshal.load(Marshal.dump("hello world"))
- ptr = Pointer[str]
- mview = MemoryView.new(ptr)
- begin
- assert_same(ptr, mview.obj)
- assert_equal(str.bytesize, mview.byte_size)
- assert_equal(true, mview.readonly?)
- assert_equal(nil, mview.format)
- assert_equal(1, mview.item_size)
- assert_equal(1, mview.ndim)
- assert_equal(nil, mview.shape)
- assert_equal(nil, mview.strides)
- assert_equal(nil, mview.sub_offsets)
-
- codes = str.codepoints
- assert_equal(codes, (0...str.bytesize).map {|i| mview[i] })
- ensure
- mview.release
- end
- end
-
- def test_memory_view_multi_dimensional
- omit "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
-
- buf = [ 1, 2, 3, 4,
- 5, 6, 7, 8,
- 9, 10, 11, 12 ].pack("l!*")
- shape = [3, 4]
- md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, nil)
- mview = Fiddle::MemoryView.new(md)
- begin
- assert_equal(buf.bytesize, mview.byte_size)
- assert_equal("l!", mview.format)
- assert_equal(Fiddle::SIZEOF_LONG, mview.item_size)
- assert_equal(2, mview.ndim)
- assert_equal(shape, mview.shape)
- assert_equal([Fiddle::SIZEOF_LONG*4, Fiddle::SIZEOF_LONG], mview.strides)
- assert_equal(nil, mview.sub_offsets)
- assert_equal(1, mview[0, 0])
- assert_equal(4, mview[0, 3])
- assert_equal(6, mview[1, 1])
- assert_equal(10, mview[2, 1])
- ensure
- mview.release
- end
- end
-
- def test_memory_view_multi_dimensional_with_strides
- omit "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
-
- buf = [ 1, 2, 3, 4, 5, 6, 7, 8,
- 9, 10, 11, 12, 13, 14, 15, 16 ].pack("l!*")
- shape = [2, 8]
- strides = [4*Fiddle::SIZEOF_LONG*2, Fiddle::SIZEOF_LONG*2]
- md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, strides)
- mview = Fiddle::MemoryView.new(md)
- begin
- assert_equal("l!", mview.format)
- assert_equal(Fiddle::SIZEOF_LONG, mview.item_size)
- assert_equal(buf.bytesize, mview.byte_size)
- assert_equal(2, mview.ndim)
- assert_equal(shape, mview.shape)
- assert_equal(strides, mview.strides)
- assert_equal(nil, mview.sub_offsets)
- assert_equal(1, mview[0, 0])
- assert_equal(5, mview[0, 2])
- assert_equal(9, mview[1, 0])
- assert_equal(15, mview[1, 3])
- ensure
- mview.release
- end
- end
-
- def test_memory_view_multi_dimensional_with_multiple_members
- omit "MemoryViewTestUtils is unavailable" unless defined? MemoryViewTestUtils
-
- buf = [ 1, 2, 3, 4, 5, 6, 7, 8,
- -1, -2, -3, -4, -5, -6, -7, -8].pack("s*")
- shape = [2, 4]
- strides = [4*Fiddle::SIZEOF_SHORT*2, Fiddle::SIZEOF_SHORT*2]
- md = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides)
- mview = Fiddle::MemoryView.new(md)
- begin
- assert_equal("ss", mview.format)
- assert_equal(Fiddle::SIZEOF_SHORT*2, mview.item_size)
- assert_equal(buf.bytesize, mview.byte_size)
- assert_equal(2, mview.ndim)
- assert_equal(shape, mview.shape)
- assert_equal(strides, mview.strides)
- assert_equal(nil, mview.sub_offsets)
- assert_equal([1, 2], mview[0, 0])
- assert_equal([5, 6], mview[0, 2])
- assert_equal([-1, -2], mview[1, 0])
- assert_equal([-7, -8], mview[1, 3])
- ensure
- mview.release
- end
- end
-
- def test_export
- str = "hello world"
- mview_str = MemoryView.export(Pointer[str]) do |mview|
- mview.to_s
- end
- assert_equal(str, mview_str)
- end
-
- def test_release
- ptr = Pointer["hello world"]
- mview = MemoryView.new(ptr)
- assert_same(ptr, mview.obj)
- mview.release
- assert_nil(mview.obj)
- end
-
- def test_to_s
- # U+3042 HIRAGANA LETTER A
- data = "\u{3042}"
- ptr = Pointer[data]
- mview = MemoryView.new(ptr)
- begin
- string = mview.to_s
- assert_equal([data.b, true],
- [string, string.frozen?])
- ensure
- mview.release
- end
- end
-
- def test_ractor_shareable
- omit("Need Ractor") unless defined?(Ractor)
- ptr = Pointer["hello world"]
- mview = MemoryView.new(ptr)
- begin
- assert_ractor_shareable(mview)
- assert_predicate(ptr, :frozen?)
- ensure
- mview.release
- end
- end
- end
-end
diff --git a/test/fiddle/test_pack.rb b/test/fiddle/test_pack.rb
deleted file mode 100644
index ade1dd5040..0000000000
--- a/test/fiddle/test_pack.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-begin
- require_relative 'helper'
- require 'fiddle/pack'
-rescue LoadError
- return
-end
-
-module Fiddle
- class TestPack < TestCase
- def test_pack_map
- if defined?(TYPE_LONG_LONG)
- assert_equal [0xffff_ffff_ffff_ffff], [0xffff_ffff_ffff_ffff].pack(PackInfo::PACK_MAP[-TYPE_LONG_LONG]).unpack(PackInfo::PACK_MAP[-TYPE_LONG_LONG])
- end
-
- case Fiddle::SIZEOF_VOIDP
- when 8
- assert_equal [0xffff_ffff_ffff_ffff], [0xffff_ffff_ffff_ffff].pack(PackInfo::PACK_MAP[TYPE_VOIDP]).unpack(PackInfo::PACK_MAP[TYPE_VOIDP])
- when 4
- assert_equal [0xffff_ffff], [0xffff_ffff].pack(PackInfo::PACK_MAP[TYPE_VOIDP]).unpack(PackInfo::PACK_MAP[TYPE_VOIDP])
- end
-
- case Fiddle::SIZEOF_LONG
- when 8
- assert_equal [0xffff_ffff_ffff_ffff], [0xffff_ffff_ffff_ffff].pack(PackInfo::PACK_MAP[-TYPE_LONG]).unpack(PackInfo::PACK_MAP[-TYPE_LONG])
- when 4
- assert_equal [0xffff_ffff], [0xffff_ffff].pack(PackInfo::PACK_MAP[-TYPE_LONG]).unpack(PackInfo::PACK_MAP[-TYPE_LONG])
- end
-
- if Fiddle::SIZEOF_INT == 4
- assert_equal [0xffff_ffff], [0xffff_ffff].pack(PackInfo::PACK_MAP[-TYPE_INT]).unpack(PackInfo::PACK_MAP[-TYPE_INT])
- end
-
- assert_equal [0xffff], [0xffff].pack(PackInfo::PACK_MAP[-TYPE_SHORT]).unpack(PackInfo::PACK_MAP[-TYPE_SHORT])
- assert_equal [0xff], [0xff].pack(PackInfo::PACK_MAP[-TYPE_CHAR]).unpack(PackInfo::PACK_MAP[-TYPE_CHAR])
- end
- end
-end
diff --git a/test/fiddle/test_pinned.rb b/test/fiddle/test_pinned.rb
deleted file mode 100644
index ad132579b0..0000000000
--- a/test/fiddle/test_pinned.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
-rescue LoadError
- return
-end
-
-module Fiddle
- class TestPinned < Fiddle::TestCase
- def test_pin_object
- x = Object.new
- pinner = Pinned.new x
- assert_same x, pinner.ref
- end
-
- def test_clear
- pinner = Pinned.new Object.new
- refute pinner.cleared?
- pinner.clear
- assert pinner.cleared?
- ex = assert_raise(Fiddle::ClearedReferenceError) do
- pinner.ref
- end
- assert_match "called on", ex.message
- end
-
- def test_ractor_shareable
- omit("Need Ractor") unless defined?(Ractor)
- obj = Object.new
- assert_ractor_shareable(Pinned.new(obj))
- assert_predicate(obj, :frozen?)
- end
- end
-end
diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb
deleted file mode 100644
index 9d490f9f26..0000000000
--- a/test/fiddle/test_pointer.rb
+++ /dev/null
@@ -1,319 +0,0 @@
-# frozen_string_literal: true
-begin
- require_relative 'helper'
-rescue LoadError
-end
-
-module Fiddle
- class TestPointer < TestCase
- def dlwrap arg
- Fiddle.dlwrap arg
- end
-
- def test_can_read_write_memory
- # Allocate some memory
- Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP, Fiddle::RUBY_FREE) do |pointer|
- address = pointer.to_i
- bytes_to_write = Fiddle::SIZEOF_VOIDP.times.to_a.pack("C*")
-
- # Write to the memory
- Fiddle::Pointer.write(address, bytes_to_write)
-
- # Read the bytes out again
- bytes = Fiddle::Pointer.read(address, Fiddle::SIZEOF_VOIDP)
- assert_equal bytes_to_write, bytes
- end
- end
-
- def test_cptr_to_int
- null = Fiddle::NULL
- assert_equal(null.to_i, null.to_int)
- end
-
- def test_malloc_free_func_int
- free = Fiddle::Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID)
- assert_equal free.to_i, Fiddle::RUBY_FREE.to_i
-
- ptr = Pointer.malloc(10, free.to_i)
- assert_equal 10, ptr.size
- assert_equal free.to_i, ptr.free.to_i
- end
-
- def test_malloc_free_func
- free = Fiddle::Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID)
-
- ptr = Pointer.malloc(10, free)
- assert_equal 10, ptr.size
- assert_equal free.to_i, ptr.free.to_i
- end
-
- def test_malloc_block
- escaped_ptr = nil
- returned = Pointer.malloc(10, Fiddle::RUBY_FREE) do |ptr|
- assert_equal 10, ptr.size
- assert_equal Fiddle::RUBY_FREE, ptr.free.to_i
- escaped_ptr = ptr
- :returned
- end
- assert_equal :returned, returned
- assert escaped_ptr.freed?
- end
-
- def test_malloc_block_no_free
- assert_raise ArgumentError do
- Pointer.malloc(10) { |ptr| }
- end
- end
-
- def test_malloc_subclass
- subclass = Class.new(Pointer)
- subclass.malloc(10, Fiddle::RUBY_FREE) do |ptr|
- assert ptr.is_a?(subclass)
- end
- end
-
- def test_to_str
- str = Marshal.load(Marshal.dump("hello world"))
- ptr = Pointer[str]
-
- assert_equal 3, ptr.to_str(3).length
- assert_equal str, ptr.to_str
-
- ptr[5] = 0
- assert_equal "hello\0world", ptr.to_str
- end
-
- def test_to_s
- str = Marshal.load(Marshal.dump("hello world"))
- ptr = Pointer[str]
-
- assert_equal 3, ptr.to_s(3).length
- assert_equal str, ptr.to_s
-
- ptr[5] = 0
- assert_equal 'hello', ptr.to_s
- end
-
- def test_minus
- str = "hello world"
- ptr = Pointer[str]
- assert_equal ptr.to_s, (ptr + 3 - 3).to_s
- end
-
- # TODO: what if the pointer size is 0? raise an exception? do we care?
- def test_plus
- str = "hello world"
- ptr = Pointer[str]
- new_str = ptr + 3
- assert_equal 'lo world', new_str.to_s
- end
-
- def test_inspect
- if ffi_backend?
- omit("Fiddle::Pointer#inspect is incompatible with FFI backend")
- end
-
- ptr = Pointer.new(0)
- inspect = ptr.inspect
- assert_match(/size=#{ptr.size}/, inspect)
- assert_match(/free=#{sprintf("%#x", ptr.free.to_i)}/, inspect)
- assert_match(/ptr=#{sprintf("%#x", ptr.to_i)}/, inspect)
- end
-
- def test_to_ptr_string
- str = "hello world"
- ptr = Pointer[str]
- assert_equal str.length, ptr.size
- assert_equal 'hello', ptr[0,5]
- end
-
- def test_to_ptr_io
- if ffi_backend?
- omit("Fiddle::Pointer.to_ptr(IO) isn't supported with FFI backend")
- end
-
- Pointer.malloc(10, Fiddle::RUBY_FREE) do |buf|
- File.open(__FILE__, 'r') do |f|
- ptr = Pointer.to_ptr f
- fread = Function.new(@libc['fread'],
- [TYPE_VOIDP, TYPE_INT, TYPE_INT, TYPE_VOIDP],
- TYPE_INT)
- fread.call(buf.to_i, Fiddle::SIZEOF_CHAR, buf.size - 1, ptr.to_i)
- end
-
- File.open(__FILE__, 'r') do |f|
- assert_equal f.read(9), buf.to_s
- end
- end
- end
-
- def test_to_ptr_with_ptr
- ptr = Pointer.new 0
- ptr2 = Pointer.to_ptr Struct.new(:to_ptr).new(ptr)
- assert_equal ptr, ptr2
-
- assert_raise(Fiddle::DLError) do
- Pointer.to_ptr Struct.new(:to_ptr).new(nil)
- end
- end
-
- def test_to_ptr_with_int
- ptr = Pointer.new 0
- assert_equal ptr, Pointer[0]
- end
-
- MimicInteger = Struct.new(:to_int)
- def test_to_ptr_with_to_int
- ptr = Pointer.new 0
- assert_equal ptr, Pointer[MimicInteger.new(0)]
- end
-
- def test_equals
- ptr = Pointer.new 0
- ptr2 = Pointer.new 0
- assert_equal ptr2, ptr
- end
-
- def test_not_equals
- ptr = Pointer.new 0
- refute_equal 10, ptr, '10 should not equal the pointer'
- end
-
- def test_cmp
- ptr = Pointer.new 0
- assert_nil(ptr <=> 10, '10 should not be comparable')
- end
-
- def test_ref_ptr
- if ffi_backend?
- omit("Fiddle.dlwrap([]) isn't supported with FFI backend")
- end
-
- ary = [0,1,2,4,5]
- addr = Pointer.new(dlwrap(ary))
- assert_equal addr.to_i, addr.ref.ptr.to_i
-
- assert_equal addr.to_i, (+ (- addr)).to_i
- end
-
- def test_to_value
- if ffi_backend?
- omit("Fiddle.dlwrap([]) isn't supported with FFI backend")
- end
-
- ary = [0,1,2,4,5]
- addr = Pointer.new(dlwrap(ary))
- assert_equal ary, addr.to_value
- end
-
- def test_free
- ptr = Pointer.malloc(4)
- begin
- assert_nil ptr.free
- ensure
- Fiddle.free ptr
- end
- end
-
- def test_free=
- free = Function.new(Fiddle::RUBY_FREE, [TYPE_VOIDP], TYPE_VOID)
- ptr = Pointer.malloc(4)
- ptr.free = free
-
- assert_equal free.ptr, ptr.free.ptr
- end
-
- def test_free_with_func
- ptr = Pointer.malloc(4, Fiddle::RUBY_FREE)
- refute ptr.freed?
- ptr.call_free
- assert ptr.freed?
- ptr.call_free # you can safely run it again
- assert ptr.freed?
- GC.start # you can safely run the GC routine
- assert ptr.freed?
- end
-
- def test_free_with_no_func
- ptr = Pointer.malloc(4)
- refute ptr.freed?
- ptr.call_free
- refute ptr.freed?
- ptr.call_free # you can safely run it again
- refute ptr.freed?
- end
-
- def test_freed?
- ptr = Pointer.malloc(4, Fiddle::RUBY_FREE)
- refute ptr.freed?
- ptr.call_free
- assert ptr.freed?
- end
-
- def test_null?
- ptr = Pointer.new(0)
- assert ptr.null?
- end
-
- def test_size
- Pointer.malloc(4, Fiddle::RUBY_FREE) do |ptr|
- assert_equal 4, ptr.size
- end
- end
-
- def test_size=
- Pointer.malloc(4, Fiddle::RUBY_FREE) do |ptr|
- ptr.size = 10
- assert_equal 10, ptr.size
- end
- end
-
- def test_aref_aset
- check = Proc.new{|str,ptr|
- assert_equal(str.size(), ptr.size())
- assert_equal(str, ptr.to_s())
- assert_equal(str[0,2], ptr.to_s(2))
- assert_equal(str[0,2], ptr[0,2])
- assert_equal(str[1,2], ptr[1,2])
- assert_equal(str[1,0], ptr[1,0])
- assert_equal(str[0].ord, ptr[0])
- assert_equal(str[1].ord, ptr[1])
- }
- str = Marshal.load(Marshal.dump('abc'))
- ptr = Pointer[str]
- check.call(str, ptr)
-
- str[0] = "c"
- assert_equal 'c'.ord, ptr[0] = "c".ord
- check.call(str, ptr)
-
- str[0,2] = "aa"
- assert_equal 'aa', ptr[0,2] = "aa"
- check.call(str, ptr)
-
- ptr2 = Pointer['cdeeee']
- str[0,2] = "cd"
- assert_equal ptr2, ptr[0,2] = ptr2
- check.call(str, ptr)
-
- ptr3 = Pointer['vvvv']
- str[0,2] = "vv"
- assert_equal ptr3.to_i, ptr[0,2] = ptr3.to_i
- check.call(str, ptr)
- end
-
- def test_null_pointer
- nullpo = Pointer.new(0)
- assert_raise(DLError) {nullpo[0]}
- assert_raise(DLError) {nullpo[0] = 1}
- end
-
- def test_ractor_shareable
- omit("Need Ractor") unless defined?(Ractor)
- assert_ractor_shareable(Fiddle::NULL)
- ary = [0, 1, 2, 4, 5]
- addr = Pointer.new(dlwrap(ary))
- assert_ractor_shareable(addr)
- end
- end
-end if defined?(Fiddle)
diff --git a/test/fileutils/test_fileutils.rb b/test/fileutils/test_fileutils.rb
index d2096a04cc..92308d9557 100644
--- a/test/fileutils/test_fileutils.rb
+++ b/test/fileutils/test_fileutils.rb
@@ -955,16 +955,27 @@ class TestFileUtils < Test::Unit::TestCase
def test_ln_s
check_singleton :ln_s
+ ln_s TARGETS, 'tmp'
+ each_srcdest do |fname, lnfname|
+ assert_equal fname, File.readlink(lnfname)
+ ensure
+ rm_f lnfname
+ end
+
+ lnfname = 'symlink'
+ assert_raise(Errno::ENOENT, "multiple targets need a destination directory") {
+ ln_s TARGETS, lnfname
+ }
+ assert_file.not_exist?(lnfname)
+
TARGETS.each do |fname|
- begin
- fname = "../#{fname}"
- lnfname = 'tmp/lnsdest'
- ln_s fname, lnfname
- assert FileTest.symlink?(lnfname), 'not symlink'
- assert_equal fname, File.readlink(lnfname)
- ensure
- rm_f lnfname
- end
+ fname = "../#{fname}"
+ lnfname = 'tmp/lnsdest'
+ ln_s fname, lnfname
+ assert_file.symlink?(lnfname)
+ assert_equal fname, File.readlink(lnfname)
+ ensure
+ rm_f lnfname
end
end if have_symlink? and !no_broken_symlink?
@@ -1017,22 +1028,64 @@ class TestFileUtils < Test::Unit::TestCase
def test_ln_sr
check_singleton :ln_sr
- TARGETS.each do |fname|
- begin
- lnfname = 'tmp/lnsdest'
- ln_sr fname, lnfname
- assert FileTest.symlink?(lnfname), 'not symlink'
- assert_equal "../#{fname}", File.readlink(lnfname), fname
+ assert_all_assertions_foreach(nil, *TARGETS) do |fname|
+ lnfname = 'tmp/lnsdest'
+ ln_sr fname, lnfname
+ assert_file.symlink?(lnfname)
+ assert_file.identical?(lnfname, fname)
+ assert_equal "../#{fname}", File.readlink(lnfname)
+ ensure
+ rm_f lnfname
+ end
+
+ ln_sr TARGETS, 'tmp'
+ assert_all_assertions do |all|
+ each_srcdest do |fname, lnfname|
+ all.for(fname) do
+ assert_equal "../#{fname}", File.readlink(lnfname)
+ end
ensure
rm_f lnfname
end
end
+
+ File.symlink 'data', 'link'
+ mkdir 'link/d1'
+ mkdir 'link/d2'
+ ln_sr 'link/d1/z', 'link/d2'
+ assert_equal '../d1/z', File.readlink('data/d2/z')
+
mkdir 'data/src'
File.write('data/src/xxx', 'ok')
File.symlink '../data/src', 'tmp/src'
ln_sr 'tmp/src/xxx', 'data'
- assert File.symlink?('data/xxx')
+ assert_file.symlink?('data/xxx')
assert_equal 'ok', File.read('data/xxx')
+ assert_equal 'src/xxx', File.readlink('data/xxx')
+ end
+
+ def test_ln_sr_not_target_directory
+ assert_raise(ArgumentError) {
+ ln_sr TARGETS, 'tmp', target_directory: false
+ }
+ assert_empty(Dir.children('tmp'))
+
+ lnfname = 'symlink'
+ assert_raise(ArgumentError) {
+ ln_sr TARGETS, lnfname, target_directory: false
+ }
+ assert_file.not_exist?(lnfname)
+
+ assert_all_assertions_foreach(nil, *TARGETS) do |fname|
+ assert_raise(Errno::EEXIST, Errno::EACCES) {
+ ln_sr fname, 'tmp', target_directory: false
+ }
+ dest = File.join('tmp/', File.basename(fname))
+ assert_file.not_exist? dest
+ ln_sr fname, dest, target_directory: false
+ assert_file.symlink?(dest)
+ assert_equal("../#{fname}", File.readlink(dest))
+ end
end if have_symlink?
def test_ln_sr_broken_symlink
@@ -1349,7 +1402,7 @@ class TestFileUtils < Test::Unit::TestCase
# regular file. It's slightly strange. Anyway it's no effect bit.
# see /usr/src/sys/ufs/ufs/ufs_chmod()
# NetBSD, OpenBSD, Solaris, and AIX also deny it.
- if /freebsd|netbsd|openbsd|solaris|aix/ !~ RUBY_PLATFORM
+ if /freebsd|netbsd|openbsd|aix/ !~ RUBY_PLATFORM
chmod "u+t,o+t", 'tmp/a'
assert_filemode 07500, 'tmp/a'
chmod "a-t,a-s", 'tmp/a'
@@ -1763,6 +1816,14 @@ class TestFileUtils < Test::Unit::TestCase
assert_file_not_exist 'data/tmpdir'
end if have_file_perm?
+ def test_remove_dir_with_file
+ File.write('data/tmpfile', 'dummy')
+ assert_raise(Errno::ENOTDIR) { remove_dir 'data/tmpfile' }
+ assert_file_exist 'data/tmpfile'
+ ensure
+ File.unlink('data/tmpfile') if File.exist?('data/tmpfile')
+ end
+
def test_compare_file
check_singleton :compare_file
# FIXME
diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb
index 2bf3df6439..c3f9c91c7d 100644
--- a/test/io/console/test_io_console.rb
+++ b/test/io/console/test_io_console.rb
@@ -7,6 +7,11 @@ rescue LoadError
end
class TestIO_Console < Test::Unit::TestCase
+ HOST_OS = RbConfig::CONFIG['host_os']
+ private def host_os?(os)
+ HOST_OS =~ os
+ end
+
begin
PATHS = $LOADED_FEATURES.grep(%r"/io/console(?:\.#{RbConfig::CONFIG['DLEXT']}|\.rb|/\w+\.rb)\z") {$`}
rescue Encoding::CompatibilityError
@@ -20,17 +25,13 @@ class TestIO_Console < Test::Unit::TestCase
# FreeBSD seems to hang on TTOU when running parallel tests
# tested on FreeBSD 11.x.
#
- # Solaris gets stuck too, even in non-parallel mode.
- # It occurs only in chkbuild. It does not occur when running
- # `make test-all` in SSH terminal.
- #
# I suspect that it occurs only when having no TTY.
# (Parallel mode runs tests in child processes, so I guess
# they has no TTY.)
# But it does not occur in `make test-all > /dev/null`, so
# there should be an additional factor, I guess.
def set_winsize_setup
- @old_ttou = trap(:TTOU, 'IGNORE') if RUBY_PLATFORM =~ /freebsd|solaris/i
+ @old_ttou = trap(:TTOU, 'IGNORE') if host_os?(/freebsd/)
end
def set_winsize_teardown
@@ -371,6 +372,15 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do
w.print cc
w.flush
result = EnvUtil.timeout(3) {r.gets}
+ if result
+ case cc.chr
+ when "\C-A".."\C-_"
+ cc = "^" + (cc.ord | 0x40).chr
+ when "\C-?"
+ cc = "^?"
+ end
+ result.sub!(cc, "")
+ end
assert_equal(expect, result.chomp)
end
@@ -382,7 +392,7 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do
# TestIO_Console#test_intr [/usr/home/chkbuild/chkbuild/tmp/build/20220304T163001Z/ruby/test/io/console/test_io_console.rb:387]:
# <"25"> expected but was
# <"-e:12:in `p': \e[1mexecution expired (\e[1;4mTimeout::Error\e[m\e[1m)\e[m">.
- omit if /freebsd/ =~ RUBY_PLATFORM
+ omit if host_os?(/freebsd/)
run_pty("#{<<~"begin;"}\n#{<<~'end;'}") do |r, w, _|
begin;
@@ -408,7 +418,7 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do
if cc = ctrl["intr"]
assert_ctrl("#{cc.ord}", cc, r, w)
assert_ctrl("#{cc.ord}", cc, r, w)
- assert_ctrl("Interrupt", cc, r, w) unless /linux|solaris/ =~ RUBY_PLATFORM
+ assert_ctrl("Interrupt", cc, r, w) unless host_os?(/linux/)
end
if cc = ctrl["dsusp"]
assert_ctrl("#{cc.ord}", cc, r, w)
@@ -444,7 +454,9 @@ defined?(PTY) and defined?(IO.console) and TestIO_Console.class_eval do
def test_ttyname
return unless IO.method_defined?(:ttyname)
- assert_equal(["true"], run_pty("p STDIN.ttyname == STDOUT.ttyname"))
+ # [Bug #20682]
+ # `sleep 0.1` is added to stabilize flaky failures on macOS.
+ assert_equal(["true"], run_pty("p STDIN.ttyname == STDOUT.ttyname; sleep 0.1"))
end
end
@@ -544,9 +556,7 @@ defined?(IO.console) and TestIO_Console.class_eval do
File.open(ttyname) {|f| assert_predicate(f, :tty?)}
end
end
-end
-defined?(IO.console) and TestIO_Console.class_eval do
case
when Process.respond_to?(:daemon)
noctty = [EnvUtil.rubybin, "-e", "Process.daemon(true)"]
diff --git a/test/io/console/test_ractor.rb b/test/io/console/test_ractor.rb
index b30988f47e..dff0c67eab 100644
--- a/test/io/console/test_ractor.rb
+++ b/test/io/console/test_ractor.rb
@@ -8,6 +8,10 @@ class TestIOConsoleInRactor < Test::Unit::TestCase
path = $".find {|path| path.end_with?(ext)}
assert_in_out_err(%W[-r#{path}], "#{<<~"begin;"}\n#{<<~'end;'}", ["true"], [])
begin;
+ class Ractor
+ alias value take
+ end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders
+
$VERBOSE = nil
r = Ractor.new do
$stdout.console_mode
@@ -18,17 +22,21 @@ class TestIOConsoleInRactor < Test::Unit::TestCase
else
true # should not success
end
- puts r.take
+ puts r.value
end;
assert_in_out_err(%W[-r#{path}], "#{<<~"begin;"}\n#{<<~'end;'}", ["true"], [])
begin;
+ class Ractor
+ alias value take
+ end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders
+
console = IO.console
$VERBOSE = nil
r = Ractor.new do
IO.console
end
- puts console.class == r.take.class
+ puts console.class == r.value.class
end;
end
end if defined? Ractor
diff --git a/test/io/wait/test_io_wait.rb b/test/io/wait/test_io_wait.rb
index cbc01f9622..c532638e09 100644
--- a/test/io/wait/test_io_wait.rb
+++ b/test/io/wait/test_io_wait.rb
@@ -4,9 +4,6 @@ require 'test/unit'
require 'timeout'
require 'socket'
-# For `IO#ready?` and `IO#nread`:
-require 'io/wait'
-
class TestIOWait < Test::Unit::TestCase
def setup
@@ -22,38 +19,11 @@ class TestIOWait < Test::Unit::TestCase
@w.close unless @w.closed?
end
- def test_nread
- assert_equal 0, @r.nread
- @w.syswrite "."
- sleep 0.1
- assert_equal 1, @r.nread
- end
-
- def test_nread_buffered
- @w.syswrite ".\n!"
- assert_equal ".\n", @r.gets
- assert_equal 1, @r.nread
- end
-
- def test_ready?
- omit 'unstable on MinGW' if /mingw/ =~ RUBY_PLATFORM
- assert_not_predicate @r, :ready?, "shouldn't ready, but ready"
- @w.syswrite "."
- sleep 0.1
- assert_predicate @r, :ready?, "should ready, but not"
- end
-
- def test_buffered_ready?
- @w.syswrite ".\n!"
- assert_equal ".\n", @r.gets
- assert_predicate @r, :ready?
- end
-
def test_wait
omit 'unstable on MinGW' if /mingw/ =~ RUBY_PLATFORM
assert_nil @r.wait(0)
@w.syswrite "."
- sleep 0.1
+ IO.select([@r])
assert_equal @r, @r.wait(0)
end
@@ -78,7 +48,8 @@ class TestIOWait < Test::Unit::TestCase
ret = nil
assert_nothing_raised(Timeout::Error) do
q.push(true)
- Timeout.timeout(0.1) { ret = @r.wait }
+ t = EnvUtil.apply_timeout_scale(1)
+ Timeout.timeout(t) { ret = @r.wait }
end
assert_equal @r, ret
ensure
@@ -113,7 +84,8 @@ class TestIOWait < Test::Unit::TestCase
ret = nil
assert_nothing_raised(Timeout::Error) do
q.push(true)
- Timeout.timeout(0.1) { ret = @r.wait_readable }
+ t = EnvUtil.apply_timeout_scale(1)
+ Timeout.timeout(t) { ret = @r.wait_readable }
end
assert_equal @r, ret
ensure
diff --git a/test/io/wait/test_io_wait_uncommon.rb b/test/io/wait/test_io_wait_uncommon.rb
index 0f922f4e24..15b13b51b1 100644
--- a/test/io/wait/test_io_wait_uncommon.rb
+++ b/test/io/wait/test_io_wait_uncommon.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require 'test/unit'
+require 'io/wait'
# test uncommon device types to check portability problems
# We may optimize IO#wait_*able for non-Linux kernels in the future
@@ -74,4 +75,18 @@ class TestIOWaitUncommon < Test::Unit::TestCase
def test_wait_writable_null
check_dev(IO::NULL, :wait_writable)
end
+
+ def test_after_ungetc_wait_readable
+ check_dev(IO::NULL, mode: "r") {|fp|
+ fp.ungetc(?a)
+ assert_predicate fp, :wait_readable
+ }
+ end
+
+ def test_after_ungetc_in_text_wait_readable
+ check_dev(IO::NULL, mode: "rt") {|fp|
+ fp.ungetc(?a)
+ assert_predicate fp, :wait_readable
+ }
+ end
end
diff --git a/test/io/wait/test_ractor.rb b/test/io/wait/test_ractor.rb
index 800216e610..c77a29bff3 100644
--- a/test/io/wait/test_ractor.rb
+++ b/test/io/wait/test_ractor.rb
@@ -7,11 +7,15 @@ class TestIOWaitInRactor < Test::Unit::TestCase
ext = "/io/wait.#{RbConfig::CONFIG['DLEXT']}"
path = $".find {|path| path.end_with?(ext)}
assert_in_out_err(%W[-r#{path}], <<-"end;", ["true"], [])
+ class Ractor
+ alias value take
+ end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders
+
$VERBOSE = nil
r = Ractor.new do
$stdout.equal?($stdout.wait_writable)
end
- puts r.take
+ puts r.value
end;
end
end if defined? Ractor
diff --git a/test/irb/command/test_cd.rb b/test/irb/command/test_cd.rb
deleted file mode 100644
index 10f77f6691..0000000000
--- a/test/irb/command/test_cd.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-require "tempfile"
-require_relative "../helper"
-
-module TestIRB
- class CDTest < IntegrationTestCase
- def setup
- super
-
- write_ruby <<~'RUBY'
- class Foo
- class Bar
- def bar
- "this is bar"
- end
- end
-
- def foo
- "this is foo"
- end
- end
-
- class BO < BasicObject
- def baz
- "this is baz"
- end
- end
-
- binding.irb
- RUBY
- end
-
- def test_cd
- out = run_ruby_file do
- type "cd Foo"
- type "ls"
- type "cd Bar"
- type "ls"
- type "cd .."
- type "exit"
- end
-
- assert_match(/irb\(Foo\):002>/, out)
- assert_match(/Foo#methods: foo/, out)
- assert_match(/irb\(Foo::Bar\):004>/, out)
- assert_match(/Bar#methods: bar/, out)
- assert_match(/irb\(Foo\):006>/, out)
- end
-
- def test_cd_basic_object_or_frozen
- out = run_ruby_file do
- type "cd BO.new"
- type "cd 1"
- type "cd Object.new.freeze"
- type "exit"
- end
-
- assert_match(/irb\(#<BO:.+\):002>/, out)
- assert_match(/irb\(1\):003>/, out)
- assert_match(/irb\(#<Object:.+\):004>/, out)
- end
-
- def test_cd_moves_top_level_with_no_args
- out = run_ruby_file do
- type "cd Foo"
- type "cd Bar"
- type "cd"
- type "exit"
- end
-
- assert_match(/irb\(Foo::Bar\):003>/, out)
- assert_match(/irb\(main\):004>/, out)
- end
-
- def test_cd_with_error
- out = run_ruby_file do
- type "cd Baz"
- type "exit"
- end
-
- assert_match(/Error: uninitialized constant Baz/, out)
- assert_match(/irb\(main\):002>/, out) # the context should not change
- end
- end
-end
diff --git a/test/irb/command/test_command_aliasing.rb b/test/irb/command/test_command_aliasing.rb
deleted file mode 100644
index 4ecc88c0aa..0000000000
--- a/test/irb/command/test_command_aliasing.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-require "tempfile"
-require_relative "../helper"
-
-module TestIRB
- class CommandAliasingTest < IntegrationTestCase
- def setup
- super
- write_rc <<~RUBY
- IRB.conf[:COMMAND_ALIASES] = {
- :c => :conf, # alias to helper method
- :f => :foo
- }
- RUBY
-
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
- end
-
- def test_aliasing_to_helper_method_triggers_warning
- out = run_ruby_file do
- type "c"
- type "exit"
- end
- assert_include(out, "Using command alias `c` for helper method `conf` is not supported.")
- assert_not_include(out, "Maybe IRB bug!")
- end
-
- def test_alias_to_non_existent_command_triggers_warning
- message = "You're trying to use command alias `f` for command `foo`, but `foo` does not exist."
- out = run_ruby_file do
- type "f"
- type "exit"
- end
- assert_include(out, message)
- assert_not_include(out, "Maybe IRB bug!")
-
- # Local variables take precedence over command aliases
- out = run_ruby_file do
- type "f = 123"
- type "f"
- type "exit"
- end
- assert_not_include(out, message)
- assert_not_include(out, "Maybe IRB bug!")
- end
- end
-end
diff --git a/test/irb/command/test_custom_command.rb b/test/irb/command/test_custom_command.rb
deleted file mode 100644
index 13f412c210..0000000000
--- a/test/irb/command/test_custom_command.rb
+++ /dev/null
@@ -1,194 +0,0 @@
-# frozen_string_literal: true
-require "irb"
-
-require_relative "../helper"
-
-module TestIRB
- class CustomCommandIntegrationTest < TestIRB::IntegrationTestCase
- def test_command_registration_can_happen_after_irb_require
- write_ruby <<~RUBY
- require "irb"
- require "irb/command"
-
- class PrintCommand < IRB::Command::Base
- category 'CommandTest'
- description 'print_command'
- def execute(*)
- puts "Hello from PrintCommand"
- end
- end
-
- IRB::Command.register(:print!, PrintCommand)
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "print!"
- type "exit"
- end
-
- assert_include(output, "Hello from PrintCommand")
- end
-
- def test_command_registration_accepts_string_too
- write_ruby <<~RUBY
- require "irb/command"
-
- class PrintCommand < IRB::Command::Base
- category 'CommandTest'
- description 'print_command'
- def execute(*)
- puts "Hello from PrintCommand"
- end
- end
-
- IRB::Command.register("print!", PrintCommand)
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "print!"
- type "exit"
- end
-
- assert_include(output, "Hello from PrintCommand")
- end
-
- def test_arguments_propagation
- write_ruby <<~RUBY
- require "irb/command"
-
- class PrintArgCommand < IRB::Command::Base
- category 'CommandTest'
- description 'print_command_arg'
- def execute(arg)
- $nth_execution ||= 0
- puts "\#{$nth_execution} arg=\#{arg.inspect}"
- $nth_execution += 1
- end
- end
-
- IRB::Command.register(:print_arg, PrintArgCommand)
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "print_arg"
- type "print_arg \n"
- type "print_arg a r g"
- type "print_arg a r g \n"
- type "exit"
- end
-
- assert_include(output, "0 arg=\"\"")
- assert_include(output, "1 arg=\"\"")
- assert_include(output, "2 arg=\"a r g\"")
- assert_include(output, "3 arg=\"a r g\"")
- end
-
- def test_def_extend_command_still_works
- write_ruby <<~RUBY
- require "irb"
-
- class FooBarCommand < IRB::Command::Base
- category 'FooBarCategory'
- description 'foobar_description'
- def execute(*)
- $nth_execution ||= 1
- puts "\#{$nth_execution} FooBar executed"
- $nth_execution += 1
- end
- end
-
- IRB::ExtendCommandBundle.def_extend_command(:foobar, FooBarCommand, nil, [:fbalias, IRB::Command::OVERRIDE_ALL])
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "foobar"
- type "fbalias"
- type "help foobar"
- type "exit"
- end
-
- assert_include(output, "1 FooBar executed")
- assert_include(output, "2 FooBar executed")
- assert_include(output, "foobar_description")
- end
-
- def test_no_meta_command_also_works
- write_ruby <<~RUBY
- require "irb/command"
-
- class NoMetaCommand < IRB::Command::Base
- def execute(*)
- puts "This command does not override meta attributes"
- end
- end
-
- IRB::Command.register(:no_meta, NoMetaCommand)
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "no_meta"
- type "help no_meta"
- type "exit"
- end
-
- assert_include(output, "This command does not override meta attributes")
- assert_include(output, "No description provided.")
- assert_not_include(output, "Maybe IRB bug")
- end
-
- def test_command_name_local_variable
- write_ruby <<~RUBY
- require "irb/command"
-
- class FooBarCommand < IRB::Command::Base
- category 'CommandTest'
- description 'test'
- def execute(arg)
- puts "arg=\#{arg.inspect}"
- end
- end
-
- IRB::Command.register(:foo_bar, FooBarCommand)
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "binding.irb"
- type "foo_bar == 1 || 1"
- type "foo_bar =~ /2/ || 2"
- type "exit"
- type "binding.irb"
- type "foo_bar = '3'; foo_bar"
- type "foo_bar == 4 || '4'"
- type "foo_bar =~ /5/ || '5'"
- type "exit"
- type "binding.irb"
- type "foo_bar ||= '6'; foo_bar"
- type "foo_bar == 7 || '7'"
- type "foo_bar =~ /8/ || '8'"
- type "exit"
- type "exit"
- end
-
- assert_include(output, 'arg="== 1 || 1"')
- assert_include(output, 'arg="=~ /2/ || 2"')
- assert_include(output, '=> "3"')
- assert_include(output, '=> "4"')
- assert_include(output, '=> "5"')
- assert_include(output, '=> "6"')
- assert_include(output, '=> "7"')
- assert_include(output, '=> "8"')
- end
- end
-end
diff --git a/test/irb/command/test_disable_irb.rb b/test/irb/command/test_disable_irb.rb
deleted file mode 100644
index 14a20043d5..0000000000
--- a/test/irb/command/test_disable_irb.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: false
-require 'irb'
-
-require_relative "../helper"
-
-module TestIRB
- class DisableIRBTest < IntegrationTestCase
- def test_disable_irb_disable_further_irb_breakpoints
- write_ruby <<~'ruby'
- puts "First line"
- puts "Second line"
- binding.irb
- puts "Third line"
- binding.irb
- puts "Fourth line"
- ruby
-
- output = run_ruby_file do
- type "disable_irb"
- end
-
- assert_match(/First line\r\n/, output)
- assert_match(/Second line\r\n/, output)
- assert_match(/Third line\r\n/, output)
- assert_match(/Fourth line\r\n/, output)
- end
- end
-end
diff --git a/test/irb/command/test_force_exit.rb b/test/irb/command/test_force_exit.rb
deleted file mode 100644
index 191a786872..0000000000
--- a/test/irb/command/test_force_exit.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: false
-require 'irb'
-
-require_relative "../helper"
-
-module TestIRB
- class ForceExitTest < IntegrationTestCase
- def test_forced_exit_finishes_process_immediately
- write_ruby <<~'ruby'
- puts "First line"
- puts "Second line"
- binding.irb
- puts "Third line"
- binding.irb
- puts "Fourth line"
- ruby
-
- output = run_ruby_file do
- type "123"
- type "456"
- type "exit!"
- end
-
- assert_match(/First line\r\n/, output)
- assert_match(/Second line\r\n/, output)
- assert_match(/irb\(main\):001> 123/, output)
- assert_match(/irb\(main\):002> 456/, output)
- refute_match(/Third line\r\n/, output)
- refute_match(/Fourth line\r\n/, output)
- end
-
- def test_forced_exit_in_nested_sessions
- write_ruby <<~'ruby'
- def foo
- binding.irb
- end
-
- binding.irb
- binding.irb
- ruby
-
- output = run_ruby_file do
- type "123"
- type "foo"
- type "exit!"
- end
-
- assert_match(/irb\(main\):001> 123/, output)
- end
- end
-end
diff --git a/test/irb/command/test_help.rb b/test/irb/command/test_help.rb
deleted file mode 100644
index b34832b022..0000000000
--- a/test/irb/command/test_help.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require "tempfile"
-require_relative "../helper"
-
-module TestIRB
- class HelpTest < IntegrationTestCase
- def setup
- super
-
- write_rc <<~'RUBY'
- IRB.conf[:USE_PAGER] = false
- RUBY
-
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
- end
-
- def test_help
- out = run_ruby_file do
- type "help"
- type "exit"
- end
-
- assert_match(/List all available commands/, out)
- assert_match(/Start the debugger of debug\.gem/, out)
- end
-
- def test_command_help
- out = run_ruby_file do
- type "help ls"
- type "exit"
- end
-
- assert_match(/Usage: ls \[obj\]/, out)
- end
-
- def test_command_help_not_found
- out = run_ruby_file do
- type "help foo"
- type "exit"
- end
-
- assert_match(/Can't find command `foo`\. Please check the command name and try again\./, out)
- end
-
- def test_show_cmds
- out = run_ruby_file do
- type "help"
- type "exit"
- end
-
- assert_match(/List all available commands/, out)
- assert_match(/Start the debugger of debug\.gem/, out)
- end
-
- def test_help_lists_user_aliases
- out = run_ruby_file do
- type "help"
- type "exit"
- end
-
- assert_match(/\$\s+Alias for `show_source`/, out)
- assert_match(/@\s+Alias for `whereami`/, out)
- end
-
- def test_help_lists_helper_methods
- out = run_ruby_file do
- type "help"
- type "exit"
- end
-
- assert_match(/Helper methods\s+conf\s+Returns the current IRB context/, out)
- end
- end
-end
diff --git a/test/irb/command/test_multi_irb_commands.rb b/test/irb/command/test_multi_irb_commands.rb
deleted file mode 100644
index e313c0c5d2..0000000000
--- a/test/irb/command/test_multi_irb_commands.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require "tempfile"
-require_relative "../helper"
-
-module TestIRB
- class MultiIRBTest < IntegrationTestCase
- def setup
- super
-
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
- end
-
- def test_jobs_command_with_print_deprecated_warning
- out = run_ruby_file do
- type "jobs"
- type "exit"
- end
-
- assert_match(/Multi-irb commands are deprecated and will be removed in IRB 2\.0\.0\. Please use workspace commands instead\./, out)
- assert_match(%r|If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653|, out)
- assert_match(/#0->irb on main \(#<Thread:0x.+ run>: running\)/, out)
- end
-
- def test_irb_jobs_and_kill_commands
- out = run_ruby_file do
- type "irb"
- type "jobs"
- type "kill 1"
- type "exit"
- end
-
- assert_match(/#0->irb on main \(#<Thread:0x.+ sleep_forever>: stop\)/, out)
- assert_match(/#1->irb#1 on main \(#<Thread:0x.+ run>: running\)/, out)
- end
-
- def test_irb_fg_jobs_and_kill_commands
- out = run_ruby_file do
- type "irb"
- type "fg 0"
- type "jobs"
- type "kill 1"
- type "exit"
- end
-
- assert_match(/#0->irb on main \(#<Thread:0x.+ run>: running\)/, out)
- assert_match(/#1->irb#1 on main \(#<Thread:0x.+ sleep_forever>: stop\)/, out)
- end
- end
-end
diff --git a/test/irb/command/test_show_source.rb b/test/irb/command/test_show_source.rb
deleted file mode 100644
index a4227231e4..0000000000
--- a/test/irb/command/test_show_source.rb
+++ /dev/null
@@ -1,410 +0,0 @@
-# frozen_string_literal: false
-require 'irb'
-
-require_relative "../helper"
-
-module TestIRB
- class ShowSourceTest < IntegrationTestCase
- def setup
- super
-
- write_rc <<~'RUBY'
- IRB.conf[:USE_PAGER] = false
- RUBY
- end
-
- def test_show_source
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source IRB.conf"
- type "exit"
- end
-
- assert_match(%r[/irb\/init\.rb], out)
- end
-
- def test_show_source_alias
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "$ IRB.conf"
- type "exit"
- end
-
- assert_match(%r[/irb\/init\.rb], out)
- end
-
- def test_show_source_with_missing_signature
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source foo"
- type "exit"
- end
-
- assert_match(%r[Couldn't locate a definition for foo], out)
- end
-
- def test_show_source_with_missing_constant
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source Foo"
- type "exit"
- end
-
- assert_match(%r[Couldn't locate a definition for Foo], out)
- end
-
- def test_show_source_with_eval_error
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source raise(Exception).itself"
- type "exit"
- end
-
- assert_match(%r[Couldn't locate a definition for raise\(Exception\)\.itself], out)
- end
-
- def test_show_source_string
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source 'IRB.conf'"
- type "exit"
- end
-
- assert_match(%r[/irb\/init\.rb], out)
- end
-
- def test_show_source_method_s
- write_ruby <<~RUBY
- class Baz
- def foo
- end
- end
-
- class Bar < Baz
- def foo
- super
- end
- end
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source Bar#foo -s"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end\r\n], out)
- end
-
- def test_show_source_method_s_with_incorrect_signature
- write_ruby <<~RUBY
- class Baz
- def foo
- end
- end
-
- class Bar < Baz
- def foo
- super
- end
- end
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source Bar#fooo -s"
- type "exit"
- end
-
- assert_match(%r[Error: Couldn't locate a super definition for Bar#fooo], out)
- end
-
- def test_show_source_private_method
- write_ruby <<~RUBY
- class Bar
- private def foo
- end
- end
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source Bar#foo"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:2\s+private def foo\r\n end\r\n], out)
- end
-
- def test_show_source_private_singleton_method
- write_ruby <<~RUBY
- class Bar
- private def foo
- end
- end
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "bar = Bar.new"
- type "show_source bar.foo"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:2\s+private def foo\r\n end\r\n], out)
- end
-
- def test_show_source_method_multiple_s
- write_ruby <<~RUBY
- class Baz
- def foo
- end
- end
-
- class Bar < Baz
- def foo
- super
- end
- end
-
- class Bob < Bar
- def foo
- super
- end
- end
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source Bob#foo -ss"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end\r\n], out)
- end
-
- def test_show_source_method_no_instance_method
- write_ruby <<~RUBY
- class Baz
- end
-
- class Bar < Baz
- def foo
- super
- end
- end
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source Bar#foo -s"
- type "exit"
- end
-
- assert_match(%r[Error: Couldn't locate a super definition for Bar#foo], out)
- end
-
- def test_show_source_method_exceeds_super_chain
- write_ruby <<~RUBY
- class Baz
- def foo
- end
- end
-
- class Bar < Baz
- def foo
- super
- end
- end
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source Bar#foo -ss"
- type "exit"
- end
-
- assert_match(%r[Error: Couldn't locate a super definition for Bar#foo], out)
- end
-
- def test_show_source_method_accidental_characters
- write_ruby <<~'RUBY'
- class Baz
- def foo
- end
- end
-
- class Bar < Baz
- def foo
- super
- end
- end
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source Bar#foo -sddddd"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out)
- end
-
- def test_show_source_receiver_super
- write_ruby <<~RUBY
- class Baz
- def foo
- end
- end
-
- class Bar < Baz
- def foo
- super
- end
- end
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "bar = Bar.new"
- type "show_source bar.foo -s"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:2\s+def foo\r\n end], out)
- end
-
- def test_show_source_with_double_colons
- write_ruby <<~RUBY
- class Foo
- end
-
- class Foo
- class Bar
- end
- end
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "show_source ::Foo"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:1\s+class Foo\r\nend], out)
-
- out = run_ruby_file do
- type "show_source ::Foo::Bar"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:5\s+class Bar\r\n end], out)
- end
-
- def test_show_source_keep_script_lines
- pend unless defined?(RubyVM.keep_script_lines)
-
- write_ruby <<~RUBY
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "def foo; end"
- type "show_source foo"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}\(irb\):1\s+def foo; end], out)
- end
-
- def test_show_source_unavailable_source
- write_ruby <<~RUBY
- binding.irb
- RUBY
-
- out = run_ruby_file do
- type "RubyVM.keep_script_lines = false if defined?(RubyVM.keep_script_lines)"
- type "def foo; end"
- type "show_source foo"
- type "exit"
- end
- assert_match(%r[#{@ruby_file.to_path}\(irb\):2\s+Source not available], out)
- end
-
- def test_show_source_shows_binary_source
- write_ruby <<~RUBY
- # io-console is an indirect dependency of irb
- require "io/console"
-
- binding.irb
- RUBY
-
- out = run_ruby_file do
- # IO::ConsoleMode is defined in io-console gem's C extension
- type "show_source IO::ConsoleMode"
- type "exit"
- end
-
- # A safeguard to make sure the test subject is actually defined
- refute_match(/NameError/, out)
- assert_match(%r[Defined in binary file:.+io/console], out)
- end
-
- def test_show_source_with_constant_lookup
- write_ruby <<~RUBY
- X = 1
- module M
- Y = 1
- Z = 2
- end
- class A
- Z = 1
- Array = 1
- class B
- include M
- Object.new.instance_eval { binding.irb }
- end
- end
- RUBY
-
- out = run_ruby_file do
- type "show_source X"
- type "show_source Y"
- type "show_source Z"
- type "show_source Array"
- type "exit"
- end
-
- assert_match(%r[#{@ruby_file.to_path}:1\s+X = 1], out)
- assert_match(%r[#{@ruby_file.to_path}:3\s+Y = 1], out)
- assert_match(%r[#{@ruby_file.to_path}:7\s+Z = 1], out)
- assert_match(%r[#{@ruby_file.to_path}:8\s+Array = 1], out)
- end
- end
-end
diff --git a/test/irb/helper.rb b/test/irb/helper.rb
deleted file mode 100644
index ea2c6ef16a..0000000000
--- a/test/irb/helper.rb
+++ /dev/null
@@ -1,234 +0,0 @@
-require "test/unit"
-require "pathname"
-require "rubygems"
-
-begin
- require_relative "../lib/helper"
- require_relative "../lib/envutil"
-rescue LoadError # ruby/ruby defines helpers differently
-end
-
-begin
- require "pty"
-rescue LoadError # some platforms don't support PTY
-end
-
-module IRB
- class InputMethod; end
-end
-
-module TestIRB
- RUBY_3_4 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0.dev")
- class TestCase < Test::Unit::TestCase
- class TestInputMethod < ::IRB::InputMethod
- attr_reader :list, :line_no
-
- def initialize(list = [])
- @line_no = 0
- @list = list
- end
-
- def gets
- @list[@line_no]&.tap {@line_no += 1}
- end
-
- def eof?
- @line_no >= @list.size
- end
-
- def encoding
- Encoding.default_external
- end
-
- def reset
- @line_no = 0
- end
- end
-
- def ruby_core?
- !Pathname(__dir__).join("../../", "irb.gemspec").exist?
- end
-
- def setup_envs(home:)
- @backup_home = ENV["HOME"]
- ENV["HOME"] = home
- @backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
- @backup_irbrc = ENV.delete("IRBRC")
- end
-
- def teardown_envs
- ENV["HOME"] = @backup_home
- ENV["XDG_CONFIG_HOME"] = @backup_xdg_config_home
- ENV["IRBRC"] = @backup_irbrc
- end
-
- def save_encodings
- @default_encoding = [Encoding.default_external, Encoding.default_internal]
- @stdio_encodings = [STDIN, STDOUT, STDERR].map {|io| [io.external_encoding, io.internal_encoding] }
- end
-
- def restore_encodings
- EnvUtil.suppress_warning do
- Encoding.default_external, Encoding.default_internal = *@default_encoding
- [STDIN, STDOUT, STDERR].zip(@stdio_encodings) do |io, encs|
- io.set_encoding(*encs)
- end
- end
- end
-
- def without_rdoc(&block)
- ::Kernel.send(:alias_method, :irb_original_require, :require)
-
- ::Kernel.define_method(:require) do |name|
- raise LoadError, "cannot load such file -- rdoc (test)" if name.match?("rdoc") || name.match?(/^rdoc\/.*/)
- ::Kernel.send(:irb_original_require, name)
- end
-
- yield
- ensure
- EnvUtil.suppress_warning {
- ::Kernel.send(:alias_method, :require, :irb_original_require)
- ::Kernel.undef_method :irb_original_require
- }
- end
- end
-
- class IntegrationTestCase < TestCase
- LIB = File.expand_path("../../lib", __dir__)
- TIMEOUT_SEC = 3
-
- def setup
- @envs = {}
- @tmpfiles = []
-
- unless defined?(PTY)
- omit "Integration tests require PTY."
- end
-
- if ruby_core?
- omit "This test works only under ruby/irb"
- end
-
- write_rc <<~RUBY
- IRB.conf[:USE_PAGER] = false
- RUBY
- end
-
- def teardown
- @tmpfiles.each do |tmpfile|
- File.unlink(tmpfile)
- end
- end
-
- def run_ruby_file(&block)
- cmd = [EnvUtil.rubybin, "-I", LIB, @ruby_file.to_path]
- tmp_dir = Dir.mktmpdir
-
- @commands = []
- lines = []
-
- yield
-
- # Test should not depend on user's irbrc file
- @envs["HOME"] ||= tmp_dir
- @envs["XDG_CONFIG_HOME"] ||= tmp_dir
- @envs["IRBRC"] = nil unless @envs.key?("IRBRC")
-
- envs_for_spawn = @envs.merge('TERM' => 'dumb', 'TEST_IRB_FORCE_INTERACTIVE' => 'true')
-
- PTY.spawn(envs_for_spawn, *cmd) do |read, write, pid|
- Timeout.timeout(TIMEOUT_SEC) do
- while line = safe_gets(read)
- lines << line
-
- # means the breakpoint is triggered
- if line.match?(/binding\.irb/)
- while command = @commands.shift
- write.puts(command)
- end
- end
- end
- end
- ensure
- read.close
- write.close
- kill_safely(pid)
- end
-
- lines.join
- rescue Timeout::Error
- message = <<~MSG
- Test timedout.
-
- #{'=' * 30} OUTPUT #{'=' * 30}
- #{lines.map { |l| " #{l}" }.join}
- #{'=' * 27} END OF OUTPUT #{'=' * 27}
- MSG
- assert_block(message) { false }
- ensure
- FileUtils.remove_entry tmp_dir
- end
-
- # read.gets could raise exceptions on some platforms
- # https://github.com/ruby/ruby/blob/master/ext/pty/pty.c#L721-L728
- def safe_gets(read)
- read.gets
- rescue Errno::EIO
- nil
- end
-
- def kill_safely pid
- return if wait_pid pid, TIMEOUT_SEC
-
- Process.kill :TERM, pid
- return if wait_pid pid, 0.2
-
- Process.kill :KILL, pid
- Process.waitpid(pid)
- rescue Errno::EPERM, Errno::ESRCH
- end
-
- def wait_pid pid, sec
- total_sec = 0.0
- wait_sec = 0.001 # 1ms
-
- while total_sec < sec
- if Process.waitpid(pid, Process::WNOHANG) == pid
- return true
- end
- sleep wait_sec
- total_sec += wait_sec
- wait_sec *= 2
- end
-
- false
- rescue Errno::ECHILD
- true
- end
-
- def type(command)
- @commands << command
- end
-
- def write_ruby(program)
- @ruby_file = Tempfile.create(%w{irbtest- .rb})
- @tmpfiles << @ruby_file
- @ruby_file.write(program)
- @ruby_file.close
- end
-
- def write_rc(content)
- # Append irbrc content if a tempfile for it already exists
- if @irbrc
- @irbrc = File.open(@irbrc, "a")
- else
- @irbrc = Tempfile.new('irbrc')
- @tmpfiles << @irbrc
- end
-
- @irbrc.write(content)
- @irbrc.close
- @envs['IRBRC'] = @irbrc.path
- end
- end
-end
diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb
deleted file mode 100644
index 5529e29042..0000000000
--- a/test/irb/test_color.rb
+++ /dev/null
@@ -1,275 +0,0 @@
-# frozen_string_literal: false
-require 'irb/color'
-require 'stringio'
-
-require_relative "helper"
-
-module TestIRB
- class ColorTest < TestCase
- CLEAR = "\e[0m"
- BOLD = "\e[1m"
- UNDERLINE = "\e[4m"
- REVERSE = "\e[7m"
- RED = "\e[31m"
- GREEN = "\e[32m"
- YELLOW = "\e[33m"
- BLUE = "\e[34m"
- MAGENTA = "\e[35m"
- CYAN = "\e[36m"
-
- def setup
- super
- if IRB.respond_to?(:conf)
- @colorize, IRB.conf[:USE_COLORIZE] = IRB.conf[:USE_COLORIZE], true
- end
- end
-
- def teardown
- if instance_variable_defined?(:@colorize)
- IRB.conf[:USE_COLORIZE] = @colorize
- end
- super
- end
-
- def test_colorize
- text = "text"
- {
- [:BOLD] => "#{BOLD}#{text}#{CLEAR}",
- [:UNDERLINE] => "#{UNDERLINE}#{text}#{CLEAR}",
- [:REVERSE] => "#{REVERSE}#{text}#{CLEAR}",
- [:RED] => "#{RED}#{text}#{CLEAR}",
- [:GREEN] => "#{GREEN}#{text}#{CLEAR}",
- [:YELLOW] => "#{YELLOW}#{text}#{CLEAR}",
- [:BLUE] => "#{BLUE}#{text}#{CLEAR}",
- [:MAGENTA] => "#{MAGENTA}#{text}#{CLEAR}",
- [:CYAN] => "#{CYAN}#{text}#{CLEAR}",
- }.each do |seq, result|
- assert_equal_with_term(result, text, seq: seq)
-
- assert_equal_with_term(text, text, seq: seq, tty: false)
- assert_equal_with_term(text, text, seq: seq, colorable: false)
- assert_equal_with_term(result, text, seq: seq, tty: false, colorable: true)
- end
- end
-
- def test_colorize_code
- # Common behaviors. Warn parser error, but do not warn compile error.
- tests = {
- "1" => "#{BLUE}#{BOLD}1#{CLEAR}",
- "2.3" => "#{MAGENTA}#{BOLD}2.3#{CLEAR}",
- "7r" => "#{BLUE}#{BOLD}7r#{CLEAR}",
- "8i" => "#{BLUE}#{BOLD}8i#{CLEAR}",
- "['foo', :bar]" => "[#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}bar#{CLEAR}]",
- "class A; end" => "#{GREEN}class#{CLEAR} #{BLUE}#{BOLD}#{UNDERLINE}A#{CLEAR}; #{GREEN}end#{CLEAR}",
- "def self.foo; bar; end" => "#{GREEN}def#{CLEAR} #{CYAN}#{BOLD}self#{CLEAR}.#{BLUE}#{BOLD}foo#{CLEAR}; bar; #{GREEN}end#{CLEAR}",
- 'erb = ERB.new("a#{nil}b", trim_mode: "-")' => "erb = #{BLUE}#{BOLD}#{UNDERLINE}ERB#{CLEAR}.new(#{RED}#{BOLD}\"#{CLEAR}#{RED}a#{CLEAR}#{RED}\#{#{CLEAR}#{CYAN}#{BOLD}nil#{CLEAR}#{RED}}#{CLEAR}#{RED}b#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}, #{MAGENTA}trim_mode:#{CLEAR} #{RED}#{BOLD}\"#{CLEAR}#{RED}-#{CLEAR}#{RED}#{BOLD}\"#{CLEAR})",
- "# comment" => "#{BLUE}#{BOLD}# comment#{CLEAR}",
- "def f;yield(hello);end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR};#{GREEN}yield#{CLEAR}(hello);#{GREEN}end#{CLEAR}",
- '"##@var]"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}\##{CLEAR}#{RED}\##{CLEAR}@var#{RED}]#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
- '"foo#{a} #{b}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}a#{RED}}#{CLEAR}#{RED} #{CLEAR}#{RED}\#{#{CLEAR}b#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
- '/r#{e}g/' => "#{RED}#{BOLD}/#{CLEAR}#{RED}r#{CLEAR}#{RED}\#{#{CLEAR}e#{RED}}#{CLEAR}#{RED}g#{CLEAR}#{RED}#{BOLD}/#{CLEAR}",
- "'a\nb'" => "#{RED}#{BOLD}'#{CLEAR}#{RED}a#{CLEAR}\n#{RED}b#{CLEAR}#{RED}#{BOLD}'#{CLEAR}",
- "%[str]" => "#{RED}#{BOLD}%[#{CLEAR}#{RED}str#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
- "%Q[str]" => "#{RED}#{BOLD}%Q[#{CLEAR}#{RED}str#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
- "%q[str]" => "#{RED}#{BOLD}%q[#{CLEAR}#{RED}str#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
- "%x[cmd]" => "#{RED}#{BOLD}%x[#{CLEAR}#{RED}cmd#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
- "%r[reg]" => "#{RED}#{BOLD}%r[#{CLEAR}#{RED}reg#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
- "%w[a b]" => "#{RED}#{BOLD}%w[#{CLEAR}#{RED}a#{CLEAR} #{RED}b#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
- "%W[a b]" => "#{RED}#{BOLD}%W[#{CLEAR}#{RED}a#{CLEAR} #{RED}b#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
- "%s[a b]" => "#{YELLOW}%s[#{CLEAR}#{YELLOW}a b#{CLEAR}#{YELLOW}]#{CLEAR}",
- "%i[c d]" => "#{YELLOW}%i[#{CLEAR}#{YELLOW}c#{CLEAR}#{YELLOW} #{CLEAR}#{YELLOW}d#{CLEAR}#{YELLOW}]#{CLEAR}",
- "%I[c d]" => "#{YELLOW}%I[#{CLEAR}#{YELLOW}c#{CLEAR}#{YELLOW} #{CLEAR}#{YELLOW}d#{CLEAR}#{YELLOW}]#{CLEAR}",
- "{'a': 1}" => "{#{RED}#{BOLD}'#{CLEAR}#{RED}a#{CLEAR}#{RED}#{BOLD}':#{CLEAR} #{BLUE}#{BOLD}1#{CLEAR}}",
- ":Struct" => "#{YELLOW}:#{CLEAR}#{YELLOW}Struct#{CLEAR}",
- '"#{}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
- ':"a#{}b"' => "#{YELLOW}:\"#{CLEAR}#{YELLOW}a#{CLEAR}#{YELLOW}\#{#{CLEAR}#{YELLOW}}#{CLEAR}#{YELLOW}b#{CLEAR}#{YELLOW}\"#{CLEAR}",
- ':"a#{ def b; end; \'c\' + "#{ :d }" }e"' => "#{YELLOW}:\"#{CLEAR}#{YELLOW}a#{CLEAR}#{YELLOW}\#{#{CLEAR} #{GREEN}def#{CLEAR} #{BLUE}#{BOLD}b#{CLEAR}; #{GREEN}end#{CLEAR}; #{RED}#{BOLD}'#{CLEAR}#{RED}c#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR} #{YELLOW}:#{CLEAR}#{YELLOW}d#{CLEAR} #{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR} #{YELLOW}}#{CLEAR}#{YELLOW}e#{CLEAR}#{YELLOW}\"#{CLEAR}",
- "[__FILE__, __LINE__, __ENCODING__]" => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]",
- ":self" => "#{YELLOW}:#{CLEAR}#{YELLOW}self#{CLEAR}",
- ":class" => "#{YELLOW}:#{CLEAR}#{YELLOW}class#{CLEAR}",
- "[:end, 2]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}end#{CLEAR}, #{BLUE}#{BOLD}2#{CLEAR}]",
- "[:>, 3]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}>#{CLEAR}, #{BLUE}#{BOLD}3#{CLEAR}]",
- "[:`, 4]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}`#{CLEAR}, #{BLUE}#{BOLD}4#{CLEAR}]",
- ":Hello ? world : nil" => "#{YELLOW}:#{CLEAR}#{YELLOW}Hello#{CLEAR} ? world : #{CYAN}#{BOLD}nil#{CLEAR}",
- 'raise "foo#{bar}baz"' => "raise #{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}bar#{RED}}#{CLEAR}#{RED}baz#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
- '["#{obj.inspect}"]' => "[#{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}obj.inspect#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}]",
- 'URI.parse "#{}"' => "#{BLUE}#{BOLD}#{UNDERLINE}URI#{CLEAR}.parse #{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
- "begin\nrescue\nend" => "#{GREEN}begin#{CLEAR}\n#{GREEN}rescue#{CLEAR}\n#{GREEN}end#{CLEAR}",
- "foo %w[bar]" => "foo #{RED}#{BOLD}%w[#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
- "foo %i[bar]" => "foo #{YELLOW}%i[#{CLEAR}#{YELLOW}bar#{CLEAR}#{YELLOW}]#{CLEAR}",
- "foo :@bar, baz, :@@qux, :$quux" => "foo #{YELLOW}:#{CLEAR}#{YELLOW}@bar#{CLEAR}, baz, #{YELLOW}:#{CLEAR}#{YELLOW}@@qux#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}$quux#{CLEAR}",
- "`echo`" => "#{RED}#{BOLD}`#{CLEAR}#{RED}echo#{CLEAR}#{RED}#{BOLD}`#{CLEAR}",
- "\t" => Reline::Unicode.escape_for_print("\t") == ' ' ? ' ' : "\t", # not ^I
- "foo(*%W(bar))" => "foo(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})",
- "$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}",
- "$&" => "#{GREEN}#{BOLD}$&#{CLEAR}",
- "__END__" => "#{GREEN}__END__#{CLEAR}",
- "foo\n__END__\nbar" => "foo\n#{GREEN}__END__#{CLEAR}\nbar",
- "foo\n<<A\0\0bar\nA\nbaz" => "foo\n#{RED}<<A#{CLEAR}^@^@bar\n#{RED}A#{CLEAR}\nbaz",
- "<<A+1\nA" => "#{RED}<<A#{CLEAR}+#{BLUE}#{BOLD}1#{CLEAR}\n#{RED}A#{CLEAR}",
- }
-
- tests.merge!({
- "4.5.6" => "#{MAGENTA}#{BOLD}4.5#{CLEAR}#{RED}#{REVERSE}.6#{CLEAR}",
- "\e[0m\n" => "#{RED}#{REVERSE}^[#{CLEAR}[#{BLUE}#{BOLD}0#{CLEAR}#{RED}#{REVERSE}m#{CLEAR}\n",
- "<<EOS\nhere\nEOS" => "#{RED}<<EOS#{CLEAR}\n#{RED}here#{CLEAR}\n#{RED}EOS#{CLEAR}",
- })
-
- # specific to Ruby 3.0+
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
- tests.merge!({
- "[1]]]\u0013" => "[#{BLUE}#{BOLD}1#{CLEAR}]#{RED}#{REVERSE}]#{CLEAR}#{RED}#{REVERSE}]#{CLEAR}#{RED}#{REVERSE}^S#{CLEAR}",
- })
- tests.merge!({
- "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}) #{RED}#{REVERSE}end#{CLEAR}",
- "nil = 1" => "#{RED}#{REVERSE}nil#{CLEAR} = #{BLUE}#{BOLD}1#{CLEAR}",
- "alias $x $1" => "#{GREEN}alias#{CLEAR} #{GREEN}#{BOLD}$x#{CLEAR} #{RED}#{REVERSE}$1#{CLEAR}",
- "class bad; end" => "#{GREEN}class#{CLEAR} #{RED}#{REVERSE}bad#{CLEAR}; #{GREEN}end#{CLEAR}",
- "def req(@a) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}@a#{CLEAR}) #{GREEN}end#{CLEAR}",
- })
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2.0')
- tests.merge!({
- "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}#{RED}#{REVERSE})#{CLEAR} #{RED}#{REVERSE}end#{CLEAR}",
- })
- end
- else
- tests.merge!({
- "[1]]]\u0013" => "[#{BLUE}#{BOLD}1#{CLEAR}]#{RED}#{REVERSE}]#{CLEAR}]^S",
- "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}) end",
- "nil = 1" => "#{CYAN}#{BOLD}nil#{CLEAR} = #{BLUE}#{BOLD}1#{CLEAR}",
- "alias $x $1" => "#{GREEN}alias#{CLEAR} #{GREEN}#{BOLD}$x#{CLEAR} #{GREEN}#{BOLD}$1#{CLEAR}",
- "class bad; end" => "#{GREEN}class#{CLEAR} bad; #{GREEN}end#{CLEAR}",
- "def req(@a) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(@a) #{GREEN}end#{CLEAR}",
- })
- end
-
- tests.each do |code, result|
- assert_equal_with_term(result, code, complete: true)
- assert_equal_with_term(result, code, complete: false)
-
- assert_equal_with_term(code, code, complete: true, tty: false)
- assert_equal_with_term(code, code, complete: false, tty: false)
-
- assert_equal_with_term(code, code, complete: true, colorable: false)
-
- assert_equal_with_term(code, code, complete: false, colorable: false)
-
- assert_equal_with_term(result, code, complete: true, tty: false, colorable: true)
-
- assert_equal_with_term(result, code, complete: false, tty: false, colorable: true)
- end
- end
-
- def test_colorize_code_with_local_variables
- code = "a /(b +1)/i"
- result_without_lvars = "a #{RED}#{BOLD}/#{CLEAR}#{RED}(b +1)#{CLEAR}#{RED}#{BOLD}/i#{CLEAR}"
- result_with_lvar = "a /(b #{BLUE}#{BOLD}+1#{CLEAR})/i"
- result_with_lvars = "a /(b +#{BLUE}#{BOLD}1#{CLEAR})/i"
-
- assert_equal_with_term(result_without_lvars, code)
- assert_equal_with_term(result_with_lvar, code, local_variables: ['a'])
- assert_equal_with_term(result_with_lvars, code, local_variables: ['a', 'b'])
- end
-
- def test_colorize_code_complete_true
- # `complete: true` behaviors. Warn end-of-file.
- {
- "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}bar#{CLEAR}",
- "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}foo#{CLEAR}",
- }.each do |code, result|
- assert_equal_with_term(result, code, complete: true)
-
- assert_equal_with_term(code, code, complete: true, tty: false)
-
- assert_equal_with_term(code, code, complete: true, colorable: false)
-
- assert_equal_with_term(result, code, complete: true, tty: false, colorable: true)
- end
- end
-
- def test_colorize_code_complete_false
- # `complete: false` behaviors. Do not warn end-of-file.
- {
- "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}bar#{CLEAR}",
- "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}",
- }.each do |code, result|
- assert_equal_with_term(result, code, complete: false)
-
- assert_equal_with_term(code, code, complete: false, tty: false)
-
- assert_equal_with_term(code, code, complete: false, colorable: false)
-
- assert_equal_with_term(result, code, complete: false, tty: false, colorable: true)
- end
- end
-
- def test_inspect_colorable
- {
- 1 => true,
- 2.3 => true,
- ['foo', :bar] => true,
- (a = []; a << a; a) => false,
- (h = {}; h[h] = h; h) => false,
- { a: 4 } => true,
- /reg/ => true,
- (1..3) => true,
- Object.new => false,
- Struct => true,
- Test => true,
- Struct.new(:a) => false,
- Struct.new(:a).new(1) => false,
- }.each do |object, result|
- assert_equal(result, IRB::Color.inspect_colorable?(object), "Case: inspect_colorable?(#{object.inspect})")
- end
- end
-
- private
-
- def with_term(tty: true)
- stdout = $stdout
- io = StringIO.new
- def io.tty?; true; end if tty
- $stdout = io
-
- env = ENV.to_h.dup
- ENV['TERM'] = 'xterm-256color'
-
- yield
- ensure
- $stdout = stdout
- ENV.replace(env) if env
- end
-
- def assert_equal_with_term(result, code, seq: nil, tty: true, **opts)
- actual = with_term(tty: tty) do
- if seq
- IRB::Color.colorize(code, seq, **opts)
- else
- IRB::Color.colorize_code(code, **opts)
- end
- end
- message = -> {
- args = [code.dump]
- args << seq.inspect if seq
- opts.each {|kwd, val| args << "#{kwd}: #{val}"}
- "Case: colorize#{seq ? "" : "_code"}(#{args.join(', ')})\nResult: #{humanized_literal(actual)}"
- }
- assert_equal(result, actual, message)
- end
-
- def humanized_literal(str)
- str
- .gsub(CLEAR, '@@@{CLEAR}')
- .gsub(BOLD, '@@@{BOLD}')
- .gsub(UNDERLINE, '@@@{UNDERLINE}')
- .gsub(REVERSE, '@@@{REVERSE}')
- .gsub(RED, '@@@{RED}')
- .gsub(GREEN, '@@@{GREEN}')
- .gsub(YELLOW, '@@@{YELLOW}')
- .gsub(BLUE, '@@@{BLUE}')
- .gsub(MAGENTA, '@@@{MAGENTA}')
- .gsub(CYAN, '@@@{CYAN}')
- .dump.gsub(/@@@/, '#')
- end
- end
-end
diff --git a/test/irb/test_color_printer.rb b/test/irb/test_color_printer.rb
deleted file mode 100644
index c2c624d868..0000000000
--- a/test/irb/test_color_printer.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: false
-require 'irb/color_printer'
-require 'stringio'
-
-require_relative "helper"
-
-module TestIRB
- class ColorPrinterTest < TestCase
- CLEAR = "\e[0m"
- BOLD = "\e[1m"
- RED = "\e[31m"
- GREEN = "\e[32m"
- BLUE = "\e[34m"
- CYAN = "\e[36m"
-
- def setup
- super
- if IRB.respond_to?(:conf)
- @colorize, IRB.conf[:USE_COLORIZE] = IRB.conf[:USE_COLORIZE], true
- end
- @get_screen_size = Reline.method(:get_screen_size)
- Reline.instance_eval { undef :get_screen_size }
- def Reline.get_screen_size
- [36, 80]
- end
- end
-
- def teardown
- Reline.instance_eval { undef :get_screen_size }
- Reline.define_singleton_method(:get_screen_size, @get_screen_size)
- if instance_variable_defined?(:@colorize)
- IRB.conf[:USE_COLORIZE] = @colorize
- end
- super
- end
-
- IRBTestColorPrinter = Struct.new(:a)
-
- def test_color_printer
- {
- 1 => "#{BLUE}#{BOLD}1#{CLEAR}\n",
- "a\nb" => %[#{RED}#{BOLD}"#{CLEAR}#{RED}a\\nb#{CLEAR}#{RED}#{BOLD}"#{CLEAR}\n],
- IRBTestColorPrinter.new('test') => "#{GREEN}#<struct TestIRB::ColorPrinterTest::IRBTestColorPrinter#{CLEAR} a#{GREEN}=#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{RED}test#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}\n",
- Ripper::Lexer.new('1').scan => "[#{GREEN}#<Ripper::Lexer::Elem:#{CLEAR} on_int@1:0 END token: #{RED}#{BOLD}\"#{CLEAR}#{RED}1#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}#{GREEN}>#{CLEAR}]\n",
- Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[#{CYAN}#{BOLD}__FILE__#{CLEAR}, #{CYAN}#{BOLD}__LINE__#{CLEAR}, #{CYAN}#{BOLD}__ENCODING__#{CLEAR}]\n",
- }.each do |object, result|
- actual = with_term { IRB::ColorPrinter.pp(object, '') }
- assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')")
- end
- end
-
- private
-
- def with_term
- stdout = $stdout
- io = StringIO.new
- def io.tty?; true; end
- $stdout = io
-
- env = ENV.to_h.dup
- ENV['TERM'] = 'xterm-256color'
-
- yield
- ensure
- $stdout = stdout
- ENV.replace(env) if env
- end
- end
-end
diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb
deleted file mode 100644
index ec2d1f92df..0000000000
--- a/test/irb/test_command.rb
+++ /dev/null
@@ -1,1001 +0,0 @@
-# frozen_string_literal: false
-require "irb"
-
-require_relative "helper"
-
-module TestIRB
- # In case when RDoc becomes a bundled gem, we may not be able to load it when running tests
- # in ruby/ruby
- HAS_RDOC = begin
- require "rdoc"
- true
- rescue LoadError
- false
- end
-
- class CommandTestCase < TestCase
- def setup
- @pwd = Dir.pwd
- @tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}")
- begin
- Dir.mkdir(@tmpdir)
- rescue Errno::EEXIST
- FileUtils.rm_rf(@tmpdir)
- Dir.mkdir(@tmpdir)
- end
- Dir.chdir(@tmpdir)
- setup_envs(home: @tmpdir)
- save_encodings
- IRB.instance_variable_get(:@CONF).clear
- IRB.instance_variable_set(:@existing_rc_name_generators, nil)
- @is_win = (RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/)
- end
-
- def teardown
- teardown_envs
- Dir.chdir(@pwd)
- FileUtils.rm_rf(@tmpdir)
- restore_encodings
- end
-
- def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
- capture_output do
- IRB.init_config(nil)
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- IRB.conf[:USE_PAGER] = false
- IRB.conf.merge!(conf)
- input = TestInputMethod.new(lines)
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
- irb.context.return_format = "=> %s\n"
- irb.context.irb_path = irb_path if irb_path
- IRB.conf[:MAIN_CONTEXT] = irb.context
- irb.eval_input
- end
- end
- end
-
- class FrozenObjectTest < CommandTestCase
- def test_calling_command_on_a_frozen_main
- main = Object.new.freeze
-
- out, err = execute_lines(
- "irb_info",
- main: main
- )
- assert_empty(err)
- assert_match(/RUBY_PLATFORM/, out)
- end
- end
-
- class InfoTest < CommandTestCase
- def setup
- super
- @locals_backup = ENV.delete("LANG"), ENV.delete("LC_ALL")
- end
-
- def teardown
- super
- ENV["LANG"], ENV["LC_ALL"] = @locals_backup
- end
-
- def test_irb_info_multiline
- FileUtils.touch("#{@tmpdir}/.inputrc")
- FileUtils.touch("#{@tmpdir}/.irbrc")
- FileUtils.touch("#{@tmpdir}/_irbrc")
-
- out, err = execute_lines(
- "irb_info",
- conf: { USE_MULTILINE: true, USE_SINGLELINE: false }
- )
-
- expected = %r{
- Ruby\sversion:\s.+\n
- IRB\sversion:\sirb\s.+\n
- InputMethod:\sAbstract\sInputMethod\n
- Completion: .+\n
- \.irbrc\spaths:.*\.irbrc.*_irbrc\n
- RUBY_PLATFORM:\s.+\n
- East\sAsian\sAmbiguous\sWidth:\s\d\n
- #{@is_win ? 'Code\spage:\s\d+\n' : ''}
- }x
-
- assert_empty err
- assert_match expected, out
- end
-
- def test_irb_info_singleline
- FileUtils.touch("#{@tmpdir}/.inputrc")
- FileUtils.touch("#{@tmpdir}/.irbrc")
-
- out, err = execute_lines(
- "irb_info",
- conf: { USE_MULTILINE: false, USE_SINGLELINE: true }
- )
-
- expected = %r{
- Ruby\sversion:\s.+\n
- IRB\sversion:\sirb\s.+\n
- InputMethod:\sAbstract\sInputMethod\n
- Completion: .+\n
- \.irbrc\spaths:\s.+\n
- RUBY_PLATFORM:\s.+\n
- East\sAsian\sAmbiguous\sWidth:\s\d\n
- #{@is_win ? 'Code\spage:\s\d+\n' : ''}
- }x
-
- assert_empty err
- assert_match expected, out
- end
-
- def test_irb_info_multiline_without_rc_files
- inputrc_backup = ENV["INPUTRC"]
- ENV["INPUTRC"] = "unknown_inpurc"
- ext_backup = IRB::IRBRC_EXT
- IRB.__send__(:remove_const, :IRBRC_EXT)
- IRB.const_set(:IRBRC_EXT, "unknown_ext")
-
- out, err = execute_lines(
- "irb_info",
- conf: { USE_MULTILINE: true, USE_SINGLELINE: false }
- )
-
- expected = %r{
- Ruby\sversion:\s.+\n
- IRB\sversion:\sirb\s.+\n
- InputMethod:\sAbstract\sInputMethod\n
- Completion: .+\n
- RUBY_PLATFORM:\s.+\n
- East\sAsian\sAmbiguous\sWidth:\s\d\n
- #{@is_win ? 'Code\spage:\s\d+\n' : ''}
- }x
-
- assert_empty err
- assert_match expected, out
- ensure
- ENV["INPUTRC"] = inputrc_backup
- IRB.__send__(:remove_const, :IRBRC_EXT)
- IRB.const_set(:IRBRC_EXT, ext_backup)
- end
-
- def test_irb_info_singleline_without_rc_files
- inputrc_backup = ENV["INPUTRC"]
- ENV["INPUTRC"] = "unknown_inpurc"
- ext_backup = IRB::IRBRC_EXT
- IRB.__send__(:remove_const, :IRBRC_EXT)
- IRB.const_set(:IRBRC_EXT, "unknown_ext")
-
- out, err = execute_lines(
- "irb_info",
- conf: { USE_MULTILINE: false, USE_SINGLELINE: true }
- )
-
- expected = %r{
- Ruby\sversion:\s.+\n
- IRB\sversion:\sirb\s.+\n
- InputMethod:\sAbstract\sInputMethod\n
- Completion: .+\n
- RUBY_PLATFORM:\s.+\n
- East\sAsian\sAmbiguous\sWidth:\s\d\n
- #{@is_win ? 'Code\spage:\s\d+\n' : ''}
- }x
-
- assert_empty err
- assert_match expected, out
- ensure
- ENV["INPUTRC"] = inputrc_backup
- IRB.__send__(:remove_const, :IRBRC_EXT)
- IRB.const_set(:IRBRC_EXT, ext_backup)
- end
-
- def test_irb_info_lang
- FileUtils.touch("#{@tmpdir}/.inputrc")
- FileUtils.touch("#{@tmpdir}/.irbrc")
- ENV["LANG"] = "ja_JP.UTF-8"
- ENV["LC_ALL"] = "en_US.UTF-8"
-
- out, err = execute_lines(
- "irb_info",
- conf: { USE_MULTILINE: true, USE_SINGLELINE: false }
- )
-
- expected = %r{
- Ruby\sversion: .+\n
- IRB\sversion:\sirb .+\n
- InputMethod:\sAbstract\sInputMethod\n
- Completion: .+\n
- \.irbrc\spaths: .+\n
- RUBY_PLATFORM: .+\n
- LANG\senv:\sja_JP\.UTF-8\n
- LC_ALL\senv:\sen_US\.UTF-8\n
- East\sAsian\sAmbiguous\sWidth:\s\d\n
- }x
-
- assert_empty err
- assert_match expected, out
- end
- end
-
- class MeasureTest < CommandTestCase
- def test_measure
- conf = {
- PROMPT: {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> '
- }
- },
- PROMPT_MODE: :DEFAULT,
- MEASURE: false
- }
-
- c = Class.new(Object)
- out, err = execute_lines(
- "measure\n",
- "3\n",
- "measure :off\n",
- "3\n",
- "measure :on\n",
- "3\n",
- "measure :off\n",
- "3\n",
- conf: conf,
- main: c
- )
-
- assert_empty err
- assert_match(/\A(TIME is added\.\nprocessing time: .+\n=> 3\n=> 3\n){2}/, out)
- assert_empty(c.class_variables)
- end
-
- def test_measure_keeps_previous_value
- conf = {
- PROMPT: {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> '
- }
- },
- PROMPT_MODE: :DEFAULT,
- MEASURE: false
- }
-
- c = Class.new(Object)
- out, err = execute_lines(
- "measure\n",
- "3\n",
- "_\n",
- conf: conf,
- main: c
- )
-
- assert_empty err
- assert_match(/\ATIME is added\.\nprocessing time: .+\n=> 3\nprocessing time: .+\n=> 3/, out)
- assert_empty(c.class_variables)
- end
-
- def test_measure_enabled_by_rc
- conf = {
- PROMPT: {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> '
- }
- },
- PROMPT_MODE: :DEFAULT,
- MEASURE: true
- }
-
- out, err = execute_lines(
- "3\n",
- "measure :off\n",
- "3\n",
- conf: conf,
- )
-
- assert_empty err
- assert_match(/\Aprocessing time: .+\n=> 3\n=> 3\n/, out)
- end
-
- def test_measure_enabled_by_rc_with_custom
- measuring_proc = proc { |line, line_no, &block|
- time = Time.now
- result = block.()
- puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE]
- result
- }
- conf = {
- PROMPT: {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> '
- }
- },
- PROMPT_MODE: :DEFAULT,
- MEASURE: true,
- MEASURE_PROC: { CUSTOM: measuring_proc }
- }
-
- out, err = execute_lines(
- "3\n",
- "measure :off\n",
- "3\n",
- conf: conf,
- )
- assert_empty err
- assert_match(/\Acustom processing time: .+\n=> 3\n=> 3\n/, out)
- end
-
- def test_measure_with_custom
- measuring_proc = proc { |line, line_no, &block|
- time = Time.now
- result = block.()
- puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE]
- result
- }
- conf = {
- PROMPT: {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> '
- }
- },
- PROMPT_MODE: :DEFAULT,
- MEASURE: false,
- MEASURE_PROC: { CUSTOM: measuring_proc }
- }
- out, err = execute_lines(
- "3\n",
- "measure\n",
- "3\n",
- "measure :off\n",
- "3\n",
- conf: conf
- )
-
- assert_empty err
- assert_match(/\A=> 3\nCUSTOM is added\.\ncustom processing time: .+\n=> 3\n=> 3\n/, out)
- end
-
- def test_measure_toggle
- conf = {
- PROMPT: {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> '
- }
- },
- PROMPT_MODE: :DEFAULT,
- MEASURE: false,
- MEASURE_PROC: {
- FOO: proc { |&block| puts 'foo'; block.call },
- BAR: proc { |&block| puts 'bar'; block.call }
- }
- }
- out, err = execute_lines(
- "measure :foo\n",
- "1\n",
- "measure :on, :bar\n",
- "2\n",
- "measure :off, :foo\n",
- "3\n",
- "measure :off, :bar\n",
- "4\n",
- conf: conf
- )
-
- assert_empty err
- assert_match(/\AFOO is added\.\nfoo\n=> 1\nBAR is added\.\nbar\nfoo\n=> 2\nbar\n=> 3\n=> 4\n/, out)
- end
-
- def test_measure_with_proc_warning
- conf = {
- PROMPT: {
- DEFAULT: {
- PROMPT_I: '> ',
- PROMPT_S: '> ',
- PROMPT_C: '> '
- }
- },
- PROMPT_MODE: :DEFAULT,
- MEASURE: false,
- }
- c = Class.new(Object)
- out, err = execute_lines(
- "3\n",
- "measure do\n",
- "3\n",
- conf: conf,
- main: c
- )
-
- assert_match(/to add custom measure/, err)
- assert_match(/\A=> 3\n=> 3\n/, out)
- assert_empty(c.class_variables)
- end
- end
-
- class IrbSourceTest < CommandTestCase
- def test_irb_source
- File.write("#{@tmpdir}/a.rb", "a = 'hi'\n")
- out, err = execute_lines(
- "a = 'bug17564'\n",
- "a\n",
- "irb_source '#{@tmpdir}/a.rb'\n",
- "a\n",
- )
- assert_empty err
- assert_pattern_list([
- /=> "bug17564"\n/,
- /=> "bug17564"\n/,
- / => "hi"\n/,
- / => "hi"\n/,
- ], out)
- end
-
- def test_irb_source_without_argument
- out, err = execute_lines(
- "irb_source\n",
- )
- assert_empty err
- assert_match(/Please specify the file name./, out)
- end
- end
-
- class IrbLoadTest < CommandTestCase
- def test_irb_load
- File.write("#{@tmpdir}/a.rb", "a = 'hi'\n")
- out, err = execute_lines(
- "a = 'bug17564'\n",
- "a\n",
- "irb_load '#{@tmpdir}/a.rb'\n",
- "a\n",
- )
- assert_empty err
- assert_pattern_list([
- /=> "bug17564"\n/,
- /=> "bug17564"\n/,
- / => "hi"\n/,
- / => "bug17564"\n/,
- ], out)
- end
-
- def test_irb_load_without_argument
- out, err = execute_lines(
- "irb_load\n",
- )
-
- assert_empty err
- assert_match(/Please specify the file name./, out)
- end
- end
-
- class WorkspaceCommandTestCase < CommandTestCase
- def setup
- super
- # create Foo under the test class's namespace so it doesn't pollute global namespace
- self.class.class_eval <<~RUBY
- class Foo; end
- RUBY
- end
- end
-
- class CwwsTest < WorkspaceCommandTestCase
- def test_cwws_returns_the_current_workspace_object
- out, err = execute_lines(
- "cwws"
- )
-
- assert_empty err
- assert_include(out, "Current workspace: #{self}")
- end
- end
-
- class PushwsTest < WorkspaceCommandTestCase
- def test_pushws_switches_to_new_workspace_and_pushes_the_current_one_to_the_stack
- out, err = execute_lines(
- "pushws #{self.class}::Foo.new",
- "self.class",
- "popws",
- "self.class"
- )
- assert_empty err
-
- assert_match(/=> #{self.class}::Foo\n/, out)
- assert_match(/=> #{self.class}\n$/, out)
- end
-
- def test_pushws_extends_the_new_workspace_with_command_bundle
- out, err = execute_lines(
- "pushws Object.new",
- "self.singleton_class.ancestors"
- )
- assert_empty err
- assert_include(out, "IRB::ExtendCommandBundle")
- end
-
- def test_pushws_prints_workspace_stack_when_no_arg_is_given
- out, err = execute_lines(
- "pushws",
- )
- assert_empty err
- assert_include(out, "[#<TestIRB::PushwsTe...>]")
- end
-
- def test_pushws_without_argument_swaps_the_top_two_workspaces
- out, err = execute_lines(
- "pushws #{self.class}::Foo.new",
- "self.class",
- "pushws",
- "self.class"
- )
- assert_empty err
- assert_match(/=> #{self.class}::Foo\n/, out)
- assert_match(/=> #{self.class}\n$/, out)
- end
- end
-
- class WorkspacesTest < WorkspaceCommandTestCase
- def test_workspaces_returns_the_stack_of_workspaces
- out, err = execute_lines(
- "pushws #{self.class}::Foo.new\n",
- "workspaces",
- )
-
- assert_empty err
- assert_match(/\[#<TestIRB::Workspac...>, #<TestIRB::Workspac...>\]\n/, out)
- end
- end
-
- class PopwsTest < WorkspaceCommandTestCase
- def test_popws_replaces_the_current_workspace_with_the_previous_one
- out, err = execute_lines(
- "pushws Foo.new\n",
- "popws\n",
- "cwws\n",
- "self.class",
- )
- assert_empty err
- assert_include(out, "=> #{self.class}")
- end
-
- def test_popws_prints_help_message_if_the_workspace_is_empty
- out, err = execute_lines(
- "popws\n",
- )
- assert_empty err
- assert_match(/\[#<TestIRB::PopwsTes...>\]\n/, out)
- end
- end
-
- class ChwsTest < WorkspaceCommandTestCase
- def test_chws_replaces_the_current_workspace
- out, err = execute_lines(
- "chws #{self.class}::Foo.new\n",
- "cwws\n",
- "self.class\n"
- )
- assert_empty err
- assert_include(out, "Current workspace: #<#{self.class.name}::Foo")
- assert_include(out, "=> #{self.class}::Foo")
- end
-
- def test_chws_does_nothing_when_receiving_no_argument
- out, err = execute_lines(
- "chws\n",
- )
- assert_empty err
- assert_include(out, "Current workspace: #{self}")
- end
- end
-
- class WhereamiTest < CommandTestCase
- def test_whereami
- out, err = execute_lines(
- "whereami\n",
- )
- assert_empty err
- assert_match(/^From: .+ @ line \d+ :\n/, out)
- end
-
- def test_whereami_alias
- out, err = execute_lines(
- "@\n",
- )
- assert_empty err
- assert_match(/^From: .+ @ line \d+ :\n/, out)
- end
- end
-
- class LsTest < CommandTestCase
- def test_ls
- out, err = execute_lines(
- "class P\n",
- " def m() end\n",
- " def m2() end\n",
- "end\n",
-
- "class C < P\n",
- " def m1() end\n",
- " def m2() end\n",
- "end\n",
-
- "module M\n",
- " def m1() end\n",
- " def m3() end\n",
- "end\n",
-
- "module M2\n",
- " include M\n",
- " def m4() end\n",
- "end\n",
-
- "obj = C.new\n",
- "obj.instance_variable_set(:@a, 1)\n",
- "obj.extend M2\n",
- "def obj.m5() end\n",
- "ls obj\n",
- )
-
- assert_empty err
- assert_match(/^instance variables:\s+@a\n/m, out)
- assert_match(/P#methods:\s+m\n/m, out)
- assert_match(/C#methods:\s+m2\n/m, out)
- assert_match(/M#methods:\s+m1\s+m3\n/m, out)
- assert_match(/M2#methods:\s+m4\n/m, out)
- assert_match(/C.methods:\s+m5\n/m, out)
- end
-
- def test_ls_class
- out, err = execute_lines(
- "module M1\n",
- " def m2; end\n",
- " def m3; end\n",
- "end\n",
-
- "class C1\n",
- " def m1; end\n",
- " def m2; end\n",
- "end\n",
-
- "class C2 < C1\n",
- " include M1\n",
- " def m3; end\n",
- " def m4; end\n",
- " def self.m3; end\n",
- " def self.m5; end\n",
- "end\n",
- "ls C2"
- )
-
- assert_empty err
- assert_match(/C2.methods:\s+m3\s+m5\n/, out)
- assert_match(/C2#methods:\s+m3\s+m4\n.*M1#methods:\s+m2\n.*C1#methods:\s+m1\n/, out)
- assert_not_match(/Module#methods/, out)
- assert_not_match(/Class#methods/, out)
- end
-
- def test_ls_module
- out, err = execute_lines(
- "module M1\n",
- " def m1; end\n",
- " def m2; end\n",
- "end\n",
-
- "module M2\n",
- " include M1\n",
- " def m1; end\n",
- " def m3; end\n",
- " def self.m4; end\n",
- "end\n",
- "ls M2"
- )
-
- assert_empty err
- assert_match(/M2\.methods:\s+m4\n/, out)
- assert_match(/M2#methods:\s+m1\s+m3\n.*M1#methods:\s+m2\n/, out)
- assert_not_match(/Module#methods/, out)
- end
-
- def test_ls_instance
- out, err = execute_lines(
- "class Foo; def bar; end; end\n",
- "ls Foo.new"
- )
-
- assert_empty err
- assert_match(/Foo#methods:\s+bar/, out)
- # don't duplicate
- assert_not_match(/Foo#methods:\s+bar\n.*Foo#methods/, out)
- end
-
- def test_ls_grep
- out, err = execute_lines("ls 42\n")
- assert_empty err
- assert_match(/times/, out)
- assert_match(/polar/, out)
-
- [
- "ls 42, grep: /times/\n",
- "ls 42 -g times\n",
- "ls 42 -G times\n",
- ].each do |line|
- out, err = execute_lines(line)
- assert_empty err
- assert_match(/times/, out)
- assert_not_match(/polar/, out)
- end
- end
-
- def test_ls_grep_empty
- out, err = execute_lines("ls\n")
- assert_empty err
- assert_match(/assert/, out)
- assert_match(/refute/, out)
-
- [
- "ls grep: /assert/\n",
- "ls -g assert\n",
- "ls -G assert\n",
- ].each do |line|
- out, err = execute_lines(line)
- assert_empty err
- assert_match(/assert/, out)
- assert_not_match(/refute/, out)
- end
- end
-
- def test_ls_with_eval_error
- [
- "ls raise(Exception,'foo')\n",
- "ls raise(Exception,'foo'), grep: /./\n",
- "ls Integer, grep: raise(Exception,'foo')\n",
- ].each do |line|
- out, err = execute_lines(line)
- assert_empty err
- assert_match(/Exception: foo/, out)
- assert_not_match(/Maybe IRB bug!/, out)
- end
- end
-
- def test_ls_with_no_singleton_class
- out, err = execute_lines(
- "ls 42",
- )
- assert_empty err
- assert_match(/Comparable#methods:\s+/, out)
- assert_match(/Numeric#methods:\s+/, out)
- assert_match(/Integer#methods:\s+/, out)
- end
- end
-
- class ShowDocTest < CommandTestCase
- if HAS_RDOC
- def test_show_doc
- out, err = execute_lines("show_doc String#gsub")
-
- # the former is what we'd get without document content installed, like on CI
- # the latter is what we may get locally
- possible_rdoc_output = [/Nothing known about String#gsub/, /gsub\(pattern\)/]
- assert_not_include err, "[Deprecation]"
- assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `show_doc` command to match one of the possible outputs. Got:\n#{out}")
- ensure
- # this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/command/help.rb" }
- end
-
- def test_ri
- out, err = execute_lines("ri String#gsub")
-
- # the former is what we'd get without document content installed, like on CI
- # the latter is what we may get locally
- possible_rdoc_output = [/Nothing known about String#gsub/, /gsub\(pattern\)/]
- assert_not_include err, "[Deprecation]"
- assert(possible_rdoc_output.any? { |output| output.match?(out) }, "Expect the `ri` command to match one of the possible outputs. Got:\n#{out}")
- ensure
- # this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/command/help.rb" }
- end
- end
-
- def test_show_doc_without_rdoc
- _, err = without_rdoc do
- execute_lines("show_doc String#gsub")
- end
-
- assert_include(err, "Can't display document because `rdoc` is not installed.\n")
- ensure
- # this is the only way to reset the redefined method without coupling the test with its implementation
- EnvUtil.suppress_warning { load "irb/command/help.rb" }
- end
- end
-
- class EditTest < CommandTestCase
- def setup
- @original_visual = ENV["VISUAL"]
- @original_editor = ENV["EDITOR"]
- # noop the command so nothing gets executed
- ENV["VISUAL"] = ": code"
- ENV["EDITOR"] = ": code2"
- end
-
- def teardown
- ENV["VISUAL"] = @original_visual
- ENV["EDITOR"] = @original_editor
- end
-
- def test_edit_without_arg
- out, err = execute_lines(
- "edit",
- irb_path: __FILE__
- )
-
- assert_empty err
- assert_match("path: #{__FILE__}", out)
- assert_match("command: ': code'", out)
- end
-
- def test_edit_without_arg_and_non_existing_irb_path
- out, err = execute_lines(
- "edit",
- irb_path: '/path/to/file.rb(irb)'
- )
-
- assert_empty err
- assert_match(/Can not find file: \/path\/to\/file\.rb\(irb\)/, out)
- end
-
- def test_edit_with_path
- out, err = execute_lines(
- "edit #{__FILE__}"
- )
-
- assert_empty err
- assert_match("path: #{__FILE__}", out)
- assert_match("command: ': code'", out)
- end
-
- def test_edit_with_non_existing_path
- out, err = execute_lines(
- "edit test_cmd_non_existing_path.rb"
- )
-
- assert_empty err
- assert_match(/Can not find file: test_cmd_non_existing_path\.rb/, out)
- end
-
- def test_edit_with_constant
- out, err = execute_lines(
- "edit IRB::Irb"
- )
-
- assert_empty err
- assert_match(/path: .*\/lib\/irb\.rb/, out)
- assert_match("command: ': code'", out)
- end
-
- def test_edit_with_class_method
- out, err = execute_lines(
- "edit IRB.start"
- )
-
- assert_empty err
- assert_match(/path: .*\/lib\/irb\.rb/, out)
- assert_match("command: ': code'", out)
- end
-
- def test_edit_with_instance_method
- out, err = execute_lines(
- "edit IRB::Irb#run"
- )
-
- assert_empty err
- assert_match(/path: .*\/lib\/irb\.rb/, out)
- assert_match("command: ': code'", out)
- end
-
- def test_edit_with_editor_env_var
- ENV.delete("VISUAL")
-
- out, err = execute_lines(
- "edit",
- irb_path: __FILE__
- )
-
- assert_empty err
- assert_match("path: #{__FILE__}", out)
- assert_match("command: ': code2'", out)
- end
- end
-
- class HistoryCmdTest < CommandTestCase
- def teardown
- TestInputMethod.send(:remove_const, "HISTORY") if defined?(TestInputMethod::HISTORY)
- super
- end
-
- def test_history
- TestInputMethod.const_set("HISTORY", %w[foo bar baz])
-
- out, err = without_rdoc do
- execute_lines("history")
- end
-
- assert_include(out, <<~EOF)
- 2: baz
- 1: bar
- 0: foo
- EOF
- assert_empty err
- end
-
- def test_multiline_history_with_truncation
- TestInputMethod.const_set("HISTORY", ["foo", "bar", <<~INPUT])
- [].each do |x|
- puts x
- end
- INPUT
-
- out, err = without_rdoc do
- execute_lines("hist")
- end
-
- assert_include(out, <<~EOF)
- 2: [].each do |x|
- puts x
- ...
- 1: bar
- 0: foo
- EOF
- assert_empty err
- end
-
- def test_history_grep
- TestInputMethod.const_set("HISTORY", ["foo", "bar", <<~INPUT])
- [].each do |x|
- puts x
- end
- INPUT
-
- out, err = without_rdoc do
- execute_lines("hist -g each\n")
- end
-
- assert_include(out, <<~EOF)
- 2: [].each do |x|
- puts x
- ...
- EOF
- assert_not_include(out, <<~EOF)
- foo
- EOF
- assert_not_include(out, <<~EOF)
- bar
- EOF
- assert_empty err
- end
-
- end
-
- class HelperMethodInsallTest < CommandTestCase
- def test_helper_method_install
- IRB::ExtendCommandBundle.module_eval do
- def foobar
- "test_helper_method_foobar"
- end
- end
-
- out, err = execute_lines("foobar.upcase")
- assert_empty err
- assert_include(out, '=> "TEST_HELPER_METHOD_FOOBAR"')
- ensure
- IRB::ExtendCommandBundle.remove_method :foobar
- end
- end
-end
diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb
deleted file mode 100644
index c9a0eafa3d..0000000000
--- a/test/irb/test_completion.rb
+++ /dev/null
@@ -1,317 +0,0 @@
-# frozen_string_literal: false
-require "pathname"
-require "irb"
-
-require_relative "helper"
-
-module TestIRB
- class CompletionTest < TestCase
- def completion_candidates(target, bind)
- IRB::RegexpCompletor.new.completion_candidates('', target, '', bind: bind)
- end
-
- def doc_namespace(target, bind)
- IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind)
- end
-
- class CommandCompletionTest < CompletionTest
- def test_command_completion
- completor = IRB::RegexpCompletor.new
- binding.eval("some_var = 1")
- # completion for help command's argument should only include command names
- assert_include(completor.completion_candidates('help ', 's', '', bind: binding), 'show_source')
- assert_not_include(completor.completion_candidates('help ', 's', '', bind: binding), 'some_var')
-
- assert_include(completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source')
- assert_not_include(completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source')
- end
- end
-
- class MethodCompletionTest < CompletionTest
- def test_complete_string
- assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase")
- # completing 'foo bar'.up
- assert_include(completion_candidates("bar'.up", binding), "bar'.upcase")
- assert_equal("String.upcase", doc_namespace("'foo'.upcase", binding))
- end
-
- def test_complete_regexp
- assert_include(completion_candidates("/foo/.ma", binding), "/foo/.match")
- # completing /foo bar/.ma
- assert_include(completion_candidates("bar/.ma", binding), "bar/.match")
- assert_equal("Regexp.match", doc_namespace("/foo/.match", binding))
- end
-
- def test_complete_array
- assert_include(completion_candidates("[].an", binding), "[].any?")
- assert_equal("Array.any?", doc_namespace("[].any?", binding))
- end
-
- def test_complete_hash_and_proc
- # hash
- assert_include(completion_candidates("{}.an", binding), "{}.any?")
- assert_equal(["Hash.any?", "Proc.any?"], doc_namespace("{}.any?", binding))
-
- # proc
- assert_include(completion_candidates("{}.bin", binding), "{}.binding")
- assert_equal(["Hash.binding", "Proc.binding"], doc_namespace("{}.binding", binding))
- end
-
- def test_complete_numeric
- assert_include(completion_candidates("1.positi", binding), "1.positive?")
- assert_equal("Integer.positive?", doc_namespace("1.positive?", binding))
-
- assert_include(completion_candidates("1r.positi", binding), "1r.positive?")
- assert_equal("Rational.positive?", doc_namespace("1r.positive?", binding))
-
- assert_include(completion_candidates("0xFFFF.positi", binding), "0xFFFF.positive?")
- assert_equal("Integer.positive?", doc_namespace("0xFFFF.positive?", binding))
-
- assert_empty(completion_candidates("1i.positi", binding))
- end
-
- def test_complete_symbol
- assert_include(completion_candidates(":foo.to_p", binding), ":foo.to_proc")
- assert_equal("Symbol.to_proc", doc_namespace(":foo.to_proc", binding))
- end
-
- def test_complete_class
- assert_include(completion_candidates("String.ne", binding), "String.new")
- assert_equal("String.new", doc_namespace("String.new", binding))
- end
- end
-
- class RequireComepletionTest < CompletionTest
- def test_complete_require
- candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
- %w['irb/init 'irb/ruby-lex].each do |word|
- assert_include candidates, word
- end
- # Test cache
- candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
- %w['irb/init 'irb/ruby-lex].each do |word|
- assert_include candidates, word
- end
- # Test string completion not disturbed by require completion
- candidates = IRB::RegexpCompletor.new.completion_candidates("'string ", "'.", "", bind: binding)
- assert_include candidates, "'.upcase"
- end
-
- def test_complete_require_with_pathname_in_load_path
- temp_dir = Dir.mktmpdir
- File.write(File.join(temp_dir, "foo.rb"), "test")
- test_path = Pathname.new(temp_dir)
- $LOAD_PATH << test_path
-
- candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
- assert_include candidates, "'foo"
- ensure
- $LOAD_PATH.pop if test_path
- FileUtils.remove_entry(temp_dir) if temp_dir
- end
-
- def test_complete_require_with_string_convertable_in_load_path
- temp_dir = Dir.mktmpdir
- File.write(File.join(temp_dir, "foo.rb"), "test")
- object = Object.new
- object.define_singleton_method(:to_s) { temp_dir }
- $LOAD_PATH << object
-
- candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
- assert_include candidates, "'foo"
- ensure
- $LOAD_PATH.pop if object
- FileUtils.remove_entry(temp_dir) if temp_dir
- end
-
- def test_complete_require_with_malformed_object_in_load_path
- object = Object.new
- def object.to_s; raise; end
- $LOAD_PATH << object
-
- assert_nothing_raised do
- IRB::RegexpCompletor.new.completion_candidates("require ", "'foo", "", bind: binding)
- end
- ensure
- $LOAD_PATH.pop if object
- end
-
- def test_complete_require_library_name_first
- # Test that library name is completed first with subdirectories
- candidates = IRB::RegexpCompletor.new.completion_candidates("require ", "'irb", "", bind: binding)
- assert_equal "'irb", candidates.first
- end
-
- def test_complete_require_relative
- candidates = Dir.chdir(__dir__ + "/../..") do
- IRB::RegexpCompletor.new.completion_candidates("require_relative ", "'lib/irb", "", bind: binding)
- end
- %w['lib/irb/init 'lib/irb/ruby-lex].each do |word|
- assert_include candidates, word
- end
- # Test cache
- candidates = Dir.chdir(__dir__ + "/../..") do
- IRB::RegexpCompletor.new.completion_candidates("require_relative ", "'lib/irb", "", bind: binding)
- end
- %w['lib/irb/init 'lib/irb/ruby-lex].each do |word|
- assert_include candidates, word
- end
- end
- end
-
- class VariableCompletionTest < CompletionTest
- def test_complete_variable
- # Bug fix issues https://github.com/ruby/irb/issues/368
- # Variables other than `str_example` and `@str_example` are defined to ensure that irb completion does not cause unintended behavior
- str_example = ''
- @str_example = ''
- private_methods = ''
- methods = ''
- global_variables = ''
- local_variables = ''
- instance_variables = ''
-
- # suppress "assigned but unused variable" warning
- str_example.clear
- @str_example.clear
- private_methods.clear
- methods.clear
- global_variables.clear
- local_variables.clear
- instance_variables.clear
-
- assert_include(completion_candidates("str_examp", binding), "str_example")
- assert_equal("String", doc_namespace("str_example", binding))
- assert_equal("String.to_s", doc_namespace("str_example.to_s", binding))
-
- assert_include(completion_candidates("@str_examp", binding), "@str_example")
- assert_equal("String", doc_namespace("@str_example", binding))
- assert_equal("String.to_s", doc_namespace("@str_example.to_s", binding))
- end
-
- def test_complete_sort_variables
- xzy, xzy_1, xzy2 = '', '', ''
-
- xzy.clear
- xzy_1.clear
- xzy2.clear
-
- candidates = completion_candidates("xz", binding)
- assert_equal(%w[xzy xzy2 xzy_1], candidates)
- end
- end
-
- class ConstantCompletionTest < CompletionTest
- class Foo
- B3 = 1
- B1 = 1
- B2 = 1
- end
-
- def test_complete_constants
- assert_equal(["Foo"], completion_candidates("Fo", binding))
- assert_equal(["Foo::B1", "Foo::B2", "Foo::B3"], completion_candidates("Foo::B", binding))
- assert_equal(["Foo::B1.positive?"], completion_candidates("Foo::B1.pos", binding))
-
- assert_equal(["::Forwardable"], completion_candidates("::Fo", binding))
- assert_equal("Forwardable", doc_namespace("::Forwardable", binding))
- end
- end
-
- def test_not_completing_empty_string
- assert_equal([], completion_candidates("", binding))
- assert_equal([], completion_candidates(" ", binding))
- assert_equal([], completion_candidates("\t", binding))
- assert_equal(nil, doc_namespace("", binding))
- end
-
- def test_complete_symbol
- symbols = %w"UTF-16LE UTF-7".map do |enc|
- "K".force_encoding(enc).to_sym
- rescue
- end
- symbols += [:aiueo, :"aiu eo"]
- candidates = completion_candidates(":a", binding)
- assert_include(candidates, ":aiueo")
- assert_not_include(candidates, ":aiu eo")
- assert_empty(completion_candidates(":irb_unknown_symbol_abcdefg", binding))
- # Do not complete empty symbol for performance reason
- assert_empty(completion_candidates(":", binding))
- end
-
- def test_complete_invalid_three_colons
- assert_empty(completion_candidates(":::A", binding))
- assert_empty(completion_candidates(":::", binding))
- end
-
- def test_complete_absolute_constants_with_special_characters
- assert_empty(completion_candidates("::A:", binding))
- assert_empty(completion_candidates("::A.", binding))
- assert_empty(completion_candidates("::A(", binding))
- assert_empty(completion_candidates("::A)", binding))
- assert_empty(completion_candidates("::A[", binding))
- end
-
- def test_complete_reserved_words
- candidates = completion_candidates("de", binding)
- %w[def defined?].each do |word|
- assert_include candidates, word
- end
-
- candidates = completion_candidates("__", binding)
- %w[__ENCODING__ __LINE__ __FILE__].each do |word|
- assert_include candidates, word
- end
- end
-
- def test_complete_methods
- obj = Object.new
- obj.singleton_class.class_eval {
- def public_hoge; end
- private def private_hoge; end
-
- # Support for overriding #methods etc.
- def methods; end
- def private_methods; end
- def global_variables; end
- def local_variables; end
- def instance_variables; end
- }
- bind = obj.instance_exec { binding }
-
- assert_include(completion_candidates("public_hog", bind), "public_hoge")
- assert_include(doc_namespace("public_hoge", bind), "public_hoge")
-
- assert_include(completion_candidates("private_hog", bind), "private_hoge")
- assert_include(doc_namespace("private_hoge", bind), "private_hoge")
- end
- end
-
- class DeprecatedInputCompletorTest < TestCase
- def setup
- save_encodings
- @verbose, $VERBOSE = $VERBOSE, nil
- IRB.init_config(nil)
- IRB.conf[:VERBOSE] = false
- IRB.conf[:MAIN_CONTEXT] = IRB::Context.new(IRB::WorkSpace.new(binding))
- end
-
- def teardown
- restore_encodings
- $VERBOSE = @verbose
- end
-
- def test_completion_proc
- assert_include(IRB::InputCompletor::CompletionProc.call('1.ab'), '1.abs')
- assert_include(IRB::InputCompletor::CompletionProc.call('1.ab', '', ''), '1.abs')
- end
-
- def test_retrieve_completion_data
- assert_include(IRB::InputCompletor.retrieve_completion_data('1.ab'), '1.abs')
- assert_equal(IRB::InputCompletor.retrieve_completion_data('1.abs', doc_namespace: true), 'Integer.abs')
- bind = eval('a = 1; binding')
- assert_include(IRB::InputCompletor.retrieve_completion_data('a.ab', bind: bind), 'a.abs')
- assert_equal(IRB::InputCompletor.retrieve_completion_data('a.abs', bind: bind, doc_namespace: true), 'Integer.abs')
- end
- end
-end
diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb
deleted file mode 100644
index b02d8dbe09..0000000000
--- a/test/irb/test_context.rb
+++ /dev/null
@@ -1,737 +0,0 @@
-# frozen_string_literal: false
-require 'tempfile'
-require 'irb'
-
-require_relative "helper"
-
-module TestIRB
- class ContextTest < TestCase
- def setup
- IRB.init_config(nil)
- IRB.conf[:USE_SINGLELINE] = false
- IRB.conf[:VERBOSE] = false
- IRB.conf[:USE_PAGER] = false
- workspace = IRB::WorkSpace.new(Object.new)
- @context = IRB::Context.new(nil, workspace, TestInputMethod.new)
-
- @get_screen_size = Reline.method(:get_screen_size)
- Reline.instance_eval { undef :get_screen_size }
- def Reline.get_screen_size
- [36, 80]
- end
- save_encodings
- end
-
- def teardown
- Reline.instance_eval { undef :get_screen_size }
- Reline.define_singleton_method(:get_screen_size, @get_screen_size)
- restore_encodings
- end
-
- def test_eval_input
- verbose, $VERBOSE = $VERBOSE, nil
- input = TestInputMethod.new([
- "raise 'Foo'\n",
- "_\n",
- "0\n",
- "_\n",
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
-
- expected_output =
- if RUBY_3_4
- [
- :*, /\(irb\):1:in '<main>': Foo \(RuntimeError\)\n/,
- :*, /#<RuntimeError: Foo>\n/,
- :*, /0$/,
- :*, /0$/,
- /\s*/
- ]
- else
- [
- :*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
- :*, /#<RuntimeError: Foo>\n/,
- :*, /0$/,
- :*, /0$/,
- /\s*/
- ]
- end
-
- assert_pattern_list(expected_output, out)
- ensure
- $VERBOSE = verbose
- end
-
- def test_eval_input_raise2x
- input = TestInputMethod.new([
- "raise 'Foo'\n",
- "raise 'Bar'\n",
- "_\n",
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- expected_output =
- if RUBY_3_4
- [
- :*, /\(irb\):1:in '<main>': Foo \(RuntimeError\)\n/,
- :*, /\(irb\):2:in '<main>': Bar \(RuntimeError\)\n/,
- :*, /#<RuntimeError: Bar>\n/,
- ]
- else
- [
- :*, /\(irb\):1:in `<main>': Foo \(RuntimeError\)\n/,
- :*, /\(irb\):2:in `<main>': Bar \(RuntimeError\)\n/,
- :*, /#<RuntimeError: Bar>\n/,
- ]
- end
- assert_pattern_list(expected_output, out)
- end
-
- def test_prompt_n_deprecation
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), TestInputMethod.new)
-
- _, err = capture_output do
- irb.context.prompt_n = "foo"
- irb.context.prompt_n
- end
-
- assert_include err, "IRB::Context#prompt_n is deprecated"
- assert_include err, "IRB::Context#prompt_n= is deprecated"
- end
-
- def test_output_to_pipe
- require 'stringio'
- input = TestInputMethod.new(["n=1"])
- input.instance_variable_set(:@stdout, StringIO.new)
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- irb.context.echo_on_assignment = :truncate
- irb.context.prompt_mode = :DEFAULT
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal "=> 1\n", out
- end
-
- {
- successful: [
- [false, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct bar=123>/],
- [:p, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct bar=123>/],
- [true, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct #<Class:.*>::Foo bar=123>/],
- [:yaml, "123", /--- 123\n/],
- [:marshal, "123", Marshal.dump(123)],
- ],
- failed: [
- [false, "BasicObject.new", /#<NoMethodError: undefined method (`|')to_s' for/],
- [:p, "class Foo; undef inspect ;end; Foo.new", /#<NoMethodError: undefined method (`|')inspect' for/],
- [:yaml, "BasicObject.new", /#<NoMethodError: undefined method (`|')inspect' for/],
- [:marshal, "[Object.new, Class.new]", /#<TypeError: can't dump anonymous class #<Class:/]
- ]
- }.each do |scenario, cases|
- cases.each do |inspect_mode, input, expected|
- define_method "test_#{inspect_mode}_inspect_mode_#{scenario}" do
- verbose, $VERBOSE = $VERBOSE, nil
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), TestInputMethod.new([input]))
- irb.context.inspect_mode = inspect_mode
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_match(expected, out)
- ensure
- $VERBOSE = verbose
- end
- end
- end
-
- def test_object_inspection_handles_basic_object
- verbose, $VERBOSE = $VERBOSE, nil
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), TestInputMethod.new(["BasicObject.new"]))
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_not_match(/NoMethodError/, out)
- assert_match(/#<BasicObject:.*>/, out)
- ensure
- $VERBOSE = verbose
- end
-
- def test_object_inspection_falls_back_to_kernel_inspect_when_errored
- verbose, $VERBOSE = $VERBOSE, nil
- main = Object.new
- main.singleton_class.module_eval <<~RUBY
- class Foo
- def inspect
- raise "foo"
- end
- end
- RUBY
-
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new(["Foo.new"]))
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_match(/An error occurred when inspecting the object: #<RuntimeError: foo>/, out)
- assert_match(/Result of Kernel#inspect: #<#<Class:.*>::Foo:/, out)
- ensure
- $VERBOSE = verbose
- end
-
- def test_object_inspection_prints_useful_info_when_kernel_inspect_also_errored
- verbose, $VERBOSE = $VERBOSE, nil
- main = Object.new
- main.singleton_class.module_eval <<~RUBY
- class Foo
- def initialize
- # Kernel#inspect goes through instance variables with #inspect
- # So this will cause Kernel#inspect to fail
- @foo = BasicObject.new
- end
-
- def inspect
- raise "foo"
- end
- end
- RUBY
-
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new(["Foo.new"]))
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_match(/An error occurred when inspecting the object: #<RuntimeError: foo>/, out)
- assert_match(/An error occurred when running Kernel#inspect: #<NoMethodError: undefined method (`|')inspect' for/, out)
- ensure
- $VERBOSE = verbose
- end
-
- def test_default_config
- assert_equal(true, @context.use_autocomplete?)
- end
-
- def test_echo_on_assignment
- input = TestInputMethod.new([
- "a = 1\n",
- "a\n",
- "a, b = 2, 3\n",
- "a\n",
- "b\n",
- "b = 4\n",
- "_\n"
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- irb.context.return_format = "=> %s\n"
-
- # The default
- irb.context.echo = true
- irb.context.echo_on_assignment = false
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> 1\n=> 2\n=> 3\n=> 4\n", out)
-
- # Everything is output, like before echo_on_assignment was introduced
- input.reset
- irb.context.echo = true
- irb.context.echo_on_assignment = true
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> 1\n=> 1\n=> [2, 3]\n=> 2\n=> 3\n=> 4\n=> 4\n", out)
-
- # Nothing is output when echo is false
- input.reset
- irb.context.echo = false
- irb.context.echo_on_assignment = false
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("", out)
-
- # Nothing is output when echo is false even if echo_on_assignment is true
- input.reset
- irb.context.echo = false
- irb.context.echo_on_assignment = true
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("", out)
- end
-
- def test_omit_on_assignment
- input = TestInputMethod.new([
- "a = [1] * 100\n",
- "a\n",
- ])
- value = [1] * 100
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- irb.context.return_format = "=> %s\n"
-
- irb.context.echo = true
- irb.context.echo_on_assignment = false
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> \n#{value.pretty_inspect}", out)
-
- input.reset
- irb.context.echo = true
- irb.context.echo_on_assignment = :truncate
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> \n#{value.pretty_inspect[0..3]}...\n=> \n#{value.pretty_inspect}", out)
-
- input.reset
- irb.context.echo = true
- irb.context.echo_on_assignment = true
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> \n#{value.pretty_inspect}=> \n#{value.pretty_inspect}", out)
-
- input.reset
- irb.context.echo = false
- irb.context.echo_on_assignment = false
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("", out)
-
- input.reset
- irb.context.echo = false
- irb.context.echo_on_assignment = :truncate
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("", out)
-
- input.reset
- irb.context.echo = false
- irb.context.echo_on_assignment = true
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("", out)
- end
-
- def test_omit_multiline_on_assignment
- without_colorize do
- input = TestInputMethod.new([
- "class A; def inspect; ([?* * 1000] * 3).join(%{\\n}); end; end; a = A.new\n",
- "a\n"
- ])
- value = ([?* * 1000] * 3).join(%{\n})
- value_first_line = (?* * 1000).to_s
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- irb.context.return_format = "=> %s\n"
-
- irb.context.echo = true
- irb.context.echo_on_assignment = false
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> \n#{value}\n", out)
- irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
-
- input.reset
- irb.context.echo = true
- irb.context.echo_on_assignment = :truncate
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> #{value_first_line[0..(input.winsize.last - 9)]}...\n=> \n#{value}\n", out)
- irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
-
- input.reset
- irb.context.echo = true
- irb.context.echo_on_assignment = true
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> \n#{value}\n=> \n#{value}\n", out)
- irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
-
- input.reset
- irb.context.echo = false
- irb.context.echo_on_assignment = false
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("", out)
- irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
-
- input.reset
- irb.context.echo = false
- irb.context.echo_on_assignment = :truncate
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("", out)
- irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
-
- input.reset
- irb.context.echo = false
- irb.context.echo_on_assignment = true
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("", out)
- irb.context.evaluate_expression('A.remove_method(:inspect)', 0)
- end
- end
-
- def test_echo_on_assignment_conf
- # Default
- IRB.conf[:ECHO] = nil
- IRB.conf[:ECHO_ON_ASSIGNMENT] = nil
- without_colorize do
- input = TestInputMethod.new()
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
-
- assert(irb.context.echo?, "echo? should be true by default")
- assert_equal(:truncate, irb.context.echo_on_assignment?, "echo_on_assignment? should be :truncate by default")
-
- # Explicitly set :ECHO to false
- IRB.conf[:ECHO] = false
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
-
- refute(irb.context.echo?, "echo? should be false when IRB.conf[:ECHO] is set to false")
- assert_equal(:truncate, irb.context.echo_on_assignment?, "echo_on_assignment? should be :truncate by default")
-
- # Explicitly set :ECHO_ON_ASSIGNMENT to true
- IRB.conf[:ECHO] = nil
- IRB.conf[:ECHO_ON_ASSIGNMENT] = false
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
-
- assert(irb.context.echo?, "echo? should be true by default")
- refute(irb.context.echo_on_assignment?, "echo_on_assignment? should be false when IRB.conf[:ECHO_ON_ASSIGNMENT] is set to false")
- end
- end
-
- def test_multiline_output_on_default_inspector
- main = Object.new
- def main.inspect
- "abc\ndef"
- end
-
- without_colorize do
- input = TestInputMethod.new([
- "self"
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
- irb.context.return_format = "=> %s\n"
-
- # The default
- irb.context.newline_before_multiline_output = true
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> \nabc\ndef\n",
- out)
-
- # No newline before multiline output
- input.reset
- irb.context.newline_before_multiline_output = false
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("=> abc\ndef\n", out)
- end
- end
-
- def test_default_return_format
- IRB.conf[:PROMPT][:MY_PROMPT] = {
- :PROMPT_I => "%03n> ",
- :PROMPT_S => "%03n> ",
- :PROMPT_C => "%03n> "
- # without :RETURN
- # :RETURN => "%s\n"
- }
- IRB.conf[:PROMPT_MODE] = :MY_PROMPT
- input = TestInputMethod.new([
- "3"
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_equal("3\n",
- out)
- end
-
- def test_eval_input_with_exception
- pend if RUBY_ENGINE == 'truffleruby'
- verbose, $VERBOSE = $VERBOSE, nil
- input = TestInputMethod.new([
- "def hoge() fuga; end; def fuga() raise; end; hoge\n",
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- expected_output =
- if RUBY_3_4
- [
- :*, /\(irb\):1:in 'fuga': unhandled exception\n/,
- :*, /\tfrom \(irb\):1:in 'hoge'\n/,
- :*, /\tfrom \(irb\):1:in '<main>'\n/,
- :*
- ]
- elsif RUBY_VERSION < '3.0.0' && STDOUT.tty?
- [
- :*, /Traceback \(most recent call last\):\n/,
- :*, /\t 2: from \(irb\):1:in `<main>'\n/,
- :*, /\t 1: from \(irb\):1:in `hoge'\n/,
- :*, /\(irb\):1:in `fuga': unhandled exception\n/,
- ]
- else
- [
- :*, /\(irb\):1:in `fuga': unhandled exception\n/,
- :*, /\tfrom \(irb\):1:in `hoge'\n/,
- :*, /\tfrom \(irb\):1:in `<main>'\n/,
- :*
- ]
- end
- assert_pattern_list(expected_output, out)
- ensure
- $VERBOSE = verbose
- end
-
- def test_eval_input_with_invalid_byte_sequence_exception
- verbose, $VERBOSE = $VERBOSE, nil
- input = TestInputMethod.new([
- %Q{def hoge() fuga; end; def fuga() raise "A\\xF3B"; end; hoge\n},
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- expected_output =
- if RUBY_3_4
- [
- :*, /\(irb\):1:in 'fuga': A\\xF3B \(RuntimeError\)\n/,
- :*, /\tfrom \(irb\):1:in 'hoge'\n/,
- :*, /\tfrom \(irb\):1:in '<main>'\n/,
- :*
- ]
- elsif RUBY_VERSION < '3.0.0' && STDOUT.tty?
- [
- :*, /Traceback \(most recent call last\):\n/,
- :*, /\t 2: from \(irb\):1:in `<main>'\n/,
- :*, /\t 1: from \(irb\):1:in `hoge'\n/,
- :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
- ]
- else
- [
- :*, /\(irb\):1:in `fuga': A\\xF3B \(RuntimeError\)\n/,
- :*, /\tfrom \(irb\):1:in `hoge'\n/,
- :*, /\tfrom \(irb\):1:in `<main>'\n/,
- :*
- ]
- end
-
- assert_pattern_list(expected_output, out)
- ensure
- $VERBOSE = verbose
- end
-
- def test_eval_input_with_long_exception
- pend if RUBY_ENGINE == 'truffleruby'
- verbose, $VERBOSE = $VERBOSE, nil
- nesting = 20
- generated_code = ''
- nesting.times do |i|
- generated_code << "def a#{i}() a#{i + 1}; end; "
- end
- generated_code << "def a#{nesting}() raise; end; a0\n"
- input = TestInputMethod.new([
- generated_code
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- if RUBY_VERSION < '3.0.0' && STDOUT.tty?
- expected = [
- :*, /Traceback \(most recent call last\):\n/,
- :*, /\t... \d+ levels...\n/,
- :*, /\t16: from \(irb\):1:in (`|')a4'\n/,
- :*, /\t15: from \(irb\):1:in (`|')a5'\n/,
- :*, /\t14: from \(irb\):1:in (`|')a6'\n/,
- :*, /\t13: from \(irb\):1:in (`|')a7'\n/,
- :*, /\t12: from \(irb\):1:in (`|')a8'\n/,
- :*, /\t11: from \(irb\):1:in (`|')a9'\n/,
- :*, /\t10: from \(irb\):1:in (`|')a10'\n/,
- :*, /\t 9: from \(irb\):1:in (`|')a11'\n/,
- :*, /\t 8: from \(irb\):1:in (`|')a12'\n/,
- :*, /\t 7: from \(irb\):1:in (`|')a13'\n/,
- :*, /\t 6: from \(irb\):1:in (`|')a14'\n/,
- :*, /\t 5: from \(irb\):1:in (`|')a15'\n/,
- :*, /\t 4: from \(irb\):1:in (`|')a16'\n/,
- :*, /\t 3: from \(irb\):1:in (`|')a17'\n/,
- :*, /\t 2: from \(irb\):1:in (`|')a18'\n/,
- :*, /\t 1: from \(irb\):1:in (`|')a19'\n/,
- :*, /\(irb\):1:in (`|')a20': unhandled exception\n/,
- ]
- else
- expected = [
- :*, /\(irb\):1:in (`|')a20': unhandled exception\n/,
- :*, /\tfrom \(irb\):1:in (`|')a19'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a18'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a17'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a16'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a15'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a14'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a13'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a12'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a11'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a10'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a9'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a8'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a7'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a6'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a5'\n/,
- :*, /\tfrom \(irb\):1:in (`|')a4'\n/,
- :*, /\t... \d+ levels...\n/,
- ]
- end
- assert_pattern_list(expected, out)
- ensure
- $VERBOSE = verbose
- end
-
- def test_prompt_main_escape
- main = Struct.new(:to_s).new("main\a\t\r\n")
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_equal("irb(main )>", irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1))
- end
-
- def test_prompt_main_inspect_escape
- main = Struct.new(:inspect).new("main\\n\nmain")
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_equal("irb(main\\n main)>", irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
- end
-
- def test_prompt_main_truncate
- main = Struct.new(:to_s).new("a" * 100)
- def main.inspect; to_s.inspect; end
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_equal('irb(aaaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1))
- assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
- end
-
- def test_prompt_main_basic_object
- main = BasicObject.new
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_match(/irb\(#<BasicObject:.+\)/, irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1))
- assert_match(/irb\(#<BasicObject:.+\)/, irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
- end
-
- def test_prompt_main_raise
- main = Object.new
- def main.to_s; raise TypeError; end
- def main.inspect; raise ArgumentError; end
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_equal("irb(!TypeError)>", irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1))
- assert_equal("irb(!ArgumentError)>", irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
- end
-
- def test_prompt_format
- main = 'main'
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
- assert_equal('%% main %m %main %%m >', irb.send(:format_prompt, '%%%% %m %%m %%%m %%%%m %l', '>', 1, 1))
- assert_equal('42,%i, 42,%3i,042,%03i', irb.send(:format_prompt, '%i,%%i,%3i,%%3i,%03i,%%03i', nil, 42, 1))
- assert_equal('42,%n, 42,%3n,042,%03n', irb.send(:format_prompt, '%n,%%n,%3n,%%3n,%03n,%%03n', nil, 1, 42))
- end
-
- def test_lineno
- input = TestInputMethod.new([
- "\n",
- "__LINE__\n",
- "__LINE__\n",
- "\n",
- "\n",
- "__LINE__\n",
- ])
- irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
- out, err = capture_output do
- irb.eval_input
- end
- assert_empty err
- assert_pattern_list([
- :*, /\b2\n/,
- :*, /\b3\n/,
- :*, /\b6\n/,
- ], out)
- end
-
- def test_irb_path_setter
- @context.irb_path = __FILE__
- assert_equal(__FILE__, @context.irb_path)
- assert_equal("#{__FILE__}(irb)", @context.instance_variable_get(:@eval_path))
- @context.irb_path = 'file/does/not/exist'
- assert_equal('file/does/not/exist', @context.irb_path)
- assert_equal('file/does/not/exist', @context.instance_variable_get(:@eval_path))
- @context.irb_path = "#{__FILE__}(irb)"
- assert_equal("#{__FILE__}(irb)", @context.irb_path)
- assert_equal("#{__FILE__}(irb)", @context.instance_variable_get(:@eval_path))
- end
-
- def test_build_completor
- verbose, $VERBOSE = $VERBOSE, nil
- original_completor = IRB.conf[:COMPLETOR]
- IRB.conf[:COMPLETOR] = nil
- assert_match(/IRB::(Regexp|Type)Completor/, @context.send(:build_completor).class.name)
- IRB.conf[:COMPLETOR] = :regexp
- assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name
- IRB.conf[:COMPLETOR] = :unknown
- assert_equal 'IRB::RegexpCompletor', @context.send(:build_completor).class.name
- # :type is tested in test_type_completor.rb
- ensure
- $VERBOSE = verbose
- IRB.conf[:COMPLETOR] = original_completor
- end
-
- private
-
- def without_colorize
- original_value = IRB.conf[:USE_COLORIZE]
- IRB.conf[:USE_COLORIZE] = false
- yield
- ensure
- IRB.conf[:USE_COLORIZE] = original_value
- end
- end
-end
diff --git a/test/irb/test_debugger_integration.rb b/test/irb/test_debugger_integration.rb
deleted file mode 100644
index 45ffb2a52e..0000000000
--- a/test/irb/test_debugger_integration.rb
+++ /dev/null
@@ -1,513 +0,0 @@
-# frozen_string_literal: true
-
-require "tempfile"
-require "tmpdir"
-
-require_relative "helper"
-
-module TestIRB
- class DebuggerIntegrationTest < IntegrationTestCase
- def setup
- super
-
- if RUBY_ENGINE == 'truffleruby'
- omit "This test runs with ruby/debug, which doesn't work with truffleruby"
- end
-
- @envs.merge!("NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => '')
- end
-
- def test_backtrace
- write_ruby <<~'RUBY'
- def foo
- binding.irb
- end
- foo
- RUBY
-
- output = run_ruby_file do
- type "backtrace"
- type "exit!"
- end
-
- assert_match(/irb\(main\):001> backtrace/, output)
- assert_match(/Object#foo at #{@ruby_file.to_path}/, output)
- end
-
- def test_debug
- write_ruby <<~'ruby'
- binding.irb
- puts "hello"
- ruby
-
- output = run_ruby_file do
- type "debug"
- type "next"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> debug/, output)
- assert_match(/irb:rdbg\(main\):002> next/, output)
- assert_match(/=> 2\| puts "hello"/, output)
- end
-
- def test_debug_command_only_runs_once
- write_ruby <<~'ruby'
- binding.irb
- ruby
-
- output = run_ruby_file do
- type "debug"
- type "debug"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> debug/, output)
- assert_match(/irb:rdbg\(main\):002> debug/, output)
- assert_match(/IRB is already running with a debug session/, output)
- end
-
- def test_debug_command_can_only_be_called_from_binding_irb
- write_ruby <<~'ruby'
- require "irb"
- # trick test framework
- puts "binding.irb"
- IRB.start
- ruby
-
- output = run_ruby_file do
- type "debug"
- type "exit"
- end
-
- assert_include(output, "Debugging commands are only available when IRB is started with binding.irb")
- end
-
- def test_next
- write_ruby <<~'ruby'
- binding.irb
- puts "hello"
- ruby
-
- output = run_ruby_file do
- type "next"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> next/, output)
- assert_match(/=> 2\| puts "hello"/, output)
- end
-
- def test_break
- write_ruby <<~'RUBY'
- binding.irb
- puts "Hello"
- RUBY
-
- output = run_ruby_file do
- type "break 2"
- type "continue"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> break/, output)
- assert_match(/=> 2\| puts "Hello"/, output)
- end
-
- def test_delete
- write_ruby <<~'RUBY'
- binding.irb
- puts "Hello"
- binding.irb
- puts "World"
- RUBY
-
- output = run_ruby_file do
- type "break 4"
- type "continue"
- type "delete 0"
- type "continue"
- end
-
- assert_match(/irb:rdbg\(main\):003> delete/, output)
- assert_match(/deleted: #0 BP - Line/, output)
- end
-
- def test_step
- write_ruby <<~'RUBY'
- def foo
- puts "Hello"
- end
- binding.irb
- foo
- RUBY
-
- output = run_ruby_file do
- type "step"
- type "step"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> step/, output)
- assert_match(/=> 5\| foo/, output)
- assert_match(/=> 2\| puts "Hello"/, output)
- end
-
- def test_long_stepping
- write_ruby <<~'RUBY'
- class Foo
- def foo(num)
- bar(num + 10)
- end
-
- def bar(num)
- num
- end
- end
-
- binding.irb
- Foo.new.foo(100)
- RUBY
-
- output = run_ruby_file do
- type "step"
- type "step"
- type "step"
- type "step"
- type "num"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> step/, output)
- assert_match(/irb:rdbg\(main\):002> step/, output)
- assert_match(/irb:rdbg\(#<Foo:.*>\):003> step/, output)
- assert_match(/irb:rdbg\(#<Foo:.*>\):004> step/, output)
- assert_match(/irb:rdbg\(#<Foo:.*>\):005> num/, output)
- assert_match(/=> 110/, output)
- end
-
- def test_continue
- write_ruby <<~'RUBY'
- binding.irb
- puts "Hello"
- binding.irb
- puts "World"
- RUBY
-
- output = run_ruby_file do
- type "continue"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> continue/, output)
- assert_match(/=> 3: binding.irb/, output)
- assert_match(/irb:rdbg\(main\):002> continue/, output)
- end
-
- def test_finish
- write_ruby <<~'RUBY'
- def foo
- binding.irb
- puts "Hello"
- end
- foo
- RUBY
-
- output = run_ruby_file do
- type "finish"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> finish/, output)
- assert_match(/=> 4\| end/, output)
- end
-
- def test_info
- write_ruby <<~'RUBY'
- def foo
- a = "He" + "llo"
- binding.irb
- end
- foo
- RUBY
-
- output = run_ruby_file do
- type "info"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> info/, output)
- assert_match(/%self = main/, output)
- assert_match(/a = "Hello"/, output)
- end
-
- def test_catch
- write_ruby <<~'RUBY'
- binding.irb
- 1 / 0
- RUBY
-
- output = run_ruby_file do
- type "catch ZeroDivisionError"
- type "continue"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> catch/, output)
- assert_match(/Stop by #0 BP - Catch "ZeroDivisionError"/, output)
- end
-
- def test_exit
- write_ruby <<~'RUBY'
- binding.irb
- puts "he" + "llo"
- RUBY
-
- output = run_ruby_file do
- type "debug"
- type "exit"
- end
-
- assert_match(/irb:rdbg\(main\):002>/, output)
- assert_match(/hello/, output)
- end
-
- def test_force_exit
- write_ruby <<~'RUBY'
- binding.irb
- puts "he" + "llo"
- RUBY
-
- output = run_ruby_file do
- type "debug"
- type "exit!"
- end
-
- assert_match(/irb:rdbg\(main\):002>/, output)
- assert_not_match(/hello/, output)
- end
-
- def test_quit
- write_ruby <<~'RUBY'
- binding.irb
- puts "he" + "llo"
- RUBY
-
- output = run_ruby_file do
- type "debug"
- type "quit!"
- end
-
- assert_match(/irb:rdbg\(main\):002>/, output)
- assert_not_match(/hello/, output)
- end
-
- def test_prompt_line_number_continues
- write_ruby <<~'ruby'
- binding.irb
- puts "Hello"
- puts "World"
- ruby
-
- output = run_ruby_file do
- type "123"
- type "456"
- type "next"
- type "info"
- type "next"
- type "continue"
- end
-
- assert_match(/irb\(main\):003> next/, output)
- assert_match(/irb:rdbg\(main\):004> info/, output)
- assert_match(/irb:rdbg\(main\):005> next/, output)
- end
-
- def test_prompt_irb_name_is_kept
- write_rc <<~RUBY
- IRB.conf[:IRB_NAME] = "foo"
- RUBY
-
- write_ruby <<~'ruby'
- binding.irb
- puts "Hello"
- ruby
-
- output = run_ruby_file do
- type "next"
- type "continue"
- end
-
- assert_match(/foo\(main\):001> next/, output)
- assert_match(/foo:rdbg\(main\):002> continue/, output)
- end
-
- def test_irb_commands_are_available_after_moving_around_with_the_debugger
- write_ruby <<~'ruby'
- class Foo
- def bar
- puts "bar"
- end
- end
-
- binding.irb
- Foo.new.bar
- ruby
-
- output = run_ruby_file do
- # Due to the way IRB defines its commands, moving into the Foo instance from main is necessary for proper testing.
- type "next"
- type "step"
- type "irb_info"
- type "continue"
- end
-
- assert_include(output, "InputMethod: RelineInputMethod")
- end
-
- def test_irb_command_can_check_local_variables
- write_ruby <<~'ruby'
- binding.irb
- ruby
-
- output = run_ruby_file do
- type "debug"
- type 'foobar = IRB'
- type "show_source foobar.start"
- type "show_source = 'Foo'"
- type "show_source + 'Bar'"
- type "continue"
- end
- assert_include(output, "def start(ap_path = nil)")
- assert_include(output, '"FooBar"')
- end
-
- def test_help_command_is_delegated_to_the_debugger
- write_ruby <<~'ruby'
- binding.irb
- ruby
-
- output = run_ruby_file do
- type "debug"
- type "help"
- type "continue"
- end
-
- assert_include(output, "### Frame control")
- end
-
- def test_help_display_different_content_when_debugger_is_enabled
- write_ruby <<~'ruby'
- binding.irb
- ruby
-
- output = run_ruby_file do
- type "debug"
- type "help"
- type "continue"
- end
-
- # IRB's commands should still be listed
- assert_match(/help\s+List all available commands/, output)
- # debug gem's commands should be appended at the end
- assert_match(/Debugging \(from debug\.gem\)\s+### Control flow/, output)
- end
-
- def test_input_is_evaluated_in_the_context_of_the_current_thread
- write_ruby <<~'ruby'
- current_thread = Thread.current
- binding.irb
- ruby
-
- output = run_ruby_file do
- type "debug"
- type '"Threads match: #{current_thread == Thread.current}"'
- type "continue"
- end
-
- assert_match(/irb\(main\):001> debug/, output)
- assert_match(/Threads match: true/, output)
- end
-
- def test_irb_switches_debugger_interface_if_debug_was_already_activated
- write_ruby <<~'ruby'
- require 'debug'
- class Foo
- def bar
- puts "bar"
- end
- end
-
- binding.irb
- Foo.new.bar
- ruby
-
- output = run_ruby_file do
- # Due to the way IRB defines its commands, moving into the Foo instance from main is necessary for proper testing.
- type "next"
- type "step"
- type 'irb_info'
- type "continue"
- end
-
- assert_match(/irb\(main\):001> next/, output)
- assert_include(output, "InputMethod: RelineInputMethod")
- end
-
- def test_debugger_cant_be_activated_while_multi_irb_is_active
- write_ruby <<~'ruby'
- binding.irb
- a = 1
- ruby
-
- output = run_ruby_file do
- type "jobs"
- type "next"
- type "exit"
- end
-
- assert_match(/irb\(main\):001> jobs/, output)
- assert_include(output, "Can't start the debugger when IRB is running in a multi-IRB session.")
- end
-
- def test_multi_irb_commands_are_not_available_after_activating_the_debugger
- write_ruby <<~'ruby'
- binding.irb
- a = 1
- ruby
-
- output = run_ruby_file do
- type "next"
- type "jobs"
- type "continue"
- end
-
- assert_match(/irb\(main\):001> next/, output)
- assert_include(output, "Multi-IRB commands are not available when the debugger is enabled.")
- end
-
- def test_irb_passes_empty_input_to_debugger_to_repeat_the_last_command
- write_ruby <<~'ruby'
- binding.irb
- puts "foo"
- puts "bar"
- puts "baz"
- ruby
-
- output = run_ruby_file do
- type "next"
- type ""
- # Test that empty input doesn't repeat expressions
- type "123"
- type ""
- type "next"
- type ""
- type ""
- end
-
- assert_include(output, "=> 2\| puts \"foo\"")
- assert_include(output, "=> 3\| puts \"bar\"")
- assert_include(output, "=> 4\| puts \"baz\"")
- end
- end
-end
diff --git a/test/irb/test_eval_history.rb b/test/irb/test_eval_history.rb
deleted file mode 100644
index 54913ceff5..0000000000
--- a/test/irb/test_eval_history.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-require "irb"
-
-require_relative "helper"
-
-module TestIRB
- class EvalHistoryTest < TestCase
- def setup
- save_encodings
- IRB.instance_variable_get(:@CONF).clear
- end
-
- def teardown
- restore_encodings
- end
-
- def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
- IRB.init_config(nil)
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- IRB.conf[:USE_PAGER] = false
- IRB.conf.merge!(conf)
- input = TestInputMethod.new(lines)
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
- irb.context.return_format = "=> %s\n"
- irb.context.irb_path = irb_path if irb_path
- IRB.conf[:MAIN_CONTEXT] = irb.context
- capture_output do
- irb.eval_input
- end
- end
-
- def test_eval_history_is_disabled_by_default
- out, err = execute_lines(
- "a = 1",
- "__"
- )
-
- assert_empty(err)
- assert_match(/undefined local variable or method (`|')__'/, out)
- end
-
- def test_eval_history_can_be_retrieved_with_double_underscore
- out, err = execute_lines(
- "a = 1",
- "__",
- conf: { EVAL_HISTORY: 5 }
- )
-
- assert_empty(err)
- assert_match("=> 1\n" + "=> 1 1\n", out)
- end
-
- def test_eval_history_respects_given_limit
- out, err = execute_lines(
- "'foo'\n",
- "'bar'\n",
- "'baz'\n",
- "'xyz'\n",
- "__",
- conf: { EVAL_HISTORY: 4 }
- )
-
- assert_empty(err)
- # Because eval_history injects `__` into the history AND decide to ignore it, we only get <limit> - 1 results
- assert_match("2 \"bar\"\n" + "3 \"baz\"\n" + "4 \"xyz\"\n", out)
- end
- end
-end
diff --git a/test/irb/test_evaluation.rb b/test/irb/test_evaluation.rb
deleted file mode 100644
index adb69b2067..0000000000
--- a/test/irb/test_evaluation.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require "tempfile"
-
-require_relative "helper"
-
-module TestIRB
- class EchoingTest < IntegrationTestCase
- def test_irb_echos_by_default
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "123123"
- type "exit"
- end
-
- assert_include(output, "=> 123123")
- end
-
- def test_irb_doesnt_echo_line_with_semicolon
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "123123;"
- type "123123 ;"
- type "123123; "
- type <<~RUBY
- if true
- 123123
- end;
- RUBY
- type "'evaluation ends'"
- type "exit"
- end
-
- assert_include(output, "=> \"evaluation ends\"")
- assert_not_include(output, "=> 123123")
- end
- end
-end
diff --git a/test/irb/test_helper_method.rb b/test/irb/test_helper_method.rb
deleted file mode 100644
index a3e2c43b2f..0000000000
--- a/test/irb/test_helper_method.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# frozen_string_literal: true
-require "irb"
-
-require_relative "helper"
-
-module TestIRB
- class HelperMethodTestCase < TestCase
- def setup
- $VERBOSE = nil
- @verbosity = $VERBOSE
- save_encodings
- IRB.instance_variable_get(:@CONF).clear
- end
-
- def teardown
- $VERBOSE = @verbosity
- restore_encodings
- end
-
- def execute_lines(*lines, conf: {}, main: self, irb_path: nil)
- IRB.init_config(nil)
- IRB.conf[:VERBOSE] = false
- IRB.conf[:PROMPT_MODE] = :SIMPLE
- IRB.conf.merge!(conf)
- input = TestInputMethod.new(lines)
- irb = IRB::Irb.new(IRB::WorkSpace.new(main), input)
- irb.context.return_format = "=> %s\n"
- irb.context.irb_path = irb_path if irb_path
- IRB.conf[:MAIN_CONTEXT] = irb.context
- IRB.conf[:USE_PAGER] = false
- capture_output do
- irb.eval_input
- end
- end
- end
-
- module TestHelperMethod
- class ConfTest < HelperMethodTestCase
- def test_conf_returns_the_context_object
- out, err = execute_lines("conf.ap_name")
-
- assert_empty err
- assert_include out, "=> \"irb\""
- end
- end
- end
-
- class HelperMethodIntegrationTest < IntegrationTestCase
- def test_arguments_propogation
- write_ruby <<~RUBY
- require "irb/helper_method"
-
- class MyHelper < IRB::HelperMethod::Base
- description "This is a test helper"
-
- def execute(
- required_arg, optional_arg = nil, *splat_arg, required_keyword_arg:,
- optional_keyword_arg: nil, **double_splat_arg, &block_arg
- )
- puts [required_arg, optional_arg, splat_arg, required_keyword_arg, optional_keyword_arg, double_splat_arg, block_arg.call].to_s
- end
- end
-
- IRB::HelperMethod.register(:my_helper, MyHelper)
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type <<~INPUT
- my_helper(
- "required", "optional", "splat", required_keyword_arg: "required",
- optional_keyword_arg: "optional", a: 1, b: 2
- ) { "block" }
- INPUT
- type "exit"
- end
-
- optional = {a: 1, b: 2}
- assert_include(output, %[["required", "optional", ["splat"], "required", "optional", #{optional.inspect}, "block"]])
- end
-
- def test_helper_method_injection_can_happen_after_irb_require
- write_ruby <<~RUBY
- require "irb"
-
- class MyHelper < IRB::HelperMethod::Base
- description "This is a test helper"
-
- def execute
- puts "Hello from MyHelper"
- end
- end
-
- IRB::HelperMethod.register(:my_helper, MyHelper)
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "my_helper"
- type "exit"
- end
-
- assert_include(output, 'Hello from MyHelper')
- end
-
- def test_helper_method_instances_are_memoized
- write_ruby <<~RUBY
- require "irb/helper_method"
-
- class MyHelper < IRB::HelperMethod::Base
- description "This is a test helper"
-
- def execute(val)
- @val ||= val
- end
- end
-
- IRB::HelperMethod.register(:my_helper, MyHelper)
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "my_helper(100)"
- type "my_helper(200)"
- type "exit"
- end
-
- assert_include(output, '=> 100')
- assert_not_include(output, '=> 200')
- end
- end
-end
diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb
deleted file mode 100644
index 0171bb0eca..0000000000
--- a/test/irb/test_history.rb
+++ /dev/null
@@ -1,573 +0,0 @@
-# frozen_string_literal: false
-require 'irb'
-require 'readline'
-require "tempfile"
-
-require_relative "helper"
-
-return if RUBY_PLATFORM.match?(/solaris|mswin|mingw/i)
-
-module TestIRB
- class HistoryTest < TestCase
- def setup
- @conf_backup = IRB.conf.dup
- @original_verbose, $VERBOSE = $VERBOSE, nil
- @tmpdir = Dir.mktmpdir("test_irb_history_")
- setup_envs(home: @tmpdir)
- IRB.conf[:LC_MESSAGES] = IRB::Locale.new
- save_encodings
- IRB.instance_variable_set(:@existing_rc_name_generators, nil)
- end
-
- def teardown
- IRB.conf.replace(@conf_backup)
- IRB.instance_variable_set(:@existing_rc_name_generators, nil)
- teardown_envs
- restore_encodings
- $VERBOSE = @original_verbose
- FileUtils.rm_rf(@tmpdir)
- end
-
- class TestInputMethodWithRelineHistory < TestInputMethod
- # When IRB.conf[:USE_MULTILINE] is true, IRB::RelineInputMethod uses Reline::History
- HISTORY = Reline::History.new(Reline.core.config)
-
- include IRB::HistorySavingAbility
- end
-
- class TestInputMethodWithReadlineHistory < TestInputMethod
- # When IRB.conf[:USE_MULTILINE] is false, IRB::ReadlineInputMethod uses Readline::HISTORY
- HISTORY = Readline::HISTORY
-
- include IRB::HistorySavingAbility
- end
-
- def test_history_dont_save
- omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
- IRB.conf[:SAVE_HISTORY] = nil
- assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT)
- 1
- 2
- EXPECTED_HISTORY
- 1
- 2
- INITIAL_HISTORY
- 3
- exit
- INPUT
- end
-
- def test_history_save_1
- omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
- IRB.conf[:SAVE_HISTORY] = 1
- assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT)
- exit
- EXPECTED_HISTORY
- 1
- 2
- 3
- 4
- INITIAL_HISTORY
- 5
- exit
- INPUT
- end
-
- def test_history_save_100
- omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
- IRB.conf[:SAVE_HISTORY] = 100
- assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT)
- 1
- 2
- 3
- 4
- 5
- exit
- EXPECTED_HISTORY
- 1
- 2
- 3
- 4
- INITIAL_HISTORY
- 5
- exit
- INPUT
- end
-
- def test_history_save_bignum
- omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
- IRB.conf[:SAVE_HISTORY] = 10 ** 19
- assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT)
- 1
- 2
- 3
- 4
- 5
- exit
- EXPECTED_HISTORY
- 1
- 2
- 3
- 4
- INITIAL_HISTORY
- 5
- exit
- INPUT
- end
-
- def test_history_save_minus_as_infinity
- omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
- IRB.conf[:SAVE_HISTORY] = -1 # infinity
- assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT)
- 1
- 2
- 3
- 4
- 5
- exit
- EXPECTED_HISTORY
- 1
- 2
- 3
- 4
- INITIAL_HISTORY
- 5
- exit
- INPUT
- end
-
- def test_history_concurrent_use_reline
- omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
- IRB.conf[:SAVE_HISTORY] = 1
- history_concurrent_use_for_input_method(TestInputMethodWithRelineHistory)
- end
-
- def test_history_concurrent_use_readline
- omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
- IRB.conf[:SAVE_HISTORY] = 1
- history_concurrent_use_for_input_method(TestInputMethodWithReadlineHistory)
- end
-
- def test_history_concurrent_use_not_present
- IRB.conf[:SAVE_HISTORY] = 1
- io = TestInputMethodWithRelineHistory.new
- io.class::HISTORY.clear
- io.load_history
- io.class::HISTORY << 'line1'
- io.class::HISTORY << 'line2'
-
- history_file = IRB.rc_file("_history")
- assert_not_send [File, :file?, history_file]
- File.write(history_file, "line0\n")
- io.save_history
- assert_equal(%w"line0 line1 line2", File.read(history_file).split)
- end
-
- def test_history_different_encodings
- IRB.conf[:SAVE_HISTORY] = 2
- IRB.conf[:LC_MESSAGES] = IRB::Locale.new("en_US.ASCII")
- IRB.__send__(:set_encoding, Encoding::US_ASCII.name, override: false)
- assert_history(<<~EXPECTED_HISTORY.encode(Encoding::US_ASCII), <<~INITIAL_HISTORY.encode(Encoding::UTF_8), <<~INPUT)
- ????
- exit
- EXPECTED_HISTORY
- 😀
- INITIAL_HISTORY
- exit
- INPUT
- end
-
- def test_history_does_not_raise_when_history_file_directory_does_not_exist
- backup_history_file = IRB.conf[:HISTORY_FILE]
- IRB.conf[:SAVE_HISTORY] = 1
- IRB.conf[:HISTORY_FILE] = "fake/fake/fake/history_file"
- io = TestInputMethodWithRelineHistory.new
-
- assert_warn(/ensure the folder exists/i) do
- io.save_history
- end
-
- # assert_warn reverts $VERBOSE to EnvUtil.original_verbose, which is true in some cases
- # We want to keep $VERBOSE as nil until teardown is called
- # TODO: check if this is an assert_warn issue
- $VERBOSE = nil
- ensure
- IRB.conf[:HISTORY_FILE] = backup_history_file
- end
-
- def test_no_home_no_history_file_does_not_raise_history_save
- ENV['HOME'] = nil
- io = TestInputMethodWithRelineHistory.new
- assert_nil(IRB.rc_file('_history'))
- assert_nothing_raised do
- io.load_history
- io.save_history
- end
- end
-
- private
-
- def history_concurrent_use_for_input_method(input_method)
- assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT, input_method) do |history_file|
- exit
- 5
- exit
- EXPECTED_HISTORY
- 1
- 2
- 3
- 4
- INITIAL_HISTORY
- 5
- exit
- INPUT
- assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2, input_method)
- exit
- EXPECTED_HISTORY2
- 1
- 2
- 3
- 4
- INITIAL_HISTORY2
- 5
- exit
- INPUT2
- File.utime(File.atime(history_file), File.mtime(history_file) + 2, history_file)
- end
- end
-
- def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory)
- actual_history = nil
- history_file = IRB.rc_file("_history")
- ENV["HOME"] = @tmpdir
- File.open(history_file, "w") do |f|
- f.write(initial_irb_history)
- end
-
- io = input_method.new
- io.class::HISTORY.clear
- io.load_history
- if block_given?
- previous_history = []
- io.class::HISTORY.each { |line| previous_history << line }
- yield history_file
- io.class::HISTORY.clear
- previous_history.each { |line| io.class::HISTORY << line }
- end
- input.split.each { |line| io.class::HISTORY << line }
- io.save_history
-
- io.load_history
- File.open(history_file, "r") do |f|
- actual_history = f.read
- end
- assert_equal(expected_history, actual_history, <<~MESSAGE)
- expected:
- #{expected_history}
- but actual:
- #{actual_history}
- MESSAGE
- end
-
- def with_temp_stdio
- Tempfile.create("test_readline_stdin") do |stdin|
- Tempfile.create("test_readline_stdout") do |stdout|
- yield stdin, stdout
- end
- end
- end
- end
-
- class IRBHistoryIntegrationTest < IntegrationTestCase
- def test_history_saving_can_be_disabled_with_false
- write_history ""
- write_rc <<~RUBY
- IRB.conf[:SAVE_HISTORY] = false
- RUBY
-
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "puts 'foo' + 'bar'"
- type "exit"
- end
-
- assert_include(output, "foobar")
- assert_equal "", @history_file.open.read
- end
-
- def test_history_saving_accepts_true
- write_history ""
- write_rc <<~RUBY
- IRB.conf[:SAVE_HISTORY] = true
- RUBY
-
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "puts 'foo' + 'bar'"
- type "exit"
- end
-
- assert_include(output, "foobar")
- assert_equal <<~HISTORY, @history_file.open.read
- puts 'foo' + 'bar'
- exit
- HISTORY
- end
-
- def test_history_saving_with_debug
- write_history ""
-
- write_ruby <<~'RUBY'
- def foo
- end
-
- binding.irb
-
- foo
- RUBY
-
- output = run_ruby_file do
- type "'irb session'"
- type "next"
- type "'irb:debug session'"
- type "step"
- type "irb_info"
- type "puts Reline::HISTORY.to_a.to_s"
- type "q!"
- end
-
- assert_include(output, "InputMethod: RelineInputMethod")
- # check that in-memory history is preserved across sessions
- assert_include output, %q(
- ["'irb session'", "next", "'irb:debug session'", "step", "irb_info", "puts Reline::HISTORY.to_a.to_s"]
- ).strip
-
- assert_equal <<~HISTORY, @history_file.open.read
- 'irb session'
- next
- 'irb:debug session'
- step
- irb_info
- puts Reline::HISTORY.to_a.to_s
- q!
- HISTORY
- end
-
- def test_history_saving_with_debug_without_prior_history
- tmpdir = Dir.mktmpdir("test_irb_history_")
- # Intentionally not creating the file so we test the reset counter logic
- history_file = File.join(tmpdir, "irb_history")
-
- write_rc <<~RUBY
- IRB.conf[:HISTORY_FILE] = "#{history_file}"
- RUBY
-
- write_ruby <<~'RUBY'
- def foo
- end
-
- binding.irb
-
- foo
- RUBY
-
- output = run_ruby_file do
- type "'irb session'"
- type "next"
- type "'irb:debug session'"
- type "step"
- type "irb_info"
- type "puts Reline::HISTORY.to_a.to_s"
- type "q!"
- end
-
- assert_include(output, "InputMethod: RelineInputMethod")
- # check that in-memory history is preserved across sessions
- assert_include output, %q(
- ["'irb session'", "next", "'irb:debug session'", "step", "irb_info", "puts Reline::HISTORY.to_a.to_s"]
- ).strip
-
- assert_equal <<~HISTORY, File.read(history_file)
- 'irb session'
- next
- 'irb:debug session'
- step
- irb_info
- puts Reline::HISTORY.to_a.to_s
- q!
- HISTORY
- ensure
- FileUtils.rm_rf(tmpdir)
- end
-
- def test_history_saving_with_nested_sessions
- write_history ""
-
- write_ruby <<~'RUBY'
- def foo
- binding.irb
- end
-
- binding.irb
- RUBY
-
- run_ruby_file do
- type "'outer session'"
- type "foo"
- type "'inner session'"
- type "exit"
- type "'outer session again'"
- type "exit"
- end
-
- assert_equal <<~HISTORY, @history_file.open.read
- 'outer session'
- foo
- 'inner session'
- exit
- 'outer session again'
- exit
- HISTORY
- end
-
- def test_nested_history_saving_from_inner_session_with_exit!
- write_history ""
-
- write_ruby <<~'RUBY'
- def foo
- binding.irb
- end
-
- binding.irb
- RUBY
-
- run_ruby_file do
- type "'outer session'"
- type "foo"
- type "'inner session'"
- type "exit!"
- end
-
- assert_equal <<~HISTORY, @history_file.open.read
- 'outer session'
- foo
- 'inner session'
- exit!
- HISTORY
- end
-
- def test_nested_history_saving_from_outer_session_with_exit!
- write_history ""
-
- write_ruby <<~'RUBY'
- def foo
- binding.irb
- end
-
- binding.irb
- RUBY
-
- run_ruby_file do
- type "'outer session'"
- type "foo"
- type "'inner session'"
- type "exit"
- type "'outer session again'"
- type "exit!"
- end
-
- assert_equal <<~HISTORY, @history_file.open.read
- 'outer session'
- foo
- 'inner session'
- exit
- 'outer session again'
- exit!
- HISTORY
- end
-
- def test_history_saving_with_nested_sessions_and_prior_history
- write_history <<~HISTORY
- old_history_1
- old_history_2
- old_history_3
- HISTORY
-
- write_ruby <<~'RUBY'
- def foo
- binding.irb
- end
-
- binding.irb
- RUBY
-
- run_ruby_file do
- type "'outer session'"
- type "foo"
- type "'inner session'"
- type "exit"
- type "'outer session again'"
- type "exit"
- end
-
- assert_equal <<~HISTORY, @history_file.open.read
- old_history_1
- old_history_2
- old_history_3
- 'outer session'
- foo
- 'inner session'
- exit
- 'outer session again'
- exit
- HISTORY
- end
-
- def test_direct_debug_session_loads_history
- @envs['RUBY_DEBUG_IRB_CONSOLE'] = "1"
- write_history <<~HISTORY
- old_history_1
- old_history_2
- old_history_3
- HISTORY
-
- write_ruby <<~'RUBY'
- require 'debug'
- debugger
- binding.irb # needed to satisfy run_ruby_file
- RUBY
-
- output = run_ruby_file do
- type "history"
- type "puts 'foo'"
- type "history"
- type "exit!"
- end
-
- assert_include(output, "irb:rdbg(main):002") # assert that we're in an irb:rdbg session
- assert_include(output, "5: history")
- assert_include(output, "4: puts 'foo'")
- assert_include(output, "3: history")
- assert_include(output, "2: old_history_3")
- assert_include(output, "1: old_history_2")
- assert_include(output, "0: old_history_1")
- end
-
- private
-
- def write_history(history)
- @history_file = Tempfile.new('irb_history')
- @history_file.write(history)
- @history_file.close
- write_rc <<~RUBY
- IRB.conf[:HISTORY_FILE] = "#{@history_file.path}"
- RUBY
- end
- end
-end
diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb
deleted file mode 100644
index f7168e02fe..0000000000
--- a/test/irb/test_init.rb
+++ /dev/null
@@ -1,388 +0,0 @@
-# frozen_string_literal: false
-require "irb"
-require "fileutils"
-
-require_relative "helper"
-
-module TestIRB
- class InitTest < TestCase
- def setup
- # IRBRC is for RVM...
- @backup_env = %w[HOME XDG_CONFIG_HOME IRBRC].each_with_object({}) do |env, hash|
- hash[env] = ENV.delete(env)
- end
- ENV["HOME"] = @tmpdir = File.realpath(Dir.mktmpdir("test_irb_init_#{$$}"))
- end
-
- def reset_rc_name_generators
- IRB.instance_variable_set(:@existing_rc_name_generators, nil)
- end
-
- def teardown
- ENV.update(@backup_env)
- FileUtils.rm_rf(@tmpdir)
- IRB.conf.delete(:SCRIPT)
- reset_rc_name_generators
- end
-
- def test_setup_with_argv_preserves_global_argv
- argv = ["foo", "bar"]
- with_argv(argv) do
- IRB.setup(eval("__FILE__"), argv: %w[-f])
- assert_equal argv, ARGV
- end
- end
-
- def test_setup_with_minimum_argv_does_not_change_dollar0
- orig = $0.dup
- IRB.setup(eval("__FILE__"), argv: %w[-f])
- assert_equal orig, $0
- end
-
- def test_rc_files
- tmpdir = @tmpdir
- Dir.chdir(tmpdir) do
- home = ENV['HOME'] = "#{tmpdir}/home"
- xdg_config_home = ENV['XDG_CONFIG_HOME'] = "#{tmpdir}/xdg"
- reset_rc_name_generators
- assert_empty(IRB.irbrc_files)
- assert_equal("#{home}/.irb_history", IRB.rc_file('_history'))
- FileUtils.mkdir_p(home)
- FileUtils.mkdir_p("#{xdg_config_home}/irb")
- FileUtils.mkdir_p("#{home}/.config/irb")
- reset_rc_name_generators
- assert_empty(IRB.irbrc_files)
- assert_equal("#{xdg_config_home}/irb/irb_history", IRB.rc_file('_history'))
- home_irbrc = "#{home}/.irbrc"
- config_irbrc = "#{home}/.config/irb/irbrc"
- xdg_config_irbrc = "#{xdg_config_home}/irb/irbrc"
- [home_irbrc, config_irbrc, xdg_config_irbrc].each do |file|
- FileUtils.touch(file)
- end
- current_dir_irbrcs = %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{tmpdir}/#{file}" }
- current_dir_irbrcs.each { |file| FileUtils.touch(file) }
- reset_rc_name_generators
- assert_equal([xdg_config_irbrc, home_irbrc, *current_dir_irbrcs], IRB.irbrc_files)
- assert_equal(xdg_config_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
- ENV['XDG_CONFIG_HOME'] = nil
- reset_rc_name_generators
- assert_equal([home_irbrc, config_irbrc, *current_dir_irbrcs], IRB.irbrc_files)
- assert_equal(home_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
- ENV['XDG_CONFIG_HOME'] = ''
- reset_rc_name_generators
- assert_equal([home_irbrc, config_irbrc] + current_dir_irbrcs, IRB.irbrc_files)
- assert_equal(home_irbrc.sub(/rc$/, '_history'), IRB.rc_file('_history'))
- ENV['XDG_CONFIG_HOME'] = xdg_config_home
- ENV['IRBRC'] = "#{tmpdir}/.irbrc"
- reset_rc_name_generators
- assert_equal([ENV['IRBRC'], xdg_config_irbrc, home_irbrc] + (current_dir_irbrcs - [ENV['IRBRC']]), IRB.irbrc_files)
- assert_equal(ENV['IRBRC'] + '_history', IRB.rc_file('_history'))
- ENV['IRBRC'] = ENV['HOME'] = ENV['XDG_CONFIG_HOME'] = nil
- reset_rc_name_generators
- assert_equal(current_dir_irbrcs, IRB.irbrc_files)
- assert_nil(IRB.rc_file('_history'))
- end
- end
-
- def test_duplicated_rc_files
- tmpdir = @tmpdir
- Dir.chdir(tmpdir) do
- ENV['XDG_CONFIG_HOME'] = "#{ENV['HOME']}/.config"
- FileUtils.mkdir_p("#{ENV['XDG_CONFIG_HOME']}/irb")
- env_irbrc = ENV['IRBRC'] = "#{tmpdir}/_irbrc"
- xdg_config_irbrc = "#{ENV['XDG_CONFIG_HOME']}/irb/irbrc"
- home_irbrc = "#{ENV['HOME']}/.irbrc"
- current_dir_irbrc = "#{tmpdir}/irbrc"
- [env_irbrc, xdg_config_irbrc, home_irbrc, current_dir_irbrc].each do |file|
- FileUtils.touch(file)
- end
- reset_rc_name_generators
- assert_equal([env_irbrc, xdg_config_irbrc, home_irbrc, current_dir_irbrc], IRB.irbrc_files)
- end
- end
-
- def test_sigint_restore_default
- pend "This test gets stuck on Solaris for unknown reason; contribution is welcome" if RUBY_PLATFORM =~ /solaris/
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- # IRB should restore SIGINT handler
- status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e Signal.trap("SIGINT","DEFAULT");binding.irb;loop{Process.kill("SIGINT",$$)} -- -f --], "exit\n", //, //)
- Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled?
- end
-
- def test_sigint_restore_block
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- # IRB should restore SIGINT handler
- status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e x=false;Signal.trap("SIGINT"){x=true};binding.irb;loop{Process.kill("SIGINT",$$);if(x);break;end} -- -f --], "exit\n", //, //)
- Process.kill("SIGKILL", status.pid) if !status.exited? && !status.stopped? && !status.signaled?
- end
-
- def test_no_color_environment_variable
- orig_no_color = ENV['NO_COLOR']
- orig_use_colorize = IRB.conf[:USE_COLORIZE]
- IRB.conf[:USE_COLORIZE] = true
-
- assert IRB.conf[:USE_COLORIZE]
-
- ENV['NO_COLOR'] = 'true'
- IRB.setup(__FILE__)
- refute IRB.conf[:USE_COLORIZE]
-
- ENV['NO_COLOR'] = ''
- IRB.setup(__FILE__)
- assert IRB.conf[:USE_COLORIZE]
-
- ENV['NO_COLOR'] = nil
- IRB.setup(__FILE__)
- assert IRB.conf[:USE_COLORIZE]
- ensure
- ENV['NO_COLOR'] = orig_no_color
- IRB.conf[:USE_COLORIZE] = orig_use_colorize
- end
-
- def test_use_autocomplete_environment_variable
- orig_use_autocomplete_env = ENV['IRB_USE_AUTOCOMPLETE']
- orig_use_autocomplete_conf = IRB.conf[:USE_AUTOCOMPLETE]
-
- ENV['IRB_USE_AUTOCOMPLETE'] = nil
- IRB.setup(__FILE__)
- assert IRB.conf[:USE_AUTOCOMPLETE]
-
- ENV['IRB_USE_AUTOCOMPLETE'] = ''
- IRB.setup(__FILE__)
- assert IRB.conf[:USE_AUTOCOMPLETE]
-
- ENV['IRB_USE_AUTOCOMPLETE'] = 'false'
- IRB.setup(__FILE__)
- refute IRB.conf[:USE_AUTOCOMPLETE]
-
- ENV['IRB_USE_AUTOCOMPLETE'] = 'true'
- IRB.setup(__FILE__)
- assert IRB.conf[:USE_AUTOCOMPLETE]
- ensure
- ENV["IRB_USE_AUTOCOMPLETE"] = orig_use_autocomplete_env
- IRB.conf[:USE_AUTOCOMPLETE] = orig_use_autocomplete_conf
- end
-
- def test_completor_environment_variable
- orig_use_autocomplete_env = ENV['IRB_COMPLETOR']
- orig_use_autocomplete_conf = IRB.conf[:COMPLETOR]
-
- # Default value is nil: auto-detect
- ENV['IRB_COMPLETOR'] = nil
- IRB.setup(__FILE__)
- assert_equal(nil, IRB.conf[:COMPLETOR])
-
- ENV['IRB_COMPLETOR'] = 'regexp'
- IRB.setup(__FILE__)
- assert_equal(:regexp, IRB.conf[:COMPLETOR])
-
- ENV['IRB_COMPLETOR'] = 'type'
- IRB.setup(__FILE__)
- assert_equal(:type, IRB.conf[:COMPLETOR])
-
- ENV['IRB_COMPLETOR'] = 'regexp'
- IRB.setup(__FILE__, argv: ['--type-completor'])
- assert_equal :type, IRB.conf[:COMPLETOR]
-
- ENV['IRB_COMPLETOR'] = 'type'
- IRB.setup(__FILE__, argv: ['--regexp-completor'])
- assert_equal :regexp, IRB.conf[:COMPLETOR]
- ensure
- ENV['IRB_COMPLETOR'] = orig_use_autocomplete_env
- IRB.conf[:COMPLETOR] = orig_use_autocomplete_conf
- end
-
- def test_completor_setup_with_argv
- orig_completor_conf = IRB.conf[:COMPLETOR]
- orig_completor_env = ENV['IRB_COMPLETOR']
- ENV['IRB_COMPLETOR'] = nil
-
- # Default value is nil: auto-detect
- IRB.setup(__FILE__, argv: [])
- assert_equal nil, IRB.conf[:COMPLETOR]
-
- IRB.setup(__FILE__, argv: ['--type-completor'])
- assert_equal :type, IRB.conf[:COMPLETOR]
-
- IRB.setup(__FILE__, argv: ['--regexp-completor'])
- assert_equal :regexp, IRB.conf[:COMPLETOR]
- ensure
- IRB.conf[:COMPLETOR] = orig_completor_conf
- ENV['IRB_COMPLETOR'] = orig_completor_env
- end
-
- def test_noscript
- argv = %w[--noscript -- -f]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_nil IRB.conf[:SCRIPT]
- assert_equal(['-f'], argv)
-
- argv = %w[--noscript -- a]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_nil IRB.conf[:SCRIPT]
- assert_equal(['a'], argv)
-
- argv = %w[--noscript a]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_nil IRB.conf[:SCRIPT]
- assert_equal(['a'], argv)
-
- argv = %w[--script --noscript a]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_nil IRB.conf[:SCRIPT]
- assert_equal(['a'], argv)
-
- argv = %w[--noscript --script a]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_equal('a', IRB.conf[:SCRIPT])
- assert_equal([], argv)
- end
-
- def test_dash
- argv = %w[-]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_equal('-', IRB.conf[:SCRIPT])
- assert_equal([], argv)
-
- argv = %w[-- -]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_equal('-', IRB.conf[:SCRIPT])
- assert_equal([], argv)
-
- argv = %w[-- - -f]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_equal('-', IRB.conf[:SCRIPT])
- assert_equal(['-f'], argv)
- end
-
- def test_option_tracer
- argv = %w[--tracer]
- IRB.setup(eval("__FILE__"), argv: argv)
- assert_equal(true, IRB.conf[:USE_TRACER])
- end
-
- private
-
- def with_argv(argv)
- orig = ARGV.dup
- ARGV.replace(argv)
- yield
- ensure
- ARGV.replace(orig)
- end
- end
-
- class ConfigValidationTest < TestCase
- def setup
- # To prevent the test from using the user's .irbrc file
- @home = Dir.mktmpdir
- setup_envs(home: @home)
- super
- end
-
- def teardown
- super
- teardown_envs
- File.unlink(@irbrc)
- Dir.rmdir(@home)
- IRB.instance_variable_set(:@existing_rc_name_generators, nil)
- end
-
- def test_irb_name_converts_non_string_values_to_string
- assert_no_irb_validation_error(<<~'RUBY')
- IRB.conf[:IRB_NAME] = :foo
- RUBY
-
- assert_equal "foo", IRB.conf[:IRB_NAME]
- end
-
- def test_irb_rc_name_only_takes_callable_objects
- assert_irb_validation_error(<<~'RUBY', "IRB.conf[:IRB_RC] should be a callable object. Got :foo.")
- IRB.conf[:IRB_RC] = :foo
- RUBY
- end
-
- def test_back_trace_limit_only_accepts_integers
- assert_irb_validation_error(<<~'RUBY', "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got \"foo\".")
- IRB.conf[:BACK_TRACE_LIMIT] = "foo"
- RUBY
- end
-
- def test_prompt_only_accepts_hash
- assert_irb_validation_error(<<~'RUBY', "IRB.conf[:PROMPT] should be a Hash. Got \"foo\".")
- IRB.conf[:PROMPT] = "foo"
- RUBY
- end
-
- def test_eval_history_only_accepts_integers
- assert_irb_validation_error(<<~'RUBY', "IRB.conf[:EVAL_HISTORY] should be an integer. Got \"foo\".")
- IRB.conf[:EVAL_HISTORY] = "foo"
- RUBY
- end
-
- private
-
- def assert_irb_validation_error(rc_content, error_message)
- write_rc rc_content
-
- assert_raise_with_message(TypeError, error_message) do
- IRB.setup(__FILE__)
- end
- end
-
- def assert_no_irb_validation_error(rc_content)
- write_rc rc_content
-
- assert_nothing_raised do
- IRB.setup(__FILE__)
- end
- end
-
- def write_rc(content)
- @irbrc = Tempfile.new('irbrc')
- @irbrc.write(content)
- @irbrc.close
- ENV['IRBRC'] = @irbrc.path
- end
- end
-
- class InitIntegrationTest < IntegrationTestCase
- def setup
- super
-
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
- end
-
- def test_load_error_in_rc_file_is_warned
- write_rc <<~'IRBRC'
- require "file_that_does_not_exist"
- IRBRC
-
- output = run_ruby_file do
- type "'foobar'"
- type "exit"
- end
-
- # IRB session should still be started
- assert_includes output, "foobar"
- assert_includes output, 'cannot load such file -- file_that_does_not_exist (LoadError)'
- end
-
- def test_normal_errors_in_rc_file_is_warned
- write_rc <<~'IRBRC'
- raise "I'm an error"
- IRBRC
-
- output = run_ruby_file do
- type "'foobar'"
- type "exit"
- end
-
- # IRB session should still be started
- assert_includes output, "foobar"
- assert_includes output, 'I\'m an error (RuntimeError)'
- end
- end
-end
diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb
deleted file mode 100644
index bd107551df..0000000000
--- a/test/irb/test_input_method.rb
+++ /dev/null
@@ -1,195 +0,0 @@
-# frozen_string_literal: false
-
-require "irb"
-begin
- require "rdoc"
-rescue LoadError
-end
-require_relative "helper"
-
-module TestIRB
- class InputMethodTest < TestCase
- def setup
- @conf_backup = IRB.conf.dup
- IRB.init_config(nil)
- IRB.conf[:LC_MESSAGES] = IRB::Locale.new
- save_encodings
- end
-
- def teardown
- IRB.conf.replace(@conf_backup)
- restore_encodings
- # Reset Reline configuration overridden by RelineInputMethod.
- Reline.instance_variable_set(:@core, nil)
- end
- end
-
- class RelineInputMethodTest < InputMethodTest
- def test_initialization
- Reline.completion_proc = nil
- Reline.dig_perfect_match_proc = nil
- IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
-
- assert_nil Reline.completion_append_character
- assert_equal '', Reline.completer_quote_characters
- assert_equal IRB::InputMethod::BASIC_WORD_BREAK_CHARACTERS, Reline.basic_word_break_characters
- assert_not_nil Reline.completion_proc
- assert_not_nil Reline.dig_perfect_match_proc
- end
-
- def test_colorize
- IRB.conf[:USE_COLORIZE] = true
- IRB.conf[:VERBOSE] = false
- original_colorable = IRB::Color.method(:colorable?)
- IRB::Color.instance_eval { undef :colorable? }
- IRB::Color.define_singleton_method(:colorable?) { true }
- workspace = IRB::WorkSpace.new(binding)
- input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
- IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new(workspace, input_method).context
- assert_equal "\e[1m$\e[0m\e[m", Reline.output_modifier_proc.call('$', complete: false)
- assert_equal "\e[1m$\e[0m\e[m \e[34m\e[1m1\e[0m + \e[34m\e[1m2\e[0m", Reline.output_modifier_proc.call('$ 1 + 2', complete: false)
- assert_equal "\e[32m\e[1m$a\e[0m", Reline.output_modifier_proc.call('$a', complete: false)
- ensure
- IRB::Color.instance_eval { undef :colorable? }
- IRB::Color.define_singleton_method(:colorable?, original_colorable)
- end
-
- def test_initialization_without_use_autocomplete
- original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc
- empty_proc = Proc.new {}
- Reline.add_dialog_proc(:show_doc, empty_proc)
-
- IRB.conf[:USE_AUTOCOMPLETE] = false
-
- IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
-
- refute Reline.autocompletion
- assert_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc
- ensure
- Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT)
- end
-
- def test_initialization_with_use_autocomplete
- omit 'This test requires RDoc' unless defined?(RDoc)
- original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc
- empty_proc = Proc.new {}
- Reline.add_dialog_proc(:show_doc, empty_proc)
-
- IRB.conf[:USE_AUTOCOMPLETE] = true
-
- IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
-
- assert Reline.autocompletion
- assert_not_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc
- ensure
- Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT)
- end
-
- def test_initialization_with_use_autocomplete_but_without_rdoc
- original_show_doc_proc = Reline.dialog_proc(:show_doc)&.dialog_proc
- empty_proc = Proc.new {}
- Reline.add_dialog_proc(:show_doc, empty_proc)
-
- IRB.conf[:USE_AUTOCOMPLETE] = true
-
- without_rdoc do
- IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
- end
-
- assert Reline.autocompletion
- # doesn't register show_doc dialog
- assert_equal empty_proc, Reline.dialog_proc(:show_doc).dialog_proc
- ensure
- Reline.add_dialog_proc(:show_doc, original_show_doc_proc, Reline::DEFAULT_DIALOG_CONTEXT)
- end
- end
-
- class DisplayDocumentTest < InputMethodTest
- def setup
- super
- @driver = RDoc::RI::Driver.new(use_stdout: true)
- end
-
- def display_document(target, bind, driver = nil)
- input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new)
- input_method.instance_variable_set(:@rdoc_ri_driver, driver) if driver
- input_method.instance_variable_set(:@completion_params, ['', target, '', bind])
- input_method.display_document(target)
- end
-
- def test_perfectly_matched_namespace_triggers_document_display
- omit unless has_rdoc_content?
-
- out, err = capture_output do
- display_document("String", binding, @driver)
- end
-
- assert_empty(err)
-
- assert_include(out, " S\bSt\btr\bri\bin\bng\bg")
- end
-
- def test_perfectly_matched_multiple_namespaces_triggers_document_display
- result = nil
- out, err = capture_output do
- result = display_document("{}.nil?", binding, @driver)
- end
-
- assert_empty(err)
-
- # check if there're rdoc contents (e.g. CI doesn't generate them)
- if has_rdoc_content?
- # if there's rdoc content, we can verify by checking stdout
- # rdoc generates control characters for formatting method names
- assert_include(out, "P\bPr\bro\boc\bc.\b.n\bni\bil\bl?\b?") # Proc.nil?
- assert_include(out, "H\bHa\bas\bsh\bh.\b.n\bni\bil\bl?\b?") # Hash.nil?
- else
- # this is a hacky way to verify the rdoc rendering code path because CI doesn't have rdoc content
- # if there are multiple namespaces to be rendered, PerfectMatchedProc renders the result with a document
- # which always returns the bytes rendered, even if it's 0
- assert_equal(0, result)
- end
- end
-
- def test_not_matched_namespace_triggers_nothing
- result = nil
- out, err = capture_output do
- result = display_document("Stri", binding, @driver)
- end
-
- assert_empty(err)
- assert_empty(out)
- assert_nil(result)
- end
-
- def test_perfect_matching_stops_without_rdoc
- result = nil
-
- out, err = capture_output do
- without_rdoc do
- result = display_document("String", binding)
- end
- end
-
- assert_empty(err)
- assert_not_match(/from ruby core/, out)
- assert_nil(result)
- end
-
- def test_perfect_matching_handles_nil_namespace
- out, err = capture_output do
- # symbol literal has `nil` doc namespace so it's a good test subject
- assert_nil(display_document(":aiueo", binding, @driver))
- end
-
- assert_empty(err)
- assert_empty(out)
- end
-
- private
-
- def has_rdoc_content?
- File.exist?(RDoc::RI::Paths::BASE)
- end
- end if defined?(RDoc)
-end
diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb
deleted file mode 100644
index 617e9c9614..0000000000
--- a/test/irb/test_irb.rb
+++ /dev/null
@@ -1,936 +0,0 @@
-# frozen_string_literal: true
-require "irb"
-
-require_relative "helper"
-
-module TestIRB
- class InputTest < IntegrationTestCase
- def test_symbol_aliases_are_handled_correctly
- write_ruby <<~'RUBY'
- class Foo
- end
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "$ Foo"
- type "exit!"
- end
-
- assert_include output, "From: #{@ruby_file.path}:1"
- end
-
- def test_symbol_aliases_are_handled_correctly_with_singleline_mode
- write_rc <<~RUBY
- IRB.conf[:USE_SINGLELINE] = true
- RUBY
-
- write_ruby <<~'RUBY'
- class Foo
- end
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "irb_info"
- type "$ Foo"
- type "exit!"
- end
-
- # Make sure it's tested in singleline mode
- assert_include output, "InputMethod: ReadlineInputMethod"
- assert_include output, "From: #{@ruby_file.path}:1"
- end
-
- def test_underscore_stores_last_result
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "1 + 1"
- type "_ + 10"
- type "exit!"
- end
-
- assert_include output, "=> 12"
- end
-
- def test_commands_dont_override_stored_last_result
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "1 + 1"
- type "ls"
- type "_ + 10"
- type "exit!"
- end
-
- assert_include output, "=> 12"
- end
-
- def test_evaluate_with_encoding_error_without_lineno
- if RUBY_ENGINE == 'truffleruby'
- omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby"
- end
-
- if RUBY_VERSION >= "3.3."
- omit "Now raises SyntaxError"
- end
-
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type %q[:"\xAE"]
- type "exit!"
- end
-
- assert_include output, 'invalid symbol in encoding UTF-8 :"\xAE"'
- # EncodingError would be wrapped with ANSI escape sequences, so we assert it separately
- assert_include output, "EncodingError"
- end
-
- def test_evaluate_still_emits_warning
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type %q[def foo; END {}; end]
- type "exit!"
- end
-
- assert_include output, '(irb):1: warning: END in method; use at_exit'
- end
-
- def test_symbol_aliases_dont_affect_ruby_syntax
- write_ruby <<~'RUBY'
- $foo = "It's a foo"
- @bar = "It's a bar"
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "$foo"
- type "@bar"
- type "exit!"
- end
-
- assert_include output, "=> \"It's a foo\""
- assert_include output, "=> \"It's a bar\""
- end
-
- def test_empty_input_echoing_behaviour
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type ""
- type " "
- type "exit"
- end
-
- assert_not_match(/irb\(main\):001> (\r*\n)?=> nil/, output)
- assert_match(/irb\(main\):002> (\r*\n)?=> nil/, output)
- end
- end
-
- class NestedBindingIrbTest < IntegrationTestCase
- def test_current_context_restore
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type '$ctx = IRB.CurrentContext'
- type 'binding.irb'
- type 'p context_changed: IRB.CurrentContext != $ctx'
- type 'exit'
- type 'p context_restored: IRB.CurrentContext == $ctx'
- type 'exit'
- end
-
- assert_include output, {context_changed: true}.inspect
- assert_include output, {context_restored: true}.inspect
- end
- end
-
- class IrbIOConfigurationTest < TestCase
- Row = Struct.new(:content, :current_line_spaces, :new_line_spaces, :indent_level)
-
- class MockIO_AutoIndent
- attr_reader :calculated_indent
-
- def initialize(*params)
- @params = params
- end
-
- def auto_indent(&block)
- @calculated_indent = block.call(*@params)
- end
- end
-
- class MockIO_DynamicPrompt
- attr_reader :prompt_list
-
- def initialize(params, &assertion)
- @params = params
- end
-
- def dynamic_prompt(&block)
- @prompt_list = block.call(@params)
- end
- end
-
- def setup
- save_encodings
- @irb = build_irb
- end
-
- def teardown
- restore_encodings
- end
-
- class AutoIndentationTest < IrbIOConfigurationTest
- def test_auto_indent
- input_with_correct_indents = [
- [%q(def each_top_level_statement), 0, 2],
- [%q( initialize_input), 2, 2],
- [%q( catch(:TERM_INPUT) do), 2, 4],
- [%q( loop do), 4, 6],
- [%q( begin), 6, 8],
- [%q( prompt), 8, 8],
- [%q( unless l = lex), 8, 10],
- [%q( throw :TERM_INPUT if @line == ''), 10, 10],
- [%q( else), 8, 10],
- [%q( @line_no += l.count("\n")), 10, 10],
- [%q( next if l == "\n"), 10, 10],
- [%q( @line.concat l), 10, 10],
- [%q( if @code_block_open or @ltype or @continue or @indent > 0), 10, 12],
- [%q( next), 12, 12],
- [%q( end), 10, 10],
- [%q( end), 8, 8],
- [%q( if @line != "\n"), 8, 10],
- [%q( @line.force_encoding(@io.encoding)), 10, 10],
- [%q( yield @line, @exp_line_no), 10, 10],
- [%q( end), 8, 8],
- [%q( break if @io.eof?), 8, 8],
- [%q( @line = ''), 8, 8],
- [%q( @exp_line_no = @line_no), 8, 8],
- [%q( ), nil, 8],
- [%q( @indent = 0), 8, 8],
- [%q( rescue TerminateLineInput), 6, 8],
- [%q( initialize_input), 8, 8],
- [%q( prompt), 8, 8],
- [%q( end), 6, 6],
- [%q( end), 4, 4],
- [%q( end), 2, 2],
- [%q(end), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_braces_on_their_own_line
- input_with_correct_indents = [
- [%q(if true), 0, 2],
- [%q( [), 2, 4],
- [%q( ]), 2, 2],
- [%q(end), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_multiple_braces_in_a_line
- input_with_correct_indents = [
- [%q([[[), 0, 6],
- [%q( ]), 4, 4],
- [%q( ]), 2, 2],
- [%q(]), 0, 0],
- [%q([<<FOO]), 0, 0],
- [%q(hello), 0, 0],
- [%q(FOO), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_a_closed_brace_and_not_closed_brace_in_a_line
- input_with_correct_indents = [
- [%q(p() {), 0, 2],
- [%q(}), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_symbols
- input_with_correct_indents = [
- [%q(:a), 0, 0],
- [%q(:A), 0, 0],
- [%q(:+), 0, 0],
- [%q(:@@a), 0, 0],
- [%q(:@a), 0, 0],
- [%q(:$a), 0, 0],
- [%q(:def), 0, 0],
- [%q(:`), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_incomplete_coding_magic_comment
- input_with_correct_indents = [
- [%q(#coding:u), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_incomplete_encoding_magic_comment
- input_with_correct_indents = [
- [%q(#encoding:u), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_incomplete_emacs_coding_magic_comment
- input_with_correct_indents = [
- [%q(# -*- coding: u), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_incomplete_vim_coding_magic_comment
- input_with_correct_indents = [
- [%q(# vim:set fileencoding=u), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_mixed_rescue
- input_with_correct_indents = [
- [%q(def m), 0, 2],
- [%q( begin), 2, 4],
- [%q( begin), 4, 6],
- [%q( x = a rescue 4), 6, 6],
- [%q( y = [(a rescue 5)]), 6, 6],
- [%q( [x, y]), 6, 6],
- [%q( rescue => e), 4, 6],
- [%q( raise e rescue 8), 6, 6],
- [%q( end), 4, 4],
- [%q( rescue), 2, 4],
- [%q( raise rescue 11), 4, 4],
- [%q( end), 2, 2],
- [%q(rescue => e), 0, 2],
- [%q( raise e rescue 14), 2, 2],
- [%q(end), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_oneliner_method_definition
- input_with_correct_indents = [
- [%q(class A), 0, 2],
- [%q( def foo0), 2, 4],
- [%q( 3), 4, 4],
- [%q( end), 2, 2],
- [%q( def foo1()), 2, 4],
- [%q( 3), 4, 4],
- [%q( end), 2, 2],
- [%q( def foo2(a, b)), 2, 4],
- [%q( a + b), 4, 4],
- [%q( end), 2, 2],
- [%q( def foo3 a, b), 2, 4],
- [%q( a + b), 4, 4],
- [%q( end), 2, 2],
- [%q( def bar0() = 3), 2, 2],
- [%q( def bar1(a) = a), 2, 2],
- [%q( def bar2(a, b) = a + b), 2, 2],
- [%q( def bar3() = :s), 2, 2],
- [%q( def bar4() = Time.now), 2, 2],
- [%q(end), 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents)
- end
-
- def test_tlambda
- input_with_correct_indents = [
- [%q(if true), 0, 2, 1],
- [%q( -> {), 2, 4, 2],
- [%q( }), 2, 2, 1],
- [%q(end), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_corresponding_syntax_to_keyword_do_in_class
- input_with_correct_indents = [
- [%q(class C), 0, 2, 1],
- [%q( while method_name do), 2, 4, 2],
- [%q( 3), 4, 4, 2],
- [%q( end), 2, 2, 1],
- [%q( foo do), 2, 4, 2],
- [%q( 3), 4, 4, 2],
- [%q( end), 2, 2, 1],
- [%q(end), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_corresponding_syntax_to_keyword_do
- input_with_correct_indents = [
- [%q(while i > 0), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(while true), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(while ->{i > 0}.call), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(while ->{true}.call), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(while i > 0 do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(while true do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(while ->{i > 0}.call do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(while ->{true}.call do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(foo do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(foo true do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(foo ->{true} do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(foo ->{i > 0} do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_corresponding_syntax_to_keyword_for
- input_with_correct_indents = [
- [%q(for i in [1]), 0, 2, 1],
- [%q( puts i), 2, 2, 1],
- [%q(end), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_corresponding_syntax_to_keyword_for_with_do
- input_with_correct_indents = [
- [%q(for i in [1] do), 0, 2, 1],
- [%q( puts i), 2, 2, 1],
- [%q(end), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_typing_incomplete_include_interpreted_as_keyword_in
- input_with_correct_indents = [
- [%q(module E), 0, 2, 1],
- [%q(end), 0, 0, 0],
- [%q(class A), 0, 2, 1],
- [%q( in), 2, 2, 1] # scenario typing `include E`
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
-
- end
-
- def test_bracket_corresponding_to_times
- input_with_correct_indents = [
- [%q(3.times { |i|), 0, 2, 1],
- [%q( puts i), 2, 2, 1],
- [%q(}), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_do_corresponding_to_times
- input_with_correct_indents = [
- [%q(3.times do |i|), 0, 2, 1],
- [%q( puts i), 2, 2, 1],
- [%q(end), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_bracket_corresponding_to_loop
- input_with_correct_indents = [
- ['loop {', 0, 2, 1],
- [' 3', 2, 2, 1],
- ['}', 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_do_corresponding_to_loop
- input_with_correct_indents = [
- [%q(loop do), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_embdoc_indent
- input_with_correct_indents = [
- [%q(=begin), 0, 0, 0],
- [%q(a), 0, 0, 0],
- [%q( b), 1, 1, 0],
- [%q(=end), 0, 0, 0],
- [%q(if 1), 0, 2, 1],
- [%q( 2), 2, 2, 1],
- [%q(=begin), 0, 0, 0],
- [%q(a), 0, 0, 0],
- [%q( b), 1, 1, 0],
- [%q(=end), 0, 2, 1],
- [%q( 3), 2, 2, 1],
- [%q(end), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_heredoc_with_indent
- input_with_correct_indents = [
- [%q(<<~Q+<<~R), 0, 2, 1],
- [%q(a), 2, 2, 1],
- [%q(a), 2, 2, 1],
- [%q( b), 2, 2, 1],
- [%q( b), 2, 2, 1],
- [%q( Q), 0, 2, 1],
- [%q( c), 4, 4, 1],
- [%q( c), 4, 4, 1],
- [%q( R), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_oneliner_def_in_multiple_lines
- input_with_correct_indents = [
- [%q(def a()=[), 0, 2, 1],
- [%q( 1,), 2, 2, 1],
- [%q(].), 0, 0, 0],
- [%q(to_s), 0, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_broken_heredoc
- input_with_correct_indents = [
- [%q(def foo), 0, 2, 1],
- [%q( <<~Q), 2, 4, 2],
- [%q( Qend), 4, 4, 2],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_pasted_code_keep_base_indent_spaces
- input_with_correct_indents = [
- [%q( def foo), 0, 6, 1],
- [%q( if bar), 6, 10, 2],
- [%q( [1), 10, 12, 3],
- [%q( ]+[["a), 10, 14, 4],
- [%q(b" + `c), 0, 14, 4],
- [%q(d` + /e), 0, 14, 4],
- [%q(f/ + :"g), 0, 14, 4],
- [%q(h".tap do), 0, 16, 5],
- [%q( 1), 16, 16, 5],
- [%q( end), 14, 14, 4],
- [%q( ]), 12, 12, 3],
- [%q( ]), 10, 10, 2],
- [%q( end), 8, 6, 1],
- [%q( end), 4, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_pasted_code_keep_base_indent_spaces_with_heredoc
- input_with_correct_indents = [
- [%q( def foo), 0, 6, 1],
- [%q( if bar), 6, 10, 2],
- [%q( [1), 10, 12, 3],
- [%q( ]+[["a), 10, 14, 4],
- [%q(b" + <<~A + <<-B + <<C), 0, 16, 5],
- [%q( a#{), 16, 18, 6],
- [%q( 1), 18, 18, 6],
- [%q( }), 16, 16, 5],
- [%q( A), 14, 16, 5],
- [%q( b#{), 16, 18, 6],
- [%q( 1), 18, 18, 6],
- [%q( }), 16, 16, 5],
- [%q( B), 14, 0, 0],
- [%q(c#{), 0, 2, 1],
- [%q(1), 2, 2, 1],
- [%q(}), 0, 0, 0],
- [%q(C), 0, 14, 4],
- [%q( ]), 12, 12, 3],
- [%q( ]), 10, 10, 2],
- [%q( end), 8, 6, 1],
- [%q( end), 4, 0, 0],
- ]
-
- assert_rows_with_correct_indents(input_with_correct_indents, assert_indent_level: true)
- end
-
- def test_heredoc_keep_indent_spaces
- (1..4).each do |indent|
- row = Row.new(' ' * indent, nil, [4, indent].max, 2)
- lines = ['def foo', ' <<~Q', row.content]
- assert_row_indenting(lines, row)
- assert_indent_level(lines, row.indent_level)
- end
- end
-
- private
-
- def assert_row_indenting(lines, row)
- actual_current_line_spaces = calculate_indenting(lines, false)
-
- error_message = <<~MSG
- Incorrect spaces calculation for line:
-
- ```
- > #{lines.last}
- ```
-
- All lines:
-
- ```
- #{lines.join("\n")}
- ```
- MSG
- assert_equal(row.current_line_spaces, actual_current_line_spaces, error_message)
-
- error_message = <<~MSG
- Incorrect spaces calculation for line after the current line:
-
- ```
- #{lines.last}
- >
- ```
-
- All lines:
-
- ```
- #{lines.join("\n")}
- ```
- MSG
- actual_next_line_spaces = calculate_indenting(lines, true)
- assert_equal(row.new_line_spaces, actual_next_line_spaces, error_message)
- end
-
- def assert_rows_with_correct_indents(rows_with_spaces, assert_indent_level: false)
- lines = []
- rows_with_spaces.map do |row|
- row = Row.new(*row)
- lines << row.content
- assert_row_indenting(lines, row)
-
- if assert_indent_level
- assert_indent_level(lines, row.indent_level)
- end
- end
- end
-
- def assert_indent_level(lines, expected)
- code = lines.map { |l| "#{l}\n" }.join # code should end with "\n"
- _tokens, opens, _ = @irb.scanner.check_code_state(code, local_variables: [])
- indent_level = @irb.scanner.calc_indent_level(opens)
- error_message = "Calculated the wrong number of indent level for:\n #{lines.join("\n")}"
- assert_equal(expected, indent_level, error_message)
- end
-
- def calculate_indenting(lines, add_new_line)
- lines = lines + [""] if add_new_line
- last_line_index = lines.length - 1
- byte_pointer = lines.last.length
-
- mock_io = MockIO_AutoIndent.new(lines, last_line_index, byte_pointer, add_new_line)
- @irb.context.auto_indent_mode = true
- @irb.context.io = mock_io
- @irb.configure_io
-
- mock_io.calculated_indent
- end
- end
-
- class DynamicPromptTest < IrbIOConfigurationTest
- def test_endless_range_at_end_of_line
- input_with_prompt = [
- ['001:0: :> ', %q(a = 3..)],
- ['002:0: :> ', %q()],
- ]
-
- assert_dynamic_prompt(input_with_prompt)
- end
-
- def test_heredoc_with_embexpr
- input_with_prompt = [
- ['001:0:":* ', %q(<<A+%W[#{<<B)],
- ['002:0:":* ', %q(#{<<C+%W[)],
- ['003:0:":* ', %q(a)],
- ['004:2:]:* ', %q(C)],
- ['005:2:]:* ', %q(a)],
- ['006:0:":* ', %q(]})],
- ['007:0:":* ', %q(})],
- ['008:0:":* ', %q(A)],
- ['009:2:]:* ', %q(B)],
- ['010:1:]:* ', %q(})],
- ['011:0: :> ', %q(])],
- ['012:0: :> ', %q()],
- ]
-
- assert_dynamic_prompt(input_with_prompt)
- end
-
- def test_heredoc_prompt_with_quotes
- input_with_prompt = [
- ["001:1:':* ", %q(<<~'A')],
- ["002:1:':* ", %q(#{foobar})],
- ["003:0: :> ", %q(A)],
- ["004:1:`:* ", %q(<<~`A`)],
- ["005:1:`:* ", %q(whoami)],
- ["006:0: :> ", %q(A)],
- ['007:1:":* ', %q(<<~"A")],
- ['008:1:":* ', %q(foobar)],
- ['009:0: :> ', %q(A)],
- ]
-
- assert_dynamic_prompt(input_with_prompt)
- end
-
- def test_backtick_method
- input_with_prompt = [
- ['001:0: :> ', %q(self.`(arg))],
- ['002:0: :> ', %q()],
- ['003:0: :> ', %q(def `(); end)],
- ['004:0: :> ', %q()],
- ]
-
- assert_dynamic_prompt(input_with_prompt)
- end
-
- def test_dynamic_prompt
- input_with_prompt = [
- ['001:1: :* ', %q(def hoge)],
- ['002:1: :* ', %q( 3)],
- ['003:0: :> ', %q(end)],
- ]
-
- assert_dynamic_prompt(input_with_prompt)
- end
-
- def test_dynamic_prompt_with_double_newline_breaking_code
- input_with_prompt = [
- ['001:1: :* ', %q(if true)],
- ['002:2: :* ', %q(%)],
- ['003:1: :* ', %q(;end)],
- ['004:1: :* ', %q(;hello)],
- ['005:0: :> ', %q(end)],
- ]
-
- assert_dynamic_prompt(input_with_prompt)
- end
-
- def test_dynamic_prompt_with_multiline_literal
- input_with_prompt = [
- ['001:1: :* ', %q(if true)],
- ['002:2:]:* ', %q( %w[)],
- ['003:2:]:* ', %q( a)],
- ['004:1: :* ', %q( ])],
- ['005:1: :* ', %q( b)],
- ['006:2:]:* ', %q( %w[)],
- ['007:2:]:* ', %q( c)],
- ['008:1: :* ', %q( ])],
- ['009:0: :> ', %q(end)],
- ]
-
- assert_dynamic_prompt(input_with_prompt)
- end
-
- def test_dynamic_prompt_with_blank_line
- input_with_prompt = [
- ['001:1:]:* ', %q(%w[)],
- ['002:1:]:* ', %q()],
- ['003:0: :> ', %q(])],
- ]
-
- assert_dynamic_prompt(input_with_prompt)
- end
-
- def assert_dynamic_prompt(input_with_prompt)
- expected_prompt_list, lines = input_with_prompt.transpose
- def @irb.generate_prompt(opens, continue, line_offset)
- ltype = @scanner.ltype_from_open_tokens(opens)
- indent = @scanner.calc_indent_level(opens)
- continue = opens.any? || continue
- line_no = @line_no + line_offset
- '%03d:%01d:%1s:%s ' % [line_no, indent, ltype, continue ? '*' : '>']
- end
- io = MockIO_DynamicPrompt.new(lines)
- @irb.context.io = io
- @irb.configure_io
-
- error_message = <<~EOM
- Expected dynamic prompt:
- #{expected_prompt_list.join("\n")}
-
- Actual dynamic prompt:
- #{io.prompt_list.join("\n")}
- EOM
- assert_equal(expected_prompt_list, io.prompt_list, error_message)
- end
- end
-
- private
-
- def build_binding
- Object.new.instance_eval { binding }
- end
-
- def build_irb
- IRB.init_config(nil)
- workspace = IRB::WorkSpace.new(build_binding)
-
- IRB.conf[:VERBOSE] = false
- IRB::Irb.new(workspace, TestInputMethod.new)
- end
- end
-
- class BacktraceFilteringTest < TestIRB::IntegrationTestCase
- def setup
- super
- # These tests are sensitive to warnings, so we disable them
- original_rubyopt = [ENV["RUBYOPT"], @envs["RUBYOPT"]].compact.join(" ")
- @envs["RUBYOPT"] = original_rubyopt + " -W0"
- end
-
- def test_backtrace_filtering
- write_ruby <<~'RUBY'
- def foo
- raise "error"
- end
-
- def bar
- foo
- end
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "bar"
- type "exit"
- end
-
- assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output)
- frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip)
-
- expected_traces = if RUBY_VERSION >= "3.3.0"
- [
- /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
- /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
- /from <internal:kernel>:\d+:in (`|'Kernel#)loop'/,
- /from <internal:prelude>:\d+:in (`|'Binding#)irb'/,
- /from .*\/irbtest-.*.rb:9:in [`']<main>'/
- ]
- else
- [
- /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
- /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
- /from <internal:prelude>:\d+:in (`|'Binding#)irb'/,
- /from .*\/irbtest-.*.rb:9:in [`']<main>'/
- ]
- end
-
- expected_traces.reverse! if RUBY_VERSION < "3.0.0"
-
- expected_traces.each_with_index do |expected_trace, index|
- assert_match(expected_trace, frame_traces[index])
- end
- end
-
- def test_backtrace_filtering_with_backtrace_filter
- write_rc <<~'RUBY'
- class TestBacktraceFilter
- def self.call(backtrace)
- backtrace.reject { |line| line.include?("internal") }
- end
- end
-
- IRB.conf[:BACKTRACE_FILTER] = TestBacktraceFilter
- RUBY
-
- write_ruby <<~'RUBY'
- def foo
- raise "error"
- end
-
- def bar
- foo
- end
-
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "bar"
- type "exit"
- end
-
- assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output)
- frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip)
-
- expected_traces = [
- /from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
- /from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
- /from .*\/irbtest-.*.rb:9:in [`']<main>'/
- ]
-
- expected_traces.reverse! if RUBY_VERSION < "3.0.0"
-
- expected_traces.each_with_index do |expected_trace, index|
- assert_match(expected_trace, frame_traces[index])
- end
- end
- end
-end
diff --git a/test/irb/test_locale.rb b/test/irb/test_locale.rb
deleted file mode 100644
index 930a38834c..0000000000
--- a/test/irb/test_locale.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-require "irb"
-require "stringio"
-
-require_relative "helper"
-
-module TestIRB
- class LocaleTestCase < TestCase
- def test_initialize_with_en
- locale = IRB::Locale.new("en_US.UTF-8")
-
- assert_equal("en", locale.lang)
- assert_equal("US", locale.territory)
- assert_equal("UTF-8", locale.encoding.name)
- assert_equal(nil, locale.modifier)
- end
-
- def test_initialize_with_ja
- locale = IRB::Locale.new("ja_JP.UTF-8")
-
- assert_equal("ja", locale.lang)
- assert_equal("JP", locale.territory)
- assert_equal("UTF-8", locale.encoding.name)
- assert_equal(nil, locale.modifier)
- end
-
- def test_initialize_with_legacy_ja_encoding_ujis
- original_stderr = $stderr
- $stderr = StringIO.new
-
- locale = IRB::Locale.new("ja_JP.ujis")
-
- assert_equal("ja", locale.lang)
- assert_equal("JP", locale.territory)
- assert_equal(Encoding::EUC_JP, locale.encoding)
- assert_equal(nil, locale.modifier)
-
- assert_include $stderr.string, "ja_JP.ujis is obsolete. use ja_JP.EUC-JP"
- ensure
- $stderr = original_stderr
- end
-
- def test_initialize_with_legacy_ja_encoding_euc
- original_stderr = $stderr
- $stderr = StringIO.new
-
- locale = IRB::Locale.new("ja_JP.euc")
-
- assert_equal("ja", locale.lang)
- assert_equal("JP", locale.territory)
- assert_equal(Encoding::EUC_JP, locale.encoding)
- assert_equal(nil, locale.modifier)
-
- assert_include $stderr.string, "ja_JP.euc is obsolete. use ja_JP.EUC-JP"
- ensure
- $stderr = original_stderr
- end
-
- %w(IRB_LANG LC_MESSAGES LC_ALL LANG).each do |env_var|
- define_method "test_initialize_with_#{env_var.downcase}" do
- original_values = {
- "IRB_LANG" => ENV["IRB_LANG"],
- "LC_MESSAGES" => ENV["LC_MESSAGES"],
- "LC_ALL" => ENV["LC_ALL"],
- "LANG" => ENV["LANG"],
- }
-
- ENV["IRB_LANG"] = ENV["LC_MESSAGES"] = ENV["LC_ALL"] = ENV["LANG"] = nil
- ENV[env_var] = "zh_TW.UTF-8"
-
- locale = IRB::Locale.new
-
- assert_equal("zh", locale.lang)
- assert_equal("TW", locale.territory)
- assert_equal("UTF-8", locale.encoding.name)
- assert_equal(nil, locale.modifier)
- ensure
- original_values.each do |key, value|
- ENV[key] = value
- end
- end
- end
-
- def test_load
- # reset Locale's internal cache
- IRB::Locale.class_variable_set(:@@loaded, [])
- # Because error.rb files define the same class, loading them causes method redefinition warnings.
- original_verbose = $VERBOSE
- $VERBOSE = nil
-
- jp_local = IRB::Locale.new("ja_JP.UTF-8")
- jp_local.load("irb/error.rb")
- msg = IRB::CantReturnToNormalMode.new.message
- assert_equal("Normalモードに戻れません.", msg)
-
- # reset Locale's internal cache
- IRB::Locale.class_variable_set(:@@loaded, [])
-
- en_local = IRB::Locale.new("en_US.UTF-8")
- en_local.load("irb/error.rb")
- msg = IRB::CantReturnToNormalMode.new.message
- assert_equal("Can't return to normal mode.", msg)
- ensure
- # before turning warnings back on, load the error.rb file again to avoid warnings in other tests
- IRB::Locale.new.load("irb/error.rb")
- $VERBOSE = original_verbose
- end
-
- def test_find
- jp_local = IRB::Locale.new("ja_JP.UTF-8")
- path = jp_local.find("irb/error.rb")
- assert_include(path, "/lib/irb/lc/ja/error.rb")
-
- en_local = IRB::Locale.new("en_US.UTF-8")
- path = en_local.find("irb/error.rb")
- assert_include(path, "/lib/irb/lc/error.rb")
- end
- end
-end
diff --git a/test/irb/test_nesting_parser.rb b/test/irb/test_nesting_parser.rb
deleted file mode 100644
index 6b4f54ee21..0000000000
--- a/test/irb/test_nesting_parser.rb
+++ /dev/null
@@ -1,339 +0,0 @@
-# frozen_string_literal: false
-require 'irb'
-
-require_relative "helper"
-
-module TestIRB
- class NestingParserTest < TestCase
- def setup
- save_encodings
- end
-
- def teardown
- restore_encodings
- end
-
- def parse_by_line(code)
- IRB::NestingParser.parse_by_line(IRB::RubyLex.ripper_lex_without_warning(code))
- end
-
- def test_open_tokens
- code = <<~'EOS'
- class A
- def f
- if true
- tap do
- {
- x: "
- #{p(1, 2, 3
- EOS
- opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning(code))
- assert_equal(%w[class def if do { " #{ (], opens.map(&:tok))
- end
-
- def test_parse_by_line
- code = <<~EOS
- (((((1+2
- ).to_s())).tap do (((
- EOS
- _tokens, prev_opens, next_opens, min_depth = parse_by_line(code).last
- assert_equal(%w[( ( ( ( (], prev_opens.map(&:tok))
- assert_equal(%w[( ( do ( ( (], next_opens.map(&:tok))
- assert_equal(2, min_depth)
- end
-
- def test_ruby_syntax
- code = <<~'EOS'
- class A
- 1 if 2
- 1 while 2
- 1 until 2
- 1 unless 2
- 1 rescue 2
- begin; rescue; ensure; end
- tap do; rescue; ensure; end
- class B; end
- module C; end
- def f; end
- def `; end
- def f() = 1
- %(); %w[]; %q(); %r{}; %i[]
- "#{1}"; ''; /#{1}/; `#{1}`
- p(``); p ``; p x: ``; p 1, ``;
- :sym; :"sym"; :+; :`; :if
- [1, 2, 3]
- { x: 1, y: 2 }
- (a, (*b, c), d), e = 1, 2, 3
- ->(a){}; ->(a) do end
- -> a = -> b = :do do end do end
- if 1; elsif 2; else; end
- unless 1; end
- while 1; end
- until 1; end
- for i in j; end
- case 1; when 2; end
- puts(1, 2, 3)
- loop{|i|}
- loop do |i| end
- end
- EOS
- line_results = parse_by_line(code)
- assert_equal(code.lines.size, line_results.size)
- class_open, *inner_line_results, class_close = line_results
- assert_equal(['class'], class_open[2].map(&:tok))
- inner_line_results.each {|result| assert_equal(['class'], result[2].map(&:tok)) }
- assert_equal([], class_close[2].map(&:tok))
- end
-
- def test_multiline_string
- code = <<~EOS
- "
- aaa
- bbb
- "
- <<A
- aaa
- bbb
- A
- EOS
- line_results = parse_by_line(code)
- assert_equal(code.lines.size, line_results.size)
- string_content_line, string_opens = line_results[1]
- assert_equal("\naaa\nbbb\n", string_content_line.first.first.tok)
- assert_equal("aaa\n", string_content_line.first.last)
- assert_equal(['"'], string_opens.map(&:tok))
- heredoc_content_line, heredoc_opens = line_results[6]
- assert_equal("aaa\nbbb\n", heredoc_content_line.first.first.tok)
- assert_equal("bbb\n", heredoc_content_line.first.last)
- assert_equal(['<<A'], heredoc_opens.map(&:tok))
- _line, _prev_opens, next_opens, _min_depth = line_results.last
- assert_equal([], next_opens)
- end
-
- def test_backslash_continued_nested_symbol
- code = <<~'EOS'
- x = <<A, :\
- heredoc #{
- here
- }
- A
- =begin
- embdoc
- =end
- # comment
-
- if # this is symbol :if
- while
- EOS
- line_results = parse_by_line(code)
- assert_equal(%w[: <<A #{], line_results[2][2].map(&:tok))
- assert_equal(%w[while], line_results.last[2].map(&:tok))
- end
-
- def test_oneliner_def
- code = <<~EOC
- if true
- # normal oneliner def
- def f = 1
- def f() = 1
- def f(*) = 1
- # keyword, backtick, op
- def * = 1
- def ` = 1
- def if = 1
- def *() = 1
- def `() = 1
- def if() = 1
- # oneliner def with receiver
- def a.* = 1
- def $a.* = 1
- def @a.` = 1
- def A.` = 1
- def ((a;b;c)).*() = 1
- def ((a;b;c)).if() = 1
- def ((a;b;c)).end() = 1
- # multiline oneliner def
- def f =
- 1
- def f()
- =
- 1
- # oneliner def with comment and embdoc
- def # comment
- =begin
- embdoc
- =end
- ((a;b;c))
- . # comment
- =begin
- embdoc
- =end
- f (*) # comment
- =begin
- embdoc
- =end
- =
- 1
- # nested oneliner def
- def f(x = def f() = 1) = def f() = 1
- EOC
- _tokens, _prev_opens, next_opens, min_depth = parse_by_line(code).last
- assert_equal(['if'], next_opens.map(&:tok))
- assert_equal(1, min_depth)
- end
-
- def test_heredoc_embexpr
- code = <<~'EOS'
- <<A+<<B+<<C+(<<D+(<<E)
- #{
- <<~F+"#{<<~G}
- #{
- here
- }
- F
- G
- "
- }
- A
- B
- C
- D
- E
- )
- EOS
- line_results = parse_by_line(code)
- last_opens = line_results.last[-2]
- assert_equal([], last_opens)
- _tokens, _prev_opens, next_opens, _min_depth = line_results[4]
- assert_equal(%w[( <<E <<D <<C <<B <<A #{ " <<~G <<~F #{], next_opens.map(&:tok))
- end
-
- def test_for_in
- code = <<~EOS
- for i in j
- here
- end
- for i in j do
- here
- end
- for i in
- j do
- here
- end
- for
- # comment
- i in j do
- here
- end
- for (a;b;c).d in (a;b;c) do
- here
- end
- for i in :in + :do do
- here
- end
- for i in -> do end do
- here
- end
- EOS
- line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') }
- assert_equal(7, line_results.size)
- line_results.each do |_tokens, _prev_opens, next_opens, _min_depth|
- assert_equal(['for'], next_opens.map(&:tok))
- end
- end
-
- def test_while_until
- base_code = <<~'EOS'
- while_or_until true
- here
- end
- while_or_until a < c
- here
- end
- while_or_until true do
- here
- end
- while_or_until
- # comment
- (a + b) <
- # comment
- c do
- here
- end
- while_or_until :\
- do do
- here
- end
- while_or_until def do; end == :do do
- here
- end
- while_or_until -> do end do
- here
- end
- EOS
- %w[while until].each do |keyword|
- code = base_code.gsub('while_or_until', keyword)
- line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') }
- assert_equal(7, line_results.size)
- line_results.each do |_tokens, _prev_opens, next_opens, _min_depth|
- assert_equal([keyword], next_opens.map(&:tok) )
- end
- end
- end
-
- def test_undef_alias
- codes = [
- 'undef foo',
- 'alias foo bar',
- 'undef !',
- 'alias + -',
- 'alias $a $b',
- 'undef do',
- 'alias do do',
- 'undef :do',
- 'alias :do :do',
- 'undef :"#{alias do do}"',
- 'alias :"#{undef do}" do',
- 'alias do :"#{undef do}"'
- ]
- code_with_comment = <<~EOS
- undef #
- #
- do #
- alias #
- #
- do #
- #
- do #
- EOS
- code_with_heredoc = <<~EOS
- <<~A; alias
- A
- :"#{<<~A}"
- A
- do
- EOS
- [*codes, code_with_comment, code_with_heredoc].each do |code|
- opens = IRB::NestingParser.open_tokens(IRB::RubyLex.ripper_lex_without_warning('(' + code + "\nif"))
- assert_equal(%w[( if], opens.map(&:tok))
- end
- end
-
- def test_case_in
- code = <<~EOS
- case 1
- in 1
- here
- in
- 2
- here
- end
- EOS
- line_results = parse_by_line(code).select { |tokens,| tokens.map(&:last).include?('here') }
- assert_equal(2, line_results.size)
- line_results.each do |_tokens, _prev_opens, next_opens, _min_depth|
- assert_equal(['in'], next_opens.map(&:tok))
- end
- end
- end
-end
diff --git a/test/irb/test_option.rb b/test/irb/test_option.rb
deleted file mode 100644
index fec31f384f..0000000000
--- a/test/irb/test_option.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: false
-require_relative "helper"
-
-module TestIRB
- class OptionTest < TestCase
- def test_end_of_option
- bug4117 = '[ruby-core:33574]'
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- status = assert_in_out_err(bundle_exec + %w[-W0 -rirb -e IRB.start(__FILE__) -- -f --], "", //, [], bug4117)
- assert(status.success?, bug4117)
- end
- end
-end
diff --git a/test/irb/test_raise_exception.rb b/test/irb/test_raise_exception.rb
deleted file mode 100644
index 44a5ae87e1..0000000000
--- a/test/irb/test_raise_exception.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: false
-require "tmpdir"
-
-require_relative "helper"
-
-module TestIRB
- class RaiseExceptionTest < TestCase
- def test_raise_exception_with_nil_backtrace
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /#<Exception: foo>/, [])
- raise Exception.new("foo").tap {|e| def e.backtrace; nil; end }
-IRB
- end
-
- def test_raise_exception_with_message_exception
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- expected = /#<Exception: foo>\nbacktraces are hidden because bar was raised when processing them/
- assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, [])
- e = Exception.new("foo")
- def e.message; raise 'bar'; end
- raise e
-IRB
- end
-
- def test_raise_exception_with_message_inspect_exception
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- expected = /Uninspectable exception occurred/
- assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, expected, [])
- e = Exception.new("foo")
- def e.message; raise; end
- def e.inspect; raise; end
- raise e
-IRB
- end
-
- def test_raise_exception_with_invalid_byte_sequence
- pend if RUBY_ENGINE == 'truffleruby' || /mswin|mingw/ =~ RUBY_PLATFORM
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<~IRB, /A\\xF3B \(StandardError\)/, [])
- raise StandardError, "A\\xf3B"
- IRB
- end
-
- def test_raise_exception_with_different_encoding_containing_invalid_byte_sequence
- backup_home = ENV["HOME"]
- Dir.mktmpdir("test_irb_raise_no_backtrace_exception_#{$$}") do |tmpdir|
- ENV["HOME"] = tmpdir
-
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- File.open("#{tmpdir}/euc.rb", 'w') do |f|
- f.write(<<~EOF)
- # encoding: euc-jp
-
- def raise_euc_with_invalid_byte_sequence
- raise "\xA4\xA2\\xFF"
- end
- EOF
- end
- env = {}
- %w(LC_MESSAGES LC_ALL LC_CTYPE LANG).each {|n| env[n] = "ja_JP.UTF-8" }
- # TruffleRuby warns when the locale does not exist
- env['TRUFFLERUBYOPT'] = "#{ENV['TRUFFLERUBYOPT']} --log.level=SEVERE" if RUBY_ENGINE == 'truffleruby'
- args = [env] + bundle_exec + %W[-rirb -C #{tmpdir} -W0 -e IRB.start(__FILE__) -- -f --]
- error = /raise_euc_with_invalid_byte_sequence': あ\\xFF \(RuntimeError\)/
- assert_in_out_err(args, <<~IRB, error, [], encoding: "UTF-8")
- require_relative 'euc'
- raise_euc_with_invalid_byte_sequence
- IRB
- end
- ensure
- ENV["HOME"] = backup_home
- end
- end
-end
diff --git a/test/irb/test_ruby_lex.rb b/test/irb/test_ruby_lex.rb
deleted file mode 100644
index 4e406a8ce0..0000000000
--- a/test/irb/test_ruby_lex.rb
+++ /dev/null
@@ -1,242 +0,0 @@
-# frozen_string_literal: true
-require "irb"
-
-require_relative "helper"
-
-module TestIRB
- class RubyLexTest < TestCase
- def setup
- save_encodings
- end
-
- def teardown
- restore_encodings
- end
-
- def test_interpolate_token_with_heredoc_and_unclosed_embexpr
- code = <<~'EOC'
- ①+<<A-②
- #{③*<<B/④
- #{⑤&<<C|⑥
- EOC
- ripper_tokens = Ripper.tokenize(code)
- rubylex_tokens = IRB::RubyLex.ripper_lex_without_warning(code)
- # Assert no missing part
- assert_equal(code, rubylex_tokens.map(&:tok).join)
- # Assert ripper tokens are not removed
- ripper_tokens.each do |tok|
- assert(rubylex_tokens.any? { |t| t.tok == tok && t.tok != :on_ignored_by_ripper })
- end
- # Assert interpolated token position
- rubylex_tokens.each do |t|
- row, col = t.pos
- assert_equal t.tok, code.lines[row - 1].byteslice(col, t.tok.bytesize)
- end
- end
-
- def test_local_variables_dependent_code
- lines = ["a /1#/ do", "2"]
- assert_indent_level(lines, 1)
- assert_code_block_open(lines, true)
- assert_indent_level(lines, 0, local_variables: ['a'])
- assert_code_block_open(lines, false, local_variables: ['a'])
- end
-
- def test_literal_ends_with_space
- assert_code_block_open(['% a'], true)
- assert_code_block_open(['% a '], false)
- end
-
- def test_literal_ends_with_newline
- assert_code_block_open(['%'], true)
- assert_code_block_open(['%', ''], false)
- end
-
- def test_should_continue
- assert_should_continue(['a'], false)
- assert_should_continue(['/a/'], false)
- assert_should_continue(['a;'], false)
- assert_should_continue(['<<A', 'A'], false)
- assert_should_continue(['a...'], false)
- assert_should_continue(['a\\'], true)
- assert_should_continue(['a.'], true)
- assert_should_continue(['a+'], true)
- assert_should_continue(['a; #comment', '', '=begin', 'embdoc', '=end', ''], false)
- assert_should_continue(['a+ #comment', '', '=begin', 'embdoc', '=end', ''], true)
- end
-
- def test_code_block_open_with_should_continue
- # syntax ok
- assert_code_block_open(['a'], false) # continue: false
- assert_code_block_open(['a\\'], true) # continue: true
-
- # recoverable syntax error code is not terminated
- assert_code_block_open(['a+'], true)
-
- # unrecoverable syntax error code is terminated
- assert_code_block_open(['.; a+'], false)
-
- # other syntax error that failed to determine if it is recoverable or not
- assert_code_block_open(['@; a'], false)
- assert_code_block_open(['@; a+'], true)
- assert_code_block_open(['@; (a'], true)
- end
-
- def test_broken_percent_literal
- tokens = IRB::RubyLex.ripper_lex_without_warning('%wwww')
- pos_to_index = {}
- tokens.each_with_index { |t, i|
- assert_nil(pos_to_index[t.pos], "There is already another token in the position of #{t.inspect}.")
- pos_to_index[t.pos] = i
- }
- end
-
- def test_broken_percent_literal_in_method
- tokens = IRB::RubyLex.ripper_lex_without_warning(<<~EOC.chomp)
- def foo
- %wwww
- end
- EOC
- pos_to_index = {}
- tokens.each_with_index { |t, i|
- assert_nil(pos_to_index[t.pos], "There is already another token in the position of #{t.inspect}.")
- pos_to_index[t.pos] = i
- }
- end
-
- def test_unterminated_code
- ['do', '<<A'].each do |code|
- tokens = IRB::RubyLex.ripper_lex_without_warning(code)
- assert_equal(code, tokens.map(&:tok).join, "Cannot reconstruct code from tokens")
- error_tokens = tokens.map(&:event).grep(/error/)
- assert_empty(error_tokens, 'Error tokens must be ignored if there is corresponding non-error token')
- end
- end
-
- def test_unterminated_heredoc_string_literal
- ['<<A;<<B', "<<A;<<B\n", "%W[\#{<<A;<<B", "%W[\#{<<A;<<B\n"].each do |code|
- tokens = IRB::RubyLex.ripper_lex_without_warning(code)
- string_literal = IRB::NestingParser.open_tokens(tokens).last
- assert_equal('<<A', string_literal&.tok)
- end
- end
-
- def test_indent_level_with_heredoc_and_embdoc
- reference_code = <<~EOC.chomp
- if true
- hello
- p(
- )
- EOC
- code_with_heredoc = <<~EOC.chomp
- if true
- <<~A
- A
- p(
- )
- EOC
- code_with_embdoc = <<~EOC.chomp
- if true
- =begin
- =end
- p(
- )
- EOC
- expected = 1
- assert_indent_level(reference_code.lines, expected)
- assert_indent_level(code_with_heredoc.lines, expected)
- assert_indent_level(code_with_embdoc.lines, expected)
- end
-
- def test_assignment_expression
- ruby_lex = IRB::RubyLex.new
-
- [
- "foo = bar",
- "@foo = bar",
- "$foo = bar",
- "@@foo = bar",
- "::Foo = bar",
- "a::Foo = bar",
- "Foo = bar",
- "foo.bar = 1",
- "foo[1] = bar",
- "foo += bar",
- "foo -= bar",
- "foo ||= bar",
- "foo &&= bar",
- "foo, bar = 1, 2",
- "foo.bar=(1)",
- "foo; foo = bar",
- "foo; foo = bar; ;\n ;",
- "foo\nfoo = bar",
- ].each do |exp|
- assert(
- ruby_lex.assignment_expression?(exp, local_variables: []),
- "#{exp.inspect}: should be an assignment expression"
- )
- end
-
- [
- "foo",
- "foo.bar",
- "foo[0]",
- "foo = bar; foo",
- "foo = bar\nfoo",
- ].each do |exp|
- refute(
- ruby_lex.assignment_expression?(exp, local_variables: []),
- "#{exp.inspect}: should not be an assignment expression"
- )
- end
- end
-
- def test_assignment_expression_with_local_variable
- ruby_lex = IRB::RubyLex.new
- code = "a /1;x=1#/"
- refute(ruby_lex.assignment_expression?(code, local_variables: []), "#{code}: should not be an assignment expression")
- assert(ruby_lex.assignment_expression?(code, local_variables: [:a]), "#{code}: should be an assignment expression")
- refute(ruby_lex.assignment_expression?("", local_variables: [:a]), "empty code should not be an assignment expression")
- end
-
- def test_initialising_the_old_top_level_ruby_lex
- assert_in_out_err(["--disable-gems", "-W:deprecated"], <<~RUBY, [], /warning: constant ::RubyLex is deprecated/)
- require "irb"
- ::RubyLex.new(nil)
- RUBY
- end
-
- private
-
- def assert_indent_level(lines, expected, local_variables: [])
- indent_level, _continue, _code_block_open = check_state(lines, local_variables: local_variables)
- error_message = "Calculated the wrong number of indent level for:\n #{lines.join("\n")}"
- assert_equal(expected, indent_level, error_message)
- end
-
- def assert_should_continue(lines, expected, local_variables: [])
- _indent_level, continue, _code_block_open = check_state(lines, local_variables: local_variables)
- error_message = "Wrong result of should_continue for:\n #{lines.join("\n")}"
- assert_equal(expected, continue, error_message)
- end
-
- def assert_code_block_open(lines, expected, local_variables: [])
- if RUBY_ENGINE == 'truffleruby'
- omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby"
- end
-
- _indent_level, _continue, code_block_open = check_state(lines, local_variables: local_variables)
- error_message = "Wrong result of code_block_open for:\n #{lines.join("\n")}"
- assert_equal(expected, code_block_open, error_message)
- end
-
- def check_state(lines, local_variables: [])
- code = lines.map { |l| "#{l}\n" }.join # code should end with "\n"
- ruby_lex = IRB::RubyLex.new
- tokens, opens, terminated = ruby_lex.check_code_state(code, local_variables: local_variables)
- indent_level = ruby_lex.calc_indent_level(opens)
- continue = ruby_lex.should_continue?(tokens)
- [indent_level, continue, !terminated]
- end
- end
-end
diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb
deleted file mode 100644
index 540f8be131..0000000000
--- a/test/irb/test_tracer.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-# frozen_string_literal: false
-require 'tempfile'
-require 'irb'
-
-require_relative "helper"
-
-module TestIRB
- class ContextWithTracerIntegrationTest < IntegrationTestCase
- def setup
- super
-
- omit "Tracer gem is not available when running on TruffleRuby" if RUBY_ENGINE == "truffleruby"
-
- @envs.merge!("NO_COLOR" => "true")
- end
-
- def example_ruby_file
- <<~'RUBY'
- class Foo
- def self.foo
- 100
- end
- end
-
- def bar(obj)
- obj.foo
- end
-
- binding.irb
- RUBY
- end
-
- def test_use_tracer_enabled_when_gem_is_unavailable
- write_rc <<~RUBY
- # Simulate the absence of the tracer gem
- ::Kernel.send(:alias_method, :irb_original_require, :require)
-
- ::Kernel.define_method(:require) do |name|
- raise LoadError, "cannot load such file -- tracer (test)" if name.match?("tracer")
- ::Kernel.send(:irb_original_require, name)
- end
-
- IRB.conf[:USE_TRACER] = true
- RUBY
-
- write_ruby example_ruby_file
-
- output = run_ruby_file do
- type "bar(Foo)"
- type "exit"
- end
-
- assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.")
- end
-
- def test_use_tracer_enabled_when_gem_is_available
- write_rc <<~RUBY
- IRB.conf[:USE_TRACER] = true
- RUBY
-
- write_ruby example_ruby_file
-
- output = run_ruby_file do
- type "bar(Foo)"
- type "exit"
- end
-
- assert_include(output, "Object#bar at")
- assert_include(output, "Foo.foo at")
- assert_include(output, "Foo.foo #=> 100")
- assert_include(output, "Object#bar #=> 100")
-
- # Test that the tracer output does not include IRB's own files
- assert_not_include(output, "irb/workspace.rb")
- end
-
- def test_use_tracer_is_disabled_by_default
- write_ruby example_ruby_file
-
- output = run_ruby_file do
- type "bar(Foo)"
- type "exit"
- end
-
- assert_not_include(output, "#depth:")
- assert_not_include(output, "Foo.foo")
- end
-
- end
-end
diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb
deleted file mode 100644
index 3d0e25d19e..0000000000
--- a/test/irb/test_type_completor.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# frozen_string_literal: true
-
-# Run test only when Ruby >= 3.0 and repl_type_completor is available
-return unless RUBY_VERSION >= '3.0.0'
-return if RUBY_ENGINE == 'truffleruby' # needs endless method definition
-begin
- require 'repl_type_completor'
-rescue LoadError
- return
-end
-
-require 'irb'
-require 'tempfile'
-require_relative './helper'
-
-module TestIRB
- class TypeCompletorTest < TestCase
- DummyContext = Struct.new(:irb_path)
-
- def setup
- ReplTypeCompletor.load_rbs unless ReplTypeCompletor.rbs_loaded?
- context = DummyContext.new('(irb)')
- @completor = IRB::TypeCompletor.new(context)
- end
-
- def empty_binding
- binding
- end
-
- def test_build_completor
- IRB.init_config(nil)
- verbose, $VERBOSE = $VERBOSE, nil
- original_completor = IRB.conf[:COMPLETOR]
- workspace = IRB::WorkSpace.new(Object.new)
- @context = IRB::Context.new(nil, workspace, TestInputMethod.new)
- IRB.conf[:COMPLETOR] = nil
- expected_default_completor = RUBY_VERSION >= '3.4' ? 'IRB::TypeCompletor' : 'IRB::RegexpCompletor'
- assert_equal expected_default_completor, @context.send(:build_completor).class.name
- IRB.conf[:COMPLETOR] = :type
- assert_equal 'IRB::TypeCompletor', @context.send(:build_completor).class.name
- ensure
- $VERBOSE = verbose
- IRB.conf[:COMPLETOR] = original_completor
- end
-
- def assert_completion(preposing, target, binding: empty_binding, include: nil, exclude: nil)
- raise ArgumentError if include.nil? && exclude.nil?
- candidates = @completor.completion_candidates(preposing, target, '', bind: binding)
- assert ([*include] - candidates).empty?, "Expected #{candidates} to include #{include}" if include
- assert (candidates & [*exclude]).empty?, "Expected #{candidates} not to include #{exclude}" if exclude
- end
-
- def assert_doc_namespace(preposing, target, namespace, binding: empty_binding)
- @completor.completion_candidates(preposing, target, '', bind: binding)
- assert_equal namespace, @completor.doc_namespace(preposing, target, '', bind: binding)
- end
-
- def test_type_completion
- bind = eval('num = 1; binding')
- assert_completion('num.times.map(&:', 'ab', binding: bind, include: 'abs')
- assert_doc_namespace('num.chr.', 'upcase', 'String#upcase', binding: bind)
- end
-
- def test_inspect
- assert_match(/\AReplTypeCompletor.*\z/, @completor.inspect)
- end
-
- def test_empty_completion
- candidates = @completor.completion_candidates('(', ')', '', bind: binding)
- assert_equal [], candidates
- assert_doc_namespace('(', ')', nil)
- end
-
- def test_command_completion
- binding.eval("some_var = 1")
- # completion for help command's argument should only include command names
- assert_include(@completor.completion_candidates('help ', 's', '', bind: binding), 'show_source')
- assert_not_include(@completor.completion_candidates('help ', 's', '', bind: binding), 'some_var')
-
- assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source')
- assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source')
- end
- end
-
- class TypeCompletorIntegrationTest < IntegrationTestCase
- def test_type_completor
- write_rc <<~RUBY
- IRB.conf[:COMPLETOR] = :type
- RUBY
-
- write_ruby <<~'RUBY'
- binding.irb
- RUBY
-
- output = run_ruby_file do
- type "irb_info"
- type "sleep 0.01 until ReplTypeCompletor.rbs_loaded?"
- type "completor = IRB.CurrentContext.io.instance_variable_get(:@completor);"
- type "n = 10"
- type "puts completor.completion_candidates 'a = n.abs;', 'a.b', '', bind: binding"
- type "puts completor.doc_namespace 'a = n.chr;', 'a.encoding', '', bind: binding"
- type "exit!"
- end
- assert_match(/Completion: Autocomplete, ReplTypeCompletor/, output)
- assert_match(/a\.bit_length/, output)
- assert_match(/String#encoding/, output)
- end
- end
-end
diff --git a/test/irb/test_workspace.rb b/test/irb/test_workspace.rb
deleted file mode 100644
index ad515f91df..0000000000
--- a/test/irb/test_workspace.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-# frozen_string_literal: false
-require 'tempfile'
-require 'irb'
-require 'irb/workspace'
-require 'irb/color'
-
-require_relative "helper"
-
-module TestIRB
- class WorkSpaceTest < TestCase
- def test_code_around_binding
- IRB.conf[:USE_COLORIZE] = false
- Tempfile.create('irb') do |f|
- code = <<~RUBY
- # 1
- # 2
- IRB::WorkSpace.new(binding) # 3
- # 4
- # 5
- RUBY
- f.print(code)
- f.close
-
- workspace = eval(code, binding, f.path)
- assert_equal(<<~EOS, without_term { workspace.code_around_binding })
-
- From: #{f.path} @ line 3 :
-
- 1: # 1
- 2: # 2
- => 3: IRB::WorkSpace.new(binding) # 3
- 4: # 4
- 5: # 5
-
- EOS
- end
- ensure
- IRB.conf.delete(:USE_COLORIZE)
- end
-
- def test_code_around_binding_with_existing_unreadable_file
- pend 'chmod cannot make file unreadable on windows' if windows?
- pend 'skipped in root privilege' if Process.uid == 0
-
- Tempfile.create('irb') do |f|
- code = "IRB::WorkSpace.new(binding)\n"
- f.print(code)
- f.close
-
- File.chmod(0, f.path)
-
- workspace = eval(code, binding, f.path)
- assert_equal(nil, workspace.code_around_binding)
- end
- end
-
- def test_code_around_binding_with_script_lines__
- IRB.conf[:USE_COLORIZE] = false
- with_script_lines do |script_lines|
- Tempfile.create('irb') do |f|
- code = "IRB::WorkSpace.new(binding)\n"
- script_lines[f.path] = code.split(/^/)
-
- workspace = eval(code, binding, f.path)
- assert_equal(<<~EOS, without_term { workspace.code_around_binding })
-
- From: #{f.path} @ line 1 :
-
- => 1: IRB::WorkSpace.new(binding)
-
- EOS
- end
- end
- ensure
- IRB.conf.delete(:USE_COLORIZE)
- end
-
- def test_code_around_binding_on_irb
- workspace = eval("IRB::WorkSpace.new(binding)", binding, "(irb)")
- assert_equal(nil, workspace.code_around_binding)
- end
-
- def test_toplevel_binding_local_variables
- bug17623 = '[ruby-core:102468]'
- bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
- top_srcdir = "#{__dir__}/../.."
- irb_path = nil
- %w[exe libexec].find do |dir|
- irb_path = "#{top_srcdir}/#{dir}/irb"
- File.exist?(irb_path)
- end or omit 'irb command not found'
- assert_in_out_err(bundle_exec + ['-W0', "-C#{top_srcdir}", '-e', <<~RUBY, '--', '-f', '--'], 'binding.local_variables', /\[:_\]/, [], bug17623)
- version = 'xyz' # typical rubygems loading file
- load('#{irb_path}')
- RUBY
- end
-
- private
-
- def with_script_lines
- script_lines = nil
- debug_lines = {}
- Object.class_eval do
- if defined?(SCRIPT_LINES__)
- script_lines = SCRIPT_LINES__
- remove_const :SCRIPT_LINES__
- end
- const_set(:SCRIPT_LINES__, debug_lines)
- end
- yield debug_lines
- ensure
- Object.class_eval do
- remove_const :SCRIPT_LINES__
- const_set(:SCRIPT_LINES__, script_lines) if script_lines
- end
- end
-
- def without_term
- env = ENV.to_h.dup
- ENV.delete('TERM')
- yield
- ensure
- ENV.replace(env)
- end
- end
-end
diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb
deleted file mode 100644
index 212ab0cf81..0000000000
--- a/test/irb/yamatanooroti/test_rendering.rb
+++ /dev/null
@@ -1,478 +0,0 @@
-require 'irb'
-
-begin
- require 'yamatanooroti'
-rescue LoadError, NameError
- # On Ruby repository, this test suite doesn't run because Ruby repo doesn't
- # have the yamatanooroti gem.
- return
-end
-
-class IRB::RenderingTest < Yamatanooroti::TestCase
- def setup
- @original_term = ENV['TERM']
- @home_backup = ENV['HOME']
- @xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
- ENV['TERM'] = "xterm-256color"
- @pwd = Dir.pwd
- suffix = '%010d' % Random.rand(0..65535)
- @tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_irb_#{$$}_#{suffix}")
- begin
- Dir.mkdir(@tmpdir)
- rescue Errno::EEXIST
- FileUtils.rm_rf(@tmpdir)
- Dir.mkdir(@tmpdir)
- end
- @irbrc_backup = ENV['IRBRC']
- @irbrc_file = ENV['IRBRC'] = File.join(@tmpdir, 'temporaty_irbrc')
- File.unlink(@irbrc_file) if File.exist?(@irbrc_file)
- ENV['HOME'] = File.join(@tmpdir, 'home')
- ENV['XDG_CONFIG_HOME'] = File.join(@tmpdir, 'xdg_config_home')
- end
-
- def teardown
- FileUtils.rm_rf(@tmpdir)
- ENV['IRBRC'] = @irbrc_backup
- ENV['TERM'] = @original_term
- ENV['HOME'] = @home_backup
- ENV['XDG_CONFIG_HOME'] = @xdg_config_home_backup
- end
-
- def test_launch
- start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write(<<~EOC)
- 'Hello, World!'
- EOC
- assert_screen(<<~EOC)
- irb(main):001> 'Hello, World!'
- => "Hello, World!"
- irb(main):002>
- EOC
- close
- end
-
- def test_configuration_file_is_skipped_with_dash_f
- write_irbrc <<~'LINES'
- puts '.irbrc file should be ignored when -f is used'
- LINES
- start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: /irb\(main\)/)
- write(<<~EOC)
- 'Hello, World!'
- EOC
- assert_screen(<<~EOC)
- irb(main):001> 'Hello, World!'
- => "Hello, World!"
- irb(main):002>
- EOC
- close
- end
-
- def test_configuration_file_is_skipped_with_dash_f_for_nested_sessions
- write_irbrc <<~'LINES'
- puts '.irbrc file should be ignored when -f is used'
- LINES
- start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: /irb\(main\)/)
- write(<<~EOC)
- 'Hello, World!'
- binding.irb
- exit!
- EOC
- assert_screen(<<~EOC)
- irb(main):001> 'Hello, World!'
- => "Hello, World!"
- irb(main):002> binding.irb
- irb(main):003> exit!
- irb(main):001>
- EOC
- close
- end
-
- def test_nomultiline
- start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb --nomultiline}, startup_message: /irb\(main\)/)
- write(<<~EOC)
- if true
- if false
- a = "hello
- world"
- puts a
- end
- end
- EOC
- assert_screen(<<~EOC)
- irb(main):001> if true
- irb(main):002* if false
- irb(main):003* a = "hello
- irb(main):004" world"
- irb(main):005* puts a
- irb(main):006* end
- irb(main):007* end
- => nil
- irb(main):008>
- EOC
- close
- end
-
- def test_multiline_paste
- start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write(<<~EOC)
- class A
- def inspect; '#<A>'; end
- def a; self; end
- def b; true; end
- end
-
- a = A.new
-
- a
- .a
- .b
- .itself
- EOC
- assert_screen(<<~EOC)
- irb(main):001* class A
- irb(main):002* def inspect; '#<A>'; end
- irb(main):003* def a; self; end
- irb(main):004* def b; true; end
- irb(main):005> end
- => :b
- irb(main):006>
- irb(main):007> a = A.new
- => #<A>
- irb(main):008>
- irb(main):009> a
- irb(main):010> .a
- irb(main):011> .b
- irb(main):012> .itself
- => true
- irb(main):013>
- EOC
- close
- end
-
- def test_evaluate_each_toplevel_statement_by_multiline_paste
- start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write(<<~EOC)
- class A
- def inspect; '#<A>'; end
- def b; self; end
- def c; true; end
- end
-
- a = A.new
-
- a
- .b
- # aaa
- .c
-
- (a)
- &.b()
-
- class A def b; self; end; def c; true; end; end;
- a = A.new
- a
- .b
- # aaa
- .c
- (a)
- &.b()
- .itself
- EOC
- assert_screen(<<~EOC)
- irb(main):001* class A
- irb(main):002* def inspect; '#<A>'; end
- irb(main):003* def b; self; end
- irb(main):004* def c; true; end
- irb(main):005> end
- => :c
- irb(main):006>
- irb(main):007> a = A.new
- => #<A>
- irb(main):008>
- irb(main):009> a
- irb(main):010> .b
- irb(main):011> # aaa
- irb(main):012> .c
- => true
- irb(main):013>
- irb(main):014> (a)
- irb(main):015> &.b()
- => #<A>
- irb(main):016>
- irb(main):017> class A def b; self; end; def c; true; end; end;
- irb(main):018> a = A.new
- => #<A>
- irb(main):019> a
- irb(main):020> .b
- irb(main):021> # aaa
- irb(main):022> .c
- => true
- irb(main):023> (a)
- irb(main):024> &.b()
- irb(main):025> .itself
- => #<A>
- irb(main):026>
- EOC
- close
- end
-
- def test_symbol_with_backtick
- start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write(<<~EOC)
- :`
- EOC
- assert_screen(<<~EOC)
- irb(main):001> :`
- => :`
- irb(main):002>
- EOC
- close
- end
-
- def test_autocomplete_with_multiple_doc_namespaces
- start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write("{}.__id_")
- write("\C-i")
- assert_screen(/irb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/)
- close
- end
-
- def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right
- rdoc_dir = File.join(@tmpdir, 'rdoc')
- system("bundle exec rdoc lib -r -o #{rdoc_dir}")
- write_irbrc <<~LINES
- IRB.conf[:EXTRA_DOC_DIRS] = ['#{rdoc_dir}']
- IRB.conf[:PROMPT][:MY_PROMPT] = {
- :PROMPT_I => "%03n> ",
- :PROMPT_S => "%03n> ",
- :PROMPT_C => "%03n> "
- }
- IRB.conf[:PROMPT_MODE] = :MY_PROMPT
- LINES
- start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /001>/)
- write("IR")
- write("\C-i")
-
- # This is because on macOS we display different shortcut for displaying the full doc
- # 'O' is for 'Option' and 'A' is for 'Alt'
- if RUBY_PLATFORM =~ /darwin/
- assert_screen(<<~EOC)
- 001> IRB
- IRBPress Opti
- IRB
- EOC
- else
- assert_screen(<<~EOC)
- 001> IRB
- IRBPress Alt+
- IRB
- EOC
- end
- close
- end
-
- def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left
- rdoc_dir = File.join(@tmpdir, 'rdoc')
- system("bundle exec rdoc lib -r -o #{rdoc_dir}")
- write_irbrc <<~LINES
- IRB.conf[:EXTRA_DOC_DIRS] = ['#{rdoc_dir}']
- IRB.conf[:PROMPT][:MY_PROMPT] = {
- :PROMPT_I => "%03n> ",
- :PROMPT_S => "%03n> ",
- :PROMPT_C => "%03n> "
- }
- IRB.conf[:PROMPT_MODE] = :MY_PROMPT
- LINES
- start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /001>/)
- write("IR")
- write("\C-i")
- assert_screen(<<~EOC)
- 001> IRB
- PressIRB
- IRB
- EOC
- close
- end
-
- def test_assignment_expression_truncate
- start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- # Assignment expression code that turns into non-assignment expression after evaluation
- code = "a /'/i if false; a=1; x=1000.times.to_a#'.size"
- write(code + "\n")
- assert_screen(<<~EOC)
- irb(main):001> #{code}
- =>
- [0,
- ...
- irb(main):002>
- EOC
- close
- end
-
- def test_ctrl_c_is_handled
- start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- # Assignment expression code that turns into non-assignment expression after evaluation
- write("\C-c")
- assert_screen(<<~EOC)
- irb(main):001>
- ^C
- irb(main):001>
- EOC
- close
- end
-
- def test_show_cmds_with_pager_can_quit_with_ctrl_c
- start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write("help\n")
- write("G") # move to the end of the screen
- write("\C-c") # quit pager
- write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
-
- # IRB should resume
- assert_screen(/foobar/)
- # IRB::Abort should be rescued
- assert_screen(/\A(?!IRB::Abort)/)
- close
- end
-
- def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_total_length
- write_irbrc <<~'LINES'
- require "irb/pager"
- LINES
- start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write("IRB::Pager.page_content('a' * (80 * 8))\n")
- write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
-
- assert_screen(/a{80}/)
- # because pager is invoked, foobar will not be evaluated
- assert_screen(/\A(?!foobar)/)
- close
- end
-
- def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_screen_height
- write_irbrc <<~'LINES'
- require "irb/pager"
- LINES
- start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write("IRB::Pager.page_content('a\n' * 8)\n")
- write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
-
- assert_screen(/(a\n){8}/)
- # because pager is invoked, foobar will not be evaluated
- assert_screen(/\A(?!foobar)/)
- close
- end
-
- def test_pager_page_content_doesnt_page_output_when_it_fits_in_the_screen
- write_irbrc <<~'LINES'
- require "irb/pager"
- LINES
- start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write("IRB::Pager.page_content('a' * (80 * 7))\n")
- write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
-
- assert_screen(/a{80}/)
- # because pager is not invoked, foobar will be evaluated
- assert_screen(/foobar/)
- close
- end
-
- def test_long_evaluation_output_is_paged
- write_irbrc <<~'LINES'
- require "irb/pager"
- LINES
- start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write("'a' * 80 * 11\n")
- write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
-
- assert_screen(/(a{80}\n){8}/)
- # because pager is invoked, foobar will not be evaluated
- assert_screen(/\A(?!foobar)/)
- close
- end
-
- def test_long_evaluation_output_is_preserved_after_paging
- write_irbrc <<~'LINES'
- require "irb/pager"
- LINES
- start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/)
- write("'a' * 80 * 11\n")
- write("q") # quit pager
- write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
-
- # confirm pager has exited
- assert_screen(/foobar/)
- # confirm output is preserved
- assert_screen(/(a{80}\n){6}/)
- close
- end
-
- def test_debug_integration_hints_debugger_commands
- write_irbrc <<~'LINES'
- IRB.conf[:USE_COLORIZE] = false
- LINES
- script = Tempfile.create(["debug", ".rb"])
- script.write <<~RUBY
- binding.irb
- RUBY
- script.close
- start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: /irb\(main\)/)
- write("debug\n")
- write("pp 1\n")
- write("pp 1")
-
- # submitted input shouldn't contain hint
- assert_screen(/irb:rdbg\(main\):002> pp 1\n/)
- # unsubmitted input should contain hint
- assert_screen(/irb:rdbg\(main\):003> pp 1 # debug command\n/)
- close
- ensure
- File.unlink(script) if script
- end
-
- def test_debug_integration_doesnt_hint_non_debugger_commands
- write_irbrc <<~'LINES'
- IRB.conf[:USE_COLORIZE] = false
- LINES
- script = Tempfile.create(["debug", ".rb"])
- script.write <<~RUBY
- binding.irb
- RUBY
- script.close
- start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: /irb\(main\)/)
- write("debug\n")
- write("foo")
- assert_screen(/irb:rdbg\(main\):002> foo\n/)
- close
- ensure
- File.unlink(script) if script
- end
-
- def test_debug_integration_doesnt_hint_debugger_commands_in_nomultiline_mode
- write_irbrc <<~'LINES'
- IRB.conf[:USE_SINGLELINE] = true
- LINES
- script = Tempfile.create(["debug", ".rb"])
- script.write <<~RUBY
- puts 'start IRB'
- binding.irb
- RUBY
- script.close
- start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB')
- write("debug\n")
- write("pp 1")
- # submitted input shouldn't contain hint
- assert_screen(/irb:rdbg\(main\):002> pp 1\n/)
- close
- ensure
- File.unlink(script) if script
- end
-
- private
-
- def write_irbrc(content)
- File.open(@irbrc_file, 'w') do |f|
- f.write content
- end
- end
-end
diff --git a/test/json/fixtures/pass15.json b/test/json/fixtures/fail15.json
index fc8376b605..fc8376b605 100644
--- a/test/json/fixtures/pass15.json
+++ b/test/json/fixtures/fail15.json
diff --git a/test/json/fixtures/pass16.json b/test/json/fixtures/fail16.json
index c43ae3c286..c43ae3c286 100644
--- a/test/json/fixtures/pass16.json
+++ b/test/json/fixtures/fail16.json
diff --git a/test/json/fixtures/pass17.json b/test/json/fixtures/fail17.json
index 62b9214aed..62b9214aed 100644
--- a/test/json/fixtures/pass17.json
+++ b/test/json/fixtures/fail17.json
diff --git a/test/json/fixtures/pass26.json b/test/json/fixtures/fail26.json
index 845d26a6a5..845d26a6a5 100644
--- a/test/json/fixtures/pass26.json
+++ b/test/json/fixtures/fail26.json
diff --git a/test/json/fixtures/pass1.json b/test/json/fixtures/pass1.json
index 7828fcc137..fa9058b136 100644
--- a/test/json/fixtures/pass1.json
+++ b/test/json/fixtures/pass1.json
@@ -12,7 +12,7 @@
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
- "": 23456789012E666,
+ "": 23456789012E66,
"zero": 0,
"one": 1,
"space": " ",
diff --git a/test/json/json_addition_test.rb b/test/json/json_addition_test.rb
index 1eb269c2f6..4d8d186873 100644
--- a/test/json/json_addition_test.rb
+++ b/test/json/json_addition_test.rb
@@ -44,10 +44,6 @@ class JSONAdditionTest < Test::Unit::TestCase
end
class B
- def self.json_creatable?
- false
- end
-
def to_json(*args)
{
'json_class' => self.class.name,
@@ -56,10 +52,6 @@ class JSONAdditionTest < Test::Unit::TestCase
end
class C
- def self.json_creatable?
- false
- end
-
def to_json(*args)
{
'json_class' => 'JSONAdditionTest::Nix',
@@ -69,7 +61,6 @@ class JSONAdditionTest < Test::Unit::TestCase
def test_extended_json
a = A.new(666)
- assert A.json_creatable?
json = generate(a)
a_again = parse(json, :create_additions => true)
assert_kind_of a.class, a_again
@@ -78,7 +69,7 @@ class JSONAdditionTest < Test::Unit::TestCase
def test_extended_json_default
a = A.new(666)
- assert A.json_creatable?
+ assert A.respond_to?(:json_create)
json = generate(a)
a_hash = parse(json)
assert_kind_of Hash, a_hash
@@ -86,7 +77,6 @@ class JSONAdditionTest < Test::Unit::TestCase
def test_extended_json_disabled
a = A.new(666)
- assert A.json_creatable?
json = generate(a)
a_again = parse(json, :create_additions => true)
assert_kind_of a.class, a_again
@@ -101,14 +91,12 @@ class JSONAdditionTest < Test::Unit::TestCase
def test_extended_json_fail1
b = B.new
- assert !B.json_creatable?
json = generate(b)
assert_equal({ "json_class"=>"JSONAdditionTest::B" }, parse(json))
end
def test_extended_json_fail2
c = C.new
- assert !C.json_creatable?
json = generate(c)
assert_raise(ArgumentError, NameError) { parse(json, :create_additions => true) }
end
diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb
new file mode 100755
index 0000000000..a8477dd7be
--- /dev/null
+++ b/test/json/json_coder_test.rb
@@ -0,0 +1,154 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require_relative 'test_helper'
+
+class JSONCoderTest < Test::Unit::TestCase
+ def test_json_coder_with_proc
+ coder = JSON::Coder.new do |object|
+ "[Object object]"
+ end
+ assert_equal %(["[Object object]"]), coder.dump([Object.new])
+ end
+
+ def test_json_coder_with_proc_with_unsupported_value
+ coder = JSON::Coder.new do |object, is_key|
+ assert_equal false, is_key
+ Object.new
+ end
+ assert_raise(JSON::GeneratorError) { coder.dump([Object.new]) }
+ end
+
+ def test_json_coder_with_proc_returning_symbol
+ coder = JSON::Coder.new { _1 }
+ assert_equal %({"sym":"sym"}), coder.dump({ sym: :sym })
+ end
+
+ def test_json_coder_hash_key
+ obj = Object.new
+ coder = JSON::Coder.new do |obj, is_key|
+ assert_equal true, is_key
+ obj.to_s
+ end
+ assert_equal %({#{obj.to_s.inspect}:1}), coder.dump({ obj => 1 })
+
+ coder = JSON::Coder.new { 42 }
+ error = assert_raise JSON::GeneratorError do
+ coder.dump({ obj => 1 })
+ end
+ assert_equal "Integer not allowed as object key in JSON", error.message
+ end
+
+ def test_json_coder_options
+ coder = JSON::Coder.new(array_nl: "\n") do |object|
+ 42
+ end
+
+ assert_equal "[\n42\n]", coder.dump([Object.new])
+ end
+
+ def test_json_coder_load
+ coder = JSON::Coder.new
+ assert_equal [1,2,3], coder.load("[1,2,3]")
+ end
+
+ def test_json_coder_load_options
+ coder = JSON::Coder.new(symbolize_names: true)
+ assert_equal({a: 1}, coder.load('{"a":1}'))
+ end
+
+ def test_json_coder_dump_NaN_or_Infinity
+ coder = JSON::Coder.new { |o| o.inspect }
+ assert_equal "NaN", coder.load(coder.dump(Float::NAN))
+ assert_equal "Infinity", coder.load(coder.dump(Float::INFINITY))
+ assert_equal "-Infinity", coder.load(coder.dump(-Float::INFINITY))
+ end
+
+ def test_json_coder_dump_NaN_or_Infinity_loop
+ coder = JSON::Coder.new { |o| o.itself }
+ error = assert_raise JSON::GeneratorError do
+ coder.dump(Float::NAN)
+ end
+ assert_include error.message, "NaN not allowed in JSON"
+ end
+
+ def test_json_coder_string_invalid_encoding
+ calls = 0
+ coder = JSON::Coder.new do |object, is_key|
+ calls += 1
+ object
+ end
+
+ error = assert_raise JSON::GeneratorError do
+ coder.dump("\xFF")
+ end
+ assert_equal "source sequence is illegal/malformed utf-8", error.message
+ assert_equal 1, calls
+
+ error = assert_raise JSON::GeneratorError do
+ coder.dump({ "\xFF" => 1 })
+ end
+ assert_equal "source sequence is illegal/malformed utf-8", error.message
+ assert_equal 2, calls
+
+ calls = 0
+ coder = JSON::Coder.new do |object, is_key|
+ calls += 1
+ object.dup
+ end
+
+ error = assert_raise JSON::GeneratorError do
+ coder.dump("\xFF")
+ end
+ assert_equal "source sequence is illegal/malformed utf-8", error.message
+ assert_equal 1, calls
+
+ error = assert_raise JSON::GeneratorError do
+ coder.dump({ "\xFF" => 1 })
+ end
+ assert_equal "source sequence is illegal/malformed utf-8", error.message
+ assert_equal 2, calls
+
+ calls = 0
+ coder = JSON::Coder.new do |object, is_key|
+ calls += 1
+ object.bytes
+ end
+
+ assert_equal "[255]", coder.dump("\xFF")
+ assert_equal 1, calls
+
+ error = assert_raise JSON::GeneratorError do
+ coder.dump({ "\xFF" => 1 })
+ end
+ assert_equal "Array not allowed as object key in JSON", error.message
+ assert_equal 2, calls
+
+ calls = 0
+ coder = JSON::Coder.new do |object, is_key|
+ calls += 1
+ [object].pack("m")
+ end
+
+ assert_equal '"/w==\\n"', coder.dump("\xFF")
+ assert_equal 1, calls
+
+ assert_equal '{"/w==\\n":1}', coder.dump({ "\xFF" => 1 })
+ assert_equal 2, calls
+ end
+
+ def test_depth
+ coder = JSON::Coder.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ", depth: 1)
+ assert_equal %({\n "foo": 42\n }), coder.dump(foo: 42)
+ end
+
+ def test_nesting_recovery
+ coder = JSON::Coder.new
+ ary = []
+ ary << ary
+ assert_raise JSON::NestingError do
+ coder.dump(ary)
+ end
+ assert_equal '{"a":1}', coder.dump({ a: 1 })
+ end
+end
diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb
index 1f157da026..37568b556e 100644
--- a/test/json/json_common_interface_test.rb
+++ b/test/json/json_common_interface_test.rb
@@ -42,12 +42,6 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}'
end
- def test_index
- assert_equal @json, JSON[@hash]
- assert_equal @json, JSON[@hash_with_method_missing]
- assert_equal @hash, JSON[@json]
- end
-
def test_parser
assert_match(/::Parser\z/, JSON.parser.name)
end
@@ -68,11 +62,6 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
JSON.create_id = 'json_class'
end
- def test_deep_const_get
- assert_raise(ArgumentError) { JSON.deep_const_get('Nix::Da') }
- assert_equal File::SEPARATOR, JSON.deep_const_get('File::SEPARATOR')
- end
-
def test_parse
assert_equal [ 1, 2, 3, ], JSON.parse('[ 1, 2, 3 ]')
end
@@ -91,6 +80,30 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
def test_pretty_generate
assert_equal "[\n 1,\n 2,\n 3\n]", JSON.pretty_generate([ 1, 2, 3 ])
+ assert_equal <<~JSON.strip, JSON.pretty_generate({ a: { b: "f"}, c: "d"})
+ {
+ "a": {
+ "b": "f"
+ },
+ "c": "d"
+ }
+ JSON
+
+ # Cause the state to be spilled on the heap.
+ o = Object.new
+ def o.to_s
+ "Object"
+ end
+ actual = JSON.pretty_generate({ a: { b: o}, c: "d", e: "f"})
+ assert_equal <<~JSON.strip, actual
+ {
+ "a": {
+ "b": "Object"
+ },
+ "c": "d",
+ "e": "f"
+ }
+ JSON
end
def test_load
@@ -110,7 +123,7 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
def test_load_with_proc
visited = []
- JSON.load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o) })
+ JSON.load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o); o })
expected = [
'"foo"',
@@ -130,12 +143,97 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
def test_load_with_options
json = '{ "foo": NaN }'
assert JSON.load(json, nil, :allow_nan => true)['foo'].nan?
+ assert JSON.load(json, :allow_nan => true)['foo'].nan?
end
def test_load_null
assert_equal nil, JSON.load(nil, nil, :allow_blank => true)
assert_raise(TypeError) { JSON.load(nil, nil, :allow_blank => false) }
assert_raise(JSON::ParserError) { JSON.load('', nil, :allow_blank => false) }
+ assert_raise(TypeError) { JSON.load([], nil, :allow_blank => true) }
+ assert_raise(TypeError) { JSON.load({}, nil, :allow_blank => true) }
+ end
+
+ def test_unsafe_load
+ string_able_klass = Class.new do
+ def initialize(str)
+ @str = str
+ end
+
+ def to_str
+ @str
+ end
+ end
+
+ io_able_klass = Class.new do
+ def initialize(str)
+ @str = str
+ end
+
+ def to_io
+ StringIO.new(@str)
+ end
+ end
+
+ assert_equal @hash, JSON.unsafe_load(@json)
+ tempfile = Tempfile.open('@json')
+ tempfile.write @json
+ tempfile.rewind
+ assert_equal @hash, JSON.unsafe_load(tempfile)
+ stringio = StringIO.new(@json)
+ stringio.rewind
+ assert_equal @hash, JSON.unsafe_load(stringio)
+ string_able = string_able_klass.new(@json)
+ assert_equal @hash, JSON.unsafe_load(string_able)
+ io_able = io_able_klass.new(@json)
+ assert_equal @hash, JSON.unsafe_load(io_able)
+ assert_equal nil, JSON.unsafe_load(nil)
+ assert_equal nil, JSON.unsafe_load('')
+ ensure
+ tempfile.close!
+ end
+
+ def test_unsafe_load_with_proc
+ visited = []
+ JSON.unsafe_load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o); o })
+
+ expected = [
+ '"foo"',
+ '1',
+ '2',
+ '3',
+ '[1,2,3]',
+ '"bar"',
+ '"baz"',
+ '"plop"',
+ '{"baz":"plop"}',
+ '{"foo":[1,2,3],"bar":{"baz":"plop"}}',
+ ]
+ assert_equal expected, visited
+ end
+
+ def test_unsafe_load_default_options
+ too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'
+ assert JSON.unsafe_load(too_deep, nil).is_a?(Array)
+ nan_json = '{ "foo": NaN }'
+ assert JSON.unsafe_load(nan_json, nil)['foo'].nan?
+ assert_equal nil, JSON.unsafe_load(nil, nil)
+ t = Time.new(2025, 9, 3, 14, 50, 0)
+ assert_equal t.to_s, JSON.unsafe_load(JSON(t)).to_s
+ end
+
+ def test_unsafe_load_with_options
+ nan_json = '{ "foo": NaN }'
+ assert_raise(JSON::ParserError) { JSON.unsafe_load(nan_json, nil, :allow_nan => false)['foo'].nan? }
+ # make sure it still uses the defaults when something is provided
+ assert JSON.unsafe_load(nan_json, nil, :allow_blank => true)['foo'].nan?
+ assert JSON.unsafe_load(nan_json, :allow_nan => true)['foo'].nan?
+ end
+
+ def test_unsafe_load_null
+ assert_equal nil, JSON.unsafe_load(nil, nil, :allow_blank => true)
+ assert_raise(TypeError) { JSON.unsafe_load(nil, nil, :allow_blank => false) }
+ assert_raise(JSON::ParserError) { JSON.unsafe_load('', nil, :allow_blank => false) }
end
def test_dump
@@ -174,9 +272,9 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
end
def test_dump_should_modify_defaults
- max_nesting = JSON.dump_default_options[:max_nesting]
+ max_nesting = JSON._dump_default_options[:max_nesting]
dump([], StringIO.new, 10)
- assert_equal max_nesting, JSON.dump_default_options[:max_nesting]
+ assert_equal max_nesting, JSON._dump_default_options[:max_nesting]
end
def test_JSON
@@ -185,6 +283,12 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
assert_equal @hash, JSON(@json)
end
+ def test_index
+ assert_equal @json, JSON[@hash]
+ assert_equal @json, JSON[@hash_with_method_missing]
+ assert_equal @hash, JSON[@json]
+ end
+
def test_load_file
test_load_shared(:load_file)
end
@@ -211,6 +315,12 @@ class JSONCommonInterfaceTest < Test::Unit::TestCase
end
end
+ def test_deprecated_dump_default_options
+ assert_deprecated_warning(/dump_default_options/) do
+ JSON.dump_default_options
+ end
+ end
+
private
def with_external_encoding(encoding)
diff --git a/test/json/json_encoding_test.rb b/test/json/json_encoding_test.rb
index afffd8976a..7ac06b2a7b 100644
--- a/test/json/json_encoding_test.rb
+++ b/test/json/json_encoding_test.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require_relative 'test_helper'
class JSONEncodingTest < Test::Unit::TestCase
@@ -30,6 +31,18 @@ class JSONEncodingTest < Test::Unit::TestCase
assert_equal @generated, JSON.generate(@utf_16_data, ascii_only: true)
end
+ def test_generate_shared_string
+ # Ref: https://github.com/ruby/json/issues/859
+ s = "01234567890"
+ assert_equal '"234567890"', JSON.dump(s[2..-1])
+ s = '01234567890123456789"a"b"c"d"e"f"g"h'
+ assert_equal '"\"a\"b\"c\"d\"e\"f\"g\""', JSON.dump(s[20, 15])
+ s = "0123456789001234567890012345678900123456789001234567890"
+ assert_equal '"23456789001234567890012345678900123456789001234567890"', JSON.dump(s[2..-1])
+ s = "0123456789001234567890012345678900123456789001234567890"
+ assert_equal '"567890012345678900123456789001234567890012345678"', JSON.dump(s[5..-3])
+ end
+
def test_unicode
assert_equal '""', ''.to_json
assert_equal '"\\b"', "\b".to_json
@@ -37,7 +50,7 @@ class JSONEncodingTest < Test::Unit::TestCase
assert_equal '"\u001f"', 0x1f.chr.to_json
assert_equal '" "', ' '.to_json
assert_equal "\"#{0x7f.chr}\"", 0x7f.chr.to_json
- utf8 = [ "© ≠ €! \01" ]
+ utf8 = ["© ≠ €! \01"]
json = '["© ≠ €! \u0001"]'
assert_equal json, utf8.to_json(ascii_only: false)
assert_equal utf8, parse(json)
@@ -78,10 +91,10 @@ class JSONEncodingTest < Test::Unit::TestCase
json = '"\u%04x"' % i
i = i.chr
assert_equal i, parse(json)[0]
- if i == ?\b
+ if i == "\b"
generated = generate(i)
- assert '"\b"' == generated || '"\10"' == generated
- elsif [?\n, ?\r, ?\t, ?\f].include?(i)
+ assert ['"\b"', '"\10"'].include?(generated)
+ elsif ["\n", "\r", "\t", "\f"].include?(i)
assert_equal i.dump, generate(i)
elsif i.chr < 0x20.chr
assert_equal json, generate(i)
@@ -92,4 +105,171 @@ class JSONEncodingTest < Test::Unit::TestCase
end
assert_equal "\302\200", parse('"\u0080"')
end
+
+ def test_deeply_nested_structures
+ # Test for deeply nested arrays
+ nesting_level = 100
+ deeply_nested = []
+ current = deeply_nested
+
+ (nesting_level - 1).times do
+ current << []
+ current = current[0]
+ end
+
+ json = generate(deeply_nested)
+ assert_equal deeply_nested, parse(json)
+
+ # Test for deeply nested objects/hashes
+ deeply_nested_hash = {}
+ current_hash = deeply_nested_hash
+
+ (nesting_level - 1).times do |i|
+ current_hash["key#{i}"] = {}
+ current_hash = current_hash["key#{i}"]
+ end
+
+ json = generate(deeply_nested_hash)
+ assert_equal deeply_nested_hash, parse(json)
+ end
+
+ def test_very_large_json_strings
+ # Create a large array with repeated elements
+ large_array = Array.new(10_000) { |i| "item#{i}" }
+
+ json = generate(large_array)
+ parsed = parse(json)
+
+ assert_equal large_array.size, parsed.size
+ assert_equal large_array.first, parsed.first
+ assert_equal large_array.last, parsed.last
+
+ # Create a large hash
+ large_hash = {}
+ 10_000.times { |i| large_hash["key#{i}"] = "value#{i}" }
+
+ json = generate(large_hash)
+ parsed = parse(json)
+
+ assert_equal large_hash.size, parsed.size
+ assert_equal large_hash["key0"], parsed["key0"]
+ assert_equal large_hash["key9999"], parsed["key9999"]
+ end
+
+ def test_invalid_utf8_sequences
+ invalid_utf8 = "\xFF\xFF"
+ error = assert_raise(JSON::GeneratorError) do
+ generate(invalid_utf8)
+ end
+ assert_match(%r{source sequence is illegal/malformed utf-8}, error.message)
+ end
+
+ def test_surrogate_pair_handling
+ # Test valid surrogate pairs
+ assert_equal "\u{10000}", parse('"\ud800\udc00"')
+ assert_equal "\u{10FFFF}", parse('"\udbff\udfff"')
+
+ # The existing test already checks for orphaned high surrogate
+ assert_raise(JSON::ParserError) { parse('"\ud800"') }
+
+ # Test generating surrogate pairs
+ utf8_string = "\u{10437}"
+ generated = generate(utf8_string, ascii_only: true)
+ assert_match(/\\ud801\\udc37/, generated)
+ end
+
+ def test_json_escaping_edge_cases
+ # Test escaping forward slashes
+ assert_equal "/", parse('"\/"')
+
+ # Test escaping backslashes
+ assert_equal "\\", parse('"\\\\"')
+
+ # Test escaping quotes
+ assert_equal '"', parse('"\\""')
+
+ # Multiple escapes in sequence - different JSON parsers might handle escaped forward slashes differently
+ # Some parsers preserve the escaping, others don't
+ escaped_result = parse('"\\\\\\"\\/"')
+ assert_match(/\\"/, escaped_result)
+ assert_match(%r{/}, escaped_result)
+
+ # Generate string with all special characters
+ special_chars = "\b\f\n\r\t\"\\"
+ escaped_json = generate(special_chars)
+ assert_equal special_chars, parse(escaped_json)
+ end
+
+ def test_empty_objects_and_arrays
+ # Test empty objects with different encodings
+ assert_equal({}, parse('{}'))
+ assert_equal({}, parse('{}'.encode(Encoding::UTF_16BE)))
+ assert_equal({}, parse('{}'.encode(Encoding::UTF_16LE)))
+ assert_equal({}, parse('{}'.encode(Encoding::UTF_32BE)))
+ assert_equal({}, parse('{}'.encode(Encoding::UTF_32LE)))
+
+ # Test empty arrays with different encodings
+ assert_equal([], parse('[]'))
+ assert_equal([], parse('[]'.encode(Encoding::UTF_16BE)))
+ assert_equal([], parse('[]'.encode(Encoding::UTF_16LE)))
+ assert_equal([], parse('[]'.encode(Encoding::UTF_32BE)))
+ assert_equal([], parse('[]'.encode(Encoding::UTF_32LE)))
+
+ # Test generating empty objects and arrays
+ assert_equal '{}', generate({})
+ assert_equal '[]', generate([])
+ end
+
+ def test_null_character_handling
+ # Test parsing null character
+ assert_equal "\u0000", parse('"\u0000"')
+
+ # Test generating null character
+ string_with_null = "\u0000"
+ generated = generate(string_with_null)
+ assert_equal '"\u0000"', generated
+
+ # Test null characters in middle of string
+ mixed_string = "before\u0000after"
+ generated = generate(mixed_string)
+ assert_equal mixed_string, parse(generated)
+ end
+
+ def test_whitespace_handling
+ # Test parsing with various whitespace patterns
+ assert_equal({}, parse(' { } '))
+ assert_equal({}, parse("{\r\n}"))
+ assert_equal([], parse(" [ \n ] "))
+ assert_equal(["a", "b"], parse(" [ \n\"a\",\r\n \"b\"\n ] "))
+ assert_equal({ "a" => "b" }, parse(" { \n\"a\" \r\n: \t\"b\"\n } "))
+
+ # Test with excessive whitespace
+ excessive_whitespace = " \n\r\t" * 10 + "{}" + " \n\r\t" * 10
+ assert_equal({}, parse(excessive_whitespace))
+
+ # Mixed whitespace in keys and values
+ mixed_json = '{"a \n b":"c \r\n d"}'
+ assert_equal({ "a \n b" => "c \r\n d" }, parse(mixed_json))
+ end
+
+ def test_control_character_handling
+ # Test all control characters (U+0000 to U+001F)
+ (0..0x1F).each do |i|
+ # Skip already tested ones
+ next if [0x08, 0x0A, 0x0D, 0x0C, 0x09].include?(i)
+
+ control_char = i.chr('UTF-8')
+ escaped_json = '"' + "\\u%04x" % i + '"'
+ assert_equal control_char, parse(escaped_json)
+
+ # Check that the character is properly escaped when generating
+ assert_match(/\\u00[0-1][0-9a-f]/, generate(control_char))
+ end
+
+ # Test string with multiple control characters
+ control_str = "\u0001\u0002\u0003\u0004"
+ generated = generate(control_str)
+ assert_equal control_str, parse(generated)
+ assert_match(/\\u0001\\u0002\\u0003\\u0004/, generated)
+ end
end
diff --git a/test/json/json_ext_parser_test.rb b/test/json/json_ext_parser_test.rb
index 8aa626257e..e610f642f1 100644
--- a/test/json/json_ext_parser_test.rb
+++ b/test/json/json_ext_parser_test.rb
@@ -14,16 +14,35 @@ class JSONExtParserTest < Test::Unit::TestCase
end
def test_error_messages
- ex = assert_raise(ParserError) { parse('Infinity') }
- assert_equal "unexpected token at 'Infinity'", ex.message
+ ex = assert_raise(ParserError) { parse('Infinity something') }
+ unless RUBY_PLATFORM =~ /java/
+ assert_equal "unexpected token 'Infinity' at line 1 column 1", ex.message
+ end
+ ex = assert_raise(ParserError) { parse('foo bar') }
unless RUBY_PLATFORM =~ /java/
- ex = assert_raise(ParserError) { parse('-Infinity') }
- assert_equal "unexpected token at '-Infinity'", ex.message
+ assert_equal "unexpected token 'foo' at line 1 column 1", ex.message
end
- ex = assert_raise(ParserError) { parse('NaN') }
- assert_equal "unexpected token at 'NaN'", ex.message
+ ex = assert_raise(ParserError) { parse('-Infinity something') }
+ unless RUBY_PLATFORM =~ /java/
+ assert_equal "unexpected token '-Infinity' at line 1 column 1", ex.message
+ end
+
+ ex = assert_raise(ParserError) { parse('NaN something') }
+ unless RUBY_PLATFORM =~ /java/
+ assert_equal "unexpected token 'NaN' at line 1 column 1", ex.message
+ end
+
+ ex = assert_raise(ParserError) { parse(' ') }
+ unless RUBY_PLATFORM =~ /java/
+ assert_equal "unexpected end of input at line 1 column 4", ex.message
+ end
+
+ ex = assert_raise(ParserError) { parse('{ ') }
+ unless RUBY_PLATFORM =~ /java/
+ assert_equal "expected object key, got EOF at line 1 column 5", ex.message
+ end
end
if GC.respond_to?(:stress=)
diff --git a/test/json/json_fixtures_test.rb b/test/json/json_fixtures_test.rb
index c153ebef7c..c0d1037939 100644
--- a/test/json/json_fixtures_test.rb
+++ b/test/json/json_fixtures_test.rb
@@ -10,6 +10,8 @@ class JSONFixturesTest < Test::Unit::TestCase
source = File.read(f)
define_method("test_#{name}") do
assert JSON.parse(source), "Did not pass for fixture '#{File.basename(f)}': #{source.inspect}"
+ rescue JSON::ParserError
+ raise "#{File.basename(f)} parsing failure"
end
end
diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb
index 8dd3913d62..753ee0fbdf 100755
--- a/test/json/json_generator_test.rb
+++ b/test/json/json_generator_test.rb
@@ -39,14 +39,6 @@ class JSONGeneratorTest < Test::Unit::TestCase
JSON
end
- def silence
- v = $VERBOSE
- $VERBOSE = nil
- yield
- ensure
- $VERBOSE = v
- end
-
def test_generate
json = generate(@hash)
assert_equal(parse(@json2), parse(json))
@@ -86,6 +78,70 @@ class JSONGeneratorTest < Test::Unit::TestCase
assert_equal '42', dump(42, strict: true)
assert_equal 'true', dump(true, strict: true)
+
+ assert_equal '"hello"', dump(:hello, strict: true)
+ assert_equal '"hello"', :hello.to_json(strict: true)
+ assert_equal '"World"', "World".to_json(strict: true)
+ assert_equal '["hello"]', dump([:hello], strict: true)
+ assert_equal '{"hello":"world"}', dump({ hello: :world }, strict: true)
+ end
+
+ def test_not_frozen
+ [
+ [[], '[]'],
+ [{}, '{}'],
+ ["string", '"string"'],
+ [:sym, '"sym"'],
+ [1, '1'],
+ [1.0, '1.0'],
+ [true, 'true'],
+ [false, 'false'],
+ [nil, 'null'],
+ ].each do |(obj, exp)|
+ dumped = dump(obj, strict: true)
+ assert_equal exp, dumped
+ refute_predicate dumped, :frozen?
+ end
+ end
+
+ def test_state_depth_to_json
+ depth = Object.new
+ def depth.to_json(state)
+ JSON::State.from_state(state).depth.to_s
+ end
+
+ assert_equal "0", JSON.generate(depth)
+ assert_equal "[1]", JSON.generate([depth])
+ assert_equal %({"depth":1}), JSON.generate(depth: depth)
+ assert_equal "[[2]]", JSON.generate([[depth]])
+ assert_equal %([{"depth":2}]), JSON.generate([{depth: depth}])
+
+ state = JSON::State.new
+ assert_equal "0", state.generate(depth)
+ assert_equal "[1]", state.generate([depth])
+ assert_equal %({"depth":1}), state.generate(depth: depth)
+ assert_equal "[[2]]", state.generate([[depth]])
+ assert_equal %([{"depth":2}]), state.generate([{depth: depth}])
+ end
+
+ def test_state_depth_to_json_recursive
+ recur = Object.new
+ def recur.to_json(state = nil, *)
+ state = JSON::State.from_state(state)
+ if state.depth < 3
+ state.generate([state.depth, self])
+ else
+ state.generate([state.depth])
+ end
+ end
+
+ assert_raise(NestingError) { JSON.generate(recur, max_nesting: 3) }
+ assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4)
+
+ state = JSON::State.new(max_nesting: 3)
+ assert_raise(NestingError) { state.generate(recur) }
+ state.max_nesting = 4
+ assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4)
end
def test_generate_pretty
@@ -118,6 +174,22 @@ class JSONGeneratorTest < Test::Unit::TestCase
assert_equal '666', pretty_generate(666)
end
+ def test_generate_pretty_custom
+ state = State.new(:space_before => "<psb>", :space => "<ps>", :indent => "<pi>", :object_nl => "\n<po_nl>\n", :array_nl => "<pa_nl>")
+ json = pretty_generate({1=>{}, 2=>['a','b'], 3=>4}, state)
+ assert_equal(<<~'JSON'.chomp, json)
+ {
+ <po_nl>
+ <pi>"1"<psb>:<ps>{},
+ <po_nl>
+ <pi>"2"<psb>:<ps>[<pa_nl><pi><pi>"a",<pa_nl><pi><pi>"b"<pa_nl><pi>],
+ <po_nl>
+ <pi>"3"<psb>:<ps>4
+ <po_nl>
+ }
+ JSON
+ end
+
def test_generate_custom
state = State.new(:space_before => " ", :space => " ", :indent => "<i>", :object_nl => "\n", :array_nl => "<a_nl>")
json = generate({1=>{2=>3,4=>[5,6]}}, state)
@@ -132,15 +204,17 @@ class JSONGeneratorTest < Test::Unit::TestCase
end
def test_fast_generate
- json = fast_generate(@hash)
- assert_equal(parse(@json2), parse(json))
- parsed_json = parse(json)
- assert_equal(@hash, parsed_json)
- json = fast_generate({1=>2})
- assert_equal('{"1":2}', json)
- parsed_json = parse(json)
- assert_equal({"1"=>2}, parsed_json)
- assert_equal '666', fast_generate(666)
+ assert_deprecated_warning(/fast_generate/) do
+ json = fast_generate(@hash)
+ assert_equal(parse(@json2), parse(json))
+ parsed_json = parse(json)
+ assert_equal(@hash, parsed_json)
+ json = fast_generate({1=>2})
+ assert_equal('{"1":2}', json)
+ parsed_json = parse(json)
+ assert_equal({"1"=>2}, parsed_json)
+ assert_equal '666', fast_generate(666)
+ end
end
def test_own_state
@@ -161,7 +235,9 @@ class JSONGeneratorTest < Test::Unit::TestCase
assert_equal('{"1":2}', json)
s = JSON.state.new
assert s.check_circular?
- assert s[:check_circular?]
+ assert_deprecated_warning(/JSON::State/) do
+ assert s[:check_circular?]
+ end
h = { 1=>2 }
h[3] = h
assert_raise(JSON::NestingError) { generate(h) }
@@ -171,7 +247,9 @@ class JSONGeneratorTest < Test::Unit::TestCase
a << a
assert_raise(JSON::NestingError) { generate(a, s) }
assert s.check_circular?
- assert s[:check_circular?]
+ assert_deprecated_warning(/JSON::State/) do
+ assert s[:check_circular?]
+ end
end
def test_falsy_state
@@ -195,29 +273,12 @@ class JSONGeneratorTest < Test::Unit::TestCase
)
end
- def test_pretty_state
- state = JSON.create_pretty_state
- assert_equal({
- :allow_nan => false,
- :array_nl => "\n",
- :ascii_only => false,
- :buffer_initial_length => 1024,
- :depth => 0,
- :script_safe => false,
- :strict => false,
- :indent => " ",
- :max_nesting => 100,
- :object_nl => "\n",
- :space => " ",
- :space_before => "",
- }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
- end
-
- def test_safe_state
+ def test_state_defaults
state = JSON::State.new
assert_equal({
:allow_nan => false,
:array_nl => "",
+ :as_json => false,
:ascii_only => false,
:buffer_initial_length => 1024,
:depth => 0,
@@ -229,20 +290,20 @@ class JSONGeneratorTest < Test::Unit::TestCase
:space => "",
:space_before => "",
}.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
- end
- def test_fast_state
- state = JSON.create_fast_state
+ state = JSON::State.new(allow_duplicate_key: true)
assert_equal({
+ :allow_duplicate_key => true,
:allow_nan => false,
:array_nl => "",
+ :as_json => false,
:ascii_only => false,
:buffer_initial_length => 1024,
:depth => 0,
:script_safe => false,
:strict => false,
:indent => "",
- :max_nesting => 0,
+ :max_nesting => 100,
:object_nl => "",
:space => "",
:space_before => "",
@@ -250,34 +311,122 @@ class JSONGeneratorTest < Test::Unit::TestCase
end
def test_allow_nan
- error = assert_raise(GeneratorError) { generate([JSON::NaN]) }
- assert_same JSON::NaN, error.invalid_object
- assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true)
- assert_raise(GeneratorError) { fast_generate([JSON::NaN]) }
- assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) }
- assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true)
- error = assert_raise(GeneratorError) { generate([JSON::Infinity]) }
- assert_same JSON::Infinity, error.invalid_object
- assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true)
- assert_raise(GeneratorError) { fast_generate([JSON::Infinity]) }
- assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) }
- assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true)
- error = assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) }
- assert_same JSON::MinusInfinity, error.invalid_object
- assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true)
- assert_raise(GeneratorError) { fast_generate([JSON::MinusInfinity]) }
- assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) }
- assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], :allow_nan => true)
+ assert_deprecated_warning(/fast_generate/) do
+ error = assert_raise(GeneratorError) { generate([JSON::NaN]) }
+ assert_same JSON::NaN, error.invalid_object
+ assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true)
+ assert_raise(GeneratorError) { fast_generate([JSON::NaN]) }
+ assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) }
+ assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true)
+ error = assert_raise(GeneratorError) { generate([JSON::Infinity]) }
+ assert_same JSON::Infinity, error.invalid_object
+ assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true)
+ assert_raise(GeneratorError) { fast_generate([JSON::Infinity]) }
+ assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) }
+ assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true)
+ error = assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) }
+ assert_same JSON::MinusInfinity, error.invalid_object
+ assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true)
+ assert_raise(GeneratorError) { fast_generate([JSON::MinusInfinity]) }
+ assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) }
+ assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], :allow_nan => true)
+ end
+ end
+
+ # An object that changes state.depth when it receives to_json(state)
+ def bad_to_json
+ obj = Object.new
+ def obj.to_json(state)
+ state.depth += 1
+ "{#{state.object_nl}"\
+ "#{state.indent * state.depth}\"foo\":#{state.space}1#{state.object_nl}"\
+ "#{state.indent * (state.depth - 1)}}"
+ end
+ obj
+ end
+
+ def test_depth_restored_bad_to_json
+ state = JSON::State.new
+ state.generate(bad_to_json)
+ assert_equal 0, state.depth
+ end
+
+ def test_depth_restored_bad_to_json_in_Array
+ assert_equal <<~JSON.chomp, JSON.pretty_generate([bad_to_json] * 2)
+ [
+ {
+ "foo": 1
+ },
+ {
+ "foo": 1
+ }
+ ]
+ JSON
+ state = JSON::State.new
+ state.generate([bad_to_json])
+ assert_equal 0, state.depth
+ end
+
+ def test_depth_restored_bad_to_json_in_Hash
+ assert_equal <<~JSON.chomp, JSON.pretty_generate(a: bad_to_json, b: bad_to_json)
+ {
+ "a": {
+ "foo": 1
+ },
+ "b": {
+ "foo": 1
+ }
+ }
+ JSON
+ state = JSON::State.new
+ state.generate(a: bad_to_json)
+ assert_equal 0, state.depth
end
def test_depth
+ pretty = { object_nl: "\n", array_nl: "\n", space: " ", indent: " " }
+ state = JSON.state.new(**pretty)
+ assert_equal %({\n "foo": 42\n}), JSON.generate({ foo: 42 }, pretty)
+ assert_equal %({\n "foo": 42\n}), state.generate(foo: 42)
+ state.depth = 1
+ assert_equal %({\n "foo": 42\n }), JSON.generate({ foo: 42 }, pretty.merge(depth: 1))
+ assert_equal %({\n "foo": 42\n }), state.generate(foo: 42)
+ end
+
+ def test_depth_nesting_error
ary = []; ary << ary
assert_raise(JSON::NestingError) { generate(ary) }
assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) }
- s = JSON.state.new
- assert_equal 0, s.depth
+ end
+
+ def test_depth_nesting_error_to_json
+ ary = []; ary << ary
+ s = JSON.state.new(depth: 1)
assert_raise(JSON::NestingError) { ary.to_json(s) }
- assert_equal 100, s.depth
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_nesting_error_Hash_to_json
+ hash = {}; hash[:a] = hash
+ s = JSON.state.new(depth: 1)
+ assert_raise(JSON::NestingError) { hash.to_json(s) }
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_nesting_error_generate
+ ary = []; ary << ary
+ s = JSON.state.new(depth: 1)
+ assert_raise(JSON::NestingError) { s.generate(ary) }
+ assert_equal 1, s.depth
+ end
+
+ def test_depth_exception_calling_to_json
+ def (obj = Object.new).to_json(*)
+ raise
+ end
+ s = JSON.state.new(depth: 1).freeze
+ assert_raise(RuntimeError) { s.generate([{ hash: obj }]) }
+ assert_equal 1, s.depth
end
def test_buffer_initial_length
@@ -346,50 +495,65 @@ class JSONGeneratorTest < Test::Unit::TestCase
assert_equal '2', state.indent
end
- def test_broken_bignum # [ruby-core:38867]
- pid = fork do
- x = 1 << 64
- x.class.class_eval do
- def to_s
- end
+ def test_broken_bignum # [Bug #5173]
+ bignum = 1 << 64
+ bignum_to_s = bignum.to_s
+
+ original_to_s = bignum.class.instance_method(:to_s)
+ bignum.class.class_eval do
+ def to_s
+ nil
end
- begin
- JSON::Ext::Generator::State.new.generate(x)
- exit 1
- rescue TypeError
- exit 0
+ alias_method :to_s, :to_s
+ end
+ case RUBY_ENGINE
+ when "jruby"
+ assert_equal bignum_to_s, JSON.generate(bignum)
+ when "truffleruby"
+ assert_raise(NoMethodError) do
+ JSON.generate(bignum)
+ end
+ when "ruby"
+ assert_raise(TypeError) do
+ JSON.generate(bignum)
end
end
- _, status = Process.waitpid2(pid)
- assert status.success?
- rescue NotImplementedError
- # forking to avoid modifying core class of a parent process and
- # introducing race conditions of tests are run in parallel
+ ensure
+ bignum.class.define_method(:to_s, original_to_s) if original_to_s
end
def test_hash_likeness_set_symbol
- state = JSON.state.new
- assert_equal nil, state[:foo]
- assert_equal nil.class, state[:foo].class
- assert_equal nil, state['foo']
- state[:foo] = :bar
- assert_equal :bar, state[:foo]
- assert_equal :bar, state['foo']
- state_hash = state.to_hash
- assert_kind_of Hash, state_hash
- assert_equal :bar, state_hash[:foo]
+ assert_deprecated_warning(/JSON::State/) do
+ state = JSON.state.new
+ assert_equal nil, state[:foo]
+ assert_equal nil.class, state[:foo].class
+ assert_equal nil, state['foo']
+ state[:foo] = :bar
+ assert_equal :bar, state[:foo]
+ assert_equal :bar, state['foo']
+ state_hash = state.to_hash
+ assert_kind_of Hash, state_hash
+ assert_equal :bar, state_hash[:foo]
+ end
end
def test_hash_likeness_set_string
+ assert_deprecated_warning(/JSON::State/) do
+ state = JSON.state.new
+ assert_equal nil, state[:foo]
+ assert_equal nil, state['foo']
+ state['foo'] = :bar
+ assert_equal :bar, state[:foo]
+ assert_equal :bar, state['foo']
+ state_hash = state.to_hash
+ assert_kind_of Hash, state_hash
+ assert_equal :bar, state_hash[:foo]
+ end
+ end
+
+ def test_json_state_to_h_roundtrip
state = JSON.state.new
- assert_equal nil, state[:foo]
- assert_equal nil, state['foo']
- state['foo'] = :bar
- assert_equal :bar, state[:foo]
- assert_equal :bar, state['foo']
- state_hash = state.to_hash
- assert_kind_of Hash, state_hash
- assert_equal :bar, state_hash[:foo]
+ assert_equal state.to_h, JSON.state.new(state.to_h).to_h
end
def test_json_generate
@@ -398,10 +562,30 @@ class JSONGeneratorTest < Test::Unit::TestCase
end
end
+ def test_json_generate_error_detailed_message
+ error = assert_raise JSON::GeneratorError do
+ generate(["\xea"])
+ end
+
+ assert_not_nil(error.detailed_message)
+ end
+
def test_json_generate_unsupported_types
assert_raise JSON::GeneratorError do
generate(Object.new, strict: true)
end
+
+ assert_raise JSON::GeneratorError do
+ generate([Object.new], strict: true)
+ end
+
+ assert_raise JSON::GeneratorError do
+ generate({ "key" => Object.new }, strict: true)
+ end
+
+ assert_raise JSON::GeneratorError do
+ generate({ Object.new => "value" }, strict: true)
+ end
end
def test_nesting
@@ -417,6 +601,8 @@ class JSONGeneratorTest < Test::Unit::TestCase
assert_equal too_deep, ok
ok = generate too_deep_ary, :max_nesting => 0
assert_equal too_deep, ok
+
+ assert_raise(TypeError) { generate too_deep_ary, max_nesting: "garbage" }
end
def test_backslash
@@ -424,18 +610,34 @@ class JSONGeneratorTest < Test::Unit::TestCase
json = '["\\\\.(?i:gif|jpe?g|png)$"]'
assert_equal json, generate(data)
#
- data = [ '\\"' ]
- json = '["\\\\\""]'
+ data = [ '\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$' ]
+ json = '["\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$"]'
+ assert_equal json, generate(data)
+ #
+ data = [ '\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"' ]
+ json = '["\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\""]'
assert_equal json, generate(data)
#
data = [ '/' ]
json = '["/"]'
assert_equal json, generate(data)
#
+ data = [ '////////////////////////////////////////////////////////////////////////////////////' ]
+ json = '["////////////////////////////////////////////////////////////////////////////////////"]'
+ assert_equal json, generate(data)
+ #
data = [ '/' ]
json = '["\/"]'
assert_equal json, generate(data, :script_safe => true)
#
+ data = [ '///////////' ]
+ json = '["\/\/\/\/\/\/\/\/\/\/\/"]'
+ assert_equal json, generate(data, :script_safe => true)
+ #
+ data = [ '///////////////////////////////////////////////////////' ]
+ json = '["\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/"]'
+ assert_equal json, generate(data, :script_safe => true)
+ #
data = [ "\u2028\u2029" ]
json = '["\u2028\u2029"]'
assert_equal json, generate(data, :script_safe => true)
@@ -452,6 +654,38 @@ class JSONGeneratorTest < Test::Unit::TestCase
json = '["\""]'
assert_equal json, generate(data)
#
+ data = ['"""""""""""""""""""""""""']
+ json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]'
+ assert_equal json, generate(data)
+ #
+ data = '"""""'
+ json = '"\"\"\"\"\""'
+ assert_equal json, generate(data)
+ #
+ data = "abc\n"
+ json = '"abc\\n"'
+ assert_equal json, generate(data)
+ #
+ data = "\nabc"
+ json = '"\\nabc"'
+ assert_equal json, generate(data)
+ #
+ data = "\n"
+ json = '"\\n"'
+ assert_equal json, generate(data)
+ #
+ (0..16).each do |i|
+ data = ('a' * i) + "\n"
+ json = '"' + ('a' * i) + '\\n"'
+ assert_equal json, generate(data)
+ end
+ #
+ (0..16).each do |i|
+ data = "\n" + ('a' * i)
+ json = '"' + '\\n' + ('a' * i) + '"'
+ assert_equal json, generate(data)
+ end
+ #
data = ["'"]
json = '["\\\'"]'
assert_equal '["\'"]', generate(data)
@@ -459,6 +693,72 @@ class JSONGeneratorTest < Test::Unit::TestCase
data = ["倩", "瀨"]
json = '["倩","瀨"]'
assert_equal json, generate(data, script_safe: true)
+ #
+ data = '["This is a "test" of the emergency broadcast system."]'
+ json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\""
+ assert_equal json, generate(data)
+ #
+ data = '\tThis is a test of the emergency broadcast system.'
+ json = "\"\\\\tThis is a test of the emergency broadcast system.\""
+ assert_equal json, generate(data)
+ #
+ data = 'This\tis a test of the emergency broadcast system.'
+ json = "\"This\\\\tis a test of the emergency broadcast system.\""
+ assert_equal json, generate(data)
+ #
+ data = 'This is\ta test of the emergency broadcast system.'
+ json = "\"This is\\\\ta test of the emergency broadcast system.\""
+ assert_equal json, generate(data)
+ #
+ data = 'This is a test of the emergency broadcast\tsystem.'
+ json = "\"This is a test of the emergency broadcast\\\\tsystem.\""
+ assert_equal json, generate(data)
+ #
+ data = 'This is a test of the emergency broadcast\tsystem.\n'
+ json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\""
+ assert_equal json, generate(data)
+ data = '"' * 15
+ json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\""
+ assert_equal json, generate(data)
+ data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a"
+ json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\""
+ assert_equal json, generate(data)
+ data = "\u0001\u0001\u0001\u0001"
+ json = "\"\\u0001\\u0001\\u0001\\u0001\""
+ assert_equal json, generate(data)
+ data = "\u0001a\u0001a\u0001a\u0001a"
+ json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\""
+ assert_equal json, generate(data)
+ data = "\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\""
+ assert_equal json, generate(data)
+ data = "\u0001aa\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\\u0001aa\""
+ assert_equal json, generate(data)
+ data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\""
+ assert_equal json, generate(data)
+ data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002"
+ json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\""
+ assert_equal json, generate(data)
+ data = "ab\u0002c"
+ json = "\"ab\\u0002c\""
+ assert_equal json, generate(data)
+ data = "ab\u0002cab\u0002cab\u0002cab\u0002c"
+ json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\""
+ assert_equal json, generate(data)
+ data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c"
+ json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\""
+ assert_equal json, generate(data)
+ data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f"
+ json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\""
+ assert_equal json, generate(data)
+ data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b"
+ json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\""
+ assert_equal json, generate(data)
+ data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t"
+ json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\""
+ assert_equal json, generate(data)
end
def test_string_subclass
@@ -619,6 +919,22 @@ class JSONGeneratorTest < Test::Unit::TestCase
assert_equal '{"JSONGeneratorTest::StringWithToS#to_s":1}', JSON.generate(StringWithToS.new => 1)
end
+ def test_string_subclass_with_broken_to_s
+ klass = Class.new(String) do
+ def to_s
+ false
+ end
+ end
+ s = klass.new("test")
+ assert_equal '["test"]', JSON.generate([s])
+
+ omit("Can't figure out how to match behavior in java code") if RUBY_PLATFORM == "java"
+
+ assert_raise TypeError do
+ JSON.generate(s => 1)
+ end
+ end
+
if defined?(JSON::Ext::Generator) and RUBY_PLATFORM != "java"
def test_valid_utf8_in_different_encoding
utf8_string = "€™"
@@ -633,32 +949,149 @@ class JSONGeneratorTest < Test::Unit::TestCase
assert_equal JSON.dump(utf8_string), JSON.dump(wrong_encoding_string)
end
end
+ end
- def test_string_ext_included_calls_super
- included = false
+ def test_nonutf8_encoding
+ assert_equal("\"5\u{b0}\"", "5\xb0".dup.force_encoding(Encoding::ISO_8859_1).to_json)
+ end
- Module.send(:alias_method, :included_orig, :included)
- Module.send(:remove_method, :included)
- Module.send(:define_method, :included) do |base|
- included_orig(base)
- included = true
- end
+ def test_utf8_multibyte
+ assert_equal('["foßbar"]', JSON.generate(["foßbar"]))
+ assert_equal('"n€ßt€ð2"', JSON.generate("n€ßt€ð2"))
+ assert_equal('"\"\u0000\u001f"', JSON.generate("\"\u0000\u001f"))
+ end
- Class.new(String) do
- include JSON::Ext::Generator::GeneratorMethods::String
- end
+ def test_fragment
+ fragment = JSON::Fragment.new(" 42")
+ assert_equal '{"number": 42}', JSON.generate({ number: fragment })
+ assert_equal '{"number": 42}', JSON.generate({ number: fragment }, strict: true)
+ end
+
+ def test_json_generate_as_json_convert_to_proc
+ object = Object.new
+ assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: -> (o, is_key) { o.object_id })
+ end
+
+ def test_as_json_nan_does_not_call_to_json
+ def (obj = Object.new).to_json(*)
+ "null"
+ end
+ assert_raise(JSON::GeneratorError) do
+ JSON.generate(Float::NAN, strict: true, as_json: proc { obj })
+ end
+ end
+
+ def assert_float_roundtrip(expected, actual)
+ assert_equal(expected, JSON.generate(actual))
+ assert_equal(actual, JSON.parse(JSON.generate(actual)), "JSON: #{JSON.generate(actual)}")
+ end
+
+ def test_json_generate_float
+ assert_float_roundtrip "-1.0", -1.0
+ assert_float_roundtrip "1.0", 1.0
+ assert_float_roundtrip "0.0", 0.0
+ assert_float_roundtrip "12.2", 12.2
+ assert_float_roundtrip "2.34375", 7.5 / 3.2
+ assert_float_roundtrip "12.0", 12.0
+ assert_float_roundtrip "100.0", 100.0
+ assert_float_roundtrip "1000.0", 1000.0
+
+ if RUBY_ENGINE == "jruby"
+ assert_float_roundtrip "1.7468619377842371E9", 1746861937.7842371
+ else
+ assert_float_roundtrip "1746861937.7842371", 1746861937.7842371
+ end
+
+ if RUBY_ENGINE == "ruby"
+ assert_float_roundtrip "100000000000000.0", 100000000000000.0
+ assert_float_roundtrip "1e+15", 1e+15
+ assert_float_roundtrip "-100000000000000.0", -100000000000000.0
+ assert_float_roundtrip "-1e+15", -1e+15
+ assert_float_roundtrip "1111111111111111.1", 1111111111111111.1
+ assert_float_roundtrip "1.1111111111111112e+16", 11111111111111111.1
+ assert_float_roundtrip "-1111111111111111.1", -1111111111111111.1
+ assert_float_roundtrip "-1.1111111111111112e+16", -11111111111111111.1
+
+ assert_float_roundtrip "-0.000000022471348024634545", -2.2471348024634545e-08
+ assert_float_roundtrip "-0.0000000022471348024634545", -2.2471348024634545e-09
+ assert_float_roundtrip "-2.2471348024634546e-10", -2.2471348024634545e-10
+ end
+ end
+
+ def test_numbers_of_various_sizes
+ numbers = [
+ 0, 1, -1, 9, -9, 13, -13, 91, -91, 513, -513, 7513, -7513,
+ 17591, -17591, -4611686018427387904, 4611686018427387903,
+ 2**62, 2**63, 2**64, -(2**62), -(2**63), -(2**64)
+ ]
+
+ numbers.each do |number|
+ assert_equal "[#{number}]", JSON.generate([number])
+ end
+ end
+
+ def test_generate_duplicate_keys_allowed
+ hash = { foo: 1, "foo" => 2 }
+ assert_equal %({"foo":1,"foo":2}), JSON.generate(hash, allow_duplicate_key: true)
+ end
+
+ def test_generate_duplicate_keys_deprecated
+ hash = { foo: 1, "foo" => 2 }
+ assert_deprecated_warning(/allow_duplicate_key/) do
+ assert_equal %({"foo":1,"foo":2}), JSON.generate(hash)
+ end
+ end
+
+ def test_generate_duplicate_keys_disallowed
+ hash = { foo: 1, "foo" => 2 }
+ error = assert_raise JSON::GeneratorError do
+ JSON.generate(hash, allow_duplicate_key: false)
+ end
+ assert_equal %(detected duplicate key "foo" in #{hash.inspect}), error.message
+ end
- assert included
- ensure
- if Module.private_method_defined?(:included_orig)
- Module.send(:remove_method, :included) if Module.method_defined?(:included)
- Module.send(:alias_method, :included, :included_orig)
- Module.send(:remove_method, :included_orig)
+ def test_frozen
+ state = JSON::State.new.freeze
+ assert_raise(FrozenError) do
+ state.configure(max_nesting: 1)
+ end
+ setters = state.methods.grep(/\w=$/)
+ assert_not_empty setters
+ setters.each do |setter|
+ assert_raise(FrozenError) do
+ state.send(setter, 1)
end
end
end
- def test_nonutf8_encoding
- assert_equal("\"5\u{b0}\"", "5\xb0".dup.force_encoding(Encoding::ISO_8859_1).to_json)
+ # The case when the State is frozen is tested in JSONCoderTest#test_nesting_recovery
+ def test_nesting_recovery
+ state = JSON::State.new
+ ary = []
+ ary << ary
+ assert_raise(JSON::NestingError) { state.generate(ary) }
+ assert_equal 0, state.depth
+ assert_equal '{"a":1}', state.generate({ a: 1 })
end
+
+ def test_negative_depth_raises
+ assert_raise(ArgumentError) do
+ JSON.generate({"a" => 1}, depth: -1)
+ end
+ assert_raise(ArgumentError) do
+ JSON.state.new(depth: -1)
+ end
+ end
+
+ def test_large_depth_raises
+ assert_raise(RangeError, ArgumentError) do
+ JSON.generate([[1]],
+ indent: " " * 5,
+ array_nl: "\n",
+ depth: 3_689_348_814_741_910_324,
+ max_nesting: 0
+ )
+ end
+ end
+
end
diff --git a/test/json/json_generic_object_test.rb b/test/json/json_generic_object_test.rb
index 471534192e..57e3bf3c52 100644
--- a/test/json/json_generic_object_test.rb
+++ b/test/json/json_generic_object_test.rb
@@ -1,10 +1,16 @@
# frozen_string_literal: true
require_relative 'test_helper'
-class JSONGenericObjectTest < Test::Unit::TestCase
+# ostruct is required to test JSON::GenericObject
+begin
+ require "ostruct"
+rescue LoadError
+ return
+end
+class JSONGenericObjectTest < Test::Unit::TestCase
def setup
- if defined?(GenericObject)
+ if defined?(JSON::GenericObject)
@go = JSON::GenericObject[ :a => 1, :b => 2 ]
else
omit("JSON::GenericObject is not available")
@@ -40,10 +46,10 @@ class JSONGenericObjectTest < Test::Unit::TestCase
)
assert_equal 1, l.a
assert_equal @go,
- l = JSON('{ "a": 1, "b": 2 }', :object_class => GenericObject)
+ l = JSON('{ "a": 1, "b": 2 }', :object_class => JSON::GenericObject)
assert_equal 1, l.a
- assert_equal GenericObject[:a => GenericObject[:b => 2]],
- l = JSON('{ "a": { "b": 2 } }', :object_class => GenericObject)
+ assert_equal JSON::GenericObject[:a => JSON::GenericObject[:b => 2]],
+ l = JSON('{ "a": { "b": 2 } }', :object_class => JSON::GenericObject)
assert_equal 2, l.a.b
end
end
@@ -51,12 +57,12 @@ class JSONGenericObjectTest < Test::Unit::TestCase
def test_from_hash
result = JSON::GenericObject.from_hash(
:foo => { :bar => { :baz => true }, :quux => [ { :foobar => true } ] })
- assert_kind_of GenericObject, result.foo
- assert_kind_of GenericObject, result.foo.bar
+ assert_kind_of JSON::GenericObject, result.foo
+ assert_kind_of JSON::GenericObject, result.foo.bar
assert_equal true, result.foo.bar.baz
- assert_kind_of GenericObject, result.foo.quux.first
+ assert_kind_of JSON::GenericObject, result.foo.quux.first
assert_equal true, result.foo.quux.first.foobar
- assert_equal true, GenericObject.from_hash(true)
+ assert_equal true, JSON::GenericObject.from_hash(true)
end
def test_json_generic_object_load
diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb
index c01e28910f..292ca1a670 100644
--- a/test/json/json_parser_test.rb
+++ b/test/json/json_parser_test.rb
@@ -104,6 +104,14 @@ class JSONParserTest < Test::Unit::TestCase
assert_raise(JSON::ParserError) { parse('+23') }
assert_raise(JSON::ParserError) { parse('.23') }
assert_raise(JSON::ParserError) { parse('023') }
+ assert_raise(JSON::ParserError) { parse('-023') }
+ assert_raise(JSON::ParserError) { parse('023.12') }
+ assert_raise(JSON::ParserError) { parse('-023.12') }
+ assert_raise(JSON::ParserError) { parse('023e12') }
+ assert_raise(JSON::ParserError) { parse('-023e12') }
+ assert_raise(JSON::ParserError) { parse('-') }
+ assert_raise(JSON::ParserError) { parse('-.1') }
+ assert_raise(JSON::ParserError) { parse('-e0') }
assert_equal(23, parse('23'))
assert_equal(-23, parse('-23'))
assert_equal_float(3.141, parse('3.141'))
@@ -120,6 +128,18 @@ class JSONParserTest < Test::Unit::TestCase
assert_equal(1.0/0, parse('Infinity', :allow_nan => true))
assert_raise(ParserError) { parse('-Infinity') }
assert_equal(-1.0/0, parse('-Infinity', :allow_nan => true))
+ capture_output { assert_equal(Float::INFINITY, parse("23456789012E666")) }
+ end
+
+ def test_parse_bignum
+ bignum = Integer('1234567890' * 10)
+ assert_equal(bignum, JSON.parse(bignum.to_s))
+ assert_equal(bignum.to_f, JSON.parse(bignum.to_s + ".0"))
+
+ bignum = Integer('1234567890' * 50)
+ assert_equal(bignum, JSON.parse(bignum.to_s))
+ bignum_float = EnvUtil.suppress_warning { bignum.to_f }
+ assert_equal(bignum_float, EnvUtil.suppress_warning { JSON.parse(bignum.to_s + ".0") })
end
def test_parse_bigdecimals
@@ -149,6 +169,37 @@ class JSONParserTest < Test::Unit::TestCase
end
end
+ def test_parse_control_chars_in_string
+ 0.upto(31) do |ord|
+ assert_raise JSON::ParserError do
+ parse(%("#{ord.chr}"))
+ end
+ end
+ end
+
+ def test_parse_allowed_control_chars_in_string
+ 0.upto(31) do |ord|
+ assert_equal ord.chr, parse(%("#{ord.chr}"), allow_control_characters: true)
+ end
+ end
+
+ def test_parse_control_char_and_backslash
+ backslash_and_control_char = "\\\t"
+ assert_raise JSON::ParserError do
+ JSON.parse(%("#{'a' * 30}#{backslash_and_control_char}"), allow_control_characters: true, allow_invalid_escape: false)
+ end
+
+ JSON.parse(%("#{'a' * 30}#{backslash_and_control_char}"), allow_control_characters: true, allow_invalid_escape: true)
+ end
+
+ def test_parse_invalid_escape
+ assert_raise JSON::ParserError do
+ parse(%("fo\\o"))
+ end
+
+ assert_equal "foo", parse(%("fo\\o"), allow_invalid_escape: true)
+ end
+
def test_parse_arrays
assert_equal([1,2,3], parse('[1,2,3]'))
assert_equal([1.2,2,3], parse('[1.2,2,3]'))
@@ -297,6 +348,33 @@ class JSONParserTest < Test::Unit::TestCase
end
end
+ def test_invalid_unicode_escape
+ assert_raise(JSON::ParserError) { parse('"\u"') }
+ assert_raise(JSON::ParserError) { parse('"\ua"') }
+ assert_raise(JSON::ParserError) { parse('"\uaa"') }
+ assert_raise(JSON::ParserError) { parse('"\uaaa"') }
+ assert_equal "\uaaaa", parse('"\uaaaa"')
+
+ assert_raise(JSON::ParserError) { parse('"\u______"') }
+ assert_raise(JSON::ParserError) { parse('"\u1_____"') }
+ assert_raise(JSON::ParserError) { parse('"\u11____"') }
+ assert_raise(JSON::ParserError) { parse('"\u111___"') }
+ end
+
+ def test_unicode_followed_by_newline
+ # Ref: https://github.com/ruby/json/issues/912
+ assert_equal "🌌\n".bytes, JSON.parse('"\ud83c\udf0c\n"').bytes
+ assert_equal "🌌\n", JSON.parse('"\ud83c\udf0c\n"')
+ assert_predicate JSON.parse('"\ud83c\udf0c\n"'), :valid_encoding?
+ end
+
+ def test_invalid_surogates
+ assert_raise(JSON::ParserError) { parse('"\\uD800"') }
+ assert_raise(JSON::ParserError) { parse('"\\uD800_________________"') }
+ assert_raise(JSON::ParserError) { parse('"\\uD800\\u0041"') }
+ assert_raise(JSON::ParserError) { parse('"\\uD800\\u004') }
+ end
+
def test_parse_big_integers
json1 = JSON(orig = (1 << 31) - 1)
assert_equal orig, parse(json1)
@@ -310,6 +388,59 @@ class JSONParserTest < Test::Unit::TestCase
assert_equal orig, parse(json5)
end
+ def test_parse_escaped_key
+ doc = {
+ "test\r1" => 1,
+ "entries" => [
+ "test\t2" => 2,
+ "test\n3" => 3,
+ ]
+ }
+
+ assert_equal doc, parse(JSON.generate(doc))
+ end
+
+ def test_parse_duplicate_key
+ expected = {"a" => 2}
+ expected_sym = {a: 2}
+
+ assert_equal expected, parse('{"a": 1, "a": 2}', allow_duplicate_key: true)
+ assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false) }
+ assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false, symbolize_names: true) }
+
+ assert_deprecated_warning(/duplicate key "a"/) do
+ assert_equal expected, parse('{"a": 1, "a": 2}')
+ end
+ assert_deprecated_warning(/duplicate key "a"/) do
+ assert_equal expected_sym, parse('{"a": 1, "a": 2}', symbolize_names: true)
+ end
+
+ if RUBY_ENGINE == 'ruby'
+ assert_deprecated_warning(/#{File.basename(__FILE__)}\:#{__LINE__ + 1}/) do
+ assert_equal expected, parse('{"a": 1, "a": 2}')
+ end
+ end
+
+ unless RUBY_ENGINE == 'jruby'
+ assert_raise(ParserError) do
+ fake_key = Object.new
+ JSON.load('{"a": 1, "a": 2}', -> (obj) { obj == "a" ? fake_key : obj }, allow_duplicate_key: false)
+ end
+
+ assert_deprecated_warning(/duplicate key #<Object:0x/) do
+ fake_key = Object.new
+ JSON.load('{"a": 1, "a": 2}', -> (obj) { obj == "a" ? fake_key : obj })
+ end
+ end
+ end
+
+ def test_parse_duplicate_key_escape
+ error = assert_raise(ParserError) do
+ JSON.parse('{"%s%s%s%s":1,"%s%s%s%s":2}', allow_duplicate_key: false)
+ end
+ assert_match "%s%s%s%s", error.message
+ end
+
def test_some_wrong_inputs
assert_raise(ParserError) { parse('[] bla') }
assert_raise(ParserError) { parse('[] 1') }
@@ -341,10 +472,8 @@ class JSONParserTest < Test::Unit::TestCase
assert_predicate parse('[]', :freeze => true), :frozen?
assert_predicate parse('"foo"', :freeze => true), :frozen?
- if string_deduplication_available?
- assert_same(-'foo', parse('"foo"', :freeze => true))
- assert_same(-'foo', parse('{"foo": 1}', :freeze => true).keys.first)
- end
+ assert_same(-'foo', parse('"foo"', :freeze => true))
+ assert_same(-'foo', parse('{"foo": 1}', :freeze => true).keys.first)
end
def test_parse_comments
@@ -393,6 +522,11 @@ class JSONParserTest < Test::Unit::TestCase
}
JSON
assert_equal({ "key1" => "value1" }, parse(json))
+ assert_equal({}, parse('{} /**/'))
+ assert_raise(ParserError) { parse('{} /* comment not closed') }
+ assert_raise(ParserError) { parse('{} /*/') }
+ assert_raise(ParserError) { parse('{} /x wrong comment') }
+ assert_raise(ParserError) { parse('{} /') }
end
def test_nesting
@@ -411,29 +545,117 @@ class JSONParserTest < Test::Unit::TestCase
end
def test_backslash
+ assert_raise(JSON::ParserError) do
+ JSON.parse('"\\')
+ end
+
data = [ '\\.(?i:gif|jpe?g|png)$' ]
json = '["\\\\.(?i:gif|jpe?g|png)$"]'
assert_equal data, parse(json)
- #
+
data = [ '\\"' ]
json = '["\\\\\""]'
assert_equal data, parse(json)
- #
+
json = '["/"]'
data = [ '/' ]
assert_equal data, parse(json)
- #
+
json = '["\""]'
data = ['"']
assert_equal data, parse(json)
- #
- json = '["\\\'"]'
- data = ["'"]
+
+ json = '["\\/"]'
+ data = ["/"]
assert_equal data, parse(json)
json = '["\/"]'
data = [ '/' ]
assert_equal data, parse(json)
+
+ data = ['"""""""""""""""""""""""""']
+ json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]'
+ assert_equal data, parse(json)
+
+ data = '["This is a "test" of the emergency broadcast system."]'
+ json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\""
+ assert_equal data, parse(json)
+
+ data = '\tThis is a test of the emergency broadcast system.'
+ json = "\"\\\\tThis is a test of the emergency broadcast system.\""
+ assert_equal data, parse(json)
+
+ data = 'This\tis a test of the emergency broadcast system.'
+ json = "\"This\\\\tis a test of the emergency broadcast system.\""
+ assert_equal data, parse(json)
+
+ data = 'This is\ta test of the emergency broadcast system.'
+ json = "\"This is\\\\ta test of the emergency broadcast system.\""
+ assert_equal data, parse(json)
+
+ data = 'This is a test of the emergency broadcast\tsystem.'
+ json = "\"This is a test of the emergency broadcast\\\\tsystem.\""
+ assert_equal data, parse(json)
+
+ data = 'This is a test of the emergency broadcast\tsystem.\n'
+ json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\""
+ assert_equal data, parse(json)
+
+ data = '"' * 15
+ json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\""
+ assert_equal data, parse(json)
+
+ data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a"
+ json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\""
+ assert_equal data, parse(json)
+
+ data = "\u0001\u0001\u0001\u0001"
+ json = "\"\\u0001\\u0001\\u0001\\u0001\""
+ assert_equal data, parse(json)
+
+ data = "\u0001a\u0001a\u0001a\u0001a"
+ json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\""
+ assert_equal data, parse(json)
+
+ data = "\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\""
+ assert_equal data, parse(json)
+
+ data = "\u0001aa\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\\u0001aa\""
+ assert_equal data, parse(json)
+
+ data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa"
+ json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\""
+ assert_equal data, parse(json)
+
+ data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002"
+ json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\""
+ assert_equal data, parse(json)
+
+ data = "ab\u0002c"
+ json = "\"ab\\u0002c\""
+ assert_equal data, parse(json)
+
+ data = "ab\u0002cab\u0002cab\u0002cab\u0002c"
+ json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\""
+ assert_equal data, parse(json)
+
+ data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c"
+ json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\""
+ assert_equal data, parse(json)
+
+ data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f"
+ json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\""
+ assert_equal data, parse(json)
+
+ data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b"
+ json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\""
+ assert_equal data, parse(json)
+
+ data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t"
+ json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\""
+ assert_equal data, parse(json)
end
class SubArray < Array
@@ -492,6 +714,7 @@ class JSONParserTest < Test::Unit::TestCase
def test_parse_array_custom_non_array_derived_class
res = parse('[1,2]', :array_class => SubArrayWrapper)
assert_equal([1,2], res.data)
+ assert_equal(1, res[0])
assert_equal(SubArrayWrapper, res.class)
assert res.shifted?
end
@@ -553,6 +776,7 @@ class JSONParserTest < Test::Unit::TestCase
def test_parse_object_custom_non_hash_derived_class
res = parse('{"foo":"bar"}', :object_class => SubOpenStruct)
assert_equal "bar", res.foo
+ assert_equal "bar", res[:foo]
assert_equal(SubOpenStruct, res.class)
assert res.item_set?
end
@@ -612,7 +836,7 @@ class JSONParserTest < Test::Unit::TestCase
error = assert_raise(JSON::ParserError) do
JSON.parse('{"foo": ' + ('A' * 500) + '}')
end
- assert_operator 60, :>, error.message.bytesize
+ assert_operator 80, :>, error.message.bytesize
end
def test_parse_error_incomplete_hash
@@ -620,22 +844,62 @@ class JSONParserTest < Test::Unit::TestCase
JSON.parse('{"input":{"firstName":"Bob","lastName":"Mob","email":"bob@example.com"}')
end
if RUBY_ENGINE == "ruby"
- assert_equal %(unexpected token at '{"input":{"firstName":"Bob","las'), error.message
+ assert_equal %(expected ',' or '}' after object value, got: EOF at line 1 column 72), error.message
end
end
- private
+ def test_parse_error_snippet
+ omit "C ext only test" unless RUBY_ENGINE == "ruby"
+
+ error = assert_raise(JSON::ParserError) { JSON.parse("あああああああああああああああああああああああ") }
+ assert_equal "unexpected character: 'ああああああああああ' at line 1 column 1", error.message
+
+ error = assert_raise(JSON::ParserError) { JSON.parse("aあああああああああああああああああああああああ") }
+ assert_equal "unexpected character: 'aああああああああああ' at line 1 column 1", error.message
+
+ error = assert_raise(JSON::ParserError) { JSON.parse("abあああああああああああああああああああああああ") }
+ assert_equal "unexpected character: 'abあああああああああ' at line 1 column 1", error.message
- def string_deduplication_available?
- r1 = rand.to_s
- r2 = r1.dup
- begin
- (-r1).equal?(-r2)
- rescue NoMethodError
- false # No String#-@
+ error = assert_raise(JSON::ParserError) { JSON.parse("abcあああああああああああああああああああああああ") }
+ assert_equal "unexpected character: 'abcあああああああああ' at line 1 column 1", error.message
+ end
+
+ def test_parse_leading_slash
+ # ref: https://github.com/ruby/ruby/pull/12598
+ assert_raise(JSON::ParserError) do
+ JSON.parse("/foo/bar")
+ end
+ end
+
+ def test_parse_whitespace_after_newline
+ assert_equal [], JSON.parse("[\n#{' ' * (8 + 8 + 4 + 3)}]")
+ end
+
+ def test_frozen
+ parser_config = JSON::Parser::Config.new({}).freeze
+ assert_raise FrozenError do
+ parser_config.send(:initialize, {})
end
end
+ def test_mutating_source_string_during_parsing
+ expected = ([1] * 100) + [2.3] + ([1] * 100)
+ source = JSON.generate(expected)
+ expected.delete_at(100)
+
+ fake_decimal_class = Class.new
+ fake_decimal_class.define_method(:initialize) do |number|
+ source.tr!('1', '0')
+ number.to_f
+ end
+
+ actual = JSON.parse(source, decimal_class: fake_decimal_class)
+ actual.delete_at(100)
+ assert_equal expected, actual
+ end
+
+ private
+
def assert_equal_float(expected, actual, delta = 1e-2)
Array === expected and expected = expected.first
Array === actual and actual = actual.first
diff --git a/test/json/json_ryu_fallback_test.rb b/test/json/json_ryu_fallback_test.rb
new file mode 100644
index 0000000000..a61b3e668d
--- /dev/null
+++ b/test/json/json_ryu_fallback_test.rb
@@ -0,0 +1,191 @@
+# frozen_string_literal: true
+require_relative 'test_helper'
+begin
+ require 'bigdecimal'
+rescue LoadError
+end
+
+class JSONRyuFallbackTest < Test::Unit::TestCase
+ include JSON
+
+ # Test that numbers with more than 17 significant digits fall back to rb_cstr_to_dbl
+ def test_more_than_17_significant_digits
+ # These numbers have > 17 significant digits and should use fallback path
+ # They should still parse correctly, just not via the Ryu optimization
+
+ test_cases = [
+ # input, expected (rounded to double precision)
+ ["1.23456789012345678901234567890", 1.2345678901234567],
+ ["123456789012345678.901234567890", 1.2345678901234568e+17],
+ ["0.123456789012345678901234567890", 0.12345678901234568],
+ ["9999999999999999999999999999.9", 1.0e+28],
+ # Edge case: exactly 18 digits
+ ["123456789012345678", 123456789012345680.0],
+ # Many fractional digits
+ ["0.12345678901234567890123456789", 0.12345678901234568],
+ ]
+
+ test_cases.each do |input, expected|
+ result = JSON.parse(input)
+ assert_in_delta(expected, result, 1e-10,
+ "Failed to parse #{input} correctly (>17 digits, fallback path)")
+ end
+ end
+
+ # Test decimal_class option forces fallback
+ def test_decimal_class_option
+ input = "3.141"
+
+ # Without decimal_class: uses Ryu, returns Float
+ result_float = JSON.parse(input)
+ assert_instance_of(Float, result_float)
+ assert_equal(3.141, result_float)
+
+ # With decimal_class: uses fallback, returns BigDecimal
+ result_bigdecimal = JSON.parse(input, decimal_class: BigDecimal)
+ assert_instance_of(BigDecimal, result_bigdecimal)
+ assert_equal(BigDecimal("3.141"), result_bigdecimal)
+ end if defined?(::BigDecimal)
+
+ # Test that numbers with <= 17 digits use Ryu optimization
+ def test_ryu_optimization_used_for_normal_numbers
+ test_cases = [
+ ["3.141", 3.141],
+ ["1.23456789012345e100", 1.23456789012345e100],
+ ["0.00000000000001", 1.0e-14],
+ ["123456789012345.67", 123456789012345.67],
+ ["-1.7976931348623157e+308", -1.7976931348623157e+308],
+ ["2.2250738585072014e-308", 2.2250738585072014e-308],
+ # Exactly 17 significant digits
+ ["12345678901234567", 12345678901234567.0],
+ ["1.2345678901234567", 1.2345678901234567],
+ ]
+
+ test_cases.each do |input, expected|
+ result = JSON.parse(input)
+ assert_in_delta(expected, result, expected.abs * 1e-15,
+ "Failed to parse #{input} correctly (<=17 digits, Ryu path)")
+ end
+ end
+
+ # Test edge cases at the boundary (17 digits)
+ def test_seventeen_digit_boundary
+ # Exactly 17 significant digits should use Ryu
+ input_17 = "12345678901234567.0" # Force it to be a float with .0
+ result = JSON.parse(input_17)
+ assert_in_delta(12345678901234567.0, result, 1e-10)
+
+ # 18 significant digits should use fallback
+ input_18 = "123456789012345678.0"
+ result = JSON.parse(input_18)
+ # Note: This will be rounded to double precision
+ assert_in_delta(123456789012345680.0, result, 1e-10)
+ end
+
+ # Test that leading zeros don't count toward the 17-digit limit
+ def test_leading_zeros_dont_count
+ test_cases = [
+ ["0.00012345678901234567", 0.00012345678901234567], # 17 significant digits
+ ["0.000000000000001234567890123456789", 1.234567890123457e-15], # >17 significant
+ ]
+
+ test_cases.each do |input, expected|
+ result = JSON.parse(input)
+ assert_in_delta(expected, result, expected.abs * 1e-10,
+ "Failed to parse #{input} correctly")
+ end
+ end
+
+ # Test that Ryu handles special values correctly
+ def test_special_double_values
+ test_cases = [
+ ["1.7976931348623157e+308", Float::MAX], # Largest finite double
+ ["2.2250738585072014e-308", Float::MIN], # Smallest normalized double
+ ]
+
+ test_cases.each do |input, expected|
+ result = JSON.parse(input)
+ assert_in_delta(expected, result, expected.abs * 1e-10,
+ "Failed to parse #{input} correctly")
+ end
+
+ # Test zero separately
+ result_pos_zero = JSON.parse("0.0")
+ assert_equal(0.0, result_pos_zero)
+
+ # Note: JSON.parse doesn't preserve -0.0 vs +0.0 distinction in standard mode
+ result_neg_zero = JSON.parse("-0.0")
+ assert_equal(0.0, result_neg_zero.abs)
+ end
+
+ # Test subnormal numbers that caused precision issues before fallback was added
+ # These are extreme edge cases discovered by fuzzing (4 in 6 billion numbers tested)
+ def test_subnormal_edge_cases_round_trip
+ # These subnormal numbers (~1e-310) had 1 ULP rounding errors in original Ryu
+ # They now use rb_cstr_to_dbl fallback for exact precision
+ test_cases = [
+ "-3.2652630314355e-310",
+ "3.9701623107025e-310",
+ "-3.6607772435415e-310",
+ "2.9714076801985e-310",
+ ]
+
+ test_cases.each do |input|
+ # Parse the number
+ result = JSON.parse(input)
+
+ # Should be bit-identical
+ assert_equal(result, JSON.parse(result.to_s),
+ "Subnormal #{input} failed round-trip test")
+
+ # Should be bit-identical
+ assert_equal(result, JSON.parse(JSON.dump(result)),
+ "Subnormal #{input} failed round-trip test")
+
+ # Verify the value is in the expected subnormal range
+ assert(result.abs < 2.225e-308,
+ "#{input} should be subnormal (< 2.225e-308)")
+ end
+ end
+
+ # Test invalid numbers are properly rejected
+ def test_invalid_numbers_rejected
+ invalid_cases = [
+ "-",
+ ".",
+ "-.",
+ "-.e10",
+ "1.2.3",
+ "1e",
+ "1e+",
+ ]
+
+ invalid_cases.each do |input|
+ assert_raise(JSON::ParserError, "Should reject invalid number: #{input}") do
+ JSON.parse(input)
+ end
+ end
+ end
+
+ def test_large_exponent_numbers
+ assert_equal Float::INFINITY, JSON.parse("1e4294967296")
+ assert_equal 0.0, JSON.parse("1e-4294967296")
+ assert_equal 0.0, JSON.parse("99999999999999999e-4294967296")
+ assert_equal Float::INFINITY, JSON.parse("1e4294967295")
+ assert_equal Float::INFINITY, JSON.parse("1e4294967297")
+
+ assert_equal(-Float::INFINITY, JSON.parse("-1e4294967296"))
+ assert_equal(-0.0, JSON.parse("-1e-4294967296"))
+ assert_equal(-0.0, JSON.parse("-99999999999999999e-4294967296"))
+ assert_equal(-Float::INFINITY, JSON.parse("-1e4294967295"))
+ assert_equal(-Float::INFINITY, JSON.parse("-1e4294967297"))
+
+ assert_equal(Float::INFINITY, JSON.parse("1e9223372036854775808"))
+ assert_equal(Float::INFINITY, JSON.parse("1e9999999999999999999"))
+ assert_equal(Float::INFINITY, JSON.parse("1e18446744073709551616"))
+ assert_equal(Float::INFINITY, JSON.parse("1e10000000000000000000"))
+ assert_equal(Float::INFINITY, JSON.parse("1e184467440737095516160"))
+ assert_equal 0.0, JSON.parse("1e-18446744073709551615")
+ assert_equal 0.0, JSON.parse("1e-9223372036854775809")
+ end
+end
diff --git a/test/json/ractor_test.rb b/test/json/ractor_test.rb
index f857c9a8bf..e53c405a74 100644
--- a/test/json/ractor_test.rb
+++ b/test/json/ractor_test.rb
@@ -8,8 +8,19 @@ rescue LoadError
end
class JSONInRactorTest < Test::Unit::TestCase
+ unless Ractor.method_defined?(:value)
+ module RactorBackport
+ refine Ractor do
+ alias_method :value, :take
+ end
+ end
+
+ using RactorBackport
+ end
+
def test_generate
pid = fork do
+ Warning[:experimental] = false
r = Ractor.new do
json = JSON.generate({
'a' => 2,
@@ -25,14 +36,14 @@ class JSONInRactorTest < Test::Unit::TestCase
end
expected_json = JSON.parse('{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}')
- actual_json = r.take
+ actual_json = r.value
if expected_json == actual_json
exit 0
else
puts "Expected:"
puts expected_json
- puts "Acutual:"
+ puts "Actual:"
puts actual_json
puts
exit 1
@@ -41,4 +52,63 @@ class JSONInRactorTest < Test::Unit::TestCase
_, status = Process.waitpid2(pid)
assert_predicate status, :success?
end
+
+ def test_coder
+ coder = JSON::Coder.new.freeze
+ assert Ractor.shareable?(coder)
+ pid = fork do
+ Warning[:experimental] = false
+ r = Ractor.new(coder) do |coder|
+ json = coder.dump({
+ 'a' => 2,
+ 'b' => 3.141,
+ 'c' => 'c',
+ 'd' => [ 1, "b", 3.14 ],
+ 'e' => { 'foo' => 'bar' },
+ 'g' => "\"\0\037",
+ 'h' => 1000.0,
+ 'i' => 0.001
+ })
+ coder.load(json)
+ end
+ expected_json = JSON.parse('{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
+ '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}')
+ actual_json = r.value
+
+ if expected_json == actual_json
+ exit 0
+ else
+ puts "Expected:"
+ puts expected_json
+ puts "Actual:"
+ puts actual_json
+ puts
+ exit 1
+ end
+ end
+ _, status = Process.waitpid2(pid)
+ assert_predicate status, :success?
+ end
+
+ class NonNative
+ def initialize(value)
+ @value = value
+ end
+ end
+
+ def test_coder_proc
+ block = Ractor.shareable_proc { |value| value.as_json }
+ coder = JSON::Coder.new(&block).freeze
+ assert Ractor.shareable?(coder)
+
+ pid = fork do
+ Warning[:experimental] = false
+ assert_equal [{}], Ractor.new(coder) { |coder|
+ coder.load('[{}]')
+ }.value
+ end
+
+ _, status = Process.waitpid2(pid)
+ assert_predicate status, :success?
+ end if Ractor.respond_to?(:shareable_proc)
end if defined?(Ractor) && Process.respond_to?(:fork)
diff --git a/test/json/test_helper.rb b/test/json/test_helper.rb
index d849e28b9b..4c5a91a192 100644
--- a/test/json/test_helper.rb
+++ b/test/json/test_helper.rb
@@ -1,5 +1,30 @@
$LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__))
+if ENV["JSON_COVERAGE"]
+ # This test helper is loaded inside Ruby's own test suite, so we try to not mess it up.
+ require 'coverage'
+
+ branches_supported = Coverage.respond_to?(:supported?) && Coverage.supported?(:branches)
+
+ # Coverage module must be started before SimpleCov to work around the cyclic require order.
+ # Track both branches and lines, or else SimpleCov misleadingly reports 0/0 = 100% for non-branching files.
+ Coverage.start(lines: true,
+ branches: branches_supported)
+
+ require 'simplecov'
+ SimpleCov.start do
+ # Enabling both coverage types to let SimpleCov know to output them together in reports
+ enable_coverage :line
+ enable_coverage :branch if branches_supported
+
+ # Can't always trust SimpleCov to find files implicitly
+ track_files 'lib/**/*.rb'
+
+ add_filter 'lib/json/truffle_ruby' unless RUBY_ENGINE == 'truffleruby'
+ add_filter 'test/'
+ end
+end
+
require 'json'
require 'test/unit'
diff --git a/test/lib/jit_support.rb b/test/lib/jit_support.rb
index cf3baaaeb7..386a5a6f1e 100644
--- a/test/lib/jit_support.rb
+++ b/test/lib/jit_support.rb
@@ -10,24 +10,20 @@ module JITSupport
end
def yjit_enabled?
- defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
+ defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
end
def yjit_force_enabled?
"#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?YJIT_FORCE_ENABLE\b/)
end
- def rjit_supported?
- return @rjit_supported if defined?(@rjit_supported)
+ def zjit_supported?
+ return @zjit_supported if defined?(@zjit_supported)
# nil in mswin
- @rjit_supported = ![nil, 'no'].include?(RbConfig::CONFIG['RJIT_SUPPORT'])
+ @zjit_supported = ![nil, 'no'].include?(RbConfig::CONFIG['ZJIT_SUPPORT'])
end
- def rjit_enabled?
- defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
- end
-
- def rjit_force_enabled?
- "#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?RJIT_FORCE_ENABLE\b/)
+ def zjit_enabled?
+ defined?(RubyVM::ZJIT) && RubyVM::ZJIT.enabled?
end
end
diff --git a/test/mkmf/test_egrep_cpp.rb b/test/mkmf/test_egrep_cpp.rb
index 7ac0e60010..1126324965 100644
--- a/test/mkmf/test_egrep_cpp.rb
+++ b/test/mkmf/test_egrep_cpp.rb
@@ -10,4 +10,18 @@ class TestMkmfEgrepCpp < TestMkmf
def test_not_have_func
assert_equal(false, egrep_cpp(/never match/, ""), MKMFLOG)
end
+
+ class TestMkmfEgrepCxx < self
+ def test_cxx_egrep_cpp
+ assert_equal(true, MakeMakefile["C++"].egrep_cpp(/^ok/, <<~SRC), MKMFLOG)
+ #ifdef __cplusplus
+ ok
+ #else
+ #error not C++
+ #endif
+ SRC
+ rescue Errno::ENOENT
+ omit "C++ compiler not available: #{$!.message}"
+ end
+ end
end
diff --git a/test/mkmf/test_pkg_config.rb b/test/mkmf/test_pkg_config.rb
index f6a960c7d9..adf5fa6e92 100644
--- a/test/mkmf/test_pkg_config.rb
+++ b/test/mkmf/test_pkg_config.rb
@@ -46,21 +46,26 @@ class TestMkmfPkgConfig < TestMkmf
def test_pkgconfig_with_libs_option_returns_output
pend("skipping because pkg-config is not installed") unless PKG_CONFIG
expected = ["-L#{@fixtures_lib_dir}", "-ltest1-public"].sort
- actual = pkg_config("test1", "libs").shellsplit.sort
- assert_equal(expected, actual, MKMFLOG)
+ actual = pkg_config("test1", "libs")
+ assert_equal_sorted(expected, actual, MKMFLOG)
end
def test_pkgconfig_with_cflags_option_returns_output
pend("skipping because pkg-config is not installed") unless PKG_CONFIG
expected = ["--cflags-other", "-I#{@fixtures_inc_dir}/cflags-I"].sort
- actual = pkg_config("test1", "cflags").shellsplit.sort
- assert_equal(expected, actual, MKMFLOG)
+ actual = pkg_config("test1", "cflags")
+ assert_equal_sorted(expected, actual, MKMFLOG)
end
def test_pkgconfig_with_multiple_options
pend("skipping because pkg-config is not installed") unless PKG_CONFIG
expected = ["-L#{@fixtures_lib_dir}", "-ltest1-public", "-ltest1-private"].sort
- actual = pkg_config("test1", "libs", "static").shellsplit.sort
- assert_equal(expected, actual, MKMFLOG)
+ actual = pkg_config("test1", "libs", "static")
+ assert_equal_sorted(expected, actual, MKMFLOG)
+ end
+
+ private def assert_equal_sorted(expected, actual, msg = nil)
+ actual = actual.shellsplit.sort if actual
+ assert_equal(expected, actual, msg)
end
end
diff --git a/test/mmtk/helper.rb b/test/mmtk/helper.rb
index e4cff30389..3bede9ed30 100644
--- a/test/mmtk/helper.rb
+++ b/test/mmtk/helper.rb
@@ -9,9 +9,20 @@ module MMTk
def setup
omit "Not running on MMTk" unless using_mmtk?
+
+ @original_timeout_scale = EnvUtil.timeout_scale
+ timeout_scale = ENV["RUBY_TEST_TIMEOUT_SCALE"].to_f
+ EnvUtil.timeout_scale = timeout_scale if timeout_scale > 0
+
super
end
+ def teardown
+ if using_mmtk?
+ EnvUtil.timeout_scale = @original_timeout_scale
+ end
+ end
+
private
def using_mmtk?
diff --git a/test/mmtk/test_configuration.rb b/test/mmtk/test_configuration.rb
index 0f60eb62f0..427cd9a079 100644
--- a/test/mmtk/test_configuration.rb
+++ b/test/mmtk/test_configuration.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+
require_relative "helper"
+
module MMTk
class TestConfiguration < TestCase
def test_MMTK_THREADS
@@ -29,16 +31,26 @@ module MMTk
end
def test_MMTK_HEAP_MIN
- # TODO: uncomment this test when the infinite loop is fixed
- # assert_separately([{ "MMTK_HEAP_MODE" => "dynamic", "MMTK_HEAP_MIN" => "1" }], <<~RUBY)
- # assert_equal(1, GC.config[:mmtk_heap_min])
- # RUBY
+ # Defaults to 1MiB
+ assert_separately([], <<~RUBY)
+ assert_equal(1 * 1024 * 1024, GC.config[:mmtk_heap_min])
+ RUBY
+
+ assert_separately([{ "MMTK_HEAP_MODE" => "dynamic", "MMTK_HEAP_MIN" => "1" }], <<~RUBY)
+ assert_equal(1, GC.config[:mmtk_heap_min])
+ RUBY
assert_separately([{ "MMTK_HEAP_MODE" => "dynamic", "MMTK_HEAP_MIN" => "10MiB", "MMTK_HEAP_MAX" => "1GiB" }], <<~RUBY)
assert_equal(10 * 1024 * 1024, GC.config[:mmtk_heap_min])
RUBY
end
+ def test_MMTK_HEAP_MIN_is_ignored_for_fixed_heaps
+ assert_separately([{ "MMTK_HEAP_MODE" => "fixed", "MMTK_HEAP_MIN" => "1" }], <<~RUBY)
+ assert_nil(GC.config[:mmtk_heap_min])
+ RUBY
+ end
+
def test_MMTK_HEAP_MAX
assert_separately([{ "MMTK_HEAP_MODE" => "fixed", "MMTK_HEAP_MAX" => "100MiB" }], <<~RUBY)
assert_equal(100 * 1024 * 1024, GC.config[:mmtk_heap_max])
diff --git a/test/monitor/test_monitor.rb b/test/monitor/test_monitor.rb
index 4c55afca6c..7a26831baf 100644
--- a/test/monitor/test_monitor.rb
+++ b/test/monitor/test_monitor.rb
@@ -274,7 +274,7 @@ class TestMonitor < Test::Unit::TestCase
@monitor.synchronize do
queue2.enq(nil)
assert_equal("foo", b)
- result2 = cond.wait(0.1)
+ result2 = cond.wait(10)
assert_equal(true, result2)
assert_equal("bar", b)
end
diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb
index a49cc87e8d..4e7fa22756 100644
--- a/test/net/http/test_http.rb
+++ b/test/net/http/test_http.rb
@@ -494,12 +494,10 @@ module TestNetHTTP_version_1_1_methods
def test_s_post
url = "http://#{config('host')}:#{config('port')}/?q=a"
- res = assert_warning(/Content-Type did not set/) do
- Net::HTTP.post(
- URI.parse(url),
- "a=x")
- end
- assert_equal "application/x-www-form-urlencoded", res["Content-Type"]
+ res = Net::HTTP.post(
+ URI.parse(url),
+ "a=x")
+ assert_equal "application/octet-stream", res["Content-Type"]
assert_equal "a=x", res.body
assert_equal url, res["X-request-uri"]
@@ -565,14 +563,12 @@ module TestNetHTTP_version_1_1_methods
conn = Net::HTTP.new('localhost', port)
conn.write_timeout = EnvUtil.apply_timeout_scale(0.01)
conn.read_timeout = EnvUtil.apply_timeout_scale(0.01) if windows?
- conn.open_timeout = EnvUtil.apply_timeout_scale(0.1)
+ conn.open_timeout = EnvUtil.apply_timeout_scale(1)
th = Thread.new do
err = !windows? ? Net::WriteTimeout : Net::ReadTimeout
assert_raise(err) do
- assert_warning(/Content-Type did not set/) do
- conn.post('/', "a"*50_000_000)
- end
+ conn.post('/', "a"*50_000_000)
end
end
assert th.join(EnvUtil.apply_timeout_scale(10))
@@ -589,9 +585,9 @@ module TestNetHTTP_version_1_1_methods
port = server.addr[1]
conn = Net::HTTP.new('localhost', port)
- conn.write_timeout = 0.01
- conn.read_timeout = 0.01 if windows?
- conn.open_timeout = 0.1
+ conn.write_timeout = EnvUtil.apply_timeout_scale(0.01)
+ conn.read_timeout = EnvUtil.apply_timeout_scale(0.01) if windows?
+ conn.open_timeout = EnvUtil.apply_timeout_scale(1)
req = Net::HTTP::Post.new('/')
data = "a"*50_000_000
@@ -1404,3 +1400,28 @@ class TestNetHTTPPartialResponse < Test::Unit::TestCase
assert_raise(EOFError) {http.get('/')}
end
end
+
+class TestNetHTTPInRactor < Test::Unit::TestCase
+ CONFIG = {
+ 'host' => '127.0.0.1',
+ 'proxy_host' => nil,
+ 'proxy_port' => nil,
+ }
+
+ include TestNetHTTPUtils
+
+ def test_get
+ assert_ractor(<<~RUBY, require: 'net/http')
+ expected = #{$test_net_http_data.dump}.b
+ ret = Ractor.new {
+ host = #{config('host').dump}
+ port = #{config('port')}
+ Net::HTTP.start(host, port) { |http|
+ res = http.get('/')
+ res.body
+ }
+ }.value
+ assert_equal expected, ret
+ RUBY
+ end
+end if defined?(Ractor) && Ractor.method_defined?(:value)
diff --git a/test/net/http/test_http_request.rb b/test/net/http/test_http_request.rb
index 7fd82b0353..9f5cf4f8f5 100644
--- a/test/net/http/test_http_request.rb
+++ b/test/net/http/test_http_request.rb
@@ -74,6 +74,18 @@ class HTTPRequestTest < Test::Unit::TestCase
assert_equal "/foo", req.path
assert_equal "example.com", req['Host']
+ req = Net::HTTP::Get.new(URI("https://203.0.113.1/foo"))
+ assert_equal "/foo", req.path
+ assert_equal "203.0.113.1", req['Host']
+
+ req = Net::HTTP::Get.new(URI("https://203.0.113.1:8000/foo"))
+ assert_equal "/foo", req.path
+ assert_equal "203.0.113.1:8000", req['Host']
+
+ req = Net::HTTP::Get.new(URI("https://[2001:db8::1]:8000/foo"))
+ assert_equal "/foo", req.path
+ assert_equal "[2001:db8::1]:8000", req['Host']
+
assert_raise(ArgumentError){ Net::HTTP::Get.new(URI("urn:ietf:rfc:7231")) }
assert_raise(ArgumentError){ Net::HTTP::Get.new(URI("http://")) }
end
@@ -89,5 +101,25 @@ class HTTPRequestTest < Test::Unit::TestCase
'Bug #7831 - do not decode content if the user overrides'
end if Net::HTTP::HAVE_ZLIB
+ def test_update_uri
+ req = Net::HTTP::Get.new(URI.parse("http://203.0.113.1"))
+ req.update_uri("test", 8080, false)
+ assert_equal "203.0.113.1", req.uri.host
+ assert_equal 8080, req.uri.port
+
+ req = Net::HTTP::Get.new(URI.parse("http://203.0.113.1:2020"))
+ req.update_uri("test", 8080, false)
+ assert_equal "203.0.113.1", req.uri.host
+ assert_equal 8080, req.uri.port
+
+ req = Net::HTTP::Get.new(URI.parse("http://[2001:db8::1]"))
+ req.update_uri("test", 8080, false)
+ assert_equal "[2001:db8::1]", req.uri.host
+ assert_equal 8080, req.uri.port
+
+ req = Net::HTTP::Get.new(URI.parse("http://[2001:db8::1]:2020"))
+ req.update_uri("test", 8080, false)
+ assert_equal "[2001:db8::1]", req.uri.host
+ assert_equal 8080, req.uri.port
+ end
end
-
diff --git a/test/net/http/test_https.rb b/test/net/http/test_https.rb
index e860c8745e..f5b21b901f 100644
--- a/test/net/http/test_https.rb
+++ b/test/net/http/test_https.rb
@@ -7,6 +7,8 @@ rescue LoadError
# should skip this test
end
+return unless defined?(OpenSSL::SSL)
+
class TestNetHTTPS < Test::Unit::TestCase
include TestNetHTTPUtils
@@ -19,7 +21,6 @@ class TestNetHTTPS < Test::Unit::TestCase
CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem"))
SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key"))
SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt"))
- DHPARAMS = OpenSSL::PKey::DH.new(read_fixture("dhparams.pem"))
TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) }
CONFIG = {
@@ -29,25 +30,16 @@ class TestNetHTTPS < Test::Unit::TestCase
'ssl_enable' => true,
'ssl_certificate' => SERVER_CERT,
'ssl_private_key' => SERVER_KEY,
- 'ssl_tmp_dh_callback' => proc { DHPARAMS },
}
def test_get
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.cert_store = TEST_STORE
- certs = []
- http.verify_callback = Proc.new do |preverify_ok, store_ctx|
- certs << store_ctx.current_cert
- preverify_ok
- end
http.request_get("/") {|res|
assert_equal($test_net_http_data, res.body)
+ assert_equal(SERVER_CERT.to_der, http.peer_cert.to_der)
}
- # TODO: OpenSSL 1.1.1h seems to yield only SERVER_CERT; need to check the incompatibility
- certs.zip([CA_CERT, SERVER_CERT][-certs.size..-1]) do |actual, expected|
- assert_equal(expected.to_der, actual.to_der)
- end
end
def test_get_SNI
@@ -55,18 +47,10 @@ class TestNetHTTPS < Test::Unit::TestCase
http.ipaddr = config('host')
http.use_ssl = true
http.cert_store = TEST_STORE
- certs = []
- http.verify_callback = Proc.new do |preverify_ok, store_ctx|
- certs << store_ctx.current_cert
- preverify_ok
- end
http.request_get("/") {|res|
assert_equal($test_net_http_data, res.body)
+ assert_equal(SERVER_CERT.to_der, http.peer_cert.to_der)
}
- # TODO: OpenSSL 1.1.1h seems to yield only SERVER_CERT; need to check the incompatibility
- certs.zip([CA_CERT, SERVER_CERT][-certs.size..-1]) do |actual, expected|
- assert_equal(expected.to_der, actual.to_der)
- end
end
def test_get_SNI_proxy
@@ -78,11 +62,6 @@ class TestNetHTTPS < Test::Unit::TestCase
http.ipaddr = "192.0.2.1"
http.use_ssl = true
http.cert_store = TEST_STORE
- certs = []
- http.verify_callback = Proc.new do |preverify_ok, store_ctx|
- certs << store_ctx.current_cert
- preverify_ok
- end
begin
http.start
rescue EOFError
@@ -114,11 +93,6 @@ class TestNetHTTPS < Test::Unit::TestCase
http.ipaddr = config('host')
http.use_ssl = true
http.cert_store = TEST_STORE
- certs = []
- http.verify_callback = Proc.new do |preverify_ok, store_ctx|
- certs << store_ctx.current_cert
- preverify_ok
- end
@log_tester = lambda {|_| }
assert_raise(OpenSSL::SSL::SSLError){ http.start }
end
@@ -135,10 +109,6 @@ class TestNetHTTPS < Test::Unit::TestCase
end
def test_session_reuse
- # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h.
- # See https://github.com/openssl/openssl/pull/5967 for details.
- omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 1.1.0h')
-
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.cert_store = TEST_STORE
@@ -165,9 +135,6 @@ class TestNetHTTPS < Test::Unit::TestCase
end
def test_session_reuse_but_expire
- # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h.
- omit if OpenSSL::OPENSSL_LIBRARY_VERSION.include?('OpenSSL 1.1.0h')
-
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.cert_store = TEST_STORE
@@ -240,6 +207,21 @@ class TestNetHTTPS < Test::Unit::TestCase
assert_match(/certificate verify failed/, ex.message)
end
+ def test_verify_callback
+ http = Net::HTTP.new(HOST, config("port"))
+ http.use_ssl = true
+ http.cert_store = TEST_STORE
+ certs = []
+ http.verify_callback = Proc.new {|preverify_ok, store_ctx|
+ certs << store_ctx.current_cert
+ preverify_ok
+ }
+ http.request_get("/") {|res|
+ assert_equal($test_net_http_data, res.body)
+ }
+ assert_equal(SERVER_CERT.to_der, certs.last.to_der)
+ end
+
def test_timeout_during_SSL_handshake
bug4246 = "expected the SSL connection to have timed out but have not. [ruby-core:34203]"
@@ -275,9 +257,7 @@ class TestNetHTTPS < Test::Unit::TestCase
http = Net::HTTP.new(HOST, config("port"))
http.use_ssl = true
http.max_version = :SSL2
- http.verify_callback = Proc.new do |preverify_ok, store_ctx|
- true
- end
+ http.cert_store = TEST_STORE
@log_tester = lambda {|_| }
ex = assert_raise(OpenSSL::SSL::SSLError){
http.request_get("/") {|res| }
@@ -286,7 +266,25 @@ class TestNetHTTPS < Test::Unit::TestCase
assert_match(re_msg, ex.message)
end
-end if defined?(OpenSSL::SSL)
+ def test_ractor
+ assert_ractor(<<~RUBY, require: 'net/https')
+ expected = #{$test_net_http_data.dump}.b
+ ret = Ractor.new {
+ host = #{HOST.dump}
+ port = #{config('port')}
+ ca_cert_pem = #{CA_CERT.to_pem.dump}
+ cert_store = OpenSSL::X509::Store.new.tap { |s|
+ s.add_cert(OpenSSL::X509::Certificate.new(ca_cert_pem))
+ }
+ Net::HTTP.start(host, port, use_ssl: true, cert_store: cert_store) { |http|
+ res = http.get('/')
+ res.body
+ }
+ }.value
+ assert_equal expected, ret
+ RUBY
+ end if defined?(Ractor) && Ractor.method_defined?(:value)
+end
class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase
include TestNetHTTPUtils
@@ -300,7 +298,6 @@ class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase
CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem"))
SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key"))
SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt"))
- DHPARAMS = OpenSSL::PKey::DH.new(read_fixture("dhparams.pem"))
TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) }
CONFIG = {
@@ -310,7 +307,6 @@ class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase
'ssl_enable' => true,
'ssl_certificate' => SERVER_CERT,
'ssl_private_key' => SERVER_KEY,
- 'ssl_tmp_dh_callback' => proc { DHPARAMS },
}
def test_identity_verify_failure
@@ -326,4 +322,4 @@ class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase
re_msg = /certificate verify failed|hostname \"#{HOST_IP}\" does not match/
assert_match(re_msg, ex.message)
end
-end if defined?(OpenSSL::SSL)
+end
diff --git a/test/net/http/test_https_proxy.rb b/test/net/http/test_https_proxy.rb
index f4c6aa0b6a..237c16e64d 100644
--- a/test/net/http/test_https_proxy.rb
+++ b/test/net/http/test_https_proxy.rb
@@ -5,14 +5,10 @@ rescue LoadError
end
require 'test/unit'
+return unless defined?(OpenSSL::SSL)
+
class HTTPSProxyTest < Test::Unit::TestCase
def test_https_proxy_authentication
- begin
- OpenSSL
- rescue LoadError
- omit 'autoload problem. see [ruby-dev:45021][Bug #5786]'
- end
-
TCPServer.open("127.0.0.1", 0) {|serv|
_, port, _, _ = serv.addr
client_thread = Thread.new {
@@ -50,12 +46,6 @@ class HTTPSProxyTest < Test::Unit::TestCase
end
def test_https_proxy_ssl_connection
- begin
- OpenSSL
- rescue LoadError
- omit 'autoload problem. see [ruby-dev:45021][Bug #5786]'
- end
-
TCPServer.open("127.0.0.1", 0) {|tcpserver|
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = OpenSSL::PKey.read(read_fixture("server.key"))
@@ -91,4 +81,4 @@ class HTTPSProxyTest < Test::Unit::TestCase
assert_join_threads([client_thread, server_thread])
}
end
-end if defined?(OpenSSL)
+end
diff --git a/test/net/http/utils.rb b/test/net/http/utils.rb
index b41341d0a0..0b9e440e7c 100644
--- a/test/net/http/utils.rb
+++ b/test/net/http/utils.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'socket'
-require 'openssl'
module TestNetHTTPUtils
@@ -14,10 +13,10 @@ module TestNetHTTPUtils
@procs = {}
if @config['ssl_enable']
+ require 'openssl'
context = OpenSSL::SSL::SSLContext.new
context.cert = @config['ssl_certificate']
context.key = @config['ssl_private_key']
- context.tmp_dh_callback = @config['ssl_tmp_dh_callback']
@ssl_server = OpenSSL::SSL::SSLServer.new(@server, context)
end
@@ -71,6 +70,11 @@ module TestNetHTTPUtils
socket.write "HTTP/1.1 100 Continue\r\n\r\n"
end
+ # Set default Content-Type if not provided
+ if !headers['Content-Type'] && (method == 'POST' || method == 'PUT' || method == 'PATCH')
+ headers['Content-Type'] = 'application/octet-stream'
+ end
+
req = Request.new(method, path, headers, socket)
if @procs.key?(req.path) || @procs.key?("#{req.path}/")
proc = @procs[req.path] || @procs["#{req.path}/"]
@@ -306,16 +310,18 @@ module TestNetHTTPUtils
scheme = headers['X-Request-Scheme'] || 'http'
host = @config['host']
port = socket.addr[1]
- charset = parse_content_type(headers['Content-Type'])[1]
+ content_type = headers['Content-Type'] || 'application/octet-stream'
+ charset = parse_content_type(content_type)[1]
path = "#{scheme}://#{host}:#{port}#{path}"
path = path.encode(charset) if charset
- response = "HTTP/1.1 200 OK\r\nContent-Type: #{headers['Content-Type']}\r\nContent-Length: #{body.bytesize}\r\nX-request-uri: #{path}\r\n\r\n#{body}"
+ response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{body.bytesize}\r\nX-request-uri: #{path}\r\n\r\n#{body}"
socket.print(response)
end
def handle_patch(path, headers, socket)
body = socket.read(headers['Content-Length'].to_i)
- response = "HTTP/1.1 200 OK\r\nContent-Type: #{headers['Content-Type']}\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}"
+ content_type = headers['Content-Type'] || 'application/octet-stream'
+ response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}"
socket.print(response)
end
diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb
index e0dde3621c..faa22f1424 100644
--- a/test/objspace/test_objspace.rb
+++ b/test/objspace/test_objspace.rb
@@ -32,8 +32,8 @@ class TestObjSpace < Test::Unit::TestCase
a = "a" * GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE]
b = a.dup
c = nil
- ObjectSpace.each_object(String) {|x| break c = x if x == a and x.frozen?}
- rv_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
+ ObjectSpace.each_object(String) {|x| break c = x if a == x and x.frozen?}
+ rv_size = Integer(ObjectSpace.dump(a)[/"slot_size":(\d+)/, 1])
assert_equal([rv_size, rv_size, a.length + 1 + rv_size], [a, b, c].map {|x| ObjectSpace.memsize_of(x)})
end
@@ -54,7 +54,11 @@ class TestObjSpace < Test::Unit::TestCase
assert_operator(a, :>, b)
assert_operator(a, :>, 0)
assert_operator(b, :>, 0)
- assert_raise(TypeError) {ObjectSpace.memsize_of_all('error')}
+ assert_kind_of(Integer, ObjectSpace.memsize_of_all(Enumerable))
+ end
+
+ def test_memsize_of_all_with_wrong_type
+ assert_raise(TypeError) { ObjectSpace.memsize_of_all(Object.new) }
end
def test_count_objects_size
@@ -76,16 +80,6 @@ class TestObjSpace < Test::Unit::TestCase
assert_raise(TypeError) { ObjectSpace.count_objects_size(0) }
end
- def test_count_nodes
- res = ObjectSpace.count_nodes
- assert_not_empty(res)
- arg = {}
- ObjectSpace.count_nodes(arg)
- assert_not_empty(arg)
- bug8014 = '[ruby-core:53130] [Bug #8014]'
- assert_empty(arg.select {|k, v| !(Symbol === k && Integer === v)}, bug8014)
- end if false
-
def test_count_tdata_objects
res = ObjectSpace.count_tdata_objects
assert_not_empty(res)
@@ -143,7 +137,7 @@ class TestObjSpace < Test::Unit::TestCase
def test_reachable_objects_during_iteration
omit 'flaky on Visual Studio with: [BUG] Unnormalized Fixnum value' if /mswin/ =~ RUBY_PLATFORM
opts = %w[--disable-gem --disable=frozen-string-literal -robjspace]
- assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
+ assert_ruby_status opts, "#{<<-"begin;"}\n#{<<-'end;'}"
begin;
ObjectSpace.each_object{|o|
o.inspect
@@ -179,7 +173,7 @@ class TestObjSpace < Test::Unit::TestCase
end
def test_trace_object_allocations_stop_first
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
require "objspace"
# Make sure stopping before the tracepoints are initialized doesn't raise. See [Bug #17020]
@@ -203,8 +197,9 @@ class TestObjSpace < Test::Unit::TestCase
assert_equal(line1, ObjectSpace.allocation_sourceline(o1))
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o1))
assert_equal(c1, ObjectSpace.allocation_generation(o1))
- assert_equal(Class.name, ObjectSpace.allocation_class_path(o1))
- assert_equal(:new, ObjectSpace.allocation_method_id(o1))
+ # These assertions fail under coverage measurement: https://bugs.ruby-lang.org/issues/21298
+ #assert_equal(self.class.name, ObjectSpace.allocation_class_path(o1))
+ #assert_equal(__method__, ObjectSpace.allocation_method_id(o1))
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o2))
assert_equal(line2, ObjectSpace.allocation_sourceline(o2))
@@ -287,6 +282,33 @@ class TestObjSpace < Test::Unit::TestCase
assert true # success
end
+ def test_trace_object_allocations_with_other_tracepoint
+ # Test that ObjectSpace.trace_object_allocations isn't changed by changes
+ # to another tracepoint
+ line_tp = TracePoint.new(:line) { }
+
+ ObjectSpace.trace_object_allocations_start
+
+ obj1 = Object.new; line1 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj1)
+ assert_equal line1, ObjectSpace.allocation_sourceline(obj1)
+
+ line_tp.enable
+
+ obj2 = Object.new; line2 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj2)
+ assert_equal line2, ObjectSpace.allocation_sourceline(obj2)
+
+ line_tp.disable
+
+ obj3 = Object.new; line3 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj3)
+ assert_equal line3, ObjectSpace.allocation_sourceline(obj3)
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ ObjectSpace.trace_object_allocations_clear
+ end
+
def test_trace_object_allocations_compaction
omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
@@ -308,7 +330,7 @@ class TestObjSpace < Test::Unit::TestCase
def test_trace_object_allocations_compaction_freed_pages
omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
- assert_normal_exit(<<~RUBY)
+ assert_normal_exit(<<~RUBY, timeout: 60)
require "objspace"
objs = []
@@ -332,15 +354,29 @@ class TestObjSpace < Test::Unit::TestCase
# Ensure that the fstring is promoted to old generation
4.times { GC.start }
info = ObjectSpace.dump("foo".freeze)
- assert_match(/"wb_protected":true, "old":true/, info)
+ assert_include(info, '"wb_protected":true')
+ assert_include(info, '"age":3')
+ assert_include(info, '"old":true')
assert_match(/"fstring":true/, info)
JSON.parse(info) if defined?(JSON)
end
+ def test_dump_flag_age
+ EnvUtil.without_gc do
+ o = Object.new
+
+ assert_include(ObjectSpace.dump(o), '"age":0')
+
+ GC.start
+
+ assert_include(ObjectSpace.dump(o), '"age":1')
+ end
+ end
+
if defined?(RubyVM::Shape)
class TooComplex; end
- def test_dump_too_complex_shape
+ def test_dump_complex_shape
omit "flaky test"
RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
@@ -349,26 +385,26 @@ class TestObjSpace < Test::Unit::TestCase
tc = TooComplex.new
info = ObjectSpace.dump(tc)
- assert_not_match(/"too_complex_shape"/, info)
+ assert_not_match(/"complex_shape"/, info)
tc.instance_variable_set(:@new_ivar, 1)
info = ObjectSpace.dump(tc)
- assert_match(/"too_complex_shape":true/, info)
+ assert_match(/"complex_shape":true/, info)
if defined?(JSON)
- assert_true(JSON.parse(info)["too_complex_shape"])
+ assert_true(JSON.parse(info)["complex_shape"])
end
end
end
class NotTooComplex ; end
- def test_dump_not_too_complex_shape
+ def test_dump_not_complex_shape
tc = NotTooComplex.new
tc.instance_variable_set(:@new_ivar, 1)
info = ObjectSpace.dump(tc)
- assert_not_match(/"too_complex_shape"/, info)
+ assert_not_match(/"complex_shape"/, info)
if defined?(JSON)
- assert_nil(JSON.parse(info)["too_complex_shape"])
+ assert_nil(JSON.parse(info)["complex_shape"])
end
end
@@ -437,12 +473,12 @@ class TestObjSpace < Test::Unit::TestCase
assert_include(info, '"embedded":true')
assert_include(info, '"ivars":0')
- # Non-embed object
+ # Non-embed object (needs > 6 ivars to exceed pool 0 embed capacity)
obj = klass.new
- 5.times { |i| obj.instance_variable_set("@ivar#{i}", 0) }
+ 7.times { |i| obj.instance_variable_set("@ivar#{i}", 0) }
info = ObjectSpace.dump(obj)
assert_not_include(info, '"embedded":true')
- assert_include(info, '"ivars":5')
+ assert_include(info, '"ivars":7')
end
def test_dump_control_char
@@ -486,6 +522,20 @@ class TestObjSpace < Test::Unit::TestCase
assert_match(/"value":"foobar\h+"/, dump)
end
+ def test_dump_outputs_object_id
+ obj = Object.new
+
+ # Doesn't output object_id when it has not been seen
+ dump = ObjectSpace.dump(obj)
+ assert_not_include(dump, "\"object_id\"")
+
+ id = obj.object_id
+
+ # Outputs object_id when it has been seen
+ dump = ObjectSpace.dump(obj)
+ assert_include(dump, "\"object_id\":#{id}")
+ end
+
def test_dump_includes_imemo_type
assert_in_out_err(%w[-robjspace], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
begin;
@@ -598,7 +648,8 @@ class TestObjSpace < Test::Unit::TestCase
next if obj["type"] == "SHAPE"
assert_not_nil obj["slot_size"]
- assert_equal 0, obj["slot_size"] % (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD])
+ slot_sizes = GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times.map { |i| GC.stat_heap(i, :slot_size) }
+ assert_include slot_sizes, obj["slot_size"]
}
end
end
@@ -653,10 +704,11 @@ class TestObjSpace < Test::Unit::TestCase
end
def test_dump_includes_slot_size
- str = "TEST"
- dump = ObjectSpace.dump(str)
+ klass = Class.new
+ obj = klass.new
+ dump = ObjectSpace.dump(obj)
- assert_includes dump, "\"slot_size\":#{GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]}"
+ assert_includes dump, "\"slot_size\":#{GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]}"
end
def test_dump_reference_addresses_match_dump_all_addresses
@@ -771,6 +823,27 @@ class TestObjSpace < Test::Unit::TestCase
end
end
+ def test_dump_all_with_ractors
+ assert_ractor("#{<<-"begin;"}#{<<-'end;'}")
+ begin;
+ require "objspace"
+ require "tempfile"
+ require "json"
+ rs = 4.times.map do
+ Ractor.new do
+ Tempfile.create do |f|
+ ObjectSpace.dump_all(output: f)
+ f.close
+ File.readlines(f.path).each do |line|
+ JSON.parse(line)
+ end
+ end
+ end
+ end
+ rs.each(&:join)
+ end;
+ end
+
def test_dump_uninitialized_file
assert_in_out_err(%[-robjspace], <<-RUBY) do |(output), (error)|
puts ObjectSpace.dump(File.allocate)
@@ -949,6 +1022,27 @@ class TestObjSpace < Test::Unit::TestCase
assert_equal class_name, JSON.parse(json)["name"]
end
+ def test_dump_free_immediately
+ require '-test-/typeddata'
+
+ # Bug::TypedData has flags=0 (no FREE_IMMEDIATELY)
+ info = ObjectSpace.dump(Bug::TypedData.new)
+ assert_include(info, '"struct":"typed_data"')
+ assert_include(info, '"free_immediately":false')
+
+ # Most typed data objects have FREE_IMMEDIATELY, so the field should be absent
+ info = ObjectSpace.dump(Thread.current.group)
+ assert_include(info, '"struct":"thgroup"')
+ assert_not_include(info, '"free_immediately"')
+ end
+
+ def test_dump_include_shareable
+ omit 'Not provided by mmtk' if RUBY_DESCRIPTION.include?("+GC[mmtk]")
+
+ assert_include(ObjectSpace.dump(ENV), '"shareable":true')
+ assert_not_include(ObjectSpace.dump([]), '"shareable":true')
+ end
+
def test_utf8_method_names
name = "utf8_❨╯°□°❩╯︵┻━┻"
obj = ObjectSpace.trace_object_allocations do
diff --git a/test/objspace/test_ractor.rb b/test/objspace/test_ractor.rb
index 4901eeae2e..fb6432a827 100644
--- a/test/objspace/test_ractor.rb
+++ b/test/objspace/test_ractor.rb
@@ -5,12 +5,78 @@ class TestObjSpaceRactor < Test::Unit::TestCase
assert_ractor(<<~RUBY, require: 'objspace')
ObjectSpace.trace_object_allocations do
r = Ractor.new do
- obj = 'a' * 1024
- Ractor.yield obj
+ _obj = 'a' * 1024
end
- r.take
- r.take
+ r.join
+ end
+ RUBY
+ end
+
+ def test_undefine_finalizer
+ assert_ractor(<<~'RUBY', timeout: 20, require: 'objspace', signal: :SEGV)
+ def fin
+ ->(id) { }
+ end
+ ractors = 5.times.map do
+ Ractor.new do
+ 10_000.times do
+ o = Object.new
+ ObjectSpace.define_finalizer(o, fin)
+ ObjectSpace.undefine_finalizer(o)
+ end
+ end
+ end
+
+ ractors.each(&:join)
+ RUBY
+ end
+
+ def test_copy_finalizer
+ assert_ractor(<<~'RUBY', require: 'objspace')
+ def fin
+ ->(id) { }
+ end
+ OBJ = Object.new
+ ObjectSpace.define_finalizer(OBJ, fin)
+ OBJ.freeze
+
+ ractors = 5.times.map do
+ Ractor.new do
+ 10_000.times do
+ OBJ.clone
+ end
+ end
+ end
+
+ ractors.each(&:join)
+ RUBY
+ end
+
+ def test_trace_object_allocations_with_ractor_tracepoint
+ # Test that ObjectSpace.trace_object_allocations works globally across all Ractors
+ assert_ractor(<<~'RUBY', require: 'objspace')
+ ObjectSpace.trace_object_allocations do
+ obj1 = Object.new; line1 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj1)
+ assert_equal line1, ObjectSpace.allocation_sourceline(obj1)
+
+ r = Ractor.new {
+ obj = Object.new; line = __LINE__
+ [line, obj]
+ }
+
+ obj2 = Object.new; line2 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj2)
+ assert_equal line2, ObjectSpace.allocation_sourceline(obj2)
+
+ expected_line, ractor_obj = r.value
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(ractor_obj)
+ assert_equal expected_line, ObjectSpace.allocation_sourceline(ractor_obj)
+
+ obj3 = Object.new; line3 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj3)
+ assert_equal line3, ObjectSpace.allocation_sourceline(obj3)
end
RUBY
end
diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb
index 0679180ce9..6f08b4089c 100644
--- a/test/open-uri/test_open-uri.rb
+++ b/test/open-uri/test_open-uri.rb
@@ -80,6 +80,8 @@ class TestOpenURI < Test::Unit::TestCase
sock.print "Content-Length: 4\r\n\r\n"
sleep 1
sock.print "ab\r\n"
+ rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
+ # expected when client times out and closes the connection
ensure
sock.close
end
diff --git a/test/openssl/fixtures/pkey/dsa1024.pem b/test/openssl/fixtures/pkey/dsa1024.pem
deleted file mode 100644
index 1bf498895e..0000000000
--- a/test/openssl/fixtures/pkey/dsa1024.pem
+++ /dev/null
@@ -1,12 +0,0 @@
------BEGIN DSA PRIVATE KEY-----
-MIIBugIBAAKBgQCH9aAoXvWWThIjkA6D+nI1F9ksF9iDq594rkiGNOT9sPDOdB+n
-D+qeeeeloRlj19ymCSADPI0ZLRgkchkAEnY2RnqnhHOjVf/roGgRbW+iQDMbQ9wa
-/pvc6/fAbsu1goE1hBYjm98/sZEeXavj8tR56IXnjF1b6Nx0+sgeUKFKEQIVAMiz
-4BJUFeTtddyM4uadBM7HKLPRAoGAZdLBSYNGiij7vAjesF5mGUKTIgPd+JKuBEDx
-OaBclsgfdoyoF/TMOkIty+PVlYD+//Vl2xnoUEIRaMXHwHfm0r2xUX++oeRaSScg
-YizJdUxe5jvBuBszGPRc/mGpb9YvP0sB+FL1KmuxYmdODfCe51zl8uM/CVhouJ3w
-DjmRGscCgYAuFlfC7p+e8huCKydfcv/beftqjewiOPpQ3u5uI6KPCtCJPpDhs3+4
-IihH2cPsAlqwGF4tlibW1+/z/OZ1AZinPK3y7b2jSJASEaPeEltVzB92hcd1khk2
-jTYcmSsV4VddplOPK9czytR/GbbibxsrhhgZUbd8LPbvIgaiadJ1PgIUBnJ/5vN2
-CVArsEzlPUCbohPvZnE=
------END DSA PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/dsa256.pem b/test/openssl/fixtures/pkey/dsa256.pem
deleted file mode 100644
index d9a407f736..0000000000
--- a/test/openssl/fixtures/pkey/dsa256.pem
+++ /dev/null
@@ -1,8 +0,0 @@
------BEGIN DSA PRIVATE KEY-----
-MIH3AgEAAkEAhk2libbY2a8y2Pt21+YPYGZeW6wzaW2yfj5oiClXro9XMR7XWLkE
-9B7XxLNFCS2gmCCdMsMW1HulaHtLFQmB2wIVAM43JZrcgpu6ajZ01VkLc93gu/Ed
-AkAOhujZrrKV5CzBKutKLb0GVyVWmdC7InoNSMZEeGU72rT96IjM59YzoqmD0pGM
-3I1o4cGqg1D1DfM1rQlnN1eSAkBq6xXfEDwJ1mLNxF6q8Zm/ugFYWR5xcX/3wFiT
-b4+EjHP/DbNh9Vm5wcfnDBJ1zKvrMEf2xqngYdrV/3CiGJeKAhRvL57QvJZcQGvn
-ISNX5cMzFHRW3Q==
------END DSA PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/dsa512.pem b/test/openssl/fixtures/pkey/dsa512.pem
deleted file mode 100644
index 962c41cc67..0000000000
--- a/test/openssl/fixtures/pkey/dsa512.pem
+++ /dev/null
@@ -1,8 +0,0 @@
------BEGIN DSA PRIVATE KEY-----
-MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok
-RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D
-AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR
-S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++
-Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
-55jreJD3Se3slps=
------END DSA PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/mldsa65-1.pem b/test/openssl/fixtures/pkey/mldsa65-1.pem
new file mode 100644
index 0000000000..21f08e3ac6
--- /dev/null
+++ b/test/openssl/fixtures/pkey/mldsa65-1.pem
@@ -0,0 +1,88 @@
+-----BEGIN PRIVATE KEY-----
+MIIP/gIBADALBglghkgBZQMEAxIEgg/qMIIP5gQg6Xunp08Ia0w6d93rvBnXnlYf
+ih3Z+9IDZSRIyAGfjbQEgg/A9DPSakjm2xFsVzCHpfwcUwP5dYpJGRYwG7/eSp8b
+/lJOHPmIHjOAC8jN3xS66UXcouWozGXbmieGjLzNs1HjBaJ0CEw51wQOuPLDg8nj
+Pdesnqu5Ct1sNzqz0K57ixyEPrdPI+Vd7XDNaXfOytZ1d4+yFBC6cGpznQ9CiRYm
+PpFEgUZSg3QzFmB0hREkB4FHhTIUZlckclcxNRRTg4UFUIVTdTcThxVyJSFFInZl
+GEUnKAIEcXBUdmgwMQMhRngCFEIFBIB2BVRjEiEwI3FwAQJEEScySFh0UVdQExeB
+ZDYgIUhlFUYxh1g2V2YRdohodCVgBXYIJRJCQHFGRiI0GGQENkgBFwUGNUeAMlh0
+ZgMhIWRmhyhGVEeAUUOHVVKEZHU4BQdwMjEBIIcQeBYFZhKBdwFjBlQECCJEdoYT
+E3FXYlcECCNIZAF4cTaAQwYxBkd2YRE3FTIYRCYgF2SBUiBCJ1ImFjZSFDCIaAY1
+J1ExVDRRVzFGgiUIV1UBCDcVFmcIM4OGeDIXEyZXaCFzBBeBFQQxcFeIJWBmJCdw
+hWMUdYFiNFAmMIEyKIRYIIeIgHA3AmNTElA3gwVmBUUHERE2AAJHUhQTJQAFAXhY
+QmYDdSZHWFhHQXUkRFBWViQ4VERRF1eEN0I2VzR1dUIxg1Uid2NmcDIWdBAzITEA
+AmJgh3JlgwIREgVoIiEoMCADEGSHFIGHUnJVU2I0CGRxaAR3JUVgAnJQOAQiZ2Vh
+OFM3MIEHUmFzQhcEdzSBhBcYVCgEKCIiWHUBhCVxhkBjRzUzJAhDQ2F3eGdUNTEj
+QQcjY3E3BnR2Q4cWIjJohyd2hCUzOIgSUodmIxY1AmaDFVBQEghncQQYQ2QIcjiB
+ZWNQZGZFhHiBIQcXURQQAVg2RBQQd0aDgGQXE3Q4eHY1iERUVoUTcQIjcmh0UVci
+Rhd4MRI0ZUVHNXEAIDQ2cVIGZYMiB1ZQQSYVMlZBQiAwQHJYCEUBExAjF4QwQBMT
+cjdGVCckJTBFJAcXREMzAlYmhRJXgEVSESFYAAJwFXRyKBZCQwcDIFQkJWUFB4gV
+F2GBiEcVEUAmcWEFREA4dEFDQwhFUQUIcHQRMRGHFTA2g0YmZxIiMjMjAQFgcHYF
+IgJlFyA4RhdRgWY1FVEhM4VoUhiIQkcIeCCFZIESZjVWFjNWQEZTNGUoBiR0cgFn
+hDNIAhIRdXQgaGBxNUdyBUIWAniAZIOHYTIyUYd0YXExBjEShGA1GBQ3FBZVEmEw
+EhhEJQNwRjUyQnSIAVNlQjKDdIEBQQhgdSdxc2RjEmRiFyY2RRYicjQCh3AhNSZo
+QzRlGDaGMzCIhRcjIocxN4cwM2gRA2WIN0NyaHR1NRVRAXIkFTR0J2NCCCEHKHYn
+FSYGF3QXBwhDYUZEJmdHEWAlBCIyVRFBR2NSNgJwZgBoAgMjcYQRNAFjI2ZVVgEE
+V3Y1eIREhFc1ABVEGGcUNGRnWAJYOAdlZmgWQlZncWJVNFNkBwOGRiAmJGQGNlIx
+NwNwZ3WBaGIBdiNlMEVXFwFkZRRCE3ZwFxgQWFKHVxOBdxJHJBhSF0d4IlAgIRUY
+UXdCWIQIKDGAhAIoVBAhdXU3AHKCiCBkF4KEhhRyU2JTIIdwdGYSCDcXIWM4QYIY
+NoCFNSESeBMnciUkMxV1RYNzYUSGggMIgWVCM3aAMRcCITMgKERIEjgiUkIlNoUT
+JkMjMzc3dxV1WGOCZRJSMzIhGIAgBEgicDM2CFclACBlBoYyhyNgJoOIExIoY3Qh
+EFRQhFYBZoJERhNoQCIUaAIARgiCQSUXYkUgR4RnYAczh0ECOCeGdBJXExNohzZH
+GFMAE2MAUUZ3NzF2FldoGCKEIzJzGDFHd3KAU4ZHUichAYUmEYVAdShYFlh3FIIl
+UzQYdScUdlAAglVjBiNCJYGBKHVTNEJBMBAQEFQhJSI0diJ0dFA3KBIAg2RxcXIA
+cWMBcoIEQBgAUBUyEDUwcQYTVwSCMjBCVEQiQWMThDhVE2YWZBNQhVZzY4cBhIZh
+IIBBQihmh4VgJjZkJ2J1VlAB5kLlGDaGXIOc++2QqMCGeB9FnTYpHFoSXQrOjQhS
+tfTln0rEelihhKhi3Bu8mdhyTSFZTShsQidqlN1/U50KnMTqII7r9QltUZqPH9sW
+CswVssxnVe1GAXY/LqJPN5DEN2ZEMoAgmxLbGYB5YdKID1lj5zquaCqpDUGDI/Wi
+zJ5xpFzn7nGJwedU2MBqcqlIVJg8VeIInkLL/v3y2uqD4+pewW8OewqosJOfBgjI
+RH1FXcdGbnqKJk1YZ8iwVMTNoU9U8gGDI5kk/dWWqqAdxaVrsmevmNRp6wtibFG6
+FxrSRb7hOP8IVv7TkMA+Cv4MRs0UhYJ2W8x0G0LxP4M+m3cAJkaHyHDda1NHjfTV
+zG9hWK8Ad7t+F9hw5++KBPlkW+/sX4eYpOlC/XjpMp1W6WIr9oIbRp6RXKNUuBXQ
+58uNAmq6peDenbwsmiBKG+RWntbMxtjOM9bo/JXMV9dIT/KIbljl2C/4TRbWy0D3
+KfZlvAHpiw2oH/vaLUFbIg7sK823keZA/uSFJ2KSPBVC6+AYX5tM/P/KKLJmFoVY
+U7h4F/SDCbOt5PJu9yg+fN6ftBT3a2723TAx7M8+WqPrvvOB5UFJRNCcpwnjqriz
+8ENLgoze5wm2sIk+QvB15tFG0n3+9eTOjD+q0dJDSxq5xAuAalBoFp7vSt2x1UO/
+4Nf/jXvJT2nXjR7QgtabQRzKqbP5lHVtL0BCJeGFlbGeuAGIfNuVY0809E66sWDo
+S18hNAfp9jKe0aU7MxGU6RvCB8vLK+cld/RzujyK8C307PJdzwCLEYIBMC3SvBcQ
+9CpJFuPIcEVoM1RiThw/l1MAaKJ3y73ekU5p+Dd2CN4P4pCDSiVj/PAOW1c7iA2A
+QBVuCfPMYJyW93toHaqpaZuD9VN3OKbtJvuMWCOIN59ERFvttv5CNQ01rhgCv3dZ
+kkkFrJsmFcwsgMW1JIGozMKywFzi9yDWUL6j/ZCc8xqkfP9fYPBBTcSsUvWV9Zq6
+AU22B9j6/EUP8crw0VViacbEJy2sJgIumEQiVlVNavorpPwjtWpVQFvsBrDm6X80
+jk9H/yTKrrR6LaTH7999s/88jOLszmbX7Yt8VmMkkliml2rd6UqG9D6zq2xEj1IV
+6ZT2zhVe+wHNmpkr1kYTIVsLXrHNpCWEQeHscSCzz/lg+aOv8kSfFqGq2VFjxnts
+7Z88TjxzIOQk14Lzkgl0PCyHXau8i2bteCOimqRYEd3ihNcC8U9MXLYrOiv24oXM
+RpkzoHGOtZoAie6k1Xj6aDwIl2mTBHg5BF0A4U+d/z7wS8Gr9nEc574s9OyKAZn6
+5L/1GgpWa0e2buxn8fkPAMptY0773prqKqwvV/SWdvUJ4B4HLNLsU70+N4XAZlRS
+7saNkghBkrD/WobJQwa/9OWWa5Gw6Frurr0AmnBU+EN7u6niFwARsa9f1yjuW8IJ
+tLD7H+Yu2bGouHWpeoXQHwqFxl+me7rQ/ePvOYQk/SzlzvroaqAGECrDoHU3kzhn
+rhJLueA9b0j3u0/+CQaNOFPWb6GAjmafVWpBcXtOSkHVUXitclURlITEwe47tn+g
+XffSw3k1q3XBKkFkJQrgPa2IbpAWvFKA7rOInY/b8N/lCI0bZAei2OOR2/MLifkx
+F3L8daWXslp7QSlIjUXwtgdD6CwQsEui99dZvTYlSxzUKC9nsF0oPYxWpHAcuoCE
+pQCR1CuyuGkDCaod2VNWqWOcZ5QXjEtbVHFO8qJdePJPKWV+0YcltaR4X5q2Pts9
+4a0SJMSM/tXrUi9g9RjjnB+F++rc4a5FrQ2r7FDXudk7NUEoJPyBvBDeowiSmXvv
+SHrL6WsQgf8n5sZxfA0uqs+8OMSLLNj72CSoBQMJNVJgYQkSyBuHl6Zk59+k/WeJ
+wX1qevXwaC6JrdF+naRcp16tNv+7230GPO1d3+X3zZOtAEuAzk6kw3da8Y15qZ5j
+FqzXPO8TsURyOf4Fp+kxpETSQ+mf8Do0hWzUYE8Cj2EFcwuE2Q7+c1ZHAFpQNk1j
+T4vR//yCYjO8/lY0yDV7iDzkT36twyvKZ/cMxC001RSNmtr3QNWWkRRDBWCSwnjW
++cn408gCVFPwVUOBwUr6aOeUY+fCcvWnYPCDj7ggdS5wEoUk+xrk4v2kU2gAH/mp
+DqhFNouIcExoNW5j7j0w0YKnZtZJ9pviiM0EXS6vhk4ayxI2pi3VOqL2RhoNleAa
+bTcCQ71wOxqpp4khssLcOsUR8trpadlvZJ9sc1ksUfoOz/pMI9Yj0IWctbuiriJp
+l193X2sPzVMn3MaEt+XPrsX5wOogbQAfSJyY8pfCnZuhVLoZDpJADJxou0EhP+wH
+p9yZc5GZosFgDJvTEhZfUmituLW4+op1FLJqA/LQSxBVz51OnmtzpgJLyR0ctTLG
+9CcYbFTrzltPlOTHVjVW4rD9jyoLjLdfUf9qG65qVpGBisV+wD+SI6P0x5rhN7Dt
+nC0YNZZ0cYyN24xw8Bxzcc9RkY8/MFfbTXOG43Uuh7fkPIdY2NQSUK2tkfiMdPgu
+zlR1HoZHBrCcsQXJH0OhbuJ6Uwzm340Upj4b/eykq+uUcVY8PAUHSg6mwKy+E4yp
+Za5Z5U50Kv9rFcE9Hwh09fGfdUrKTCFxoKrqfeW+ogTXJHQR5A41r9PP1l7/9Bp7
+P+UtdjJtAHzTO1r7/dckvghBslqhNBzA55wtWEmjMFh4Mm3lBMvBGrCelKPtaOrb
+CYlv4eqGZMEeE3VoEKO3QnXU/dqJvhwQhjCcgxPtOzm9eSrofTvXa4xIMKyuNF2z
+F6K0S5o3I+pBUInshXHWwN1pAT1R4FRYAUTv9mZbhLP+MWgPIMrdWWHAMDL5DeBH
+G4AT5RQbzIHjQ12fJq30m1LajjLlL+mF5og+plMgEGOCJMHZyT2NcNb7gFHWk2mh
+JmO/qxdXQ1FQ/oEf+gNmfgdlw/N6TY7PvmkVfdkhgp/zQLcGgJ33gj0gy4Jr284G
+EhmeOGQflVsMFDqrAgjCEEJSLl/+FXuDfJjTixyly/yTTJCAeiEXsSW4xDisYZyR
+dmEXPtx7eyelJjbsM2yMTNacvCA8TCywTqxYMlYF45kHhTrnQoMvx83U0vqB+ALA
+JsGGrYQZ3tx9j8ae27b0rkSrccFYhKCXI/mwEZcZ6SG3q6/PhHWQOaie2EkuVLDq
+YAK0ZjlTv0znE1OVN3ovKAqq8ga/y5tOKXREo/i/SRPj4aHel4Lky26+Nmm+t+E2
+CL3SBcqhBC45qIB27kdsqBsnCfSzm1fQsy6jivCEDneLTLNoltDyXunSwyLP/7HI
+qclQDtLzvC0mHUNlhcds4I20
+-----END PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/mldsa65-2.pem b/test/openssl/fixtures/pkey/mldsa65-2.pem
new file mode 100644
index 0000000000..0ae64c2c5d
--- /dev/null
+++ b/test/openssl/fixtures/pkey/mldsa65-2.pem
@@ -0,0 +1,88 @@
+-----BEGIN PRIVATE KEY-----
+MIIP/gIBADALBglghkgBZQMEAxIEgg/qMIIP5gQgDdLfrcKpbcx2qbjvcE+SqUnW
++y7uWok/WM51jtrhQGIEgg/Az2AAnia3lgXsNEHx5NtoKaslKSsMPpvhRGFlcTcT
+ZFF3hKrybqvpUxtqF8nqPTy0geEN/k/k6rYHDIcaBfE5J65Xn8dwRbUSzpjSJVD7
+aBv1qprz1pAAXMYcazKeqWCJxyy7u9opGuNMaJ7SHcqwQ1kZ4nWaxEua9JnXZ6aJ
+zGkVg3WFgnBFVFJjNwFRGGNDNjNVAUUgURZgiFhARkBIIIVFZkJ3UlaAFzBodRVj
+OGhnUHAyQkc0ZRQYVTUDAQcHNhVTInEyZ0hngkBRcmKFdxgjVShoRmAyRYRTMAFi
+KARRInZVhCMHhCQzcDh4hAd2V4UoVVGIIHclWFZXJVCDUXeFZ1YnIgcjgiJnU0BI
+EoFgEnZyF2OEUSUlJSNicSUChwFGJCglhnBCMUZWdAeACCVEVxVFYTRFQ3AyhkNo
+cwFSBAd0BGVBBlRyhVYQVyNHVSJmYhNhBXaFMIUTEEJYYFhxhWN0IQNYQQdHMHMC
+FTM0cUVGcnBlCAZSYQM4Unh0NTCBIlAHEYMmYzUmQ1cIBIKENGYlKAYnMSYwBFAy
+IiMWVBMQeAJQVRJBh2A1ImB3cYVgd2QhJSUVg3VYd0VxMmQhIhB4YQJTIoFiBkcj
+QgIBVEQIgXaABoNAOGUkcUdjUEB2SGiFKCRBYTZAISJRFnaEJmMoRndWBhCDBIJQ
+EXFDYIAieBYnQYIwY2J3hoVRMTYGaDEmgSMxFidoETEjFCgnUYCFQhB4NWhmVAZw
+M0gCEDAWCIcQgXQjgxNkFVZCKCOEiBIFFFA3NBQVZiSCYWRnYXASRISGQTUWAUA4
+IYMhckd2UhiDQYE3RxdAAnEIODYAUUhnMABIERdYFSQkcSYFAyIBASUkFAQEdTJA
+IxZABjhyBjF1Q4UWNEMgFBY4NSdjRxCFhyg0FVIAI1FWVhUlgRBHEGgYKEcIEoEl
+cIRzRWE0Z0g2BzQ2FxUGiEchJ1ZzUBY1d0EQQjd2AwY3KHhDQ1IVAUcIhDgWYIZI
+WFN3BEhTgBciRhNTGGUARoeBMXExNAIwcRYnBRgohUM4gYSARhCIMkMDJVhIgoMS
+R2BRKDFHh2JgGGUyMlIUKCR1ImJFgCY0gFgABgUYBXU2BDRGCHaGiCgTIDIyBFh4
+RmFzUBBCE2FUJWYAY4ZjSAg1BWWFAFQ1hkBjAnMiByEWVIVgN2MXYmBUU2BhYkUh
+OBNEWEBChUSDVgYEZiJ1cjUmNDIGQghgZEUIcUdwZVYjRzgBeHA1gRSGVXYhgmaB
+NVZ3UTgQUSF1NldYc1aDRgdGMVYUdwgQVThTVEJyN2IFUwdgUTZoJhQDVIRzEEdI
+E2YFd2EhESIVMSVFdhRHYAAFBShCEoAXQScWdEdSiGIydUNiZzhmJzY2ZlMTOCYH
+UzcVZxYUiHAxhDgmcWcHZjAFdBSHVhMyhiIyCCcwdgcACGOHRiNlAmAFAyFVVRcW
+FQQhNwIFYUBxNxKGVlQzF4IBEyAYFDF3BzMlUjJyYIMTFDAUFWRDhnWGdABYQRRQ
+h0VTAIgWhnd1FEhng4hlUwMiY2c4hzczM2BzgxRoZRRVRkF1VngWFFEogkEEQAWA
+J3R3A3IBhmQVaDVGKCJkMlc1GAFwIXQHF4h1RkJVEiJRRQB2ACGEgDFBBDEIWBEx
+VQBBRoFjMzKEKCZYcnRWYVh1M0N4GEMmRIInExYFNUMgExZRQ1h4h3NwdGYEJDJR
+ECiCYjQ0Z2ZGIGIwZCZGAFYjaEIzVFIQGEYUBHOBcTJiQSdCF3dCUQEBhgJBRyZU
+JlZxMHKHYHh2E0NwCIdCM3gHBjFWYhJoghJIBCgHI1hWJTRiF2eBcRBRNgISBYZx
+YzYTdoM4YGJWVBBYFzWBAUOHcVaAFERQEWEHExYyZIN4BjhEVQFIgyRAcGY3hiRI
+QkiDckYRgRQQISUQFDR0VxZyZ0hiIgQhOIhgiGdnIlaEJgQARjgxgWBzAgJ2FCAz
+NmgwVlGGdUcxYhZjVCQXAkGIMoJAJwFFBWMGhzExGCA1ZzdIRhMwQiaGN4AlMzNW
+RyFAA3UzYCBSNXOBMUEgRUY0E2wwl7ncBzQaQCHLGi4fo4iNcjppgeVmwIkz3lQc
+B1+enw6xro9Jj41TDjetf9GWcQeGt7rWjs2Q4b1R1IzuRhVgw7PuvtM1PzoP1Wfj
+sT1ugBTv5FzYo1zBx6L1hAYrB1ZR8EY/0qp3JhMeMDNry3kxoWxBk6NRXCl550nV
+DjxIXzzQYBOtvEUdRjX0jKFRtp5r+usf5BY+HjkFPlCxUNZ9U1EuWNZenAG47Q1a
+xL6aiyTUOsmyzXmIRXA3lQVurP1GqH5beYWgt6g+veH9MZm7HqC2iAqrQvTWrfQg
+Vh9h6K2VgK7rA7/SUHJS++3l3YqRV+0CL05OSa3+55ZheCQyinGcHqOR4DiaO4tl
+XWlYis6KU7ZkQPwEejPibzrK6OvCBHOYzBmZXu6Q3h57TzCAZoS7Uinm1VHVFqIw
+eid4VR9QHv14bj2pj8lz+bSEeP3/quUci+TF9A/CNzrTdv7eYxlS1EGd40PQMA6F
+p/ZHenXBp9vp8xCFPO6N4BSpLTm4HATPv0IfSJvalmj8YGMx9baCkuv1zInD/nij
+XjAOpuxXuLo4Odnsyh8nG3ApojSwPew6Nw0/gIkaaBAEHatk36bif14ZfpjdtMSu
+ZqqRe28YAX5Cc+CGgwmiMVp1wcfnsREStQljA51Wgh9BMDj864vIxDRwoDvnGUP0
+2us2kCc1YWOYO0fhXnQwLv0SfhMSueXSWI+TtavZ7XEpoxLR59KfQXNr54lD2LVi
+SAIfnugQFY6QWQJy9tqj/EQVRVAz7DtsKYhuaXXXUob1WjRpW1GA0Haz+fflunto
+PVI8jfubex+6clxGXhc8wef3P6mZI//C4qdhU1DL+Lfa+nUvzq2RbsrpZh6wB0RU
+a608XykbdkVPR56khUm+a+p4jMQrp2YCmiPj68CvZz49voij9v4sFhUPQ8NuqoRy
+VYRGSSiAccpoDocgn4mcScbj5WgxgDHxhYt2N2FeO37//2AiXNzOhZVSpjNe8OIs
+F5msuuf6lYDFQj8yy5wYkR9VLUMajV54E0vdo1ns3MCZzGUuibHRlkPrAOXMsHrY
+80le9gLR6QUtfT/2C3PPT6v6psdI86FvQbjly24skPaG9Vm0HGOc3ImuQu5m6CbQ
+37Nb8uIMSRG7QBxhVAXAwxXs6KiWZ8TxyS4zn7INRNyBHm1cgZPSxxXfPZIwosjv
+G0cMqD/CCtxriy0HkJjwtCbUYLc0nU+nMEuMCylWvf6w7hw0SkB/Sa9XvPTiMr3m
+B3qxGr69du5HKLFap9HhlYzgOOuPQrzQxAkIIQ2bCxsIPmTuDXJPvCBB84/I5RyL
+dG7j3LsOg4JGjp0+1wQE7+dynxIFru/DCfNSl5En0ON/Pmu4rteTd3X78Wovr6cV
+8bTp+AQ4ZuSk4bT0S9OtC3hAM8WUjQDZplmJkB8tzo6T5fRU9YX0MFAjUAh7MYAe
+a6+Up3mxWmYeo+c4msn7aRfvi4lplMoSKHs+rMgN93ExN/M+IUgTUsjlVQselOzV
+SKlUIQ5oXI+f/Sdtt8PKU/ltSFrAvNwowbarIOjClBPeNrNhYgascjzZ6vBbDYkU
+zaWo97QIvsW5Fj63uYNdeKEfGc+Pf6/IkiVBf5kKGUvHjmBXpgmf02FkDRF065pL
+Bx8UDeaHpzAcLBMDbUVejokoEtqDZjlkOljhUeiau99YGC3u3vQia8e64Z0IliaH
+FXXH/8l2FR3ZnYWnLBL4YBSmvwOvosB0iQhKaIb05oxzn6xmgotVhTT5IDqE3PX1
+OfpnFIoNe2IPvobu4z1Q5X2YjA8AvgXOsmH71Js4Ihy2DvGXSESVfF6EB9TqpmHC
+YPIhPORi2g7El4iZrbSvAqOoGILx+8HwBTm8rg4zuqCg44J/nEn7vgjA0rzmQ5mm
+uex3OEpi2RosZ4RecwFrbnkDtN8geeiaZGovGjGtLlBwd09VRji0DFy/l8fUqOqi
+NttZxiGLRL8SsOntUzbkNKcH3GnuN2FUOVD/xMiqOl8IvS6EO81CpPq0c8JxHWQ0
+ew2QsV8EIJLHpjZRNE4wp/3r4Z9U3ziV7ip8N64ScNj0YedJEvRgLM6hracg7Tey
+bOXhJ8FLh1/cGbeGD0tlbwg8YNZeXkA7YYDmlpWw57jZwYoyx7HqgziF0qqLIPIO
+s9CwjtAb26B0trIJHjkpv0EQHtRsOJuaQIXDcj/A8QVSb9F58cVALYb6NVCADdl5
+fytsoVJst8UQRT+AL5i61ZIVFG9URvoDFCJOYfp5WMF26lb6R0OVw0fuo5XHAcql
+T+gPyx5SaU8Klo/2k5jIm4+JQ74/d4srfcqnVPX/5ueIxtvJx+2q+MuhcOPelwmx
+BjPNvzLRcNFQU8/6meFFFVclBfIPDzOspxTOKu6DvcrkMLFiX1Vl40x/FpVMdRFj
+M5DmJR7NoFhoOguGin1cOdpvUmPxGEzKEJfrQqQ5CX4Kj4lHIFjHeYR1nLTagAyA
+rGv029l5af0CcPmQgl4NlTWh5eRtsFs16YDRkz+1xdQwJnpU9Qs91f6ckScGRHaM
+pbpJPoCGPT+kNvHbWrziupkaFYRTy3kHlhkZ7aqqq5phtDVk89LmS355V46t6CoL
+clkAwCWPBqUtJ4Dd0G1Qo6v/3wv63GkrezzxMjvINrJ6rVNqQBgLN5fiHzxYNRjD
+UB9BRLJq7updAnEFSNLqifwDwIHD75EtQEOfWoGBE/beMU4vecnzh7Q3aM3YE7zJ
+sZdtSoLzIycXZczICi8kNRZ/yXUS/mswUaUTNwANWGvYpXXsGRjdg8rCG7bMdBzX
+op9qNMFoxbZjg1NnEvlY33gMgHi0hTVeVd3WvFLrOSnV16dIb6wvgIjW90L/dqXx
+iKHKlXtaq01N1vpFBQnUhjL+ZvKh2rQpQpyVB4HBdQeSWC16/tYA8EPUZsISZAM2
+nYdbxHlIooPFz/Ali+g0B1JD0wFs/GljQloKVmBG1otF0FMBRvFlLUflcI4VSsDk
+odpZfBwFPq3K3qN6SAgtpKRzaJH7YDZn6XYIcekoAHP0rsqUH34eeHJ3Rv2U2ml0
+lzi+Ydsv+/REFoiLYNL8Gqa6WpVcZ1qCUse6ORnHCWd2Vxf4d8YDvcLwPwEqcB6u
+ewhC3Gme5ydV4hwTv9SzHOJ2ohTI9J4FpdEmGxOuB9HROQ3c8qPm6PDh5fXwOItz
+qaD8y6RLZUaPMXgwbAI/Gr5EFLAGp6CXfLrKE9yD3yLAop4DZ6GPPoHqOc+hKkgY
+edxUwijnQ25mtgrrJzvUWOpO0hi+CNdcMQMXXU2phyibN8h/JoejzIm9HXk2EvV9
+qX4cmZjkE9fKw15cRjvQt1K1
+-----END PRIVATE KEY-----
diff --git a/test/openssl/fixtures/pkey/rsa1024.pem b/test/openssl/fixtures/pkey/rsa1024.pem
deleted file mode 100644
index 464de074be..0000000000
--- a/test/openssl/fixtures/pkey/rsa1024.pem
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx
-aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/
-Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB
-AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0
-maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T
-gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572
-74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE
-JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX
-sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII
-8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA
-wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi
-qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD
-dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA==
------END RSA PRIVATE KEY-----
diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb
index 354b587895..0293813a8d 100644
--- a/test/openssl/test_asn1.rb
+++ b/test/openssl/test_asn1.rb
@@ -6,7 +6,7 @@ if defined?(OpenSSL)
class OpenSSL::TestASN1 < OpenSSL::TestCase
def test_decode_x509_certificate
subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA")
- key = Fixtures.pkey("rsa1024")
+ key = Fixtures.pkey("rsa-1")
now = Time.at(Time.now.to_i) # suppress usec
s = 0xdeadbeafdeadbeafdeadbeafdeadbeaf
exts = [
@@ -306,7 +306,11 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase
end
def test_object_identifier
- encode_decode_test B(%w{ 06 01 00 }), OpenSSL::ASN1::ObjectId.new("0.0".b)
+ obj = encode_decode_test B(%w{ 06 01 00 }), OpenSSL::ASN1::ObjectId.new("0.0".b)
+ assert_equal "0.0", obj.oid
+ assert_nil obj.sn
+ assert_nil obj.ln
+ assert_equal obj.oid, obj.value
encode_decode_test B(%w{ 06 01 28 }), OpenSSL::ASN1::ObjectId.new("1.0".b)
encode_decode_test B(%w{ 06 03 88 37 03 }), OpenSSL::ASN1::ObjectId.new("2.999.3".b)
encode_decode_test B(%w{ 06 05 2A 22 83 BB 55 }), OpenSSL::ASN1::ObjectId.new("1.2.34.56789".b)
@@ -314,6 +318,7 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase
assert_equal "2.16.840.1.101.3.4.2.1", obj.oid
assert_equal "SHA256", obj.sn
assert_equal "sha256", obj.ln
+ assert_equal obj.sn, obj.value
assert_raise(OpenSSL::ASN1::ASN1Error) {
OpenSSL::ASN1.decode(B(%w{ 06 00 }))
}
@@ -389,6 +394,11 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase
])
expected.indefinite_length = true
encode_test B(%w{ 30 80 04 01 00 00 00 }), expected
+
+ # Missing EOC at the end of contents octets
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 30 80 01 01 FF }))
+ }
end
def test_set
@@ -406,24 +416,38 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase
def test_utctime
encode_decode_test B(%w{ 17 0D }) + "160908234339Z".b,
OpenSSL::ASN1::UTCTime.new(Time.utc(2016, 9, 8, 23, 43, 39))
- begin
- # possible range of UTCTime is 1969-2068 currently
- encode_decode_test B(%w{ 17 0D }) + "690908234339Z".b,
- OpenSSL::ASN1::UTCTime.new(Time.utc(1969, 9, 8, 23, 43, 39))
- rescue OpenSSL::ASN1::ASN1Error
- pend "No negative time_t support?"
- end
- # not implemented
+
+ # 1950-2049 range is assumed to match RFC 5280's expectation
+ encode_decode_test B(%w{ 17 0D }) + "490908234339Z".b,
+ OpenSSL::ASN1::UTCTime.new(Time.utc(2049, 9, 8, 23, 43, 39))
+ encode_decode_test B(%w{ 17 0D }) + "500908234339Z".b,
+ OpenSSL::ASN1::UTCTime.new(Time.utc(1950, 9, 8, 23, 43, 39))
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1::UTCTime.new(Time.new(2049, 12, 31, 23, 0, 0, "-04:00")).to_der
+ }
+
+ # UTC offset (BER): ASN1_TIME_to_tm() may or may not support it
# decode_test B(%w{ 17 11 }) + "500908234339+0930".b,
# OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 39, "+09:30"))
# decode_test B(%w{ 17 0F }) + "5009082343-0930".b,
# OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 0, "-09:30"))
- # assert_raise(OpenSSL::ASN1::ASN1Error) {
- # OpenSSL::ASN1.decode(B(%w{ 17 0C }) + "500908234339".b)
- # }
- # assert_raise(OpenSSL::ASN1::ASN1Error) {
- # OpenSSL::ASN1.decode(B(%w{ 17 0D }) + "500908234339Y".b)
- # }
+
+ # Seconds is omitted (BER)
+ # decode_test B(%w{ 18 0D }) + "201612081934Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 0))
+
+ # Fractional seconds is not allowed in UTCTime
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 17 0F }) + "160908234339.5Z".b)
+ }
+
+ # Missing "Z"
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 17 0C }) + "500908234339".b)
+ }
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 17 0D }) + "500908234339Y".b)
+ }
end
def test_generalizedtime
@@ -431,24 +455,46 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase
OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 29))
encode_decode_test B(%w{ 18 0F }) + "99990908234339Z".b,
OpenSSL::ASN1::GeneralizedTime.new(Time.utc(9999, 9, 8, 23, 43, 39))
- # not implemented
+
+ # Fractional seconds (DER). Not supported by ASN1_TIME_to_tm()
+ # because struct tm cannot store it.
+ # encode_decode_test B(%w{ 18 11 }) + "20161208193439.5Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 39.5))
+
+ # UTC offset (BER): ASN1_TIME_to_tm() may or may not support it
# decode_test B(%w{ 18 13 }) + "20161208193439+0930".b,
# OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 39, "+09:30"))
# decode_test B(%w{ 18 11 }) + "201612081934-0930".b,
# OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 0, "-09:30"))
# decode_test B(%w{ 18 11 }) + "201612081934-09".b,
# OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 0, "-09:00"))
+
+ # Minutes and seconds are omitted (BER)
+ # decode_test B(%w{ 18 0B }) + "2016120819Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 0, 0))
+ # Fractional hours (BER)
# decode_test B(%w{ 18 0D }) + "2016120819.5Z".b,
# OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0))
+ # Fractional hours with "," as the decimal separator (BER)
# decode_test B(%w{ 18 0D }) + "2016120819,5Z".b,
# OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0))
+
+ # Seconds is omitted (BER)
+ # decode_test B(%w{ 18 0D }) + "201612081934Z".b,
+ # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 0))
+ # Fractional minutes (BER)
# decode_test B(%w{ 18 0F }) + "201612081934.5Z".b,
# OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 30))
- # decode_test B(%w{ 18 11 }) + "20161208193439.5Z".b,
- # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 39.5))
- # assert_raise(OpenSSL::ASN1::ASN1Error) {
- # OpenSSL::ASN1.decode(B(%w{ 18 0D }) + "201612081934Y".b)
- # }
+
+ # Missing "Z"
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1.decode(B(%w{ 18 0F }) + "20161208193429Y".b)
+ }
+
+ # Encoding year out of range
+ assert_raise(OpenSSL::ASN1::ASN1Error) {
+ OpenSSL::ASN1::GeneralizedTime.new(Time.utc(10000, 9, 8, 23, 43, 39)).to_der
+ }
end
def test_basic_asn1data
@@ -458,7 +504,7 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase
encode_decode_test B(%w{ 81 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :CONTEXT_SPECIFIC)
encode_decode_test B(%w{ C1 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :PRIVATE)
encode_decode_test B(%w{ 1F 20 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 32, :UNIVERSAL)
- encode_decode_test B(%w{ 1F C0 20 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 8224, :UNIVERSAL)
+ encode_decode_test B(%w{ 9F C0 20 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 8224, :CONTEXT_SPECIFIC)
encode_decode_test B(%w{ 41 02 AB CD }), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 1, :APPLICATION)
encode_decode_test B(%w{ 41 81 80 } + %w{ AB CD } * 64), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD } * 64), 1, :APPLICATION)
encode_decode_test B(%w{ 41 82 01 00 } + %w{ AB CD } * 128), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD } * 128), 1, :APPLICATION)
@@ -650,6 +696,20 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase
assert_equal 17, ret[0][6]
end
+ def test_decode_constructed_deeply_nested
+ bool = OpenSSL::ASN1::Boolean.new(true)
+ nested_100 = B(%w{ 30 80 }) * 100 + bool.to_der + B(%w{ 00 00 }) * 100
+ decoded = OpenSSL::ASN1.decode(nested_100)
+ assert_equal(nested_100, decoded.to_der)
+ content = 100.times.inject(decoded) { |a,| a.value[0] }
+ assert_kind_of(OpenSSL::ASN1::Boolean, content)
+
+ nested_500 = B(%w{ 30 80 }) * 500 + bool.to_der + B(%w{ 00 00 }) * 500
+ assert_raise_with_message(OpenSSL::ASN1::ASN1Error, /nesting depth/) {
+ OpenSSL::ASN1.decode(nested_500)
+ }
+ end
+
def test_constructive_each
data = [OpenSSL::ASN1::Integer.new(0), OpenSSL::ASN1::Integer.new(1)]
seq = OpenSSL::ASN1::Sequence.new data
diff --git a/test/openssl/test_bn.rb b/test/openssl/test_bn.rb
index 1217f250a7..f663102d45 100644
--- a/test/openssl/test_bn.rb
+++ b/test/openssl/test_bn.rb
@@ -321,6 +321,8 @@ class OpenSSL::TestBN < OpenSSL::TestCase
end
def test_get_flags_and_set_flags
+ return if aws_lc? # AWS-LC does not support BN::CONSTTIME.
+
e = OpenSSL::BN.new(999)
assert_equal(0, e.get_flags(OpenSSL::BN::CONSTTIME))
@@ -343,28 +345,38 @@ class OpenSSL::TestBN < OpenSSL::TestCase
assert_equal(4, e.get_flags(OpenSSL::BN::CONSTTIME))
end
- if respond_to?(:ractor)
+ if defined?(Ractor) && respond_to?(:ractor)
+ unless Ractor.method_defined?(:value) # Ruby 3.4 or earlier
+ using Module.new {
+ refine Ractor do
+ alias value take
+ end
+ }
+ end
+
ractor
def test_ractor
- assert_equal(@e1, Ractor.new { OpenSSL::BN.new("999") }.take)
- assert_equal(@e3, Ractor.new { OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2) }.take)
- assert_equal("999", Ractor.new(@e1) { |e1| e1.to_s }.take)
- assert_equal("07FFFFFFFFFFFFFFFFFFFFFFFFFF", Ractor.new(@e3) { |e3| e3.to_s(16) }.take)
- assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.take)
- assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.take)
- assert_equal(false, Ractor.new { 1.to_bn.zero? }.take)
- assert_equal(true, Ractor.new { 1.to_bn.one? }.take)
- assert_equal(true, Ractor.new(@e2) { _1.negative? }.take)
- assert_equal("-03E7", Ractor.new(@e2) { _1.to_s(16) }.take)
- assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.take)
- assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.take)
- assert_equal(true, Ractor.new { 0.to_bn.zero? }.take)
- assert_equal(true, Ractor.new { 1.to_bn.one? }.take )
- assert_equal(false,Ractor.new { 2.to_bn.odd? }.take)
- assert_equal(true, Ractor.new(@e2) { _1.negative? }.take)
- assert_include(128..255, Ractor.new { OpenSSL::BN.rand(8)}.take)
- assert_include(0...2**32, Ractor.new { OpenSSL::BN.generate_prime(32) }.take)
- assert_equal(0, Ractor.new { OpenSSL::BN.new(999).get_flags(OpenSSL::BN::CONSTTIME) }.take)
+ assert_equal(@e1, Ractor.new { OpenSSL::BN.new("999") }.value)
+ assert_equal(@e3, Ractor.new { OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2) }.value)
+ assert_equal("999", Ractor.new(@e1) { |e1| e1.to_s }.value)
+ assert_equal("07FFFFFFFFFFFFFFFFFFFFFFFFFF", Ractor.new(@e3) { |e3| e3.to_s(16) }.value)
+ assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.value)
+ assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.value)
+ assert_equal(false, Ractor.new { 1.to_bn.zero? }.value)
+ assert_equal(true, Ractor.new { 1.to_bn.one? }.value)
+ assert_equal(true, Ractor.new(@e2) { _1.negative? }.value)
+ assert_equal("-03E7", Ractor.new(@e2) { _1.to_s(16) }.value)
+ assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.value)
+ assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.value)
+ assert_equal(true, Ractor.new { 0.to_bn.zero? }.value)
+ assert_equal(true, Ractor.new { 1.to_bn.one? }.value )
+ assert_equal(false,Ractor.new { 2.to_bn.odd? }.value)
+ assert_equal(true, Ractor.new(@e2) { _1.negative? }.value)
+ assert_include(128..255, Ractor.new { OpenSSL::BN.rand(8)}.value)
+ assert_include(0...2**32, Ractor.new { OpenSSL::BN.generate_prime(32) }.value)
+ if !aws_lc? # AWS-LC does not support BN::CONSTTIME.
+ assert_equal(0, Ractor.new { OpenSSL::BN.new(999).get_flags(OpenSSL::BN::CONSTTIME) }.value)
+ end
# test if shareable when frozen
assert Ractor.shareable?(@e1.freeze)
end
diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb
index cd0b3dcb44..6a405da0a9 100644
--- a/test/openssl/test_cipher.rb
+++ b/test/openssl/test_cipher.rb
@@ -32,28 +32,28 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
salt = "\x01" * 8
num = 2048
pt = "data to be encrypted"
- cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt
- cipher.pkcs5_keyivgen(pass, salt, num, "MD5")
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt
+ cipher.pkcs5_keyivgen(pass, salt, num, "SHA256")
s1 = cipher.update(pt) << cipher.final
- d1 = num.times.inject(pass + salt) {|out, _| OpenSSL::Digest.digest('MD5', out) }
- d2 = num.times.inject(d1 + pass + salt) {|out, _| OpenSSL::Digest.digest('MD5', out) }
- key = (d1 + d2)[0, 24]
- iv = (d1 + d2)[24, 8]
- cipher = new_encryptor("DES-EDE3-CBC", key: key, iv: iv)
+ d1 = num.times.inject(pass + salt) {|out, _| OpenSSL::Digest.digest('SHA256', out) }
+ d2 = num.times.inject(d1 + pass + salt) {|out, _| OpenSSL::Digest.digest('SHA256', out) }
+ key = (d1 + d2)[0, 32]
+ iv = (d1 + d2)[32, 16]
+ cipher = new_encryptor("AES-256-CBC", key: key, iv: iv)
s2 = cipher.update(pt) << cipher.final
assert_equal s1, s2
- cipher2 = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt
- assert_raise(ArgumentError) { cipher2.pkcs5_keyivgen(pass, salt, -1, "MD5") }
+ cipher2 = OpenSSL::Cipher.new("AES-256-CBC").encrypt
+ assert_raise(ArgumentError) { cipher2.pkcs5_keyivgen(pass, salt, -1, "SHA256") }
end
def test_info
- cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt
- assert_equal "DES-EDE3-CBC", cipher.name
- assert_equal 24, cipher.key_len
- assert_equal 8, cipher.iv_len
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt
+ assert_equal "AES-256-CBC", cipher.name
+ assert_equal 32, cipher.key_len
+ assert_equal 16, cipher.iv_len
end
def test_dup
@@ -80,13 +80,13 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
end
def test_key_iv_set
- cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt
- assert_raise(ArgumentError) { cipher.key = "\x01" * 23 }
- assert_nothing_raised { cipher.key = "\x01" * 24 }
- assert_raise(ArgumentError) { cipher.key = "\x01" * 25 }
- assert_raise(ArgumentError) { cipher.iv = "\x01" * 7 }
- assert_nothing_raised { cipher.iv = "\x01" * 8 }
- assert_raise(ArgumentError) { cipher.iv = "\x01" * 9 }
+ cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt
+ assert_raise(ArgumentError) { cipher.key = "\x01" * 31 }
+ assert_nothing_raised { cipher.key = "\x01" * 32 }
+ assert_raise(ArgumentError) { cipher.key = "\x01" * 33 }
+ assert_raise(ArgumentError) { cipher.iv = "\x01" * 15 }
+ assert_nothing_raised { cipher.iv = "\x01" * 16 }
+ assert_raise(ArgumentError) { cipher.iv = "\x01" * 17 }
end
def test_random_key_iv
@@ -109,9 +109,12 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
end
def test_initialize
- cipher = OpenSSL::Cipher.new("DES-EDE3-CBC")
- assert_raise(RuntimeError) { cipher.__send__(:initialize, "DES-EDE3-CBC") }
+ cipher = OpenSSL::Cipher.new("AES-256-CBC")
+ assert_raise(RuntimeError) { cipher.__send__(:initialize, "AES-256-CBC") }
assert_raise(RuntimeError) { OpenSSL::Cipher.allocate.final }
+ assert_raise(OpenSSL::Cipher::CipherError) {
+ OpenSSL::Cipher.new("no such algorithm")
+ }
end
def test_ctr_if_exists
@@ -131,13 +134,14 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
def test_update_with_buffer
cipher = OpenSSL::Cipher.new("aes-128-ecb").encrypt
cipher.random_key
- expected = cipher.update("data") << cipher.final
- assert_equal 16, expected.bytesize
+ expected = cipher.update("data" * 10) << cipher.final
+ assert_equal 48, expected.bytesize
# Buffer is supplied
cipher.reset
buf = String.new
- assert_same buf, cipher.update("data", buf)
+ assert_same buf, cipher.update("data" * 10, buf)
+ assert_equal 32, buf.bytesize
assert_equal expected, buf + cipher.final
# Buffer is frozen
@@ -146,9 +150,9 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
# Buffer is a shared string [ruby-core:120141] [Bug #20937]
cipher.reset
- buf = "x" * 1024
- shared = buf[-("data".bytesize + 32)..-1]
- assert_same shared, cipher.update("data", shared)
+ buf = "x".b * 1024
+ shared = buf[-("data".bytesize * 10 + 32)..-1]
+ assert_same shared, cipher.update("data" * 10, shared)
assert_equal expected, shared + cipher.final
end
@@ -165,12 +169,12 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
%w(ecb cbc cfb ofb).each{|mode|
c1 = OpenSSL::Cipher.new("aes-256-#{mode}")
c1.encrypt
- c1.pkcs5_keyivgen("passwd")
+ c1.pkcs5_keyivgen("passwd", "12345678", 10000, "SHA256")
ct = c1.update(pt) + c1.final
c2 = OpenSSL::Cipher.new("aes-256-#{mode}")
c2.decrypt
- c2.pkcs5_keyivgen("passwd")
+ c2.pkcs5_keyivgen("passwd", "12345678", 10000, "SHA256")
assert_equal(pt, c2.update(ct) + c2.final)
}
end
@@ -182,6 +186,10 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
end
end
+ def test_auth_tag_error_inheritance
+ assert_equal OpenSSL::Cipher::CipherError, OpenSSL::Cipher::AuthTagError.superclass
+ end
+
def test_authenticated
cipher = OpenSSL::Cipher.new('aes-128-gcm')
assert_predicate(cipher, :authenticated?)
@@ -212,7 +220,8 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag[0, 8], auth_data: aad)
assert_equal pt, cipher.update(ct) << cipher.final
- # wrong tag is rejected
+ # wrong tag is rejected - in CCM, authentication happens during update, but
+ # we consider this a general CipherError since update failures can have various causes
tag2 = tag.dup
tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag2, auth_data: aad)
@@ -265,19 +274,19 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag2, auth_data: aad)
cipher.update(ct)
- assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
# wrong aad is rejected
aad2 = aad[0..-2] << aad[-1].succ
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad2)
cipher.update(ct)
- assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
# wrong ciphertext is rejected
ct2 = ct[0..-2] << ct[-1].succ
cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
cipher.update(ct2)
- assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+ assert_raise(OpenSSL::Cipher::AuthTagError) { cipher.final }
end
def test_aes_gcm_variable_iv_len
@@ -304,6 +313,9 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
end
def test_aes_ocb_tag_len
+ # AES-128-OCB is not FIPS-approved.
+ omit_on_fips
+
# RFC 7253 Appendix A; the second sample
key = ["000102030405060708090A0B0C0D0E0F"].pack("H*")
iv = ["BBAA99887766554433221101"].pack("H*")
@@ -337,6 +349,27 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
end if has_cipher?("aes-128-ocb")
+ def test_aes_gcm_siv
+ # AES-128-GCM-SIV is not FIPS-approved.
+ omit_on_fips
+
+ # RFC 8452 Appendix C.1., 8th example
+ key = ["01000000000000000000000000000000"].pack("H*")
+ iv = ["030000000000000000000000"].pack("H*")
+ aad = ["01"].pack("H*")
+ pt = ["0200000000000000"].pack("H*")
+ ct = ["1e6daba35669f4273b0a1a2560969cdf790d99759abd1508"].pack("H*")
+ tag = ["3b0a1a2560969cdf790d99759abd1508"].pack("H*")
+ ct_without_tag = ct.byteslice(0, ct.bytesize - tag.bytesize)
+
+ cipher = new_encryptor("aes-128-gcm-siv", key: key, iv: iv, auth_data: aad)
+ assert_equal ct_without_tag, cipher.update(pt) << cipher.final
+ assert_equal tag, cipher.auth_tag
+ cipher = new_decryptor("aes-128-gcm-siv", key: key, iv: iv, auth_tag: tag,
+ auth_data: aad)
+ assert_equal pt, cipher.update(ct_without_tag) << cipher.final
+ end if openssl?(3, 2, 0)
+
def test_aes_gcm_key_iv_order_issue
pt = "[ruby/openssl#49]"
cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
@@ -363,7 +396,7 @@ class OpenSSL::TestCipher < OpenSSL::TestCase
begin
cipher = OpenSSL::Cipher.new("id-aes192-wrap-pad").encrypt
- rescue OpenSSL::Cipher::CipherError, RuntimeError
+ rescue OpenSSL::Cipher::CipherError
omit "id-aes192-wrap-pad is not supported: #$!"
end
cipher.key = kek
diff --git a/test/openssl/test_config.rb b/test/openssl/test_config.rb
index 759a5bbd44..c10a855a4b 100644
--- a/test/openssl/test_config.rb
+++ b/test/openssl/test_config.rb
@@ -43,6 +43,9 @@ __EOD__
end
def test_s_parse_format
+ # AWS-LC removed support for parsing $foo variables.
+ return if aws_lc?
+
c = OpenSSL::Config.parse(<<__EOC__)
baz =qx\t # "baz = qx"
@@ -213,13 +216,15 @@ __EOC__
assert_raise(TypeError) do
@it.get_value(nil, 'HOME') # not allowed unlike Config#value
end
- # fallback to 'default' ugly...
- assert_equal('.', @it.get_value('unknown', 'HOME'))
+ unless aws_lc? # AWS-LC does not support the fallback
+ # fallback to 'default' ugly...
+ assert_equal('.', @it.get_value('unknown', 'HOME'))
+ end
end
def test_get_value_ENV
- # LibreSSL removed support for NCONF_get_string(conf, "ENV", str)
- return if libressl?
+ # LibreSSL and AWS-LC removed support for NCONF_get_string(conf, "ENV", str)
+ return if libressl? || aws_lc?
key = ENV.keys.first
assert_not_nil(key) # make sure we have at least one ENV var.
diff --git a/test/openssl/test_digest.rb b/test/openssl/test_digest.rb
index 988330e405..bc1f680df5 100644
--- a/test/openssl/test_digest.rb
+++ b/test/openssl/test_digest.rb
@@ -6,23 +6,31 @@ if defined?(OpenSSL)
class OpenSSL::TestDigest < OpenSSL::TestCase
def setup
super
- @d1 = OpenSSL::Digest.new("MD5")
- @d2 = OpenSSL::Digest::MD5.new
+ @d1 = OpenSSL::Digest.new("SHA256")
+ @d2 = OpenSSL::Digest::SHA256.new
+ end
+
+ def test_initialize
+ assert_raise(OpenSSL::Digest::DigestError) {
+ OpenSSL::Digest.new("no such algorithm")
+ }
end
def test_digest
- null_hex = "d41d8cd98f00b204e9800998ecf8427e"
+ # SHA256 null value calculated by `echo -n "" | sha256sum`
+ null_hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
null_bin = [null_hex].pack("H*")
data = "DATA"
- hex = "e44f9e348e41cb272efa87387728571b"
+ # SHA256 DATA value calculated by `echo -n "DATA" | sha256sum`
+ hex = "c97c29c7a71b392b437ee03fd17f09bb10b75e879466fc0eb757b2c4a78ac938"
bin = [hex].pack("H*")
assert_equal(null_bin, @d1.digest)
assert_equal(null_hex, @d1.hexdigest)
@d1 << data
assert_equal(bin, @d1.digest)
assert_equal(hex, @d1.hexdigest)
- assert_equal(bin, OpenSSL::Digest.digest('MD5', data))
- assert_equal(hex, OpenSSL::Digest.hexdigest('MD5', data))
+ assert_equal(bin, OpenSSL::Digest.digest('SHA256', data))
+ assert_equal(hex, OpenSSL::Digest.hexdigest('SHA256', data))
end
def test_eql
@@ -32,9 +40,9 @@ class OpenSSL::TestDigest < OpenSSL::TestCase
end
def test_info
- assert_equal("MD5", @d1.name, "name")
- assert_equal("MD5", @d2.name, "name")
- assert_equal(16, @d1.size, "size")
+ assert_equal("SHA256", @d1.name, "name")
+ assert_equal("SHA256", @d2.name, "name")
+ assert_equal(32, @d1.size, "size")
end
def test_dup
@@ -54,7 +62,10 @@ class OpenSSL::TestDigest < OpenSSL::TestCase
end
def test_digest_constants
- %w{MD5 SHA1 SHA224 SHA256 SHA384 SHA512}.each do |name|
+ non_fips_names = %w{MD5}
+ names = %w{SHA1 SHA224 SHA256 SHA384 SHA512}
+ names = non_fips_names + names unless OpenSSL.fips_mode
+ names.each do |name|
assert_not_nil(OpenSSL::Digest.new(name))
klass = OpenSSL::Digest.const_get(name.tr('-', '_'))
assert_not_nil(klass.new)
@@ -62,8 +73,17 @@ class OpenSSL::TestDigest < OpenSSL::TestCase
end
def test_digest_by_oid_and_name
- check_digest(OpenSSL::ASN1::ObjectId.new("MD5"))
- check_digest(OpenSSL::ASN1::ObjectId.new("SHA1"))
+ # SHA256
+ o1 = OpenSSL::Digest.digest("SHA256", "")
+ o2 = OpenSSL::Digest.digest("sha256", "")
+ assert_equal(o1, o2)
+ o3 = OpenSSL::Digest.digest("2.16.840.1.101.3.4.2.1", "")
+ assert_equal(o1, o3)
+
+ # An alias for SHA256 recognized by EVP_get_digestbyname(), but not by
+ # EVP_MD_fetch()
+ o4 = OpenSSL::Digest.digest("RSA-SHA256", "")
+ assert_equal(o1, o4)
end
def encode16(str)
@@ -88,7 +108,6 @@ class OpenSSL::TestDigest < OpenSSL::TestCase
end
def test_sha512_truncate
- pend "SHA512_224 is not implemented" unless digest_available?('sha512-224')
sha512_224_a = "d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327"
sha512_256_a = "455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8"
@@ -100,23 +119,25 @@ class OpenSSL::TestDigest < OpenSSL::TestCase
end
def test_sha3
- pend "SHA3 is not implemented" unless digest_available?('sha3-224')
s224 = '6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7'
s256 = 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a'
s384 = '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004'
s512 = 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26'
- assert_equal(OpenSSL::Digest.hexdigest('SHA3-224', ""), s224)
- assert_equal(OpenSSL::Digest.hexdigest('SHA3-256', ""), s256)
- assert_equal(OpenSSL::Digest.hexdigest('SHA3-384', ""), s384)
- assert_equal(OpenSSL::Digest.hexdigest('SHA3-512', ""), s512)
+ assert_equal(s224, OpenSSL::Digest.hexdigest('SHA3-224', ""))
+ assert_equal(s256, OpenSSL::Digest.hexdigest('SHA3-256', ""))
+ assert_equal(s384, OpenSSL::Digest.hexdigest('SHA3-384', ""))
+ assert_equal(s512, OpenSSL::Digest.hexdigest('SHA3-512', ""))
end
- def test_digest_by_oid_and_name_sha2
- check_digest(OpenSSL::ASN1::ObjectId.new("SHA224"))
- check_digest(OpenSSL::ASN1::ObjectId.new("SHA256"))
- check_digest(OpenSSL::ASN1::ObjectId.new("SHA384"))
- check_digest(OpenSSL::ASN1::ObjectId.new("SHA512"))
- end
+ def test_fetched_evp_md
+ # KECCAK-256 is not FIPS-approved.
+ omit_on_fips
+
+ # Pre-NIST Keccak is an example of a digest algorithm that doesn't have an
+ # NID and requires dynamic allocation of EVP_MD
+ hex = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
+ assert_equal(hex, OpenSSL::Digest.hexdigest("KECCAK-256", ""))
+ end if openssl?(3, 2, 0)
def test_openssl_digest
assert_equal OpenSSL::Digest::MD5, OpenSSL::Digest("MD5")
@@ -135,20 +156,20 @@ class OpenSSL::TestDigest < OpenSSL::TestCase
assert_include digests, "sha512"
end
- private
-
- def check_digest(oid)
- d = OpenSSL::Digest.new(oid.sn)
- assert_not_nil(d)
- d = OpenSSL::Digest.new(oid.ln)
- assert_not_nil(d)
- d = OpenSSL::Digest.new(oid.oid)
- assert_not_nil(d)
- end
+ if respond_to?(:ractor) && defined?(Ractor.shareable_proc)
+ ractor
- def digest_available?(name)
- @digests ||= OpenSSL::Digest.digests
- @digests.include?(name)
+ def test_ractor
+ assert_nothing_raised do
+ Ractor.new {
+ [
+ OpenSSL::Digest::SHA256.new(""),
+ OpenSSL::Digest::SHA256.hexdigest(""),
+ OpenSSL::Digest::SHA256.digest(""),
+ ]
+ }.value
+ end
+ end
end
end
diff --git a/test/openssl/test_fips.rb b/test/openssl/test_fips.rb
index 4a3dd43a41..683e0011e8 100644
--- a/test/openssl/test_fips.rb
+++ b/test/openssl/test_fips.rb
@@ -28,14 +28,19 @@ class OpenSSL::TestFIPS < OpenSSL::TestCase
end
def test_fips_mode_is_reentrant
- assert_separately(["-ropenssl"], <<~"end;")
+ return if aws_lc? # AWS-LC's FIPS mode is decided at compile time.
+
+ assert_ruby_status(["-ropenssl"], <<~"end;")
OpenSSL.fips_mode = false
OpenSSL.fips_mode = false
end;
end
def test_fips_mode_get_with_fips_mode_set
- omit('OpenSSL is not FIPS-capable') unless OpenSSL::OPENSSL_FIPS
+ return if aws_lc? # AWS-LC's FIPS mode is decided at compile time.
+ unless ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"]
+ omit "Only for FIPS mode environment"
+ end
assert_separately(["-ropenssl"], <<~"end;")
begin
diff --git a/test/openssl/test_hmac.rb b/test/openssl/test_hmac.rb
index 3cb707448a..7cf820628e 100644
--- a/test/openssl/test_hmac.rb
+++ b/test/openssl/test_hmac.rb
@@ -4,14 +4,18 @@ require_relative 'utils'
if defined?(OpenSSL)
class OpenSSL::TestHMAC < OpenSSL::TestCase
- def test_hmac
+ def test_hmac_md5
+ omit_on_fips # MD5
+
# RFC 2202 2. Test Cases for HMAC-MD5
hmac = OpenSSL::HMAC.new(["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*"), "MD5")
hmac.update("Hi There")
assert_equal ["9294727a3638bb1c13f48ef8158bfc9d"].pack("H*"), hmac.digest
assert_equal "9294727a3638bb1c13f48ef8158bfc9d", hmac.hexdigest
assert_equal "kpRyejY4uxwT9I74FYv8nQ==", hmac.base64digest
+ end
+ def test_hmac_sha224
# RFC 4231 4.2. Test Case 1
hmac = OpenSSL::HMAC.new(["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*"), "SHA224")
hmac.update("Hi There")
@@ -21,7 +25,7 @@ class OpenSSL::TestHMAC < OpenSSL::TestCase
end
def test_dup
- h1 = OpenSSL::HMAC.new("KEY", "MD5")
+ h1 = OpenSSL::HMAC.new("KEY"*32, "SHA256")
h1.update("DATA")
h = h1.dup
assert_equal(h1.digest, h.digest, "dup digest")
@@ -35,7 +39,7 @@ class OpenSSL::TestHMAC < OpenSSL::TestCase
end
def test_reset_keep_key
- h1 = OpenSSL::HMAC.new("KEY", "MD5")
+ h1 = OpenSSL::HMAC.new("KEY"*32, "SHA256")
first = h1.update("test").hexdigest
h1.reset
second = h1.update("test").hexdigest
@@ -43,9 +47,9 @@ class OpenSSL::TestHMAC < OpenSSL::TestCase
end
def test_eq
- h1 = OpenSSL::HMAC.new("KEY", "MD5")
- h2 = OpenSSL::HMAC.new("KEY", OpenSSL::Digest.new("MD5"))
- h3 = OpenSSL::HMAC.new("FOO", "MD5")
+ h1 = OpenSSL::HMAC.new("KEY"*32, "SHA256")
+ h2 = OpenSSL::HMAC.new("KEY"*32, OpenSSL::Digest.new("SHA256"))
+ h3 = OpenSSL::HMAC.new("FOO"*32, "SHA256")
assert_equal h1, h2
refute_equal h1, h2.digest
@@ -53,17 +57,19 @@ class OpenSSL::TestHMAC < OpenSSL::TestCase
end
def test_singleton_methods
- # RFC 2202 2. Test Cases for HMAC-MD5
- key = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*")
- digest = OpenSSL::HMAC.digest("MD5", key, "Hi There")
- assert_equal ["9294727a3638bb1c13f48ef8158bfc9d"].pack("H*"), digest
- hexdigest = OpenSSL::HMAC.hexdigest("MD5", key, "Hi There")
- assert_equal "9294727a3638bb1c13f48ef8158bfc9d", hexdigest
- b64digest = OpenSSL::HMAC.base64digest("MD5", key, "Hi There")
- assert_equal "kpRyejY4uxwT9I74FYv8nQ==", b64digest
+ # RFC 4231 4.2. Test Case 1
+ key = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*")
+ digest = OpenSSL::HMAC.digest("SHA256", key, "Hi There")
+ assert_equal ["b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"].pack("H*"), digest
+ hexdigest = OpenSSL::HMAC.hexdigest("SHA256", key, "Hi There")
+ assert_equal "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", hexdigest
+ b64digest = OpenSSL::HMAC.base64digest("SHA256", key, "Hi There")
+ assert_equal "sDRMYdjbOFNcqK/OrwvxK4gdwgDJgz2nJuk3bC4yz/c=", b64digest
end
def test_zero_length_key
+ omit_on_fips # Key length
+
# Empty string as the key
hexdigest = OpenSSL::HMAC.hexdigest("SHA256", "\0"*32, "test")
assert_equal "43b0cef99265f9e34c10ea9d3501926d27b39f57c6d674561d8ba236e7a819fb", hexdigest
diff --git a/test/openssl/test_kdf.rb b/test/openssl/test_kdf.rb
index f4790c96af..708d1883af 100644
--- a/test/openssl/test_kdf.rb
+++ b/test/openssl/test_kdf.rb
@@ -5,64 +5,31 @@ if defined?(OpenSSL)
class OpenSSL::TestKDF < OpenSSL::TestCase
def test_pkcs5_pbkdf2_hmac_compatibility
- expected = OpenSSL::KDF.pbkdf2_hmac("password", salt: "salt", iterations: 1, length: 20, hash: "sha1")
- assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("password", "salt", 1, 20, "sha1"))
- assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "salt", 1, 20))
+ # PBKDF2 salt >= 16 bytes (128 bits) and iterations >= 1000 are required in
+ # FIPS.
+ # SP 800-132.
+ # https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
+ # * 5.1 The Salt (S)
+ # * 5.2 The Iteration Count (C)
+ # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L235-L240
+ # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/providers/implementations/kdfs/pbkdf2.c#L247-L252
+ # Use the same parameters with test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25.
+ expected = OpenSSL::KDF.pbkdf2_hmac("passwordPASSWORDpassword",
+ salt: "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+ iterations: 4096,
+ length: 25,
+ hash: "sha1")
+ assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("passwordPASSWORDpassword",
+ "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+ 4096,
+ 25,
+ "sha1"))
+ assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("passwordPASSWORDpassword",
+ "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+ 4096,
+ 25))
end
- def test_pbkdf2_hmac_sha1_rfc6070_c_1_len_20
- p ="password"
- s = "salt"
- c = 1
- dk_len = 20
- raw = %w{ 0c 60 c8 0f 96 1f 0e 71
- f3 a9 b5 24 af 60 12 06
- 2f e0 37 a6 }
- expected = [raw.join('')].pack('H*')
- value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1")
- assert_equal(expected, value)
- end
-
- def test_pbkdf2_hmac_sha1_rfc6070_c_2_len_20
- p ="password"
- s = "salt"
- c = 2
- dk_len = 20
- raw = %w{ ea 6c 01 4d c7 2d 6f 8c
- cd 1e d9 2a ce 1d 41 f0
- d8 de 89 57 }
- expected = [raw.join('')].pack('H*')
- value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1")
- assert_equal(expected, value)
- end
-
- def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_20
- p ="password"
- s = "salt"
- c = 4096
- dk_len = 20
- raw = %w{ 4b 00 79 01 b7 65 48 9a
- be ad 49 d9 26 f7 21 d0
- 65 a4 29 c1 }
- expected = [raw.join('')].pack('H*')
- value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1")
- assert_equal(expected, value)
- end
-
-# takes too long!
-# def test_pbkdf2_hmac_sha1_rfc6070_c_16777216_len_20
-# p ="password"
-# s = "salt"
-# c = 16777216
-# dk_len = 20
-# raw = %w{ ee fe 3d 61 cd 4d a4 e4
-# e9 94 5b 3d 6b a2 15 8c
-# 26 34 e9 84 }
-# expected = [raw.join('')].pack('H*')
-# value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1")
-# assert_equal(expected, value)
-# end
-
def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25
p ="passwordPASSWORDpassword"
s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"
@@ -78,18 +45,6 @@ class OpenSSL::TestKDF < OpenSSL::TestCase
assert_equal(expected, value)
end
- def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_16
- p ="pass\0word"
- s = "sa\0lt"
- c = 4096
- dk_len = 16
- raw = %w{ 56 fa 6a a7 55 48 09 9d
- cc 37 d7 f0 34 25 e0 c3 }
- expected = [raw.join('')].pack('H*')
- value = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1")
- assert_equal(expected, value)
- end
-
def test_pbkdf2_hmac_sha256_c_20000_len_32
#unfortunately no official test vectors available yet for SHA-2
p ="password"
@@ -103,6 +58,11 @@ class OpenSSL::TestKDF < OpenSSL::TestCase
def test_scrypt_rfc7914_first
pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0
+ # scrypt is not available in FIPS.
+ # EVP_KDF_fetch(ctx, OSSL_KDF_NAME_SCRYPT, propq) returns NULL in FIPS.
+ # https://github.com/openssl/openssl/blob/71943544885ff364a10bcc5ffc62d0e651c9a021/crypto/evp/pbe_scrypt.c#L67-L71
+ omit_on_fips
+
pass = ""
salt = ""
n = 16
@@ -118,6 +78,9 @@ class OpenSSL::TestKDF < OpenSSL::TestCase
def test_scrypt_rfc7914_second
pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0
+ # scrypt is not available in FIPS.
+ omit_on_fips
+
pass = "password"
salt = "NaCl"
n = 1024
@@ -131,8 +94,8 @@ class OpenSSL::TestKDF < OpenSSL::TestCase
assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen))
end
+ # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.1
def test_hkdf_rfc5869_test_case_1
- pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
hash = "sha256"
ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
salt = B("000102030405060708090a0b0c")
@@ -145,8 +108,8 @@ class OpenSSL::TestKDF < OpenSSL::TestCase
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
end
+ # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.3
def test_hkdf_rfc5869_test_case_3
- pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
hash = "sha256"
ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
salt = B("")
@@ -159,17 +122,32 @@ class OpenSSL::TestKDF < OpenSSL::TestCase
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
end
- def test_hkdf_rfc5869_test_case_4
- pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0
+ # https://www.rfc-editor.org/rfc/rfc5869#appendix-A.5
+ def test_hkdf_rfc5869_test_case_5
hash = "sha1"
- ikm = B("0b0b0b0b0b0b0b0b0b0b0b")
- salt = B("000102030405060708090a0b0c")
- info = B("f0f1f2f3f4f5f6f7f8f9")
- l = 42
-
- okm = B("085a01ea1b10f36933068b56efa5ad81" \
- "a4f14b822f5b091568a9cdd4f155fda2" \
- "c22e422478d305f3f896")
+ ikm = B("000102030405060708090a0b0c0d0e0f" \
+ "101112131415161718191a1b1c1d1e1f" \
+ "202122232425262728292a2b2c2d2e2f" \
+ "303132333435363738393a3b3c3d3e3f" \
+ "404142434445464748494a4b4c4d4e4f")
+ salt = B("606162636465666768696a6b6c6d6e6f" \
+ "707172737475767778797a7b7c7d7e7f" \
+ "808182838485868788898a8b8c8d8e8f" \
+ "909192939495969798999a9b9c9d9e9f" \
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf")
+ info = B("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" \
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" \
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" \
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" \
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
+ l = 82
+
+ okm = B("0bd770a74d1160f7c9f12cd5912a06eb" \
+ "ff6adcae899d92191fe4305673ba2ffe" \
+ "8fa3f1a4e5ad79f3f334b3b202b2173c" \
+ "486ea37ce3d397ed034c7f9dfeb15c5e" \
+ "927336d0441f4c4300e2cff0d0900b52" \
+ "d3b4")
assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash))
end
diff --git a/test/openssl/test_ns_spki.rb b/test/openssl/test_ns_spki.rb
index d76fc9e5cf..0484429289 100644
--- a/test/openssl/test_ns_spki.rb
+++ b/test/openssl/test_ns_spki.rb
@@ -17,8 +17,8 @@ class OpenSSL::TestNSSPI < OpenSSL::TestCase
end
def test_build_data
- key1 = Fixtures.pkey("rsa1024")
- key2 = Fixtures.pkey("rsa2048")
+ key1 = Fixtures.pkey("rsa-1")
+ key2 = Fixtures.pkey("rsa-2")
spki = OpenSSL::Netscape::SPKI.new
spki.challenge = "RandomString"
spki.public_key = key1.public_key
diff --git a/test/openssl/test_ocsp.rb b/test/openssl/test_ocsp.rb
index cf96fc22e5..c43ff5cb55 100644
--- a/test/openssl/test_ocsp.rb
+++ b/test/openssl/test_ocsp.rb
@@ -13,7 +13,7 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase
# @cert2 @ocsp_cert
ca_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA")
- @ca_key = Fixtures.pkey("rsa1024")
+ @ca_key = Fixtures.pkey("rsa-1")
ca_exts = [
["basicConstraints", "CA:TRUE", true],
["keyUsage", "cRLSign,keyCertSign", true],
@@ -22,7 +22,7 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase
ca_subj, @ca_key, 1, ca_exts, nil, nil)
cert_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA2")
- @cert_key = Fixtures.pkey("rsa1024")
+ @cert_key = Fixtures.pkey("rsa-2")
cert_exts = [
["basicConstraints", "CA:TRUE", true],
["keyUsage", "cRLSign,keyCertSign", true],
@@ -31,14 +31,14 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase
cert_subj, @cert_key, 5, cert_exts, @ca_cert, @ca_key)
cert2_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCert")
- @cert2_key = Fixtures.pkey("rsa1024")
+ @cert2_key = Fixtures.pkey("rsa-3")
cert2_exts = [
]
@cert2 = OpenSSL::TestUtils.issue_cert(
cert2_subj, @cert2_key, 10, cert2_exts, @cert, @cert_key)
ocsp_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCAOCSP")
- @ocsp_key = Fixtures.pkey("rsa2048")
+ @ocsp_key = Fixtures.pkey("p256")
ocsp_exts = [
["extendedKeyUsage", "OCSPSigning", true],
]
@@ -63,8 +63,10 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase
def test_certificate_id_issuer_key_hash
cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
- assert_equal OpenSSL::Digest.hexdigest('SHA1', OpenSSL::ASN1.decode(@ca_cert.to_der).value[0].value[6].value[1].value), cid.issuer_key_hash
- assert_equal "d1fef9fbf8ae1bc160cbfa03e2596dd873089213", cid.issuer_key_hash
+ # content of subjectPublicKey (bit string) in SubjectPublicKeyInfo
+ spki = OpenSSL::ASN1.decode(@ca_key.public_to_der)
+ assert_equal OpenSSL::Digest.hexdigest("SHA1", spki.value[1].value),
+ cid.issuer_key_hash
end
def test_certificate_id_hash_algorithm
@@ -213,6 +215,35 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase
assert_equal bres.to_der, bres.dup.to_der
end
+ def test_basic_response_status_good
+ bres = OpenSSL::OCSP::BasicResponse.new
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, nil)
+ bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert])
+
+ statuses = bres.status
+ assert_equal 1, statuses.size
+ status = statuses[0]
+ assert_equal cid.to_der, status[0].to_der
+ assert_equal OpenSSL::OCSP::V_CERTSTATUS_GOOD, status[1]
+ assert_nil status[3] # revtime should be nil for GOOD status
+ end
+
+ def test_basic_response_status_revoked
+ bres = OpenSSL::OCSP::BasicResponse.new
+ now = Time.at(Time.now.to_i)
+ cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1'))
+ bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_REVOKED,
+ OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, now - 400, -300, nil, nil)
+ bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert])
+
+ statuses = bres.status
+ assert_equal 1, statuses.size
+ status = statuses[0]
+ assert_equal OpenSSL::OCSP::V_CERTSTATUS_REVOKED, status[1]
+ assert_equal now - 400, status[3] # revtime should be the revocation time
+ end
+
def test_basic_response_response_operations
bres = OpenSSL::OCSP::BasicResponse.new
now = Time.at(Time.now.to_i)
diff --git a/test/openssl/test_ossl.rb b/test/openssl/test_ossl.rb
index 3a90ead10a..1b9bde53ef 100644
--- a/test/openssl/test_ossl.rb
+++ b/test/openssl/test_ossl.rb
@@ -3,51 +3,55 @@ require_relative "utils"
if defined?(OpenSSL)
-class OpenSSL::OSSL < OpenSSL::SSLTestCase
+class OpenSSL::TestOSSL < OpenSSL::TestCase
def test_fixed_length_secure_compare
assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "a") }
assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aa") }
- assert OpenSSL.fixed_length_secure_compare("aaa", "aaa")
- assert OpenSSL.fixed_length_secure_compare(
+ assert_true(OpenSSL.fixed_length_secure_compare("aaa", "aaa"))
+ assert_true(OpenSSL.fixed_length_secure_compare(
OpenSSL::Digest.digest('SHA256', "aaa"), OpenSSL::Digest::SHA256.digest("aaa")
- )
+ ))
assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaaa") }
- refute OpenSSL.fixed_length_secure_compare("aaa", "baa")
- refute OpenSSL.fixed_length_secure_compare("aaa", "aba")
- refute OpenSSL.fixed_length_secure_compare("aaa", "aab")
+ assert_false(OpenSSL.fixed_length_secure_compare("aaa", "baa"))
+ assert_false(OpenSSL.fixed_length_secure_compare("aaa", "aba"))
+ assert_false(OpenSSL.fixed_length_secure_compare("aaa", "aab"))
assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaab") }
assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "b") }
assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bb") }
- refute OpenSSL.fixed_length_secure_compare("aaa", "bbb")
+ assert_false(OpenSSL.fixed_length_secure_compare("aaa", "bbb"))
assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bbbb") }
end
+ def test_fixed_length_secure_compare_uaf
+ str1 = "A" * 1000000
+ evil_obj = Object.new
+ evil_obj.define_singleton_method(:to_str) do
+ str1.replace("C" * 1000000)
+ "B" * 1000000
+ end
+ assert_false(OpenSSL.fixed_length_secure_compare(str1, evil_obj))
+ end
+
def test_secure_compare
- refute OpenSSL.secure_compare("aaa", "a")
- refute OpenSSL.secure_compare("aaa", "aa")
+ assert_false(OpenSSL.secure_compare("aaa", "a"))
+ assert_false(OpenSSL.secure_compare("aaa", "aa"))
- assert OpenSSL.secure_compare("aaa", "aaa")
+ assert_true(OpenSSL.secure_compare("aaa", "aaa"))
- refute OpenSSL.secure_compare("aaa", "aaaa")
- refute OpenSSL.secure_compare("aaa", "baa")
- refute OpenSSL.secure_compare("aaa", "aba")
- refute OpenSSL.secure_compare("aaa", "aab")
- refute OpenSSL.secure_compare("aaa", "aaab")
- refute OpenSSL.secure_compare("aaa", "b")
- refute OpenSSL.secure_compare("aaa", "bb")
- refute OpenSSL.secure_compare("aaa", "bbb")
- refute OpenSSL.secure_compare("aaa", "bbbb")
+ assert_false(OpenSSL.secure_compare("aaa", "aaaa"))
+ assert_false(OpenSSL.secure_compare("aaa", "baa"))
+ assert_false(OpenSSL.secure_compare("aaa", "aba"))
+ assert_false(OpenSSL.secure_compare("aaa", "aab"))
+ assert_false(OpenSSL.secure_compare("aaa", "aaab"))
+ assert_false(OpenSSL.secure_compare("aaa", "b"))
+ assert_false(OpenSSL.secure_compare("aaa", "bb"))
+ assert_false(OpenSSL.secure_compare("aaa", "bbb"))
+ assert_false(OpenSSL.secure_compare("aaa", "bbbb"))
end
def test_memcmp_timing
- begin
- require "benchmark"
- rescue LoadError
- pend "Benchmark is not available in this environment. Please install it with `gem install benchmark`."
- end
-
# Ensure using fixed_length_secure_compare takes almost exactly the same amount of time to compare two different strings.
# Regular string comparison will short-circuit on the first non-matching character, failing this test.
# NOTE: this test may be susceptible to noise if the system running the tests is otherwise under load.
@@ -58,24 +62,41 @@ class OpenSSL::OSSL < OpenSSL::SSLTestCase
a_b_time = a_c_time = 0
100.times do
- a_b_time += Benchmark.measure { 100.times { OpenSSL.fixed_length_secure_compare(a, b) } }.real
- a_c_time += Benchmark.measure { 100.times { OpenSSL.fixed_length_secure_compare(a, c) } }.real
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ 100.times { OpenSSL.fixed_length_secure_compare(a, b) }
+ t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ 100.times { OpenSSL.fixed_length_secure_compare(a, c) }
+ t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+
+ a_b_time += t2 - t1
+ a_c_time += t3 - t2
end
assert_operator(a_b_time, :<, a_c_time * 10, "fixed_length_secure_compare timing test failed")
assert_operator(a_c_time, :<, a_b_time * 10, "fixed_length_secure_compare timing test failed")
- end
+ end if ENV["OSSL_TEST_ALL"] == "1"
def test_error_data
- # X509V3_EXT_nconf_nid() called from OpenSSL::X509::ExtensionFactory#create_ext is a function
- # that uses ERR_raise_data() to append additional information about the error.
+ # X509V3_EXT_nconf_nid() called from
+ # OpenSSL::X509::ExtensionFactory#create_ext is a function that uses
+ # ERR_raise_data() to append additional information about the error.
#
# The generated message should look like:
# "subjectAltName = IP:not.a.valid.ip.address: bad ip address (value=not.a.valid.ip.address)"
# "subjectAltName = IP:not.a.valid.ip.address: error in extension (name=subjectAltName, value=IP:not.a.valid.ip.address)"
+ #
+ # The string inside parentheses is the ERR_TXT_STRING data, and is appended
+ # by ossl_make_error(), so we check it here.
ef = OpenSSL::X509::ExtensionFactory.new
- assert_raise_with_message(OpenSSL::X509::ExtensionError, /value=(IP:)?not.a.valid.ip.address\)/) {
+ e = assert_raise(OpenSSL::X509::ExtensionError) {
ef.create_ext("subjectAltName", "IP:not.a.valid.ip.address")
}
+ assert_match(/not.a.valid.ip.address\)\z/, e.message)
+
+ # We currently craft the strings based on ERR_error_string()'s style:
+ # error:<error code in hex>:<library>:<function>:<reason> (data)
+ assert_instance_of(Array, e.errors)
+ assert_match(/\Aerror:.*not.a.valid.ip.address\)\z/, e.errors.last)
+ assert_include(e.detailed_message, "not.a.valid.ip.address")
end
end
diff --git a/test/openssl/test_pkcs12.rb b/test/openssl/test_pkcs12.rb
index 68a23b28c0..617c156cbd 100644
--- a/test/openssl/test_pkcs12.rb
+++ b/test/openssl/test_pkcs12.rb
@@ -3,6 +3,29 @@ require_relative "utils"
if defined?(OpenSSL)
+# OpenSSL::PKCS12.create calling the PKCS12_create() has the argument mac_iter
+# which uses a MAC key using PKCS12KDF which is not FIPS-approved.
+# OpenSSL::PKCS12.new with base64-encoded example calling PKCS12_parse()
+# verifies the MAC key using PKCS12KDF which is not FIPS-approved.
+#
+# PBE-SHA1-3DES uses PKCS12KDF which is not FIPS-approved according to the RFC
+# 7292 PKCS#12.
+# https://datatracker.ietf.org/doc/html/rfc7292#appendix-C
+# > The PBES1 encryption scheme defined in PKCS #5 provides a number of
+# > algorithm identifiers for deriving keys and IVs; here, we specify a
+# > few more, all of which use the procedure detailed in Appendices B.2
+# > and B.3 to construct keys (and IVs, where needed). As is implied by
+# > their names, all of the object identifiers below use the hash
+# > function SHA-1.
+# > ...
+# > pbeWithSHAAnd3-KeyTripleDES-CBC OBJECT IDENTIFIER ::= {pkcs-12PbeIds 3}
+#
+# Note that the pbeWithSHAAnd3-KeyTripleDES-CBC (pkcs12-pbeids 3) in the RFC
+# 7292 PKCS#12 means PBE-SHA1-3DES in OpenSSL. PKCS12KDF is used in PKCS#12.
+# https://oidref.com/1.2.840.113549.1.12.1.3
+# https://github.com/openssl/openssl/blob/ed57d1e06dca28689190e00d9893e0fd7ecc67c1/crypto/objects/objects.txt#L385
+return if OpenSSL.fips_mode
+
module OpenSSL
class TestPKCS12 < OpenSSL::TestCase
DEFAULT_PBE_PKEYS = "PBE-SHA1-3DES"
@@ -178,6 +201,8 @@ module OpenSSL
end
def test_create_with_keytype
+ omit "AWS-LC does not support KEY_SIG and KEY_EX" if aws_lc?
+
OpenSSL::PKCS12.create(
"omg",
"hello",
@@ -208,8 +233,13 @@ module OpenSSL
end
def test_new_with_no_keys
- # generated with:
- # openssl pkcs12 -certpbe PBE-SHA1-3DES -in <@mycert> -nokeys -export
+ # Generated with the following steps:
+ # Print the value of the @mycert such as by `puts @mycert.to_s` and
+ # save the value as the file `mycert.pem`.
+ # Run the following commands:
+ # openssl pkcs12 -certpbe PBE-SHA1-3DES -in <(cat mycert.pem) \
+ # -nokeys -export -passout pass:abc123 -out /tmp/p12.out
+ # base64 -w 60 /tmp/p12.out
str = <<~EOF.unpack1("m")
MIIGJAIBAzCCBeoGCSqGSIb3DQEHAaCCBdsEggXXMIIF0zCCBc8GCSqGSIb3
DQEHBqCCBcAwggW8AgEAMIIFtQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQMw
@@ -257,8 +287,10 @@ AA==
end
def test_new_with_no_certs
- # generated with:
- # openssl pkcs12 -inkey fixtures/openssl/pkey/rsa-1.pem -nocerts -export
+ # Generated with the folowing steps:
+ # openssl pkcs12 -inkey test/openssl/fixtures/pkey/rsa-1.pem \
+ # -nocerts -export -passout pass:abc123 -out /tmp/p12.out
+ # base64 -w 60 /tmp/p12.out
str = <<~EOF.unpack1("m")
MIIJ7wIBAzCCCbUGCSqGSIb3DQEHAaCCCaYEggmiMIIJnjCCCZoGCSqGSIb3
DQEHAaCCCYsEggmHMIIJgzCCCX8GCyqGSIb3DQEMCgECoIIJbjCCCWowHAYK
diff --git a/test/openssl/test_pkcs7.rb b/test/openssl/test_pkcs7.rb
index 862716b4d8..b3129c0cdf 100644
--- a/test/openssl/test_pkcs7.rb
+++ b/test/openssl/test_pkcs7.rb
@@ -6,92 +6,125 @@ if defined?(OpenSSL)
class OpenSSL::TestPKCS7 < OpenSSL::TestCase
def setup
super
- @rsa1024 = Fixtures.pkey("rsa1024")
- @rsa2048 = Fixtures.pkey("rsa2048")
- ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
- ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
- ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
+ @ca_key = Fixtures.pkey("rsa-1")
+ @ee1_key = Fixtures.pkey("rsa-2")
+ @ee2_key = Fixtures.pkey("rsa-3")
+ ca = OpenSSL::X509::Name.new([["CN", "CA"]])
+ ee1 = OpenSSL::X509::Name.new([["CN", "EE1"]])
+ ee2 = OpenSSL::X509::Name.new([["CN", "EE2"]])
ca_exts = [
- ["basicConstraints","CA:TRUE",true],
- ["keyUsage","keyCertSign, cRLSign",true],
- ["subjectKeyIdentifier","hash",false],
- ["authorityKeyIdentifier","keyid:always",false],
+ ["basicConstraints", "CA:TRUE", true],
+ ["keyUsage", "keyCertSign, cRLSign", true],
+ ["subjectKeyIdentifier", "hash", false],
+ ["authorityKeyIdentifier", "keyid:always", false],
]
- @ca_cert = issue_cert(ca, @rsa2048, 1, ca_exts, nil, nil)
+ @ca_cert = issue_cert(ca, @ca_key, 1, ca_exts, nil, nil)
ee_exts = [
- ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true],
- ["authorityKeyIdentifier","keyid:always",false],
- ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false],
+ ["keyUsage", "nonRepudiation, digitalSignature, keyEncipherment", true],
+ ["authorityKeyIdentifier", "keyid:always", false],
+ ["extendedKeyUsage", "clientAuth, emailProtection, codeSigning", false],
]
- @ee1_cert = issue_cert(ee1, @rsa1024, 2, ee_exts, @ca_cert, @rsa2048)
- @ee2_cert = issue_cert(ee2, @rsa1024, 3, ee_exts, @ca_cert, @rsa2048)
+ @ee1_cert = issue_cert(ee1, @ee1_key, 2, ee_exts, @ca_cert, @ca_key)
+ @ee2_cert = issue_cert(ee2, @ee2_key, 3, ee_exts, @ca_cert, @ca_key)
end
def test_signed
store = OpenSSL::X509::Store.new
store.add_cert(@ca_cert)
+
+ data = "aaaaa\nbbbbb\nccccc\n"
ca_certs = [@ca_cert]
+ tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, ca_certs)
+ # TODO: #data contains untranslated content
+ assert_equal("aaaaa\nbbbbb\nccccc\n", tmp.data)
+ assert_nil(tmp.error_string)
- data = "aaaaa\r\nbbbbb\r\nccccc\r\n"
- tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs)
p7 = OpenSSL::PKCS7.new(tmp.to_der)
+ assert_nil(p7.data)
+ assert_nil(p7.error_string)
+
+ assert_true(p7.verify([], store))
+ # AWS-LC does not appear to convert to CRLF automatically
+ assert_equal("aaaaa\r\nbbbbb\r\nccccc\r\n", p7.data) unless aws_lc?
+ assert_nil(p7.error_string)
+
certs = p7.certificates
- signers = p7.signers
- assert(p7.verify([], store))
- assert_equal(data, p7.data)
assert_equal(2, certs.size)
- assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
- assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+ assert_equal(@ee1_cert.subject, certs[0].subject)
+ assert_equal(@ca_cert.subject, certs[1].subject)
+
+ signers = p7.signers
assert_equal(1, signers.size)
assert_equal(@ee1_cert.serial, signers[0].serial)
- assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+ assert_equal(@ee1_cert.issuer, signers[0].issuer)
+ # AWS-LC does not generate authenticatedAttributes
+ assert_in_delta(Time.now, signers[0].signed_time, 10) unless aws_lc?
+
+ assert_false(p7.verify([@ca_cert], OpenSSL::X509::Store.new))
+ end
+
+ def test_signed_flags
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
# Normally OpenSSL tries to translate the supplied content into canonical
# MIME format (e.g. a newline character is converted into CR+LF).
# If the content is a binary, PKCS7::BINARY flag should be used.
-
+ #
+ # PKCS7::NOATTR flag suppresses authenticatedAttributes.
data = "aaaaa\nbbbbb\nccccc\n"
- flag = OpenSSL::PKCS7::BINARY
- tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag)
+ flag = OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::NOATTR
+ tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, [@ca_cert], flag)
p7 = OpenSSL::PKCS7.new(tmp.to_der)
- certs = p7.certificates
- signers = p7.signers
- assert(p7.verify([], store))
+
+ assert_true(p7.verify([], store))
assert_equal(data, p7.data)
+
+ certs = p7.certificates
assert_equal(2, certs.size)
- assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
- assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+ assert_equal(@ee1_cert.subject, certs[0].subject)
+ assert_equal(@ca_cert.subject, certs[1].subject)
+
+ signers = p7.signers
assert_equal(1, signers.size)
assert_equal(@ee1_cert.serial, signers[0].serial)
- assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+ assert_equal(@ee1_cert.issuer, signers[0].issuer)
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { signers[0].signed_time }
+ end
+
+ def test_signed_multiple_signers
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
# A signed-data which have multiple signatures can be created
# through the following steps.
# 1. create two signed-data
# 2. copy signerInfo and certificate from one to another
-
- tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, [], flag)
- tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @rsa1024, data, [], flag)
+ data = "aaaaa\r\nbbbbb\r\nccccc\r\n"
+ tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data)
+ tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @ee2_key, data)
tmp1.add_signer(tmp2.signers[0])
tmp1.add_certificate(@ee2_cert)
p7 = OpenSSL::PKCS7.new(tmp1.to_der)
- certs = p7.certificates
- signers = p7.signers
- assert(p7.verify([], store))
+ assert_true(p7.verify([], store))
assert_equal(data, p7.data)
+
+ certs = p7.certificates
assert_equal(2, certs.size)
+
+ signers = p7.signers
assert_equal(2, signers.size)
assert_equal(@ee1_cert.serial, signers[0].serial)
- assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+ assert_equal(@ee1_cert.issuer, signers[0].issuer)
assert_equal(@ee2_cert.serial, signers[1].serial)
- assert_equal(@ee2_cert.issuer.to_s, signers[1].issuer.to_s)
+ assert_equal(@ee2_cert.issuer, signers[1].issuer)
end
def test_signed_add_signer
data = "aaaaa\nbbbbb\nccccc\n"
- psi = OpenSSL::PKCS7::SignerInfo.new(@ee1_cert, @rsa1024, "sha256")
+ psi = OpenSSL::PKCS7::SignerInfo.new(@ee1_cert, @ee1_key, "sha256")
p7 = OpenSSL::PKCS7.new
p7.type = :signed
p7.add_signer(psi)
@@ -110,30 +143,82 @@ class OpenSSL::TestPKCS7 < OpenSSL::TestCase
def test_detached_sign
store = OpenSSL::X509::Store.new
store.add_cert(@ca_cert)
- ca_certs = [@ca_cert]
data = "aaaaa\nbbbbb\nccccc\n"
+ ca_certs = [@ca_cert]
flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED
- tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag)
+ tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, ca_certs, flag)
p7 = OpenSSL::PKCS7.new(tmp.to_der)
- assert_nothing_raised do
- OpenSSL::ASN1.decode(p7)
- end
+ assert_predicate(p7, :detached?)
+ assert_true(p7.detached)
- certs = p7.certificates
- signers = p7.signers
- assert(!p7.verify([], store))
- assert(p7.verify([], store, data))
+ assert_false(p7.verify([], store))
+ # FIXME: Should it be nil?
+ assert_equal("", p7.data)
+ assert_match(/no content|NO_CONTENT/, p7.error_string)
+
+ assert_true(p7.verify([], store, data))
assert_equal(data, p7.data)
+ assert_nil(p7.error_string)
+
+ certs = p7.certificates
assert_equal(2, certs.size)
- assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
- assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+ assert_equal(@ee1_cert.subject, certs[0].subject)
+ assert_equal(@ca_cert.subject, certs[1].subject)
+
+ signers = p7.signers
assert_equal(1, signers.size)
assert_equal(@ee1_cert.serial, signers[0].serial)
- assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+ assert_equal(@ee1_cert.issuer, signers[0].issuer)
+ end
+
+ def test_signed_authenticated_attributes
+ # Using static PEM data because AWS-LC does not support generating one
+ # with authenticatedAttributes.
+ #
+ # p7 was generated with OpenSSL 3.4.1 with this program with commandline
+ # "faketime 2025-04-03Z ruby prog.rb":
+ #
+ # require_relative "test/openssl/utils"
+ # include OpenSSL::TestUtils
+ # key = Fixtures.pkey("p256")
+ # cert = issue_cert(OpenSSL::X509::Name.new([["CN", "cert"]]), key, 1, [], nil, nil)
+ # p7 = OpenSSL::PKCS7.sign(cert, key, "content", [])
+ # puts p7.to_pem
+ p7 = OpenSSL::PKCS7.new(<<~EOF)
+-----BEGIN PKCS7-----
+MIICvgYJKoZIhvcNAQcCoIICrzCCAqsCAQExDzANBglghkgBZQMEAgEFADAWBgkq
+hkiG9w0BBwGgCQQHY29udGVudKCCAQ4wggEKMIGxoAMCAQICAQEwCgYIKoZIzj0E
+AwIwDzENMAsGA1UEAwwEY2VydDAeFw0yNTA0MDIyMzAwMDFaFw0yNTA0MDMwMTAw
+MDFaMA8xDTALBgNVBAMMBGNlcnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQW
+CWTZz6hVQgpDrh5kb1uEs09YHuVJn8CsrjV4bLnADNT/QbnVe20J4FSX4xqFm2f1
+87Ukp0XiomZLf11eekQ2MAoGCCqGSM49BAMCA0gAMEUCIEg1fDI8b3hZAArgniVk
+HeM6puwgcMh5NXwvJ9x0unVmAiEAppecVTSQ+yEPyBG415Og6sK+RC78pcByEC81
+C/QSwRYxggFpMIIBZQIBATAUMA8xDTALBgNVBAMMBGNlcnQCAQEwDQYJYIZIAWUD
+BAIBBQCggeQwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx
+DxcNMjUwNDAzMDAwMDAxWjAvBgkqhkiG9w0BCQQxIgQg7XACtDnprIRfIjV9gius
+FERzD722AW0+yUMil7nsn3MweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASow
+CwYJYIZIAWUDBAEWMAsGCWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0D
+AgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwCgYI
+KoZIzj0EAwIESDBGAiEAssymc28HySAhg+XeWIpSbtzkwycr2JG6dzHRZ+vn0ocC
+IQCJVpo1FTLZOHSc9UpjS+VKR4cg50Iz0HiPyo6hwjCrwA==
+-----END PKCS7-----
+ EOF
+
+ cert = p7.certificates[0]
+ store = OpenSSL::X509::Store.new.tap { |store|
+ store.time = Time.utc(2025, 4, 3)
+ store.add_cert(cert)
+ }
+ assert_equal(true, p7.verify([], store))
+ assert_equal(1, p7.signers.size)
+ signer = p7.signers[0]
+ assert_in_delta(Time.utc(2025, 4, 3), signer.signed_time, 10)
end
def test_enveloped
+ omit_on_fips # PKCS #1 v1.5 padding
+
certs = [@ee1_cert, @ee2_cert]
cipher = OpenSSL::Cipher::AES.new("128-CBC")
data = "aaaaa\nbbbbb\nccccc\n"
@@ -144,15 +229,20 @@ class OpenSSL::TestPKCS7 < OpenSSL::TestCase
assert_equal(:enveloped, p7.type)
assert_equal(2, recip.size)
- assert_equal(@ca_cert.subject.to_s, recip[0].issuer.to_s)
- assert_equal(2, recip[0].serial)
- assert_equal(data, p7.decrypt(@rsa1024, @ee1_cert))
+ assert_equal(@ca_cert.subject, recip[0].issuer)
+ assert_equal(@ee1_cert.serial, recip[0].serial)
+ assert_equal(16, @ee1_key.decrypt(recip[0].enc_key).size)
+ assert_equal(data, p7.decrypt(@ee1_key, @ee1_cert))
- assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s)
- assert_equal(3, recip[1].serial)
- assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert))
+ assert_equal(@ca_cert.subject, recip[1].issuer)
+ assert_equal(@ee2_cert.serial, recip[1].serial)
+ assert_equal(data, p7.decrypt(@ee2_key, @ee2_cert))
- assert_equal(data, p7.decrypt(@rsa1024))
+ assert_equal(data, p7.decrypt(@ee1_key))
+
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) {
+ p7.decrypt(@ca_key, @ca_cert)
+ }
# Default cipher has been removed in v3.3
assert_raise_with_message(ArgumentError, /RC2-40-CBC/) {
@@ -160,9 +250,61 @@ class OpenSSL::TestPKCS7 < OpenSSL::TestCase
}
end
+ def test_enveloped_add_recipient
+ omit_on_fips # PKCS #1 v1.5 padding
+
+ data = "aaaaa\nbbbbb\nccccc\n"
+ ktri_ee1 = OpenSSL::PKCS7::RecipientInfo.new(@ee1_cert)
+ ktri_ee2 = OpenSSL::PKCS7::RecipientInfo.new(@ee2_cert)
+
+ tmp = OpenSSL::PKCS7.new
+ tmp.type = :enveloped
+ tmp.cipher = "AES-128-CBC"
+ tmp.add_recipient(ktri_ee1)
+ tmp.add_recipient(ktri_ee2)
+ tmp.add_data(data)
+
+ p7 = OpenSSL::PKCS7.new(tmp.to_der)
+ assert_equal(:enveloped, p7.type)
+ assert_equal(data, p7.decrypt(@ee1_key, @ee1_cert))
+ assert_equal(data, p7.decrypt(@ee2_key, @ee2_cert))
+ assert_equal([@ee1_cert.serial, @ee2_cert.serial].sort,
+ p7.recipients.map(&:serial).sort)
+ end
+
+ def test_data
+ asn1 = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::ObjectId("pkcs7-data"),
+ OpenSSL::ASN1::OctetString("content", 0, :EXPLICIT),
+ ])
+ p7 = OpenSSL::PKCS7.new
+ p7.type = :data
+ p7.data = "content"
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.add_certificate(@ee1_cert) }
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.certificates = [@ee1_cert] }
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.cipher = "aes-128-cbc" }
+ assert_equal(asn1.to_der, p7.to_der)
+
+ p7 = OpenSSL::PKCS7.new(asn1)
+ assert_equal(:data, p7.type)
+ assert_equal(false, p7.detached)
+ assert_equal(false, p7.detached?)
+ # Not applicable
+ assert_nil(p7.certificates)
+ assert_nil(p7.crls)
+ # Not applicable. Should they return nil or raise an exception instead?
+ assert_equal([], p7.signers)
+ assert_equal([], p7.recipients)
+ # PKCS7#verify can't distinguish verification failure and other errors
+ store = OpenSSL::X509::Store.new
+ assert_equal(false, p7.verify([@ee1_cert], store))
+ assert_match(/wrong content type|WRONG_CONTENT_TYPE/, p7.error_string)
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.decrypt(@ee1_key) }
+ end
+
def test_empty_signed_data_ruby_bug_19974
data = "-----BEGIN PKCS7-----\nMAsGCSqGSIb3DQEHAg==\n-----END PKCS7-----\n"
- assert_raise(ArgumentError) { OpenSSL::PKCS7.new(data) }
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { OpenSSL::PKCS7.new(data) }
data = <<END
MIME-Version: 1.0
@@ -176,8 +318,8 @@ END
end
def test_graceful_parsing_failure #[ruby-core:43250]
- contents = File.read(__FILE__)
- assert_raise(ArgumentError) { OpenSSL::PKCS7.new(contents) }
+ contents = "not a valid PKCS #7 PEM block"
+ assert_raise(OpenSSL::PKCS7::PKCS7Error) { OpenSSL::PKCS7.new(contents) }
end
def test_set_type_signed
@@ -198,12 +340,6 @@ END
assert_equal(:signedAndEnveloped, p7.type)
end
- def test_set_type_enveloped
- p7 = OpenSSL::PKCS7.new
- p7.type = "enveloped"
- assert_equal(:enveloped, p7.type)
- end
-
def test_set_type_encrypted
p7 = OpenSSL::PKCS7.new
p7.type = "encrypted"
@@ -211,12 +347,14 @@ END
end
def test_smime
+ pend "AWS-LC has no current support for SMIME with PKCS7" if aws_lc?
+
store = OpenSSL::X509::Store.new
store.add_cert(@ca_cert)
ca_certs = [@ca_cert]
data = "aaaaa\r\nbbbbb\r\nccccc\r\n"
- tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs)
+ tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, ca_certs)
p7 = OpenSSL::PKCS7.new(tmp.to_der)
smime = OpenSSL::PKCS7.write_smime(p7)
assert_equal(true, smime.start_with?(<<END))
@@ -233,6 +371,8 @@ END
end
def test_to_text
+ omit "AWS-LC does not support PKCS7.to_text" if aws_lc?
+
p7 = OpenSSL::PKCS7.new
p7.type = "signed"
assert_match(/signed/, p7.to_text)
@@ -275,78 +415,34 @@ END
end
end
- def test_split_content
- pki_message_pem = <<END
------BEGIN PKCS7-----
-MIIHSwYJKoZIhvcNAQcCoIIHPDCCBzgCAQExCzAJBgUrDgMCGgUAMIIDiAYJKoZI
-hvcNAQcBoIIDeQSCA3UwgAYJKoZIhvcNAQcDoIAwgAIBADGCARAwggEMAgEAMHUw
-cDEQMA4GA1UECgwHZXhhbXBsZTEXMBUGA1UEAwwOVEFSTUFDIFJPT1QgQ0ExIjAg
-BgkqhkiG9w0BCQEWE3NvbWVvbmVAZXhhbXBsZS5vcmcxCzAJBgNVBAYTAlVTMRIw
-EAYDVQQHDAlUb3duIEhhbGwCAWYwDQYJKoZIhvcNAQEBBQAEgYBspXXse8ZhG1FE
-E3PVAulbvrdR52FWPkpeLvSjgEkYzTiUi0CC3poUL1Ku5mOlavWAJgoJpFICDbvc
-N4ZNDCwOhnzoI9fMGmm1gvPQy15BdhhZRo9lP7Ga/Hg2APKT0/0yhPsmJ+w+u1e7
-OoJEVeEZ27x3+u745bGEcu8of5th6TCABgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcE
-CBNs2U5mMsd/oIAEggIQU6cur8QBz02/4eMpHdlU9IkyrRMiaMZ/ky9zecOAjnvY
-d2jZqS7RhczpaNJaSli3GmDsKrF+XqE9J58s9ScGqUigzapusTsxIoRUPr7Ztb0a
-pg8VWDipAsuw7GfEkgx868sV93uC4v6Isfjbhd+JRTFp/wR1kTi7YgSXhES+RLUW
-gQbDIDgEQYxJ5U951AJtnSpjs9za2ZkTdd8RSEizJK0bQ1vqLoApwAVgZqluATqQ
-AHSDCxhweVYw6+y90B9xOrqPC0eU7Wzryq2+Raq5ND2Wlf5/N11RQ3EQdKq/l5Te
-ijp9PdWPlkUhWVoDlOFkysjk+BE+7AkzgYvz9UvBjmZsMsWqf+KsZ4S8/30ndLzu
-iucsu6eOnFLLX8DKZxV6nYffZOPzZZL8hFBcE7PPgSdBEkazMrEBXq1j5mN7exbJ
-NOA5uGWyJNBMOCe+1JbxG9UeoqvCCTHESxEeDu7xR3NnSOD47n7cXwHr81YzK2zQ
-5oWpP3C8jzI7tUjLd1S0Z3Psd17oaCn+JOfUtuB0nc3wfPF/WPo0xZQodWxp2/Cl
-EltR6qr1zf5C7GwmLzBZ6bHFAIT60/JzV0/56Pn8ztsRFtI4cwaBfTfvnwi8/sD9
-/LYOMY+/b6UDCUSR7RTN7XfrtAqDEzSdzdJkOWm1jvM8gkLmxpZdvxG3ZvDYnEQE
-5Nq+un5nAny1wf3rWierBAjE5ntiAmgs5AAAAAAAAAAAAACgggHqMIIB5jCCAU+g
-AwIBAgIBATANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDEyQwQUM5RjAyNi1EQ0VB
-LTRDMTItOTEyNy1DMEZEN0QyQThCNUEwHhcNMTIxMDE5MDk0NTQ3WhcNMTMxMDE5
-MDk0NTQ3WjAvMS0wKwYDVQQDEyQwQUM5RjAyNi1EQ0VBLTRDMTItOTEyNy1DMEZE
-N0QyQThCNUEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALTsTNyGIsKvyw56
-WI3Gll/RmjsupkrdEtPbx7OjS9MEgyhOAf9+u6CV0LJGHpy7HUeROykF6xpbSdCm
-Mr6kNObl5N0ljOb8OmV4atKjmGg1rWawDLyDQ9Dtuby+dzfHtzAzP+J/3ZoOtSqq
-AHVTnCclU1pm/uHN0HZ5nL5iLJTvAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIFoDAN
-BgkqhkiG9w0BAQUFAAOBgQA8K+BouEV04HRTdMZd3akjTQOm6aEGW4nIRnYIf8ZV
-mvUpLirVlX/unKtJinhGisFGpuYLMpemx17cnGkBeLCQRvHQjC+ho7l8/LOGheMS
-nvu0XHhvmJtRbm8MKHhogwZqHFDnXonvjyqhnhEtK5F2Fimcce3MoF2QtEe0UWv/
-8DGCAaowggGmAgEBMDQwLzEtMCsGA1UEAxMkMEFDOUYwMjYtRENFQS00QzEyLTkx
-MjctQzBGRDdEMkE4QjVBAgEBMAkGBSsOAwIaBQCggc0wEgYKYIZIAYb4RQEJAjEE
-EwIxOTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0x
-MjEwMTkwOTQ1NDdaMCAGCmCGSAGG+EUBCQUxEgQQ2EFUJdQNwQDxclIQ8qNyYzAj
-BgkqhkiG9w0BCQQxFgQUy8GFXPpAwRJUT3rdvNC9Pn+4eoswOAYKYIZIAYb4RQEJ
-BzEqEygwRkU3QzJEQTVEMDc2NzFFOTcxNDlCNUE3MDRCMERDNkM4MDYwRDJBMA0G
-CSqGSIb3DQEBAQUABIGAWUNdzvU2iiQOtihBwF0h48Nnw/2qX8uRjg6CVTOMcGji
-BxjUMifEbT//KJwljshl4y3yBLqeVYLOd04k6aKSdjgdZnrnUPI6p5tL5PfJkTAE
-L6qflZ9YCU5erE4T5U98hCQBMh4nOYxgaTjnZzhpkKQuEiKq/755cjzTzlI/eok=
------END PKCS7-----
-END
- pki_message_content_pem = <<END
------BEGIN PKCS7-----
-MIIDawYJKoZIhvcNAQcDoIIDXDCCA1gCAQAxggEQMIIBDAIBADB1MHAxEDAOBgNV
-BAoMB2V4YW1wbGUxFzAVBgNVBAMMDlRBUk1BQyBST09UIENBMSIwIAYJKoZIhvcN
-AQkBFhNzb21lb25lQGV4YW1wbGUub3JnMQswCQYDVQQGEwJVUzESMBAGA1UEBwwJ
-VG93biBIYWxsAgFmMA0GCSqGSIb3DQEBAQUABIGAbKV17HvGYRtRRBNz1QLpW763
-UedhVj5KXi70o4BJGM04lItAgt6aFC9SruZjpWr1gCYKCaRSAg273DeGTQwsDoZ8
-6CPXzBpptYLz0MteQXYYWUaPZT+xmvx4NgDyk9P9MoT7JifsPrtXuzqCRFXhGdu8
-d/ru+OWxhHLvKH+bYekwggI9BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECBNs2U5m
-Msd/gIICGFOnLq/EAc9Nv+HjKR3ZVPSJMq0TImjGf5Mvc3nDgI572Hdo2aku0YXM
-6WjSWkpYtxpg7Cqxfl6hPSefLPUnBqlIoM2qbrE7MSKEVD6+2bW9GqYPFVg4qQLL
-sOxnxJIMfOvLFfd7guL+iLH424XfiUUxaf8EdZE4u2IEl4REvkS1FoEGwyA4BEGM
-SeVPedQCbZ0qY7Pc2tmZE3XfEUhIsyStG0Nb6i6AKcAFYGapbgE6kAB0gwsYcHlW
-MOvsvdAfcTq6jwtHlO1s68qtvkWquTQ9lpX+fzddUUNxEHSqv5eU3oo6fT3Vj5ZF
-IVlaA5ThZMrI5PgRPuwJM4GL8/VLwY5mbDLFqn/irGeEvP99J3S87ornLLunjpxS
-y1/AymcVep2H32Tj82WS/IRQXBOzz4EnQRJGszKxAV6tY+Zje3sWyTTgObhlsiTQ
-TDgnvtSW8RvVHqKrwgkxxEsRHg7u8UdzZ0jg+O5+3F8B6/NWMyts0OaFqT9wvI8y
-O7VIy3dUtGdz7Hde6Ggp/iTn1LbgdJ3N8Hzxf1j6NMWUKHVsadvwpRJbUeqq9c3+
-QuxsJi8wWemxxQCE+tPyc1dP+ej5/M7bERbSOHMGgX03758IvP7A/fy2DjGPv2+l
-AwlEke0Uze1367QKgxM0nc3SZDlptY7zPIJC5saWXb8Rt2bw2JxEBOTavrp+ZwJ8
-tcH961onq8Tme2ICaCzk
------END PKCS7-----
-END
- pki_msg = OpenSSL::PKCS7.new(pki_message_pem)
- store = OpenSSL::X509::Store.new
- pki_msg.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY)
- p7enc = OpenSSL::PKCS7.new(pki_msg.data)
- assert_equal(pki_message_content_pem, p7enc.to_pem)
+ def test_decode_ber_constructed_string
+ omit_on_fips # PKCS #1 v1.5 padding
+
+ p7 = OpenSSL::PKCS7.encrypt([@ee1_cert], "content", "aes-128-cbc")
+
+ # Make an equivalent BER to p7.to_der. Here we convert the encryptedContent
+ # field of EncryptedContentInfo into a constructed encoding using the
+ # indefinite length form.
+ # See https://www.rfc-editor.org/rfc/rfc2315#section-10.1
+ asn1 = OpenSSL::ASN1.decode(p7.to_der)
+ asn1.indefinite_length = true
+ enveloped_data_explicit_tag = asn1.value[1]
+ enveloped_data_explicit_tag.indefinite_length = true
+ enveloped_data = enveloped_data_explicit_tag.value[0]
+ enveloped_data.indefinite_length = true
+ encrypted_content_info = enveloped_data.value[2]
+ encrypted_content_info.indefinite_length = true
+ orig = encrypted_content_info.value[2]
+ encrypted_content_info.value[2] = OpenSSL::ASN1::ASN1Data.new([
+ OpenSSL::ASN1::OctetString(orig.value[...5]),
+ OpenSSL::ASN1::OctetString(orig.value[5...]),
+ ], 0, :CONTEXT_SPECIFIC).tap { |x| x.indefinite_length = true }
+
+ assert_not_equal(p7.to_der, asn1.to_der)
+ assert_equal(p7.to_der, OpenSSL::PKCS7.new(asn1.to_der).to_der)
+
+ assert_equal("content", OpenSSL::PKCS7.new(p7.to_der).decrypt(@ee1_key))
+ assert_equal("content", OpenSSL::PKCS7.new(asn1.to_der).decrypt(@ee1_key))
end
end
diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb
index f132b65882..93d9e1d42f 100644
--- a/test/openssl/test_pkey.rb
+++ b/test/openssl/test_pkey.rb
@@ -8,17 +8,7 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
assert_instance_of OpenSSL::PKey::RSA, rsa
assert_equal "rsaEncryption", rsa.oid
assert_match %r{oid=rsaEncryption}, rsa.inspect
- end
-
- def test_generic_oid_inspect_x25519
- omit "X25519 not supported" if openssl? && !openssl?(1, 1, 0)
- omit_on_fips
-
- # X25519 private key
- x25519 = OpenSSL::PKey.generate_key("X25519")
- assert_instance_of OpenSSL::PKey::PKey, x25519
- assert_equal "X25519", x25519.oid
- assert_match %r{oid=X25519}, x25519.inspect
+ assert_match %r{type_name=RSA}, rsa.inspect if openssl?(3, 0, 0)
end
def test_s_generate_parameters
@@ -70,10 +60,115 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
assert_not_equal nil, pkey.private_key
end
+ def test_s_read_pem_unknown_block
+ # A PEM-encoded certificate and a PEM-encoded private key are combined.
+ # Check that OSSL_STORE doesn't stop after the first PEM block.
+ orig = Fixtures.pkey("rsa-1")
+ subject = OpenSSL::X509::Name.new([["CN", "test"]])
+ cert = issue_cert(subject, orig, 1, [], nil, nil)
+
+ input = cert.to_text + cert.to_pem + orig.to_text + orig.private_to_pem
+ pkey = OpenSSL::PKey.read(input)
+ assert_equal(orig.private_to_der, pkey.private_to_der)
+ end
+
+ def test_s_read_der_then_pem
+ # If the input is valid as both DER and PEM (which allows garbage data
+ # before and after the block), it is read as DER
+ #
+ # TODO: Garbage data after DER should not be allowed, but it is currently
+ # ignored
+ orig1 = Fixtures.pkey("rsa-1")
+ orig2 = Fixtures.pkey("rsa-2")
+ pkey = OpenSSL::PKey.read(orig1.public_to_der + orig2.private_to_pem)
+ assert_equal(orig1.public_to_der, pkey.public_to_der)
+ assert_not_predicate(pkey, :private?)
+ end
+
+ def test_s_read_passphrase
+ orig = Fixtures.pkey("rsa-1")
+ encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase")
+ assert_match(/\A-----BEGIN ENCRYPTED PRIVATE KEY-----/, encrypted_pem)
+
+ # Correct passphrase passed as the second argument
+ pkey1 = OpenSSL::PKey.read(encrypted_pem, "correct_passphrase")
+ assert_equal(orig.private_to_der, pkey1.private_to_der)
+
+ # Correct passphrase returned by the block. The block gets false
+ called = 0
+ flag = nil
+ pkey2 = OpenSSL::PKey.read(encrypted_pem) { |f|
+ called += 1
+ flag = f
+ "correct_passphrase"
+ }
+ assert_equal(orig.private_to_der, pkey2.private_to_der)
+ assert_equal(1, called)
+ assert_false(flag)
+
+ # Incorrect passphrase passed. The block is not called
+ called = 0
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey.read(encrypted_pem, "incorrect_passphrase") {
+ called += 1
+ }
+ }
+ assert_equal(0, called)
+
+ # Incorrect passphrase returned by the block. The block is called only once
+ called = 0
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey.read(encrypted_pem) {
+ called += 1
+ "incorrect_passphrase"
+ }
+ }
+ assert_equal(1, called)
+ end
+
+ def test_s_read_passphrase_tty
+ omit "https://github.com/aws/aws-lc/pull/2555" if aws_lc?
+
+ orig = Fixtures.pkey("rsa-1")
+ encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase")
+
+ # Correct passphrase passed to OpenSSL's prompt
+ script = <<~"end;"
+ require "openssl"
+ Process.setsid
+ OpenSSL::PKey.read(#{encrypted_pem.dump})
+ puts "ok"
+ end;
+ assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"],
+ "correct_passphrase\n") { |stdout, stderr|
+ assert_equal(["Enter PEM pass phrase:"], stderr)
+ assert_equal(["ok"], stdout)
+ }
+
+ # Incorrect passphrase passed to OpenSSL's prompt
+ script = <<~"end;"
+ require "openssl"
+ Process.setsid
+ begin
+ OpenSSL::PKey.read(#{encrypted_pem.dump})
+ rescue OpenSSL::PKey::PKeyError
+ puts "ok"
+ else
+ puts "expected OpenSSL::PKey::PKeyError"
+ end
+ end;
+ stdin = "incorrect_passphrase\n" * 5
+ assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"],
+ stdin) { |stdout, stderr|
+ assert_equal(1, stderr.count("Enter PEM pass phrase:"))
+ assert_equal(["ok"], stdout)
+ }
+ end if ENV["OSSL_TEST_ALL"] == "1" && Process.respond_to?(:setsid)
+
def test_hmac_sign_verify
- pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "abcd" })
+ pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "a"*32 })
- hmac = OpenSSL::HMAC.new("abcd", "SHA256").update("data").digest
+ hmac = OpenSSL::HMAC.new("a"*32, "SHA256").update("data").digest
assert_equal hmac, pkey.sign("SHA256", "data")
# EVP_PKEY_HMAC does not support verify
@@ -85,7 +180,6 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
def test_ed25519
# Ed25519 is not FIPS-approved.
omit_on_fips
- omit "Ed25519 not supported" if openssl? && !openssl?(1, 1, 1)
# Test vector from RFC 8032 Section 7.1 TEST 2
priv_pem = <<~EOF
@@ -136,7 +230,6 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
end
def test_x25519
- omit "X25519 not supported" if openssl? && !openssl?(1, 1, 0)
omit_on_fips
# Test vector from RFC 7748 Section 6.1
@@ -155,13 +248,12 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
alice = OpenSSL::PKey.read(alice_pem)
bob = OpenSSL::PKey.read(bob_pem)
assert_instance_of OpenSSL::PKey::PKey, alice
+ assert_equal "X25519", alice.oid
+ assert_match %r{oid=X25519}, alice.inspect
assert_equal alice_pem, alice.private_to_pem
assert_equal bob_pem, bob.public_to_pem
assert_equal [shared_secret].pack("H*"), alice.derive(bob)
- if openssl? && !openssl?(1, 1, 1)
- omit "running OpenSSL version does not have raw public key support"
- end
alice_private = OpenSSL::PKey.new_raw_private_key("X25519", alice.raw_private_key)
bob_public = OpenSSL::PKey.new_raw_public_key("X25519", bob.raw_public_key)
assert_equal alice_private.private_to_pem,
@@ -174,9 +266,26 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
bob.raw_public_key.unpack1("H*")
end
- def test_raw_initialize_errors
- omit "Ed25519 not supported" if openssl? && !openssl?(1, 1, 1)
+ def test_ml_dsa
+ # AWS-LC also supports ML-DSA, but it's implemented in a different way
+ return unless openssl?(3, 5, 0)
+
+ pkey = OpenSSL::PKey.generate_key("ML-DSA-44")
+ assert_match(/type_name=ML-DSA-44/, pkey.inspect)
+ sig = pkey.sign(nil, "data")
+ assert_equal(2420, sig.bytesize)
+ assert_equal(true, pkey.verify(nil, sig, "data"))
+
+ pub2 = OpenSSL::PKey.read(pkey.public_to_der)
+ assert_equal(true, pub2.verify(nil, sig, "data"))
+
+ raw_public_key = pkey.raw_public_key
+ assert_equal(1312, raw_public_key.bytesize)
+ pub3 = OpenSSL::PKey.new_raw_public_key("ML-DSA-44", raw_public_key)
+ assert_equal(true, pub3.verify(nil, sig, "data"))
+ end
+ def test_raw_initialize_errors
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") }
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") }
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("foo123", "xxx") }
@@ -184,10 +293,10 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
end
def test_compare?
- key1 = Fixtures.pkey("rsa1024")
- key2 = Fixtures.pkey("rsa1024")
- key3 = Fixtures.pkey("rsa2048")
- key4 = Fixtures.pkey("dh-1")
+ key1 = Fixtures.pkey("rsa-1")
+ key2 = Fixtures.pkey("rsa-1")
+ key3 = Fixtures.pkey("rsa-2")
+ key4 = Fixtures.pkey("p256")
assert_equal(true, key1.compare?(key2))
assert_equal(true, key1.public_key.compare?(key2))
@@ -202,7 +311,14 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
end
def test_to_text
- rsa = Fixtures.pkey("rsa1024")
+ rsa = Fixtures.pkey("rsa-1")
assert_include rsa.to_text, "publicExponent"
end
+
+ def test_legacy_error_classes
+ assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::DSAError)
+ assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::DHError)
+ assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::ECError)
+ assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::RSAError)
+ end
end
diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb
index d32ffaf6b1..cd13283a2a 100644
--- a/test/openssl/test_pkey_dh.rb
+++ b/test/openssl/test_pkey_dh.rb
@@ -4,36 +4,43 @@ require_relative 'utils'
if defined?(OpenSSL) && defined?(OpenSSL::PKey::DH)
class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
- NEW_KEYLEN = 2048
-
def test_new_empty
- dh = OpenSSL::PKey::DH.new
- assert_equal nil, dh.p
- assert_equal nil, dh.priv_key
+ # pkeys are immutable with OpenSSL >= 3.0
+ if openssl?(3, 0, 0)
+ assert_raise(ArgumentError) { OpenSSL::PKey::DH.new }
+ else
+ dh = OpenSSL::PKey::DH.new
+ assert_nil(dh.p)
+ assert_nil(dh.priv_key)
+ end
end
def test_new_generate
- # This test is slow
- dh = OpenSSL::PKey::DH.new(NEW_KEYLEN)
- assert_key(dh)
- end if ENV["OSSL_TEST_ALL"]
-
- def test_new_break_on_non_fips
- omit_on_fips
-
- assert_nil(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break })
- assert_raise(RuntimeError) do
- OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise }
+ begin
+ dh1 = OpenSSL::PKey::DH.new(512)
+ rescue OpenSSL::PKey::PKeyError
+ omit "generating 512-bit DH parameters failed; " \
+ "likely not supported by this OpenSSL build"
+ end
+ assert_equal(512, dh1.p.num_bits)
+ assert_key(dh1)
+
+ dh2 = OpenSSL::PKey::DH.generate(512)
+ assert_equal(512, dh2.p.num_bits)
+ assert_key(dh2)
+ assert_not_equal(dh1.p, dh2.p)
+ end if ENV["OSSL_TEST_ALL"] == "1"
+
+ def test_new_break
+ unless openssl? && OpenSSL.fips_mode
+ assert_raise(RuntimeError) do
+ OpenSSL::PKey::DH.new(2048) { raise }
+ end
+ else
+ # The block argument is not executed in FIPS case.
+ # See https://github.com/ruby/openssl/issues/692 for details.
+ assert_kind_of(OpenSSL::PKey::DH, OpenSSL::PKey::DH.new(2048) { raise })
end
- end
-
- def test_new_break_on_fips
- omit_on_non_fips
-
- # The block argument is not executed in FIPS case.
- # See https://github.com/ruby/openssl/issues/692 for details.
- assert(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break })
- assert(OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise })
end
def test_derive_key
@@ -55,15 +62,15 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
end
def test_DHparams
- dh = Fixtures.pkey("dh2048_ffdhe2048")
- dh_params = dh.public_key
+ dh_params = Fixtures.pkey("dh2048_ffdhe2048")
asn1 = OpenSSL::ASN1::Sequence([
- OpenSSL::ASN1::Integer(dh.p),
- OpenSSL::ASN1::Integer(dh.g)
+ OpenSSL::ASN1::Integer(dh_params.p),
+ OpenSSL::ASN1::Integer(dh_params.g)
])
+ assert_equal(asn1.to_der, dh_params.to_der)
key = OpenSSL::PKey::DH.new(asn1.to_der)
- assert_same_dh dh_params, key
+ assert_same_dh_params(dh_params, key)
pem = <<~EOF
-----BEGIN DH PARAMETERS-----
@@ -75,14 +82,20 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----
EOF
+ assert_equal(pem, dh_params.export)
key = OpenSSL::PKey::DH.new(pem)
- assert_same_dh dh_params, key
+ assert_same_dh_params(dh_params, key)
+ assert_no_key(key)
key = OpenSSL::PKey.read(pem)
- assert_same_dh dh_params, key
-
- assert_equal asn1.to_der, dh.to_der
- assert_equal pem, dh.export
+ assert_same_dh_params(dh_params, key)
+ assert_no_key(key)
+
+ key = OpenSSL::PKey.generate_key(dh_params)
+ assert_same_dh_params(dh_params, key)
+ assert_key(key)
+ assert_equal(dh_params.to_der, key.to_der)
+ assert_equal(dh_params.to_pem, key.to_pem)
end
def test_public_key
@@ -95,23 +108,25 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
def test_generate_key
# Deprecated in v3.0.0; incompatible with OpenSSL 3.0
- # Creates a copy with params only
- dh = Fixtures.pkey("dh2048_ffdhe2048").public_key
+ dh = Fixtures.pkey("dh2048_ffdhe2048")
assert_no_key(dh)
dh.generate_key!
assert_key(dh)
- dh2 = dh.public_key
+ dh2 = OpenSSL::PKey::DH.new(dh.to_der)
dh2.generate_key!
+ assert_not_equal(dh.pub_key, dh2.pub_key)
assert_equal(dh.compute_key(dh2.pub_key), dh2.compute_key(dh.pub_key))
end if !openssl?(3, 0, 0)
def test_params_ok?
+ omit_on_fips
+
# Skip the tests in old OpenSSL version 1.1.1c or early versions before
# applying the following commits in OpenSSL 1.1.1d to make `DH_check`
# function pass the RFC 7919 FFDHE group texts.
# https://github.com/openssl/openssl/pull/9435
- unless openssl?(1, 1, 1, 4)
+ if openssl? && !openssl?(1, 1, 1, 4)
pend 'DH check for RFC 7919 FFDHE group texts is not implemented'
end
@@ -123,11 +138,41 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
]))
assert_equal(true, dh1.params_ok?)
- dh2 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
- OpenSSL::ASN1::Integer(dh0.p + 1),
- OpenSSL::ASN1::Integer(dh0.g)
- ]))
- assert_equal(false, dh2.params_ok?)
+ # AWS-LC automatically does parameter checks on the parsed params.
+ if aws_lc?
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(dh0.p + 1),
+ OpenSSL::ASN1::Integer(dh0.g)
+ ]))
+ }
+ else
+ dh2 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer(dh0.p + 1),
+ OpenSSL::ASN1::Integer(dh0.g)
+ ]))
+ assert_equal(false, dh2.params_ok?)
+ end
+
+ end
+
+ def test_params
+ dh = Fixtures.pkey("dh2048_ffdhe2048")
+ assert_kind_of(OpenSSL::BN, dh.p)
+ assert_equal(dh.p, dh.params["p"])
+ assert_kind_of(OpenSSL::BN, dh.g)
+ assert_equal(dh.g, dh.params["g"])
+ assert_nil(dh.pub_key)
+ assert_nil(dh.params["pub_key"])
+ assert_nil(dh.priv_key)
+ assert_nil(dh.params["priv_key"])
+
+ dhkey = OpenSSL::PKey.generate_key(dh)
+ assert_equal(dh.params["p"], dhkey.params["p"])
+ assert_kind_of(OpenSSL::BN, dhkey.pub_key)
+ assert_equal(dhkey.pub_key, dhkey.params["pub_key"])
+ assert_kind_of(OpenSSL::BN, dhkey.priv_key)
+ assert_equal(dhkey.priv_key, dhkey.params["priv_key"])
end
def test_dup
@@ -176,14 +221,14 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
end
def assert_key(dh)
- assert(dh.public?)
- assert(dh.private?)
- assert(dh.pub_key)
- assert(dh.priv_key)
+ assert_true(dh.public?)
+ assert_true(dh.private?)
+ assert_kind_of(OpenSSL::BN, dh.pub_key)
+ assert_kind_of(OpenSSL::BN, dh.priv_key)
end
- def assert_same_dh(expected, key)
- check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key])
+ def assert_same_dh_params(expected, key)
+ check_component(expected, key, [:p, :q, :g])
end
end
diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb
index 3e8a83b2d0..1ec0bf0b4d 100644
--- a/test/openssl/test_pkey_dsa.rb
+++ b/test/openssl/test_pkey_dsa.rb
@@ -10,7 +10,7 @@ class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
end
def test_private
- key = Fixtures.pkey("dsa1024")
+ key = Fixtures.pkey("dsa2048")
assert_equal true, key.private?
key2 = OpenSSL::PKey::DSA.new(key.to_der)
assert_equal true, key2.private?
@@ -33,6 +33,17 @@ class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
end
end
+ def test_new_empty
+ # pkeys are immutable with OpenSSL >= 3.0
+ if openssl?(3, 0, 0)
+ assert_raise(ArgumentError) { OpenSSL::PKey::DSA.new }
+ else
+ key = OpenSSL::PKey::DSA.new
+ assert_nil(key.p)
+ assert_raise(OpenSSL::PKey::PKeyError) { key.to_der }
+ end
+ end
+
def test_generate
# DSA.generate used to call DSA_generate_parameters_ex(), which adjusts the
# size of q according to the size of p
@@ -41,11 +52,11 @@ class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
assert_equal 1024, key1024.p.num_bits
assert_equal 160, key1024.q.num_bits
- key2048 = OpenSSL::PKey::DSA.generate(2048)
- assert_equal 2048, key2048.p.num_bits
- assert_equal 256, key2048.q.num_bits
-
if ENV["OSSL_TEST_ALL"] == "1" # slow
+ key2048 = OpenSSL::PKey::DSA.generate(2048)
+ assert_equal 2048, key2048.p.num_bits
+ assert_equal 256, key2048.q.num_bits
+
key3072 = OpenSSL::PKey::DSA.generate(3072)
assert_equal 3072, key3072.p.num_bits
assert_equal 256, key3072.q.num_bits
@@ -86,122 +97,93 @@ class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
sig = key.syssign(digest)
assert_equal true, key.sysverify(digest, sig)
assert_equal false, key.sysverify(digest, invalid_sig)
- assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) }
+ assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) }
assert_equal true, key.verify_raw(nil, sig, digest)
assert_equal false, key.verify_raw(nil, invalid_sig, digest)
- assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) }
+ assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) }
# Sign by #sign_raw
sig = key.sign_raw(nil, digest)
assert_equal true, key.sysverify(digest, sig)
assert_equal false, key.sysverify(digest, invalid_sig)
- assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) }
+ assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) }
assert_equal true, key.verify_raw(nil, sig, digest)
assert_equal false, key.verify_raw(nil, invalid_sig, digest)
- assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) }
+ assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) }
end
def test_DSAPrivateKey
# OpenSSL DSAPrivateKey format; similar to RSAPrivateKey
- dsa512 = Fixtures.pkey("dsa512")
+ orig = Fixtures.pkey("dsa2048")
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Integer(0),
- OpenSSL::ASN1::Integer(dsa512.p),
- OpenSSL::ASN1::Integer(dsa512.q),
- OpenSSL::ASN1::Integer(dsa512.g),
- OpenSSL::ASN1::Integer(dsa512.pub_key),
- OpenSSL::ASN1::Integer(dsa512.priv_key)
+ OpenSSL::ASN1::Integer(orig.p),
+ OpenSSL::ASN1::Integer(orig.q),
+ OpenSSL::ASN1::Integer(orig.g),
+ OpenSSL::ASN1::Integer(orig.pub_key),
+ OpenSSL::ASN1::Integer(orig.priv_key)
])
key = OpenSSL::PKey::DSA.new(asn1.to_der)
assert_predicate key, :private?
- assert_same_dsa dsa512, key
-
- pem = <<~EOF
- -----BEGIN DSA PRIVATE KEY-----
- MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok
- RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D
- AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR
- S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++
- Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
- 55jreJD3Se3slps=
- -----END DSA PRIVATE KEY-----
- EOF
+ assert_same_dsa orig, key
+
+ pem = der_to_pem(asn1.to_der, "DSA PRIVATE KEY")
key = OpenSSL::PKey::DSA.new(pem)
- assert_same_dsa dsa512, key
+ assert_same_dsa orig, key
- assert_equal asn1.to_der, dsa512.to_der
- assert_equal pem, dsa512.export
+ assert_equal asn1.to_der, orig.to_der
+ assert_equal pem, orig.export
end
def test_DSAPrivateKey_encrypted
- # key = abcdef
- dsa512 = Fixtures.pkey("dsa512")
- pem = <<~EOF
- -----BEGIN DSA PRIVATE KEY-----
- Proc-Type: 4,ENCRYPTED
- DEK-Info: AES-128-CBC,F8BB7BFC7EAB9118AC2E3DA16C8DB1D9
-
- D2sIzsM9MLXBtlF4RW42u2GB9gX3HQ3prtVIjWPLaKBYoToRUiv8WKsjptfZuLSB
- 74ZPdMS7VITM+W1HIxo/tjS80348Cwc9ou8H/E6WGat8ZUk/igLOUEII+coQS6qw
- QpuLMcCIavevX0gjdjEIkojBB81TYDofA1Bp1z1zDI/2Zhw822xapI79ZF7Rmywt
- OSyWzFaGipgDpdFsGzvT6//z0jMr0AuJVcZ0VJ5lyPGQZAeVBlbYEI4T72cC5Cz7
- XvLiaUtum6/sASD2PQqdDNpgx/WA6Vs1Po2kIUQIM5TIwyJI0GdykZcYm6xIK/ta
- Wgx6c8K+qBAIVrilw3EWxw==
- -----END DSA PRIVATE KEY-----
- EOF
+ # OpenSSL DSAPrivateKey with OpenSSL encryption
+ orig = Fixtures.pkey("dsa2048")
+
+ pem = der_to_encrypted_pem(orig.to_der, "DSA PRIVATE KEY", "abcdef")
key = OpenSSL::PKey::DSA.new(pem, "abcdef")
- assert_same_dsa dsa512, key
+ assert_same_dsa orig, key
key = OpenSSL::PKey::DSA.new(pem) { "abcdef" }
- assert_same_dsa dsa512, key
+ assert_same_dsa orig, key
cipher = OpenSSL::Cipher.new("aes-128-cbc")
- exported = dsa512.to_pem(cipher, "abcdef\0\1")
- assert_same_dsa dsa512, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1")
- assert_raise(OpenSSL::PKey::DSAError) {
+ exported = orig.to_pem(cipher, "abcdef\0\1")
+ assert_same_dsa orig, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1")
+ assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey::DSA.new(exported, "abcdef")
}
end
def test_PUBKEY
- dsa512 = Fixtures.pkey("dsa512")
- dsa512pub = OpenSSL::PKey::DSA.new(dsa512.public_to_der)
+ orig = Fixtures.pkey("dsa2048")
+ pub = OpenSSL::PKey::DSA.new(orig.public_to_der)
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::ObjectId("DSA"),
OpenSSL::ASN1::Sequence([
- OpenSSL::ASN1::Integer(dsa512.p),
- OpenSSL::ASN1::Integer(dsa512.q),
- OpenSSL::ASN1::Integer(dsa512.g)
+ OpenSSL::ASN1::Integer(orig.p),
+ OpenSSL::ASN1::Integer(orig.q),
+ OpenSSL::ASN1::Integer(orig.g)
])
]),
OpenSSL::ASN1::BitString(
- OpenSSL::ASN1::Integer(dsa512.pub_key).to_der
+ OpenSSL::ASN1::Integer(orig.pub_key).to_der
)
])
key = OpenSSL::PKey::DSA.new(asn1.to_der)
assert_not_predicate key, :private?
- assert_same_dsa dsa512pub, key
-
- pem = <<~EOF
- -----BEGIN PUBLIC KEY-----
- MIHxMIGoBgcqhkjOOAQBMIGcAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgT
- YiEEHaOYhkIxv0OkRZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB
- 4DZGH7UyarcaGy6DAkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqo
- ji3/lHdKoVdTQNuRS/m6DlCwhjRjiQ/lBRgCLCcaA0QAAkEAjN891JBjzpMj4bWg
- sACmMggFf57DS0Ti+5++Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxX
- oXi9OA==
- -----END PUBLIC KEY-----
- EOF
+ assert_same_dsa pub, key
+
+ pem = der_to_pem(asn1.to_der, "PUBLIC KEY")
key = OpenSSL::PKey::DSA.new(pem)
- assert_same_dsa dsa512pub, key
+ assert_same_dsa pub, key
assert_equal asn1.to_der, key.to_der
assert_equal pem, key.export
- assert_equal asn1.to_der, dsa512.public_to_der
+ assert_equal asn1.to_der, orig.public_to_der
assert_equal asn1.to_der, key.public_to_der
- assert_equal pem, dsa512.public_to_pem
+ assert_equal pem, orig.public_to_pem
assert_equal pem, key.public_to_pem
end
@@ -230,8 +212,29 @@ fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ==
assert_equal(nil, key.priv_key)
end
+ def test_params
+ key = Fixtures.pkey("dsa2048")
+ assert_kind_of(OpenSSL::BN, key.p)
+ assert_equal(key.p, key.params["p"])
+ assert_kind_of(OpenSSL::BN, key.q)
+ assert_equal(key.q, key.params["q"])
+ assert_kind_of(OpenSSL::BN, key.g)
+ assert_equal(key.g, key.params["g"])
+ assert_kind_of(OpenSSL::BN, key.pub_key)
+ assert_equal(key.pub_key, key.params["pub_key"])
+ assert_kind_of(OpenSSL::BN, key.priv_key)
+ assert_equal(key.priv_key, key.params["priv_key"])
+
+ pubkey = OpenSSL::PKey.read(key.public_to_der)
+ assert_equal(key.params["p"], pubkey.params["p"])
+ assert_equal(key.pub_key, pubkey.pub_key)
+ assert_equal(key.pub_key, pubkey.params["pub_key"])
+ assert_nil(pubkey.priv_key)
+ assert_nil(pubkey.params["priv_key"])
+ end
+
def test_dup
- key = Fixtures.pkey("dsa1024")
+ key = Fixtures.pkey("dsa2048")
key2 = key.dup
assert_equal key.params, key2.params
@@ -243,7 +246,7 @@ fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ==
end
def test_marshal
- key = Fixtures.pkey("dsa1024")
+ key = Fixtures.pkey("dsa2048")
deserialized = Marshal.load(Marshal.dump(key))
assert_equal key.to_der, deserialized.to_der
diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb
index 5a15c54415..ec97a747a3 100644
--- a/test/openssl/test_pkey_ec.rb
+++ b/test/openssl/test_pkey_ec.rb
@@ -4,19 +4,9 @@ require_relative 'utils'
if defined?(OpenSSL)
class OpenSSL::TestEC < OpenSSL::PKeyTestCase
- def test_ec_key
+ def test_ec_key_new
key1 = OpenSSL::PKey::EC.generate("prime256v1")
- # PKey is immutable in OpenSSL >= 3.0; constructing an empty EC object is
- # deprecated
- if !openssl?(3, 0, 0)
- key2 = OpenSSL::PKey::EC.new
- key2.group = key1.group
- key2.private_key = key1.private_key
- key2.public_key = key1.public_key
- assert_equal key1.to_der, key2.to_der
- end
-
key3 = OpenSSL::PKey::EC.new(key1)
assert_equal key1.to_der, key3.to_der
@@ -35,6 +25,23 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
end
end
+ def test_ec_key_new_empty
+ # pkeys are immutable with OpenSSL >= 3.0; constructing an empty EC object is
+ # disallowed
+ if openssl?(3, 0, 0)
+ assert_raise(ArgumentError) { OpenSSL::PKey::EC.new }
+ else
+ key = OpenSSL::PKey::EC.new
+ assert_nil(key.group)
+
+ p256 = Fixtures.pkey("p256")
+ key.group = p256.group
+ key.private_key = p256.private_key
+ key.public_key = p256.public_key
+ assert_equal(p256.to_der, key.to_der)
+ end
+ end
+
def test_builtin_curves
builtin_curves = OpenSSL::PKey::EC.builtin_curves
assert_not_empty builtin_curves
@@ -47,7 +54,9 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
end
def test_generate
- assert_raise(OpenSSL::PKey::ECError) { OpenSSL::PKey::EC.generate("non-existent") }
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ OpenSSL::PKey::EC.generate("non-existent")
+ }
g = OpenSSL::PKey::EC::Group.new("prime256v1")
ec = OpenSSL::PKey::EC.generate(g)
assert_equal(true, ec.private?)
@@ -58,7 +67,7 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
def test_generate_key
ec = OpenSSL::PKey::EC.new("prime256v1")
assert_equal false, ec.private?
- assert_raise(OpenSSL::PKey::ECError) { ec.to_der }
+ assert_raise(OpenSSL::PKey::PKeyError) { ec.to_der }
ec.generate_key!
assert_equal true, ec.private?
assert_nothing_raised { ec.to_der }
@@ -72,6 +81,8 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
end
def test_check_key
+ omit_on_fips
+
key0 = Fixtures.pkey("p256")
assert_equal(true, key0.check_key)
assert_equal(true, key0.private?)
@@ -89,19 +100,24 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
# Behavior of EVP_PKEY_public_check changes between OpenSSL 1.1.1 and 3.0
# The public key does not match the private key
- key4 = OpenSSL::PKey.read(<<~EOF)
+ ec_key_data = <<~EOF
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIP+TT0V8Fndsnacji9tyf6hmhHywcOWTee9XkiBeJoVloAoGCCqGSM49
AwEHoUQDQgAEBkhhJIU/2/YdPSlY2I1k25xjK4trr5OXSgXvBC21PtY0HQ7lor7A
jzT0giJITqmcd81fwGw5+96zLcdxTF1hVQ==
-----END EC PRIVATE KEY-----
EOF
- assert_raise(OpenSSL::PKey::ECError) { key4.check_key }
+ if aws_lc? # AWS-LC automatically does key checks on the parsed key.
+ assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(ec_key_data) }
+ else
+ key4 = OpenSSL::PKey.read(ec_key_data)
+ assert_raise(OpenSSL::PKey::PKeyError) { key4.check_key }
+ end
# EC#private_key= is deprecated in 3.0 and won't work on OpenSSL 3.0
if !openssl?(3, 0, 0)
key2.private_key += 1
- assert_raise(OpenSSL::PKey::ECError) { key2.check_key }
+ assert_raise(OpenSSL::PKey::PKeyError) { key2.check_key }
end
end
@@ -147,19 +163,19 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
sig = key.dsa_sign_asn1(data1)
assert_equal true, key.dsa_verify_asn1(data1, sig)
assert_equal false, key.dsa_verify_asn1(data2, sig)
- assert_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) }
+ assert_sign_verify_false_or_error { key.dsa_verify_asn1(data1, malformed_sig) }
assert_equal true, key.verify_raw(nil, sig, data1)
assert_equal false, key.verify_raw(nil, sig, data2)
- assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, data1) }
+ assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, data1) }
# Sign by #sign_raw
sig = key.sign_raw(nil, data1)
assert_equal true, key.dsa_verify_asn1(data1, sig)
assert_equal false, key.dsa_verify_asn1(data2, sig)
- assert_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) }
+ assert_sign_verify_false_or_error { key.dsa_verify_asn1(data1, malformed_sig) }
assert_equal true, key.verify_raw(nil, sig, data1)
assert_equal false, key.verify_raw(nil, sig, data2)
- assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, data1) }
+ assert_sign_verify_false_or_error{ key.verify_raw(nil, malformed_sig, data1) }
end
def test_dsa_sign_asn1_FIPS186_3
@@ -255,7 +271,7 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
cipher = OpenSSL::Cipher.new("aes-128-cbc")
exported = p256.to_pem(cipher, "abcdef\0\1")
assert_same_ec p256, OpenSSL::PKey::EC.new(exported, "abcdef\0\1")
- assert_raise(OpenSSL::PKey::ECError) {
+ assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey::EC.new(exported, "abcdef")
}
end
@@ -304,7 +320,10 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
assert_equal group1.to_der, group2.to_der
assert_equal group1, group2
group2.asn1_flag ^=OpenSSL::PKey::EC::NAMED_CURVE
- assert_not_equal group1.to_der, group2.to_der
+ # AWS-LC does not support serializing explicit curves.
+ unless aws_lc?
+ assert_not_equal group1.to_der, group2.to_der
+ end
assert_equal group1, group2
group3 = group1.dup
@@ -326,6 +345,15 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
assert_equal group1.degree, group4.degree
end
+ def test_ec_group_initialize_error_message
+ # Test that passing 2 arguments raises the helpful error
+ e = assert_raise(ArgumentError) do
+ OpenSSL::PKey::EC::Group.new(:GFp, 123)
+ end
+
+ assert_equal("wrong number of arguments (given 2, expected 1 or 4)", e.message)
+ end
+
def test_ec_point
group = OpenSSL::PKey::EC::Group.new("prime256v1")
key = OpenSSL::PKey::EC.generate(group)
@@ -350,18 +378,26 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
point2.to_octet_string(:uncompressed)
assert_equal point2.to_octet_string(:uncompressed),
point3.to_octet_string(:uncompressed)
+ end
+ def test_small_curve
begin
group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
group.point_conversion_form = :uncompressed
generator = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 }))
group.set_generator(generator, 19, 1)
- point = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
rescue OpenSSL::PKey::EC::Group::Error
pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message
raise
end
-
+ assert_equal 17.to_bn.num_bits, group.degree
+ assert_equal B(%w{ 04 05 01 }),
+ group.generator.to_octet_string(:uncompressed)
+ assert_equal 19.to_bn, group.order
+ assert_equal 1.to_bn, group.cofactor
+ assert_nil group.curve_name
+
+ point = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 }))
assert_equal 0x040603.to_bn, point.to_bn
assert_equal 0x040603.to_bn, point.to_bn(:uncompressed)
assert_equal 0x0306.to_bn, point.to_bn(:compressed)
@@ -425,28 +461,6 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
# 3 * (6, 3) + 3 * (5, 1) = (7, 6)
result_a2 = point_a.mul(3, 3)
assert_equal B(%w{ 04 07 06 }), result_a2.to_octet_string(:uncompressed)
- EnvUtil.suppress_warning do # Point#mul(ary, ary [, bn]) is deprecated
- begin
- result_b1 = point_a.mul([3], [])
- rescue NotImplementedError
- # LibreSSL and OpenSSL 3.0 do no longer support this form of calling
- next
- end
-
- # 3 * point_a = 3 * (6, 3) = (16, 13)
- result_b1 = point_a.mul([3], [])
- assert_equal B(%w{ 04 10 0D }), result_b1.to_octet_string(:uncompressed)
- # 3 * point_a + 2 * point_a = 3 * (6, 3) + 2 * (6, 3) = (7, 11)
- result_b1 = point_a.mul([3, 2], [point_a])
- assert_equal B(%w{ 04 07 0B }), result_b1.to_octet_string(:uncompressed)
- # 3 * point_a + 5 * point_a.group.generator = 3 * (6, 3) + 5 * (5, 1) = (13, 10)
- result_b1 = point_a.mul([3], [], 5)
- assert_equal B(%w{ 04 0D 0A }), result_b1.to_octet_string(:uncompressed)
-
- assert_raise(ArgumentError) { point_a.mul([1], [point_a]) }
- assert_raise(TypeError) { point_a.mul([1], nil) }
- assert_raise(TypeError) { point_a.mul([nil], []) }
- end
rescue OpenSSL::PKey::EC::Group::Error
# CentOS patches OpenSSL to reject curves defined over Fp where p < 256 bits
raise if $!.message !~ /unsupported field/
@@ -459,6 +473,9 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
# invalid argument
point = p256_key.public_key
assert_raise(TypeError) { point.mul(nil) }
+
+ # mul with arrays was removed in version 4.0.0
+ assert_raise(NotImplementedError) { point.mul([1], []) }
end
# test Group: asn1_flag, point_conversion
diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb
index e1a0df13f7..1716aef380 100644
--- a/test/openssl/test_pkey_rsa.rb
+++ b/test/openssl/test_pkey_rsa.rb
@@ -6,40 +6,38 @@ if defined?(OpenSSL)
class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
def test_no_private_exp
key = OpenSSL::PKey::RSA.new
- rsa = Fixtures.pkey("rsa2048")
+ rsa = Fixtures.pkey("rsa-1")
key.set_key(rsa.n, rsa.e, nil)
key.set_factors(rsa.p, rsa.q)
- assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt("foo") }
- assert_raise(OpenSSL::PKey::RSAError){ key.private_decrypt("foo") }
+ assert_raise(OpenSSL::PKey::PKeyError){ key.private_encrypt("foo") }
+ assert_raise(OpenSSL::PKey::PKeyError){ key.private_decrypt("foo") }
end if !openssl?(3, 0, 0) # Impossible state in OpenSSL 3.0
def test_private
- key = Fixtures.pkey("rsa2048")
+ key = Fixtures.pkey("rsa-1")
# Generated by DER
key2 = OpenSSL::PKey::RSA.new(key.to_der)
- assert(key2.private?)
+ assert_true(key2.private?)
# public key
key3 = key.public_key
- assert(!key3.private?)
+ assert_false(key3.private?)
# Generated by public key DER
key4 = OpenSSL::PKey::RSA.new(key3.to_der)
- assert(!key4.private?)
- rsa1024 = Fixtures.pkey("rsa1024")
+ assert_false(key4.private?)
if !openssl?(3, 0, 0)
- key = OpenSSL::PKey::RSA.new
# Generated by RSA#set_key
key5 = OpenSSL::PKey::RSA.new
- key5.set_key(rsa1024.n, rsa1024.e, rsa1024.d)
- assert(key5.private?)
+ key5.set_key(key.n, key.e, key.d)
+ assert_true(key5.private?)
# Generated by RSA#set_key, without d
key6 = OpenSSL::PKey::RSA.new
- key6.set_key(rsa1024.n, rsa1024.e, nil)
- assert(!key6.private?)
+ key6.set_key(key.n, key.e, nil)
+ assert_false(key6.private?)
end
end
@@ -61,6 +59,16 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
assert_equal 3, key.e
end
+ def test_new_empty
+ # pkeys are immutable with OpenSSL >= 3.0
+ if openssl?(3, 0, 0)
+ assert_raise(ArgumentError) { OpenSSL::PKey::RSA.new }
+ else
+ key = OpenSSL::PKey::RSA.new
+ assert_nil(key.n)
+ end
+ end
+
def test_s_generate
key1 = OpenSSL::PKey::RSA.generate(2048)
assert_equal 2048, key1.n.num_bits
@@ -108,13 +116,13 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
pssopts = {
"rsa_padding_mode" => "pss",
"rsa_pss_saltlen" => 20,
- "rsa_mgf1_md" => "SHA1"
+ "rsa_mgf1_md" => "SHA256"
}
sig_pss = key.sign("SHA256", data, pssopts)
assert_equal 256, sig_pss.bytesize
assert_equal true, key.verify("SHA256", sig_pss, data, pssopts)
assert_equal true, key.verify_pss("SHA256", sig_pss, data,
- salt_length: 20, mgf1_hash: "SHA1")
+ salt_length: 20, mgf1_hash: "SHA256")
# Defaults to PKCS #1 v1.5 padding => verification failure
assert_equal false, key.verify("SHA256", sig_pss, data)
@@ -172,7 +180,7 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
# Failure cases
assert_raise(ArgumentError){ key.private_encrypt() }
assert_raise(ArgumentError){ key.private_encrypt("hi", 1, nil) }
- assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt(plain0, 666) }
+ assert_raise(OpenSSL::PKey::PKeyError){ key.private_encrypt(plain0, 666) }
end
@@ -181,29 +189,29 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
assert_raise(OpenSSL::PKey::PKeyError, "[Bug #12783]") {
rsa.verify("SHA1", "a", "b")
}
- end
+ end unless openssl?(3, 0, 0) # Empty RSA is not possible with OpenSSL >= 3.0
def test_sign_verify_pss
key = Fixtures.pkey("rsa2048")
data = "Sign me!"
invalid_data = "Sign me?"
- signature = key.sign_pss("SHA256", data, salt_length: 20, mgf1_hash: "SHA1")
+ signature = key.sign_pss("SHA256", data, salt_length: 20, mgf1_hash: "SHA256")
assert_equal 256, signature.bytesize
assert_equal true,
- key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA1")
+ key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA256")
assert_equal true,
- key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1")
+ key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256")
assert_equal false,
- key.verify_pss("SHA256", signature, invalid_data, salt_length: 20, mgf1_hash: "SHA1")
+ key.verify_pss("SHA256", signature, invalid_data, salt_length: 20, mgf1_hash: "SHA256")
- signature = key.sign_pss("SHA256", data, salt_length: :digest, mgf1_hash: "SHA1")
+ signature = key.sign_pss("SHA256", data, salt_length: :digest, mgf1_hash: "SHA256")
assert_equal true,
- key.verify_pss("SHA256", signature, data, salt_length: 32, mgf1_hash: "SHA1")
+ key.verify_pss("SHA256", signature, data, salt_length: 32, mgf1_hash: "SHA256")
assert_equal true,
- key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1")
+ key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256")
assert_equal false,
- key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA1")
+ key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA256")
# The sign_pss with `salt_length: :max` raises the "invalid salt length"
# error in FIPS. We need to skip the tests in FIPS.
@@ -213,18 +221,18 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
# FIPS 186-5 section 5.4 PKCS #1
# https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
unless OpenSSL.fips_mode
- signature = key.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA1")
+ signature = key.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256")
# Should verify on the following salt_length (sLen).
# sLen <= emLen (octat) - 2 - hLen (octet) = 2048 / 8 - 2 - 256 / 8 = 222
# https://datatracker.ietf.org/doc/html/rfc8017#section-9.1.1
assert_equal true,
- key.verify_pss("SHA256", signature, data, salt_length: 222, mgf1_hash: "SHA1")
+ key.verify_pss("SHA256", signature, data, salt_length: 222, mgf1_hash: "SHA256")
assert_equal true,
- key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1")
+ key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256")
end
- assert_raise(OpenSSL::PKey::RSAError) {
- key.sign_pss("SHA256", data, salt_length: 223, mgf1_hash: "SHA1")
+ assert_raise(OpenSSL::PKey::PKeyError) {
+ key.sign_pss("SHA256", data, salt_length: 223, mgf1_hash: "SHA256")
}
end
@@ -270,57 +278,57 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
end
def test_export
- rsa1024 = Fixtures.pkey("rsa1024")
+ orig = Fixtures.pkey("rsa-1")
- pub = OpenSSL::PKey.read(rsa1024.public_to_der)
- assert_not_equal rsa1024.export, pub.export
- assert_equal rsa1024.public_to_pem, pub.export
+ pub = OpenSSL::PKey.read(orig.public_to_der)
+ assert_not_equal orig.export, pub.export
+ assert_equal orig.public_to_pem, pub.export
# PKey is immutable in OpenSSL >= 3.0
if !openssl?(3, 0, 0)
key = OpenSSL::PKey::RSA.new
# key has only n, e and d
- key.set_key(rsa1024.n, rsa1024.e, rsa1024.d)
- assert_equal rsa1024.public_key.export, key.export
+ key.set_key(orig.n, orig.e, orig.d)
+ assert_equal orig.public_key.export, key.export
# key has only n, e, d, p and q
- key.set_factors(rsa1024.p, rsa1024.q)
- assert_equal rsa1024.public_key.export, key.export
+ key.set_factors(orig.p, orig.q)
+ assert_equal orig.public_key.export, key.export
# key has n, e, d, p, q, dmp1, dmq1 and iqmp
- key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp)
- assert_equal rsa1024.export, key.export
+ key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp)
+ assert_equal orig.export, key.export
end
end
def test_to_der
- rsa1024 = Fixtures.pkey("rsa1024")
+ orig = Fixtures.pkey("rsa-1")
- pub = OpenSSL::PKey.read(rsa1024.public_to_der)
- assert_not_equal rsa1024.to_der, pub.to_der
- assert_equal rsa1024.public_to_der, pub.to_der
+ pub = OpenSSL::PKey.read(orig.public_to_der)
+ assert_not_equal orig.to_der, pub.to_der
+ assert_equal orig.public_to_der, pub.to_der
# PKey is immutable in OpenSSL >= 3.0
if !openssl?(3, 0, 0)
key = OpenSSL::PKey::RSA.new
# key has only n, e and d
- key.set_key(rsa1024.n, rsa1024.e, rsa1024.d)
- assert_equal rsa1024.public_key.to_der, key.to_der
+ key.set_key(orig.n, orig.e, orig.d)
+ assert_equal orig.public_key.to_der, key.to_der
# key has only n, e, d, p and q
- key.set_factors(rsa1024.p, rsa1024.q)
- assert_equal rsa1024.public_key.to_der, key.to_der
+ key.set_factors(orig.p, orig.q)
+ assert_equal orig.public_key.to_der, key.to_der
# key has n, e, d, p, q, dmp1, dmq1 and iqmp
- key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp)
- assert_equal rsa1024.to_der, key.to_der
+ key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp)
+ assert_equal orig.to_der, key.to_der
end
end
def test_RSAPrivateKey
- rsa = Fixtures.pkey("rsa2048")
+ rsa = Fixtures.pkey("rsa-1")
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Integer(0),
OpenSSL::ASN1::Integer(rsa.n),
@@ -336,35 +344,7 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
assert_predicate key, :private?
assert_same_rsa rsa, key
- pem = <<~EOF
- -----BEGIN RSA PRIVATE KEY-----
- MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN
- s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign
- 4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D
- kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl
- NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J
- DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb
- I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq
- PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V
- seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0
- Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc
- VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW
- wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G
- 0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj
- XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb
- aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n
- h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw
- Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k
- IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb
- v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId
- U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr
- vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS
- Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC
- 9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41
- gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG
- 4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw==
- -----END RSA PRIVATE KEY-----
- EOF
+ pem = der_to_pem(asn1.to_der, "RSA PRIVATE KEY")
key = OpenSSL::PKey::RSA.new(pem)
assert_same_rsa rsa, key
@@ -379,69 +359,46 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
end
def test_RSAPrivateKey_encrypted
+ # PKCS #1 RSAPrivateKey with OpenSSL encryption
omit_on_fips
- rsa1024 = Fixtures.pkey("rsa1024")
- # key = abcdef
- pem = <<~EOF
- -----BEGIN RSA PRIVATE KEY-----
- Proc-Type: 4,ENCRYPTED
- DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0
-
- zgJniZZQfvv8TFx3LzV6zhAQVayvQVZlAYqFq2yWbbxzF7C+IBhKQle9IhUQ9j/y
- /jkvol550LS8vZ7TX5WxyDLe12cdqzEvpR6jf3NbxiNysOCxwG4ErhaZGP+krcoB
- ObuL0nvls/+3myy5reKEyy22+0GvTDjaChfr+FwJjXMG+IBCLscYdgZC1LQL6oAn
- 9xY5DH3W7BW4wR5ttxvtN32TkfVQh8xi3jrLrduUh+hV8DTiAiLIhv0Vykwhep2p
- WZA+7qbrYaYM8GLLgLrb6LfBoxeNxAEKiTpl1quFkm+Hk1dKq0EhVnxHf92x0zVF
- jRGZxAMNcrlCoE4f5XK45epVZSZvihdo1k73GPbp84aZ5P/xlO4OwZ3i4uCQXynl
- jE9c+I+4rRWKyPz9gkkqo0+teJL8ifeKt/3ab6FcdA0aArynqmsKJMktxmNu83We
- YVGEHZPeOlyOQqPvZqWsLnXQUfg54OkbuV4/4mWSIzxFXdFy/AekSeJugpswMXqn
- oNck4qySNyfnlyelppXyWWwDfVus9CVAGZmJQaJExHMT/rQFRVchlmY0Ddr5O264
- gcjv90o1NBOc2fNcqjivuoX7ROqys4K/YdNQ1HhQ7usJghADNOtuLI8ZqMh9akXD
- Eqp6Ne97wq1NiJj0nt3SJlzTnOyTjzrTe0Y+atPkVKp7SsjkATMI9JdhXwGhWd7a
- qFVl0owZiDasgEhyG2K5L6r+yaJLYkPVXZYC/wtWC3NEchnDWZGQcXzB4xROCQkD
- OlWNYDkPiZioeFkA3/fTMvG4moB2Pp9Q4GU5fJ6k43Ccu1up8dX/LumZb4ecg5/x
- -----END RSA PRIVATE KEY-----
- EOF
+ rsa = Fixtures.pkey("rsa2048")
+
+ pem = der_to_encrypted_pem(rsa.to_der, "RSA PRIVATE KEY", "abcdef")
key = OpenSSL::PKey::RSA.new(pem, "abcdef")
- assert_same_rsa rsa1024, key
+ assert_same_rsa rsa, key
key = OpenSSL::PKey::RSA.new(pem) { "abcdef" }
- assert_same_rsa rsa1024, key
+ assert_same_rsa rsa, key
cipher = OpenSSL::Cipher.new("aes-128-cbc")
- exported = rsa1024.to_pem(cipher, "abcdef\0\1")
- assert_same_rsa rsa1024, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1")
- assert_raise(OpenSSL::PKey::RSAError) {
+ exported = rsa.to_pem(cipher, "abcdef\0\1")
+ assert_same_rsa rsa, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1")
+ assert_raise(OpenSSL::PKey::PKeyError) {
OpenSSL::PKey::RSA.new(exported, "abcdef")
}
end
def test_RSAPublicKey
- rsa1024 = Fixtures.pkey("rsa1024")
- rsa1024pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der)
+ # PKCS #1 RSAPublicKey. Only decoding is supported
+ orig = Fixtures.pkey("rsa-1")
+ pub = OpenSSL::PKey::RSA.new(orig.public_to_der)
asn1 = OpenSSL::ASN1::Sequence([
- OpenSSL::ASN1::Integer(rsa1024.n),
- OpenSSL::ASN1::Integer(rsa1024.e)
+ OpenSSL::ASN1::Integer(orig.n),
+ OpenSSL::ASN1::Integer(orig.e)
])
key = OpenSSL::PKey::RSA.new(asn1.to_der)
assert_not_predicate key, :private?
- assert_same_rsa rsa1024pub, key
+ assert_same_rsa pub, key
- pem = <<~EOF
- -----BEGIN RSA PUBLIC KEY-----
- MIGJAoGBAMvCxLDUQKc+1P4+Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RF
- geyTgE8KQTduu1OE9Zz2SMcRBDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u
- /xkP2mKGjAokPIwOI3oCthSZlzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAE=
- -----END RSA PUBLIC KEY-----
- EOF
+ pem = der_to_pem(asn1.to_der, "RSA PUBLIC KEY")
key = OpenSSL::PKey::RSA.new(pem)
- assert_same_rsa rsa1024pub, key
+ assert_same_rsa pub, key
end
def test_PUBKEY
- rsa1024 = Fixtures.pkey("rsa1024")
- rsa1024pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der)
+ orig = Fixtures.pkey("rsa-1")
+ pub = OpenSSL::PKey::RSA.new(orig.public_to_der)
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Sequence([
@@ -450,39 +407,32 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
]),
OpenSSL::ASN1::BitString(
OpenSSL::ASN1::Sequence([
- OpenSSL::ASN1::Integer(rsa1024.n),
- OpenSSL::ASN1::Integer(rsa1024.e)
+ OpenSSL::ASN1::Integer(orig.n),
+ OpenSSL::ASN1::Integer(orig.e)
]).to_der
)
])
key = OpenSSL::PKey::RSA.new(asn1.to_der)
assert_not_predicate key, :private?
- assert_same_rsa rsa1024pub, key
+ assert_same_rsa pub, key
- pem = <<~EOF
- -----BEGIN PUBLIC KEY-----
- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLwsSw1ECnPtT+PkOgHhcGA71n
- wC2/nL85VBGnRqDxOqjVh7CxaKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbC
- z0layNqHyywQEVLFmp1cpIt/Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU
- 3+l54E6lF/JfFEU5hwIDAQAB
- -----END PUBLIC KEY-----
- EOF
+ pem = der_to_pem(asn1.to_der, "PUBLIC KEY")
key = OpenSSL::PKey::RSA.new(pem)
- assert_same_rsa rsa1024pub, key
+ assert_same_rsa pub, key
assert_equal asn1.to_der, key.to_der
assert_equal pem, key.export
- assert_equal asn1.to_der, rsa1024.public_to_der
+ assert_equal asn1.to_der, orig.public_to_der
assert_equal asn1.to_der, key.public_to_der
- assert_equal pem, rsa1024.public_to_pem
+ assert_equal pem, orig.public_to_pem
assert_equal pem, key.public_to_pem
end
def test_pem_passwd
omit_on_fips
- key = Fixtures.pkey("rsa1024")
+ key = Fixtures.pkey("rsa-1")
pem3c = key.to_pem("aes-128-cbc", "key")
assert_match (/ENCRYPTED/), pem3c
assert_equal key.to_der, OpenSSL::PKey.read(pem3c, "key").to_der
@@ -493,94 +443,97 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
end
def test_private_encoding
- rsa1024 = Fixtures.pkey("rsa1024")
+ pkey = Fixtures.pkey("rsa-1")
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Integer(0),
OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::ObjectId("rsaEncryption"),
OpenSSL::ASN1::Null(nil)
]),
- OpenSSL::ASN1::OctetString(rsa1024.to_der)
+ OpenSSL::ASN1::OctetString(pkey.to_der)
])
- assert_equal asn1.to_der, rsa1024.private_to_der
- assert_same_rsa rsa1024, OpenSSL::PKey.read(asn1.to_der)
+ assert_equal asn1.to_der, pkey.private_to_der
+ assert_same_rsa pkey, OpenSSL::PKey.read(asn1.to_der)
- pem = <<~EOF
- -----BEGIN PRIVATE KEY-----
- MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMvCxLDUQKc+1P4+
- Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RFgeyTgE8KQTduu1OE9Zz2SMcR
- BDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u/xkP2mKGjAokPIwOI3oCthSZ
- lzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAECgYEApKX8xBqvJ7XI7Kypfo/x8MVC
- 3rxW+1eQ2aVKIo4a7PKGjQz5RVIVyzqTUvSZoMTbkAxlSIbO5YfJpTnl3tFcOB6y
- QMxqQPW/pl6Ni3EmRJdsRM5MsPBRZOfrXxOCdvXu1TWOS1S1TrvEr/TyL9eh2WCd
- CGzpWgdO4KHce7vs7pECQQDv6DGoG5lHnvbvj9qSJb9K5ebRJc8S+LI7Uy5JHC0j
- zsHTYPSqBXwPVQdGbgCEycnwwKzXzT2QxAQmJBQKun2ZAkEA2W3aeAE7Xi6zo2eG
- 4Cx4UNMHMIdfBRS7VgoekwybGmcapqV0aBew5kHeWAmxP1WUZ/dgZh2QtM1VuiBA
- qUqkHwJBAOJLCRvi/JB8N7z82lTk2i3R8gjyOwNQJv6ilZRMyZ9vFZFHcUE27zCf
- Kb+bX03h8WPwupjMdfgpjShU+7qq8nECQQDBrmyc16QVyo40sgTgblyiysitvviy
- ovwZsZv4q5MCmvOPnPUrwGbRRb2VONUOMOKpFiBl9lIv7HU//nj7FMVLAkBjUXED
- 83dA8JcKM+HlioXEAxCzZVVhN+D63QwRwkN08xAPklfqDkcqccWDaZm2hdCtaYlK
- funwYkrzI1OikQSs
- -----END PRIVATE KEY-----
- EOF
- assert_equal pem, rsa1024.private_to_pem
- assert_same_rsa rsa1024, OpenSSL::PKey.read(pem)
+ pem = der_to_pem(asn1.to_der, "PRIVATE KEY")
+ assert_equal pem, pkey.private_to_pem
+ assert_same_rsa pkey, OpenSSL::PKey.read(pem)
end
def test_private_encoding_encrypted
rsa = Fixtures.pkey("rsa2048")
- encoded = rsa.private_to_der("aes-128-cbc", "abcdef")
+ encoded = rsa.private_to_der("aes-128-cbc", "abcdefgh")
asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo
assert_kind_of OpenSSL::ASN1::Sequence, asn1
assert_equal 2, asn1.value.size
assert_not_equal rsa.private_to_der, encoded
- assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef")
- assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdef" }
+ assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh")
+ assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdefgh" }
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encoded, "abcxyz") }
- encoded = rsa.private_to_pem("aes-128-cbc", "abcdef")
+ encoded = rsa.private_to_pem("aes-128-cbc", "abcdefgh")
assert_match (/BEGIN ENCRYPTED PRIVATE KEY/), encoded.lines[0]
- assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef")
+ assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh")
# Use openssl instead of certtool due to https://gitlab.com/gnutls/gnutls/-/issues/1632
- # openssl pkcs8 -in test/openssl/fixtures/pkey/rsa2048.pem -topk8 -v2 aes-128-cbc -passout pass:abcdef
+ # openssl pkcs8 -in test/openssl/fixtures/pkey/rsa2048.pem -topk8 -v2 aes-128-cbc -passout pass:abcdefgh
pem = <<~EOF
- -----BEGIN ENCRYPTED PRIVATE KEY-----
- MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIay5V8CDQi5oCAggA
- MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBB6eyagcbsvdQlM1kPcH7kiBIIE
- 0Ng1apIyoPAZ4BfC4kMNeSmeAv3XspxqYi3uWzXiNyTcoE6390swrwM6WvdpXvLI
- /n/V06krxPZ9X4fBG2kLUzXt5f09lEvmQU1HW1wJGU5Sq3bNeXBrlJF4DzJE4WWd
- whVVvNMm44ghdzN/jGSw3z+6d717N+waa7vrpBDsHjhsPNwxpyzUvcFPFysTazxx
- kN/dziIBF6SRKi6w8VaJEMQ8czGu5T3jOc2e/1p3/AYhHLPS4NHhLR5OUh0TKqLK
- tANAqI9YqCAjhqcYCmN3mMQXY52VfOqG9hlX1x9ZQyqiH7l102EWbPqouk6bCBLQ
- wHepPg4uK99Wsdh65qEryNnXQ5ZmO6aGb6T3TFENCaNKmi8Nh+/5dr7J7YfhIwpo
- FqHvk0hrZ8r3EQlr8/td0Yb1/IKzeQ34638uXf9UxK7C6o+ilsmJDR4PHJUfZL23
- Yb9qWJ0GEzd5AMsI7x6KuUxSuH9nKniv5Tzyty3Xmb4FwXUyADWE19cVuaT+HrFz
- GraKnA3UXbEgWAU48/l4K2HcAHyHDD2Kbp8k+o1zUkH0fWUdfE6OUGtx19Fv44Jh
- B7xDngK8K48C6nrj06/DSYfXlb2X7WQiapeG4jt6U57tLH2XAjHCkvu0IBZ+//+P
- yIWduEHQ3w8FBRcIsTNJo5CjkGk580TVQB/OBLWfX48Ay3oF9zgnomDIlVjl9D0n
- lKxw/KMCLkvB78rUeGbr1Kwj36FhGpTBw3FgcYGa5oWFZTlcOgMTXLqlbb9JnDlA
- Zs7Tu0WTyOTV/Dne9nEm39Dzu6wRojiIpmygTD4FI7rmOy3CYNvL3XPv7XQj0hny
- Ee/fLxugYlQnwPZSqOVEQY2HsG7AmEHRsvy4bIWIGt+yzAPZixt9MUdJh91ttRt7
- QA/8J1pAsGqEuQpF6UUINZop3J7twfhO4zWYN/NNQ52eWNX2KLfjfGRhrvatzmZ0
- BuCsCI9hwEeE6PTlhbX1Rs177MrDc3vlqz2V3Po0OrFjXAyg9DR/OC4iK5wOG2ZD
- 7StVSP8bzwQXsz3fJ0ardKXgnU2YDAP6Vykjgt+nFI09HV/S2faOc2g/UK4Y2khl
- J93u/GHMz/Kr3bKWGY1/6nPdIdFheQjsiNhd5gI4tWik2B3QwU9mETToZ2LSvDHU
- jYCys576xJLkdMM6nJdq72z4tCoES9IxyHVs4uLjHKIo/ZtKr+8xDo8IL4ax3U8+
- NMhs/lwReHmPGahm1fu9zLRbNCVL7e0zrOqbjvKcSEftObpV/LLcPYXtEm+lZcck
- /PMw49HSE364anKEXCH1cyVWJwdZRpFUHvRpLIrpHru7/cthhiEMdLgK1/x8sLob
- DiyieLxH1DPeXT4X+z94ER4IuPVOcV5AXc/omghispEX6DNUnn5jC4e3WyabjUbw
- MuO9lVH9Wi2/ynExCqVmQkdbTXuLwjni1fJ27Q5zb0aCmhO8eq6P869NCjhJuiUj
- NI9XtGLP50YVWE0kL8KEJqnyFudky8Khzk4/dyixQFqin5GfT4vetrLunGHy7lRB
- 3LpnFrpMOr+0xr1RW1k9vlmjRsJSiojJfReYO7gH3B5swiww2azogoL+4jhF1Jxh
- OYLWdkKhP2jSVGqtIDtny0O4lBm2+hLpWjiI0mJQ7wdA
- -----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ+Sg92Hgy8EgVPf7t
+Hen1qwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEB5UX2xdDO8/AKA8
++Y5CZyUEggTQkArh4mMPpnAe3xOcDKMz8KCn5lrLb/6Dla7Rp9LHKGkUfyI11EZt
+m+OIriwy9oDQquKyVuLQVGAxXKk+3pyxMqLB0i3hLYamT3vzoPctyVwjuRuKoU3E
+CbF0YhCoxvWMvjHsolwYzx00DbLXouE4BGKvPjnhw5hwtdoZ9Px0ZnCXCxVXi8z/
+mlw7a2ptKEiHQVjuPPbttq+dA+ez7pbWonWVod5TMaPtyEZu5XfPD+0pMboceHZg
+H8ehgUhV3mzEJiisFGg1q9hj+4BaFl5m4tvqp43inCCdShE78CNnOPzJ7WCjKJqi
+jGvHjeMoVx3rZXHcZDAzfIZvDigp9uAfzjRJjpRG8sg5sDQVC7vdUhQDe5TorKT2
+Vb0tdVYxoEpMJ3dhU6Ds5JxMR6GTLjsjTqOkAl6db3HxulwfEpr7YjOpfODR+ttA
+BeIcUcMLsDHayIaQaMLIftHxOkfX7UxoFW9CMG5UMQf/m3eEgVUwgK/E5sUJRUTo
+yhRzJ4NAP4fgc4YH9tbzvUrhfdCXCBEOn6IlDQL66SZr8Mm+Ggu4Ij4TnKWXLrXL
+nSTDDa42kPOvtedKqxC/uXE7rrfh+uyw6J6OjSl6u86TIebndLuDo5DTdWKh8rsg
+fvZZ6332dfMp8JC9/4YnYIJdI7acInSoyHp52OB+2+dgYCr5OrZFjjKS7nELVfo7
+OxGy6uH3NHF9qyUEf3MN17TRHI7jP3zKbXcDTPSyxLQkWe/CU5B251CTmoTSidSW
+EhKnPlGZYbpVQJ4KGEL5UeY8W9PXQo4Dl7TmXBGvuPqNF8kMB3XrPIph7GmihmX0
+nlJqLk9eiRFmUETS0IdAyKJrm4R9Hf6rjYCbXlaApylyVUdSZ2BxgeoTY9BA6Kgf
+3xlgMv01MoUkXMx2+OLIc9MzhButQiDxh3mfS012CjKqUFrJhRSa8DOpUfVgmXpq
+/HP4drWamLWYJR8FsmJS11ZYc1EK/ctJTSpqfewvoUGOSHomhh7zXn1Acb6+9/3p
+bcrJjoR5K8Jg6NlG4dSNkpY/x92I7bFLXFqELIH5tteDrlQen5eASjaiyPPAoOw8
+IGfOmFS4VUPh1VP6g8Jtn5Hr2qXB3DoQoI6EvUZhJ6GJfi67mx5VKux6G9MzJkix
+GU1cL4WzWK2DU0l39UxXjS+4TmOYbrqLVnVMjusX0fwb8LkDC/fVohbhLwhHNwu6
+nSTSEpS9zSDrv1JXFtAtPv6XCSFs6ssPWJMwGSdThn7EfV0GEhG2mCzTyVhwxxQo
+6U/Suqq4oMZoracPUCZx0E4u/bb4KBoFA/eBNPJENTR18IiV+D7wAxlxauO3N1t4
+iJxwrrvSgQPmOGuxrh5LVD41UXYUWLtndzabnpByppFn2MbmvrqJgon0MSs84cTA
+7scnbPu1V3PpKy/t67gtVw9Ue8hLjrskWB1JPFYr7vRWvJzYjfbflyroF+QEJ3TA
+6rTfUC9+ePci6T+i9jF4xcmzqYzRtnGtp5nRUitJGw0uwBTDwzfI2WD6ltvvu7lc
+pHuzvY5zEapuu1JhjHLUd+OE8rVVM999DUXo/IDLsWyRCphCiYfVXJNogd9rB0Ta
+5AhVgpRhxkarBURZyLTYj7NRxCsbHq7XExJNrIdRG/KlBQfyEyIzZ7E=
+-----END ENCRYPTED PRIVATE KEY-----
EOF
- assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdef")
+ assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdefgh")
+ end
+
+ def test_params
+ key = Fixtures.pkey("rsa2048")
+ assert_equal(2048, key.n.num_bits)
+ assert_equal(key.n, key.params["n"])
+ assert_equal(65537, key.e)
+ assert_equal(key.e, key.params["e"])
+ [:d, :p, :q, :dmp1, :dmq1, :iqmp].each do |name|
+ assert_kind_of(OpenSSL::BN, key.send(name))
+ assert_equal(key.send(name), key.params[name.to_s])
+ end
+
+ pubkey = OpenSSL::PKey.read(key.public_to_der)
+ assert_equal(key.n, pubkey.n)
+ assert_equal(key.e, pubkey.e)
+ [:d, :p, :q, :dmp1, :dmq1, :iqmp].each do |name|
+ assert_nil(pubkey.send(name))
+ assert_nil(pubkey.params[name.to_s])
+ end
end
def test_dup
- key = Fixtures.pkey("rsa1024")
+ key = Fixtures.pkey("rsa-1")
key2 = key.dup
assert_equal key.params, key2.params
@@ -592,7 +545,7 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
end
def test_marshal
- key = Fixtures.pkey("rsa2048")
+ key = Fixtures.pkey("rsa-1")
deserialized = Marshal.load(Marshal.dump(key))
assert_equal key.to_der, deserialized.to_der
diff --git a/test/openssl/test_provider.rb b/test/openssl/test_provider.rb
index 6f85c00c98..10081e208c 100644
--- a/test/openssl/test_provider.rb
+++ b/test/openssl/test_provider.rb
@@ -46,6 +46,7 @@ class OpenSSL::TestProvider < OpenSSL::TestCase
with_openssl(<<-'end;')
begin
+ OpenSSL::Provider.load("default")
OpenSSL::Provider.load("legacy")
rescue OpenSSL::Provider::ProviderError
omit "Only for OpenSSL with legacy provider"
diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb
index c9cc7a02e7..e4fd581079 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -39,8 +39,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_ctx_options_config
- omit "LibreSSL does not support OPENSSL_CONF" if libressl?
- omit "OpenSSL < 1.1.1 does not support system_default" if openssl? && !openssl?(1, 1, 1)
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
Tempfile.create("openssl.cnf") { |f|
f.puts(<<~EOF)
@@ -231,6 +230,34 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
end
+ def test_extra_chain_cert_auto_chain
+ start_server { |port|
+ server_connect(port) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ assert_equal @svr_cert.to_der, ssl.peer_cert.to_der
+ assert_equal [@svr_cert], ssl.peer_cert_chain
+ }
+ }
+
+ # AWS-LC enables SSL_MODE_NO_AUTO_CHAIN by default
+ unless aws_lc?
+ ctx_proc = -> ctx {
+ # Sanity check: start_server won't set extra_chain_cert
+ assert_nil ctx.extra_chain_cert
+ ctx.cert_store = OpenSSL::X509::Store.new.tap { |store|
+ store.add_cert(@ca_cert)
+ }
+ }
+ start_server(ctx_proc: ctx_proc) { |port|
+ server_connect(port) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ assert_equal @svr_cert.to_der, ssl.peer_cert.to_der
+ assert_equal [@svr_cert, @ca_cert], ssl.peer_cert_chain
+ }
+ }
+ end
+ end
+
def test_sysread_and_syswrite
start_server { |port|
server_connect(port) { |ssl|
@@ -243,6 +270,11 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ssl.syswrite(str)
assert_same buf, ssl.sysread(str.size, buf)
assert_equal(str, buf)
+
+ obj = Object.new
+ obj.define_singleton_method(:to_str) { str }
+ ssl.syswrite(obj)
+ assert_equal(str, ssl.sysread(str.bytesize))
}
}
end
@@ -256,11 +288,16 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ssl.syswrite(str)
assert_equal(str, ssl.sysread(str.bytesize))
- ssl.timeout = 1
- assert_raise(IO::TimeoutError) {ssl.read(1)}
+ ssl.timeout = 0.1
+ assert_raise(IO::TimeoutError) { ssl.sysread(1) }
ssl.syswrite(str)
assert_equal(str, ssl.sysread(str.bytesize))
+
+ buf = "orig".b
+ assert_raise(IO::TimeoutError) { ssl.sysread(1, buf) }
+ assert_equal("orig", buf)
+ assert_nothing_raised { buf.clear }
end
end
end
@@ -318,6 +355,22 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
end
+ def test_sync_close_initialize_opt
+ start_server do |port|
+ begin
+ sock = TCPSocket.new("127.0.0.1", port)
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, sync_close: true)
+ assert_equal true, ssl.sync_close
+ ssl.connect
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ ssl.close
+ assert_predicate sock, :closed?
+ ensure
+ sock&.close
+ end
+ end
+ end
+
def test_copy_stream
start_server do |port|
server_connect(port) do |ssl|
@@ -344,27 +397,27 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
empty_store = OpenSSL::X509::Store.new
# Valid certificate, SSL_VERIFY_PEER
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.cert_store = populated_store
assert_nothing_raised {
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
- ctx.cert_store = populated_store
server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets }
}
# Invalid certificate, SSL_VERIFY_NONE
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ ctx.cert_store = empty_store
assert_nothing_raised {
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
- ctx.cert_store = empty_store
server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets }
}
# Invalid certificate, SSL_VERIFY_PEER
- assert_handshake_error {
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
- ctx.cert_store = empty_store
- server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets }
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.cert_store = empty_store
+ assert_raise(OpenSSL::SSL::SSLError) {
+ server_connect(port, ctx)
}
}
end
@@ -392,11 +445,15 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
def test_client_auth_success
vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
- start_server(verify_mode: vflag,
- ctx_proc: proc { |ctx|
- # LibreSSL doesn't support client_cert_cb in TLS 1.3
- ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?
- }) { |port|
+ ctx_proc = proc { |ctx|
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+ store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+ ctx.cert_store = store
+ # LibreSSL doesn't support client_cert_cb in TLS 1.3
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?
+ }
+ start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |port|
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = @cli_key
ctx.cert = @cli_cert
@@ -441,6 +498,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
pend "LibreSSL doesn't support certificate_authorities" if libressl?
ctx_proc = Proc.new do |ctx|
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+ store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+ ctx.cert_store = store
ctx.client_ca = [@ca_cert]
end
@@ -506,7 +567,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ssl.sync_close = true
begin
assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
- assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result)
+ assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ssl.verify_result)
ensure
ssl.close
end
@@ -640,8 +701,12 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_post_connect_check_with_anon_ciphers
+ # DH missing the q value on unknown named parameters is not FIPS-approved.
+ omit_on_fips
+ omit "AWS-LC does not support DHE ciphersuites" if aws_lc?
+
ctx_proc = -> ctx {
- ctx.ssl_version = :TLSv1_2
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
ctx.ciphers = "aNULL"
ctx.tmp_dh = Fixtures.pkey("dh-1")
ctx.security_level = 0
@@ -649,7 +714,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
start_server(ctx_proc: ctx_proc) { |port|
ctx = OpenSSL::SSL::SSLContext.new
- ctx.ssl_version = :TLSv1_2
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
ctx.ciphers = "aNULL"
ctx.security_level = 0
server_connect(port, ctx) { |ssl|
@@ -793,11 +858,6 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# buzz.example.net, respectively). ...
assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
create_cert_with_san('DNS:baz*.example.com'), 'baz1.example.com'))
-
- # LibreSSL 3.5.0+ doesn't support other wildcard certificates
- # (it isn't required to, as RFC states MAY, not MUST)
- return if libressl?
-
assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
create_cert_with_san('DNS:*baz.example.com'), 'foobaz.example.com'))
assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
@@ -881,11 +941,17 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def create_cert_with_san(san)
- ef = OpenSSL::X509::ExtensionFactory.new
cert = OpenSSL::X509::Certificate.new
cert.subject = OpenSSL::X509::Name.parse("/DC=some/DC=site/CN=Some Site")
- ext = ef.create_ext('subjectAltName', san)
- cert.add_extension(ext)
+ v = OpenSSL::ASN1::Sequence(san.split(",").map { |item|
+ type, value = item.split(":", 2)
+ case type
+ when "DNS" then OpenSSL::ASN1::IA5String(value, 2, :IMPLICIT)
+ when "IP" then OpenSSL::ASN1::OctetString(IPAddr.new(value).hton, 7, :IMPLICIT)
+ else raise "unsupported"
+ end
+ })
+ cert.add_extension(OpenSSL::X509::Extension.new("subjectAltName", v))
cert
end
@@ -922,7 +988,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_keylog_cb
- pend "Keylog callback is not supported" if !openssl?(1, 1, 1) || libressl?
+ omit "Keylog callback is not supported" if libressl?
prefix = 'CLIENT_RANDOM'
context = OpenSSL::SSL::SSLContext.new
@@ -942,30 +1008,28 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
end
- if tls13_supported?
- prefixes = [
- 'SERVER_HANDSHAKE_TRAFFIC_SECRET',
- 'EXPORTER_SECRET',
- 'SERVER_TRAFFIC_SECRET_0',
- 'CLIENT_HANDSHAKE_TRAFFIC_SECRET',
- 'CLIENT_TRAFFIC_SECRET_0',
- ]
- context = OpenSSL::SSL::SSLContext.new
- context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION
- cb_called = false
- context.keylog_cb = proc do |_sock, line|
- cb_called = true
- assert_not_nil(prefixes.delete(line.split.first))
- end
+ prefixes = [
+ 'SERVER_HANDSHAKE_TRAFFIC_SECRET',
+ 'EXPORTER_SECRET',
+ 'SERVER_TRAFFIC_SECRET_0',
+ 'CLIENT_HANDSHAKE_TRAFFIC_SECRET',
+ 'CLIENT_TRAFFIC_SECRET_0',
+ ]
+ context = OpenSSL::SSL::SSLContext.new
+ context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION
+ cb_called = false
+ context.keylog_cb = proc do |_sock, line|
+ cb_called = true
+ assert_not_nil(prefixes.delete(line.split.first))
+ end
- start_server do |port|
- server_connect(port, context) do |ssl|
- ssl.puts "abc"
- assert_equal("abc\n", ssl.gets)
- assert_equal(true, cb_called)
- end
- assert_equal(0, prefixes.size)
+ start_server do |port|
+ server_connect(port, context) do |ssl|
+ ssl.puts "abc"
+ assert_equal("abc\n", ssl.gets)
+ assert_equal(true, cb_called)
end
+ assert_equal(0, prefixes.size)
end
end
@@ -1016,36 +1080,46 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
end
- def test_servername_cb_raises_an_exception_on_unknown_objects
- hostname = 'example.org'
-
- ctx2 = OpenSSL::SSL::SSLContext.new
- ctx2.cert = @svr_cert
- ctx2.key = @svr_key
- ctx2.servername_cb = lambda { |args| Object.new }
-
+ def test_servername_cb_exception
sock1, sock2 = socketpair
+ t = Thread.new {
+ s1 = OpenSSL::SSL::SSLSocket.new(sock1)
+ s1.hostname = "localhost"
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /unrecognized.name/i) {
+ s1.connect
+ }
+ }
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.servername_cb = lambda { |args| raise RuntimeError, "foo" }
s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+ assert_raise_with_message(RuntimeError, "foo") { s2.accept }
+ assert t.join
+ ensure
+ sock1.close
+ sock2.close
+ t.kill.join
+ end
- ctx1 = OpenSSL::SSL::SSLContext.new
+ def test_servername_cb_raises_an_exception_on_unknown_objects
+ sock1, sock2 = socketpair
- s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
- s1.hostname = hostname
t = Thread.new {
- assert_raise(OpenSSL::SSL::SSLError) do
- s1.connect
- end
+ s1 = OpenSSL::SSL::SSLSocket.new(sock1)
+ s1.hostname = "localhost"
+ assert_raise(OpenSSL::SSL::SSLError) { s1.connect }
}
- assert_raise(ArgumentError) do
- s2.accept
- end
-
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.servername_cb = lambda { |args| Object.new }
+ s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+ assert_raise(ArgumentError) { s2.accept }
assert t.join
ensure
- sock1.close if sock1
- sock2.close if sock2
+ sock1.close
+ sock2.close
+ t.kill.join
end
def test_accept_errors_include_peeraddr
@@ -1109,7 +1183,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ssl.connect
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
else
- assert_handshake_error { ssl.connect }
+ assert_raise(OpenSSL::SSL::SSLError) { ssl.connect }
end
ensure
ssl.close if ssl
@@ -1147,7 +1221,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
sock = TCPSocket.new("127.0.0.1", port)
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
ssl.hostname = "b.example.com"
- assert_handshake_error { ssl.connect }
+ assert_raise(OpenSSL::SSL::SSLError) { ssl.connect }
assert_equal false, verify_callback_ok
assert_equal OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH, verify_callback_err
ensure
@@ -1160,9 +1234,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
start_server(ignore_listener_error: true) { |port|
ctx = OpenSSL::SSL::SSLContext.new
ctx.set_params
- # OpenSSL <= 1.1.0: "self signed certificate in certificate chain"
- # OpenSSL >= 3.0.0: "self-signed certificate in certificate chain"
- assert_raise_with_message(OpenSSL::SSL::SSLError, /self.signed/) {
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /unable to get local issuer certificate/) {
server_connect(port, ctx)
}
}
@@ -1204,34 +1276,33 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
OpenSSL::SSL::TLS1_VERSION,
OpenSSL::SSL::TLS1_1_VERSION,
OpenSSL::SSL::TLS1_2_VERSION,
- # OpenSSL 1.1.1
- defined?(OpenSSL::SSL::TLS1_3_VERSION) && OpenSSL::SSL::TLS1_3_VERSION,
- ].compact
+ OpenSSL::SSL::TLS1_3_VERSION,
+ ]
- # Prepare for testing & do sanity check
supported = []
- possible_versions.each do |ver|
- catch(:unsupported) {
- ctx_proc = proc { |ctx|
- begin
- ctx.min_version = ctx.max_version = ver
- rescue ArgumentError, OpenSSL::SSL::SSLError
- throw :unsupported
- end
+ ctx_proc = proc { |ctx|
+ # The default security level is 1 in OpenSSL <= 3.1, 2 in OpenSSL >= 3.2
+ # In OpenSSL >= 3.0, TLS 1.1 or older is disabled at level 1
+ ctx.security_level = 0
+ # Explicitly reset them to avoid influenced by OPENSSL_CONF
+ ctx.min_version = ctx.max_version = nil
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ possible_versions.each do |ver|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.security_level = 0
+ ctx.min_version = ctx.max_version = ver
+ server_connect(port, ctx) { |ssl|
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
- start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
- begin
- server_connect(port) { |ssl|
- ssl.puts "abc"; assert_equal "abc\n", ssl.gets
- }
- rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET
- else
- supported << ver
- end
- end
- }
+ supported << ver
+ rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET
+ end
end
- assert_not_empty supported
+
+ # Sanity check: in our test suite we assume these are always supported
+ assert_include(supported, OpenSSL::SSL::TLS1_2_VERSION)
+ assert_include(supported, OpenSSL::SSL::TLS1_3_VERSION)
supported
end
@@ -1249,7 +1320,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
ctx = OpenSSL::SSL::SSLContext.new
ctx.set_params(cert_store: store, verify_hostname: false)
- assert_handshake_error { server_connect(port, ctx) { } }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) }
}
end
end
@@ -1265,18 +1336,20 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
OpenSSL::SSL::TLS1_VERSION => { name: "TLSv1", method: "TLSv1" },
OpenSSL::SSL::TLS1_1_VERSION => { name: "TLSv1.1", method: "TLSv1_1" },
OpenSSL::SSL::TLS1_2_VERSION => { name: "TLSv1.2", method: "TLSv1_2" },
- # OpenSSL 1.1.1
- defined?(OpenSSL::SSL::TLS1_3_VERSION) && OpenSSL::SSL::TLS1_3_VERSION =>
- { name: "TLSv1.3", method: nil },
+ OpenSSL::SSL::TLS1_3_VERSION => { name: "TLSv1.3", method: nil },
}
# Server enables a single version
supported.each do |ver|
- ctx_proc = proc { |ctx| ctx.min_version = ctx.max_version = ver }
+ ctx_proc = proc { |ctx|
+ ctx.security_level = 0
+ ctx.min_version = ctx.max_version = ver
+ }
start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
supported.each do |cver|
# Client enables a single version
ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.security_level = 0
ctx1.min_version = ctx1.max_version = cver
if ver == cver
server_connect(port, ctx1) { |ssl|
@@ -1284,13 +1357,14 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
else
- assert_handshake_error { server_connect(port, ctx1) { } }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) }
end
# There is no version-specific SSL methods for TLS 1.3
if cver <= OpenSSL::SSL::TLS1_2_VERSION
# Client enables a single version using #ssl_version=
ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.security_level = 0
ctx2.ssl_version = vmap[cver][:method]
if ver == cver
server_connect(port, ctx2) { |ssl|
@@ -1298,13 +1372,14 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
else
- assert_handshake_error { server_connect(port, ctx2) { } }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) }
end
end
end
# Client enables all supported versions
ctx3 = OpenSSL::SSL::SSLContext.new
+ ctx3.security_level = 0
ctx3.min_version = ctx3.max_version = nil
server_connect(port, ctx3) { |ssl|
assert_equal vmap[ver][:name], ssl.ssl_version
@@ -1319,12 +1394,17 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# Server sets min_version (earliest is disabled)
sver = supported[1]
- ctx_proc = proc { |ctx| ctx.min_version = sver }
+ ctx_proc = proc { |ctx|
+ ctx.security_level = 0
+ ctx.min_version = sver
+ }
start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
supported.each do |cver|
# Client sets min_version
ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.security_level = 0
ctx1.min_version = cver
+ ctx1.max_version = 0
server_connect(port, ctx1) { |ssl|
assert_equal vmap[supported.last][:name], ssl.ssl_version
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
@@ -1332,6 +1412,8 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# Client sets max_version
ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.security_level = 0
+ ctx2.min_version = 0
ctx2.max_version = cver
if cver >= sver
server_connect(port, ctx2) { |ssl|
@@ -1339,14 +1421,18 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
else
- assert_handshake_error { server_connect(port, ctx2) { } }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) }
end
end
}
# Server sets max_version (latest is disabled)
sver = supported[-2]
- ctx_proc = proc { |ctx| ctx.max_version = sver }
+ ctx_proc = proc { |ctx|
+ ctx.security_level = 0
+ ctx.min_version = 0
+ ctx.max_version = sver
+ }
start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
supported.each do |cver|
# Client sets min_version
@@ -1358,11 +1444,13 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
else
- assert_handshake_error { server_connect(port, ctx1) { } }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) }
end
# Client sets max_version
ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.security_level = 0
+ ctx2.min_version = 0
ctx2.max_version = cver
server_connect(port, ctx2) { |ssl|
if cver >= sver
@@ -1376,13 +1464,105 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
}
end
+ def test_minmax_version_system_default
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
+
+ Tempfile.create("openssl.cnf") { |f|
+ f.puts(<<~EOF)
+ openssl_conf = default_conf
+ [default_conf]
+ ssl_conf = ssl_sect
+ [ssl_sect]
+ system_default = ssl_default_sect
+ [ssl_default_sect]
+ MaxProtocol = TLSv1.2
+ EOF
+ f.close
+
+ start_server(ignore_listener_error: true) do |port|
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
+ sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ ssl.connect
+ assert_equal("TLSv1.2", ssl.ssl_version)
+ ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
+ ssl.close
+ end;
+
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
+ sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
+ ctx.max_version = nil
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ ssl.connect
+ assert_equal("TLSv1.3", ssl.ssl_version)
+ ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
+ ssl.close
+ end;
+ end
+ }
+ end
+
+ def test_respect_system_default_min
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
+
+ Tempfile.create("openssl.cnf") { |f|
+ f.puts(<<~EOF)
+ openssl_conf = default_conf
+ [default_conf]
+ ssl_conf = ssl_sect
+ [ssl_sect]
+ system_default = ssl_default_sect
+ [ssl_default_sect]
+ MinProtocol = TLSv1.3
+ EOF
+ f.close
+
+ ctx_proc = proc { |ctx|
+ ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
+ sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ assert_raise(OpenSSL::SSL::SSLError) do
+ ssl.connect
+ end
+ ssl.close
+ end;
+ end
+
+ ctx_proc = proc { |ctx|
+ ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;")
+ sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.sync_close = true
+ ssl.connect
+ assert_equal("TLSv1.3", ssl.ssl_version)
+ ssl.puts("abc"); assert_equal("abc\n", ssl.gets)
+ ssl.close
+ end;
+ end
+ }
+ end
+
def test_options_disable_versions
# It's recommended to use SSLContext#{min,max}_version= instead in real
# applications. The purpose of this test case is to check that SSL options
# are properly propagated to OpenSSL library.
supported = check_supported_protocol_versions
- if !defined?(OpenSSL::SSL::TLS1_3_VERSION) ||
- !supported.include?(OpenSSL::SSL::TLS1_2_VERSION) ||
+ if !supported.include?(OpenSSL::SSL::TLS1_2_VERSION) ||
!supported.include?(OpenSSL::SSL::TLS1_3_VERSION)
pend "this test case requires both TLS 1.2 and TLS 1.3 to be supported " \
"and enabled by default"
@@ -1398,7 +1578,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# Client only supports TLS 1.2
ctx1 = OpenSSL::SSL::SSLContext.new
ctx1.min_version = ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION
- assert_handshake_error { server_connect(port, ctx1) { } }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) }
# Client only supports TLS 1.3
ctx2 = OpenSSL::SSL::SSLContext.new
@@ -1414,7 +1594,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# Client doesn't support TLS 1.2
ctx1 = OpenSSL::SSL::SSLContext.new
ctx1.options |= OpenSSL::SSL::OP_NO_TLSv1_2
- assert_handshake_error { server_connect(port, ctx1) { } }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) }
# Client supports TLS 1.2 by default
ctx2 = OpenSSL::SSL::SSLContext.new
@@ -1438,7 +1618,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
num_handshakes = 0
renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 }
ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb }
- start_server_version(:SSLv23, ctx_proc) { |port|
+ start_server(ctx_proc: ctx_proc) { |port|
server_connect(port) { |ssl|
assert_equal(1, num_handshakes)
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
@@ -1454,7 +1634,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
}
ctx.alpn_protocols = advertised
}
- start_server_version(:SSLv23, ctx_proc) { |port|
+ start_server(ctx_proc: ctx_proc) { |port|
ctx = OpenSSL::SSL::SSLContext.new
ctx.alpn_protocols = advertised
server_connect(port, ctx) { |ssl|
@@ -1496,9 +1676,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
advertised = ["http/1.1", "spdy/2"]
ctx_proc = proc { |ctx| ctx.npn_protocols = advertised }
- start_server_version(:TLSv1_2, ctx_proc) { |port|
+ start_server(ctx_proc: ctx_proc) { |port|
selector = lambda { |which|
ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
ctx.npn_select_cb = -> (protocols) { protocols.send(which) }
server_connect(port, ctx) { |ssl|
assert_equal(advertised.send(which), ssl.npn_protocol)
@@ -1518,9 +1699,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
yield "spdy/2"
end
ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised }
- start_server_version(:TLSv1_2, ctx_proc) { |port|
+ start_server(ctx_proc: ctx_proc) { |port|
selector = lambda { |selected, which|
ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
ctx.npn_select_cb = -> (protocols) { protocols.to_a.send(which) }
server_connect(port, ctx) { |ssl|
assert_equal(selected, ssl.npn_protocol)
@@ -1535,8 +1717,9 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] }
- start_server_version(:TLSv1_2, ctx_proc) { |port|
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new }
assert_raise(RuntimeError) { server_connect(port, ctx) }
}
@@ -1545,22 +1728,22 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
def test_npn_advertised_protocol_too_long
return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
- ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["a" * 256] }
- start_server_version(:TLSv1_2, ctx_proc) { |port|
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.npn_select_cb = -> (protocols) { protocols.first }
- assert_handshake_error { server_connect(port, ctx) }
- }
+ ctx = OpenSSL::SSL::SSLContext.new
+ assert_raise(OpenSSL::SSL::SSLError) do
+ ctx.npn_protocols = ["a" * 256]
+ ctx.setup
+ end
end
def test_npn_selected_protocol_too_long
return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] }
- start_server_version(:TLSv1_2, ctx_proc) { |port|
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port|
ctx = OpenSSL::SSL::SSLContext.new
+ ctx.max_version = :TLS1_2
ctx.npn_select_cb = -> (protocols) { "a" * 256 }
- assert_handshake_error { server_connect(port, ctx) }
+ assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) }
}
end
@@ -1592,14 +1775,17 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_get_ephemeral_key
+ # kRSA is not FIPS-approved.
+ omit_on_fips
+
# kRSA
ctx_proc1 = proc { |ctx|
- ctx.ssl_version = :TLSv1_2
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
ctx.ciphers = "kRSA"
}
start_server(ctx_proc: ctx_proc1, ignore_listener_error: true) do |port|
ctx = OpenSSL::SSL::SSLContext.new
- ctx.ssl_version = :TLSv1_2
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
ctx.ciphers = "kRSA"
begin
server_connect(port, ctx) { |ssl| assert_nil ssl.tmp_key }
@@ -1610,30 +1796,27 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
# DHE
- # TODO: How to test this with TLS 1.3?
- ctx_proc2 = proc { |ctx|
- ctx.ssl_version = :TLSv1_2
- ctx.ciphers = "EDH"
- ctx.tmp_dh = Fixtures.pkey("dh-1")
- }
- start_server(ctx_proc: ctx_proc2) do |port|
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.ssl_version = :TLSv1_2
- ctx.ciphers = "EDH"
- server_connect(port, ctx) { |ssl|
- assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key
- }
+ # OpenSSL 3.0 added support for named FFDHE groups in TLS 1.3
+ # LibreSSL does not support named FFDHE groups currently
+ # AWS-LC does not support DHE ciphersuites
+ if openssl?(3, 0, 0)
+ start_server do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "ffdhe3072"
+ server_connect(port, ctx) { |ssl|
+ assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key
+ assert_equal 3072, ssl.tmp_key.p.num_bits
+ ssl.puts "abc"; assert_equal "abc\n", ssl.gets
+ }
+ end
end
# ECDHE
ctx_proc3 = proc { |ctx|
- ctx.ciphers = "DEFAULT:!kRSA:!kEDH"
- ctx.ecdh_curves = "P-256"
+ ctx.groups = "P-256"
}
start_server(ctx_proc: ctx_proc3) do |port|
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.ciphers = "DEFAULT:!kRSA:!kEDH"
- server_connect(port, ctx) { |ssl|
+ server_connect(port) { |ssl|
assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
@@ -1642,11 +1825,11 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
def test_fallback_scsv
supported = check_supported_protocol_versions
- return unless supported.include?(OpenSSL::SSL::TLS1_1_VERSION) &&
- supported.include?(OpenSSL::SSL::TLS1_2_VERSION)
+ unless supported.include?(OpenSSL::SSL::TLS1_1_VERSION)
+ omit "TLS 1.1 support is required to run this test case"
+ end
- pend "Fallback SCSV is not supported" unless \
- OpenSSL::SSL::SSLContext.method_defined?(:enable_fallback_scsv)
+ omit "Fallback SCSV is not supported" if libressl?
start_server do |port|
ctx = OpenSSL::SSL::SSLContext.new
@@ -1657,11 +1840,15 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
ctx_proc = proc { |ctx|
+ ctx.security_level = 0
+ ctx.min_version = 0
ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION
}
start_server(ctx_proc: ctx_proc) do |port|
ctx = OpenSSL::SSL::SSLContext.new
ctx.enable_fallback_scsv
+ ctx.security_level = 0
+ ctx.min_version = 0
ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION
# Here is OK too
# TLS1.2 not supported, fallback to TLS1.1 and signaling the fallback
@@ -1679,19 +1866,24 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# Otherwise, this test fails when using openssl 1.1.1 (or later) that supports TLS1.3.
# TODO: We may need another test for TLS1.3 because it seems to have a different mechanism.
ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.security_level = 0
+ ctx1.min_version = 0
ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION
s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
ctx2 = OpenSSL::SSL::SSLContext.new
ctx2.enable_fallback_scsv
+ ctx2.security_level = 0
+ ctx2.min_version = 0
ctx2.max_version = OpenSSL::SSL::TLS1_1_VERSION
s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+ # AWS-LC has slightly different error messages in all-caps.
t = Thread.new {
- assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback/) {
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) {
s2.connect
}
}
- assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback/) {
+ assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) {
s1.accept
}
t.join
@@ -1702,6 +1894,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_tmp_dh_callback
+ # DH missing the q value on unknown named parameters is not FIPS-approved.
+ omit_on_fips
+ omit "AWS-LC does not support DHE ciphersuites" if aws_lc?
+
dh = Fixtures.pkey("dh-1")
called = false
ctx_proc = -> ctx {
@@ -1713,7 +1909,9 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
}
}
start_server(ctx_proc: ctx_proc) do |port|
- server_connect(port) { |ssl|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "P-256" # Exclude RFC 7919 groups
+ server_connect(port, ctx) { |ssl|
assert called, "dh callback should be called"
assert_equal dh.to_der, ssl.tmp_key.to_der
}
@@ -1721,11 +1919,6 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_ciphersuites_method_tls_connection
- ssl_ctx = OpenSSL::SSL::SSLContext.new
- if !tls13_supported? || !ssl_ctx.respond_to?(:ciphersuites=)
- pend 'TLS 1.3 not supported'
- end
-
csuite = ['TLS_AES_128_GCM_SHA256', 'TLSv1.3', 128, 128]
inputs = [csuite[0], [csuite[0]], [csuite]]
@@ -1746,26 +1939,21 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
def test_ciphersuites_method_nil_argument
ssl_ctx = OpenSSL::SSL::SSLContext.new
- pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
-
assert_nothing_raised { ssl_ctx.ciphersuites = nil }
end
def test_ciphersuites_method_frozen_object
ssl_ctx = OpenSSL::SSL::SSLContext.new
- pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
-
ssl_ctx.freeze
assert_raise(FrozenError) { ssl_ctx.ciphersuites = 'TLS_AES_256_GCM_SHA384' }
end
def test_ciphersuites_method_bogus_csuite
ssl_ctx = OpenSSL::SSL::SSLContext.new
- pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=)
-
+ # AWS-LC has slightly different error messages in all-caps.
assert_raise_with_message(
OpenSSL::SSL::SSLError,
- /SSL_CTX_set_ciphersuites: no cipher match/i
+ /SSL_CTX_set_ciphersuites: (no cipher match|NO_CIPHER_MATCH)/i
) { ssl_ctx.ciphersuites = 'BOGUS' }
end
@@ -1801,34 +1989,184 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_ciphers_method_bogus_csuite
- omit "Old #{OpenSSL::OPENSSL_LIBRARY_VERSION}" if
- year = OpenSSL::OPENSSL_LIBRARY_VERSION[/\A OpenSSL\s+[01]\..*\s\K\d+\z/x] and
- year.to_i <= 2018
-
ssl_ctx = OpenSSL::SSL::SSLContext.new
+ # AWS-LC has slightly different error messages in all-caps.
assert_raise_with_message(
OpenSSL::SSL::SSLError,
- /SSL_CTX_set_cipher_list: no cipher match/i
+ /SSL_CTX_set_cipher_list: (no cipher match|NO_CIPHER_MATCH)/i
) { ssl_ctx.ciphers = 'BOGUS' }
end
+ def test_sigalgs
+ omit "SSL_CTX_set1_sigalgs_list() not supported" if libressl?
+
+ svr_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ["subjectAltName", "DNS:localhost", false],
+ ]
+ ecdsa_key = Fixtures.pkey("p256")
+ ecdsa_cert = issue_cert(@svr, ecdsa_key, 10, svr_exts, @ca_cert, @ca_key)
+
+ ctx_proc = -> ctx {
+ # Unset values set by start_server
+ ctx.cert = ctx.key = ctx.extra_chain_cert = nil
+ ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA
+ ctx.add_certificate(ecdsa_cert, ecdsa_key, [@ca_cert]) # ECDSA
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.sigalgs = "rsa_pss_rsae_sha256"
+ server_connect(port, ctx1) { |ssl|
+ assert_kind_of(OpenSSL::PKey::RSA, ssl.peer_cert.public_key)
+ ssl.puts("abc"); ssl.gets
+ }
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.sigalgs = "ed25519:ecdsa_secp256r1_sha256"
+ server_connect(port, ctx2) { |ssl|
+ assert_kind_of(OpenSSL::PKey::EC, ssl.peer_cert.public_key)
+ ssl.puts("abc"); ssl.gets
+ }
+ end
+
+ # Frozen
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ ssl_ctx.freeze
+ assert_raise(FrozenError) { ssl_ctx.sigalgs = "ECDSA+SHA256:RSA+SHA256" }
+
+ # Bogus
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
+ assert_raise(TypeError) { ssl_ctx.sigalgs = nil }
+ assert_raise(OpenSSL::SSL::SSLError) { ssl_ctx.sigalgs = "BOGUS" }
+ end
+
+ def test_client_sigalgs
+ omit "SSL_CTX_set1_client_sigalgs_list() not supported" if libressl? || aws_lc?
+
+ cli_exts = [
+ ["keyUsage", "keyEncipherment,digitalSignature", true],
+ ["subjectAltName", "DNS:localhost", false],
+ ]
+ ecdsa_key = Fixtures.pkey("p256")
+ ecdsa_cert = issue_cert(@cli, ecdsa_key, 10, cli_exts, @ca_cert, @ca_key)
+
+ ctx_proc = -> ctx {
+ store = OpenSSL::X509::Store.new
+ store.add_cert(@ca_cert)
+ store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+ ctx.cert_store = store
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+ ctx.client_sigalgs = "ECDSA+SHA256"
+ }
+ start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
+ ctx1 = OpenSSL::SSL::SSLContext.new
+ ctx1.add_certificate(@cli_cert, @cli_key) # RSA
+ assert_handshake_error {
+ server_connect(port, ctx1) { |ssl|
+ ssl.puts("abc"); ssl.gets
+ }
+ }
+
+ ctx2 = OpenSSL::SSL::SSLContext.new
+ ctx2.add_certificate(ecdsa_cert, ecdsa_key) # ECDSA
+ server_connect(port, ctx2) { |ssl|
+ ssl.puts("abc"); ssl.gets
+ }
+ end
+ end
+
+ def test_get_sigalg
+ # SSL_get0_signature_name() not supported
+ # SSL_get0_peer_signature_name() not supported
+ return unless openssl?(3, 5, 0)
+
+ server_proc = -> (ctx, ssl) {
+ assert_equal('rsa_pss_rsae_sha256', ssl.sigalg)
+ assert_nil(ssl.peer_sigalg)
+
+ readwrite_loop(ctx, ssl)
+ }
+ start_server(server_proc: server_proc) do |port|
+ cli_ctx = OpenSSL::SSL::SSLContext.new
+ server_connect(port, cli_ctx) do |ssl|
+ assert_nil(ssl.sigalg)
+ assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg)
+ ssl.puts "abc"; ssl.gets
+ end
+ end
+ end
+
+ def test_pqc_sigalg
+ # PQC algorithm ML-DSA (FIPS 204) is supported on OpenSSL 3.5 or later.
+ return unless openssl?(3, 5, 0)
+
+ mldsa = Fixtures.pkey("mldsa65-1")
+ mldsa_ca_key = Fixtures.pkey("mldsa65-2")
+ mldsa_ca_cert = issue_cert(@ca, mldsa_ca_key, 1, @ca_exts, nil, nil,
+ digest: nil)
+ mldsa_cert = issue_cert(@svr, mldsa, 60, [], mldsa_ca_cert, mldsa_ca_key,
+ digest: nil)
+ rsa = Fixtures.pkey("rsa-1")
+ rsa_cert = issue_cert(@svr, rsa, 61, [], @ca_cert, @ca_key)
+ ctx_proc = -> ctx {
+ # Unset values set by start_server
+ ctx.cert = ctx.key = ctx.extra_chain_cert = nil
+ ctx.sigalgs = "rsa_pss_rsae_sha256:mldsa65"
+ ctx.add_certificate(mldsa_cert, mldsa)
+ ctx.add_certificate(rsa_cert, rsa)
+ }
+
+ server_proc = -> (ctx, ssl) {
+ assert_equal('mldsa65', ssl.sigalg)
+
+ readwrite_loop(ctx, ssl)
+ }
+ start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ # Set signature algorithm because while OpenSSL may use ML-DSA by
+ # default, the system OpenSSL configuration affects the used signature
+ # algorithm.
+ ctx.sigalgs = 'mldsa65'
+ server_connect(port, ctx) { |ssl|
+ assert_equal('mldsa65', ssl.peer_sigalg)
+ ssl.puts "abc"; ssl.gets
+ }
+ end
+
+ server_proc = -> (ctx, ssl) {
+ assert_equal('rsa_pss_rsae_sha256', ssl.sigalg)
+
+ readwrite_loop(ctx, ssl)
+ }
+ start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.sigalgs = 'rsa_pss_rsae_sha256'
+ server_connect(port, ctx) { |ssl|
+ assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg)
+ ssl.puts "abc"; ssl.gets
+ }
+ end
+ end
+
def test_connect_works_when_setting_dh_callback_to_nil
+ omit "AWS-LC does not support DHE ciphersuites" if aws_lc?
+
ctx_proc = -> ctx {
ctx.max_version = :TLS1_2
ctx.ciphers = "DH:!NULL" # use DH
ctx.tmp_dh_callback = nil
}
start_server(ctx_proc: ctx_proc) do |port|
- EnvUtil.suppress_warning { # uses default callback
- assert_nothing_raised {
- server_connect(port) { }
- }
- }
+ assert_nothing_raised { server_connect(port) { } }
end
end
def test_tmp_dh
+ # DH missing the q value on unknown named parameters is not FIPS-approved.
+ omit_on_fips
+ omit "AWS-LC does not support DHE ciphersuites" if aws_lc?
+
dh = Fixtures.pkey("dh-1")
ctx_proc = -> ctx {
ctx.max_version = :TLS1_2
@@ -1836,92 +2174,133 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
ctx.tmp_dh = dh
}
start_server(ctx_proc: ctx_proc) do |port|
- server_connect(port) { |ssl|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = "P-256" # Exclude RFC 7919 groups
+ server_connect(port, ctx) { |ssl|
assert_equal dh.to_der, ssl.tmp_key.to_der
}
end
end
- def test_ecdh_curves_tls12
+ def test_set_groups_tls12
ctx_proc = -> ctx {
# Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3
ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
ctx.ciphers = "kEECDH"
- ctx.ecdh_curves = "P-384:P-521"
+ ctx.groups = "P-384:P-521"
}
start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
# Test 1: Client=P-256:P-384, Server=P-384:P-521 --> P-384
ctx = OpenSSL::SSL::SSLContext.new
- ctx.ecdh_curves = "P-256:P-384"
+ ctx.groups = "P-256:P-384"
server_connect(port, ctx) { |ssl|
cs = ssl.cipher[0]
assert_match (/\AECDH/), cs
+ # SSL_get0_group_name() is supported on OpenSSL 3.2 or later.
+ assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0)
assert_equal "secp384r1", ssl.tmp_key.group.curve_name
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
# Test 2: Client=P-256, Server=P-521:P-384 --> Fail
ctx = OpenSSL::SSL::SSLContext.new
- ctx.ecdh_curves = "P-256"
+ ctx.groups = "P-256"
assert_raise(OpenSSL::SSL::SSLError) {
server_connect(port, ctx) { }
}
# Test 3: Client=P-521:P-384, Server=P-521:P-384 --> P-521
ctx = OpenSSL::SSL::SSLContext.new
- ctx.ecdh_curves = "P-521:P-384"
+ ctx.groups = "P-521:P-384"
server_connect(port, ctx) { |ssl|
assert_equal "secp521r1", ssl.tmp_key.group.curve_name
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
+
+ # Test 4: #ecdh_curves= alias
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.ecdh_curves = "P-256:P-384"
+ server_connect(port, ctx) { |ssl|
+ assert_equal "secp384r1", ssl.tmp_key.group.curve_name
+ }
end
end
- def test_ecdh_curves_tls13
- pend "TLS 1.3 not supported" unless tls13_supported?
-
+ def test_set_groups_tls13
ctx_proc = -> ctx {
# Assume TLS 1.3 is enabled and chosen by default
- ctx.ecdh_curves = "P-384:P-521"
+ ctx.groups = "P-384:P-521"
}
start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
ctx = OpenSSL::SSL::SSLContext.new
- ctx.ecdh_curves = "P-256:P-384" # disable P-521
+ ctx.groups = "P-256:P-384" # disable P-521
server_connect(port, ctx) { |ssl|
assert_equal "TLSv1.3", ssl.ssl_version
+ # SSL_get0_group_name() is supported on OpenSSL 3.2 or later.
+ assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0)
assert_equal "secp384r1", ssl.tmp_key.group.curve_name
ssl.puts "abc"; assert_equal "abc\n", ssl.gets
}
end
end
+ def test_pqc_group
+ # PQC algorithm ML-KEM (FIPS 203) is supported on OpenSSL 3.5 or later.
+ return unless openssl?(3, 5, 0)
+
+ [
+ 'X25519MLKEM768',
+ 'SecP256r1MLKEM768',
+ 'SecP384r1MLKEM1024'
+ ].each do |group|
+ ctx_proc = -> ctx {
+ ctx.groups = group
+ }
+ start_server(ctx_proc: ctx_proc) do |port|
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.groups = group
+ server_connect(port, ctx) { |ssl|
+ assert_equal(group, ssl.group)
+ ssl.puts "abc"; ssl.gets
+ }
+ end
+ end
+ end
+
def test_security_level
ctx = OpenSSL::SSL::SSLContext.new
- begin
- ctx.security_level = 1
- rescue NotImplementedError
+ ctx.security_level = 1
+ if aws_lc? # AWS-LC does not support security levels.
assert_equal(0, ctx.security_level)
return
end
assert_equal(1, ctx.security_level)
- dsa512 = Fixtures.pkey("dsa512")
- dsa512_cert = issue_cert(@svr, dsa512, 50, [], @ca_cert, @ca_key)
- rsa1024 = Fixtures.pkey("rsa1024")
- rsa1024_cert = issue_cert(@svr, rsa1024, 51, [], @ca_cert, @ca_key)
+ # See SSL_CTX_set_security_level(3). Definitions of security levels may
+ # change in future OpenSSL versions. As of OpenSSL 1.1.0:
+ # - Level 1 requires 160-bit ECC keys or 1024-bit RSA keys.
+ # - Level 2 requires 224-bit ECC keys or 2048-bit RSA keys.
+ begin
+ ec112 = OpenSSL::PKey::EC.generate("secp112r1")
+ ec112_cert = issue_cert(@svr, ec112, 50, [], @ca_cert, @ca_key)
+ ec192 = OpenSSL::PKey::EC.generate("prime192v1")
+ ec192_cert = issue_cert(@svr, ec192, 51, [], @ca_cert, @ca_key)
+ rescue OpenSSL::PKey::PKeyError
+ # Distro-provided OpenSSL may refuse to generate small keys
+ return
+ end
assert_raise(OpenSSL::SSL::SSLError) {
- # 512 bit DSA key is rejected because it offers < 80 bits of security
- ctx.add_certificate(dsa512_cert, dsa512)
+ ctx.add_certificate(ec112_cert, ec112)
}
assert_nothing_raised {
- ctx.add_certificate(rsa1024_cert, rsa1024)
+ ctx.add_certificate(ec192_cert, ec192)
}
ctx.security_level = 2
assert_raise(OpenSSL::SSL::SSLError) {
# < 112 bits of security
- ctx.add_certificate(rsa1024_cert, rsa1024)
+ ctx.add_certificate(ec192_cert, ec192)
}
end
@@ -1977,22 +2356,52 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
end
- private
+ # OpenSSL::Buffering requires $/ accessible from non-main Ractors (Ruby 4.0)
+ # https://bugs.ruby-lang.org/issues/21109
+ #
+ # Hangs on Windows
+ # https://bugs.ruby-lang.org/issues/21537
+ if respond_to?(:ractor) && RUBY_VERSION >= "4.0" && RUBY_PLATFORM !~ /mswin|mingw/
+ ractor
+ def test_ractor_client
+ start_server { |port|
+ s = Ractor.new(port, @ca_cert) { |port, ca_cert|
+ sock = TCPSocket.new("127.0.0.1", port)
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ ctx.cert_store = OpenSSL::X509::Store.new.tap { |store|
+ store.add_cert(ca_cert)
+ }
+ begin
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+ ssl.connect
+ ssl.puts("abc")
+ ssl.gets
+ ensure
+ ssl.close
+ sock.close
+ end
+ }.value
+ assert_equal("abc\n", s)
+ }
+ end
- def start_server_version(version, ctx_proc = nil,
- server_proc = method(:readwrite_loop), &blk)
- ctx_wrap = Proc.new { |ctx|
- ctx.ssl_version = version
- ctx_proc.call(ctx) if ctx_proc
- }
- start_server(
- ctx_proc: ctx_wrap,
- server_proc: server_proc,
- ignore_listener_error: true,
- &blk
- )
+ ractor
+ def test_ractor_set_params
+ # We cannot actually test default stores in the test suite as it depends
+ # on the environment, but at least check that it does not raise an
+ # exception
+ ok = Ractor.new {
+ ctx = OpenSSL::SSL::SSLContext.new
+ ctx.set_params
+ ctx.cert_store.kind_of?(OpenSSL::X509::Store)
+ }.value
+ assert(ok, "ctx.cert_store is an instance of OpenSSL::X509::Store")
+ end
end
+ private
+
def server_connect(port, ctx = nil)
sock = TCPSocket.new("127.0.0.1", port)
ssl = ctx ? OpenSSL::SSL::SSLSocket.new(sock, ctx) : OpenSSL::SSL::SSLSocket.new(sock)
diff --git a/test/openssl/test_ssl_session.rb b/test/openssl/test_ssl_session.rb
index 4fa3821177..37874ca273 100644
--- a/test/openssl/test_ssl_session.rb
+++ b/test/openssl/test_ssl_session.rb
@@ -5,7 +5,9 @@ if defined?(OpenSSL::SSL)
class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase
def test_session
- ctx_proc = proc { |ctx| ctx.ssl_version = :TLSv1_2 }
+ ctx_proc = proc { |ctx|
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
+ }
start_server(ctx_proc: ctx_proc) do |port|
server_connect_with_session(port, nil, nil) { |ssl|
session = ssl.session
@@ -28,9 +30,10 @@ class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase
end
end
+ # PEM file updated to use TLS 1.2 with ECDHE-RSA-AES256-SHA.
DUMMY_SESSION = <<__EOS__
-----BEGIN SSL SESSION PARAMETERS-----
-MIIDzQIBAQICAwEEAgA5BCAF219w9ZEV8dNA60cpEGOI34hJtIFbf3bkfzSgMyad
+MIIDzQIBAQICAwMEAsAUBCAF219w9ZEV8dNA60cpEGOI34hJtIFbf3bkfzSgMyad
MQQwyGLbkCxE4OiMLdKKem+pyh8V7ifoP7tCxhdmwoDlJxI1v6nVCjai+FGYuncy
NNSWoQYCBE4DDWuiAwIBCqOCAo4wggKKMIIBcqADAgECAgECMA0GCSqGSIb3DQEB
BQUAMD0xEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5
@@ -54,9 +57,10 @@ j+RBGfCFrrQbBdnkFI/ztgM=
-----END SSL SESSION PARAMETERS-----
__EOS__
+ # PEM file updated to use TLS 1.1 with ECDHE-RSA-AES256-SHA.
DUMMY_SESSION_NO_EXT = <<-__EOS__
-----BEGIN SSL SESSION PARAMETERS-----
-MIIDCAIBAQICAwAEAgA5BCDyAW7rcpzMjDSosH+Tv6sukymeqgq3xQVVMez628A+
+MIIDCAIBAQICAwIEAsAUBCDyAW7rcpzMjDSosH+Tv6sukymeqgq3xQVVMez628A+
lAQw9TrKzrIqlHEh6ltuQaqv/Aq83AmaAlogYktZgXAjOGnhX7ifJDNLMuCfQq53
hPAaoQYCBE4iDeeiBAICASyjggKOMIICijCCAXKgAwIBAgIBAjANBgkqhkiG9w0B
AQUFADA9MRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVi
@@ -120,7 +124,8 @@ __EOS__
ctx.options &= ~OpenSSL::SSL::OP_NO_TICKET
# Disable server-side session cache which is enabled by default
ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF
- ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?
+ # Session tickets must be retrieved via ctx.session_new_cb in TLS 1.3 in AWS-LC.
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? || aws_lc?
}
start_server(ctx_proc: ctx_proc) do |port|
sess1 = server_connect_with_session(port, nil, nil) { |ssl|
@@ -143,7 +148,7 @@ __EOS__
def test_server_session_cache
ctx_proc = Proc.new do |ctx|
- ctx.ssl_version = :TLSv1_2
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
ctx.options |= OpenSSL::SSL::OP_NO_TICKET
end
@@ -197,7 +202,7 @@ __EOS__
10.times do |i|
connections = i
cctx = OpenSSL::SSL::SSLContext.new
- cctx.ssl_version = :TLSv1_2
+ cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
server_connect_with_session(port, cctx, first_session) { |ssl|
ssl.puts("abc"); assert_equal "abc\n", ssl.gets
first_session ||= ssl.session
@@ -217,7 +222,7 @@ __EOS__
# Skipping tests that use session_remove_cb by default because it may cause
# deadlock.
- TEST_SESSION_REMOVE_CB = ENV["OSSL_TEST_ALL"] == "1"
+ TEST_SESSION_REMOVE_CB = ENV["OSSL_TEST_UNSAFE"] == "1"
def test_ctx_client_session_cb_tls12
start_server do |port|
@@ -237,21 +242,25 @@ __EOS__
end
server_connect_with_session(port, ctx, nil) { |ssl|
- assert_equal(1, ctx.session_cache_stats[:cache_num])
assert_equal(1, ctx.session_cache_stats[:connect_good])
assert_equal([ssl, ssl.session], called[:new])
- assert_equal(true, ctx.session_remove(ssl.session))
- assert_equal(false, ctx.session_remove(ssl.session))
- if TEST_SESSION_REMOVE_CB
- assert_equal([ctx, ssl.session], called[:remove])
+ # AWS-LC doesn't support internal session caching on the client, but
+ # the callback is still enabled as expected.
+ unless aws_lc?
+ assert_equal(1, ctx.session_cache_stats[:cache_num])
+ assert_equal(true, ctx.session_remove(ssl.session))
+ if TEST_SESSION_REMOVE_CB
+ assert_equal([ctx, ssl.session], called[:remove])
+ end
end
+ assert_equal(false, ctx.session_remove(ssl.session))
}
end
end
def test_ctx_client_session_cb_tls13
- omit "TLS 1.3 not supported" unless tls13_supported?
omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl?
+ omit "AWS-LC does not support internal session caching on the client" if aws_lc?
start_server do |port|
called = {}
@@ -274,7 +283,6 @@ __EOS__
end
def test_ctx_client_session_cb_tls13_exception
- omit "TLS 1.3 not supported" unless tls13_supported?
omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl?
server_proc = lambda do |ctx, ssl|
@@ -301,11 +309,11 @@ __EOS__
connections = nil
called = {}
cctx = OpenSSL::SSL::SSLContext.new
- cctx.ssl_version = :TLSv1_2
+ cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
sctx = nil
ctx_proc = Proc.new { |ctx|
sctx = ctx
- ctx.ssl_version = :TLSv1_2
+ ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
ctx.options |= OpenSSL::SSL::OP_NO_TICKET
# get_cb is called whenever a client proposed to resume a session but
@@ -375,11 +383,6 @@ __EOS__
connections = 2
sess2 = server_connect_with_session(port, cctx, sess0.dup) { |ssl|
ssl.puts("abc"); assert_equal "abc\n", ssl.gets
- if !ssl.session_reused? && openssl?(1, 1, 0) && !openssl?(1, 1, 0, 7)
- # OpenSSL >= 1.1.0, < 1.1.0g
- pend "External session cache is not working; " \
- "see https://github.com/openssl/openssl/pull/4014"
- end
assert_equal true, ssl.session_reused?
ssl.session
}
diff --git a/test/openssl/test_ts.rb b/test/openssl/test_ts.rb
index ac0469ad56..69780a6579 100644
--- a/test/openssl/test_ts.rb
+++ b/test/openssl/test_ts.rb
@@ -4,43 +4,11 @@ if defined?(OpenSSL) && defined?(OpenSSL::Timestamp)
class OpenSSL::TestTimestamp < OpenSSL::TestCase
def intermediate_key
- @intermediate_key ||= OpenSSL::PKey::RSA.new <<-_end_of_pem_
------BEGIN RSA PRIVATE KEY-----
-MIICWwIBAAKBgQCcyODxH+oTrr7l7MITWcGaYnnBma6vidCCJjuSzZpaRmXZHAyH
-0YcY4ttC0BdJ4uV+cE05IySVC7tyvVfFb8gFQ6XJV+AEktP+XkLbcxZgj9d2NVu1
-ziXdI+ldXkPnMhyWpMS5E7SD6gflv9NhUYEsmAGsUgdK6LDmm2W2/4TlewIDAQAB
-AoGAYgx6KDFWONLqjW3f/Sv/mGYHUNykUyDzpcD1Npyf797gqMMSzwlo3FZa2tC6
-D7n23XirwpTItvEsW9gvgMikJDPlThAeGLZ+L0UbVNNBHVxGP998Nda1kxqKvhRE
-pfZCKc7PLM9ZXc6jBTmgxdcAYfVCCVUoa2mEf9Ktr3BlI4kCQQDQAM09+wHDXGKP
-o2UnCwCazGtyGU2r0QCzHlh9BVY+KD2KjjhuWh86rEbdWN7hEW23Je1vXIhuM6Pa
-/Ccd+XYnAkEAwPZ91PK6idEONeGQ4I3dyMKV2SbaUjfq3MDL4iIQPQPuj7QsBO/5
-3Nf9ReSUUTRFCUVwoC8k4Z1KAJhR/K/ejQJANE7PTnPuGJQGETs09+GTcFpR9uqY
-FspDk8fg1ufdrVnvSAXF+TJewiGK3KU5v33jinhWQngRsyz3Wt2odKhEZwJACbjh
-oicQqvzzgFd7GzVKpWDYd/ZzLY1PsgusuhoJQ2m9TVRAm4cTycLAKhNYPbcqe0sa
-X5fAffWU0u7ZwqeByQJAOUAbYET4RU3iymAvAIDFj8LiQnizG9t5Ty3HXlijKQYv
-y8gsvWd4CdxwOPatWpBUX9L7IXcMJmD44xXTUvpbfQ==
------END RSA PRIVATE KEY-----
-_end_of_pem_
+ @intermediate_key ||= Fixtures.pkey("rsa-1")
end
def ee_key
- @ee_key ||= OpenSSL::PKey::RSA.new <<-_end_of_pem_
------BEGIN RSA PRIVATE KEY-----
-MIICWwIBAAKBgQDA6eB5r2O5KOKNbKMBhzadl43lgpwqq28m+G0gH38kKCL1f3o9
-P8xUZm7sZqcWEervZMSSXMGBV9DgeoSR+U6FMJywgQGx/JNRx7wZTMNym3PvgLkl
-xCXh6ZA0/xbtJtcNI+UUv0ENBkTIuUWBhkAf3jQclAr9aQ0ktYBuHAcRcQIDAQAB
-AoGAKNhcAuezwZx6e18pFEXAtpVEIfgJgK9TlXi8AjUpAkrNPBWFmDpN1QDrM3p4
-nh+lEpLPW/3vqqchPqYyM4YJraMLpS3KUG+s7+m9QIia0ri2WV5Cig7WL+Tl9p7K
-b3oi2Aj/wti8GfOLFQXOQQ4Ea4GoCv2Sxe0GZR39UBxzTsECQQD1zuVIwBvqU2YR
-8innsoa+j4u2hulRmQO6Zgpzj5vyRYfA9uZxQ9nKbfJvzuWwUv+UzyS9RqxarqrP
-5nQw5EmVAkEAyOmJg6+AfGrgvSWfSpXEds/WA/sHziCO3rE4/sd6cnDc6XcTgeMs
-mT8Z3kAYGpqFDew5orUylPfJJa+PUueJbQJAY+gkvw3+Cp69FLw1lgu0wo07fwOU
-n2qu3jsNMm0DOFRUWfTAMvcd9S385L7WEnWZldUfnKK1+OGXYYrMXPbchQJAChU2
-UoaHQzc16iguM1cK0g+iJPb/MEgQA3sPajHmokGpxIm2T+lvvo0dJjs/Om6QyN8X
-EWRYkoNQ8/Q4lCeMjQJAfvDIGtyqF4PieFHYgluQAv5pGgYpakdc8SYyeRH9NKey
-GaL27FRs4fRWf9OmxPhUVgIyGzLGXrueemvQUDHObA==
------END RSA PRIVATE KEY-----
-_end_of_pem_
+ @ee_key ||= Fixtures.pkey("rsa-2")
end
def ca_cert
@@ -70,15 +38,14 @@ _end_of_pem_
def test_request_mandatory_fields
req = OpenSSL::Timestamp::Request.new
assert_raise(OpenSSL::Timestamp::TimestampError) do
- tmp = req.to_der
- pp OpenSSL::ASN1.decode(tmp)
+ req.to_der
end
req.algorithm = "sha1"
assert_raise(OpenSSL::Timestamp::TimestampError) do
req.to_der
end
req.message_imprint = OpenSSL::Digest.digest('SHA1', "data")
- req.to_der
+ assert_nothing_raised { req.to_der }
end
def test_request_assignment
@@ -89,8 +56,9 @@ _end_of_pem_
assert_raise(TypeError) { req.version = nil }
assert_raise(TypeError) { req.version = "foo" }
- req.algorithm = "SHA1"
+ req.algorithm = "sha1"
assert_equal("SHA1", req.algorithm)
+ assert_equal("SHA1", OpenSSL::ASN1.ObjectId("SHA1").sn)
assert_raise(TypeError) { req.algorithm = nil }
assert_raise(OpenSSL::ASN1::ASN1Error) { req.algorithm = "xxx" }
@@ -371,60 +339,60 @@ _end_of_pem_
end
def test_response_no_policy_defined
- assert_raise(OpenSSL::Timestamp::TimestampError) do
- req = OpenSSL::Timestamp::Request.new
- req.algorithm = "SHA1"
- digest = OpenSSL::Digest.digest('SHA1', "test")
- req.message_imprint = digest
+ req = OpenSSL::Timestamp::Request.new
+ req.algorithm = "SHA1"
+ digest = OpenSSL::Digest.digest('SHA1', "test")
+ req.message_imprint = digest
- fac = OpenSSL::Timestamp::Factory.new
- fac.gen_time = Time.now
- fac.serial_number = 1
- fac.allowed_digests = ["sha1"]
+ fac = OpenSSL::Timestamp::Factory.new
+ fac.gen_time = Time.now
+ fac.serial_number = 1
+ fac.allowed_digests = ["sha1"]
+ assert_raise(OpenSSL::Timestamp::TimestampError) do
fac.create_timestamp(ee_key, ts_cert_ee, req)
end
end
def test_verify_ee_no_req
+ ts, _ = timestamp_ee
assert_raise(TypeError) do
- ts, _ = timestamp_ee
ts.verify(nil, ca_cert)
end
end
def test_verify_ee_no_store
+ ts, req = timestamp_ee
assert_raise(TypeError) do
- ts, req = timestamp_ee
ts.verify(req, nil)
end
end
def test_verify_ee_wrong_root_no_intermediate
+ ts, req = timestamp_ee
assert_raise(OpenSSL::Timestamp::TimestampError) do
- ts, req = timestamp_ee
ts.verify(req, intermediate_store)
end
end
def test_verify_ee_wrong_root_wrong_intermediate
+ ts, req = timestamp_ee
assert_raise(OpenSSL::Timestamp::TimestampError) do
- ts, req = timestamp_ee
ts.verify(req, intermediate_store, [ca_cert])
end
end
def test_verify_ee_nonce_mismatch
+ ts, req = timestamp_ee
+ req.nonce = 1
assert_raise(OpenSSL::Timestamp::TimestampError) do
- ts, req = timestamp_ee
- req.nonce = 1
ts.verify(req, ca_store, [intermediate_cert])
end
end
def test_verify_ee_intermediate_missing
+ ts, req = timestamp_ee
assert_raise(OpenSSL::Timestamp::TimestampError) do
- ts, req = timestamp_ee
ts.verify(req, ca_store)
end
end
@@ -472,27 +440,27 @@ _end_of_pem_
end
def test_verify_direct_wrong_root
+ ts, req = timestamp_direct
assert_raise(OpenSSL::Timestamp::TimestampError) do
- ts, req = timestamp_direct
ts.verify(req, intermediate_store)
end
end
def test_verify_direct_no_cert_no_intermediate
+ ts, req = timestamp_direct_no_cert
assert_raise(OpenSSL::Timestamp::TimestampError) do
- ts, req = timestamp_direct_no_cert
ts.verify(req, ca_store)
end
end
def test_verify_ee_no_cert
ts, req = timestamp_ee_no_cert
- ts.verify(req, ca_store, [ts_cert_ee, intermediate_cert])
+ assert_same(ts, ts.verify(req, ca_store, [ts_cert_ee, intermediate_cert]))
end
def test_verify_ee_no_cert_no_intermediate
+ ts, req = timestamp_ee_no_cert
assert_raise(OpenSSL::Timestamp::TimestampError) do
- ts, req = timestamp_ee_no_cert
ts.verify(req, ca_store, [ts_cert_ee])
end
end
diff --git a/test/openssl/test_x509cert.rb b/test/openssl/test_x509cert.rb
index 4f7aa0cb10..9e0aa4edf6 100644
--- a/test/openssl/test_x509cert.rb
+++ b/test/openssl/test_x509cert.rb
@@ -6,17 +6,16 @@ if defined?(OpenSSL)
class OpenSSL::TestX509Certificate < OpenSSL::TestCase
def setup
super
- @rsa1024 = Fixtures.pkey("rsa1024")
- @rsa2048 = Fixtures.pkey("rsa2048")
- @dsa256 = Fixtures.pkey("dsa256")
- @dsa512 = Fixtures.pkey("dsa512")
+ @rsa1 = Fixtures.pkey("rsa-1")
+ @rsa2 = Fixtures.pkey("rsa-2")
+ @ec1 = Fixtures.pkey("p256")
@ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
@ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
end
def test_serial
[1, 2**32, 2**100].each{|s|
- cert = issue_cert(@ca, @rsa2048, s, [], nil, nil)
+ cert = issue_cert(@ca, @rsa1, s, [], nil, nil)
assert_equal(s, cert.serial)
cert = OpenSSL::X509::Certificate.new(cert.to_der)
assert_equal(s, cert.serial)
@@ -29,40 +28,34 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
["subjectKeyIdentifier","hash",false],
["authorityKeyIdentifier","keyid:always",false],
]
-
- [
- @rsa1024, @rsa2048, @dsa256, @dsa512,
- ].each{|pk|
- cert = issue_cert(@ca, pk, 1, exts, nil, nil)
- assert_equal(cert.extensions.sort_by(&:to_s)[2].value,
- OpenSSL::TestUtils.get_subject_key_id(cert))
- cert = OpenSSL::X509::Certificate.new(cert.to_der)
- assert_equal(cert.extensions.sort_by(&:to_s)[2].value,
- OpenSSL::TestUtils.get_subject_key_id(cert))
- }
+ cert = issue_cert(@ca, @rsa1, 1, exts, nil, nil)
+ assert_kind_of(OpenSSL::PKey::RSA, cert.public_key)
+ assert_equal(@rsa1.public_to_der, cert.public_key.public_to_der)
+ cert = OpenSSL::X509::Certificate.new(cert.to_der)
+ assert_equal(@rsa1.public_to_der, cert.public_key.public_to_der)
end
def test_validity
now = Time.at(Time.now.to_i + 0.9)
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil,
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
not_before: now, not_after: now+3600)
assert_equal(Time.at(now.to_i), cert.not_before)
assert_equal(Time.at(now.to_i+3600), cert.not_after)
now = Time.at(now.to_i)
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil,
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
not_before: now, not_after: now+3600)
assert_equal(now.getutc, cert.not_before)
assert_equal((now+3600).getutc, cert.not_after)
now = Time.at(0)
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil,
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
not_before: now, not_after: now)
assert_equal(now.getutc, cert.not_before)
assert_equal(now.getutc, cert.not_after)
now = Time.at(0x7fffffff)
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil,
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
not_before: now, not_after: now)
assert_equal(now.getutc, cert.not_before)
assert_equal(now.getutc, cert.not_after)
@@ -75,7 +68,7 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
["subjectKeyIdentifier","hash",false],
["authorityKeyIdentifier","issuer:always,keyid:always",false],
]
- ca_cert = issue_cert(@ca, @rsa2048, 1, ca_exts, nil, nil)
+ ca_cert = issue_cert(@ca, @rsa1, 1, ca_exts, nil, nil)
ca_cert.extensions.each_with_index{|ext, i|
assert_equal(ca_exts[i].first, ext.oid)
assert_equal(ca_exts[i].last, ext.critical?)
@@ -88,7 +81,7 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false],
["subjectAltName","email:ee1@ruby-lang.org",false],
]
- ee1_cert = issue_cert(@ee1, @rsa1024, 2, ee1_exts, ca_cert, @rsa2048)
+ ee1_cert = issue_cert(@ee1, @rsa2, 2, ee1_exts, ca_cert, @rsa1)
assert_equal(ca_cert.subject.to_der, ee1_cert.issuer.to_der)
ee1_cert.extensions.each_with_index{|ext, i|
assert_equal(ee1_exts[i].first, ext.oid)
@@ -97,25 +90,25 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
end
def test_akiski
- ca_cert = generate_cert(@ca, @rsa2048, 4, nil)
+ ca_cert = generate_cert(@ca, @rsa1, 4, nil)
ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert)
ca_cert.add_extension(
ef.create_extension("subjectKeyIdentifier", "hash", false))
ca_cert.add_extension(
ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false))
- ca_cert.sign(@rsa2048, "sha256")
+ ca_cert.sign(@rsa1, "sha256")
ca_keyid = get_subject_key_id(ca_cert.to_der, hex: false)
assert_equal ca_keyid, ca_cert.authority_key_identifier
assert_equal ca_keyid, ca_cert.subject_key_identifier
- ee_cert = generate_cert(@ee1, Fixtures.pkey("p256"), 5, ca_cert)
+ ee_cert = generate_cert(@ee1, @rsa2, 5, ca_cert)
ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ee_cert)
ee_cert.add_extension(
ef.create_extension("subjectKeyIdentifier", "hash", false))
ee_cert.add_extension(
ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false))
- ee_cert.sign(@rsa2048, "sha256")
+ ee_cert.sign(@rsa1, "sha256")
ee_keyid = get_subject_key_id(ee_cert.to_der, hex: false)
assert_equal ca_keyid, ee_cert.authority_key_identifier
@@ -123,13 +116,13 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
end
def test_akiski_missing
- cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil)
+ cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil)
assert_nil(cert.authority_key_identifier)
assert_nil(cert.subject_key_identifier)
end
def test_crl_uris_no_crl_distribution_points
- cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil)
+ cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil)
assert_nil(cert.crl_uris)
end
@@ -141,10 +134,10 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
URI.1 = http://www.example.com/crl
URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary
_cnf_
- cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil)
+ cdp_cert = generate_cert(@ee1, @rsa1, 3, nil)
ef.subject_certificate = cdp_cert
cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "@crlDistPts"))
- cdp_cert.sign(@rsa2048, "sha256")
+ cdp_cert.sign(@rsa1, "sha256")
assert_equal(
["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"],
cdp_cert.crl_uris
@@ -158,10 +151,10 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
[crlDistPts_section]
fullname = URI:http://www.example.com/crl, URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary
_cnf_
- cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil)
+ cdp_cert = generate_cert(@ee1, @rsa1, 3, nil)
ef.subject_certificate = cdp_cert
cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section"))
- cdp_cert.sign(@rsa2048, "sha256")
+ cdp_cert.sign(@rsa1, "sha256")
assert_equal(
["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"],
cdp_cert.crl_uris
@@ -177,22 +170,22 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
[dirname_section]
CN = dirname
_cnf_
- cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil)
+ cdp_cert = generate_cert(@ee1, @rsa1, 3, nil)
ef.subject_certificate = cdp_cert
cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section"))
- cdp_cert.sign(@rsa2048, "sha256")
+ cdp_cert.sign(@rsa1, "sha256")
assert_nil(cdp_cert.crl_uris)
end
def test_aia_missing
- cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil)
+ cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil)
assert_nil(cert.ca_issuer_uris)
assert_nil(cert.ocsp_uris)
end
def test_aia
ef = OpenSSL::X509::ExtensionFactory.new
- aia_cert = generate_cert(@ee1, @rsa2048, 4, nil)
+ aia_cert = generate_cert(@ee1, @rsa1, 4, nil)
ef.subject_certificate = aia_cert
aia_cert.add_extension(
ef.create_extension(
@@ -204,7 +197,7 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
false
)
)
- aia_cert.sign(@rsa2048, "sha256")
+ aia_cert.sign(@rsa1, "sha256")
assert_equal(
["http://www.example.com/caIssuers", "ldap://ldap.example.com/cn=ca?authorityInfoAccessCaIssuers;binary"],
aia_cert.ca_issuer_uris
@@ -217,7 +210,7 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
def test_invalid_extension
integer = OpenSSL::ASN1::Integer.new(0)
- invalid_exts_cert = generate_cert(@ee1, @rsa1024, 1, nil)
+ invalid_exts_cert = generate_cert(@ee1, @rsa1, 1, nil)
["subjectKeyIdentifier", "authorityKeyIdentifier", "crlDistributionPoints", "authorityInfoAccess"].each do |ext|
invalid_exts_cert.add_extension(
OpenSSL::X509::Extension.new(ext, integer.to_der)
@@ -241,83 +234,31 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
}
end
- def test_sign_and_verify_rsa_sha1
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: "SHA1")
- assert_equal(false, cert.verify(@rsa1024))
- assert_equal(true, cert.verify(@rsa2048))
- assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) })
- assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) })
+ def test_sign_and_verify
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, digest: "SHA256")
+ assert_equal("sha256WithRSAEncryption", cert.signature_algorithm) # ln
+ assert_equal(true, cert.verify(@rsa1))
+ assert_equal(false, cert.verify(@rsa2))
+ assert_equal(false, certificate_error_returns_false { cert.verify(@ec1) })
cert.serial = 2
- assert_equal(false, cert.verify(@rsa2048))
- rescue OpenSSL::X509::CertificateError # RHEL 9 disables SHA1
- end
-
- def test_sign_and_verify_rsa_md5
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: "md5")
- assert_equal(false, cert.verify(@rsa1024))
- assert_equal(true, cert.verify(@rsa2048))
-
- assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) })
- assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) })
- cert.subject = @ee1
- assert_equal(false, cert.verify(@rsa2048))
- rescue OpenSSL::X509::CertificateError # RHEL7 disables MD5
- end
-
- def test_sign_and_verify_dsa
- cert = issue_cert(@ca, @dsa512, 1, [], nil, nil)
- assert_equal(false, certificate_error_returns_false { cert.verify(@rsa1024) })
- assert_equal(false, certificate_error_returns_false { cert.verify(@rsa2048) })
- assert_equal(false, cert.verify(@dsa256))
- assert_equal(true, cert.verify(@dsa512))
- cert.not_after = Time.now
- assert_equal(false, cert.verify(@dsa512))
+ assert_equal(false, cert.verify(@rsa1))
end
- def test_sign_and_verify_rsa_dss1
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: OpenSSL::Digest.new('DSS1'))
- assert_equal(false, cert.verify(@rsa1024))
- assert_equal(true, cert.verify(@rsa2048))
- assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) })
- assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) })
- cert.subject = @ee1
- assert_equal(false, cert.verify(@rsa2048))
- rescue OpenSSL::X509::CertificateError
- end if defined?(OpenSSL::Digest::DSS1)
-
- def test_sign_and_verify_dsa_md5
- assert_raise(OpenSSL::X509::CertificateError){
- issue_cert(@ca, @dsa512, 1, [], nil, nil, digest: "md5")
- }
- end
-
- def test_sign_and_verify_ed25519
+ def test_sign_and_verify_nil_digest
# Ed25519 is not FIPS-approved.
omit_on_fips
- omit "Ed25519 not supported" if openssl? && !openssl?(1, 1, 1)
ed25519 = OpenSSL::PKey::generate_key("ED25519")
cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil)
assert_equal(true, cert.verify(ed25519))
end
- def test_dsa_with_sha2
- cert = issue_cert(@ca, @dsa256, 1, [], nil, nil, digest: "sha256")
- assert_equal("dsa_with_SHA256", cert.signature_algorithm)
- # TODO: need more tests for dsa + sha2
-
- # SHA1 is allowed from OpenSSL 1.0.0 (0.9.8 requires DSS1)
- cert = issue_cert(@ca, @dsa256, 1, [], nil, nil, digest: "sha1")
- assert_equal("dsaWithSHA1", cert.signature_algorithm)
- rescue OpenSSL::X509::CertificateError # RHEL 9 disables SHA1
- end
-
def test_check_private_key
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
- assert_equal(true, cert.check_private_key(@rsa2048))
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ assert_equal(true, cert.check_private_key(@rsa1))
end
def test_read_from_file
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
Tempfile.create("cert") { |f|
f << cert.to_pem
f.rewind
@@ -326,12 +267,12 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
end
def test_read_der_then_pem
- cert1 = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+ cert1 = issue_cert(@ca, @rsa1, 1, [], nil, nil)
exts = [
# A new line before PEM block
["nsComment", "Another certificate:\n" + cert1.to_pem],
]
- cert2 = issue_cert(@ca, @rsa2048, 2, exts, nil, nil)
+ cert2 = issue_cert(@ca, @rsa1, 2, exts, nil, nil)
assert_equal cert2, OpenSSL::X509::Certificate.new(cert2.to_der)
assert_equal cert2, OpenSSL::X509::Certificate.new(cert2.to_pem)
@@ -339,15 +280,15 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
def test_eq
now = Time.now
- cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil,
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
not_before: now, not_after: now + 3600)
- cert1 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024,
+ cert1 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1,
not_before: now, not_after: now + 3600)
- cert2 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024,
+ cert2 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1,
not_before: now, not_after: now + 3600)
- cert3 = issue_cert(@ee1, @rsa2048, 3, [], cacert, @rsa1024,
+ cert3 = issue_cert(@ee1, @rsa2, 3, [], cacert, @rsa1,
not_before: now, not_after: now + 3600)
- cert4 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024,
+ cert4 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1,
digest: "sha512", not_before: now, not_after: now + 3600)
assert_equal false, cert1 == 12345
@@ -357,11 +298,19 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
assert_equal false, cert3 == cert4
end
+ def test_inspect
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ assert_include(cacert.inspect, "subject=#{@ca.inspect}")
+
+ # Do not raise an exception for an invalid certificate
+ assert_instance_of(String, OpenSSL::X509::Certificate.new.inspect)
+ end
+
def test_marshal
now = Time.now
- cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil,
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil,
not_before: now, not_after: now + 3600)
- cert = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024,
+ cert = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1,
not_before: now, not_after: now + 3600)
deserialized = Marshal.load(Marshal.dump(cert))
@@ -379,8 +328,8 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
end
def test_load_file_fullchain_pem
- cert1 = issue_cert(@ee1, @rsa2048, 1, [], nil, nil)
- cert2 = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+ cert1 = issue_cert(@ee1, @rsa1, 1, [], nil, nil)
+ cert2 = issue_cert(@ca, @rsa2, 1, [], nil, nil)
Tempfile.create("fullchain.pem") do |f|
f.puts cert1.to_pem
@@ -395,7 +344,7 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
end
def test_load_file_certificate_der
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
Tempfile.create("certificate.der", binmode: true) do |f|
f.write cert.to_der
f.close
@@ -420,7 +369,7 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
end
def test_tbs_precert_bytes
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
seq = OpenSSL::ASN1.decode(cert.tbs_bytes)
assert_equal 7, seq.value.size
diff --git a/test/openssl/test_x509crl.rb b/test/openssl/test_x509crl.rb
index caab795d5b..81c9247df2 100644
--- a/test/openssl/test_x509crl.rb
+++ b/test/openssl/test_x509crl.rb
@@ -6,25 +6,21 @@ if defined?(OpenSSL)
class OpenSSL::TestX509CRL < OpenSSL::TestCase
def setup
super
- @rsa1024 = Fixtures.pkey("rsa1024")
- @rsa2048 = Fixtures.pkey("rsa2048")
- @dsa256 = Fixtures.pkey("dsa256")
- @dsa512 = Fixtures.pkey("dsa512")
+ @rsa1 = Fixtures.pkey("rsa-1")
+ @rsa2 = Fixtures.pkey("rsa-2")
@ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
- @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
- @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
end
def test_basic
now = Time.at(Time.now.to_i)
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
- crl = issue_crl([], 1, now, now+1600, [],
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl = issue_crl([], 1, now, now+1600, [], cert, @rsa1, "SHA256")
assert_equal(1, crl.version)
assert_equal(cert.issuer.to_der, crl.issuer.to_der)
assert_equal(now, crl.last_update)
assert_equal(now+1600, crl.next_update)
+ assert_equal("sha256WithRSAEncryption", crl.signature_algorithm) # ln
crl = OpenSSL::X509::CRL.new(crl.to_der)
assert_equal(1, crl.version)
@@ -55,9 +51,9 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase
[4, now, 4],
[5, now, 5],
]
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
+ cert, @rsa1, "SHA256")
revoked = crl.revoked
assert_equal(5, revoked.size)
assert_equal(1, revoked[0].serial)
@@ -98,7 +94,7 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase
revoke_info = (1..1000).collect{|i| [i, now, 0] }
crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
+ cert, @rsa1, "SHA256")
revoked = crl.revoked
assert_equal(1000, revoked.size)
assert_equal(1, revoked[0].serial)
@@ -122,9 +118,9 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase
["issuerAltName", "issuer:copy", false],
]
- cert = issue_cert(@ca, @rsa2048, 1, cert_exts, nil, nil)
+ cert = issue_cert(@ca, @rsa1, 1, cert_exts, nil, nil)
crl = issue_crl([], 1, Time.now, Time.now+1600, crl_exts,
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
+ cert, @rsa1, "SHA256")
exts = crl.extensions
assert_equal(3, exts.size)
assert_equal("1", exts[0].value)
@@ -160,60 +156,55 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase
assert_equal(false, exts[2].critical?)
no_ext_crl = issue_crl([], 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
+ cert, @rsa1, "SHA256")
assert_equal nil, no_ext_crl.authority_key_identifier
end
def test_crlnumber
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
- crl = issue_crl([], 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256")
assert_match(1.to_s, crl.extensions[0].value)
assert_match(/X509v3 CRL Number:\s+#{1}/m, crl.to_text)
crl = issue_crl([], 2**32, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
+ cert, @rsa1, "SHA256")
assert_match((2**32).to_s, crl.extensions[0].value)
assert_match(/X509v3 CRL Number:\s+#{2**32}/m, crl.to_text)
crl = issue_crl([], 2**100, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
+ cert, @rsa1, "SHA256")
assert_match(/X509v3 CRL Number:\s+#{2**100}/m, crl.to_text)
assert_match((2**100).to_s, crl.extensions[0].value)
end
def test_sign_and_verify
- cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
- crl = issue_crl([], 1, Time.now, Time.now+1600, [],
- cert, @rsa2048, OpenSSL::Digest.new('SHA256'))
- assert_equal(false, crl.verify(@rsa1024))
- assert_equal(true, crl.verify(@rsa2048))
- assert_equal(false, crl_error_returns_false { crl.verify(@dsa256) })
- assert_equal(false, crl_error_returns_false { crl.verify(@dsa512) })
+ p256 = Fixtures.pkey("p256")
+
+ cert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256")
+ assert_equal(true, crl.verify(@rsa1))
+ assert_equal(false, crl.verify(@rsa2))
+ assert_equal(false, crl_error_returns_false { crl.verify(p256) })
crl.version = 0
- assert_equal(false, crl.verify(@rsa2048))
+ assert_equal(false, crl.verify(@rsa1))
- cert = issue_cert(@ca, @dsa512, 1, [], nil, nil)
- crl = issue_crl([], 1, Time.now, Time.now+1600, [],
- cert, @dsa512, OpenSSL::Digest.new('SHA256'))
- assert_equal(false, crl_error_returns_false { crl.verify(@rsa1024) })
- assert_equal(false, crl_error_returns_false { crl.verify(@rsa2048) })
- assert_equal(false, crl.verify(@dsa256))
- assert_equal(true, crl.verify(@dsa512))
+ cert = issue_cert(@ca, p256, 1, [], nil, nil)
+ crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, p256, "SHA256")
+ assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) })
+ assert_equal(false, crl_error_returns_false { crl.verify(@rsa2) })
+ assert_equal(true, crl.verify(p256))
crl.version = 0
- assert_equal(false, crl.verify(@dsa512))
+ assert_equal(false, crl.verify(p256))
end
- def test_sign_and_verify_ed25519
+ def test_sign_and_verify_nil_digest
# Ed25519 is not FIPS-approved.
omit_on_fips
- omit "Ed25519 not supported" if openssl? && !openssl?(1, 1, 1)
ed25519 = OpenSSL::PKey::generate_key("ED25519")
cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil)
crl = issue_crl([], 1, Time.now, Time.now+1600, [],
cert, ed25519, nil)
- assert_equal(false, crl_error_returns_false { crl.verify(@rsa1024) })
- assert_equal(false, crl_error_returns_false { crl.verify(@rsa2048) })
+ assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) })
assert_equal(false, crl.verify(OpenSSL::PKey::generate_key("ED25519")))
assert_equal(true, crl.verify(ed25519))
crl.version = 0
@@ -246,8 +237,8 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase
def test_eq
now = Time.now
- cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil)
- crl1 = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1024, "sha256")
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl1 = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1, "SHA256")
rev1 = OpenSSL::X509::Revoked.new.tap { |rev|
rev.serial = 1
rev.time = now
@@ -275,8 +266,8 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase
def test_marshal
now = Time.now
- cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil)
- crl = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1024, "sha256")
+ cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil)
+ crl = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1, "SHA256")
rev = OpenSSL::X509::Revoked.new.tap { |rev|
rev.serial = 1
rev.time = now
diff --git a/test/openssl/test_x509name.rb b/test/openssl/test_x509name.rb
index c6d15219f5..223c575e4e 100644
--- a/test/openssl/test_x509name.rb
+++ b/test/openssl/test_x509name.rb
@@ -423,24 +423,14 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase
assert_equal(nil, n3 <=> nil)
end
- def name_hash(name)
- # OpenSSL 1.0.0 uses SHA1 for canonical encoding (not just a der) of
- # X509Name for X509_NAME_hash.
- name.respond_to?(:hash_old) ? name.hash_old : name.hash
- end
+ def test_hash_old
+ omit_on_fips # MD5
- def test_hash
dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org"
name = OpenSSL::X509::Name.parse(dn)
d = OpenSSL::Digest.digest('MD5', name.to_der)
expected = (d[0].ord & 0xff) | (d[1].ord & 0xff) << 8 | (d[2].ord & 0xff) << 16 | (d[3].ord & 0xff) << 24
- assert_equal(expected, name_hash(name))
- #
- dn = "/DC=org/DC=ruby-lang/CN=baz.ruby-lang.org"
- name = OpenSSL::X509::Name.parse(dn)
- d = OpenSSL::Digest.digest('MD5', name.to_der)
- expected = (d[0].ord & 0xff) | (d[1].ord & 0xff) << 8 | (d[2].ord & 0xff) << 16 | (d[3].ord & 0xff) << 24
- assert_equal(expected, name_hash(name))
+ assert_equal(expected, name.hash_old)
end
def test_equality
diff --git a/test/openssl/test_x509req.rb b/test/openssl/test_x509req.rb
index 88a7bee93a..b198a1185a 100644
--- a/test/openssl/test_x509req.rb
+++ b/test/openssl/test_x509req.rb
@@ -6,10 +6,8 @@ if defined?(OpenSSL)
class OpenSSL::TestX509Request < OpenSSL::TestCase
def setup
super
- @rsa1024 = Fixtures.pkey("rsa1024")
- @rsa2048 = Fixtures.pkey("rsa2048")
- @dsa256 = Fixtures.pkey("dsa256")
- @dsa512 = Fixtures.pkey("dsa512")
+ @rsa1 = Fixtures.pkey("rsa-1")
+ @rsa2 = Fixtures.pkey("rsa-2")
@dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou")
end
@@ -23,31 +21,32 @@ class OpenSSL::TestX509Request < OpenSSL::TestCase
end
def test_public_key
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256'))
- assert_equal(@rsa1024.public_to_der, req.public_key.public_to_der)
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
+ assert_kind_of(OpenSSL::PKey::RSA, req.public_key)
+ assert_equal(@rsa1.public_to_der, req.public_key.public_to_der)
req = OpenSSL::X509::Request.new(req.to_der)
- assert_equal(@rsa1024.public_to_der, req.public_key.public_to_der)
-
- req = issue_csr(0, @dn, @dsa512, OpenSSL::Digest.new('SHA256'))
- assert_equal(@dsa512.public_to_der, req.public_key.public_to_der)
- req = OpenSSL::X509::Request.new(req.to_der)
- assert_equal(@dsa512.public_to_der, req.public_key.public_to_der)
+ assert_equal(@rsa1.public_to_der, req.public_key.public_to_der)
end
def test_version
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256'))
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
assert_equal(0, req.version)
req = OpenSSL::X509::Request.new(req.to_der)
assert_equal(0, req.version)
end
def test_subject
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256'))
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
assert_equal(@dn.to_der, req.subject.to_der)
req = OpenSSL::X509::Request.new(req.to_der)
assert_equal(@dn.to_der, req.subject.to_der)
end
+ def test_signature_algorithm
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
+ assert_equal("sha256WithRSAEncryption", req.signature_algorithm) # ln
+ end
+
def create_ext_req(exts)
ef = OpenSSL::X509::ExtensionFactory.new
exts = exts.collect{|e| ef.create_extension(*e) }
@@ -73,9 +72,9 @@ class OpenSSL::TestX509Request < OpenSSL::TestCase
OpenSSL::X509::Attribute.new("msExtReq", attrval),
]
- req0 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256'))
+ req0 = issue_csr(0, @dn, @rsa1, "SHA256")
attrs.each{|attr| req0.add_attribute(attr) }
- req1 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256'))
+ req1 = issue_csr(0, @dn, @rsa1, "SHA256")
req1.attributes = attrs
assert_equal(req0.to_der, req1.to_der)
@@ -95,66 +94,44 @@ class OpenSSL::TestX509Request < OpenSSL::TestCase
assert_equal(exts, get_ext_req(attrs[1].value))
end
- def test_sign_and_verify_rsa_sha1
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA1'))
- assert_equal(true, req.verify(@rsa1024))
- assert_equal(false, req.verify(@rsa2048))
- assert_equal(false, request_error_returns_false { req.verify(@dsa256) })
- assert_equal(false, request_error_returns_false { req.verify(@dsa512) })
- req.subject = OpenSSL::X509::Name.parse("/C=JP/CN=FooBarFooBar")
- assert_equal(false, req.verify(@rsa1024))
- rescue OpenSSL::X509::RequestError # RHEL 9 disables SHA1
- end
-
- def test_sign_and_verify_rsa_md5
- req = issue_csr(0, @dn, @rsa2048, OpenSSL::Digest.new('MD5'))
- assert_equal(false, req.verify(@rsa1024))
- assert_equal(true, req.verify(@rsa2048))
- assert_equal(false, request_error_returns_false { req.verify(@dsa256) })
- assert_equal(false, request_error_returns_false { req.verify(@dsa512) })
- req.subject = OpenSSL::X509::Name.parse("/C=JP/CN=FooBar")
- assert_equal(false, req.verify(@rsa2048))
- rescue OpenSSL::X509::RequestError # RHEL7 disables MD5
- end
-
- def test_sign_and_verify_dsa
- req = issue_csr(0, @dn, @dsa512, OpenSSL::Digest.new('SHA256'))
- assert_equal(false, request_error_returns_false { req.verify(@rsa1024) })
- assert_equal(false, request_error_returns_false { req.verify(@rsa2048) })
- assert_equal(false, req.verify(@dsa256))
- assert_equal(true, req.verify(@dsa512))
- req.public_key = @rsa1024.public_key
- assert_equal(false, req.verify(@dsa512))
+ def test_sign_digest_instance
+ req1 = issue_csr(0, @dn, @rsa1, "SHA256")
+ req2 = issue_csr(0, @dn, @rsa1, OpenSSL::Digest.new("SHA256"))
+ assert_equal(req1.to_der, req2.to_der)
end
- def test_sign_and_verify_dsa_md5
- assert_raise(OpenSSL::X509::RequestError){
- issue_csr(0, @dn, @dsa512, OpenSSL::Digest.new('MD5')) }
+ def test_sign_and_verify
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
+ assert_equal(true, req.verify(@rsa1))
+ assert_equal(false, req.verify(@rsa2))
+ ec = OpenSSL::PKey::EC.generate("prime256v1")
+ assert_equal(false, request_error_returns_false { req.verify(ec) })
+ req.subject = OpenSSL::X509::Name.parse_rfc2253("CN=FooBarFooBar,C=JP")
+ assert_equal(false, req.verify(@rsa1))
end
- def test_sign_and_verify_ed25519
+ def test_sign_and_verify_nil_digest
# Ed25519 is not FIPS-approved.
omit_on_fips
- omit "Ed25519 not supported" if openssl? && !openssl?(1, 1, 1)
ed25519 = OpenSSL::PKey::generate_key("ED25519")
req = issue_csr(0, @dn, ed25519, nil)
- assert_equal(false, request_error_returns_false { req.verify(@rsa1024) })
- assert_equal(false, request_error_returns_false { req.verify(@rsa2048) })
+ assert_equal(false, request_error_returns_false { req.verify(@rsa1) })
+ assert_equal(false, request_error_returns_false { req.verify(@rsa2) })
assert_equal(false, req.verify(OpenSSL::PKey::generate_key("ED25519")))
assert_equal(true, req.verify(ed25519))
- req.public_key = @rsa1024.public_key
+ req.public_key = @rsa1
assert_equal(false, req.verify(ed25519))
end
def test_dup
- req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256'))
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
assert_equal(req.to_der, req.dup.to_der)
end
def test_eq
- req1 = issue_csr(0, @dn, @rsa1024, "sha256")
- req2 = issue_csr(0, @dn, @rsa1024, "sha256")
- req3 = issue_csr(0, @dn, @rsa1024, "sha512")
+ req1 = issue_csr(0, @dn, @rsa1, "SHA256")
+ req2 = issue_csr(0, @dn, @rsa1, "SHA256")
+ req3 = issue_csr(0, @dn, @rsa1, "SHA512")
assert_equal false, req1 == 12345
assert_equal true, req1 == req2
@@ -162,7 +139,7 @@ class OpenSSL::TestX509Request < OpenSSL::TestCase
end
def test_marshal
- req = issue_csr(0, @dn, @rsa1024, "sha256")
+ req = issue_csr(0, @dn, @rsa1, "SHA256")
deserialized = Marshal.load(Marshal.dump(req))
assert_equal req.to_der, deserialized.to_der
diff --git a/test/openssl/test_x509store.rb b/test/openssl/test_x509store.rb
index 93e24e02b7..c13beae364 100644
--- a/test/openssl/test_x509store.rb
+++ b/test/openssl/test_x509store.rb
@@ -91,6 +91,18 @@ class OpenSSL::TestX509Store < OpenSSL::TestCase
assert_match(/ok/i, store.error_string)
assert_equal(OpenSSL::X509::V_OK, store.error)
assert_equal([ee1_cert, ca2_cert, ca1_cert], store.chain)
+
+ # Manually instantiated StoreContext
+ # Nothing trusted
+ store = OpenSSL::X509::Store.new
+ ctx = OpenSSL::X509::StoreContext.new(store, ee1_cert)
+ assert_nil(ctx.current_cert)
+ assert_nil(ctx.current_crl)
+ assert_equal(false, ctx.verify)
+ assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ctx.error)
+ assert_equal(0, ctx.error_depth)
+ assert_equal([ee1_cert], ctx.chain)
+ assert_equal(ee1_cert, ctx.current_cert)
end
def test_verify_callback
@@ -329,15 +341,12 @@ class OpenSSL::TestX509Store < OpenSSL::TestCase
end
def test_add_cert_duplicate
- # Up until OpenSSL 1.1.0, X509_STORE_add_{cert,crl}() returned an error
- # if the given certificate is already in the X509_STORE
- return unless openssl? && !openssl?(1, 1, 0)
ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
ca1_key = Fixtures.pkey("rsa-1")
ca1_cert = issue_cert(ca1, ca1_key, 1, [], nil, nil)
store = OpenSSL::X509::Store.new
store.add_cert(ca1_cert)
- assert_raise(OpenSSL::X509::StoreError){
+ assert_nothing_raised {
store.add_cert(ca1_cert) # add same certificate twice
}
@@ -349,7 +358,7 @@ class OpenSSL::TestX509Store < OpenSSL::TestCase
crl2 = issue_crl(revoke_info, 2, now+1800, now+3600, [],
ca1_cert, ca1_key, "sha256")
store.add_crl(crl1)
- assert_raise(OpenSSL::X509::StoreError){
+ assert_nothing_raised {
store.add_crl(crl2) # add CRL issued by same CA twice.
}
end
diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb
index 4110d9b0f2..7e6fe8b163 100644
--- a/test/openssl/utils.rb
+++ b/test/openssl/utils.rb
@@ -103,7 +103,7 @@ module OpenSSL::TestUtils
end
def openssl?(major = nil, minor = nil, fix = nil, patch = 0, status = 0)
- return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL")
+ return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL") || OpenSSL::OPENSSL_VERSION.include?("AWS-LC")
return true unless major
OpenSSL::OPENSSL_VERSION_NUMBER >=
major * 0x10000000 + minor * 0x100000 + fix * 0x1000 + patch * 0x10 +
@@ -115,6 +115,10 @@ module OpenSSL::TestUtils
return false unless version
!major || (version.map(&:to_i) <=> [major, minor, fix]) >= 0
end
+
+ def aws_lc?
+ OpenSSL::OPENSSL_VERSION.include?("AWS-LC")
+ end
end
class OpenSSL::TestCase < Test::Unit::TestCase
@@ -173,43 +177,31 @@ class OpenSSL::SSLTestCase < OpenSSL::TestCase
@ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
@svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
@cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
- ca_exts = [
+ @ca_exts = [
["basicConstraints","CA:TRUE",true],
["keyUsage","cRLSign,keyCertSign",true],
]
- ee_exts = [
+ @ee_exts = [
["keyUsage","keyEncipherment,digitalSignature",true],
]
- @ca_cert = issue_cert(@ca, @ca_key, 1, ca_exts, nil, nil)
- @svr_cert = issue_cert(@svr, @svr_key, 2, ee_exts, @ca_cert, @ca_key)
- @cli_cert = issue_cert(@cli, @cli_key, 3, ee_exts, @ca_cert, @ca_key)
+ @ca_cert = issue_cert(@ca, @ca_key, 1, @ca_exts, nil, nil)
+ @svr_cert = issue_cert(@svr, @svr_key, 2, @ee_exts, @ca_cert, @ca_key)
+ @cli_cert = issue_cert(@cli, @cli_key, 3, @ee_exts, @ca_cert, @ca_key)
@server = nil
end
- def tls13_supported?
- return false unless defined?(OpenSSL::SSL::TLS1_3_VERSION)
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
- true
- rescue
- end
-
def readwrite_loop(ctx, ssl)
while line = ssl.gets
ssl.write(line)
end
end
- def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, start_immediately: true,
+ def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE,
ctx_proc: nil, server_proc: method(:readwrite_loop),
accept_proc: proc{},
ignore_listener_error: false, &block)
IO.pipe {|stop_pipe_r, stop_pipe_w|
- store = OpenSSL::X509::Store.new
- store.add_cert(@ca_cert)
- store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
ctx = OpenSSL::SSL::SSLContext.new
- ctx.cert_store = store
ctx.cert = @svr_cert
ctx.key = @svr_key
ctx.verify_mode = verify_mode
@@ -220,7 +212,6 @@ class OpenSSL::SSLTestCase < OpenSSL::TestCase
port = tcps.connect_address.ip_port
ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
- ssls.start_immediately = start_immediately
threads = []
begin
@@ -295,6 +286,41 @@ class OpenSSL::PKeyTestCase < OpenSSL::TestCase
assert_equal base.send(comp), test.send(comp)
}
end
+
+ def assert_sign_verify_false_or_error
+ ret = yield
+ rescue => e
+ assert_kind_of(OpenSSL::PKey::PKeyError, e)
+ else
+ assert_equal(false, ret)
+ end
+
+ def der_to_pem(der, pem_header)
+ # RFC 7468
+ <<~EOS
+ -----BEGIN #{pem_header}-----
+ #{[der].pack("m0").scan(/.{1,64}/).join("\n")}
+ -----END #{pem_header}-----
+ EOS
+ end
+
+ def der_to_encrypted_pem(der, pem_header, password)
+ # OpenSSL encryption, non-standard
+ iv = 16.times.to_a.pack("C*")
+ encrypted = OpenSSL::Cipher.new("aes-128-cbc").encrypt.then { |cipher|
+ cipher.key = OpenSSL::Digest.digest("MD5", password + iv[0, 8])
+ cipher.iv = iv
+ cipher.update(der) << cipher.final
+ }
+ <<~EOS
+ -----BEGIN #{pem_header}-----
+ Proc-Type: 4,ENCRYPTED
+ DEK-Info: AES-128-CBC,#{iv.unpack1("H*").upcase}
+
+ #{[encrypted].pack("m0").scan(/.{1,64}/).join("\n")}
+ -----END #{pem_header}-----
+ EOS
+ end
end
module OpenSSL::Certs
diff --git a/test/optparse/test_load.rb b/test/optparse/test_load.rb
index 0ebe855682..f664cfbf72 100644
--- a/test/optparse/test_load.rb
+++ b/test/optparse/test_load.rb
@@ -31,7 +31,13 @@ class TestOptionParserLoad < Test::Unit::TestCase
assert_equal({test: result}, into)
end
+ def assert_load_nothing
+ assert !new_parser.load
+ assert_nil @result
+ end
+
def setup_options(env, dir, suffix = nil)
+ env.update({'HOME'=>@tmpdir})
optdir = File.join(@tmpdir, dir)
FileUtils.mkdir_p(optdir)
file = File.join(optdir, [@basename, suffix].join(""))
@@ -41,7 +47,7 @@ class TestOptionParserLoad < Test::Unit::TestCase
begin
yield dir, optdir
ensure
- File.unlink(file)
+ File.unlink(file) rescue nil
Dir.rmdir(optdir) rescue nil
end
else
@@ -50,7 +56,7 @@ class TestOptionParserLoad < Test::Unit::TestCase
end
def setup_options_home(&block)
- setup_options({'HOME'=>@tmpdir}, ".options", &block)
+ setup_options({}, ".options", &block)
end
def setup_options_xdg_config_home(&block)
@@ -58,7 +64,7 @@ class TestOptionParserLoad < Test::Unit::TestCase
end
def setup_options_home_config(&block)
- setup_options({'HOME'=>@tmpdir}, ".config", ".options", &block)
+ setup_options({}, ".config", ".options", &block)
end
def setup_options_xdg_config_dirs(&block)
@@ -66,7 +72,11 @@ class TestOptionParserLoad < Test::Unit::TestCase
end
def setup_options_home_config_settings(&block)
- setup_options({'HOME'=>@tmpdir}, "config/settings", ".options", &block)
+ setup_options({}, "config/settings", ".options", &block)
+ end
+
+ def setup_options_home_options(envname, &block)
+ setup_options({envname => '~/options'}, "options", ".options", &block)
end
def test_load_home_options
@@ -91,7 +101,7 @@ class TestOptionParserLoad < Test::Unit::TestCase
end
def test_load_xdg_config_home
- result, = setup_options_xdg_config_home
+ result, dir = setup_options_xdg_config_home
assert_load(result)
setup_options_home_config do
@@ -105,6 +115,11 @@ class TestOptionParserLoad < Test::Unit::TestCase
setup_options_home_config_settings do
assert_load(result)
end
+
+ File.unlink("#{dir}/#{@basename}.options")
+ setup_options_home_config do
+ assert_load_nothing
+ end
end
def test_load_home_config
@@ -118,6 +133,11 @@ class TestOptionParserLoad < Test::Unit::TestCase
setup_options_home_config_settings do
assert_load(result)
end
+
+ setup_options_xdg_config_home do |_, dir|
+ File.unlink("#{dir}/#{@basename}.options")
+ assert_load_nothing
+ end
end
def test_load_xdg_config_dirs
@@ -135,7 +155,34 @@ class TestOptionParserLoad < Test::Unit::TestCase
end
def test_load_nothing
- assert !new_parser.load
- assert_nil @result
+ setup_options({}, "") do
+ assert_load_nothing
+ end
+ end
+
+ def test_not_expand_path_basename
+ basename = @basename
+ @basename = "~"
+ $test_optparse_basename = "/" + @basename
+ alias $test_optparse_prog $0
+ alias $0 $test_optparse_basename
+ setup_options({'HOME'=>@tmpdir+"/~options"}, "", "options") do
+ assert_load_nothing
+ end
+ ensure
+ alias $0 $test_optparse_prog
+ @basename = basename
+ end
+
+ def test_not_expand_path_xdg_config_home
+ setup_options_home_options('XDG_CONFIG_HOME') do
+ assert_load_nothing
+ end
+ end
+
+ def test_not_expand_path_xdg_config_dirs
+ setup_options_home_options('XDG_CONFIG_DIRS') do
+ assert_load_nothing
+ end
end
end
diff --git a/test/optparse/test_optparse.rb b/test/optparse/test_optparse.rb
index 7f35cb4a8a..ff334009a6 100644
--- a/test/optparse/test_optparse.rb
+++ b/test/optparse/test_optparse.rb
@@ -184,10 +184,9 @@ class TestOptionParser < Test::Unit::TestCase
File.open(File.join(dir, "options.rb"), "w") do |f|
f.puts "#{<<~"begin;"}\n#{<<~'end;'}"
begin;
- stdout = STDOUT.dup
+ stdout = $stdout.dup
def stdout.tty?; true; end
- Object.__send__(:remove_const, :STDOUT)
- STDOUT = stdout
+ $stdout = stdout
ARGV.options do |opt|
end;
100.times {|i| f.puts " opt.on('--opt-#{i}') {}"}
@@ -217,4 +216,16 @@ class TestOptionParser < Test::Unit::TestCase
end
end
end
+
+ def test_program_name
+ program = $0
+ $0 = "rdbg3.5"
+ assert_equal "rdbg3.5", OptionParser.new.program_name
+ RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ") do |ext|
+ $0 = "rdbg3.5" + ext
+ assert_equal "rdbg3.5", OptionParser.new.program_name
+ end
+ ensure
+ $0 = program
+ end
end
diff --git a/test/optparse/test_placearg.rb b/test/optparse/test_placearg.rb
index a8a11e676b..d5be5a66fb 100644
--- a/test/optparse/test_placearg.rb
+++ b/test/optparse/test_placearg.rb
@@ -7,6 +7,10 @@ class TestOptionParserPlaceArg < TestOptionParser
@opt.def_option("-x [VAL]") {|x| @flag = x}
@opt.def_option("--option [VAL]") {|x| @flag = x}
@opt.def_option("-T [level]", /^[0-4]$/, Integer) {|x| @topt = x}
+ @opt.def_option("--enum [VAL]", [:Alpha, :Bravo, :Charlie]) {|x| @enum = x}
+ @opt.def_option("--enumval [VAL]", [[:Alpha, 1], [:Bravo, 2], [:Charlie, 3]]) {|x| @enum = x}
+ @opt.def_option("--integer [VAL]", Integer, [1, 2, 3]) {|x| @integer = x}
+ @opt.def_option("--range [VAL]", Integer, 1..3) {|x| @range = x}
@topt = nil
@opt.def_option("-n") {}
@opt.def_option("--regexp [REGEXP]", Regexp) {|x| @reopt = x}
@@ -93,4 +97,25 @@ class TestOptionParserPlaceArg < TestOptionParser
assert_equal(%w"", no_error {@opt.parse!(%w"--lambda")})
assert_equal(nil, @flag)
end
+
+ def test_enum
+ assert_equal([], no_error {@opt.parse!(%w"--enum=A")})
+ assert_equal(:Alpha, @enum)
+ end
+
+ def test_enum_pair
+ assert_equal([], no_error {@opt.parse!(%w"--enumval=A")})
+ assert_equal(1, @enum)
+ end
+
+ def test_enum_conversion
+ assert_equal([], no_error {@opt.parse!(%w"--integer=1")})
+ assert_equal(1, @integer)
+ end
+
+ def test_enum_range
+ assert_equal([], no_error {@opt.parse!(%w"--range=1")})
+ assert_equal(1, @range)
+ assert_raise(OptionParser::InvalidArgument) {@opt.parse!(%w"--range=4")}
+ end
end
diff --git a/test/optparse/test_switch.rb b/test/optparse/test_switch.rb
new file mode 100644
index 0000000000..b06f4e310b
--- /dev/null
+++ b/test/optparse/test_switch.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: false
+
+require 'test/unit'
+require 'optparse'
+
+
+class TestOptionParserSwitch < Test::Unit::TestCase
+
+ def setup
+ @parser = OptionParser.new
+ end
+
+ def assert_invalidarg_error(msg, &block)
+ exc = assert_raise(OptionParser::InvalidArgument) do
+ yield
+ end
+ assert_equal "invalid argument: #{msg}", exc.message
+ end
+
+ def test_make_switch__enum_array
+ p = @parser
+ p.on("--enum=<val>", ["aa", "bb", "cc"])
+ p.permute(["--enum=bb"], into: (opts={}))
+ assert_equal({:enum=>"bb"}, opts)
+ assert_invalidarg_error("--enum=dd") do
+ p.permute(["--enum=dd"], into: (opts={}))
+ end
+ end
+
+ def test_make_switch__enum_hash
+ p = @parser
+ p.on("--hash=<val>", {"aa"=>"AA", "bb"=>"BB"})
+ p.permute(["--hash=bb"], into: (opts={}))
+ assert_equal({:hash=>"BB"}, opts)
+ assert_invalidarg_error("--hash=dd") do
+ p.permute(["--hash=dd"], into: (opts={}))
+ end
+ end
+
+ def test_make_switch__enum_set
+ p = @parser
+ p.on("--set=<val>", Set.new(["aa", "bb", "cc"]))
+ p.permute(["--set=bb"], into: (opts={}))
+ assert_equal({:set=>"bb"}, opts)
+ assert_invalidarg_error("--set=dd") do
+ p.permute(["--set=dd"], into: (opts={}))
+ end
+ end
+
+end
diff --git a/test/pathname/test_pathname.rb b/test/pathname/test_pathname.rb
index 6a4bb784bd..6354e6a9b5 100644
--- a/test/pathname/test_pathname.rb
+++ b/test/pathname/test_pathname.rb
@@ -175,19 +175,19 @@ class TestPathname < Test::Unit::TestCase
if DOSISH_UNC
defassert(:del_trailing_separator, "//", "//")
- defassert(:del_trailing_separator, "//a", "//a")
- defassert(:del_trailing_separator, "//a", "//a/")
- defassert(:del_trailing_separator, "//a", "//a//")
- defassert(:del_trailing_separator, "//a/b", "//a/b")
- defassert(:del_trailing_separator, "//a/b", "//a/b/")
- defassert(:del_trailing_separator, "//a/b", "//a/b//")
- defassert(:del_trailing_separator, "//a/b/c", "//a/b/c")
- defassert(:del_trailing_separator, "//a/b/c", "//a/b/c/")
- defassert(:del_trailing_separator, "//a/b/c", "//a/b/c//")
else
defassert(:del_trailing_separator, "/", "///")
- defassert(:del_trailing_separator, "///a", "///a/")
end
+ defassert(:del_trailing_separator, "//a", "//a")
+ defassert(:del_trailing_separator, "//a", "//a/")
+ defassert(:del_trailing_separator, "//a", "//a//")
+ defassert(:del_trailing_separator, "//a/b", "//a/b")
+ defassert(:del_trailing_separator, "//a/b", "//a/b/")
+ defassert(:del_trailing_separator, "//a/b", "//a/b//")
+ defassert(:del_trailing_separator, "//a/b/c", "//a/b/c")
+ defassert(:del_trailing_separator, "//a/b/c", "//a/b/c/")
+ defassert(:del_trailing_separator, "//a/b/c", "//a/b/c//")
+ defassert(:del_trailing_separator, "///a", "///a/")
if DOSISH
defassert(:del_trailing_separator, "a", "a\\")
@@ -260,13 +260,12 @@ class TestPathname < Test::Unit::TestCase
assert_equal(Pathname("/foo/var"), r)
end
- def test_absolute
- assert_equal(true, Pathname("/").absolute?)
- assert_equal(false, Pathname("a").absolute?)
- end
-
def relative?(path)
- Pathname.new(path).relative?
+ path = Pathname.new(path)
+ relative = path.relative?
+ absolute = path.absolute?
+ assert_equal(!relative, absolute)
+ relative
end
defassert(:relative?, true, '')
@@ -281,7 +280,7 @@ class TestPathname < Test::Unit::TestCase
defassert(:relative?, !DOSISH_DRIVE_LETTER, 'A:/')
defassert(:relative?, !DOSISH_DRIVE_LETTER, 'A:/a')
- if File.dirname('//') == '//'
+ if DOSISH_UNC
defassert(:relative?, false, '//')
defassert(:relative?, false, '//a')
defassert(:relative?, false, '//a/')
@@ -348,7 +347,7 @@ class TestPathname < Test::Unit::TestCase
rescue NotImplementedError
return false
rescue Errno::ENOENT
- return false
+ return true
rescue Errno::EACCES
return false
end
@@ -370,10 +369,11 @@ class TestPathname < Test::Unit::TestCase
end
def realpath(path, basedir=nil)
- Pathname.new(path).realpath(basedir).to_s
+ Pathname.new(path).realpath(*basedir).to_s
end
def test_realpath
+ omit "not working yet" if RUBY_ENGINE == "jruby"
return if !has_symlink?
with_tmpchdir('rubytest-pathname') {|dir|
assert_raise(Errno::ENOENT) { realpath("#{dir}/not-exist") }
@@ -434,6 +434,7 @@ class TestPathname < Test::Unit::TestCase
end
def test_realdirpath
+ omit "not working yet" if RUBY_ENGINE == "jruby"
return if !has_symlink?
Dir.mktmpdir('rubytest-pathname') {|dir|
rdir = realpath(dir)
@@ -482,12 +483,28 @@ class TestPathname < Test::Unit::TestCase
assert_equal('a', p1.to_s)
p2 = Pathname.new(p1)
assert_equal(p1, p2)
+
+ obj = Object.new
+ assert_raise_with_message(TypeError, /#to_path or #to_str/) { Pathname.new(obj) }
+
+ obj = Object.new
+ def obj.to_path; "a/path"; end
+ assert_equal("a/path", Pathname.new(obj).to_s)
+
+ obj = Object.new
+ def obj.to_str; "a/b"; end
+ assert_equal("a/b", Pathname.new(obj).to_s)
end
def test_initialize_nul
assert_raise(ArgumentError) { Pathname.new("a\0") }
end
+ def test_initialize_encoding
+ omit "https://github.com/jruby/jruby/issues/9120" if RUBY_ENGINE == "jruby"
+ assert_raise(Encoding::CompatibilityError) { Pathname.new("a".encode(Encoding::UTF_32BE)) }
+ end
+
def test_global_constructor
p = Pathname.new('a')
assert_equal(p, Pathname('a'))
@@ -606,6 +623,7 @@ class TestPathname < Test::Unit::TestCase
end
def test_null_character
+ omit "https://github.com/truffleruby/truffleruby/issues/4047" if RUBY_ENGINE == "truffleruby"
assert_raise(ArgumentError) { Pathname.new("\0") }
end
@@ -682,6 +700,7 @@ class TestPathname < Test::Unit::TestCase
end
def test_each_line
+ omit "not working yet" if RUBY_ENGINE == "jruby"
with_tmpchdir('rubytest-pathname') {|dir|
open("a", "w") {|f| f.puts 1, 2 }
a = []
@@ -708,6 +727,7 @@ class TestPathname < Test::Unit::TestCase
end
def test_each_line_opts
+ omit "not working yet" if RUBY_ENGINE == "jruby"
with_tmpchdir('rubytest-pathname') {|dir|
open("a", "w") {|f| f.puts 1, 2 }
a = []
@@ -815,7 +835,7 @@ class TestPathname < Test::Unit::TestCase
end
def test_birthtime
- omit if RUBY_PLATFORM =~ /android/
+ omit "no File.birthtime" if RUBY_PLATFORM =~ /android/ or !File.respond_to?(:birthtime)
# Check under a (probably) local filesystem.
# Remote filesystems often may not support birthtime.
with_tmpchdir('rubytest-pathname') do |dir|
@@ -1052,7 +1072,11 @@ class TestPathname < Test::Unit::TestCase
latime = Time.utc(2000)
lmtime = Time.utc(1999)
File.symlink("a", "l")
- Pathname("l").utime(latime, lmtime)
+ begin
+ Pathname("l").lutime(latime, lmtime)
+ rescue NotImplementedError
+ next
+ end
s = File.lstat("a")
ls = File.lstat("l")
assert_equal(atime, s.atime)
@@ -1322,7 +1346,8 @@ class TestPathname < Test::Unit::TestCase
end
def test_s_glob_3args
- expect = RUBY_VERSION >= "3.1" ? [Pathname("."), Pathname("f")] : [Pathname("."), Pathname(".."), Pathname("f")]
+ # Note: truffleruby should behave like CRuby 3.1+, but it's not the case currently
+ expect = (RUBY_VERSION >= "3.1" && RUBY_ENGINE != "truffleruby") ? [Pathname("."), Pathname("f")] : [Pathname("."), Pathname(".."), Pathname("f")]
with_tmpchdir('rubytest-pathname') {|dir|
open("f", "w") {|f| f.write "abc" }
Dir.chdir("/") {
diff --git a/test/pathname/test_ractor.rb b/test/pathname/test_ractor.rb
index 3d7b63deed..737e4a4111 100644
--- a/test/pathname/test_ractor.rb
+++ b/test/pathname/test_ractor.rb
@@ -9,14 +9,22 @@ class TestPathnameRactor < Test::Unit::TestCase
def test_ractor_shareable
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ class Ractor
+ alias value take
+ end unless Ractor.method_defined? :value # compat with Ruby 3.4 and olders
+
begin;
$VERBOSE = nil
require "pathname"
r = Ractor.new Pathname("a") do |x|
x.join(Pathname("b"), Pathname("c"))
end
- assert_equal(Pathname("a/b/c"), r.take)
+ assert_equal(Pathname("a/b/c"), r.value)
+
+ r = Ractor.new Pathname("a") do |a|
+ Pathname("b").relative_path_from(a)
+ end
+ assert_equal(Pathname("../b"), r.value)
end;
end
end
-
diff --git a/test/prism/api/freeze_test.rb b/test/prism/api/freeze_test.rb
index 5533a00331..bf91792e69 100644
--- a/test/prism/api/freeze_test.rb
+++ b/test/prism/api/freeze_test.rb
@@ -8,6 +8,11 @@ module Prism
assert_frozen(Prism.parse("1 + 2; %i{foo} + %i{bar}", freeze: true))
end
+ def test_offsets_usable
+ node = Prism.parse_statement("1 + 2", freeze: true)
+ assert_equal(1, node.start_line)
+ end
+
def test_lex
assert_frozen(Prism.lex("1 + 2; %i{foo} + %i{bar}", freeze: true))
end
diff --git a/test/prism/api/parse_stream_test.rb b/test/prism/api/parse_stream_test.rb
index 1c068c617c..3bc86fbd61 100644
--- a/test/prism/api/parse_stream_test.rb
+++ b/test/prism/api/parse_stream_test.rb
@@ -30,16 +30,28 @@ module Prism
end
def test___END__
- io = StringIO.new("1 + 2\n3 + 4\n__END__\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ __END__
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
assert_equal 2, result.value.statements.body.length
- assert_equal "5 + 6", io.read
+ assert_equal "5 + 6\n", io.read
end
def test_false___END___in_string
- io = StringIO.new("1 + 2\n3 + 4\n\"\n__END__\n\"\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ "
+ __END__
+ "
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
@@ -47,7 +59,14 @@ module Prism
end
def test_false___END___in_regexp
- io = StringIO.new("1 + 2\n3 + 4\n/\n__END__\n/\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ /
+ __END__
+ /
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
@@ -55,7 +74,14 @@ module Prism
end
def test_false___END___in_list
- io = StringIO.new("1 + 2\n3 + 4\n%w[\n__END__\n]\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ %w[
+ __END__
+ ]
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
@@ -63,7 +89,14 @@ module Prism
end
def test_false___END___in_heredoc
- io = StringIO.new("1 + 2\n3 + 4\n<<-EOF\n__END__\nEOF\n5 + 6")
+ io = StringIO.new(<<~RUBY)
+ 1 + 2
+ 3 + 4
+ <<-EOF
+ __END__
+ EOF
+ 5 + 6
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
@@ -71,7 +104,11 @@ module Prism
end
def test_nul_bytes
- io = StringIO.new("1 # \0\0\0 \n2 # \0\0\0\n3")
+ io = StringIO.new(<<~RUBY)
+ 1 # \0\0\0\t
+ 2 # \0\0\0
+ 3
+ RUBY
result = Prism.parse_stream(io)
assert result.success?
diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb
index bbce8a8fad..c9a47c1a61 100644
--- a/test/prism/api/parse_test.rb
+++ b/test/prism/api/parse_test.rb
@@ -119,6 +119,12 @@ module Prism
assert Prism.parse_success?("1 + 1", version: "3.5")
assert Prism.parse_success?("1 + 1", version: "3.5.0")
+ assert Prism.parse_success?("1 + 1", version: "4.0")
+ assert Prism.parse_success?("1 + 1", version: "4.0.0")
+
+ assert Prism.parse_success?("1 + 1", version: "4.1")
+ assert Prism.parse_success?("1 + 1", version: "4.1.0")
+
assert Prism.parse_success?("1 + 1", version: "latest")
# Test edge case
@@ -140,6 +146,18 @@ module Prism
end
end
+ def test_version_current
+ if RUBY_VERSION >= "3.3"
+ assert Prism.parse_success?("1 + 1", version: "current")
+ else
+ assert_raise(CurrentVersionError) { Prism.parse_success?("1 + 1", version: "current") }
+ end
+ end
+
+ def test_nearest
+ assert Prism.parse_success?("1 + 1", version: "nearest")
+ end
+
def test_scopes
assert_kind_of Prism::CallNode, Prism.parse_statement("foo")
assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]])
diff --git a/test/prism/bom_test.rb b/test/prism/bom_test.rb
index 890bc4b36c..0fa00ae4e8 100644
--- a/test/prism/bom_test.rb
+++ b/test/prism/bom_test.rb
@@ -5,6 +5,7 @@
return if RUBY_ENGINE != "ruby"
require_relative "test_helper"
+require "ripper"
module Prism
class BOMTest < TestCase
@@ -53,7 +54,7 @@ module Prism
def assert_bom(source)
bommed = "\xEF\xBB\xBF#{source}"
- assert_equal Prism.lex_ripper(bommed), Prism.lex_compat(bommed).value
+ assert_equal Ripper.lex(bommed), Prism.lex_compat(bommed).value
end
end
end
diff --git a/test/prism/encoding/encodings_test.rb b/test/prism/encoding/encodings_test.rb
index 4ad2b465cc..b008fc3fa1 100644
--- a/test/prism/encoding/encodings_test.rb
+++ b/test/prism/encoding/encodings_test.rb
@@ -56,21 +56,11 @@ module Prism
# Check that we can properly parse every codepoint in the given encoding.
def assert_encoding(encoding, name, range)
- # I'm not entirely sure, but I believe these codepoints are incorrect in
- # their parsing in CRuby. They all report as matching `[[:lower:]]` but
- # then they are parsed as constants. This is because CRuby determines if
- # an identifier is a constant or not by case folding it down to lowercase
- # and checking if there is a difference. And even though they report
- # themselves as lowercase, their case fold is different. I have reported
- # this bug upstream.
+ unicode = false
+
case encoding
when Encoding::UTF_8, Encoding::UTF_8_MAC, Encoding::UTF8_DoCoMo, Encoding::UTF8_KDDI, Encoding::UTF8_SoftBank, Encoding::CESU_8
- range = range.to_a - [
- 0x01c5, 0x01c8, 0x01cb, 0x01f2, 0x1f88, 0x1f89, 0x1f8a, 0x1f8b,
- 0x1f8c, 0x1f8d, 0x1f8e, 0x1f8f, 0x1f98, 0x1f99, 0x1f9a, 0x1f9b,
- 0x1f9c, 0x1f9d, 0x1f9e, 0x1f9f, 0x1fa8, 0x1fa9, 0x1faa, 0x1fab,
- 0x1fac, 0x1fad, 0x1fae, 0x1faf, 0x1fbc, 0x1fcc, 0x1ffc,
- ]
+ unicode = true
when Encoding::Windows_1253
range = range.to_a - [0xb5]
end
@@ -79,7 +69,7 @@ module Prism
character = codepoint.chr(encoding)
if character.match?(/[[:alpha:]]/)
- if character.match?(/[[:upper:]]/)
+ if character.match?(/[[:upper:]]/) || (unicode && character.match?(Regexp.new("\\p{Lt}".encode(encoding))))
assert_encoding_constant(name, character)
else
assert_encoding_identifier(name, character)
diff --git a/test/prism/encoding/regular_expression_encoding_test.rb b/test/prism/encoding/regular_expression_encoding_test.rb
index e2daae1d7f..fdff1e3281 100644
--- a/test/prism/encoding/regular_expression_encoding_test.rb
+++ b/test/prism/encoding/regular_expression_encoding_test.rb
@@ -2,6 +2,7 @@
return unless defined?(RubyVM::InstructionSequence)
return if RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism
+return if RUBY_VERSION < "3.2"
require_relative "../test_helper"
@@ -21,7 +22,7 @@ module Prism
["n", "u", "e", "s"].each do |modifier|
define_method(:"test_regular_expression_encoding_modifiers_/#{modifier}_#{encoding.name}") do
- regexp_sources = ["abc", "garçon", "\\x80", "gar\\xC3\\xA7on", "gar\\u{E7}on", "abc\\u{FFFFFF}", "\\x80\\u{80}" ]
+ regexp_sources = ["abc", "garçon", "\\x80", "gar\\xC3\\xA7on", "gar\\u{E7}on", "abc\\u{FFFFFF}", "\\x80\\u{80}", "\\p{L}" ]
assert_regular_expression_encoding_flags(
encoding,
@@ -35,17 +36,15 @@ module Prism
def assert_regular_expression_encoding_flags(encoding, regexps)
regexps.each do |regexp|
- regexp_modifier_used = regexp.end_with?("/u") || regexp.end_with?("/e") || regexp.end_with?("/s") || regexp.end_with?("/n")
source = "# encoding: #{encoding.name}\n#{regexp}"
- encoding_errors = ["invalid multibyte char", "escaped non ASCII character in UTF-8 regexp", "differs from source encoding"]
- skipped_errors = ["invalid multibyte escape", "incompatible character encoding", "UTF-8 character in non UTF-8 regexp", "invalid Unicode range", "invalid Unicode list"]
-
- # TODO (nirvdrum 21-Feb-2024): Prism currently does not handle Regexp validation unless modifiers are used. So, skip processing those errors for now: https://github.com/ruby/prism/issues/2104
- unless regexp_modifier_used
- skipped_errors += encoding_errors
- encoding_errors.clear
- end
+ encoding_errors = [
+ "invalid multibyte char", "escaped non ASCII character in UTF-8 regexp",
+ "differs from source encoding", "incompatible character encoding",
+ "invalid multibyte escape", "UTF-8 character in non UTF-8 regexp",
+ "invalid Unicode range", "non escaped non ASCII character",
+ "invalid character property name", "invalid Unicode list",
+ ]
expected =
begin
@@ -53,8 +52,6 @@ module Prism
rescue SyntaxError => error
if encoding_errors.find { |e| error.message.include?(e) }
error.message.split("\n").map { |m| m[/: (.+?)$/, 1] }
- elsif skipped_errors.find { |e| error.message.include?(e) }
- next
else
raise
end
@@ -111,19 +108,6 @@ module Prism
end
end
- # TODO (nirvdrum 22-Feb-2024): Remove this workaround once Prism better maps CRuby's error messages.
- # This class of error message is tricky. The part not being compared is a representation of the regexp.
- # Depending on the source encoding and any encoding modifiers being used, CRuby alters how the regexp is represented.
- # Sometimes it's an MBC string. Other times it uses hexadecimal character escapes. And in other cases it uses
- # the long-form Unicode escape sequences. This short-circuit checks that the error message is mostly correct.
- if expected.is_a?(Array) && actual.is_a?(Array)
- if expected.last.start_with?("/.../n has a non escaped non ASCII character in non ASCII-8BIT script:") &&
- actual.last.start_with?("/.../n has a non escaped non ASCII character in non ASCII-8BIT script:")
- expected.pop
- actual.pop
- end
- end
-
assert_equal expected, actual
end
end
diff --git a/test/prism/errors/3.3-3.3/circular_parameters.txt b/test/prism/errors/3.3-3.3/circular_parameters.txt
new file mode 100644
index 0000000000..ef9642b075
--- /dev/null
+++ b/test/prism/errors/3.3-3.3/circular_parameters.txt
@@ -0,0 +1,12 @@
+def foo(bar = bar) = 42
+ ^~~ circular argument reference - bar
+
+def foo(bar: bar) = 42
+ ^~~ circular argument reference - bar
+
+proc { |foo = foo| }
+ ^~~ circular argument reference - foo
+
+proc { |foo: foo| }
+ ^~~ circular argument reference - foo
+
diff --git a/test/prism/errors/3.3-3.4/leading_logical.txt b/test/prism/errors/3.3-3.4/leading_logical.txt
new file mode 100644
index 0000000000..2a702e281d
--- /dev/null
+++ b/test/prism/errors/3.3-3.4/leading_logical.txt
@@ -0,0 +1,34 @@
+1
+&& 2
+^~ unexpected '&&', ignoring it
+&& 3
+^~ unexpected '&&', ignoring it
+
+1
+|| 2
+^ unexpected '|', ignoring it
+ ^ unexpected '|', ignoring it
+|| 3
+^ unexpected '|', ignoring it
+ ^ unexpected '|', ignoring it
+
+1
+and 2
+^~~ unexpected 'and', ignoring it
+and 3
+^~~ unexpected 'and', ignoring it
+
+1
+or 2
+^~ unexpected 'or', ignoring it
+or 3
+^~ unexpected 'or', ignoring it
+
+1
+and foo
+^~~ unexpected 'and', ignoring it
+
+2
+or foo
+^~ unexpected 'or', ignoring it
+
diff --git a/test/prism/errors/3.3-3.4/private_endless_method.txt b/test/prism/errors/3.3-3.4/private_endless_method.txt
new file mode 100644
index 0000000000..8aae5e0cd3
--- /dev/null
+++ b/test/prism/errors/3.3-3.4/private_endless_method.txt
@@ -0,0 +1,3 @@
+private def foo = puts "Hello"
+ ^ unexpected string literal, expecting end-of-input
+
diff --git a/test/prism/errors/do_not_allow_trailing_commas_in_method_parameters.txt b/test/prism/errors/3.3-4.0/do_not_allow_trailing_commas_in_method_parameters.txt
index c0fec0c704..c0fec0c704 100644
--- a/test/prism/errors/do_not_allow_trailing_commas_in_method_parameters.txt
+++ b/test/prism/errors/3.3-4.0/do_not_allow_trailing_commas_in_method_parameters.txt
diff --git a/test/prism/errors/3.3-4.0/noblock.txt b/test/prism/errors/3.3-4.0/noblock.txt
new file mode 100644
index 0000000000..07939041bb
--- /dev/null
+++ b/test/prism/errors/3.3-4.0/noblock.txt
@@ -0,0 +1,6 @@
+def foo(&nil)
+ ^~~ unexpected 'nil'; expected a `)` to close the parameters
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+end
+
diff --git a/test/prism/errors/3.3-4.0/singleton_method_with_void_value.txt b/test/prism/errors/3.3-4.0/singleton_method_with_void_value.txt
new file mode 100644
index 0000000000..2954f7ea48
--- /dev/null
+++ b/test/prism/errors/3.3-4.0/singleton_method_with_void_value.txt
@@ -0,0 +1,3 @@
+def ((return; 1)).bar; end
+ ^ cannot define singleton method for literals
+
diff --git a/test/prism/errors/3.4-4.0/void_value.txt b/test/prism/errors/3.4-4.0/void_value.txt
new file mode 100644
index 0000000000..c03139bb05
--- /dev/null
+++ b/test/prism/errors/3.4-4.0/void_value.txt
@@ -0,0 +1,18 @@
+x = begin
+ return
+ ^~~~~~ unexpected void value expression
+rescue
+ return
+else
+ return
+end
+
+x = begin
+ return
+rescue
+ "OK"
+else
+ return
+ ^~~~~~ unexpected void value expression
+end
+
diff --git a/test/prism/errors/block_args_in_array_assignment.txt b/test/prism/errors/3.4/block_args_in_array_assignment.txt
index 71dca8452b..71dca8452b 100644
--- a/test/prism/errors/block_args_in_array_assignment.txt
+++ b/test/prism/errors/3.4/block_args_in_array_assignment.txt
diff --git a/test/prism/errors/dont_allow_return_inside_sclass_body.txt b/test/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt
index c29fe01728..c29fe01728 100644
--- a/test/prism/errors/dont_allow_return_inside_sclass_body.txt
+++ b/test/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt
diff --git a/test/prism/errors/it_with_ordinary_parameter.txt b/test/prism/errors/3.4/it_with_ordinary_parameter.txt
index ff9c4276ca..ff9c4276ca 100644
--- a/test/prism/errors/it_with_ordinary_parameter.txt
+++ b/test/prism/errors/3.4/it_with_ordinary_parameter.txt
diff --git a/test/prism/errors/keyword_args_in_array_assignment.txt b/test/prism/errors/3.4/keyword_args_in_array_assignment.txt
index e379ec0ef4..e379ec0ef4 100644
--- a/test/prism/errors/keyword_args_in_array_assignment.txt
+++ b/test/prism/errors/3.4/keyword_args_in_array_assignment.txt
diff --git a/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt b/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt
new file mode 100644
index 0000000000..b3e06f4154
--- /dev/null
+++ b/test/prism/errors/4.1/do_not_allow_trailing_commas_after_terminating_arguments.txt
@@ -0,0 +1,6 @@
+def foo(a,b,...,);end
+ ^ unexpected `,` in parameters
+
+def foo(a,b,&block,);end
+ ^ unexpected `,` in parameters
+
diff --git a/test/prism/errors/4.1/end_block_exit.txt b/test/prism/errors/4.1/end_block_exit.txt
new file mode 100644
index 0000000000..a4a1e9bc2c
--- /dev/null
+++ b/test/prism/errors/4.1/end_block_exit.txt
@@ -0,0 +1,10 @@
+END {
+ break
+ ^~~~~ Invalid break
+}
+
+END {
+ next
+ ^~~~ Invalid next
+}
+
diff --git a/test/prism/errors/4.1/multiple_blocks.txt b/test/prism/errors/4.1/multiple_blocks.txt
new file mode 100644
index 0000000000..7e8433cf82
--- /dev/null
+++ b/test/prism/errors/4.1/multiple_blocks.txt
@@ -0,0 +1,12 @@
+def foo(&nil, &nil); end
+ ^ unexpected parameter order
+ ^~~~ multiple block parameters; only one block is allowed
+
+def foo(&foo, &nil); end
+ ^ unexpected parameter order
+ ^~~~ multiple block parameters; only one block is allowed
+
+def foo(&nil, &foo); end
+ ^ unexpected parameter order
+ ^~~~ multiple block parameters; only one block is allowed
+
diff --git a/test/prism/errors/4.1/singleton_method_with_void_value.txt b/test/prism/errors/4.1/singleton_method_with_void_value.txt
new file mode 100644
index 0000000000..bc6cf9c602
--- /dev/null
+++ b/test/prism/errors/4.1/singleton_method_with_void_value.txt
@@ -0,0 +1,4 @@
+def ((return; 1)).bar; end
+ ^~~~~~ unexpected void value expression
+ ^ cannot define singleton method for literals
+
diff --git a/test/prism/errors/4.1/void_value.txt b/test/prism/errors/4.1/void_value.txt
new file mode 100644
index 0000000000..a27ffd763a
--- /dev/null
+++ b/test/prism/errors/4.1/void_value.txt
@@ -0,0 +1,44 @@
+x = begin
+ return
+rescue
+ return
+else
+ return
+ ^~~~~~ unexpected void value expression
+end
+
+x = begin
+ ignored_because_else_branch
+rescue
+ return
+else
+ return
+ ^~~~~~ unexpected void value expression
+end
+
+x = case
+ when 1 then return
+ ^~~~~~ unexpected void value expression
+ else return
+end
+
+x = case 1
+ in 2 then return
+ ^~~~~~ unexpected void value expression
+ else return
+end
+
+x = begin
+ return
+ ^~~~~~ unexpected void value expression
+ "NG"
+end
+
+x = if rand < 0.5
+ return
+ ^~~~~~ unexpected void value expression
+ "NG"
+else
+ return
+end
+
diff --git a/test/prism/errors/block_args_with_endless_def.txt b/test/prism/errors/block_args_with_endless_def.txt
new file mode 100644
index 0000000000..a7242160d2
--- /dev/null
+++ b/test/prism/errors/block_args_with_endless_def.txt
@@ -0,0 +1,5 @@
+p do |a = def f = 1; b| end
+ ^~~~~~~ unexpected endless method definition; expected a default value for a parameter
+p do |a = def f = 1| 2; b|c end
+ ^~~~~~~ unexpected endless method definition; expected a default value for a parameter
+
diff --git a/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt b/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt
index f0fa964c8a..1184b38ce8 100644
--- a/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt
+++ b/test/prism/errors/block_beginning_with_brace_and_ending_with_end.txt
@@ -1,6 +1,5 @@
x.each { x end
^~~ unexpected 'end', expecting end-of-input
^~~ unexpected 'end', ignoring it
- ^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a block beginning with `{` to end with `}`
+ ^ expected a block beginning with `{` to end with `}`
diff --git a/test/prism/errors/block_pass_return_value.txt b/test/prism/errors/block_pass_return_value.txt
new file mode 100644
index 0000000000..c9d12281d9
--- /dev/null
+++ b/test/prism/errors/block_pass_return_value.txt
@@ -0,0 +1,33 @@
+return &b
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+
+return(&b)
+ ^ unexpected '&', ignoring it
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+ ^ expected a matching `)`
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+return a, &b
+ ^~ block argument should not be given
+
+return(a, &b)
+ ^~ unexpected write target
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+ ^ expected a matching `)`
+ ^ unexpected '&', expecting end-of-input
+ ^ unexpected '&', ignoring it
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+tap { break a, &b }
+ ^~ block argument should not be given
+
+tap { next a, &b }
+ ^~ block argument should not be given
+
diff --git a/test/prism/errors/command_call_in.txt b/test/prism/errors/command_call_in.txt
index 2fdcf09738..2b7286abc3 100644
--- a/test/prism/errors/command_call_in.txt
+++ b/test/prism/errors/command_call_in.txt
@@ -2,4 +2,5 @@ foo 1 in a
^~ unexpected 'in', expecting end-of-input
^~ unexpected 'in', ignoring it
a = foo 2 in b
+ ^~ unexpected 'in', expecting end-of-input
diff --git a/test/prism/errors/command_call_in_2.txt b/test/prism/errors/command_call_in_2.txt
new file mode 100644
index 0000000000..6676b1acba
--- /dev/null
+++ b/test/prism/errors/command_call_in_2.txt
@@ -0,0 +1,4 @@
+a.b x in pattern
+ ^~ unexpected 'in', expecting end-of-input
+ ^~ unexpected 'in', ignoring it
+
diff --git a/test/prism/errors/command_call_in_3.txt b/test/prism/errors/command_call_in_3.txt
new file mode 100644
index 0000000000..6fe026d7d3
--- /dev/null
+++ b/test/prism/errors/command_call_in_3.txt
@@ -0,0 +1,4 @@
+a.b x: in pattern
+ ^~ unexpected 'in', expecting end-of-input
+ ^~ unexpected 'in', ignoring it
+
diff --git a/test/prism/errors/command_call_in_4.txt b/test/prism/errors/command_call_in_4.txt
new file mode 100644
index 0000000000..045afe6498
--- /dev/null
+++ b/test/prism/errors/command_call_in_4.txt
@@ -0,0 +1,4 @@
+a.b &x in pattern
+ ^~ unexpected 'in', expecting end-of-input
+ ^~ unexpected 'in', ignoring it
+
diff --git a/test/prism/errors/command_call_in_5.txt b/test/prism/errors/command_call_in_5.txt
new file mode 100644
index 0000000000..be07287f81
--- /dev/null
+++ b/test/prism/errors/command_call_in_5.txt
@@ -0,0 +1,4 @@
+a.b *x => pattern
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+
diff --git a/test/prism/errors/command_call_in_6.txt b/test/prism/errors/command_call_in_6.txt
new file mode 100644
index 0000000000..470f323872
--- /dev/null
+++ b/test/prism/errors/command_call_in_6.txt
@@ -0,0 +1,4 @@
+a.b x: => pattern
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+
diff --git a/test/prism/errors/command_call_in_7.txt b/test/prism/errors/command_call_in_7.txt
new file mode 100644
index 0000000000..a8bea912b5
--- /dev/null
+++ b/test/prism/errors/command_call_in_7.txt
@@ -0,0 +1,4 @@
+a.b &x => pattern
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+
diff --git a/test/prism/errors/command_call_value_and.txt b/test/prism/errors/command_call_value_and.txt
new file mode 100644
index 0000000000..a131aa5530
--- /dev/null
+++ b/test/prism/errors/command_call_value_and.txt
@@ -0,0 +1,3 @@
+a = b c and 1
+ ^~~ unexpected 'and', expecting end-of-input
+
diff --git a/test/prism/errors/command_call_value_or.txt b/test/prism/errors/command_call_value_or.txt
new file mode 100644
index 0000000000..cc75714166
--- /dev/null
+++ b/test/prism/errors/command_call_value_or.txt
@@ -0,0 +1,3 @@
+a = b c or 1
+ ^~ unexpected 'or', expecting end-of-input
+
diff --git a/test/prism/errors/command_calls.txt b/test/prism/errors/command_calls.txt
index 19812a1d0a..6601e5fbbc 100644
--- a/test/prism/errors/command_calls.txt
+++ b/test/prism/errors/command_calls.txt
@@ -1,3 +1,10 @@
[a b]
^ unexpected local variable or method; expected a `,` separator for the array elements
+
+[
+ a b do
+ ^ unexpected local variable or method; expected a `,` separator for the array elements
+ end,
+]
+
diff --git a/test/prism/errors/command_calls_2.txt b/test/prism/errors/command_calls_2.txt
index b0983c015b..13e10f7ebf 100644
--- a/test/prism/errors/command_calls_2.txt
+++ b/test/prism/errors/command_calls_2.txt
@@ -1,5 +1,5 @@
{a: b c}
- ^ expected a `}` to close the hash literal
+^ expected a `}` to close the hash literal
^ unexpected local variable or method, expecting end-of-input
^ unexpected '}', expecting end-of-input
^ unexpected '}', ignoring it
diff --git a/test/prism/errors/command_calls_24.txt b/test/prism/errors/command_calls_24.txt
index 3046b36dc1..27a32ea3bf 100644
--- a/test/prism/errors/command_calls_24.txt
+++ b/test/prism/errors/command_calls_24.txt
@@ -1,5 +1,5 @@
->a=b c{}
^ expected a `do` keyword or a `{` to open the lambda block
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a lambda block beginning with `do` to end with `end`
+^~ expected a lambda block beginning with `do` to end with `end`
diff --git a/test/prism/errors/command_calls_25.txt b/test/prism/errors/command_calls_25.txt
index 5fddd90fdd..cf04508f87 100644
--- a/test/prism/errors/command_calls_25.txt
+++ b/test/prism/errors/command_calls_25.txt
@@ -4,5 +4,5 @@
^ unexpected ')', expecting end-of-input
^ unexpected ')', ignoring it
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a lambda block beginning with `do` to end with `end`
+^~ expected a lambda block beginning with `do` to end with `end`
diff --git a/test/prism/errors/command_calls_31.txt b/test/prism/errors/command_calls_31.txt
new file mode 100644
index 0000000000..e662b25444
--- /dev/null
+++ b/test/prism/errors/command_calls_31.txt
@@ -0,0 +1,17 @@
+true && not true
+ ^~~~ expected a `(` after `not`
+ ^~~~ unexpected 'true', expecting end-of-input
+
+true || not true
+ ^~~~ expected a `(` after `not`
+ ^~~~ unexpected 'true', expecting end-of-input
+
+true && not (true)
+ ^ expected a `(` immediately after `not`
+ ^ unexpected '(', expecting end-of-input
+
+true && not
+true
+^~~~ expected a `(` after `not`
+^~~~ unexpected 'true', expecting end-of-input
+
diff --git a/test/prism/errors/command_calls_32.txt b/test/prism/errors/command_calls_32.txt
new file mode 100644
index 0000000000..14488ca335
--- /dev/null
+++ b/test/prism/errors/command_calls_32.txt
@@ -0,0 +1,19 @@
+foo && return bar
+ ^~~ unexpected local variable or method, expecting end-of-input
+
+tap { foo && break bar }
+ ^~~ unexpected local variable or method, expecting end-of-input
+
+tap { foo && next bar }
+ ^~~ unexpected local variable or method, expecting end-of-input
+
+foo && return()
+ ^ unexpected '(', expecting end-of-input
+
+foo && return(bar)
+ ^ unexpected '(', expecting end-of-input
+
+foo && return(bar, baz)
+ ^~~~~~~~~~ unexpected write target
+ ^ unexpected '(', expecting end-of-input
+
diff --git a/test/prism/errors/command_calls_33.txt b/test/prism/errors/command_calls_33.txt
new file mode 100644
index 0000000000..13e3b35c9e
--- /dev/null
+++ b/test/prism/errors/command_calls_33.txt
@@ -0,0 +1,6 @@
+1 if foo = bar baz
+ ^~~ unexpected local variable or method, expecting end-of-input
+
+1 and foo = bar baz
+ ^~~ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/command_calls_34.txt b/test/prism/errors/command_calls_34.txt
new file mode 100644
index 0000000000..bc0ea5e81c
--- /dev/null
+++ b/test/prism/errors/command_calls_34.txt
@@ -0,0 +1,31 @@
+foo(bar 1 do end, 2)
+ ^~ unexpected 'do'; expected a `)` to close the arguments
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', ignoring it
+ ^ unexpected ',', ignoring it
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+foo(bar 1 do end,)
+ ^~ unexpected 'do'; expected a `)` to close the arguments
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', ignoring it
+ ^ unexpected ',', ignoring it
+ ^ unexpected ')', ignoring it
+
+foo(1, bar 2 do end)
+ ^ unexpected integer; expected a `)` to close the arguments
+ ^ unexpected integer, expecting end-of-input
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', ignoring it
+ ^ unexpected ')', ignoring it
+
+foo(1, bar 2)
+ ^ unexpected integer; expected a `)` to close the arguments
+ ^ unexpected integer, expecting end-of-input
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
diff --git a/test/prism/errors/command_calls_35.txt b/test/prism/errors/command_calls_35.txt
new file mode 100644
index 0000000000..bd72d1be56
--- /dev/null
+++ b/test/prism/errors/command_calls_35.txt
@@ -0,0 +1,50 @@
+p(p a, x: b => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a, x: => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a, &block => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a do end => value)
+ ^~ unexpected 'do'; expected a `)` to close the arguments
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', ignoring it
+ ^~ unexpected '=>', ignoring it
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a, *args => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p(p a, **kwargs => value)
+ ^~ unexpected '=>'; expected a `)` to close the arguments
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
+p p 1, &block => 2, &block
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+ ^ unexpected ',', expecting end-of-input
+ ^ unexpected ',', ignoring it
+ ^ unexpected '&', ignoring it
+
+p p p 1 => 2 => 3 => 4
+ ^~ unexpected '=>', expecting end-of-input
+ ^~ unexpected '=>', ignoring it
+
+p[p a, x: b => value]
+ ^ expected a matching `]`
+ ^ unexpected ']', expecting end-of-input
+ ^ unexpected ']', ignoring it
+
diff --git a/test/prism/errors/def_endless_do.txt b/test/prism/errors/def_endless_do.txt
new file mode 100644
index 0000000000..d66b7086da
--- /dev/null
+++ b/test/prism/errors/def_endless_do.txt
@@ -0,0 +1,6 @@
+def a = a b do 1 end
+ ^~ unexpected 'do', expecting end-of-input
+ ^~ unexpected 'do', ignoring it
+ ^~~ unexpected 'end', expecting end-of-input
+ ^~~ unexpected 'end', ignoring it
+
diff --git a/test/prism/errors/def_with_optional_splat.txt b/test/prism/errors/def_with_optional_splat.txt
new file mode 100644
index 0000000000..74a833ceec
--- /dev/null
+++ b/test/prism/errors/def_with_optional_splat.txt
@@ -0,0 +1,6 @@
+def foo(*bar = nil); end
+ ^ unexpected '='; expected a `)` to close the parameters
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+ ^~~ unexpected 'end', ignoring it
+
diff --git a/test/prism/errors/defined_empty.txt b/test/prism/errors/defined_empty.txt
new file mode 100644
index 0000000000..4d7ea76413
--- /dev/null
+++ b/test/prism/errors/defined_empty.txt
@@ -0,0 +1,3 @@
+defined?()
+ ^ expected an expression after `defined?`
+
diff --git a/test/prism/errors/destroy_call_operator_write_arguments.txt b/test/prism/errors/destroy_call_operator_write_arguments.txt
new file mode 100644
index 0000000000..b6933d61d1
--- /dev/null
+++ b/test/prism/errors/destroy_call_operator_write_arguments.txt
@@ -0,0 +1,11 @@
+t next&&do end&=
+ ^~ unexpected 'do'; expected an expression after the operator
+ ^~~~ unexpected void value expression
+ ^~~~ unexpected void value expression
+ ^~ unexpected '&=', expecting end-of-input
+ ^~ unexpected '&=', ignoring it
+ ^~~~ Invalid next
+''while=
+ ^~~~~ expected a predicate expression for the `while` statement
+ ^ unexpected '='; target cannot be written
+
diff --git a/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt b/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt
index df49557617..639dec3af2 100644
--- a/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt
+++ b/test/prism/errors/do_not_allow_forward_arguments_in_blocks.txt
@@ -1,3 +1,13 @@
a {|...|}
- ^~~ unexpected ... when the parent method is not forwarding
+ ^~~ unexpected ... in block argument
+
+def foo(...)
+ a {|...|}
+ ^~~ unexpected ... in block argument
+end
+
+def foo
+ a {|...|}
+ ^~~ unexpected ... in block argument
+end
diff --git a/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt b/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt
index c2405a5c66..03e17683e4 100644
--- a/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt
+++ b/test/prism/errors/do_not_allow_forward_arguments_in_lambda_literals.txt
@@ -1,3 +1,13 @@
->(...) {}
- ^~~ unexpected ... when the parent method is not forwarding
+ ^~~ unexpected ... in lambda argument
+
+def foo(...)
+ ->(...) {}
+ ^~~ unexpected ... in lambda argument
+end
+
+def foo
+ ->(...) {}
+ ^~~ unexpected ... in lambda argument
+end
diff --git a/test/prism/errors/endless_method_command_call.txt b/test/prism/errors/endless_method_command_call.txt
new file mode 100644
index 0000000000..e6a328c294
--- /dev/null
+++ b/test/prism/errors/endless_method_command_call.txt
@@ -0,0 +1,3 @@
+private :m, def hello = puts "Hello"
+ ^ unexpected string literal, expecting end-of-input
+
diff --git a/test/prism/errors/endless_method_command_call_parameters.txt b/test/prism/errors/endless_method_command_call_parameters.txt
new file mode 100644
index 0000000000..5dc92ce7f9
--- /dev/null
+++ b/test/prism/errors/endless_method_command_call_parameters.txt
@@ -0,0 +1,27 @@
+def f x: = 1
+ ^ could not parse the endless method parameters
+
+def f ... = 1
+ ^ could not parse the endless method parameters
+
+def f * = 1
+ ^ could not parse the endless method parameters
+
+def f ** = 1
+ ^ could not parse the endless method parameters
+
+def f & = 1
+ ^ could not parse the endless method parameters
+
+def f *a = 1
+ ^ could not parse the endless method parameters
+
+def f **a = 1
+ ^ could not parse the endless method parameters
+
+def f &a = 1
+ ^ could not parse the endless method parameters
+
+def f a, (b) = 1
+ ^ could not parse the endless method parameters
+
diff --git a/test/prism/errors/escape_unicode_curly_whitespace.txt b/test/prism/errors/escape_unicode_curly_whitespace.txt
new file mode 100644
index 0000000000..324d8a2ae5
--- /dev/null
+++ b/test/prism/errors/escape_unicode_curly_whitespace.txt
@@ -0,0 +1,5 @@
+"\u{
+ ^ invalid Unicode escape sequence
+ ^ unterminated Unicode escape
+61}"
+
diff --git a/test/prism/errors/heredoc_percent_q_newline_delimiter.txt b/test/prism/errors/heredoc_percent_q_newline_delimiter.txt
new file mode 100644
index 0000000000..73664c071f
--- /dev/null
+++ b/test/prism/errors/heredoc_percent_q_newline_delimiter.txt
@@ -0,0 +1,11 @@
+%q
+#{<<B}
+B
+^ unexpected constant, expecting end-of-input
+
+<<A; %q
+A
+#{<<B}
+B
+^ unexpected constant, expecting end-of-input
+
diff --git a/test/prism/errors/heredoc_unterminated.txt b/test/prism/errors/heredoc_unterminated.txt
index 3c6aeaeb81..56bd162998 100644
--- a/test/prism/errors/heredoc_unterminated.txt
+++ b/test/prism/errors/heredoc_unterminated.txt
@@ -3,7 +3,7 @@ a=>{<<b
^~~ unexpected heredoc beginning; expected a key in the hash pattern
^ unterminated heredoc; can't find string "b" anywhere before EOF
^~~ expected a label as the key in the hash pattern
- ^ expected a `}` to close the pattern expression
+ ^ expected a `}` to close the pattern expression
^ unexpected heredoc ending, expecting end-of-input
^ unexpected heredoc ending, ignoring it
diff --git a/test/prism/errors/infix_after_label.txt b/test/prism/errors/infix_after_label.txt
index c3bcfaeceb..f02a29470f 100644
--- a/test/prism/errors/infix_after_label.txt
+++ b/test/prism/errors/infix_after_label.txt
@@ -1,6 +1,6 @@
{ 'a':.upcase => 1 }
^ unexpected '.'; expected a value in the hash literal
- ^ expected a `}` to close the hash literal
+^ expected a `}` to close the hash literal
^ unexpected '}', expecting end-of-input
^ unexpected '}', ignoring it
diff --git a/test/prism/errors/interpolated_symbol_pattern_hash_key.txt b/test/prism/errors/interpolated_symbol_pattern_hash_key.txt
new file mode 100644
index 0000000000..b4532439ff
--- /dev/null
+++ b/test/prism/errors/interpolated_symbol_pattern_hash_key.txt
@@ -0,0 +1,3 @@
+case foo; in { "bar#{1}": 1 }; end
+ ^~~~~~~~~~ symbol literal with interpolation is not allowed
+
diff --git a/test/prism/errors/label_in_interpolated_string.txt b/test/prism/errors/label_in_interpolated_string.txt
new file mode 100644
index 0000000000..29af5310a1
--- /dev/null
+++ b/test/prism/errors/label_in_interpolated_string.txt
@@ -0,0 +1,14 @@
+case in el""Q
+^~~~ expected a predicate for a case matching statement
+ ^ expected a delimiter after the patterns of an `in` clause
+ ^ unexpected constant, expecting end-of-input
+^~~~ expected an `end` to close the `case` statement
+ !"""#{in el"":Q
+ ^~ unexpected 'in', assuming it is closing the parent 'in' clause
+ ^ expected a `}` to close the embedded expression
+ ^~ cannot parse the string part
+ ^~ cannot parse the string part
+ ^ cannot parse the string part
+ ^~~~~~~~~~~ unexpected label
+ ^~~~~~~~~~~ expected a string for concatenation
+
diff --git a/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt b/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt
index fead8aaf23..f599dc476b 100644
--- a/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt
+++ b/test/prism/errors/match_predicate_after_rescue_with_dot_method_call.txt
@@ -1,3 +1,4 @@
'a' rescue 2 in 3.upcase
^ unexpected '.', expecting end-of-input
+ ^ unexpected '.', ignoring it
diff --git a/test/prism/errors/match_predicate_after_rescue_with_opreator.txt b/test/prism/errors/match_predicate_after_rescue_with_opreator.txt
index b2363a544d..44a4ba8488 100644
--- a/test/prism/errors/match_predicate_after_rescue_with_opreator.txt
+++ b/test/prism/errors/match_predicate_after_rescue_with_opreator.txt
@@ -1,3 +1,4 @@
1 rescue 2 in 3 << 4
^~ unexpected <<, expecting end-of-input
+ ^~ unexpected <<, ignoring it
diff --git a/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt b/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt
index d72d72ce60..abcfaf094d 100644
--- a/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt
+++ b/test/prism/errors/match_required_after_rescue_with_dot_method_call.txt
@@ -1,3 +1,4 @@
1 rescue 2 => 3.inspect
^ unexpected '.', expecting end-of-input
+ ^ unexpected '.', ignoring it
diff --git a/test/prism/errors/match_required_after_rescue_with_opreator.txt b/test/prism/errors/match_required_after_rescue_with_opreator.txt
index 903e2ccc8e..5e6387ca4d 100644
--- a/test/prism/errors/match_required_after_rescue_with_opreator.txt
+++ b/test/prism/errors/match_required_after_rescue_with_opreator.txt
@@ -1,3 +1,4 @@
1 rescue 2 => 3 ** 4
^~ unexpected '**', expecting end-of-input
+ ^~ unexpected '**', ignoring it
diff --git a/test/prism/errors/modifier_conditional_in_predicate.txt b/test/prism/errors/modifier_conditional_in_predicate.txt
new file mode 100644
index 0000000000..5b89ee4a26
--- /dev/null
+++ b/test/prism/errors/modifier_conditional_in_predicate.txt
@@ -0,0 +1,12 @@
+if a if b then end
+ ^~ expected `then` or `;` or '\n'
+ ^~ unexpected 'if', ignoring it
+ ^~~~ unexpected 'then', expecting end-of-input
+ ^~~~ unexpected 'then', ignoring it
+
+unless a unless b then end
+ ^~~~~~ expected `then` or `;` or '\n'
+ ^~~~~~ unexpected 'unless', ignoring it
+ ^~~~ unexpected 'then', expecting end-of-input
+ ^~~~ unexpected 'then', ignoring it
+
diff --git a/test/prism/errors/not_without_parens_assignment.txt b/test/prism/errors/not_without_parens_assignment.txt
new file mode 100644
index 0000000000..32d58efedf
--- /dev/null
+++ b/test/prism/errors/not_without_parens_assignment.txt
@@ -0,0 +1,4 @@
+x = not y
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/not_without_parens_call.txt b/test/prism/errors/not_without_parens_call.txt
new file mode 100644
index 0000000000..a778193400
--- /dev/null
+++ b/test/prism/errors/not_without_parens_call.txt
@@ -0,0 +1,7 @@
+foo(not y)
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method; expected a `)` to close the arguments
+ ^ unexpected local variable or method, expecting end-of-input
+ ^ unexpected ')', expecting end-of-input
+ ^ unexpected ')', ignoring it
+
diff --git a/test/prism/errors/not_without_parens_command.txt b/test/prism/errors/not_without_parens_command.txt
new file mode 100644
index 0000000000..957a06f8f1
--- /dev/null
+++ b/test/prism/errors/not_without_parens_command.txt
@@ -0,0 +1,4 @@
+foo not y
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/not_without_parens_command_call.txt b/test/prism/errors/not_without_parens_command_call.txt
new file mode 100644
index 0000000000..564833c7de
--- /dev/null
+++ b/test/prism/errors/not_without_parens_command_call.txt
@@ -0,0 +1,4 @@
+a.b not y
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/not_without_parens_return.txt b/test/prism/errors/not_without_parens_return.txt
new file mode 100644
index 0000000000..1c7edb6ff1
--- /dev/null
+++ b/test/prism/errors/not_without_parens_return.txt
@@ -0,0 +1,4 @@
+return not y
+ ^ expected a `(` after `not`
+ ^ unexpected local variable or method, expecting end-of-input
+
diff --git a/test/prism/errors/numbered_and_write.txt b/test/prism/errors/numbered_and_write.txt
new file mode 100644
index 0000000000..f80b97b2d5
--- /dev/null
+++ b/test/prism/errors/numbered_and_write.txt
@@ -0,0 +1,3 @@
+tap { _1 &&= 1 }
+ ^~ _1 is reserved for numbered parameters
+
diff --git a/test/prism/errors/numbered_operator_write.txt b/test/prism/errors/numbered_operator_write.txt
new file mode 100644
index 0000000000..70cd58c811
--- /dev/null
+++ b/test/prism/errors/numbered_operator_write.txt
@@ -0,0 +1,3 @@
+tap { _1 += 1 }
+ ^~ _1 is reserved for numbered parameters
+
diff --git a/test/prism/errors/numbered_or_write.txt b/test/prism/errors/numbered_or_write.txt
new file mode 100644
index 0000000000..b27495498d
--- /dev/null
+++ b/test/prism/errors/numbered_or_write.txt
@@ -0,0 +1,3 @@
+tap { _1 ||= 1 }
+ ^~ _1 is reserved for numbered parameters
+
diff --git a/test/prism/errors/pattern-capture-in-alt-array.txt b/test/prism/errors/pattern-capture-in-alt-array.txt
new file mode 100644
index 0000000000..5cb59fa328
--- /dev/null
+++ b/test/prism/errors/pattern-capture-in-alt-array.txt
@@ -0,0 +1,4 @@
+1 => [a, b] | 2
+ ^ variable capture in alternative pattern
+ ^ variable capture in alternative pattern
+
diff --git a/test/prism/errors/pattern-capture-in-alt-hash.txt b/test/prism/errors/pattern-capture-in-alt-hash.txt
new file mode 100644
index 0000000000..150b3baecc
--- /dev/null
+++ b/test/prism/errors/pattern-capture-in-alt-hash.txt
@@ -0,0 +1,3 @@
+1 => { a: b } | 2
+ ^ variable capture in alternative pattern
+
diff --git a/test/prism/errors/pattern-capture-in-alt-name.txt b/test/prism/errors/pattern-capture-in-alt-name.txt
new file mode 100644
index 0000000000..cbf2bae85f
--- /dev/null
+++ b/test/prism/errors/pattern-capture-in-alt-name.txt
@@ -0,0 +1,3 @@
+1 => (2 => b) | 2
+ ^ variable capture in alternative pattern
+
diff --git a/test/prism/errors/pattern-capture-in-alt-top.txt b/test/prism/errors/pattern-capture-in-alt-top.txt
new file mode 100644
index 0000000000..bdf3a7f637
--- /dev/null
+++ b/test/prism/errors/pattern-capture-in-alt-top.txt
@@ -0,0 +1,4 @@
+1 => a | b
+ ^ variable capture in alternative pattern
+ ^ variable capture in alternative pattern
+
diff --git a/test/prism/errors/pattern_arithmetic_expressions.txt b/test/prism/errors/pattern_arithmetic_expressions.txt
new file mode 100644
index 0000000000..cfb3650531
--- /dev/null
+++ b/test/prism/errors/pattern_arithmetic_expressions.txt
@@ -0,0 +1,3 @@
+case 1; in -1**2; end
+ ^~~~~ expected a pattern expression after the `in` keyword
+
diff --git a/test/prism/errors/pattern_match_implicit_rest.txt b/test/prism/errors/pattern_match_implicit_rest.txt
new file mode 100644
index 0000000000..8602c0add0
--- /dev/null
+++ b/test/prism/errors/pattern_match_implicit_rest.txt
@@ -0,0 +1,3 @@
+a=>b, *,
+ ^ expected a pattern expression after `,`
+
diff --git a/test/prism/errors/pattern_string_key.txt b/test/prism/errors/pattern_string_key.txt
new file mode 100644
index 0000000000..41bc1fa57b
--- /dev/null
+++ b/test/prism/errors/pattern_string_key.txt
@@ -0,0 +1,8 @@
+case:a
+^~~~ expected an `end` to close the `case` statement
+in b:"","#{}"
+ ^~~~~ expected a label after the `,` in the hash pattern
+ ^ expected a pattern expression after the key
+ ^ expected a delimiter after the patterns of an `in` clause
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+
diff --git a/test/prism/errors/rescue_pattern.txt b/test/prism/errors/rescue_pattern.txt
new file mode 100644
index 0000000000..c85feb27bd
--- /dev/null
+++ b/test/prism/errors/rescue_pattern.txt
@@ -0,0 +1,4 @@
+a rescue b => c in d
+ ^~ unexpected 'in', expecting end-of-input
+ ^~ unexpected 'in', ignoring it
+
diff --git a/test/prism/errors/shadow_args_in_lambda.txt b/test/prism/errors/shadow_args_in_lambda.txt
index 2399a0ebd5..7fc78d7d8f 100644
--- a/test/prism/errors/shadow_args_in_lambda.txt
+++ b/test/prism/errors/shadow_args_in_lambda.txt
@@ -1,5 +1,5 @@
->a;b{}
^ expected a `do` keyword or a `{` to open the lambda block
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a lambda block beginning with `do` to end with `end`
+^~ expected a lambda block beginning with `do` to end with `end`
diff --git a/test/prism/errors/singleton_method_for_literals.txt b/test/prism/errors/singleton_method_for_literals.txt
index 6247b4f025..ae850fca29 100644
--- a/test/prism/errors/singleton_method_for_literals.txt
+++ b/test/prism/errors/singleton_method_for_literals.txt
@@ -2,8 +2,6 @@ def (1).g; end
^ cannot define singleton method for literals
def ((a; 1)).foo; end
^ cannot define singleton method for literals
-def ((return; 1)).bar; end
- ^ cannot define singleton method for literals
def (((1))).foo; end
^ cannot define singleton method for literals
def (__FILE__).foo; end
diff --git a/test/prism/errors/unterminated_begin.txt b/test/prism/errors/unterminated_begin.txt
new file mode 100644
index 0000000000..2733f830c9
--- /dev/null
+++ b/test/prism/errors/unterminated_begin.txt
@@ -0,0 +1,4 @@
+begin
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~~~ expected an `end` to close the `begin` statement
+
diff --git a/test/prism/errors/unterminated_begin_upcase.txt b/test/prism/errors/unterminated_begin_upcase.txt
new file mode 100644
index 0000000000..5512f2089e
--- /dev/null
+++ b/test/prism/errors/unterminated_begin_upcase.txt
@@ -0,0 +1,4 @@
+BEGIN {
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^ expected a `}` to close the `BEGIN` statement
+
diff --git a/test/prism/errors/unterminated_block.txt b/test/prism/errors/unterminated_block.txt
index 8cc772db16..db6a4aa56c 100644
--- a/test/prism/errors/unterminated_block.txt
+++ b/test/prism/errors/unterminated_block.txt
@@ -1,4 +1,4 @@
foo {
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected a block beginning with `{` to end with `}`
+ ^ expected a block beginning with `{` to end with `}`
diff --git a/test/prism/errors/unterminated_block_do_end.txt b/test/prism/errors/unterminated_block_do_end.txt
new file mode 100644
index 0000000000..0b7c64965f
--- /dev/null
+++ b/test/prism/errors/unterminated_block_do_end.txt
@@ -0,0 +1,4 @@
+foo do
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^~ expected a block beginning with `do` to end with `end`
+
diff --git a/test/prism/errors/unterminated_class.txt b/test/prism/errors/unterminated_class.txt
new file mode 100644
index 0000000000..f47a3aa7df
--- /dev/null
+++ b/test/prism/errors/unterminated_class.txt
@@ -0,0 +1,4 @@
+class Foo
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~~~ expected an `end` to close the `class` statement
+
diff --git a/test/prism/errors/unterminated_def.txt b/test/prism/errors/unterminated_def.txt
new file mode 100644
index 0000000000..a6212e3a21
--- /dev/null
+++ b/test/prism/errors/unterminated_def.txt
@@ -0,0 +1,5 @@
+def foo
+ ^ expected a delimiter to close the parameters
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~ expected an `end` to close the `def` statement
+
diff --git a/test/prism/errors/unterminated_end_upcase.txt b/test/prism/errors/unterminated_end_upcase.txt
new file mode 100644
index 0000000000..ef01caa0ca
--- /dev/null
+++ b/test/prism/errors/unterminated_end_upcase.txt
@@ -0,0 +1,4 @@
+END {
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^ expected a `}` to close the `END` statement
+
diff --git a/test/prism/errors/unterminated_for.txt b/test/prism/errors/unterminated_for.txt
new file mode 100644
index 0000000000..75978a7cae
--- /dev/null
+++ b/test/prism/errors/unterminated_for.txt
@@ -0,0 +1,5 @@
+for x in y
+ ^ unexpected end-of-input; expected a 'do', newline, or ';' after the 'for' loop collection
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~ expected an `end` to close the `for` loop
+
diff --git a/test/prism/errors/unterminated_heredoc_and_embexpr.txt b/test/prism/errors/unterminated_heredoc_and_embexpr.txt
new file mode 100644
index 0000000000..bed7fcd24e
--- /dev/null
+++ b/test/prism/errors/unterminated_heredoc_and_embexpr.txt
@@ -0,0 +1,11 @@
+<<A+B
+ ^ unterminated heredoc; can't find string "A" anywhere before EOF
+ ^ unexpected '+', ignoring it
+ ^ unterminated heredoc; can't find string "A" anywhere before EOF
+#{C
+ ^ unexpected heredoc ending; expected an argument
+ ^ unexpected heredoc ending, expecting end-of-input
+ ^ unexpected heredoc ending, ignoring it
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^ expected a `}` to close the embedded expression
+
diff --git a/test/prism/errors/unterminated_heredoc_and_embexpr_2.txt b/test/prism/errors/unterminated_heredoc_and_embexpr_2.txt
new file mode 100644
index 0000000000..a03ff1d212
--- /dev/null
+++ b/test/prism/errors/unterminated_heredoc_and_embexpr_2.txt
@@ -0,0 +1,9 @@
+<<A+B
+ ^ unterminated heredoc; can't find string "A" anywhere before EOF
+#{C + "#{"}
+ ^ unterminated string meets end of file
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^ expected a `}` to close the embedded expression
+ ^ unterminated string; expected a closing delimiter for the interpolated string
+ ^ expected a `}` to close the embedded expression
+
diff --git a/test/prism/errors/unterminated_if.txt b/test/prism/errors/unterminated_if.txt
new file mode 100644
index 0000000000..1697931773
--- /dev/null
+++ b/test/prism/errors/unterminated_if.txt
@@ -0,0 +1,5 @@
+if true
+ ^ expected `then` or `;` or '\n'
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~ expected an `end` to close the conditional clause
+
diff --git a/test/prism/errors/unterminated_if_else.txt b/test/prism/errors/unterminated_if_else.txt
new file mode 100644
index 0000000000..db7828cce8
--- /dev/null
+++ b/test/prism/errors/unterminated_if_else.txt
@@ -0,0 +1,5 @@
+if true
+^~ expected an `end` to close the `else` clause
+else
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+
diff --git a/test/prism/errors/unterminated_lambda_brace.txt b/test/prism/errors/unterminated_lambda_brace.txt
new file mode 100644
index 0000000000..75474c7534
--- /dev/null
+++ b/test/prism/errors/unterminated_lambda_brace.txt
@@ -0,0 +1,4 @@
+-> {
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+ ^ expected a lambda block beginning with `{` to end with `}`
+
diff --git a/test/prism/errors/unterminated_module.txt b/test/prism/errors/unterminated_module.txt
new file mode 100644
index 0000000000..4c50ba5f63
--- /dev/null
+++ b/test/prism/errors/unterminated_module.txt
@@ -0,0 +1,4 @@
+module Foo
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~~~~ expected an `end` to close the `module` statement
+
diff --git a/test/prism/errors/unterminated_pattern_bracket.txt b/test/prism/errors/unterminated_pattern_bracket.txt
new file mode 100644
index 0000000000..4f35cd84af
--- /dev/null
+++ b/test/prism/errors/unterminated_pattern_bracket.txt
@@ -0,0 +1,7 @@
+case x
+^~~~ expected an `end` to close the `case` statement
+in [1
+ ^ expected a `]` to close the pattern expression
+ ^ expected a delimiter after the patterns of an `in` clause
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+
diff --git a/test/prism/errors/unterminated_pattern_paren.txt b/test/prism/errors/unterminated_pattern_paren.txt
new file mode 100644
index 0000000000..426d614e61
--- /dev/null
+++ b/test/prism/errors/unterminated_pattern_paren.txt
@@ -0,0 +1,7 @@
+case x
+^~~~ expected an `end` to close the `case` statement
+in (1
+ ^ expected a `)` to close the pattern expression
+ ^ expected a delimiter after the patterns of an `in` clause
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+
diff --git a/test/prism/errors/unterminated_until.txt b/test/prism/errors/unterminated_until.txt
new file mode 100644
index 0000000000..42a0545200
--- /dev/null
+++ b/test/prism/errors/unterminated_until.txt
@@ -0,0 +1,5 @@
+until true
+ ^ expected a predicate expression for the `until` statement
+ ^ unexpected end-of-input, assuming it is closing the parent top level context
+^~~~~ expected an `end` to close the `until` statement
+
diff --git a/test/prism/errors/void_value_expression_in_begin_statement.txt b/test/prism/errors/void_value_expression_in_begin_statement.txt
index aa8f1ded96..fb968a12e1 100644
--- a/test/prism/errors/void_value_expression_in_begin_statement.txt
+++ b/test/prism/errors/void_value_expression_in_begin_statement.txt
@@ -14,8 +14,6 @@ x = begin return ensure return end
^~~~~~ unexpected void value expression
x = begin return; rescue; return end
^~~~~~ unexpected void value expression
-x = begin return; rescue; return; else return end
- ^~~~~~ unexpected void value expression
x = begin; return; rescue; retry; end
^~~~~~ unexpected void value expression
diff --git a/test/prism/errors/while_endless_method.txt b/test/prism/errors/while_endless_method.txt
index 6f062d89d0..cdd7ba9aba 100644
--- a/test/prism/errors/while_endless_method.txt
+++ b/test/prism/errors/while_endless_method.txt
@@ -1,5 +1,5 @@
while def f = g do end
^ expected a predicate expression for the `while` statement
^ unexpected end-of-input, assuming it is closing the parent top level context
- ^ expected an `end` to close the `while` statement
+^~~~~ expected an `end` to close the `while` statement
diff --git a/test/prism/errors/xstring_concat.txt b/test/prism/errors/xstring_concat.txt
new file mode 100644
index 0000000000..f4d453d68d
--- /dev/null
+++ b/test/prism/errors/xstring_concat.txt
@@ -0,0 +1,5 @@
+<<`EOC` "bar"
+^~~~~~~ expected a string for concatenation
+echo foo
+EOC
+
diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb
index cb2fd48d37..9dd7fbe3fe 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -1,38 +1,24 @@
# frozen_string_literal: true
+return if RUBY_VERSION < "3.3.0"
+
require_relative "test_helper"
module Prism
class ErrorsTest < TestCase
base = File.expand_path("errors", __dir__)
- filepaths = Dir["*.txt", base: base]
-
- if RUBY_VERSION < "3.0"
- filepaths -= [
- "cannot_assign_to_a_reserved_numbered_parameter.txt",
- "writing_numbered_parameter.txt",
- "targeting_numbered_parameter.txt",
- "defining_numbered_parameter.txt",
- "defining_numbered_parameter_2.txt",
- "numbered_parameters_in_block_arguments.txt"
- ]
- end
+ filepaths = Dir[ENV.fetch("FOCUS", "**/*.txt"), base: base]
- if RUBY_VERSION < "3.4"
- filepaths -= [
- "it_with_ordinary_parameter.txt",
- "block_args_in_array_assignment.txt",
- "keyword_args_in_array_assignment.txt"
- ]
- end
-
- if RUBY_VERSION < "3.4" || RUBY_RELEASE_DATE < "2024-07-24"
- filepaths -= ["dont_allow_return_inside_sclass_body.txt"]
- end
+ PARSE_Y_EXCLUDES = [
+ # https://bugs.ruby-lang.org/issues/20409
+ "#{base}/4.1/end_block_exit.txt"
+ ]
filepaths.each do |filepath|
- define_method(:"test_#{File.basename(filepath, ".txt")}") do
- assert_errors(File.join(base, filepath))
+ ruby_versions_for(filepath).each do |version|
+ define_method(:"test_#{version}_#{File.basename(filepath, ".txt")}") do
+ assert_errors(File.join(base, filepath), version)
+ end
end
end
@@ -64,52 +50,85 @@ module Prism
def test_unterminated_string_closing
statement = Prism.parse_statement("'hello")
assert_equal statement.unescaped, "hello"
- assert_empty statement.closing
+ assert_nil statement.closing
end
def test_unterminated_interpolated_string_closing
statement = Prism.parse_statement('"hello')
assert_equal statement.unescaped, "hello"
- assert_empty statement.closing
+ assert_nil statement.closing
end
def test_unterminated_empty_string_closing
statement = Prism.parse_statement('"')
assert_empty statement.unescaped
- assert_empty statement.closing
+ assert_nil statement.closing
end
- def test_invalid_message_name
- assert_equal :"", Prism.parse_statement("+.@foo,+=foo").write_name
+ def test_regexp_encoding_option_mismatch_error
+ # UTF-8 char with ASCII-8BIT modifier
+ result = Prism.parse('/Ȃ/n')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with EUC-JP modifier
+ result = Prism.parse('/Ȃ/e')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with Windows-31J modifier
+ result = Prism.parse('/Ȃ/s')
+ assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch
+
+ # UTF-8 char with UTF-8 modifier
+ result = Prism.parse('/Ȃ/u')
+ assert_empty result.errors
end
- def test_circular_parameters
- source = <<~RUBY
- def foo(bar = bar) = 42
- def foo(bar: bar) = 42
- proc { |foo = foo| }
- proc { |foo: foo| }
- RUBY
+ def test_incomplete_def_closing_loc
+ statement = Prism.parse_statement("def f; 123")
+ assert_nil(statement.end_keyword)
+ end
- source.each_line do |line|
- assert_predicate Prism.parse(line, version: "3.3.0"), :failure?
- assert_predicate Prism.parse(line), :success?
- end
+ def test_unclosed_interpolation
+ statement = Prism.parse_statement("\"\#{")
+ assert_equal('"', statement.opening)
+ assert_nil(statement.closing)
+
+ assert_equal(1, statement.parts.count)
+ assert_equal('#{', statement.parts[0].opening)
+ assert_equal("", statement.parts[0].closing)
+ assert_nil(statement.parts[0].statements)
+ end
+
+ def test_unclosed_heredoc_and_interpolation
+ statement = Prism.parse_statement("<<D\n\#{")
+ assert_equal("<<D", statement.opening)
+ assert_nil(statement.closing)
+
+ assert_equal(1, statement.parts.count)
+ assert_equal('#{', statement.parts[0].opening)
+ assert_equal("", statement.parts[0].closing)
+ assert_nil(statement.parts[0].statements)
end
private
- def assert_errors(filepath)
+ def assert_errors(filepath, version)
expected = File.read(filepath, binmode: true, external_encoding: Encoding::UTF_8)
source = expected.lines.grep_v(/^\s*\^/).join.gsub(/\n*\z/, "")
- refute_valid_syntax(source)
+ if CURRENT_MAJOR_MINOR == version && !PARSE_Y_EXCLUDES.include?(filepath)
+ refute_valid_syntax(source)
+ end
- result = Prism.parse(source)
+ result = Prism.parse(source, version: version)
errors = result.errors
refute_empty errors, "Expected errors in #{filepath}"
actual = result.errors_format
+ if expected != actual && ENV["UPDATE_SNAPSHOTS"]
+ File.write(filepath, actual)
+ end
+
assert_equal expected, actual, "Expected errors to match for #{filepath}"
end
end
diff --git a/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt b/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt
new file mode 100644
index 0000000000..6d6b052681
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt
@@ -0,0 +1 @@
+matrix[5, &block] = 8
diff --git a/test/prism/fixtures/it.txt b/test/prism/fixtures/3.3-3.3/it.txt
index 76deb68028..5410b01e71 100644
--- a/test/prism/fixtures/it.txt
+++ b/test/prism/fixtures/3.3-3.3/it.txt
@@ -1,3 +1,5 @@
x do
it
end
+
+-> { it }
diff --git a/test/prism/fixtures/3.3-3.3/it_indirect_writes.txt b/test/prism/fixtures/3.3-3.3/it_indirect_writes.txt
new file mode 100644
index 0000000000..bb87e9483e
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/it_indirect_writes.txt
@@ -0,0 +1,23 @@
+tap { it += 1 }
+
+tap { it ||= 1 }
+
+tap { it &&= 1 }
+
+tap { it; it += 1 }
+
+tap { it; it ||= 1 }
+
+tap { it; it &&= 1 }
+
+tap { it += 1; it }
+
+tap { it ||= 1; it }
+
+tap { it &&= 1; it }
+
+tap { it; it += 1; it }
+
+tap { it; it ||= 1; it }
+
+tap { it; it &&= 1; it }
diff --git a/test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt b/test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt
new file mode 100644
index 0000000000..2cceeb2a54
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/it_read_and_assignment.txt
@@ -0,0 +1 @@
+42.tap { p it; it = it; p it }
diff --git a/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt b/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt
new file mode 100644
index 0000000000..178b641e6b
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt
@@ -0,0 +1 @@
+proc { || it }
diff --git a/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt b/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt
new file mode 100644
index 0000000000..88016c2afe
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt
@@ -0,0 +1 @@
+matrix[5, axis: :y] = 8
diff --git a/test/prism/fixtures/3.3-3.3/return_in_sclass.txt b/test/prism/fixtures/3.3-3.3/return_in_sclass.txt
new file mode 100644
index 0000000000..f1fde5771a
--- /dev/null
+++ b/test/prism/fixtures/3.3-3.3/return_in_sclass.txt
@@ -0,0 +1 @@
+class << A; return; end
diff --git a/test/prism/fixtures/3.3-4.0/end_block_exit.txt b/test/prism/fixtures/3.3-4.0/end_block_exit.txt
new file mode 100644
index 0000000000..8ebf0d6369
--- /dev/null
+++ b/test/prism/fixtures/3.3-4.0/end_block_exit.txt
@@ -0,0 +1,11 @@
+END {
+ return
+}
+
+END {
+ break
+}
+
+END {
+ next
+}
diff --git a/test/prism/fixtures/3.3-4.0/void_value.txt b/test/prism/fixtures/3.3-4.0/void_value.txt
new file mode 100644
index 0000000000..bfb8eff09c
--- /dev/null
+++ b/test/prism/fixtures/3.3-4.0/void_value.txt
@@ -0,0 +1,29 @@
+x = begin
+ foo
+rescue
+ return
+else
+ return
+end
+
+x = case
+ when 1 then return
+ else return
+end
+
+x = case 1
+ in 2 then return
+ else return
+end
+
+x = begin
+ return
+ "NG"
+end
+
+x = if rand < 0.5
+ return
+ "NG"
+else
+ return
+end
diff --git a/test/prism/fixtures/3.4/circular_parameters.txt b/test/prism/fixtures/3.4/circular_parameters.txt
new file mode 100644
index 0000000000..11537023ad
--- /dev/null
+++ b/test/prism/fixtures/3.4/circular_parameters.txt
@@ -0,0 +1,4 @@
+def foo(bar = bar) = 42
+def foo(bar: bar) = 42
+proc { |foo = foo| }
+proc { |foo: foo| }
diff --git a/test/prism/fixtures/3.4/it.txt b/test/prism/fixtures/3.4/it.txt
new file mode 100644
index 0000000000..5410b01e71
--- /dev/null
+++ b/test/prism/fixtures/3.4/it.txt
@@ -0,0 +1,5 @@
+x do
+ it
+end
+
+-> { it }
diff --git a/test/prism/fixtures/3.4/it_indirect_writes.txt b/test/prism/fixtures/3.4/it_indirect_writes.txt
new file mode 100644
index 0000000000..bb87e9483e
--- /dev/null
+++ b/test/prism/fixtures/3.4/it_indirect_writes.txt
@@ -0,0 +1,23 @@
+tap { it += 1 }
+
+tap { it ||= 1 }
+
+tap { it &&= 1 }
+
+tap { it; it += 1 }
+
+tap { it; it ||= 1 }
+
+tap { it; it &&= 1 }
+
+tap { it += 1; it }
+
+tap { it ||= 1; it }
+
+tap { it &&= 1; it }
+
+tap { it; it += 1; it }
+
+tap { it; it ||= 1; it }
+
+tap { it; it &&= 1; it }
diff --git a/test/prism/fixtures/3.4/it_read_and_assignment.txt b/test/prism/fixtures/3.4/it_read_and_assignment.txt
new file mode 100644
index 0000000000..2cceeb2a54
--- /dev/null
+++ b/test/prism/fixtures/3.4/it_read_and_assignment.txt
@@ -0,0 +1 @@
+42.tap { p it; it = it; p it }
diff --git a/test/prism/fixtures/4.0/endless_methods_command_call.txt b/test/prism/fixtures/4.0/endless_methods_command_call.txt
new file mode 100644
index 0000000000..146a6ee579
--- /dev/null
+++ b/test/prism/fixtures/4.0/endless_methods_command_call.txt
@@ -0,0 +1,11 @@
+private def foo = puts "Hello"
+private def foo = puts "Hello", "World"
+private def foo = puts "Hello" do expr end
+private def foo() = puts "Hello"
+private def foo(x) = puts x
+private def obj.foo = puts "Hello"
+private def obj.foo() = puts "Hello"
+private def obj.foo(x) = puts x
+
+private def foo = bar baz
+private def foo = bar baz do expr end
diff --git a/test/prism/fixtures/4.0/leading_logical.txt b/test/prism/fixtures/4.0/leading_logical.txt
new file mode 100644
index 0000000000..ee87e00d4f
--- /dev/null
+++ b/test/prism/fixtures/4.0/leading_logical.txt
@@ -0,0 +1,16 @@
+1
+&& 2
+&& 3
+
+1
+|| 2
+|| 3
+
+1
+and 2
+and 3
+
+1
+or 2
+or 3
+
diff --git a/test/prism/fixtures/4.1/noblock.txt b/test/prism/fixtures/4.1/noblock.txt
new file mode 100644
index 0000000000..2395393e22
--- /dev/null
+++ b/test/prism/fixtures/4.1/noblock.txt
@@ -0,0 +1,4 @@
+def foo(&nil)
+end
+
+-> (&nil) {}
diff --git a/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt b/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt
new file mode 100644
index 0000000000..ef1385d973
--- /dev/null
+++ b/test/prism/fixtures/4.1/trailing_comma_after_method_arguments.txt
@@ -0,0 +1,15 @@
+def foo(a,b,c,);end
+
+def foo(a,b,*c,);end
+
+def foo(a,b,*,);end
+
+def foo(a,b,**c,);end
+
+def foo(a,b,**,);end
+
+def foo(
+ a,
+ b,
+ c,
+);end
diff --git a/test/prism/fixtures/4.1/void_value.txt b/test/prism/fixtures/4.1/void_value.txt
new file mode 100644
index 0000000000..915112d623
--- /dev/null
+++ b/test/prism/fixtures/4.1/void_value.txt
@@ -0,0 +1,7 @@
+x = begin
+ return
+rescue
+ "OK"
+else
+ return
+end
diff --git a/test/prism/fixtures/__END__.txt b/test/prism/fixtures/__END__.txt
new file mode 100644
index 0000000000..c0f4f28004
--- /dev/null
+++ b/test/prism/fixtures/__END__.txt
@@ -0,0 +1,3 @@
+foo
+__END__
+Available in DATA constant
diff --git a/test/prism/fixtures/and_or_with_suffix.txt b/test/prism/fixtures/and_or_with_suffix.txt
new file mode 100644
index 0000000000..59ee4d0b88
--- /dev/null
+++ b/test/prism/fixtures/and_or_with_suffix.txt
@@ -0,0 +1,17 @@
+foo
+and?
+
+foo
+or?
+
+foo
+and!
+
+foo
+or!
+
+foo
+andbar
+
+foo
+orbar
diff --git a/test/prism/fixtures/begin_rescue.txt b/test/prism/fixtures/begin_rescue.txt
index 0a56fbef9f..790574f4ff 100644
--- a/test/prism/fixtures/begin_rescue.txt
+++ b/test/prism/fixtures/begin_rescue.txt
@@ -2,6 +2,12 @@ begin; a; rescue; b; else; c; end
begin; a; rescue; b; else; c; ensure; d; end
+begin; rescue ; end
+
+begin; rescue ; ensure ; end
+
+begin; rescue ; else ; end
+
begin
a
end
diff --git a/test/prism/fixtures/blocks.txt b/test/prism/fixtures/blocks.txt
index e33d95c150..51ec84950c 100644
--- a/test/prism/fixtures/blocks.txt
+++ b/test/prism/fixtures/blocks.txt
@@ -52,3 +52,11 @@ foo lambda { |
}
foo do |bar,| end
+
+foo bar baz, qux do end
+
+foo.bar baz do end
+
+foo.bar baz do end.qux quux do end
+
+foo bar, baz do |x| x end
diff --git a/test/prism/fixtures/bom_leading_space.txt b/test/prism/fixtures/bom_leading_space.txt
new file mode 100644
index 0000000000..48d3ee50ea
--- /dev/null
+++ b/test/prism/fixtures/bom_leading_space.txt
@@ -0,0 +1 @@
+ p (42)
diff --git a/test/prism/fixtures/bom_spaces.txt b/test/prism/fixtures/bom_spaces.txt
new file mode 100644
index 0000000000..c18ad4c21a
--- /dev/null
+++ b/test/prism/fixtures/bom_spaces.txt
@@ -0,0 +1 @@
+p ( 42 )
diff --git a/test/prism/fixtures/break.txt b/test/prism/fixtures/break.txt
index 5532322c5c..d823f866df 100644
--- a/test/prism/fixtures/break.txt
+++ b/test/prism/fixtures/break.txt
@@ -20,6 +20,10 @@ tap { break() }
tap { break(1) }
+tap { (break 1) }
+
+tap { foo && (break 1) }
+
foo { break 42 } == 42
foo { |a| break } == 42
diff --git a/test/prism/fixtures/case_in_hash_key.txt b/test/prism/fixtures/case_in_hash_key.txt
new file mode 100644
index 0000000000..75ac8a846f
--- /dev/null
+++ b/test/prism/fixtures/case_in_hash_key.txt
@@ -0,0 +1,6 @@
+case 1
+in 2
+ A.print message:
+in 3
+ A.print message:
+end
diff --git a/test/prism/fixtures/case_in_in.txt b/test/prism/fixtures/case_in_in.txt
new file mode 100644
index 0000000000..a5f9e4ec41
--- /dev/null
+++ b/test/prism/fixtures/case_in_in.txt
@@ -0,0 +1,4 @@
+case args
+in [event]
+ context.event in ^event
+end
diff --git a/test/prism/fixtures/character_literal.txt b/test/prism/fixtures/character_literal.txt
new file mode 100644
index 0000000000..920332123f
--- /dev/null
+++ b/test/prism/fixtures/character_literal.txt
@@ -0,0 +1,2 @@
+# encoding: Windows-31J
+p ?\u3042""
diff --git a/test/prism/fixtures/command_method_call_2.txt b/test/prism/fixtures/command_method_call_2.txt
new file mode 100644
index 0000000000..8bd40cff9e
--- /dev/null
+++ b/test/prism/fixtures/command_method_call_2.txt
@@ -0,0 +1 @@
+foo(bar baz, bat)
diff --git a/test/prism/fixtures/command_method_call_3.txt b/test/prism/fixtures/command_method_call_3.txt
new file mode 100644
index 0000000000..6de0446aa9
--- /dev/null
+++ b/test/prism/fixtures/command_method_call_3.txt
@@ -0,0 +1,19 @@
+foo(bar 1, key => '2')
+
+foo(bar 1, KEY => '2')
+
+foo(bar 1, :key => '2')
+
+foo(bar 1, { baz: :bat } => '2')
+
+foo bar - %i[baz] => '2'
+
+foo(bar {} => '2')
+
+foo(bar baz {} => '2')
+
+foo(bar do end => '2')
+
+foo(1, bar {} => '2')
+
+foo(1, bar do end => '2')
diff --git a/test/prism/fixtures/defined.txt b/test/prism/fixtures/defined.txt
index 247fa94e3a..09fc0a29e7 100644
--- a/test/prism/fixtures/defined.txt
+++ b/test/prism/fixtures/defined.txt
@@ -8,3 +8,12 @@ defined? 1
defined?("foo"
)
+
+defined?
+1
+
+defined?
+(1)
+
+defined?
+()
diff --git a/test/prism/fixtures/dstring.txt b/test/prism/fixtures/dstring.txt
index 99f6c0dfac..ef698d8fe9 100644
--- a/test/prism/fixtures/dstring.txt
+++ b/test/prism/fixtures/dstring.txt
@@ -34,5 +34,9 @@ b\nar
#{}
"
+"foo
+\n#{}bar\n\n#{}
+a\nb\n#{}\nc\n"
+
"
’"
diff --git a/test/prism/fixtures/endless_method_as_default_arg.txt b/test/prism/fixtures/endless_method_as_default_arg.txt
new file mode 100644
index 0000000000..0063d9a8fa
--- /dev/null
+++ b/test/prism/fixtures/endless_method_as_default_arg.txt
@@ -0,0 +1,11 @@
+def foo(a = def f = 1); end
+
+def foo(a = def f = 1, b); end
+
+def foo(b, a = def f = 1); end
+
+def foo(a: def f = 1); end
+
+def foo(a = def f = 1+2); end
+
+->(a = def f = 1) {}
diff --git a/test/prism/fixtures/endless_methods.txt b/test/prism/fixtures/endless_methods.txt
index 8c2f2a30cc..6e0488a5ee 100644
--- a/test/prism/fixtures/endless_methods.txt
+++ b/test/prism/fixtures/endless_methods.txt
@@ -3,3 +3,9 @@ def foo = 1
def bar = A ""
def method = 1 + 2 + 3
+
+x = def f = p 1
+
+def foo = bar baz
+
+def foo = bar(baz)
diff --git a/test/prism/fixtures/escaped_newline_with_trailing_content.txt b/test/prism/fixtures/escaped_newline_with_trailing_content.txt
new file mode 100644
index 0000000000..fe947a3f10
--- /dev/null
+++ b/test/prism/fixtures/escaped_newline_with_trailing_content.txt
@@ -0,0 +1,2 @@
+"A
+B\nCC"
diff --git a/test/prism/fixtures/heredoc_dedent_line_continuation.txt b/test/prism/fixtures/heredoc_dedent_line_continuation.txt
new file mode 100644
index 0000000000..661db490c7
--- /dev/null
+++ b/test/prism/fixtures/heredoc_dedent_line_continuation.txt
@@ -0,0 +1,5 @@
+<<~FOO
+ foo\
+ \
+ bar
+FOO
diff --git a/test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt b/test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt
new file mode 100644
index 0000000000..dbfa0bf4b4
--- /dev/null
+++ b/test/prism/fixtures/heredoc_percent_q_newline_delimiter.txt
@@ -0,0 +1,22 @@
+%Q
+#{<<B}
+B
+
+%
+#{<<B}
+B
+
+<<A; %Q
+A
+#{<<B}
+B
+
+<<A; %
+A
+#{<<B}
+B
+
+# \r\n
+%Q
+#{<<B}
+B
diff --git a/test/prism/fixtures/heredocs_with_fake_newlines.txt b/test/prism/fixtures/heredocs_with_fake_newlines.txt
new file mode 100644
index 0000000000..887b7ab5e7
--- /dev/null
+++ b/test/prism/fixtures/heredocs_with_fake_newlines.txt
@@ -0,0 +1,55 @@
+<<-RUBY
+ \n
+ \n
+ exit
+ \\n
+ \n\n\n\n
+ argh
+ \\
+ \\\
+ foo\nbar
+ \f
+ ok
+RUBY
+
+<<~RUBY
+ \n
+ \n
+ exit
+ \\n
+ \n\n\n\n
+ argh
+ \\
+ \\\
+ foo\nbar
+ \f
+ ok
+RUBY
+
+<<~RUBY
+ #{123}\n
+ \n
+ exit
+ \\#{123}n
+ \n#{123}\n\n\n
+ argh
+ \\#{123}baz
+ \\\
+ foo\nbar
+ \f
+ ok
+RUBY
+
+<<'RUBY'
+ \n
+ \n
+ exit
+ \n
+ \n\n\n\n
+ argh
+ \
+ \
+ foo\nbar
+ \f
+ ok
+RUBY
diff --git a/test/prism/fixtures/it_assignment.txt b/test/prism/fixtures/it_assignment.txt
new file mode 100644
index 0000000000..523b0ffe1e
--- /dev/null
+++ b/test/prism/fixtures/it_assignment.txt
@@ -0,0 +1 @@
+42.tap { it = it; p it }
diff --git a/test/prism/fixtures/keyword_method_names.txt b/test/prism/fixtures/keyword_method_names.txt
index 9154469441..d3b6eac537 100644
--- a/test/prism/fixtures/keyword_method_names.txt
+++ b/test/prism/fixtures/keyword_method_names.txt
@@ -12,18 +12,9 @@ end
def m(a, **nil)
end
-def __ENCODING__.a
-end
-
%{abc}
%"abc"
-def __FILE__.a
-end
-
-def __LINE__.a
-end
-
def nil::a
end
diff --git a/test/prism/fixtures/next.txt b/test/prism/fixtures/next.txt
index 2ef14c6304..0d2d6a11f5 100644
--- a/test/prism/fixtures/next.txt
+++ b/test/prism/fixtures/next.txt
@@ -22,3 +22,7 @@ tap { next
tap { next() }
tap { next(1) }
+
+tap { (next 1) }
+
+tap { foo && (next 1) }
diff --git a/test/prism/fixtures/non_void_value.txt b/test/prism/fixtures/non_void_value.txt
new file mode 100644
index 0000000000..388e9f2574
--- /dev/null
+++ b/test/prism/fixtures/non_void_value.txt
@@ -0,0 +1,31 @@
+x = begin
+ return if true
+ "conditional"
+end
+
+x = if rand < 0.5
+ return
+ "else is nil"
+end
+
+x = if true
+ return if true
+ "conditional"
+else
+ return
+end
+
+x = if true
+ return if true
+else
+ return if true
+ "conditional"
+end
+
+x = case
+ when 1
+ return if true
+ "conditional"
+ else
+ return
+end
diff --git a/test/prism/fixtures/patterns.txt b/test/prism/fixtures/patterns.txt
index f4f3489e4d..449dac619b 100644
--- a/test/prism/fixtures/patterns.txt
+++ b/test/prism/fixtures/patterns.txt
@@ -212,8 +212,13 @@ foo => Object[{x:}]
case (); in [_a, _a]; end
case (); in [{a:1}, {a:2}]; end
+a => ^({'a' => 'b'})
a in b, and c
a in b, or c
(a in b,) and c
(a in b,) or c
+
+x => ^([*a.x])
+x => ^([**a.x])
+x => ^({ a: })
diff --git a/test/prism/fixtures/regex_with_fake_newlines.txt b/test/prism/fixtures/regex_with_fake_newlines.txt
new file mode 100644
index 0000000000..d92a2e4ade
--- /dev/null
+++ b/test/prism/fixtures/regex_with_fake_newlines.txt
@@ -0,0 +1,41 @@
+/
+ \n
+ \n
+ exit
+ \\n
+ \n\n\n\n
+ argh
+ \\
+ \\\
+ foo\nbar
+ \f
+ ok
+/
+
+%r{
+ \n
+ \n
+ exit
+ \\n
+ \n\n\n\n
+ argh
+ \\
+ \\\
+ foo\nbar
+ \f
+ ok
+}
+
+%r{
+ #{123}\n
+ \n
+ exit\\\
+ \\#{123}n
+ \n#{123}\n\n\n
+ argh\
+ \\#{123}baz\\
+ \\\
+ foo\nbar
+ \f
+ ok
+}
diff --git a/test/prism/fixtures/rescue.txt b/test/prism/fixtures/rescue.txt
index 99170fbe0f..f436463029 100644
--- a/test/prism/fixtures/rescue.txt
+++ b/test/prism/fixtures/rescue.txt
@@ -33,3 +33,7 @@ end
foo if bar rescue baz
z = x y rescue c d
+
+begin
+rescue => A[]
+end
diff --git a/test/prism/fixtures/rescue_modifier.txt b/test/prism/fixtures/rescue_modifier.txt
new file mode 100644
index 0000000000..def9e2dbed
--- /dev/null
+++ b/test/prism/fixtures/rescue_modifier.txt
@@ -0,0 +1,7 @@
+a rescue b if c
+
+a = b rescue c if d
+
+a, = b rescue c if d
+
+def a = b rescue c if d
diff --git a/test/prism/fixtures/return.txt b/test/prism/fixtures/return.txt
index a8b5b95fab..952fb80da8 100644
--- a/test/prism/fixtures/return.txt
+++ b/test/prism/fixtures/return.txt
@@ -22,3 +22,6 @@ return()
return(1)
+(return 1)
+
+foo && (return 1)
diff --git a/test/prism/fixtures/string_concatination_frozen_false.txt b/test/prism/fixtures/string_concatination_frozen_false.txt
new file mode 100644
index 0000000000..abe9301408
--- /dev/null
+++ b/test/prism/fixtures/string_concatination_frozen_false.txt
@@ -0,0 +1,5 @@
+# frozen_string_literal: false
+
+'foo' 'bar'
+
+'foo' 'bar' "baz#{bat}"
diff --git a/test/prism/fixtures/string_concatination_frozen_true.txt b/test/prism/fixtures/string_concatination_frozen_true.txt
new file mode 100644
index 0000000000..829777f0a7
--- /dev/null
+++ b/test/prism/fixtures/string_concatination_frozen_true.txt
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+'foo' 'bar'
+
+'foo' 'bar' "baz#{bat}"
diff --git a/test/prism/fixtures/strings.txt b/test/prism/fixtures/strings.txt
index 83f38cb606..1419f975b7 100644
--- a/test/prism/fixtures/strings.txt
+++ b/test/prism/fixtures/strings.txt
@@ -45,6 +45,10 @@ foo\
b\nar
"
+"foo
+\nbar\n\n
+a\nb\n\nc\n"
+
%q{abc}
%s[abc]
@@ -69,6 +73,62 @@ b\nar
%w[foo\ bar baz]
+%w[foo\ bar\\ baz\\\
+ bat]
+
+%W[#{foo}\
+bar
+baz #{bat}
+]
+
+%w(foo\n)
+
+%w(foo\
+)
+
+%w(foo \n)
+
+%W(foo\
+bar)
+
+%w[foo bar]
+
+%w[
+ a
+ b c
+ d
+]
+
+%w[
+ foo\nbar baz\n\n\
+ bat\n\\\n\foo
+]
+
+%W[
+ foo\nbar baz\n\n\
+ bat\n\\\n\foo
+]
+
+%w[foo\
+ bar
+ baz\\
+ bat
+ 1\n
+ 2
+ 3\\n
+]
+
+%W[foo\
+ bar
+ baz\\
+ bat
+ 1\n
+ 2
+ 3\\n
+]
+
+%W[f\u{006f 006f}]
+
%W[a b#{c}d e]
%W[a b c]
@@ -96,6 +156,10 @@ baz
"\7 \43 \141"
+"ち\xE3\x81\xFF"
+
+"\777"
+
%[abc]
%(abc)
@@ -110,6 +174,10 @@ baz
%Q{abc}
+%Q(\«)
+
+%q(\«)
+
%^#$^#
%@#@#
diff --git a/test/prism/fixtures/symbols.txt b/test/prism/fixtures/symbols.txt
index edee418bca..34895b9e9f 100644
--- a/test/prism/fixtures/symbols.txt
+++ b/test/prism/fixtures/symbols.txt
@@ -4,12 +4,12 @@
:"abc#{1}"
-"
+:"
foo\
b\nar
"
-"
+:"
foo\
b\nar
#{}
diff --git a/test/prism/fixtures/unary_method_calls.txt b/test/prism/fixtures/unary_method_calls.txt
new file mode 100644
index 0000000000..a8327d23cc
--- /dev/null
+++ b/test/prism/fixtures/unary_method_calls.txt
@@ -0,0 +1,8 @@
+42.~@
+42.!@
+
+-
+42
+
++
+42
diff --git a/test/prism/fixtures/variables.txt b/test/prism/fixtures/variables.txt
index 1545c30c80..4f4dc6f9c8 100644
--- a/test/prism/fixtures/variables.txt
+++ b/test/prism/fixtures/variables.txt
@@ -45,3 +45,5 @@ Foo = 1, 2
(a; b; c)
a, (b, c), d = []
+
+(a,), = []
diff --git a/test/prism/fixtures/write_command_operator.txt b/test/prism/fixtures/write_command_operator.txt
new file mode 100644
index 0000000000..d719d24f87
--- /dev/null
+++ b/test/prism/fixtures/write_command_operator.txt
@@ -0,0 +1,3 @@
+foo = 123 | '456' or return
+
+foo = 123 | '456' in BAR
diff --git a/test/prism/fixtures_test.rb b/test/prism/fixtures_test.rb
index 3b4a502b90..dcbcb7c117 100644
--- a/test/prism/fixtures_test.rb
+++ b/test/prism/fixtures_test.rb
@@ -8,7 +8,6 @@ module Prism
class FixturesTest < TestCase
except = []
-
if RUBY_VERSION < "3.3.0"
# Ruby < 3.3.0 cannot parse heredocs where there are leading whitespace
# characters in the heredoc start.
@@ -25,7 +24,14 @@ module Prism
except << "whitequark/ruby_bug_19281.txt"
end
- Fixture.each(except: except) do |fixture|
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ except << "command_method_call_2.txt"
+ # https://bugs.ruby-lang.org/issues/21669
+ except << "4.1/void_value.txt"
+ # https://bugs.ruby-lang.org/issues/19107
+ except << "4.1/trailing_comma_after_method_arguments.txt"
+
+ Fixture.each_for_current_ruby(except: except) do |fixture|
define_method(fixture.test_name) { assert_valid_syntax(fixture.read) }
end
end
diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb
index 0e03874a15..1e06d52184 100644
--- a/test/prism/lex_test.rb
+++ b/test/prism/lex_test.rb
@@ -3,45 +3,10 @@
return if !(RUBY_ENGINE == "ruby" && RUBY_VERSION >= "3.2.0")
require_relative "test_helper"
+require "ripper"
module Prism
class LexTest < TestCase
- except = [
- # It seems like there are some oddities with nested heredocs and ripper.
- # Waiting for feedback on https://bugs.ruby-lang.org/issues/19838.
- "seattlerb/heredoc_nested.txt",
- "whitequark/dedenting_heredoc.txt",
- # Ripper seems to have a bug that the regex portions before and after
- # the heredoc are combined into a single token. See
- # https://bugs.ruby-lang.org/issues/19838.
- "spanning_heredoc.txt",
- "spanning_heredoc_newlines.txt"
- ]
-
- if RUBY_VERSION < "3.3.0"
- # This file has changed behavior in Ripper in Ruby 3.3, so we skip it if
- # we're on an earlier version.
- except << "seattlerb/pct_w_heredoc_interp_nested.txt"
-
- # Ruby < 3.3.0 cannot parse heredocs where there are leading whitespace
- # characters in the heredoc start.
- # Example: <<~' EOF' or <<-' EOF'
- # https://bugs.ruby-lang.org/issues/19539
- except << "heredocs_leading_whitespace.txt"
- except << "whitequark/ruby_bug_19539.txt"
-
- # https://bugs.ruby-lang.org/issues/19025
- except << "whitequark/numparam_ruby_bug_19025.txt"
- # https://bugs.ruby-lang.org/issues/18878
- except << "whitequark/ruby_bug_18878.txt"
- # https://bugs.ruby-lang.org/issues/19281
- except << "whitequark/ruby_bug_19281.txt"
- end
-
- Fixture.each(except: except) do |fixture|
- define_method(fixture.test_name) { assert_lex(fixture) }
- end
-
def test_lex_file
assert_nothing_raised do
Prism.lex_file(__FILE__)
@@ -82,17 +47,77 @@ module Prism
end
end
- private
-
- def assert_lex(fixture)
- source = fixture.read
+ def test_lex_encoding
+ tokens = Prism.lex('"わたし"', encoding: Encoding::Windows_31J).value
+ tokens.each do |t|
+ assert_equal(Encoding::Windows_31J, t[0].value.encoding)
+ end
- result = Prism.lex_compat(source)
- assert_equal [], result.errors
+ # Shebangs must appear on the first line. For these cases, the encoding
+ # comment may appear second, but it should still change encoding.
+ tokens = Prism.lex(<<~RUBY, encoding: Encoding::Windows_31J).value
+ #! /usr/bin/env ruby
+ # encoding: utf-8
+ "わたし"
+ RUBY
+ tokens.each do |t|
+ assert_equal(Encoding::UTF_8, t[0].value.encoding)
+ end
+ end
- Prism.lex_ripper(source).zip(result.value).each do |(ripper, prism)|
- assert_equal ripper, prism
+ if RUBY_VERSION >= "3.3"
+ def test_lex_compat
+ source = "foo bar"
+ prism = Prism.lex_compat(source, version: "current").value
+ ripper = Ripper.lex(source)
+ assert_equal(ripper, prism)
end
end
+
+ def test_lex_interpolation_unterminated
+ assert_equal(
+ %i[STRING_BEGIN EMBEXPR_BEGIN EOF],
+ token_types('"#{')
+ )
+
+ assert_equal(
+ %i[STRING_BEGIN EMBEXPR_BEGIN IGNORED_NEWLINE EOF],
+ token_types('"#{' + "\n")
+ )
+ end
+
+ def test_lex_interpolation_unterminated_with_content
+ # FIXME: Emits EOL twice.
+ assert_equal(
+ %i[STRING_BEGIN EMBEXPR_BEGIN CONSTANT EOF EOF],
+ token_types('"#{C')
+ )
+
+ assert_equal(
+ %i[STRING_BEGIN EMBEXPR_BEGIN CONSTANT NEWLINE EOF],
+ token_types('"#{C' + "\n")
+ )
+ end
+
+ def test_lex_heredoc_unterminated
+ code = <<~'RUBY'.strip
+ <<A+B
+ #{C
+ RUBY
+
+ assert_equal(
+ %i[HEREDOC_START EMBEXPR_BEGIN CONSTANT HEREDOC_END PLUS CONSTANT NEWLINE EOF],
+ token_types(code)
+ )
+
+ assert_equal(
+ %i[HEREDOC_START EMBEXPR_BEGIN CONSTANT NEWLINE HEREDOC_END PLUS CONSTANT NEWLINE EOF],
+ token_types(code + "\n")
+ )
+ end
+
+ def token_types(code)
+ Prism.lex(code).value.map { |token, _state| token.type }
+ end
end
end
diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb
index c48b295a49..417730a8a7 100644
--- a/test/prism/locals_test.rb
+++ b/test/prism/locals_test.rb
@@ -13,11 +13,6 @@ return if !defined?(RubyVM::InstructionSequence) || RUBY_VERSION < "3.4.0"
# in comparing the locals because they will be the same.
return if RubyVM::InstructionSequence.compile("").to_a[4][:parser] == :prism
-# In Ruby 3.4.0, the local table for method forwarding changed. But 3.4.0 can
-# refer to the dev version, so while 3.4.0 still isn't released, we need to
-# check if we have a high enough revision.
-return if RubyVM::InstructionSequence.compile("def foo(...); end").to_a[13][2][2][10].length != 1
-
# Omit tests if running on a 32-bit machine because there is a bug with how
# Ruby is handling large ISeqs on 32-bit machines
return if RUBY_PLATFORM =~ /i686/
@@ -29,10 +24,19 @@ module Prism
except = [
# Skip this fixture because it has a different number of locals because
# CRuby is eliminating dead code.
- "whitequark/ruby_bug_10653.txt"
+ "whitequark/ruby_bug_10653.txt",
+
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ "command_method_call_2.txt",
+
+ # https://bugs.ruby-lang.org/issues/21669
+ "4.1/void_value.txt",
+
+ # https://bugs.ruby-lang.org/issues/19107
+ "4.1/trailing_comma_after_method_arguments.txt",
]
- Fixture.each(except: except) do |fixture|
+ Fixture.each_for_current_ruby(except: except) do |fixture|
define_method(fixture.test_name) { assert_locals(fixture) }
end
@@ -147,7 +151,7 @@ module Prism
elsif node.parameters.is_a?(NumberedParametersNode)
# nothing
elsif node.parameters.is_a?(ItParametersNode)
- names << AnonymousLocal
+ names.unshift(AnonymousLocal)
else
params = node.parameters&.parameters
end
@@ -206,7 +210,7 @@ module Prism
end
end
- if params.block
+ if params.block.is_a?(BlockParameterNode)
sorted << (params.block.name || :&)
end
diff --git a/test/prism/magic_comment_test.rb b/test/prism/magic_comment_test.rb
index ab4b5f56e5..7985bae568 100644
--- a/test/prism/magic_comment_test.rb
+++ b/test/prism/magic_comment_test.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative "test_helper"
+require "ripper"
module Prism
class MagicCommentTest < TestCase
@@ -68,6 +69,10 @@ module Prism
assert_magic_encoding(Encoding::US_ASCII, "# -*- foo: bar; encoding: ascii -*-")
end
+ def test_emacs_missing_delimiter
+ assert_magic_encoding(Encoding::US_ASCII, '# -*- \1; encoding: ascii -*-')
+ end
+
def test_coding_whitespace
assert_magic_encoding(Encoding::ASCII_8BIT, "# coding \t \r \v : \t \v \r ascii-8bit")
end
diff --git a/test/prism/newline_offsets_test.rb b/test/prism/newline_offsets_test.rb
index 99b808b1df..bb06876a96 100644
--- a/test/prism/newline_offsets_test.rb
+++ b/test/prism/newline_offsets_test.rb
@@ -8,15 +8,38 @@ module Prism
define_method(fixture.test_name) { assert_newline_offsets(fixture) }
end
+ def test_escape_control_newline
+ # Newlines consumed inside escape sequences like \C-, \c, and \M-
+ # must be tracked in line offsets across all literal types.
+ %w[\\C- \\c \\M-].each do |escape|
+ assert_newline_offsets_for("\"#{escape}\n\"", "#{escape} in string")
+ assert_newline_offsets_for("`#{escape}\n`", "#{escape} in xstring")
+ assert_newline_offsets_for("/#{escape}\n/", "#{escape} in regexp")
+ assert_newline_offsets_for("%Q{#{escape}\n}", "#{escape} in %Q")
+ assert_newline_offsets_for("%W[#{escape}\n]", "#{escape} in %W")
+ assert_newline_offsets_for("<<~H\n#{escape}\n\nH\n", "#{escape} in heredoc")
+ assert_newline_offsets_for("?#{escape}\n", "#{escape} in char literal")
+ end
+
+ # Combined meta + control escapes
+ assert_newline_offsets_for("\"\\M-\\C-\n\"", "\\M-\\C- in string")
+ assert_newline_offsets_for("\"\\M-\\c\n\"", "\\M-\\c in string")
+
+ # \r\n consumed inside escape context
+ assert_newline_offsets_for("\"\\C-\r\n\"", "\\C- with \\r\\n")
+ end
+
private
def assert_newline_offsets(fixture)
- source = fixture.read
+ assert_newline_offsets_for(fixture.read)
+ end
+ def assert_newline_offsets_for(source, message = nil)
expected = [0]
source.b.scan("\n") { expected << $~.offset(0)[0] + 1 }
- assert_equal expected, Prism.parse(source).source.offsets
+ assert_equal expected, Prism.parse(source).source.offsets, message
end
end
end
diff --git a/test/prism/newline_test.rb b/test/prism/newline_test.rb
index fefe9def91..97e698202d 100644
--- a/test/prism/newline_test.rb
+++ b/test/prism/newline_test.rb
@@ -17,7 +17,10 @@ module Prism
result/breadth_first_search_test.rb
result/static_literals_test.rb
result/warnings_test.rb
+ ruby/find_fixtures.rb
+ ruby/find_test.rb
ruby/parser_test.rb
+ ruby/ripper_test.rb
ruby/ruby_parser_test.rb
]
diff --git a/test/prism/ractor_test.rb b/test/prism/ractor_test.rb
new file mode 100644
index 0000000000..0e008ffb08
--- /dev/null
+++ b/test/prism/ractor_test.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+return unless defined?(Ractor) && Process.respond_to?(:fork)
+
+require_relative "test_helper"
+
+module Prism
+ class RactorTest < TestCase
+ def test_version
+ assert_match(/\A\d+\.\d+\.\d+\z/, with_ractor { Prism::VERSION })
+ end
+
+ def test_parse_file
+ assert_equal("Prism::ParseResult", with_ractor(__FILE__) { |filepath| Prism.parse_file(filepath).class })
+ end
+
+ def test_lex_file
+ assert_equal("Prism::LexResult", with_ractor(__FILE__) { |filepath| Prism.lex_file(filepath).class })
+ end
+
+ def test_parse_file_comments
+ assert_equal("Array", with_ractor(__FILE__) { |filepath| Prism.parse_file_comments(filepath).class })
+ end
+
+ def test_parse_lex_file
+ assert_equal("Prism::ParseLexResult", with_ractor(__FILE__) { |filepath| Prism.parse_lex_file(filepath).class })
+ end
+
+ def test_parse_success
+ assert_equal("true", with_ractor("1 + 1") { |source| Prism.parse_success?(source) })
+ end
+
+ def test_parse_failure
+ assert_equal("true", with_ractor("1 +") { |source| Prism.parse_failure?(source) })
+ end
+
+ def test_string_query_local
+ assert_equal("true", with_ractor("foo") { |source| StringQuery.local?(source) })
+ end
+
+ def test_string_query_constant
+ assert_equal("true", with_ractor("FOO") { |source| StringQuery.constant?(source) })
+ end
+
+ def test_string_query_method_name
+ assert_equal("true", with_ractor("foo?") { |source| StringQuery.method_name?(source) })
+ end
+
+ if !ENV["PRISM_BUILD_MINIMAL"]
+ def test_dump_file
+ result = with_ractor(__FILE__) { |filepath| Prism.dump_file(filepath) }
+ assert_operator(result, :start_with?, "PRISM")
+ end
+ end
+
+ private
+
+ # Note that this must be done in a subprocess, otherwise it can mess up
+ # CRuby's test suite.
+ def with_ractor(*arguments, &block)
+ IO.popen("-") do |reader|
+ if reader
+ reader.gets.chomp
+ else
+ ractor = ignore_warnings { Ractor.new(*arguments, &block) }
+
+ # Somewhere in the Ruby 4.0.* series, Ractor#take was removed and
+ # Ractor#value was added.
+ puts(ractor.respond_to?(:value) ? ractor.value : ractor.take)
+ end
+ end
+ end
+ end
+end
diff --git a/test/prism/result/breadth_first_search_test.rb b/test/prism/result/breadth_first_search_test.rb
index e2e043a902..7e7962f172 100644
--- a/test/prism/result/breadth_first_search_test.rb
+++ b/test/prism/result/breadth_first_search_test.rb
@@ -14,5 +14,16 @@ module Prism
refute_nil found
assert_equal 8, found.start_offset
end
+
+ def test_breadth_first_search_all
+ result = Prism.parse("[1 + 2, 2]")
+ found_nodes =
+ result.value.breadth_first_search_all do |node|
+ node.is_a?(IntegerNode)
+ end
+
+ assert_equal 3, found_nodes.size
+ assert_equal 8, found_nodes[0].start_offset
+ end
end
end
diff --git a/test/prism/result/continuable_test.rb b/test/prism/result/continuable_test.rb
new file mode 100644
index 0000000000..3533552167
--- /dev/null
+++ b/test/prism/result/continuable_test.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class ContinuableTest < TestCase
+ def test_valid_input
+ # Valid input is not continuable (nothing to continue).
+ refute_predicate Prism.parse("1 + 1"), :continuable?
+ refute_predicate Prism.parse(""), :continuable?
+ end
+
+ def test_stray_closing_tokens
+ # Stray closing tokens make input non-continuable regardless of what
+ # follows (matches the feature-request examples exactly).
+ refute_predicate Prism.parse("1 + ]"), :continuable?
+ refute_predicate Prism.parse("end.tap do"), :continuable?
+
+ # A mix: stray end plus an unclosed block is not continuable because the
+ # stray end cannot be fixed by appending more input.
+ refute_predicate Prism.parse("end\ntap do"), :continuable?
+ end
+
+ def test_unclosed_constructs
+ # Unclosed constructs are continuable.
+ assert_predicate Prism.parse("1 + ["), :continuable?
+ assert_predicate Prism.parse("tap do"), :continuable?
+ end
+
+ def test_unclosed_keywords
+ assert_predicate Prism.parse("def foo"), :continuable?
+ assert_predicate Prism.parse("class Foo"), :continuable?
+ assert_predicate Prism.parse("module Foo"), :continuable?
+ assert_predicate Prism.parse("if true"), :continuable?
+ assert_predicate Prism.parse("while true"), :continuable?
+ assert_predicate Prism.parse("begin"), :continuable?
+ assert_predicate Prism.parse("for x in [1]"), :continuable?
+ end
+
+ def test_unclosed_delimiters
+ assert_predicate Prism.parse("{"), :continuable?
+ assert_predicate Prism.parse("foo("), :continuable?
+ assert_predicate Prism.parse('"hello'), :continuable?
+ assert_predicate Prism.parse("'hello"), :continuable?
+ assert_predicate Prism.parse("<<~HEREDOC\nhello"), :continuable?
+ end
+
+ def test_trailing_whitespace
+ # Trailing whitespace or newlines should not affect continuability.
+ assert_predicate Prism.parse("class A\n"), :continuable?
+ assert_predicate Prism.parse("def f "), :continuable?
+ assert_predicate Prism.parse("def f\n"), :continuable?
+ assert_predicate Prism.parse("def f\n "), :continuable?
+ assert_predicate Prism.parse("( "), :continuable?
+ assert_predicate Prism.parse("(\n"), :continuable?
+ assert_predicate Prism.parse("1 +\n"), :continuable?
+ end
+
+ def test_incomplete_expressions
+ assert_predicate Prism.parse("-"), :continuable?
+ assert_predicate Prism.parse("[1,"), :continuable?
+ assert_predicate Prism.parse("f arg1,"), :continuable?
+ assert_predicate Prism.parse("def f ="), :continuable?
+ assert_predicate Prism.parse("def $a"), :continuable?
+ assert_predicate Prism.parse("a ="), :continuable?
+ assert_predicate Prism.parse("a,b"), :continuable?
+ end
+
+ def test_modifier_keywords
+ assert_predicate Prism.parse("return if"), :continuable?
+ assert_predicate Prism.parse("return unless"), :continuable?
+ assert_predicate Prism.parse("while"), :continuable?
+ assert_predicate Prism.parse("until"), :continuable?
+ end
+
+ def test_ternary_operator
+ assert_predicate Prism.parse("x ?"), :continuable?
+ assert_predicate Prism.parse("x ? y :"), :continuable?
+ end
+
+ def test_class_with_superclass
+ assert_predicate Prism.parse("class Foo <"), :continuable?
+ end
+
+ def test_keyword_expressions
+ assert_predicate Prism.parse("not"), :continuable?
+ assert_predicate Prism.parse("defined?"), :continuable?
+ assert_predicate Prism.parse("module"), :continuable?
+ end
+
+ def test_for_loops
+ assert_predicate Prism.parse("for"), :continuable?
+ assert_predicate Prism.parse("for x in"), :continuable?
+ end
+
+ def test_pattern_matching
+ assert_predicate Prism.parse("foo => ["), :continuable?
+ assert_predicate Prism.parse("case foo; when"), :continuable?
+ end
+
+ def test_splat_and_block_pass
+ assert_predicate Prism.parse("[*"), :continuable?
+ assert_predicate Prism.parse("f(**"), :continuable?
+ assert_predicate Prism.parse("f(&"), :continuable?
+ end
+
+ def test_default_parameter_value
+ assert_predicate Prism.parse("def f(x ="), :continuable?
+ end
+
+ def test_line_continuation
+ assert_predicate Prism.parse("1 +\\"), :continuable?
+ assert_predicate Prism.parse("\"foo\" \\"), :continuable?
+ end
+
+ def test_embedded_document
+ # Embedded document (=begin) truncated at various points.
+ assert_predicate Prism.parse("=b"), :continuable?
+ assert_predicate Prism.parse("=beg"), :continuable?
+ assert_predicate Prism.parse("=begin"), :continuable?
+ assert_predicate Prism.parse("foo\n=b"), :continuable?
+ end
+ end
+end
diff --git a/test/prism/result/error_recovery_test.rb b/test/prism/result/error_recovery_test.rb
new file mode 100644
index 0000000000..d07c858d1b
--- /dev/null
+++ b/test/prism/result/error_recovery_test.rb
@@ -0,0 +1,237 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class ErrorRecoveryTest < TestCase
+ def test_alias_global_variable_node_old_name_symbol
+ result = Prism.parse("alias $a b")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_kind_of SymbolNode, node.old_name.unexpected
+ end
+
+ def test_alias_global_variable_node_old_name_missing
+ result = Prism.parse("alias $a 42")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_nil node.old_name.unexpected
+ end
+
+ def test_alias_method_node_old_name_global_variable
+ result = Prism.parse("alias a $b")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_kind_of GlobalVariableReadNode, node.old_name.unexpected
+ end
+
+ def test_alias_method_node_old_name_missing
+ result = Prism.parse("alias a 42")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.old_name
+ assert_nil node.old_name.unexpected
+ end
+
+ def test_class_node_constant_path_call
+ result = Prism.parse("class 0.X; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_kind_of CallNode, node.constant_path.unexpected
+ end
+
+ def test_for_node_index_back_reference
+ result = Prism.parse("for $& in a; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_kind_of BackReferenceReadNode, node.index.unexpected
+ end
+
+ def test_for_node_index_numbered_reference
+ result = Prism.parse("for $1 in a; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_kind_of NumberedReferenceReadNode, node.index.unexpected
+ end
+
+ def test_for_node_index_missing
+ result = Prism.parse("for in 1..10; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.index
+ assert_nil node.index.unexpected
+ end
+
+ def test_interpolated_string_node_parts_xstring
+ result = Prism.parse("<<~`FOO` \"bar\"\nls\nFOO\n")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.parts.any? { |part| part.is_a?(ErrorRecoveryNode) && part.unexpected.is_a?(XStringNode) }
+ end
+
+ def test_interpolated_string_node_parts_interpolated_xstring
+ result = Prism.parse("<<~`FOO` \"bar\"\n\#{ls}\nFOO\n")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.parts.any? { |part| part.is_a?(ErrorRecoveryNode) && part.unexpected.is_a?(InterpolatedXStringNode) }
+ end
+
+ def test_module_node_constant_path_def
+ result = Prism.parse("module def foo; end")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_kind_of DefNode, node.constant_path.unexpected
+ end
+
+ def test_module_node_constant_path_missing
+ result = Prism.parse("module Parent module end")
+ refute result.success?
+
+ node = result.value.statements.body.first.body.body.first
+ assert_kind_of ErrorRecoveryNode, node.constant_path
+ assert_nil node.constant_path.unexpected
+ end
+
+ def test_multi_target_node_lefts_back_reference
+ result = Prism.parse("a, (b, $&) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_target_node_lefts_numbered_reference
+ result = Prism.parse("a, (b, $1) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_target_node_rights_back_reference
+ result = Prism.parse("a, (*, $&) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_target_node_rights_numbered_reference
+ result = Prism.parse("a, (*, $1) = z")
+ refute result.success?
+
+ node = result.value.statements.body.first.lefts.last
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_write_node_lefts_back_reference
+ result = Prism.parse("$&, = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_write_node_lefts_numbered_reference
+ result = Prism.parse("$1, = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.lefts.any? { |left| left.is_a?(ErrorRecoveryNode) && left.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_multi_write_node_rights_back_reference
+ result = Prism.parse("*, $& = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(BackReferenceReadNode) }
+ end
+
+ def test_multi_write_node_rights_numbered_reference
+ result = Prism.parse("*, $1 = z")
+ refute result.success?
+
+ node = result.value.statements.body.first
+ assert node.rights.any? { |right| right.is_a?(ErrorRecoveryNode) && right.unexpected.is_a?(NumberedReferenceReadNode) }
+ end
+
+ def test_parameters_node_posts_keyword_rest
+ result = Prism.parse("def f(**kwargs, ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(KeywordRestParameterNode) }
+ end
+
+ def test_parameters_node_posts_no_keywords
+ result = Prism.parse("def f(**nil, ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(NoKeywordsParameterNode) }
+ end
+
+ def test_parameters_node_posts_forwarding
+ result = Prism.parse("def f(..., ...); end")
+ refute result.success?
+
+ node = result.value.statements.body.first.parameters
+ assert node.posts.any? { |post| post.is_a?(ErrorRecoveryNode) && post.unexpected.is_a?(ForwardingParameterNode) }
+ end
+
+ def test_pinned_variable_node_variable_missing
+ result = Prism.parse("foo in ^Bar")
+ refute result.success?
+
+ node = result.value.statements.body.first.pattern
+ assert_kind_of ErrorRecoveryNode, node.variable
+ assert_nil node.variable.unexpected
+ end
+
+ def test_rescue_node_reference_back_reference
+ result = Prism.parse("begin; rescue => $&; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_kind_of BackReferenceReadNode, node.reference.unexpected
+ end
+
+ def test_rescue_node_reference_numbered_reference
+ result = Prism.parse("begin; rescue => $1; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_kind_of NumberedReferenceReadNode, node.reference.unexpected
+ end
+
+ def test_rescue_node_reference_missing
+ result = Prism.parse("begin; rescue =>; end")
+ refute result.success?
+
+ node = result.value.statements.body.first.rescue_clause
+ assert_kind_of ErrorRecoveryNode, node.reference
+ assert_nil node.reference.unexpected
+ end
+ end
+end
diff --git a/test/prism/result/numeric_value_test.rb b/test/prism/result/numeric_value_test.rb
index 5c89230a1f..0207fa6a86 100644
--- a/test/prism/result/numeric_value_test.rb
+++ b/test/prism/result/numeric_value_test.rb
@@ -6,16 +6,27 @@ module Prism
class NumericValueTest < TestCase
def test_numeric_value
assert_equal 123, Prism.parse_statement("123").value
+ assert_equal 123, Prism.parse_statement("1_23").value
assert_equal 3.14, Prism.parse_statement("3.14").value
+ assert_equal 3.14, Prism.parse_statement("3.1_4").value
assert_equal 42i, Prism.parse_statement("42i").value
+ assert_equal 42i, Prism.parse_statement("4_2i").value
assert_equal 42.1ri, Prism.parse_statement("42.1ri").value
+ assert_equal 42.1ri, Prism.parse_statement("42.1_0ri").value
assert_equal 3.14i, Prism.parse_statement("3.14i").value
+ assert_equal 3.14i, Prism.parse_statement("3.1_4i").value
assert_equal 42r, Prism.parse_statement("42r").value
+ assert_equal 42r, Prism.parse_statement("4_2r").value
assert_equal 0.5r, Prism.parse_statement("0.5r").value
+ assert_equal 0.5r, Prism.parse_statement("0.5_0r").value
assert_equal 42ri, Prism.parse_statement("42ri").value
+ assert_equal 42ri, Prism.parse_statement("4_2ri").value
assert_equal 0.5ri, Prism.parse_statement("0.5ri").value
+ assert_equal 0.5ri, Prism.parse_statement("0.5_0ri").value
assert_equal 0xFFr, Prism.parse_statement("0xFFr").value
+ assert_equal 0xFFr, Prism.parse_statement("0xF_Fr").value
assert_equal 0xFFri, Prism.parse_statement("0xFFri").value
+ assert_equal 0xFFri, Prism.parse_statement("0xF_Fri").value
end
end
end
diff --git a/test/prism/result/overlap_test.rb b/test/prism/result/overlap_test.rb
index 155bc870d3..d605eeca44 100644
--- a/test/prism/result/overlap_test.rb
+++ b/test/prism/result/overlap_test.rb
@@ -33,8 +33,13 @@ module Prism
queue << child
if compare
- assert_operator current.location.start_offset, :<=, child.location.start_offset
- assert_operator current.location.end_offset, :>=, child.location.end_offset
+ assert_operator current.location.start_offset, :<=, child.location.start_offset, -> {
+ "[#{fixture.full_path}] Parent node #{current.class} at #{current.location} does not start before child node #{child.class} at #{child.location}"
+ }
+
+ assert_operator current.location.end_offset, :>=, child.location.end_offset, -> {
+ "[#{fixture.full_path}] Parent node #{current.class} at #{current.location} does not end after child node #{child.class} at #{child.location}"
+ }
end
end
end
diff --git a/test/prism/result/source_location_test.rb b/test/prism/result/source_location_test.rb
index 7bdc707658..a8d27b95a8 100644
--- a/test/prism/result/source_location_test.rb
+++ b/test/prism/result/source_location_test.rb
@@ -13,7 +13,7 @@ module Prism
end
def test_AlternationPatternNode
- assert_location(AlternationPatternNode, "foo => bar | baz", 7...16, &:pattern)
+ assert_location(AlternationPatternNode, "foo => 0 | 1", 7...12, &:pattern)
end
def test_AndNode
@@ -650,6 +650,10 @@ module Prism
assert_location(NilNode, "nil")
end
+ def test_NoBlockParameterNode
+ assert_location(NoBlockParameterNode, "def foo(&nil); end", 8...12) { |node| node.parameters.block }
+ end
+
def test_NoKeywordsParameterNode
assert_location(NoKeywordsParameterNode, "def foo(**nil); end", 8...13) { |node| node.parameters.keyword_rest }
end
@@ -920,7 +924,7 @@ module Prism
end
def test_all_tested
- expected = Prism.constants.grep(/.Node$/).sort - %i[MissingNode ProgramNode]
+ expected = Prism.constants.grep(/.Node$/).sort - %i[ErrorRecoveryNode ProgramNode]
actual = SourceLocationTest.instance_methods(false).grep(/.Node$/).map { |name| name[5..].to_sym }.sort
assert_equal expected, actual
end
@@ -935,16 +939,16 @@ module Prism
node = yield node if block_given?
if expected.begin == 0
- assert_equal 0, node.location.start_column
+ assert_equal 0, node.location.start_column, "#{kind} start_column"
end
if expected.end == source.length
- assert_equal source.split("\n").last.length, node.location.end_column
+ assert_equal source.split("\n").last.length, node.location.end_column, "#{kind} end_column"
end
assert_kind_of kind, node
- assert_equal expected.begin, node.location.start_offset
- assert_equal expected.end, node.location.end_offset
+ assert_equal expected.begin, node.location.start_offset, "#{kind} start_offset"
+ assert_equal expected.end, node.location.end_offset, "#{kind} end_offset"
end
end
end
diff --git a/test/prism/result/warnings_test.rb b/test/prism/result/warnings_test.rb
index 04542dbada..27f1119b98 100644
--- a/test/prism/result/warnings_test.rb
+++ b/test/prism/result/warnings_test.rb
@@ -230,6 +230,8 @@ module Prism
refute_warning("foo = 1", compare: false, command_line: "e")
refute_warning("foo = 1", compare: false, scopes: [[]])
+ refute_warning("foo(bar = 1)")
+
assert_warning("def foo; bar = 1; end", "unused")
assert_warning("def foo; bar, = 1; end", "unused")
@@ -263,6 +265,23 @@ module Prism
refute_warning("def foo; bar = 1; end", line: -2, compare: false)
end
+ def test_unused_local_variable_or_assign_with_begin_node
+ assert_warning(<<~RUBY, "assigned but unused variable - foo", compare: false)
+ var ||= begin
+ foo = bar
+ baz
+ end
+ RUBY
+
+ assert_warning(<<~RUBY, "assigned but unused variable - foo", compare: false)
+ foo = false
+ var ||= begin
+ foo = true
+ bar
+ end
+ RUBY
+ end
+
def test_void_statements
assert_warning("foo = 1; foo", "a variable in void")
assert_warning("@foo", "a variable in void")
@@ -339,7 +358,7 @@ module Prism
assert_warning("tap { redo; foo }", "statement not reached")
end
- if RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince/i)
+ if windows?
def test_shebang_ending_with_carriage_return
refute_warning("#!ruby\r\np(123)\n", compare: false)
end
diff --git a/test/prism/ruby/dispatcher_test.rb b/test/prism/ruby/dispatcher_test.rb
index 1b6d7f4117..83eb29e1f3 100644
--- a/test/prism/ruby/dispatcher_test.rb
+++ b/test/prism/ruby/dispatcher_test.rb
@@ -25,9 +25,12 @@ module Prism
end
def test_dispatching_events
- listener = TestListener.new
+ listener_manual = TestListener.new
+ listener_public = TestListener.new
+
dispatcher = Dispatcher.new
- dispatcher.register(listener, :on_call_node_enter, :on_call_node_leave, :on_integer_node_enter)
+ dispatcher.register(listener_manual, :on_call_node_enter, :on_call_node_leave, :on_integer_node_enter)
+ dispatcher.register_public_methods(listener_public)
root = Prism.parse(<<~RUBY).value
def foo
@@ -36,11 +39,17 @@ module Prism
RUBY
dispatcher.dispatch(root)
- assert_equal([:on_call_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_call_node_leave], listener.events_received)
- listener.events_received.clear
+ [listener_manual, listener_public].each do |listener|
+ assert_equal([:on_call_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_integer_node_enter, :on_call_node_leave], listener.events_received)
+ listener.events_received.clear
+ end
+
dispatcher.dispatch_once(root.statements.body.first.body.body.first)
- assert_equal([:on_call_node_enter, :on_call_node_leave], listener.events_received)
+
+ [listener_manual, listener_public].each do |listener|
+ assert_equal([:on_call_node_enter, :on_call_node_leave], listener.events_received)
+ end
end
end
end
diff --git a/test/prism/ruby/find_fixtures.rb b/test/prism/ruby/find_fixtures.rb
new file mode 100644
index 0000000000..c1bef0d0e6
--- /dev/null
+++ b/test/prism/ruby/find_fixtures.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+# Test fixtures for Prism.find. These must be in a separate file because
+# source_location returns the file path and Prism.find re-parses the file.
+
+module Prism
+ module FindFixtures
+ module Methods
+ def simple_method
+ 42
+ end
+
+ def method_with_params(a, b, c)
+ a + b + c
+ end
+
+ def method_with_block(&block)
+ block.call
+ end
+
+ def self.singleton_method_fixture
+ :singleton
+ end
+
+ def été
+ :utf8
+ end
+
+ def inline_method; :inline; end
+ end
+
+ module Procs
+ SIMPLE_PROC = proc { 42 }
+ SIMPLE_LAMBDA = ->(x) { x * 2 }
+ MULTI_LINE_LAMBDA = lambda do |x|
+ x + 1
+ end
+ DO_BLOCK_PROC = proc do |x|
+ x - 1
+ end
+ end
+
+ module DefineMethod
+ define_method(:dynamic) { |x| x + 1 }
+ end
+
+ module ForLoop
+ for_proc = nil
+ o = Object.new
+ def o.each(&block) = block.call(block)
+ for for_proc in o; end
+ FOR_PROC = for_proc
+ end
+
+ module MultipleOnLine
+ def self.first; end; def self.second; end
+ end
+
+ module Errors
+ def self.divide(a, b)
+ a / b
+ end
+
+ def self.call_undefined
+ undefined_method_call
+ end
+ end
+ end
+end
diff --git a/test/prism/ruby/find_test.rb b/test/prism/ruby/find_test.rb
new file mode 100644
index 0000000000..5b59113d30
--- /dev/null
+++ b/test/prism/ruby/find_test.rb
@@ -0,0 +1,242 @@
+# frozen_string_literal: true
+
+return if RUBY_ENGINE == "ruby" && RUBY_VERSION < "3.4"
+return if defined?(RubyVM::InstructionSequence) && RubyVM::InstructionSequence.compile("").to_a[4][:parser] != :prism
+
+require_relative "../test_helper"
+require_relative "find_fixtures"
+
+module Prism
+ class FindTest < TestCase
+ Fixtures = FindFixtures
+ FIXTURES_PATH = File.expand_path("find_fixtures.rb", __dir__)
+
+ # === Method / UnboundMethod tests ===
+
+ def test_simple_method
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:simple_method)), :simple_method
+ end
+
+ def test_method_with_params
+ node = Prism.find(Fixtures::Methods.instance_method(:method_with_params))
+ assert_def_node node, :method_with_params
+ assert_equal 3, node.parameters.requireds.length
+ end
+
+ def test_method_with_block_param
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:method_with_block)), :method_with_block
+ end
+
+ def test_singleton_method
+ assert_def_node Prism.find(Fixtures::Methods.method(:singleton_method_fixture)), :singleton_method_fixture
+ end
+
+ def test_utf8_method_name
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:été)), :été
+ end
+
+ def test_inline_method
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:inline_method)), :inline_method
+ end
+
+ def test_bound_method
+ obj = Object.new
+ obj.extend(Fixtures::Methods)
+ assert_def_node Prism.find(obj.method(:simple_method)), :simple_method
+ end
+
+ # === Proc / Lambda tests ===
+
+ def test_simple_proc
+ assert_not_nil Prism.find(Fixtures::Procs::SIMPLE_PROC)
+ end
+
+ def test_simple_lambda
+ assert_not_nil Prism.find(Fixtures::Procs::SIMPLE_LAMBDA)
+ end
+
+ def test_multi_line_lambda
+ assert_not_nil Prism.find(Fixtures::Procs::MULTI_LINE_LAMBDA)
+ end
+
+ def test_do_block_proc
+ assert_not_nil Prism.find(Fixtures::Procs::DO_BLOCK_PROC)
+ end
+
+ # === define_method tests ===
+
+ def test_define_method
+ assert_not_nil Prism.find(Fixtures::DefineMethod.instance_method(:dynamic))
+ end
+
+ def test_define_method_bound
+ obj = Object.new
+ obj.extend(Fixtures::DefineMethod)
+ assert_not_nil Prism.find(obj.method(:dynamic))
+ end
+
+ # === for loop test ===
+
+ def test_for_loop_proc
+ node = Prism.find(Fixtures::ForLoop::FOR_PROC)
+ assert_instance_of ForNode, node
+ end
+
+ # === Thread::Backtrace::Location tests ===
+
+ def test_backtrace_location_zero_division
+ location = zero_division_location
+ assert_not_nil location, "could not find backtrace location in fixtures file"
+ assert_not_nil Prism.find(location)
+ end
+
+ def test_backtrace_location_name_error
+ location = begin
+ Fixtures::Errors.call_undefined
+ rescue NameError => e
+ fixture_backtrace_location(e)
+ end
+
+ assert_not_nil location, "could not find backtrace location in fixtures file"
+ assert_not_nil Prism.find(location)
+ end
+
+ def test_backtrace_location_from_caller
+ # caller_locations returns locations for the current call stack
+ location = caller_locations(0, 1).first
+ node = Prism.find(location)
+ assert_not_nil node
+ end
+
+ def test_backtrace_location_eval_returns_nil
+ location = begin
+ eval("raise 'eval error'")
+ rescue RuntimeError => e
+ e.backtrace_locations.find { |loc| loc.path == "(eval)" || loc.label&.include?("eval") }
+ end
+
+ # eval locations have no file on disk
+ assert_nil Prism.find(location) if location
+ end
+
+ # === Edge cases ===
+
+ def test_nil_source_location
+ # Built-in methods have nil source_location
+ assert_nil Prism.find(method(:puts))
+ end
+
+ def test_argument_error_on_wrong_type
+ assert_raise(ArgumentError) { Prism.find("not a callable") }
+ assert_raise(ArgumentError) { Prism.find(42) }
+ assert_raise(ArgumentError) { Prism.find(nil) }
+ end
+
+ def test_eval_returns_nil
+ # eval'd code has no file on disk
+ m = eval("proc { 1 }")
+ assert_nil Prism.find(m)
+ end
+
+ def test_multiple_methods_on_same_line
+ assert_def_node Prism.find(Fixtures::MultipleOnLine.method(:first)), :first
+ assert_def_node Prism.find(Fixtures::MultipleOnLine.method(:second)), :second
+ end
+
+ # === Fallback (line-based) tests via rubyvm: false ===
+
+ def test_fallback_simple_method
+ assert_def_node Prism.find(Fixtures::Methods.instance_method(:simple_method), rubyvm: false), :simple_method
+ end
+
+ def test_fallback_singleton_method
+ assert_def_node Prism.find(Fixtures::Methods.method(:singleton_method_fixture), rubyvm: false), :singleton_method_fixture
+ end
+
+ def test_fallback_lambda
+ node = Prism.find(Fixtures::Procs::SIMPLE_LAMBDA, rubyvm: false)
+ assert_instance_of LambdaNode, node
+ end
+
+ def test_fallback_proc
+ node = Prism.find(Fixtures::Procs::SIMPLE_PROC, rubyvm: false)
+ assert_instance_of CallNode, node
+ assert node.block.is_a?(BlockNode)
+ end
+
+ def test_fallback_define_method
+ node = Prism.find(Fixtures::DefineMethod.instance_method(:dynamic), rubyvm: false)
+ assert_instance_of CallNode, node
+ assert node.block.is_a?(BlockNode)
+ end
+
+ def test_fallback_for_loop
+ node = Prism.find(Fixtures::ForLoop::FOR_PROC, rubyvm: false)
+ assert_instance_of ForNode, node
+ end
+
+ def test_fallback_backtrace_location
+ location = zero_division_location
+ assert_not_nil location
+ node = Prism.find(location, rubyvm: false)
+ assert_not_nil node
+ assert_equal location.lineno, node.location.start_line
+ end
+
+ # === Node identity with node_id (CRuby only) ===
+
+ if defined?(RubyVM::InstructionSequence)
+ def test_node_id_matches_iseq
+ m = Fixtures::Methods.instance_method(:simple_method)
+ node = Prism.find(m)
+ assert_equal node_id_of(m), node.node_id
+ end
+
+ def test_node_id_for_lambda
+ node = Prism.find(Fixtures::Procs::SIMPLE_LAMBDA)
+ assert_equal node_id_of(Fixtures::Procs::SIMPLE_LAMBDA), node.node_id
+ end
+
+ def test_node_id_for_proc
+ node = Prism.find(Fixtures::Procs::SIMPLE_PROC)
+ assert_equal node_id_of(Fixtures::Procs::SIMPLE_PROC), node.node_id
+ end
+
+ def test_node_id_for_define_method
+ m = Fixtures::DefineMethod.instance_method(:dynamic)
+ node = Prism.find(m)
+ assert_equal node_id_of(m), node.node_id
+ end
+
+ def test_node_id_for_backtrace_location
+ location = zero_division_location
+ assert_not_nil location
+ expected_node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(location)
+
+ node = Prism.find(location)
+ assert_equal expected_node_id, node.node_id
+ end
+ end
+
+ private
+
+ def assert_def_node(node, expected_name)
+ assert_instance_of DefNode, node
+ assert_equal expected_name, node.name
+ end
+
+ def fixture_backtrace_location(exception)
+ exception.backtrace_locations.find { |loc| loc.path == FIXTURES_PATH }
+ end
+
+ def zero_division_location
+ Fixtures::Errors.divide(1, 0)
+ rescue ZeroDivisionError => e
+ fixture_backtrace_location(e)
+ end
+
+ def node_id_of(callable)
+ RubyVM::InstructionSequence.of(callable).to_a[4][:node_id]
+ end
+ end
+end
diff --git a/test/prism/ruby/location_test.rb b/test/prism/ruby/location_test.rb
index 33f844243c..12c4258cde 100644
--- a/test/prism/ruby/location_test.rb
+++ b/test/prism/ruby/location_test.rb
@@ -13,19 +13,22 @@ module Prism
assert_equal 0, joined.start_offset
assert_equal 10, joined.length
- assert_raise(RuntimeError, "Incompatible locations") do
+ e = assert_raise(RuntimeError) do
argument.location.join(receiver.location)
end
+ assert_equal "Incompatible locations", e.message
other_argument = Prism.parse_statement("1234 + 567").arguments.arguments.first
- assert_raise(RuntimeError, "Incompatible sources") do
+ e = assert_raise(RuntimeError) do
other_argument.location.join(receiver.location)
end
+ assert_equal "Incompatible sources", e.message
- assert_raise(RuntimeError, "Incompatible sources") do
+ e = assert_raise(RuntimeError) do
receiver.location.join(other_argument.location)
end
+ assert_equal "Incompatible sources", e.message
end
def test_character_offsets
@@ -70,7 +73,7 @@ module Prism
assert_equal 0, location.start_code_units_offset(Encoding::UTF_16LE)
assert_equal 0, location.start_code_units_offset(Encoding::UTF_32LE)
- assert_equal 1, location.end_code_units_offset(Encoding::UTF_8)
+ assert_equal 4, location.end_code_units_offset(Encoding::UTF_8)
assert_equal 2, location.end_code_units_offset(Encoding::UTF_16LE)
assert_equal 1, location.end_code_units_offset(Encoding::UTF_32LE)
@@ -78,37 +81,37 @@ module Prism
assert_equal 0, location.start_code_units_column(Encoding::UTF_16LE)
assert_equal 0, location.start_code_units_column(Encoding::UTF_32LE)
- assert_equal 1, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 4, location.end_code_units_column(Encoding::UTF_8)
assert_equal 2, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 1, location.end_code_units_column(Encoding::UTF_32LE)
# second 😀
location = program.statements.body.first.arguments.arguments.first.location
- assert_equal 4, location.start_code_units_offset(Encoding::UTF_8)
+ assert_equal 7, location.start_code_units_offset(Encoding::UTF_8)
assert_equal 5, location.start_code_units_offset(Encoding::UTF_16LE)
assert_equal 4, location.start_code_units_offset(Encoding::UTF_32LE)
- assert_equal 5, location.end_code_units_offset(Encoding::UTF_8)
+ assert_equal 11, location.end_code_units_offset(Encoding::UTF_8)
assert_equal 7, location.end_code_units_offset(Encoding::UTF_16LE)
assert_equal 5, location.end_code_units_offset(Encoding::UTF_32LE)
- assert_equal 4, location.start_code_units_column(Encoding::UTF_8)
+ assert_equal 7, location.start_code_units_column(Encoding::UTF_8)
assert_equal 5, location.start_code_units_column(Encoding::UTF_16LE)
assert_equal 4, location.start_code_units_column(Encoding::UTF_32LE)
- assert_equal 5, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 11, location.end_code_units_column(Encoding::UTF_8)
assert_equal 7, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 5, location.end_code_units_column(Encoding::UTF_32LE)
# first 😍
location = program.statements.body.last.name_loc
- assert_equal 6, location.start_code_units_offset(Encoding::UTF_8)
+ assert_equal 12, location.start_code_units_offset(Encoding::UTF_8)
assert_equal 8, location.start_code_units_offset(Encoding::UTF_16LE)
assert_equal 6, location.start_code_units_offset(Encoding::UTF_32LE)
- assert_equal 7, location.end_code_units_offset(Encoding::UTF_8)
+ assert_equal 16, location.end_code_units_offset(Encoding::UTF_8)
assert_equal 10, location.end_code_units_offset(Encoding::UTF_16LE)
assert_equal 7, location.end_code_units_offset(Encoding::UTF_32LE)
@@ -116,26 +119,26 @@ module Prism
assert_equal 0, location.start_code_units_column(Encoding::UTF_16LE)
assert_equal 0, location.start_code_units_column(Encoding::UTF_32LE)
- assert_equal 1, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 4, location.end_code_units_column(Encoding::UTF_8)
assert_equal 2, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 1, location.end_code_units_column(Encoding::UTF_32LE)
# second 😍
location = program.statements.body.last.value.location
- assert_equal 12, location.start_code_units_offset(Encoding::UTF_8)
+ assert_equal 21, location.start_code_units_offset(Encoding::UTF_8)
assert_equal 15, location.start_code_units_offset(Encoding::UTF_16LE)
assert_equal 12, location.start_code_units_offset(Encoding::UTF_32LE)
- assert_equal 13, location.end_code_units_offset(Encoding::UTF_8)
+ assert_equal 25, location.end_code_units_offset(Encoding::UTF_8)
assert_equal 17, location.end_code_units_offset(Encoding::UTF_16LE)
assert_equal 13, location.end_code_units_offset(Encoding::UTF_32LE)
- assert_equal 6, location.start_code_units_column(Encoding::UTF_8)
+ assert_equal 9, location.start_code_units_column(Encoding::UTF_8)
assert_equal 7, location.start_code_units_column(Encoding::UTF_16LE)
assert_equal 6, location.start_code_units_column(Encoding::UTF_32LE)
- assert_equal 7, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 13, location.end_code_units_column(Encoding::UTF_8)
assert_equal 9, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 7, location.end_code_units_column(Encoding::UTF_32LE)
end
@@ -154,7 +157,7 @@ module Prism
assert_equal 0, location.cached_start_code_units_offset(utf16_cache)
assert_equal 0, location.cached_start_code_units_offset(utf32_cache)
- assert_equal 1, location.cached_end_code_units_offset(utf8_cache)
+ assert_equal 4, location.cached_end_code_units_offset(utf8_cache)
assert_equal 2, location.cached_end_code_units_offset(utf16_cache)
assert_equal 1, location.cached_end_code_units_offset(utf32_cache)
@@ -162,26 +165,26 @@ module Prism
assert_equal 0, location.cached_start_code_units_column(utf16_cache)
assert_equal 0, location.cached_start_code_units_column(utf32_cache)
- assert_equal 1, location.cached_end_code_units_column(utf8_cache)
+ assert_equal 4, location.cached_end_code_units_column(utf8_cache)
assert_equal 2, location.cached_end_code_units_column(utf16_cache)
assert_equal 1, location.cached_end_code_units_column(utf32_cache)
# second 😀
location = result.value.statements.body.first.arguments.arguments.first.location
- assert_equal 4, location.cached_start_code_units_offset(utf8_cache)
+ assert_equal 7, location.cached_start_code_units_offset(utf8_cache)
assert_equal 5, location.cached_start_code_units_offset(utf16_cache)
assert_equal 4, location.cached_start_code_units_offset(utf32_cache)
- assert_equal 5, location.cached_end_code_units_offset(utf8_cache)
+ assert_equal 11, location.cached_end_code_units_offset(utf8_cache)
assert_equal 7, location.cached_end_code_units_offset(utf16_cache)
assert_equal 5, location.cached_end_code_units_offset(utf32_cache)
- assert_equal 4, location.cached_start_code_units_column(utf8_cache)
+ assert_equal 7, location.cached_start_code_units_column(utf8_cache)
assert_equal 5, location.cached_start_code_units_column(utf16_cache)
assert_equal 4, location.cached_start_code_units_column(utf32_cache)
- assert_equal 5, location.cached_end_code_units_column(utf8_cache)
+ assert_equal 11, location.cached_end_code_units_column(utf8_cache)
assert_equal 7, location.cached_end_code_units_column(utf16_cache)
assert_equal 5, location.cached_end_code_units_column(utf32_cache)
end
@@ -197,7 +200,7 @@ module Prism
assert_equal "😀".b.to_sym, receiver.name
location = receiver.location
- assert_equal 1, location.end_code_units_column(Encoding::UTF_8)
+ assert_equal 4, location.end_code_units_column(Encoding::UTF_8)
assert_equal 2, location.end_code_units_column(Encoding::UTF_16LE)
assert_equal 1, location.end_code_units_column(Encoding::UTF_32LE)
end
diff --git a/test/prism/ruby/parameters_signature_test.rb b/test/prism/ruby/parameters_signature_test.rb
index 9256bcc070..1ca2b144a9 100644
--- a/test/prism/ruby/parameters_signature_test.rb
+++ b/test/prism/ruby/parameters_signature_test.rb
@@ -50,13 +50,19 @@ module Prism
assert_parameters([[:nokey]], "**nil")
end
+ def test_noblock
+ # FIXME: `compare: RUBY_VERSION >= "4.1"` once builds are available
+ assert_parameters([[:noblock]], "&nil", compare: false)
+ end
+
def test_keyrest_anonymous
assert_parameters([[:keyrest, :**]], "**")
end
- def test_key_ordering
- omit("TruffleRuby returns keys in order they were declared") if RUBY_ENGINE == "truffleruby"
- assert_parameters([[:keyreq, :a], [:keyreq, :b], [:key, :c], [:key, :d]], "a:, c: 1, b:, d: 2")
+ if RUBY_ENGINE == "ruby"
+ def test_key_ordering
+ assert_parameters([[:keyreq, :a], [:keyreq, :b], [:key, :c], [:key, :d]], "a:, c: 1, b:, d: 2")
+ end
end
def test_block
@@ -71,12 +77,20 @@ module Prism
assert_parameters([[:rest, :*], [:keyrest, :**], [:block, :&]], "...")
end
+ def test_invalid_syntax
+ e = assert_raise(RuntimeError) do
+ Prism.parse_statement("def f(**nil, ...); end").parameters.signature
+ end
+ assert_equal("Invalid syntax", e.message)
+ end
+
private
- def assert_parameters(expected, source)
+ def assert_parameters(expected, source, compare: true)
# Compare against our expectation.
assert_equal(expected, signature(source))
+ return unless compare
# Compare against Ruby's expectation.
object = Object.new
eval("def object.m(#{source}); end")
diff --git a/test/prism/ruby/parser_test.rb b/test/prism/ruby/parser_test.rb
index cff36f56b0..ad9fa0c92c 100644
--- a/test/prism/ruby/parser_test.rb
+++ b/test/prism/ruby/parser_test.rb
@@ -5,7 +5,6 @@ require_relative "../test_helper"
begin
verbose, $VERBOSE = $VERBOSE, nil
require "parser/ruby33"
- require "prism/translation/parser33"
rescue LoadError
# In CRuby's CI, we're not going to test against the parser gem because we
# don't want to have to install it. So in this case we'll just skip this test.
@@ -16,6 +15,7 @@ end
# First, opt in to every AST feature.
Parser::Builders::Default.modernize
+Prism::Translation::Parser::Builder.modernize
# The parser gem rejects some strings that would most likely lead to errors
# in consumers due to encoding problems. RuboCop however monkey-patches this
@@ -54,6 +54,22 @@ Parser::AST::Node.prepend(
module Prism
class ParserTest < TestCase
+ # These files contain code with valid syntax that can't be parsed.
+ skip_syntax_error = [
+ # alias/undef with %s(abc) symbol literal
+ "alias.txt",
+ "seattlerb/bug_215.txt",
+
+ # %Q with newline delimiter and heredoc interpolation
+ "heredoc_percent_q_newline_delimiter.txt",
+
+ # 1.. && 2
+ "ranges.txt",
+
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ "command_method_call_2.txt",
+ ]
+
# These files contain code that is being parsed incorrectly by the parser
# gem, and therefore we don't want to compare against our translation.
skip_incorrect = [
@@ -80,31 +96,22 @@ module Prism
"seattlerb/heredoc_with_extra_carriage_returns_windows.txt",
"seattlerb/heredoc_with_only_carriage_returns_windows.txt",
"seattlerb/heredoc_with_only_carriage_returns.txt",
- ]
- # These files are either failing to parse or failing to translate, so we'll
- # skip them for now.
- skip_all = skip_incorrect | [
+ # https://github.com/whitequark/parser/issues/1026
+ # Regex with \c escape
"unescaping.txt",
- "seattlerb/pctW_lineno.txt",
"seattlerb/regexp_esc_C_slash.txt",
- "unparser/corpus/literal/literal.txt",
- "whitequark/parser_slash_slash_n_escaping_in_literals.txt",
- ]
- # Not sure why these files are failing on JRuby, but skipping them for now.
- if RUBY_ENGINE == "jruby"
- skip_all.push("emoji_method_calls.txt", "symbols.txt")
- end
+ # https://github.com/whitequark/parser/issues/1084
+ "unary_method_calls.txt",
+ ]
# These files are failing to translate their lexer output into the lexer
# output expected by the parser gem, so we'll skip them for now.
skip_tokens = [
"dash_heredocs.txt",
"embdoc_no_newline_at_end.txt",
- "heredocs_with_ignored_newlines.txt",
"methods.txt",
- "strings.txt",
"seattlerb/bug169.txt",
"seattlerb/case_in.txt",
"seattlerb/difficult4__leading_dots2.txt",
@@ -114,9 +121,9 @@ module Prism
"seattlerb/parse_line_heredoc.txt",
"seattlerb/pct_w_heredoc_interp_nested.txt",
"seattlerb/required_kwarg_no_value.txt",
- "seattlerb/slashy_newlines_within_string.txt",
"seattlerb/TestRubyParserShared.txt",
"unparser/corpus/literal/assignment.txt",
+ "unparser/corpus/literal/literal.txt",
"whitequark/args.txt",
"whitequark/beginless_erange_after_newline.txt",
"whitequark/beginless_irange_after_newline.txt",
@@ -125,28 +132,85 @@ module Prism
"whitequark/lbrace_arg_after_command_args.txt",
"whitequark/multiple_pattern_matches.txt",
"whitequark/newline_in_hash_argument.txt",
- "whitequark/parser_bug_640.txt",
"whitequark/pattern_matching_expr_in_paren.txt",
"whitequark/pattern_matching_hash.txt",
- "whitequark/pin_expr.txt",
"whitequark/ruby_bug_14690.txt",
"whitequark/ruby_bug_9669.txt",
- "whitequark/slash_newline_in_heredocs.txt",
"whitequark/space_args_arg_block.txt",
"whitequark/space_args_block.txt"
]
- Fixture.each do |fixture|
+ Fixture.each_for_version(except: skip_syntax_error, version: "3.3") do |fixture|
define_method(fixture.test_name) do
assert_equal_parses(
fixture,
- compare_asts: !skip_all.include?(fixture.path),
+ compare_asts: !skip_incorrect.include?(fixture.path),
compare_tokens: !skip_tokens.include?(fixture.path),
compare_comments: fixture.path != "embdoc_no_newline_at_end.txt"
)
end
end
+ def test_non_prism_builder_class_deprecated
+ warnings = capture_warnings { Prism::Translation::Parser33.new(Parser::Builders::Default.new) }
+
+ assert_include(warnings, "#{__FILE__}:#{__LINE__ - 2}")
+ assert_include(warnings, "is not a `Prism::Translation::Parser::Builder` subclass")
+
+ warnings = capture_warnings { Prism::Translation::Parser33.new }
+ assert_empty(warnings)
+ end
+
+ if RUBY_VERSION >= "3.3"
+ def test_current_parser_for_current_ruby
+ major, minor = CURRENT_MAJOR_MINOR.split(".")
+ # Let's just hope there never is a Ruby 3.10 or similar
+ expected = major.to_i * 10 + minor.to_i
+ assert_equal(expected, Translation::ParserCurrent.new.version)
+ end
+ end
+
+ def test_invalid_syntax
+ code = <<~RUBY
+ foo do
+ case bar
+ when
+ end
+ end
+ RUBY
+ buffer = Parser::Source::Buffer.new("(string)")
+ buffer.source = code
+
+ parser = Prism::Translation::Parser33.new
+ parser.diagnostics.all_errors_are_fatal = true
+ assert_raise(Parser::SyntaxError) { parser.tokenize(buffer) }
+ end
+
+ def test_it_block_parameter_syntax
+ assert_new_syntax("3.4/it.txt", Prism::Translation::Parser34) do
+ s(:begin,
+ s(:itblock,
+ s(:send, nil, :x), :it,
+ s(:lvar, :it)),
+ s(:itblock,
+ s(:lambda), :it,
+ s(:lvar, :it)))
+ end
+ end
+
+ def test_nil_block_parameter_syntax
+ assert_new_syntax("4.1/noblock.txt", Prism::Translation::Parser41) do
+ s(:begin,
+ s(:def, :foo,
+ s(:args,
+ s(:blocknilarg)), nil),
+ s(:block,
+ s(:lambda),
+ s(:args,
+ s(:blocknilarg)), nil))
+ end
+ end
+
private
def assert_equal_parses(fixture, compare_asts: true, compare_tokens: true, compare_comments: true)
@@ -158,17 +222,13 @@ module Prism
parser.diagnostics.all_errors_are_fatal = true
expected_ast, expected_comments, expected_tokens =
- begin
- ignore_warnings { parser.tokenize(buffer) }
- rescue ArgumentError, Parser::SyntaxError
- return
- end
+ ignore_warnings { parser.tokenize(buffer) }
actual_ast, actual_comments, actual_tokens =
ignore_warnings { Prism::Translation::Parser33.new.tokenize(buffer) }
if expected_ast == actual_ast
- if !compare_asts
+ if !compare_asts && !Fixture.custom_base_path?
puts "#{fixture.path} is now passing"
end
@@ -179,7 +239,7 @@ module Prism
rescue Test::Unit::AssertionFailedError
raise if compare_tokens
else
- puts "#{fixture.path} is now passing" if !compare_tokens
+ puts "#{fixture.path} is now passing" if !compare_tokens && !Fixture.custom_base_path?
end
assert_equal_comments(expected_comments, actual_comments) if compare_comments
@@ -245,5 +305,19 @@ module Prism
"actual: #{actual_comments.inspect}"
}
end
+
+ def assert_new_syntax(path, parser, &sexp)
+ fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures", path)
+
+ buffer = Parser::Source::Buffer.new(fixture_path)
+ buffer.source = fixture_path.read
+ actual_ast = parser.new.tokenize(buffer)[0]
+
+ assert_equal(parse_sexp(&sexp), actual_ast.to_sexp)
+ end
+
+ def parse_sexp(&block)
+ Class.new { extend AST::Sexp }.instance_eval(&block).to_sexp
+ end
end
end
diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb
index 7ed32ed216..4fff630561 100644
--- a/test/prism/ruby/ripper_test.rb
+++ b/test/prism/ruby/ripper_test.rb
@@ -1,37 +1,60 @@
# frozen_string_literal: true
-return if RUBY_VERSION < "3.3"
+return if RUBY_VERSION < "3.3" || RUBY_ENGINE != "ruby"
require_relative "../test_helper"
+require "ripper"
module Prism
class RipperTest < TestCase
# Skip these tests that Ripper is reporting the wrong results for.
incorrect = [
# Ripper incorrectly attributes the block to the keyword.
- "seattlerb/block_break.txt",
- "seattlerb/block_next.txt",
"seattlerb/block_return.txt",
- "whitequark/break_block.txt",
- "whitequark/next_block.txt",
"whitequark/return_block.txt",
- # Ripper is not accounting for locals created by patterns using the **
- # operator within an `in` clause.
- "seattlerb/parse_pattern_058.txt",
-
# Ripper cannot handle named capture groups in regular expressions.
"regex.txt",
- "regex_char_width.txt",
- "whitequark/lvar_injecting_match.txt",
# Ripper fails to understand some structures that span across heredocs.
- "spanning_heredoc.txt"
+ "spanning_heredoc.txt",
+
+ # Ripper interprets circular keyword arguments as method calls.
+ "3.4/circular_parameters.txt",
+
+ # Ripper doesn't emit `args_add_block` when endless method is prefixed by modifier.
+ "4.0/endless_methods_command_call.txt",
+
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ "command_method_call_2.txt",
]
+ if RUBY_VERSION.start_with?("3.3.")
+ incorrect += [
+ "whitequark/lvar_injecting_match.txt",
+ "seattlerb/parse_pattern_058.txt",
+ "regex_char_width.txt",
+ ]
+ end
+
+ if RUBY_VERSION.start_with?("4.")
+ incorrect += [
+ # https://bugs.ruby-lang.org/issues/21945
+ "and_or_with_suffix.txt",
+ ]
+ end
+
+ # https://bugs.ruby-lang.org/issues/21669
+ incorrect << "4.1/void_value.txt"
+ # https://bugs.ruby-lang.org/issues/19107
+ incorrect << "4.1/trailing_comma_after_method_arguments.txt"
+
# Skip these tests that we haven't implemented yet.
- omitted = [
+ omitted_sexp_raw = [
+ "bom_leading_space.txt",
+ "bom_spaces.txt",
"dos_endings.txt",
+ "heredocs_with_fake_newlines.txt",
"heredocs_with_ignored_newlines.txt",
"seattlerb/block_call_dot_op2_brace_block.txt",
"seattlerb/block_command_operation_colon.txt",
@@ -50,14 +73,237 @@ module Prism
"whitequark/slash_newline_in_heredocs.txt"
]
- Fixture.each(except: incorrect | omitted) do |fixture|
- define_method(fixture.test_name) { assert_ripper(fixture.read) }
+ omitted_lex = [
+ "heredoc_with_escaped_newline_at_start.txt",
+ "heredocs_with_fake_newlines.txt",
+ "indented_file_end.txt",
+ "spanning_heredoc_newlines.txt",
+ "whitequark/dedenting_heredoc.txt",
+ "whitequark/procarg0.txt",
+ ]
+
+ omitted_scan = [
+ "bom_leading_space.txt",
+ "bom_spaces.txt",
+ "dos_endings.txt",
+ "heredocs_with_fake_newlines.txt",
+ "rescue_modifier.txt",
+ "seattlerb/block_call_dot_op2_brace_block.txt",
+ "seattlerb/block_command_operation_colon.txt",
+ "seattlerb/block_command_operation_dot.txt",
+ "seattlerb/case_in.txt",
+ "seattlerb/heredoc__backslash_dos_format.txt",
+ "seattlerb/heredoc_backslash_nl.txt",
+ "seattlerb/heredoc_nested.txt",
+ "seattlerb/heredoc_squiggly_blank_line_plus_interpolation.txt",
+ "seattlerb/heredoc_squiggly_empty.txt",
+ "seattlerb/masgn_command_call.txt",
+ "seattlerb/messy_op_asgn_lineno.txt",
+ "seattlerb/op_asgn_primary_colon_const_command_call.txt",
+ "seattlerb/parse_pattern_076.txt",
+ "seattlerb/pct_w_heredoc_interp_nested.txt",
+ "tilde_heredocs.txt",
+ "unparser/corpus/literal/assignment.txt",
+ "unparser/corpus/literal/pattern.txt",
+ "unparser/corpus/semantic/dstr.txt",
+ "variables.txt",
+ "whitequark/dedenting_heredoc.txt",
+ "whitequark/masgn_nested.txt",
+ "whitequark/newline_in_hash_argument.txt",
+ "whitequark/numparam_ruby_bug_19025.txt",
+ "whitequark/op_asgn_cmd.txt",
+ "whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt",
+ "whitequark/parser_slash_slash_n_escaping_in_literals.txt",
+ "whitequark/pattern_matching_nil_pattern.txt",
+ "whitequark/ruby_bug_12402.txt",
+ "whitequark/ruby_bug_18878.txt",
+ "whitequark/send_block_chain_cmd.txt",
+ "whitequark/slash_newline_in_heredocs.txt",
+ ]
+
+ Fixture.each_for_current_ruby(except: incorrect | omitted_sexp_raw) do |fixture|
+ define_method("#{fixture.test_name}_sexp_raw") { assert_ripper_sexp_raw(fixture.read) }
+ end
+
+ Fixture.each_for_current_ruby(except: incorrect | omitted_lex) do |fixture|
+ define_method("#{fixture.test_name}_lex") { assert_ripper_lex(fixture.read) }
+ end
+
+ def test_lex_ignored_missing_heredoc_end
+ ["", "-", "~"].each do |type|
+ source = "<<#{type}FOO\n"
+ assert_ripper_lex(source)
+
+ source = "<<#{type}'FOO'\n"
+ assert_ripper_lex(source)
+ end
+ end
+
+ UNSUPPORTED_EVENTS = %i[comma ignored_nl nl semicolon sp ignored_sp]
+ # Events that are currently not emitted
+ SUPPORTED_EVENTS = Translation::Ripper::EVENTS - UNSUPPORTED_EVENTS
+ # Events that assert against their line/column
+ CHECK_LOCATION_EVENTS = %i[kw op lbrace rbrace lbracket rbracket lparen rparen words_sep label_end]
+
+ module Events
+ attr_reader :events
+
+ def initialize(...)
+ super
+ @events = []
+ end
+
+ SUPPORTED_EVENTS.each do |event|
+ define_method(:"on_#{event}") do |*args|
+ if CHECK_LOCATION_EVENTS.include?(event)
+ @events << [event, lineno, column, *args]
+ else
+ @events << [event, *args]
+ end
+ super(*args)
+ end
+ end
+ end
+
+ class RipperEvents < Ripper
+ include Events
+ end
+
+ class PrismEvents < Translation::Ripper
+ include Events
+ end
+
+ class ObjectEvents < Translation::Ripper
+ OBJECT = BasicObject.new
+ SUPPORTED_EVENTS.each do |event|
+ define_method(:"on_#{event}") { |*args| OBJECT }
+ end
+ end
+
+ Fixture.each_for_current_ruby(except: incorrect | omitted_scan) do |fixture|
+ define_method("#{fixture.test_name}_events") do
+ source = fixture.read
+ # Similar to test/ripper/assert_parse_files.rb in CRuby
+ object_events = ObjectEvents.new(source)
+ assert_nothing_raised { object_events.parse }
+
+ ripper = RipperEvents.new(source, fixture.path)
+ prism = PrismEvents.new(source, fixture.path)
+ ripper.parse
+ prism.parse
+ # Check that the same events are emitted, regardless of order
+ assert_equal(ripper.events.sort_by(&:inspect), prism.events.sort_by(&:inspect))
+ end
+ end
+
+ def test_lexer
+ lexer = Translation::Ripper::Lexer.new("foo")
+ expected = [[1, 0], :on_ident, "foo", Translation::Ripper::EXPR_CMDARG]
+
+ assert_equal([expected], lexer.lex)
+ assert_equal(expected, lexer.parse[0].to_a)
+ assert_equal(lexer.parse[0].to_a, lexer.scan[0].to_a)
+
+ assert_equal(%i[on_int on_sp on_op], Translation::Ripper::Lexer.new("1 +").lex.map { |token| token[1] })
+ assert_raise(SyntaxError) { Translation::Ripper::Lexer.new("1 +").lex(raise_errors: true) }
+ end
+
+
+ # On syntax invalid code the output doesn't always match up
+ # In these cases we just want to make sure that it doesn't raise.
+ def test_lex_invalid_syntax
+ assert_nothing_raised do
+ Translation::Ripper.lex('scan/\p{alpha}/')
+ end
+
+ assert_equal(Ripper.lex('if;)'), Translation::Ripper.lex('if;)'))
+ end
+
+ def test_tokenize
+ source = "foo;1;BAZ"
+ assert_equal(Ripper.tokenize(source), Translation::Ripper.tokenize(source))
+ end
+
+ def test_encoding
+ source = '"わたし"'.encode(Encoding::Windows_31J)
+ assert_equal(Ripper.tokenize(source), Translation::Ripper.tokenize(source))
+ assert_equal(Ripper.sexp(source), Translation::Ripper.sexp(source))
+ end
+
+ def test_sexp_coercion
+ string_like = Object.new
+ def string_like.to_str
+ "a"
+ end
+ assert_equal Ripper.sexp(string_like), Translation::Ripper.sexp(string_like)
+
+ File.open(__FILE__) do |file1|
+ File.open(__FILE__) do |file2|
+ assert_equal Ripper.sexp(file1), Translation::Ripper.sexp(file2)
+ end
+ end
+
+ File.open(__FILE__) do |file1|
+ File.open(__FILE__) do |file2|
+ object1_with_gets = Object.new
+ object1_with_gets.define_singleton_method(:gets) do
+ file1.gets
+ end
+
+ object2_with_gets = Object.new
+ object2_with_gets.define_singleton_method(:gets) do
+ file2.gets
+ end
+
+ assert_equal Ripper.sexp(object1_with_gets), Translation::Ripper.sexp(object2_with_gets)
+ end
+ end
+ end
+
+ def test_lex_coersion
+ string_like = Object.new
+ def string_like.to_str
+ "a"
+ end
+ assert_equal Ripper.lex(string_like), Translation::Ripper.lex(string_like)
+ end
+
+ # Check that the hardcoded values don't change without us noticing.
+ def test_internals
+ actual = Translation::Ripper.constants.select { |name| name.start_with?("EXPR_") }.sort
+ expected = Ripper.constants.select { |name| name.start_with?("EXPR_") }.sort
+
+ assert_equal(expected, actual)
+ expected.zip(actual).each do |ripper, prism|
+ assert_equal(Ripper.const_get(ripper), Translation::Ripper.const_get(prism))
+ end
end
private
- def assert_ripper(source)
+ def assert_ripper_sexp_raw(source)
assert_equal Ripper.sexp_raw(source), Prism::Translation::Ripper.sexp_raw(source)
end
+
+ def assert_ripper_lex(source)
+ prism = Translation::Ripper.lex(source)
+ ripper = Ripper.lex(source)
+
+ # Prism emits tokens by their order in the code, not in parse order
+ ripper.sort_by! { |elem| elem[0] }
+
+ [prism.size, ripper.size].max.times do |index|
+ expected = ripper[index]
+ actual = prism[index]
+
+ # There are some tokens that have slightly different state that do not
+ # effect the parse tree, so they may not match.
+ if expected && actual && expected[1] == actual[1] && %i[on_comment on_heredoc_end on_embexpr_end on_sp].include?(expected[1])
+ expected[3] = actual[3] = nil
+ end
+
+ assert_equal(expected, actual)
+ end
+ end
end
end
diff --git a/test/prism/ruby/ruby_parser_test.rb b/test/prism/ruby/ruby_parser_test.rb
index 1d530dd13b..bc89bdae72 100644
--- a/test/prism/ruby/ruby_parser_test.rb
+++ b/test/prism/ruby/ruby_parser_test.rb
@@ -13,38 +13,23 @@ rescue LoadError
return
end
-# We want to also compare lines and files to make sure we're setting them
-# correctly.
-Sexp.prepend(
- Module.new do
- def ==(other)
- super && line == other.line && file == other.file # && line_max == other.line_max
- end
- end
-)
-
module Prism
class RubyParserTest < TestCase
todos = [
+ "character_literal.txt",
"encoding_euc_jp.txt",
- "newline_terminated.txt",
"regex_char_width.txt",
- "seattlerb/bug169.txt",
"seattlerb/masgn_colon3.txt",
"seattlerb/messy_op_asgn_lineno.txt",
"seattlerb/op_asgn_primary_colon_const_command_call.txt",
"seattlerb/regexp_esc_C_slash.txt",
"seattlerb/str_lit_concat_bad_encodings.txt",
+ "strings.txt",
"unescaping.txt",
- "unparser/corpus/literal/kwbegin.txt",
- "unparser/corpus/literal/send.txt",
"whitequark/masgn_const.txt",
"whitequark/pattern_matching_constants.txt",
- "whitequark/pattern_matching_implicit_array_match.txt",
"whitequark/pattern_matching_single_match.txt",
"whitequark/ruby_bug_12402.txt",
- "whitequark/ruby_bug_14690.txt",
- "whitequark/space_args_block.txt"
]
# https://github.com/seattlerb/ruby_parser/issues/344
@@ -52,6 +37,9 @@ module Prism
"alias.txt",
"dsym_str.txt",
"dos_endings.txt",
+ "heredoc_dedent_line_continuation.txt",
+ "heredoc_percent_q_newline_delimiter.txt",
+ "heredocs_with_fake_newlines.txt",
"heredocs_with_ignored_newlines.txt",
"method_calls.txt",
"methods.txt",
@@ -69,7 +57,9 @@ module Prism
"seattlerb/heredoc_with_only_carriage_returns.txt",
"spanning_heredoc_newlines.txt",
"spanning_heredoc.txt",
+ "symbols.txt",
"tilde_heredocs.txt",
+ "unary_method_calls.txt",
"unparser/corpus/literal/literal.txt",
"while.txt",
"whitequark/cond_eflipflop.txt",
@@ -87,10 +77,20 @@ module Prism
"whitequark/ruby_bug_11989.txt",
"whitequark/ruby_bug_18878.txt",
"whitequark/ruby_bug_19281.txt",
- "whitequark/slash_newline_in_heredocs.txt"
+ "whitequark/slash_newline_in_heredocs.txt",
+
+ "3.3-3.3/block_args_in_array_assignment.txt",
+ "3.3-3.3/it_with_ordinary_parameter.txt",
+ "3.3-3.3/keyword_args_in_array_assignment.txt",
+ "3.3-3.3/return_in_sclass.txt",
+
+ "3.3-4.0/void_value.txt",
+
+ # https://bugs.ruby-lang.org/issues/21168#note-5
+ "command_method_call_2.txt",
]
- Fixture.each(except: failures) do |fixture|
+ Fixture.each_for_version(version: "3.3", except: failures) do |fixture|
define_method(fixture.test_name) do
assert_ruby_parser(fixture, todos.include?(fixture.path))
end
@@ -102,10 +102,16 @@ module Prism
source = fixture.read
expected = ignore_warnings { ::RubyParser.new.parse(source, fixture.path) }
actual = Prism::Translation::RubyParser.new.parse(source, fixture.path)
+ on_failure = -> { message(expected, actual) }
if !allowed_failure
- assert_equal(expected, actual, -> { message(expected, actual) })
- elsif expected == actual
+ assert_equal(expected, actual, on_failure)
+
+ unless actual.nil?
+ assert_equal(expected.line, actual.line, on_failure)
+ assert_equal(expected.file, actual.file, on_failure)
+ end
+ elsif expected == actual && expected.line && actual.line && expected.file == actual.file
puts "#{name} now passes"
end
end
diff --git a/test/prism/ruby/source_test.rb b/test/prism/ruby/source_test.rb
new file mode 100644
index 0000000000..f7cf4fe83a
--- /dev/null
+++ b/test/prism/ruby/source_test.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require_relative "../test_helper"
+
+module Prism
+ class SourceTest < TestCase
+ def test_byte_offset
+ source = Prism.parse(<<~SRC).source
+ abcd
+ efgh
+ ijkl
+ SRC
+
+ assert_equal 0, source.byte_offset(1, 0)
+ assert_equal 5, source.byte_offset(2, 0)
+ assert_equal 10, source.byte_offset(3, 0)
+ assert_equal 15, source.byte_offset(4, 0)
+
+ error = assert_raise(ArgumentError) { source.byte_offset(5, 0) }
+ assert_equal "line 5 is out of range", error.message
+
+ error = assert_raise(ArgumentError) { source.byte_offset(0, 0) }
+ assert_equal "line 0 is out of range", error.message
+
+ error = assert_raise(ArgumentError) { source.byte_offset(-1, 0) }
+ assert_equal "line -1 is out of range", error.message
+ end
+
+ def test_byte_offset_with_start_line
+ source = Prism.parse(<<~SRC, line: 11).source
+ abcd
+ efgh
+ ijkl
+ SRC
+
+ assert_equal 0, source.byte_offset(11, 0)
+ assert_equal 5, source.byte_offset(12, 0)
+ assert_equal 10, source.byte_offset(13, 0)
+ assert_equal 15, source.byte_offset(14, 0)
+
+ error = assert_raise(ArgumentError) { source.byte_offset(15, 0) }
+ assert_equal "line 15 is out of range", error.message
+
+ error = assert_raise(ArgumentError) { source.byte_offset(10, 0) }
+ assert_equal "line 10 is out of range", error.message
+
+ error = assert_raise(ArgumentError) { source.byte_offset(9, 0) }
+ assert_equal "line 9 is out of range", error.message
+ end
+ end
+end
diff --git a/test/prism/snippets_test.rb b/test/prism/snippets_test.rb
index 66802c5dc3..3c28d27a25 100644
--- a/test/prism/snippets_test.rb
+++ b/test/prism/snippets_test.rb
@@ -18,24 +18,24 @@ module Prism
"whitequark/multiple_pattern_matches.txt"
]
- Fixture.each(except: except) do |fixture|
- define_method(fixture.test_name) { assert_snippets(fixture) }
+ Fixture.each_with_all_versions(except: except) do |fixture, version|
+ define_method(fixture.test_name(version)) { assert_snippets(fixture, version) }
end
private
# We test every snippet (separated by \n\n) in isolation to ensure the
# parser does not try to read bytes further than the end of each snippet.
- def assert_snippets(fixture)
+ def assert_snippets(fixture, version)
fixture.read.split(/(?<=\S)\n\n(?=\S)/).each do |snippet|
snippet = snippet.rstrip
- result = Prism.parse(snippet, filepath: fixture.path)
+ result = Prism.parse(snippet, filepath: fixture.path, version: version)
assert result.success?
if !ENV["PRISM_BUILD_MINIMAL"]
- dumped = Prism.dump(snippet, filepath: fixture.path)
- assert_equal_nodes(result.value, Prism.load(snippet, dumped).value)
+ dumped = Prism.dump(snippet, filepath: fixture.path, version: version)
+ assert_equal_nodes(result.value, Prism.load(snippet, dumped, version: version).value)
end
end
end
diff --git a/test/prism/test_helper.rb b/test/prism/test_helper.rb
index b848500283..406582c0a5 100644
--- a/test/prism/test_helper.rb
+++ b/test/prism/test_helper.rb
@@ -2,7 +2,6 @@
require "prism"
require "pp"
-require "ripper"
require "stringio"
require "test/unit"
require "tempfile"
@@ -38,7 +37,7 @@ module Prism
# are used to define test methods that assert against each fixture in some
# way.
class Fixture
- BASE = File.join(__dir__, "fixtures")
+ BASE = ENV.fetch("FIXTURE_BASE", File.join(__dir__, "fixtures"))
attr_reader :path
@@ -55,17 +54,45 @@ module Prism
end
def snapshot_path
- File.join(__dir__, "snapshots", path)
+ File.join(File.expand_path("../..", __dir__), "snapshots", path)
end
- def test_name
- :"test_#{path}"
+ def test_name(version = nil)
+ if version
+ :"test_#{version}_#{path}"
+ else
+ :"test_#{path}"
+ end
end
def self.each(except: [], &block)
- paths = Dir[ENV.fetch("FOCUS") { File.join("**", "*.txt") }, base: BASE] - except
+ glob_pattern = ENV.fetch("FOCUS") { custom_base_path? ? File.join("**", "*.rb") : File.join("**", "*.txt") }
+ paths = Dir[glob_pattern, base: BASE] - except
paths.each { |path| yield Fixture.new(path) }
end
+
+ def self.each_for_version(except: [], version:, &block)
+ each(except: except) do |fixture|
+ next unless TestCase.ruby_versions_for(fixture.path).include?(version)
+ yield fixture
+ end
+ end
+
+ def self.each_for_current_ruby(except: [], &block)
+ each_for_version(except: except, version: CURRENT_MAJOR_MINOR, &block)
+ end
+
+ def self.each_with_all_versions(except: [], &block)
+ each(except: except) do |fixture|
+ TestCase.ruby_versions_for(fixture.path).each do |version|
+ yield fixture, version
+ end
+ end
+ end
+
+ def self.custom_base_path?
+ ENV.key?("FIXTURE_BASE")
+ end
end
# Yield each encoding that we want to test, along with a range of the
@@ -207,6 +234,41 @@ module Prism
yield Encoding::EUC_TW, codepoints_euc_tw
end
+ # True if the current platform is Windows.
+ def self.windows?
+ RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince/i)
+ end
+
+ # All versions that prism can parse
+ SYNTAX_VERSIONS = %w[3.3 3.4 4.0 4.1]
+
+ # `RUBY_VERSION` with the patch version excluded
+ CURRENT_MAJOR_MINOR = RUBY_VERSION.split(".")[0, 2].join(".")
+
+ # Returns an array of ruby versions that a given filepath should test against:
+ # test.txt # => all available versions
+ # 3.4/test.txt # => versions since 3.4 (inclusive)
+ # 3.4-4.2/test.txt # => verisions since 3.4 (inclusive) up to 4.2 (inclusive)
+ def self.ruby_versions_for(filepath)
+ return [ENV['SYNTAX_VERSION']] if ENV['SYNTAX_VERSION']
+
+ parts = filepath.split("/")
+ return SYNTAX_VERSIONS if parts.size == 1
+
+ version_start, version_stop = parts[0].split("-")
+ if version_stop
+ SYNTAX_VERSIONS[SYNTAX_VERSIONS.index(version_start)..SYNTAX_VERSIONS.index(version_stop)]
+ else
+ SYNTAX_VERSIONS[SYNTAX_VERSIONS.index(version_start)..]
+ end
+ end
+
+ if RUBY_VERSION >= "3.3.0"
+ def test_all_syntax_versions_present
+ assert_include(SYNTAX_VERSIONS, CURRENT_MAJOR_MINOR)
+ end
+ end
+
private
if RUBY_ENGINE == "ruby" && RubyVM::InstructionSequence.compile("").to_a[4][:parser] != :prism
@@ -309,15 +371,16 @@ module Prism
end
end
- def ignore_warnings
- previous = $VERBOSE
- $VERBOSE = nil
+ def capture_warnings
+ $stderr = StringIO.new
+ yield
+ $stderr.string
+ ensure
+ $stderr = STDERR
+ end
- begin
- yield
- ensure
- $VERBOSE = previous
- end
+ def ignore_warnings
+ capture_warnings { return yield }
end
end
end
diff --git a/test/psych/test_data.rb b/test/psych/test_data.rb
new file mode 100644
index 0000000000..5e340c580a
--- /dev/null
+++ b/test/psych/test_data.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+require_relative 'helper'
+
+class PsychDataWithIvar < Data.define(:foo)
+ attr_reader :bar
+ def initialize(**)
+ @bar = 'hello'
+ super
+ end
+end unless RUBY_VERSION < "3.2"
+
+module Psych
+ class TestData < TestCase
+ class SelfReferentialData < Data.define(:foo)
+ attr_accessor :ref
+ def initialize(foo:)
+ @ref = self
+ super
+ end
+ end unless RUBY_VERSION < "3.2"
+
+ def setup
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ end
+
+ # TODO: move to another test?
+ def test_dump_data
+ assert_equal <<~eoyml, Psych.dump(PsychDataWithIvar["bar"])
+ --- !ruby/data-with-ivars:PsychDataWithIvar
+ members:
+ foo: bar
+ ivars:
+ "@bar": hello
+ eoyml
+ end
+
+ def test_self_referential_data
+ circular = SelfReferentialData.new("foo")
+
+ loaded = Psych.unsafe_load(Psych.dump(circular))
+ assert_instance_of(SelfReferentialData, loaded.ref)
+
+ assert_equal(circular, loaded)
+ assert_same(loaded, loaded.ref)
+ end
+
+ def test_roundtrip
+ thing = PsychDataWithIvar.new("bar")
+ data = Psych.unsafe_load(Psych.dump(thing))
+
+ assert_equal "hello", data.bar
+ assert_equal "bar", data.foo
+ end
+
+ def test_load
+ obj = Psych.unsafe_load(<<~eoyml)
+ --- !ruby/data-with-ivars:PsychDataWithIvar
+ members:
+ foo: bar
+ ivars:
+ "@bar": hello
+ eoyml
+
+ assert_equal "hello", obj.bar
+ assert_equal "bar", obj.foo
+ end
+
+ def test_members_must_be_identical
+ TestData.const_set :D, Data.define(:a, :b)
+ d = Psych.dump(TestData::D.new(1, 2))
+
+ # more members
+ TestData.send :remove_const, :D
+ TestData.const_set :D, Data.define(:a, :b, :c)
+ e = assert_raise(ArgumentError) { Psych.unsafe_load d }
+ assert_equal 'missing keyword: :c', e.message
+
+ # less members
+ TestData.send :remove_const, :D
+ TestData.const_set :D, Data.define(:a)
+ e = assert_raise(ArgumentError) { Psych.unsafe_load d }
+ assert_equal 'unknown keyword: :b', e.message
+
+ # completely different members
+ TestData.send :remove_const, :D
+ TestData.const_set :D, Data.define(:a, :c)
+ e = assert_raise(ArgumentError) { Psych.unsafe_load d }
+ assert_include e.message, 'keyword:'
+ ensure
+ TestData.send :remove_const, :D
+ end
+ end
+end
diff --git a/test/psych/test_date_time.rb b/test/psych/test_date_time.rb
index 4565b8e764..79a48e2472 100644
--- a/test/psych/test_date_time.rb
+++ b/test/psych/test_date_time.rb
@@ -85,5 +85,20 @@ module Psych
assert_match('&', yaml)
assert_match('*', yaml)
end
+
+ def test_overwritten_to_s
+ pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/
+ s = Psych.dump(Date.new(2023, 9, 2), permitted_classes: [Date])
+ assert_separately(%W[-rpsych -rdate - #{s}], "#{<<~"begin;"}\n#{<<~'end;'}")
+ class Date
+ undef to_s
+ def to_s; strftime("%D"); end
+ end
+ expected = ARGV.shift
+ begin;
+ s = Psych.dump(Date.new(2023, 9, 2), permitted_classes: [Date])
+ assert_equal(expected, s)
+ end;
+ end
end
end
diff --git a/test/psych/test_exception.rb b/test/psych/test_exception.rb
index c1e69ab18d..6fd92abf9d 100644
--- a/test/psych/test_exception.rb
+++ b/test/psych/test_exception.rb
@@ -82,6 +82,19 @@ module Psych
assert_equal 'omg!', ex.file
end
+ def test_safe_load_stream_takes_file
+ ex = assert_raise(Psych::SyntaxError) do
+ Psych.safe_load_stream '--- `'
+ end
+ assert_nil ex.file
+ assert_match '(<unknown>)', ex.message
+
+ ex = assert_raise(Psych::SyntaxError) do
+ Psych.safe_load_stream '--- `', filename: 'omg!'
+ end
+ assert_equal 'omg!', ex.file
+ end
+
def test_parse_file_exception
Tempfile.create(['parsefile', 'yml']) {|t|
t.binmode
diff --git a/test/psych/test_object_references.rb b/test/psych/test_object_references.rb
index 86bb9034b9..0498d54eec 100644
--- a/test/psych/test_object_references.rb
+++ b/test/psych/test_object_references.rb
@@ -31,6 +31,11 @@ module Psych
assert_reference_trip Struct.new(:foo).new(1)
end
+ def test_data_has_references
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ assert_reference_trip Data.define(:foo).new(1)
+ end
+
def assert_reference_trip obj
yml = Psych.dump([obj, obj])
assert_match(/\*-?\d+/, yml)
diff --git a/test/psych/test_parser.rb b/test/psych/test_parser.rb
index c1e0abb89d..4ca4d63d80 100644
--- a/test/psych/test_parser.rb
+++ b/test/psych/test_parser.rb
@@ -198,6 +198,48 @@ module Psych
assert_called :end_stream
end
+ def test_parse_io_returns_more_bytes_than_requested
+ # An IO-like source whose #read returns more bytes than the size it was
+ # asked for must not overflow libyaml's read buffer.
+ io = Object.new
+ def io.external_encoding; Encoding::UTF_8 end
+ def io.read len
+ return nil if @done
+ @done = true
+ "--- a\n" + ("#" * (len + (1 << 20)))
+ end
+
+ # CRuby clamps the over-read and parses; JRuby's parser rejects the
+ # over-reading IO with an IOError. Either way there is no overflow.
+ begin
+ @parser.parse io
+ rescue IOError
+ return
+ end
+ assert_called :start_stream
+ assert_called :scalar
+ assert_called :end_stream
+ end
+
+ def test_parse_io_returns_more_bytes_than_requested_multibyte
+ # The over-read is rounded down to a character boundary so a multibyte
+ # character is never split when the copy is clamped.
+ io = Object.new
+ def io.external_encoding; Encoding::UTF_8 end
+ def io.read len
+ return nil if @done
+ @done = true
+ "--- a\n#" + ("あ" * (len + (1 << 20)))
+ end
+
+ begin
+ @parser.parse io
+ rescue IOError
+ return
+ end
+ assert_called :scalar
+ end
+
def test_syntax_error
assert_raise(Psych::SyntaxError) do
@parser.parse("---\n\"foo\"\n\"bar\"\n")
diff --git a/test/psych/test_psych.rb b/test/psych/test_psych.rb
index 42586a8779..4455c471e7 100644
--- a/test/psych/test_psych.rb
+++ b/test/psych/test_psych.rb
@@ -89,6 +89,7 @@ class TestPsych < Psych::TestCase
things = [22, "foo \n", {}]
stream = Psych.dump_stream(*things)
assert_equal things, Psych.load_stream(stream)
+ assert_equal things, Psych.safe_load_stream(stream)
end
def test_dump_file
@@ -119,6 +120,8 @@ class TestPsych < Psych::TestCase
def test_load_stream
docs = Psych.load_stream("--- foo\n...\n--- bar\n...")
assert_equal %w{ foo bar }, docs
+ safe_docs = Psych.safe_load_stream("--- foo\n...\n--- bar\n...")
+ assert_equal %w{ foo bar }, safe_docs
end
def test_load_stream_freeze
@@ -138,10 +141,18 @@ class TestPsych < Psych::TestCase
assert_equal [], Psych.load_stream("")
end
+ def test_safe_load_stream_default_fallback
+ assert_equal [], Psych.safe_load_stream("")
+ end
+
def test_load_stream_raises_on_bad_input
assert_raise(Psych::SyntaxError) { Psych.load_stream("--- `") }
end
+ def test_safe_load_stream_raises_on_bad_input
+ assert_raise(Psych::SyntaxError) { Psych.safe_load_stream("--- `") }
+ end
+
def test_parse_stream
docs = Psych.parse_stream("--- foo\n...\n--- bar\n...")
assert_equal(%w[foo bar], docs.children.map(&:transform))
diff --git a/test/psych/test_psych_set.rb b/test/psych/test_psych_set.rb
new file mode 100644
index 0000000000..c72cd73f18
--- /dev/null
+++ b/test/psych/test_psych_set.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+require_relative 'helper'
+
+module Psych
+ class TestPsychSet < TestCase
+ def setup
+ super
+ @set = Psych::Set.new
+ @set['foo'] = 'bar'
+ @set['bar'] = 'baz'
+ end
+
+ def test_dump
+ assert_match(/!set/, Psych.dump(@set))
+ end
+
+ def test_roundtrip
+ assert_cycle(@set)
+ end
+
+ ###
+ # FIXME: Syck should also support !!set as shorthand
+ def test_load_from_yaml
+ loaded = Psych.unsafe_load(<<-eoyml)
+--- !set
+foo: bar
+bar: baz
+ eoyml
+ assert_equal(@set, loaded)
+ end
+
+ def test_loaded_class
+ assert_instance_of(Psych::Set, Psych.unsafe_load(Psych.dump(@set)))
+ end
+
+ def test_set_shorthand
+ loaded = Psych.unsafe_load(<<-eoyml)
+--- !!set
+foo: bar
+bar: baz
+ eoyml
+ assert_instance_of(Psych::Set, loaded)
+ end
+
+ def test_set_self_reference
+ @set['self'] = @set
+ assert_cycle(@set)
+ end
+
+ def test_stringify_names
+ @set[:symbol] = :value
+
+ assert_match(/^:symbol: :value/, Psych.dump(@set))
+ assert_match(/^symbol: :value/, Psych.dump(@set, stringify_names: true))
+ end
+ end
+end
diff --git a/test/psych/test_ractor.rb b/test/psych/test_ractor.rb
index 1b0d810609..f1c8327aa3 100644
--- a/test/psych/test_ractor.rb
+++ b/test/psych/test_ractor.rb
@@ -7,7 +7,7 @@ class TestPsychRactor < Test::Unit::TestCase
obj = {foo: [42]}
obj2 = Ractor.new(obj) do |obj|
Psych.unsafe_load(Psych.dump(obj))
- end.take
+ end.value
assert_equal obj, obj2
RUBY
end
@@ -33,7 +33,7 @@ class TestPsychRactor < Test::Unit::TestCase
val * 2
end
Psych.load('--- !!omap hello')
- end.take
+ end.value
assert_equal 'hellohello', r
assert_equal 'hello', Psych.load('--- !!omap hello')
RUBY
@@ -43,7 +43,7 @@ class TestPsychRactor < Test::Unit::TestCase
assert_ractor(<<~RUBY, require_relative: 'helper')
r = Ractor.new do
Psych.libyaml_version.join('.') == Psych::LIBYAML_VERSION
- end.take
+ end.value
assert_equal true, r
RUBY
end
diff --git a/test/psych/test_safe_load.rb b/test/psych/test_safe_load.rb
index a9ed737528..e6ca1e142b 100644
--- a/test/psych/test_safe_load.rb
+++ b/test/psych/test_safe_load.rb
@@ -114,6 +114,38 @@ module Psych
end
end
+ D = Data.define(:d) unless RUBY_VERSION < "3.2"
+
+ def test_data_depends_on_sym
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ assert_safe_cycle(D.new(nil), permitted_classes: [D, Symbol])
+ assert_raise(Psych::DisallowedClass) do
+ cycle D.new(nil), permitted_classes: [D]
+ end
+ end
+
+ def test_anon_data
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ assert Psych.safe_load(<<-eoyml, permitted_classes: [Data, Symbol])
+--- !ruby/data
+ foo: bar
+ eoyml
+
+ assert_raise(Psych::DisallowedClass) do
+ Psych.safe_load(<<-eoyml, permitted_classes: [Data])
+--- !ruby/data
+ foo: bar
+ eoyml
+ end
+
+ assert_raise(Psych::DisallowedClass) do
+ Psych.safe_load(<<-eoyml, permitted_classes: [Symbol])
+--- !ruby/data
+ foo: bar
+ eoyml
+ end
+ end
+
def test_safe_load_default_fallback
assert_nil Psych.safe_load("")
end
diff --git a/test/psych/test_scalar_scanner.rb b/test/psych/test_scalar_scanner.rb
index 2637a74df8..bc6a74ad8b 100644
--- a/test/psych/test_scalar_scanner.rb
+++ b/test/psych/test_scalar_scanner.rb
@@ -138,6 +138,11 @@ module Psych
assert_equal '-0b___', scanner.tokenize('-0b___')
end
+ def test_scan_without_parse_symbols
+ scanner = Psych::ScalarScanner.new ClassLoader.new, parse_symbols: false
+ assert_equal ':foo', scanner.tokenize(':foo')
+ end
+
def test_scan_int_commas_and_underscores
# NB: This test is to ensure backward compatibility with prior Psych versions,
# not to test against any actual YAML specification.
diff --git a/test/psych/test_serialize_subclasses.rb b/test/psych/test_serialize_subclasses.rb
index 344c79b3ef..640c331337 100644
--- a/test/psych/test_serialize_subclasses.rb
+++ b/test/psych/test_serialize_subclasses.rb
@@ -35,5 +35,23 @@ module Psych
so = StructSubclass.new('foo', [1,2,3])
assert_equal so, Psych.unsafe_load(Psych.dump(so))
end
+
+ class DataSubclass < Data.define(:foo)
+ def initialize(foo:)
+ @bar = "hello #{foo}"
+ super(foo: foo)
+ end
+
+ def == other
+ super(other) && @bar == other.instance_eval{ @bar }
+ end
+ end unless RUBY_VERSION < "3.2"
+
+ def test_data_subclass
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ so = DataSubclass.new('foo')
+ assert_equal so, Psych.unsafe_load(Psych.dump(so))
+ end
+
end
end
diff --git a/test/psych/test_set.rb b/test/psych/test_set.rb
index b4968d3425..ccd591c626 100644
--- a/test/psych/test_set.rb
+++ b/test/psych/test_set.rb
@@ -1,57 +1,36 @@
+# encoding: UTF-8
# frozen_string_literal: true
require_relative 'helper'
+require 'set' unless defined?(Set)
module Psych
class TestSet < TestCase
def setup
- super
- @set = Psych::Set.new
- @set['foo'] = 'bar'
- @set['bar'] = 'baz'
+ @set = ::Set.new([1, 2, 3])
end
def test_dump
- assert_match(/!set/, Psych.dump(@set))
+ assert_equal <<~YAML, Psych.dump(@set)
+ --- !ruby/object:Set
+ hash:
+ 1: true
+ 2: true
+ 3: true
+ YAML
end
- def test_roundtrip
- assert_cycle(@set)
- end
-
- ###
- # FIXME: Syck should also support !!set as shorthand
- def test_load_from_yaml
- loaded = Psych.unsafe_load(<<-eoyml)
---- !set
-foo: bar
-bar: baz
- eoyml
- assert_equal(@set, loaded)
+ def test_load
+ assert_equal @set, Psych.load(<<~YAML, permitted_classes: [::Set])
+ --- !ruby/object:Set
+ hash:
+ 1: true
+ 2: true
+ 3: true
+ YAML
end
- def test_loaded_class
- assert_instance_of(Psych::Set, Psych.unsafe_load(Psych.dump(@set)))
- end
-
- def test_set_shorthand
- loaded = Psych.unsafe_load(<<-eoyml)
---- !!set
-foo: bar
-bar: baz
- eoyml
- assert_instance_of(Psych::Set, loaded)
- end
-
- def test_set_self_reference
- @set['self'] = @set
- assert_cycle(@set)
- end
-
- def test_stringify_names
- @set[:symbol] = :value
-
- assert_match(/^:symbol: :value/, Psych.dump(@set))
- assert_match(/^symbol: :value/, Psych.dump(@set, stringify_names: true))
+ def test_roundtrip
+ assert_equal @set, Psych.load(Psych.dump(@set), permitted_classes: [::Set])
end
end
end
diff --git a/test/psych/test_stream.rb b/test/psych/test_stream.rb
index 9b71c6d996..ae940d1ee4 100644
--- a/test/psych/test_stream.rb
+++ b/test/psych/test_stream.rb
@@ -54,6 +54,14 @@ module Psych
assert_equal %w{ foo bar }, list
end
+ def test_safe_load_stream_yields_documents
+ list = []
+ Psych.safe_load_stream("--- foo\n...\n--- bar") do |ruby|
+ list << ruby
+ end
+ assert_equal %w{ foo bar }, list
+ end
+
def test_load_stream_break
list = []
Psych.load_stream("--- foo\n...\n--- `") do |ruby|
diff --git a/test/psych/test_stringio.rb b/test/psych/test_stringio.rb
new file mode 100644
index 0000000000..7fef1402a0
--- /dev/null
+++ b/test/psych/test_stringio.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+require_relative 'helper'
+
+module Psych
+ class TestStringIO < TestCase
+ # The superclass of StringIO before Ruby 3.0 was `Data`,
+ # which can interfere with the Ruby 3.2+ `Data` dumping.
+ def test_stringio
+ assert_nothing_raised do
+ Psych.dump(StringIO.new("foo"))
+ end
+ end
+ end
+end
diff --git a/test/psych/test_yaml.rb b/test/psych/test_yaml.rb
index 897a7c8935..134c346c90 100644
--- a/test/psych/test_yaml.rb
+++ b/test/psych/test_yaml.rb
@@ -6,6 +6,7 @@ require_relative 'helper'
# [ruby-core:01946]
module Psych_Tests
StructTest = Struct::new( :c )
+ DataTest = Data.define( :c ) unless RUBY_VERSION < "3.2"
end
class Psych_Unit_Tests < Psych::TestCase
@@ -35,6 +36,10 @@ class Psych_Unit_Tests < Psych::TestCase
assert_cycle(Regexp.new("foo\nbar"))
end
+ def test_regexp_with_slash
+ assert_cycle(Regexp.new('/'))
+ end
+
# [ruby-core:34969]
def test_regexp_with_n
assert_cycle(Regexp.new('',Regexp::NOENCODING))
@@ -1037,7 +1042,6 @@ EOY
end
def test_ruby_struct
- Struct.send(:remove_const, :MyBookStruct) if Struct.const_defined?(:MyBookStruct)
# Ruby structures
book_struct = Struct::new( "MyBookStruct", :author, :title, :year, :isbn )
assert_to_yaml(
@@ -1069,6 +1073,47 @@ EOY
c: 123
EOY
+ ensure
+ Struct.__send__(:remove_const, :MyBookStruct) if book_struct
+ end
+
+ def test_ruby_data
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ # Ruby Data value objects
+ book_class = Data.define(:author, :title, :year, :isbn)
+ Object.const_set(:MyBookData, book_class)
+ assert_to_yaml(
+ [ book_class.new( "Yukihiro Matsumoto", "Ruby in a Nutshell", 2002, "0-596-00214-9" ),
+ book_class.new( [ 'Dave Thomas', 'Andy Hunt' ], "The Pickaxe", 2002,
+ book_class.new( "This should be the ISBN", "but I have more data here", 2002, "None" )
+ )
+ ], <<EOY
+- !ruby/data:MyBookData
+ author: Yukihiro Matsumoto
+ title: Ruby in a Nutshell
+ year: 2002
+ isbn: 0-596-00214-9
+- !ruby/data:MyBookData
+ author:
+ - Dave Thomas
+ - Andy Hunt
+ title: The Pickaxe
+ year: 2002
+ isbn: !ruby/data:MyBookData
+ author: This should be the ISBN
+ title: but I have more data here
+ year: 2002
+ isbn: None
+EOY
+ )
+
+ assert_to_yaml( Psych_Tests::DataTest.new( 123 ), <<EOY )
+--- !ruby/data:Psych_Tests::DataTest
+c: 123
+EOY
+
+ ensure
+ Object.__send__(:remove_const, :MyBookData) if book_class
end
def test_ruby_rational
diff --git a/test/psych/test_yaml_special_cases.rb b/test/psych/test_yaml_special_cases.rb
index 205457bcae..f1a607783e 100644
--- a/test/psych/test_yaml_special_cases.rb
+++ b/test/psych/test_yaml_special_cases.rb
@@ -15,6 +15,7 @@ module Psych
s = ""
assert_equal false, Psych.unsafe_load(s)
assert_equal [], Psych.load_stream(s)
+ assert_equal [], Psych.safe_load_stream(s)
assert_equal false, Psych.parse(s)
assert_equal [], Psych.parse_stream(s).transform
assert_nil Psych.safe_load(s)
@@ -24,6 +25,7 @@ module Psych
s = "false"
assert_equal false, Psych.load(s)
assert_equal [false], Psych.load_stream(s)
+ assert_equal [false], Psych.safe_load_stream(s)
assert_equal false, Psych.parse(s).transform
assert_equal [false], Psych.parse_stream(s).transform
assert_equal false, Psych.safe_load(s)
@@ -33,6 +35,7 @@ module Psych
s = "n"
assert_equal "n", Psych.load(s)
assert_equal ["n"], Psych.load_stream(s)
+ assert_equal ["n"], Psych.safe_load_stream(s)
assert_equal "n", Psych.parse(s).transform
assert_equal ["n"], Psych.parse_stream(s).transform
assert_equal "n", Psych.safe_load(s)
@@ -42,6 +45,7 @@ module Psych
s = "off"
assert_equal false, Psych.load(s)
assert_equal [false], Psych.load_stream(s)
+ assert_equal [false], Psych.safe_load_stream(s)
assert_equal false, Psych.parse(s).transform
assert_equal [false], Psych.parse_stream(s).transform
assert_equal false, Psych.safe_load(s)
@@ -51,6 +55,7 @@ module Psych
s = "-.inf"
assert_equal(-Float::INFINITY, Psych.load(s))
assert_equal([-Float::INFINITY], Psych.load_stream(s))
+ assert_equal([-Float::INFINITY], Psych.safe_load_stream(s))
assert_equal(-Float::INFINITY, Psych.parse(s).transform)
assert_equal([-Float::INFINITY], Psych.parse_stream(s).transform)
assert_equal(-Float::INFINITY, Psych.safe_load(s))
@@ -60,6 +65,7 @@ module Psych
s = ".NaN"
assert Psych.load(s).nan?
assert Psych.load_stream(s).first.nan?
+ assert Psych.safe_load_stream(s).first.nan?
assert Psych.parse(s).transform.nan?
assert Psych.parse_stream(s).transform.first.nan?
assert Psych.safe_load(s).nan?
@@ -69,6 +75,7 @@ module Psych
s = "0xC"
assert_equal 12, Psych.load(s)
assert_equal [12], Psych.load_stream(s)
+ assert_equal [12], Psych.safe_load_stream(s)
assert_equal 12, Psych.parse(s).transform
assert_equal [12], Psych.parse_stream(s).transform
assert_equal 12, Psych.safe_load(s)
@@ -78,6 +85,7 @@ module Psych
s = "<<"
assert_equal "<<", Psych.load(s)
assert_equal ["<<"], Psych.load_stream(s)
+ assert_equal ["<<"], Psych.safe_load_stream(s)
assert_equal "<<", Psych.parse(s).transform
assert_equal ["<<"], Psych.parse_stream(s).transform
assert_equal "<<", Psych.safe_load(s)
@@ -87,6 +95,7 @@ module Psych
s = "<<: {}"
assert_equal({}, Psych.load(s))
assert_equal [{}], Psych.load_stream(s)
+ assert_equal [{}], Psych.safe_load_stream(s)
assert_equal({}, Psych.parse(s).transform)
assert_equal [{}], Psych.parse_stream(s).transform
assert_equal({}, Psych.safe_load(s))
@@ -96,6 +105,7 @@ module Psych
s = "- 1000\n- +1000\n- 1_000"
assert_equal [1000, 1000, 1000], Psych.load(s)
assert_equal [[1000, 1000, 1000]], Psych.load_stream(s)
+ assert_equal [[1000, 1000, 1000]], Psych.safe_load_stream(s)
assert_equal [1000, 1000, 1000], Psych.parse(s).transform
assert_equal [[1000, 1000, 1000]], Psych.parse_stream(s).transform
assert_equal [1000, 1000, 1000], Psych.safe_load(s)
@@ -105,6 +115,7 @@ module Psych
s = "[8, 08, 0o10, 010]"
assert_equal [8, "08", "0o10", 8], Psych.load(s)
assert_equal [[8, "08", "0o10", 8]], Psych.load_stream(s)
+ assert_equal [[8, "08", "0o10", 8]], Psych.safe_load_stream(s)
assert_equal [8, "08", "0o10", 8], Psych.parse(s).transform
assert_equal [[8, "08", "0o10", 8]], Psych.parse_stream(s).transform
assert_equal [8, "08", "0o10", 8], Psych.safe_load(s)
@@ -114,6 +125,7 @@ module Psych
s = "null"
assert_nil Psych.load(s)
assert_equal [nil], Psych.load_stream(s)
+ assert_equal [nil], Psych.safe_load_stream(s)
assert_nil Psych.parse(s).transform
assert_equal [nil], Psych.parse_stream(s).transform
assert_nil Psych.safe_load(s)
diff --git a/test/psych/visitors/test_to_ruby.rb b/test/psych/visitors/test_to_ruby.rb
index 89c3676651..c9b501dfa2 100644
--- a/test/psych/visitors/test_to_ruby.rb
+++ b/test/psych/visitors/test_to_ruby.rb
@@ -328,6 +328,12 @@ description:
mapping.children << Nodes::Scalar.new('bar')
assert_equal({'foo' => 'bar'}, mapping.to_ruby)
end
+
+ def test_parse_symbols
+ node = Nodes::Scalar.new(':foo')
+ assert_equal :foo, node.to_ruby
+ assert_equal ':foo', node.to_ruby(parse_symbols: false)
+ end
end
end
end
diff --git a/test/psych/visitors/test_yaml_tree.rb b/test/psych/visitors/test_yaml_tree.rb
index 01e685134a..bd3919f83d 100644
--- a/test/psych/visitors/test_yaml_tree.rb
+++ b/test/psych/visitors/test_yaml_tree.rb
@@ -73,6 +73,27 @@ module Psych
assert_equal s.method, obj.method
end
+ D = Data.define(:foo) unless RUBY_VERSION < "3.2"
+
+ def test_data
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ assert_cycle D.new('bar')
+ end
+
+ def test_data_anon
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ d = Data.define(:foo).new('bar')
+ obj = Psych.unsafe_load(Psych.dump(d))
+ assert_equal d.foo, obj.foo
+ end
+
+ def test_data_override_method
+ omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
+ d = Data.define(:method).new('override')
+ obj = Psych.unsafe_load(Psych.dump(d))
+ assert_equal d.method, obj.method
+ end
+
def test_exception
ex = Exception.new 'foo'
loaded = Psych.unsafe_load(Psych.dump(ex))
diff --git a/test/reline/helper.rb b/test/reline/helper.rb
deleted file mode 100644
index 6f470a617f..0000000000
--- a/test/reline/helper.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
-
-ENV['TERM'] = 'xterm' # for some CI environments
-
-require 'reline'
-require 'test/unit'
-
-begin
- require 'rbconfig'
-rescue LoadError
-end
-
-begin
- # This should exist and available in load path when this file is mirrored to ruby/ruby and running at there
- if File.exist?(File.expand_path('../../tool/lib/envutil.rb', __dir__))
- require 'envutil'
- end
-rescue LoadError
-end
-
-module Reline
- class << self
- def test_mode(ansi: false)
- @original_iogate = IOGate
-
- if defined?(RELINE_TEST_ENCODING)
- encoding = RELINE_TEST_ENCODING
- else
- encoding = Encoding::UTF_8
- end
-
- if ansi
- new_io_gate = ANSI.new
- # Setting ANSI gate's screen size through set_screen_size will also change the tester's stdin's screen size
- # Let's avoid that side-effect by stubbing the get_screen_size method
- new_io_gate.define_singleton_method(:get_screen_size) do
- [24, 80]
- end
- new_io_gate.define_singleton_method(:encoding) do
- encoding
- end
- else
- new_io_gate = Dumb.new(encoding: encoding)
- end
-
- remove_const('IOGate')
- const_set('IOGate', new_io_gate)
- core.config.instance_variable_set(:@test_mode, true)
- core.config.reset
- end
-
- def test_reset
- remove_const('IOGate')
- const_set('IOGate', @original_iogate)
- Reline.instance_variable_set(:@core, nil)
- end
-
- # Return a executable name to spawn Ruby process. In certain build configuration,
- # "ruby" may not be available.
- def test_rubybin
- # When this test suite is running in ruby/ruby, prefer EnvUtil result over original implementation
- if const_defined?(:EnvUtil)
- return EnvUtil.rubybin
- end
-
- # The following is a simplified port of EnvUtil.rubybin in ruby/ruby
- if ruby = ENV["RUBY"]
- return ruby
- end
- ruby = "ruby"
- exeext = RbConfig::CONFIG["EXEEXT"]
- rubyexe = (ruby + exeext if exeext and !exeext.empty?)
- if File.exist? ruby and File.executable? ruby and !File.directory? ruby
- return File.expand_path(ruby)
- end
- if rubyexe and File.exist? rubyexe and File.executable? rubyexe
- return File.expand_path(rubyexe)
- end
- if defined?(RbConfig.ruby)
- RbConfig.ruby
- else
- "ruby"
- end
- end
- end
-end
-
-class Reline::TestCase < Test::Unit::TestCase
- private def convert_str(input)
- input.encode(@line_editor.encoding, Encoding::UTF_8)
- end
-
- def omit_unless_utf8
- omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8
- end
-
- def input_key_by_symbol(method_symbol, char: nil, csi: false)
- char ||= csi ? "\e[A" : "\C-a"
- @line_editor.input_key(Reline::Key.new(char, method_symbol, false))
- end
-
- def input_keys(input)
- input = convert_str(input)
-
- key_stroke = Reline::KeyStroke.new(@config, @encoding)
- input_bytes = input.bytes
- until input_bytes.empty?
- expanded, input_bytes = key_stroke.expand(input_bytes)
- expanded.each do |key|
- @line_editor.input_key(key)
- end
- end
- end
-
- def set_line_around_cursor(before, after)
- input_keys("\C-a\C-k")
- input_keys(after)
- input_keys("\C-a")
- input_keys(before)
- end
-
- def assert_line_around_cursor(before, after)
- before = convert_str(before)
- after = convert_str(after)
- line = @line_editor.current_line
- byte_pointer = @line_editor.instance_variable_get(:@byte_pointer)
- actual_before = line.byteslice(0, byte_pointer)
- actual_after = line.byteslice(byte_pointer..)
- assert_equal([before, after], [actual_before, actual_after])
- end
-
- def assert_byte_pointer_size(expected)
- expected = convert_str(expected)
- byte_pointer = @line_editor.instance_variable_get(:@byte_pointer)
- chunk = @line_editor.line.byteslice(0, byte_pointer)
- assert_equal(
- expected.bytesize, byte_pointer,
- <<~EOM)
- <#{expected.inspect} (#{expected.encoding.inspect})> expected but was
- <#{chunk.inspect} (#{chunk.encoding.inspect})> in <Terminal #{Reline::Dumb.new.encoding.inspect}>
- EOM
- end
-
- def assert_line_index(expected)
- assert_equal(expected, @line_editor.instance_variable_get(:@line_index))
- end
-
- def assert_whole_lines(expected)
- assert_equal(expected, @line_editor.whole_lines)
- end
-
- def assert_key_binding(input, method_symbol, editing_modes = [:emacs, :vi_insert, :vi_command])
- editing_modes.each do |editing_mode|
- @config.editing_mode = editing_mode
- assert_equal(method_symbol, @config.editing_mode.get(input.bytes))
- end
- end
-end
diff --git a/test/reline/test_ansi.rb b/test/reline/test_ansi.rb
deleted file mode 100644
index 5e28e72b06..0000000000
--- a/test/reline/test_ansi.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-require_relative 'helper'
-require 'reline'
-
-class Reline::ANSITest < Reline::TestCase
- def setup
- Reline.send(:test_mode, ansi: true)
- @config = Reline::Config.new
- Reline.core.io_gate.set_default_key_bindings(@config)
- end
-
- def teardown
- Reline.test_reset
- end
-
- def test_home
- assert_key_binding("\e[1~", :ed_move_to_beg) # Console (80x25)
- assert_key_binding("\e[H", :ed_move_to_beg) # KDE
- assert_key_binding("\e[7~", :ed_move_to_beg) # urxvt / exoterm
- assert_key_binding("\eOH", :ed_move_to_beg) # GNOME
- end
-
- def test_end
- assert_key_binding("\e[4~", :ed_move_to_end) # Console (80x25)
- assert_key_binding("\e[F", :ed_move_to_end) # KDE
- assert_key_binding("\e[8~", :ed_move_to_end) # urxvt / exoterm
- assert_key_binding("\eOF", :ed_move_to_end) # GNOME
- end
-
- def test_delete
- assert_key_binding("\e[3~", :key_delete)
- end
-
- def test_up_arrow
- assert_key_binding("\e[A", :ed_prev_history) # Console (80x25)
- assert_key_binding("\eOA", :ed_prev_history)
- end
-
- def test_down_arrow
- assert_key_binding("\e[B", :ed_next_history) # Console (80x25)
- assert_key_binding("\eOB", :ed_next_history)
- end
-
- def test_right_arrow
- assert_key_binding("\e[C", :ed_next_char) # Console (80x25)
- assert_key_binding("\eOC", :ed_next_char)
- end
-
- def test_left_arrow
- assert_key_binding("\e[D", :ed_prev_char) # Console (80x25)
- assert_key_binding("\eOD", :ed_prev_char)
- end
-
- # Ctrl+arrow and Meta+arrow
- def test_extended
- assert_key_binding("\e[1;5C", :em_next_word) # Ctrl+→
- assert_key_binding("\e[1;5D", :ed_prev_word) # Ctrl+←
- assert_key_binding("\e[1;3C", :em_next_word) # Meta+→
- assert_key_binding("\e[1;3D", :ed_prev_word) # Meta+←
- assert_key_binding("\e\e[C", :em_next_word) # Meta+→
- assert_key_binding("\e\e[D", :ed_prev_word) # Meta+←
- end
-
- def test_shift_tab
- assert_key_binding("\e[Z", :completion_journey_up, [:emacs, :vi_insert])
- end
-
- # A few emacs bindings that are always mapped
- def test_more_emacs
- assert_key_binding("\e ", :em_set_mark, [:emacs])
- assert_key_binding("\C-x\C-x", :em_exchange_mark, [:emacs])
- end
-end
diff --git a/test/reline/test_config.rb b/test/reline/test_config.rb
deleted file mode 100644
index 3c9094eece..0000000000
--- a/test/reline/test_config.rb
+++ /dev/null
@@ -1,616 +0,0 @@
-require_relative 'helper'
-
-class Reline::Config::Test < Reline::TestCase
- def setup
- @pwd = Dir.pwd
- @tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}")
- begin
- Dir.mkdir(@tmpdir)
- rescue Errno::EEXIST
- FileUtils.rm_rf(@tmpdir)
- Dir.mkdir(@tmpdir)
- end
- Dir.chdir(@tmpdir)
- Reline.test_mode
- @config = Reline::Config.new
- @inputrc_backup = ENV['INPUTRC']
- end
-
- def teardown
- Dir.chdir(@pwd)
- FileUtils.rm_rf(@tmpdir)
- Reline.test_reset
- @config.reset
- ENV['INPUTRC'] = @inputrc_backup
- end
-
- def get_config_variable(variable)
- @config.instance_variable_get(variable)
- end
-
- def additional_key_bindings(keymap_label)
- get_config_variable(:@additional_key_bindings)[keymap_label].instance_variable_get(:@key_bindings)
- end
-
- def registered_key_bindings(keys)
- key_bindings = @config.key_bindings
- keys.to_h { |key| [key, key_bindings.get(key)] }
- end
-
- def test_read_lines
- @config.read_lines(<<~LINES.lines)
- set show-mode-in-prompt on
- LINES
-
- assert_equal true, get_config_variable(:@show_mode_in_prompt)
- end
-
- def test_read_lines_with_variable
- @config.read_lines(<<~LINES.lines)
- set disable-completion on
- LINES
-
- assert_equal true, get_config_variable(:@disable_completion)
- end
-
- def test_string_value
- @config.read_lines(<<~LINES.lines)
- set show-mode-in-prompt on
- set emacs-mode-string Emacs
- LINES
-
- assert_equal 'Emacs', get_config_variable(:@emacs_mode_string)
- end
-
- def test_string_value_with_brackets
- @config.read_lines(<<~LINES.lines)
- set show-mode-in-prompt on
- set emacs-mode-string [Emacs]
- LINES
-
- assert_equal '[Emacs]', get_config_variable(:@emacs_mode_string)
- end
-
- def test_string_value_with_brackets_and_quotes
- @config.read_lines(<<~LINES.lines)
- set show-mode-in-prompt on
- set emacs-mode-string "[Emacs]"
- LINES
-
- assert_equal '[Emacs]', get_config_variable(:@emacs_mode_string)
- end
-
- def test_string_value_with_parens
- @config.read_lines(<<~LINES.lines)
- set show-mode-in-prompt on
- set emacs-mode-string (Emacs)
- LINES
-
- assert_equal '(Emacs)', get_config_variable(:@emacs_mode_string)
- end
-
- def test_string_value_with_parens_and_quotes
- @config.read_lines(<<~LINES.lines)
- set show-mode-in-prompt on
- set emacs-mode-string "(Emacs)"
- LINES
-
- assert_equal '(Emacs)', get_config_variable(:@emacs_mode_string)
- end
-
- def test_encoding_is_ascii
- @config.reset
- Reline.core.io_gate.instance_variable_set(:@encoding, Encoding::US_ASCII)
- @config = Reline::Config.new
-
- assert_equal true, @config.convert_meta
- end
-
- def test_encoding_is_not_ascii
- @config = Reline::Config.new
-
- assert_equal false, @config.convert_meta
- end
-
- def test_invalid_keystroke
- @config.read_lines(<<~LINES.lines)
- #"a": comment
- a: error
- "b": no-error
- LINES
- key_bindings = additional_key_bindings(:emacs)
- assert_not_include key_bindings, 'a'.bytes
- assert_not_include key_bindings, nil
- assert_not_include key_bindings, []
- assert_include key_bindings, 'b'.bytes
- end
-
- def test_bind_key
- assert_equal ['input'.bytes, 'abcde'.bytes], @config.parse_key_binding('"input"', '"abcde"')
- end
-
- def test_bind_key_with_macro
-
- assert_equal ['input'.bytes, :abcde], @config.parse_key_binding('"input"', 'abcde')
- end
-
- def test_bind_key_with_escaped_chars
- assert_equal ['input'.bytes, "\e \\ \" ' \a \b \d \f \n \r \t \v".bytes], @config.parse_key_binding('"input"', '"\\e \\\\ \\" \\\' \\a \\b \\d \\f \\n \\r \\t \\v"')
- end
-
- def test_bind_key_with_ctrl_chars
- assert_equal ['input'.bytes, "\C-h\C-h\C-_".bytes], @config.parse_key_binding('"input"', '"\C-h\C-H\C-_"')
- assert_equal ['input'.bytes, "\C-h\C-h\C-_".bytes], @config.parse_key_binding('"input"', '"\Control-h\Control-H\Control-_"')
- end
-
- def test_bind_key_with_meta_chars
- assert_equal ['input'.bytes, "\eh\eH\e_".bytes], @config.parse_key_binding('"input"', '"\M-h\M-H\M-_"')
- assert_equal ['input'.bytes, "\eh\eH\e_".bytes], @config.parse_key_binding('"input"', '"\Meta-h\Meta-H\M-_"')
- end
-
- def test_bind_key_with_ctrl_meta_chars
- assert_equal ['input'.bytes, "\e\C-h\e\C-h\e\C-_".bytes], @config.parse_key_binding('"input"', '"\M-\C-h\C-\M-H\M-\C-_"')
- assert_equal ['input'.bytes, "\e\C-h\e\C-_".bytes], @config.parse_key_binding('"input"', '"\Meta-\Control-h\Control-\Meta-_"')
- end
-
- def test_bind_key_with_octal_number
- input = %w{i n p u t}.map(&:ord)
- assert_equal [input, "\1".bytes], @config.parse_key_binding('"input"', '"\1"')
- assert_equal [input, "\12".bytes], @config.parse_key_binding('"input"', '"\12"')
- assert_equal [input, "\123".bytes], @config.parse_key_binding('"input"', '"\123"')
- assert_equal [input, "\123".bytes + '4'.bytes], @config.parse_key_binding('"input"', '"\1234"')
- end
-
- def test_bind_key_with_hexadecimal_number
- input = %w{i n p u t}.map(&:ord)
- assert_equal [input, "\x4".bytes], @config.parse_key_binding('"input"', '"\x4"')
- assert_equal [input, "\x45".bytes], @config.parse_key_binding('"input"', '"\x45"')
- assert_equal [input, "\x45".bytes + '6'.bytes], @config.parse_key_binding('"input"', '"\x456"')
- end
-
- def test_include
- File.open('included_partial', 'wt') do |f|
- f.write(<<~PARTIAL_LINES)
- set show-mode-in-prompt on
- PARTIAL_LINES
- end
- @config.read_lines(<<~LINES.lines)
- $include included_partial
- LINES
-
- assert_equal true, get_config_variable(:@show_mode_in_prompt)
- end
-
- def test_include_expand_path
- home_backup = ENV['HOME']
- File.open('included_partial', 'wt') do |f|
- f.write(<<~PARTIAL_LINES)
- set show-mode-in-prompt on
- PARTIAL_LINES
- end
- ENV['HOME'] = Dir.pwd
- @config.read_lines(<<~LINES.lines)
- $include ~/included_partial
- LINES
-
- assert_equal true, get_config_variable(:@show_mode_in_prompt)
- ensure
- ENV['HOME'] = home_backup
- end
-
- def test_if
- @config.read_lines(<<~LINES.lines)
- $if Ruby
- set vi-cmd-mode-string (cmd)
- $else
- set vi-cmd-mode-string [cmd]
- $endif
- LINES
-
- assert_equal '(cmd)', get_config_variable(:@vi_cmd_mode_string)
- end
-
- def test_if_with_false
- @config.read_lines(<<~LINES.lines)
- $if Python
- set vi-cmd-mode-string (cmd)
- $else
- set vi-cmd-mode-string [cmd]
- $endif
- LINES
-
- assert_equal '[cmd]', get_config_variable(:@vi_cmd_mode_string)
- end
-
- def test_if_with_indent
- %w[Ruby Reline].each do |cond|
- @config.read_lines(<<~LINES.lines)
- set vi-cmd-mode-string {cmd}
- $if #{cond}
- set vi-cmd-mode-string (cmd)
- $else
- set vi-cmd-mode-string [cmd]
- $endif
- LINES
-
- assert_equal '(cmd)', get_config_variable(:@vi_cmd_mode_string)
- end
- end
-
- def test_nested_if_else
- @config.read_lines(<<~LINES.lines)
- $if Ruby
- "\x1": "O"
- $if NotRuby
- "\x2": "X"
- $else
- "\x3": "O"
- $if Ruby
- "\x4": "O"
- $else
- "\x5": "X"
- $endif
- "\x6": "O"
- $endif
- "\x7": "O"
- $else
- "\x8": "X"
- $if NotRuby
- "\x9": "X"
- $else
- "\xA": "X"
- $endif
- "\xB": "X"
- $endif
- "\xC": "O"
- LINES
- keys = [0x1, 0x3, 0x4, 0x6, 0x7, 0xC]
- key_bindings = keys.to_h { |k| [[k], ['O'.ord]] }
- assert_equal(key_bindings, additional_key_bindings(:emacs))
- end
-
- def test_unclosed_if
- e = assert_raise(Reline::Config::InvalidInputrc) do
- @config.read_lines(<<~LINES.lines, "INPUTRC")
- $if Ruby
- LINES
- end
- assert_equal "INPUTRC:1: unclosed if", e.message
- end
-
- def test_unmatched_else
- e = assert_raise(Reline::Config::InvalidInputrc) do
- @config.read_lines(<<~LINES.lines, "INPUTRC")
- $else
- LINES
- end
- assert_equal "INPUTRC:1: unmatched else", e.message
- end
-
- def test_unmatched_endif
- e = assert_raise(Reline::Config::InvalidInputrc) do
- @config.read_lines(<<~LINES.lines, "INPUTRC")
- $endif
- LINES
- end
- assert_equal "INPUTRC:1: unmatched endif", e.message
- end
-
- def test_if_with_mode
- @config.read_lines(<<~LINES.lines)
- $if mode=emacs
- "\C-e": history-search-backward # comment
- $else
- "\C-f": history-search-forward
- $endif
- LINES
-
- assert_equal({[5] => :history_search_backward}, additional_key_bindings(:emacs))
- assert_equal({}, additional_key_bindings(:vi_insert))
- assert_equal({}, additional_key_bindings(:vi_command))
- end
-
- def test_else
- @config.read_lines(<<~LINES.lines)
- $if mode=vi
- "\C-e": history-search-backward # comment
- $else
- "\C-f": history-search-forward
- $endif
- LINES
-
- assert_equal({[6] => :history_search_forward}, additional_key_bindings(:emacs))
- assert_equal({}, additional_key_bindings(:vi_insert))
- assert_equal({}, additional_key_bindings(:vi_command))
- end
-
- def test_if_with_invalid_mode
- @config.read_lines(<<~LINES.lines)
- $if mode=vim
- "\C-e": history-search-backward
- $else
- "\C-f": history-search-forward # comment
- $endif
- LINES
-
- assert_equal({[6] => :history_search_forward}, additional_key_bindings(:emacs))
- assert_equal({}, additional_key_bindings(:vi_insert))
- assert_equal({}, additional_key_bindings(:vi_command))
- end
-
- def test_mode_label_differs_from_keymap_label
- @config.read_lines(<<~LINES.lines)
- # Sets mode_label and keymap_label to vi
- set editing-mode vi
- # Change keymap_label to emacs. mode_label is still vi.
- set keymap emacs
- # condition=true because current mode_label is vi
- $if mode=vi
- # sets keybinding to current keymap_label=emacs
- "\C-e": history-search-backward
- $endif
- LINES
- assert_equal({[5] => :history_search_backward}, additional_key_bindings(:emacs))
- assert_equal({}, additional_key_bindings(:vi_insert))
- assert_equal({}, additional_key_bindings(:vi_command))
- end
-
- def test_if_without_else_condition
- @config.read_lines(<<~LINES.lines)
- set editing-mode vi
- $if mode=vi
- "\C-e": history-search-backward
- $endif
- LINES
-
- assert_equal({}, additional_key_bindings(:emacs))
- assert_equal({[5] => :history_search_backward}, additional_key_bindings(:vi_insert))
- assert_equal({}, additional_key_bindings(:vi_command))
- end
-
- def test_default_key_bindings
- @config.add_default_key_binding('abcd'.bytes, 'EFGH'.bytes)
- @config.read_lines(<<~'LINES'.lines)
- "abcd": "ABCD"
- "ijkl": "IJKL"
- LINES
-
- expected = { 'abcd'.bytes => 'ABCD'.bytes, 'ijkl'.bytes => 'IJKL'.bytes }
- assert_equal expected, registered_key_bindings(expected.keys)
- end
-
- def test_additional_key_bindings
- @config.read_lines(<<~'LINES'.lines)
- "ef": "EF"
- "gh": "GH"
- LINES
-
- expected = { 'ef'.bytes => 'EF'.bytes, 'gh'.bytes => 'GH'.bytes }
- assert_equal expected, registered_key_bindings(expected.keys)
- end
-
- def test_unquoted_additional_key_bindings
- @config.read_lines(<<~'LINES'.lines)
- Meta-a: "Ma"
- Control-b: "Cb"
- Meta-Control-c: "MCc"
- Control-Meta-d: "CMd"
- M-C-e: "MCe"
- C-M-f: "CMf"
- LINES
-
- expected = { "\ea".bytes => 'Ma'.bytes, "\C-b".bytes => 'Cb'.bytes, "\e\C-c".bytes => 'MCc'.bytes, "\e\C-d".bytes => 'CMd'.bytes, "\e\C-e".bytes => 'MCe'.bytes, "\e\C-f".bytes => 'CMf'.bytes }
- assert_equal expected, registered_key_bindings(expected.keys)
- end
-
- def test_additional_key_bindings_with_nesting_and_comment_out
- @config.read_lines(<<~'LINES'.lines)
- #"ab": "AB"
- #"cd": "cd"
- "ef": "EF"
- "gh": "GH"
- LINES
-
- expected = { 'ef'.bytes => 'EF'.bytes, 'gh'.bytes => 'GH'.bytes }
- assert_equal expected, registered_key_bindings(expected.keys)
- end
-
- def test_additional_key_bindings_for_other_keymap
- @config.read_lines(<<~'LINES'.lines)
- set keymap vi-command
- "ab": "AB"
- set keymap vi-insert
- "cd": "CD"
- set keymap emacs
- "ef": "EF"
- set editing-mode vi # keymap changes to be vi-insert
- LINES
-
- expected = { 'cd'.bytes => 'CD'.bytes }
- assert_equal expected, registered_key_bindings(expected.keys)
- end
-
- def test_additional_key_bindings_for_auxiliary_emacs_keymaps
- @config.read_lines(<<~'LINES'.lines)
- set keymap emacs
- "ab": "AB"
- set keymap emacs-standard
- "cd": "CD"
- set keymap emacs-ctlx
- "ef": "EF"
- set keymap emacs-meta
- "gh": "GH"
- set editing-mode emacs # keymap changes to be emacs
- LINES
-
- expected = {
- 'ab'.bytes => 'AB'.bytes,
- 'cd'.bytes => 'CD'.bytes,
- "\C-xef".bytes => 'EF'.bytes,
- "\egh".bytes => 'GH'.bytes,
- }
- assert_equal expected, registered_key_bindings(expected.keys)
- end
-
- def test_key_bindings_with_reset
- # @config.reset is called after each readline.
- # inputrc file is read once, so key binding shouldn't be cleared by @config.reset
- @config.add_default_key_binding('default'.bytes, 'DEFAULT'.bytes)
- @config.read_lines(<<~'LINES'.lines)
- "additional": "ADDITIONAL"
- LINES
- @config.reset
- expected = { 'default'.bytes => 'DEFAULT'.bytes, 'additional'.bytes => 'ADDITIONAL'.bytes }
- assert_equal expected, registered_key_bindings(expected.keys)
- end
-
- def test_history_size
- @config.read_lines(<<~LINES.lines)
- set history-size 5000
- LINES
-
- assert_equal 5000, get_config_variable(:@history_size)
- history = Reline::History.new(@config)
- history << "a\n"
- assert_equal 1, history.size
- end
-
- def test_empty_inputrc_env
- inputrc_backup = ENV['INPUTRC']
- ENV['INPUTRC'] = ''
- assert_nothing_raised do
- @config.read
- end
- ensure
- ENV['INPUTRC'] = inputrc_backup
- end
-
- def test_inputrc
- inputrc_backup = ENV['INPUTRC']
- expected = "#{@tmpdir}/abcde"
- ENV['INPUTRC'] = expected
- assert_equal expected, @config.inputrc_path
- ensure
- ENV['INPUTRC'] = inputrc_backup
- end
-
- def test_inputrc_raw_value
- @config.read_lines(<<~'LINES'.lines)
- set editing-mode vi ignored-string
- set vi-ins-mode-string aaa aaa
- set vi-cmd-mode-string bbb ccc # comment
- LINES
- assert_equal :vi_insert, get_config_variable(:@editing_mode_label)
- assert_equal 'aaa aaa', @config.vi_ins_mode_string
- assert_equal 'bbb ccc # comment', @config.vi_cmd_mode_string
- end
-
- def test_inputrc_with_utf8
- # This file is encoded by UTF-8 so this heredoc string is also UTF-8.
- @config.read_lines(<<~'LINES'.lines)
- set editing-mode vi
- set vi-cmd-mode-string 🍸
- set vi-ins-mode-string 🍶
- LINES
- assert_equal '🍸', @config.vi_cmd_mode_string
- assert_equal '🍶', @config.vi_ins_mode_string
- rescue Reline::ConfigEncodingConversionError
- # do nothing
- end
-
- def test_inputrc_with_eucjp
- @config.read_lines(<<~"LINES".encode(Encoding::EUC_JP).lines)
- set editing-mode vi
- set vi-cmd-mode-string ォャッ
- set vi-ins-mode-string 能
- LINES
- assert_equal 'ォャッ'.encode(Reline.encoding_system_needs), @config.vi_cmd_mode_string
- assert_equal '能'.encode(Reline.encoding_system_needs), @config.vi_ins_mode_string
- rescue Reline::ConfigEncodingConversionError
- # do nothing
- end
-
- def test_empty_inputrc
- assert_nothing_raised do
- @config.read_lines([])
- end
- end
-
- def test_xdg_config_home
- home_backup = ENV['HOME']
- xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
- inputrc_backup = ENV['INPUTRC']
- xdg_config_home = File.expand_path("#{@tmpdir}/.config/example_dir")
- expected = File.expand_path("#{xdg_config_home}/readline/inputrc")
- FileUtils.mkdir_p(File.dirname(expected))
- FileUtils.touch(expected)
- ENV['HOME'] = @tmpdir
- ENV['XDG_CONFIG_HOME'] = xdg_config_home
- ENV['INPUTRC'] = ''
- assert_equal expected, @config.inputrc_path
- ensure
- FileUtils.rm(expected)
- ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup
- ENV['HOME'] = home_backup
- ENV['INPUTRC'] = inputrc_backup
- end
-
- def test_empty_xdg_config_home
- home_backup = ENV['HOME']
- xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
- inputrc_backup = ENV['INPUTRC']
- ENV['HOME'] = @tmpdir
- ENV['XDG_CONFIG_HOME'] = ''
- ENV['INPUTRC'] = ''
- expected = File.expand_path('~/.config/readline/inputrc')
- FileUtils.mkdir_p(File.dirname(expected))
- FileUtils.touch(expected)
- assert_equal expected, @config.inputrc_path
- ensure
- FileUtils.rm(expected)
- ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup
- ENV['HOME'] = home_backup
- ENV['INPUTRC'] = inputrc_backup
- end
-
- def test_relative_xdg_config_home
- home_backup = ENV['HOME']
- xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
- inputrc_backup = ENV['INPUTRC']
- ENV['HOME'] = @tmpdir
- ENV['INPUTRC'] = ''
- expected = File.expand_path('~/.config/readline/inputrc')
- FileUtils.mkdir_p(File.dirname(expected))
- FileUtils.touch(expected)
- result = Dir.chdir(@tmpdir) do
- xdg_config_home = ".config/example_dir"
- ENV['XDG_CONFIG_HOME'] = xdg_config_home
- inputrc = "#{xdg_config_home}/readline/inputrc"
- FileUtils.mkdir_p(File.dirname(inputrc))
- FileUtils.touch(inputrc)
- @config.inputrc_path
- end
- assert_equal expected, result
- FileUtils.rm(expected)
- ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup
- ENV['HOME'] = home_backup
- ENV['INPUTRC'] = inputrc_backup
- end
-
- def test_reload
- inputrc = "#{@tmpdir}/inputrc"
- ENV['INPUTRC'] = inputrc
-
- File.write(inputrc, "set emacs-mode-string !")
- @config.read
- assert_equal '!', @config.emacs_mode_string
-
- File.write(inputrc, "set emacs-mode-string ?")
- @config.reload
- assert_equal '?', @config.emacs_mode_string
-
- File.write(inputrc, "")
- @config.reload
- assert_equal '@', @config.emacs_mode_string
- end
-end
diff --git a/test/reline/test_face.rb b/test/reline/test_face.rb
deleted file mode 100644
index 8fa2be8fa4..0000000000
--- a/test/reline/test_face.rb
+++ /dev/null
@@ -1,257 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'helper'
-
-class Reline::Face::Test < Reline::TestCase
- RESET_SGR = "\e[0m"
-
- def setup
- @colorterm_backup = ENV['COLORTERM']
- ENV['COLORTERM'] = 'truecolor'
- end
-
- def teardown
- Reline::Face.reset_to_initial_configs
- ENV['COLORTERM'] = @colorterm_backup
- end
-
- class WithInsufficientSetupTest < self
- def setup
- super
- Reline::Face.config(:my_insufficient_config) do |face|
- end
- @face = Reline::Face[:my_insufficient_config]
- end
-
- def test_my_insufficient_config_line
- assert_equal RESET_SGR, @face[:default]
- assert_equal RESET_SGR, @face[:enhanced]
- assert_equal RESET_SGR, @face[:scrollbar]
- end
-
- def test_my_insufficient_configs
- my_configs = Reline::Face.configs[:my_insufficient_config]
- assert_equal(
- {
- default: { style: :reset, escape_sequence: RESET_SGR },
- enhanced: { style: :reset, escape_sequence: RESET_SGR },
- scrollbar: { style: :reset, escape_sequence: RESET_SGR }
- },
- my_configs
- )
- end
- end
-
- class WithSetupTest < self
- def setup
- super
- Reline::Face.config(:my_config) do |face|
- face.define :default, foreground: :blue
- face.define :enhanced, foreground: "#FF1020", background: :black, style: [:bold, :underlined]
- end
- Reline::Face.config(:another_config) do |face|
- face.define :another_label, foreground: :red
- end
- @face = Reline::Face[:my_config]
- end
-
- def test_now_there_are_four_configs
- assert_equal %i(default completion_dialog my_config another_config), Reline::Face.configs.keys
- end
-
- def test_resetting_config_discards_user_defined_configs
- Reline::Face.reset_to_initial_configs
- assert_equal %i(default completion_dialog), Reline::Face.configs.keys
- end
-
- def test_my_configs
- my_configs = Reline::Face.configs[:my_config]
- assert_equal(
- {
- default: {
- escape_sequence: "#{RESET_SGR}\e[34m", foreground: :blue
- },
- enhanced: {
- background: :black,
- foreground: "#FF1020",
- style: [:bold, :underlined],
- escape_sequence: "\e[0m\e[38;2;255;16;32;40;1;4m"
- },
- scrollbar: {
- style: :reset,
- escape_sequence: "\e[0m"
- }
- },
- my_configs
- )
- end
-
- def test_my_config_line
- assert_equal "#{RESET_SGR}\e[34m", @face[:default]
- end
-
- def test_my_config_enhanced
- assert_equal "#{RESET_SGR}\e[38;2;255;16;32;40;1;4m", @face[:enhanced]
- end
-
- def test_not_respond_to_another_label
- assert_equal false, @face.respond_to?(:another_label)
- end
- end
-
- class WithoutSetupTest < self
- def test_my_config_default
- Reline::Face.config(:my_config) do |face|
- # do nothing
- end
- face = Reline::Face[:my_config]
- assert_equal RESET_SGR, face[:default]
- end
-
- def test_style_does_not_exist
- face = Reline::Face[:default]
- assert_raise ArgumentError do
- face[:style_does_not_exist]
- end
- end
-
- def test_invalid_keyword
- assert_raise ArgumentError do
- Reline::Face.config(:invalid_config) do |face|
- face.define :default, invalid_keyword: :red
- end
- end
- end
-
- def test_invalid_foreground_name
- assert_raise ArgumentError do
- Reline::Face.config(:invalid_config) do |face|
- face.define :default, foreground: :invalid_name
- end
- end
- end
-
- def test_invalid_background_name
- assert_raise ArgumentError do
- Reline::Face.config(:invalid_config) do |face|
- face.define :default, background: :invalid_name
- end
- end
- end
-
- def test_invalid_style_name
- assert_raise ArgumentError do
- Reline::Face.config(:invalid_config) do |face|
- face.define :default, style: :invalid_name
- end
- end
- end
-
- def test_private_constants
- [:SGR_PARAMETER, :Config, :CONFIGS].each do |name|
- assert_equal false, Reline::Face.constants.include?(name)
- end
- end
- end
-
- class ConfigTest < self
- def setup
- super
- @config = Reline::Face.const_get(:Config).new(:my_config) { }
- end
-
- def teardown
- super
- Reline::Face.instance_variable_set(:@force_truecolor, nil)
- end
-
- def test_rgb?
- assert_equal true, @config.send(:rgb_expression?, "#FFFFFF")
- end
-
- def test_invalid_rgb?
- assert_equal false, @config.send(:rgb_expression?, "FFFFFF")
- assert_equal false, @config.send(:rgb_expression?, "#FFFFF")
- end
-
- def test_format_to_sgr_preserves_order
- assert_equal(
- "#{RESET_SGR}\e[37;41;1;3m",
- @config.send(:format_to_sgr, foreground: :white, background: :red, style: [:bold, :italicized])
- )
-
- assert_equal(
- "#{RESET_SGR}\e[37;1;3;41m",
- @config.send(:format_to_sgr, foreground: :white, style: [:bold, :italicized], background: :red)
- )
- end
-
- def test_format_to_sgr_with_reset
- assert_equal(
- RESET_SGR,
- @config.send(:format_to_sgr, style: :reset)
- )
- assert_equal(
- "#{RESET_SGR}\e[37;0;41m",
- @config.send(:format_to_sgr, foreground: :white, style: :reset, background: :red)
- )
- end
-
- def test_format_to_sgr_with_single_style
- assert_equal(
- "#{RESET_SGR}\e[37;41;1m",
- @config.send(:format_to_sgr, foreground: :white, background: :red, style: :bold)
- )
- end
-
- def test_truecolor
- ENV['COLORTERM'] = 'truecolor'
- assert_equal true, Reline::Face.truecolor?
- ENV['COLORTERM'] = '24bit'
- assert_equal true, Reline::Face.truecolor?
- ENV['COLORTERM'] = nil
- assert_equal false, Reline::Face.truecolor?
- Reline::Face.force_truecolor
- assert_equal true, Reline::Face.truecolor?
- end
-
- def test_sgr_rgb_truecolor
- ENV['COLORTERM'] = 'truecolor'
- assert_equal "38;2;255;255;255", @config.send(:sgr_rgb, :foreground, "#ffffff")
- assert_equal "48;2;18;52;86", @config.send(:sgr_rgb, :background, "#123456")
- end
-
- def test_sgr_rgb_256color
- ENV['COLORTERM'] = nil
- assert_equal '38;5;231', @config.send(:sgr_rgb, :foreground, '#ffffff')
- assert_equal '48;5;16', @config.send(:sgr_rgb, :background, '#000000')
- # Color steps are [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
- assert_equal '38;5;24', @config.send(:sgr_rgb, :foreground, '#005f87')
- assert_equal '38;5;67', @config.send(:sgr_rgb, :foreground, '#5f87af')
- assert_equal '48;5;110', @config.send(:sgr_rgb, :background, '#87afd7')
- assert_equal '48;5;153', @config.send(:sgr_rgb, :background, '#afd7ff')
- # Boundary values are [0x30, 0x73, 0x9b, 0xc3, 0xeb]
- assert_equal '38;5;24', @config.send(:sgr_rgb, :foreground, '#2f729a')
- assert_equal '38;5;67', @config.send(:sgr_rgb, :foreground, '#30739b')
- assert_equal '48;5;110', @config.send(:sgr_rgb, :background, '#9ac2ea')
- assert_equal '48;5;153', @config.send(:sgr_rgb, :background, '#9bc3eb')
- end
-
- def test_force_truecolor_reconfigure
- ENV['COLORTERM'] = nil
-
- Reline::Face.config(:my_config) do |face|
- face.define :default, foreground: '#005f87'
- face.define :enhanced, background: '#afd7ff'
- end
-
- assert_equal "\e[0m\e[38;5;24m", Reline::Face[:my_config][:default]
- assert_equal "\e[0m\e[48;5;153m", Reline::Face[:my_config][:enhanced]
-
- Reline::Face.force_truecolor
-
- assert_equal "\e[0m\e[38;2;0;95;135m", Reline::Face[:my_config][:default]
- assert_equal "\e[0m\e[48;2;175;215;255m", Reline::Face[:my_config][:enhanced]
- end
- end
-end
diff --git a/test/reline/test_history.rb b/test/reline/test_history.rb
deleted file mode 100644
index ea902b0653..0000000000
--- a/test/reline/test_history.rb
+++ /dev/null
@@ -1,317 +0,0 @@
-require_relative 'helper'
-require "reline/history"
-
-class Reline::History::Test < Reline::TestCase
- def setup
- Reline.send(:test_mode)
- end
-
- def teardown
- Reline.test_reset
- end
-
- def test_ancestors
- assert_equal(Reline::History.ancestors.include?(Array), true)
- end
-
- def test_to_s
- history = history_new
- expected = "HISTORY"
- assert_equal(expected, history.to_s)
- end
-
- def test_get
- history, lines = lines = history_new_and_push_history(5)
- lines.each_with_index do |s, i|
- assert_external_string_equal(s, history[i])
- end
- end
-
- def test_get__negative
- history, lines = lines = history_new_and_push_history(5)
- (1..5).each do |i|
- assert_equal(lines[-i], history[-i])
- end
- end
-
- def test_get__out_of_range
- history, _ = history_new_and_push_history(5)
- invalid_indexes = [5, 6, 100, -6, -7, -100]
- invalid_indexes.each do |i|
- assert_raise(IndexError, "i=<#{i}>") do
- history[i]
- end
- end
-
- invalid_indexes = [100_000_000_000_000_000_000,
- -100_000_000_000_000_000_000]
- invalid_indexes.each do |i|
- assert_raise(RangeError, "i=<#{i}>") do
- history[i]
- end
- end
- end
-
- def test_set
- begin
- history, _ = history_new_and_push_history(5)
- 5.times do |i|
- expected = "set: #{i}"
- history[i] = expected
- assert_external_string_equal(expected, history[i])
- end
- rescue NotImplementedError
- end
- end
-
- def test_set__out_of_range
- history = history_new
- assert_raise(IndexError, NotImplementedError, "index=<0>") do
- history[0] = "set: 0"
- end
-
- history, _ = history_new_and_push_history(5)
- invalid_indexes = [5, 6, 100, -6, -7, -100]
- invalid_indexes.each do |i|
- assert_raise(IndexError, NotImplementedError, "index=<#{i}>") do
- history[i] = "set: #{i}"
- end
- end
-
- invalid_indexes = [100_000_000_000_000_000_000,
- -100_000_000_000_000_000_000]
- invalid_indexes.each do |i|
- assert_raise(RangeError, NotImplementedError, "index=<#{i}>") do
- history[i] = "set: #{i}"
- end
- end
- end
-
- def test_push
- history = history_new
- 5.times do |i|
- s = i.to_s
- assert_equal(history, history.push(s))
- assert_external_string_equal(s, history[i])
- end
- assert_equal(5, history.length)
- end
-
- def test_push__operator
- history = history_new
- 5.times do |i|
- s = i.to_s
- assert_equal(history, history << s)
- assert_external_string_equal(s, history[i])
- end
- assert_equal(5, history.length)
- end
-
- def test_push__plural
- history = history_new
- assert_equal(history, history.push("0", "1", "2", "3", "4"))
- (0..4).each do |i|
- assert_external_string_equal(i.to_s, history[i])
- end
- assert_equal(5, history.length)
-
- assert_equal(history, history.push("5", "6", "7", "8", "9"))
- (5..9).each do |i|
- assert_external_string_equal(i.to_s, history[i])
- end
- assert_equal(10, history.length)
- end
-
- def test_pop
- history = history_new
- begin
- assert_equal(nil, history.pop)
-
- history, lines = lines = history_new_and_push_history(5)
- (1..5).each do |i|
- assert_external_string_equal(lines[-i], history.pop)
- assert_equal(lines.length - i, history.length)
- end
-
- assert_equal(nil, history.pop)
- rescue NotImplementedError
- end
- end
-
- def test_shift
- history = history_new
- begin
- assert_equal(nil, history.shift)
-
- history, lines = lines = history_new_and_push_history(5)
- (0..4).each do |i|
- assert_external_string_equal(lines[i], history.shift)
- assert_equal(lines.length - (i + 1), history.length)
- end
-
- assert_equal(nil, history.shift)
- rescue NotImplementedError
- end
- end
-
- def test_each
- history = history_new
- e = history.each do |s|
- assert(false) # not reachable
- end
- assert_equal(history, e)
- history, lines = lines = history_new_and_push_history(5)
- i = 0
- e = history.each do |s|
- assert_external_string_equal(history[i], s)
- assert_external_string_equal(lines[i], s)
- i += 1
- end
- assert_equal(history, e)
- end
-
- def test_each__enumerator
- history = history_new
- e = history.each
- assert_instance_of(Enumerator, e)
- end
-
- def test_length
- history = history_new
- assert_equal(0, history.length)
- push_history(history, 1)
- assert_equal(1, history.length)
- push_history(history, 4)
- assert_equal(5, history.length)
- history.clear
- assert_equal(0, history.length)
- end
-
- def test_empty_p
- history = history_new
- 2.times do
- assert(history.empty?)
- history.push("s")
- assert_equal(false, history.empty?)
- history.clear
- assert(history.empty?)
- end
- end
-
- def test_delete_at
- begin
- history, lines = lines = history_new_and_push_history(5)
- (0..4).each do |i|
- assert_external_string_equal(lines[i], history.delete_at(0))
- end
- assert(history.empty?)
-
- history, lines = lines = history_new_and_push_history(5)
- (1..5).each do |i|
- assert_external_string_equal(lines[lines.length - i], history.delete_at(-1))
- end
- assert(history.empty?)
-
- history, lines = lines = history_new_and_push_history(5)
- assert_external_string_equal(lines[0], history.delete_at(0))
- assert_external_string_equal(lines[4], history.delete_at(3))
- assert_external_string_equal(lines[1], history.delete_at(0))
- assert_external_string_equal(lines[3], history.delete_at(1))
- assert_external_string_equal(lines[2], history.delete_at(0))
- assert(history.empty?)
- rescue NotImplementedError
- end
- end
-
- def test_delete_at__out_of_range
- history = history_new
- assert_raise(IndexError, NotImplementedError, "index=<0>") do
- history.delete_at(0)
- end
-
- history, _ = history_new_and_push_history(5)
- invalid_indexes = [5, 6, 100, -6, -7, -100]
- invalid_indexes.each do |i|
- assert_raise(IndexError, NotImplementedError, "index=<#{i}>") do
- history.delete_at(i)
- end
- end
-
- invalid_indexes = [100_000_000_000_000_000_000,
- -100_000_000_000_000_000_000]
- invalid_indexes.each do |i|
- assert_raise(RangeError, NotImplementedError, "index=<#{i}>") do
- history.delete_at(i)
- end
- end
- end
-
- def test_history_size_zero
- history = history_new(history_size: 0)
- assert_equal 0, history.size
- history << 'aa'
- history << 'bb'
- assert_equal 0, history.size
- history.push(*%w{aa bb cc})
- assert_equal 0, history.size
- end
-
- def test_history_size_negative_unlimited
- history = history_new(history_size: -1)
- assert_equal 0, history.size
- history << 'aa'
- history << 'bb'
- assert_equal 2, history.size
- history.push(*%w{aa bb cc})
- assert_equal 5, history.size
- end
-
- def test_history_encoding_conversion
- history = history_new
- text1 = String.new("a\u{65535}b\xFFc", encoding: Encoding::UTF_8)
- text2 = String.new("d\xFFe", encoding: Encoding::Shift_JIS)
- history.push(text1.dup, text2.dup)
- expected = [text1, text2].map { |s| s.encode(Reline.encoding_system_needs, invalid: :replace, undef: :replace) }
- assert_equal(expected, history.to_a)
- end
-
- private
-
- def history_new(history_size: 10)
- Reline::History.new(Struct.new(:history_size).new(history_size))
- end
-
- def push_history(history, num)
- lines = []
- num.times do |i|
- s = "a"
- i.times do
- s = s.succ
- end
- lines.push("#{i + 1}:#{s}")
- end
- history.push(*lines)
- return history, lines
- end
-
- def history_new_and_push_history(num)
- history = history_new(history_size: 100)
- return push_history(history, num)
- end
-
- def assert_external_string_equal(expected, actual)
- assert_equal(expected, actual)
- mes = "Encoding of #{actual.inspect} is expected #{get_default_internal_encoding.inspect} but #{actual.encoding}"
- assert_equal(get_default_internal_encoding, actual.encoding, mes)
- end
-
- def get_default_internal_encoding
- if encoding = Reline.core.encoding
- encoding
- elsif RUBY_PLATFORM =~ /mswin|mingw/
- Encoding.default_internal || Encoding::UTF_8
- else
- Encoding.default_internal || Encoding.find("locale")
- end
- end
-end
diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb
deleted file mode 100644
index 78b4c936b9..0000000000
--- a/test/reline/test_key_actor_emacs.rb
+++ /dev/null
@@ -1,1743 +0,0 @@
-require_relative 'helper'
-
-class Reline::KeyActor::EmacsTest < Reline::TestCase
- def setup
- Reline.send(:test_mode)
- @prompt = '> '
- @config = Reline::Config.new # Emacs mode is default
- @config.autocompletion = false
- Reline::HISTORY.instance_variable_set(:@config, @config)
- Reline::HISTORY.clear
- @encoding = Reline.core.encoding
- @line_editor = Reline::LineEditor.new(@config)
- @line_editor.reset(@prompt)
- end
-
- def teardown
- Reline.test_reset
- end
-
- def test_ed_insert_one
- input_keys('a')
- assert_line_around_cursor('a', '')
- end
-
- def test_ed_insert_two
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- end
-
- def test_ed_insert_mbchar_one
- input_keys('か')
- assert_line_around_cursor('か', '')
- end
-
- def test_ed_insert_mbchar_two
- input_keys('かき')
- assert_line_around_cursor('かき', '')
- end
-
- def test_ed_insert_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099")
- assert_line_around_cursor("か\u3099", '')
- end
-
- def test_ed_insert_for_plural_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099")
- assert_line_around_cursor("か\u3099き\u3099", '')
- end
-
- def test_move_next_and_prev
- input_keys('abd')
- assert_line_around_cursor('abd', '')
- input_keys("\C-b")
- assert_line_around_cursor('ab', 'd')
- input_keys("\C-b")
- assert_line_around_cursor('a', 'bd')
- input_keys("\C-f")
- assert_line_around_cursor('ab', 'd')
- input_keys('c')
- assert_line_around_cursor('abc', 'd')
- end
-
- def test_move_next_and_prev_for_mbchar
- input_keys('かきけ')
- assert_line_around_cursor('かきけ', '')
- input_keys("\C-b")
- assert_line_around_cursor('かき', 'け')
- input_keys("\C-b")
- assert_line_around_cursor('か', 'きけ')
- input_keys("\C-f")
- assert_line_around_cursor('かき', 'け')
- input_keys('く')
- assert_line_around_cursor('かきく', 'け')
- end
-
- def test_move_next_and_prev_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099け\u3099")
- assert_line_around_cursor("か\u3099き\u3099け\u3099", '')
- input_keys("\C-b")
- assert_line_around_cursor("か\u3099き\u3099", "け\u3099")
- input_keys("\C-b")
- assert_line_around_cursor("か\u3099", "き\u3099け\u3099")
- input_keys("\C-f")
- assert_line_around_cursor("か\u3099き\u3099", "け\u3099")
- input_keys("く\u3099")
- assert_line_around_cursor("か\u3099き\u3099く\u3099", "け\u3099")
- end
-
- def test_move_to_beg_end
- input_keys('bcd')
- assert_line_around_cursor('bcd', '')
- input_keys("\C-a")
- assert_line_around_cursor('', 'bcd')
- input_keys('a')
- assert_line_around_cursor('a', 'bcd')
- input_keys("\C-e")
- assert_line_around_cursor('abcd', '')
- input_keys('e')
- assert_line_around_cursor('abcde', '')
- end
-
- def test_ed_newline_with_cr
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- refute(@line_editor.finished?)
- input_keys("\C-m")
- assert_line_around_cursor('ab', '')
- assert(@line_editor.finished?)
- end
-
- def test_ed_newline_with_lf
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- refute(@line_editor.finished?)
- input_keys("\C-j")
- assert_line_around_cursor('ab', '')
- assert(@line_editor.finished?)
- end
-
- def test_em_delete_prev_char
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- input_keys("\C-h")
- assert_line_around_cursor('a', '')
- end
-
- def test_em_delete_prev_char_for_mbchar
- input_keys('かき')
- assert_line_around_cursor('かき', '')
- input_keys("\C-h")
- assert_line_around_cursor('か', '')
- end
-
- def test_em_delete_prev_char_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099")
- assert_line_around_cursor("か\u3099き\u3099", '')
- input_keys("\C-h")
- assert_line_around_cursor("か\u3099", '')
- end
-
- def test_bracketed_paste_insert
- set_line_around_cursor('A', 'Z')
- input_key_by_symbol(:insert_multiline_text, char: "abc\n\C-abc")
- assert_whole_lines(['Aabc', "\C-abcZ"])
- assert_line_around_cursor("\C-abc", 'Z')
- end
-
- def test_ed_quoted_insert
- set_line_around_cursor('A', 'Z')
- input_key_by_symbol(:insert_raw_char, char: "\C-a")
- assert_line_around_cursor("A\C-a", 'Z')
- end
-
- def test_ed_quoted_insert_with_vi_arg
- input_keys("a\C-[3")
- input_key_by_symbol(:insert_raw_char, char: "\C-a")
- input_keys("b\C-[4")
- input_key_by_symbol(:insert_raw_char, char: '1')
- assert_line_around_cursor("a\C-a\C-a\C-ab1111", '')
- end
-
- def test_ed_kill_line
- input_keys("\C-k")
- assert_line_around_cursor('', '')
- input_keys('abc')
- assert_line_around_cursor('abc', '')
- input_keys("\C-k")
- assert_line_around_cursor('abc', '')
- input_keys("\C-b\C-k")
- assert_line_around_cursor('ab', '')
- end
-
- def test_em_kill_line
- input_key_by_symbol(:em_kill_line)
- assert_line_around_cursor('', '')
- input_keys('abc')
- input_key_by_symbol(:em_kill_line)
- assert_line_around_cursor('', '')
- input_keys('abc')
- input_keys("\C-b")
- input_key_by_symbol(:em_kill_line)
- assert_line_around_cursor('', '')
- input_keys('abc')
- input_keys("\C-a")
- input_key_by_symbol(:em_kill_line)
- assert_line_around_cursor('', '')
- end
-
- def test_ed_move_to_beg
- input_keys('abd')
- assert_line_around_cursor('abd', '')
- input_keys("\C-b")
- assert_line_around_cursor('ab', 'd')
- input_keys('c')
- assert_line_around_cursor('abc', 'd')
- input_keys("\C-a")
- assert_line_around_cursor('', 'abcd')
- input_keys('012')
- assert_line_around_cursor('012', 'abcd')
- input_keys("\C-a")
- assert_line_around_cursor('', '012abcd')
- input_keys('ABC')
- assert_line_around_cursor('ABC', '012abcd')
- input_keys("\C-f" * 10 + "\C-a")
- assert_line_around_cursor('', 'ABC012abcd')
- input_keys('a')
- assert_line_around_cursor('a', 'ABC012abcd')
- end
-
- def test_ed_move_to_beg_with_blank
- input_keys(' abc')
- assert_line_around_cursor(' abc', '')
- input_keys("\C-a")
- assert_line_around_cursor('', ' abc')
- end
-
- def test_ed_move_to_end
- input_keys('abd')
- assert_line_around_cursor('abd', '')
- input_keys("\C-b")
- assert_line_around_cursor('ab', 'd')
- input_keys('c')
- assert_line_around_cursor('abc', 'd')
- input_keys("\C-e")
- assert_line_around_cursor('abcd', '')
- input_keys('012')
- assert_line_around_cursor('abcd012', '')
- input_keys("\C-e")
- assert_line_around_cursor('abcd012', '')
- input_keys('ABC')
- assert_line_around_cursor('abcd012ABC', '')
- input_keys("\C-b" * 10 + "\C-e")
- assert_line_around_cursor('abcd012ABC', '')
- input_keys('a')
- assert_line_around_cursor('abcd012ABCa', '')
- end
-
- def test_em_delete
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- input_keys("\C-a")
- assert_line_around_cursor('', 'ab')
- input_keys("\C-d")
- assert_line_around_cursor('', 'b')
- end
-
- def test_em_delete_for_mbchar
- input_keys('かき')
- assert_line_around_cursor('かき', '')
- input_keys("\C-a")
- assert_line_around_cursor('', 'かき')
- input_keys("\C-d")
- assert_line_around_cursor('', 'き')
- end
-
- def test_em_delete_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099")
- assert_line_around_cursor("か\u3099き\u3099", '')
- input_keys("\C-a")
- assert_line_around_cursor('', "か\u3099き\u3099")
- input_keys("\C-d")
- assert_line_around_cursor('', "き\u3099")
- end
-
- def test_em_delete_ends_editing
- input_keys("\C-d") # quit from inputing
- assert_nil(@line_editor.line)
- assert(@line_editor.finished?)
- end
-
- def test_ed_clear_screen
- @line_editor.instance_variable_get(:@rendered_screen).lines = [[]]
- input_keys("\C-l")
- assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines)
- end
-
- def test_ed_clear_screen_with_inputted
- input_keys('abc')
- input_keys("\C-b")
- @line_editor.instance_variable_get(:@rendered_screen).lines = [[]]
- assert_line_around_cursor('ab', 'c')
- input_keys("\C-l")
- assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines)
- assert_line_around_cursor('ab', 'c')
- end
-
- def test_key_delete
- input_keys('abc')
- assert_line_around_cursor('abc', '')
- input_key_by_symbol(:key_delete)
- assert_line_around_cursor('abc', '')
- end
-
- def test_key_delete_does_not_end_editing
- input_key_by_symbol(:key_delete)
- assert_line_around_cursor('', '')
- refute(@line_editor.finished?)
- end
-
- def test_key_delete_preserves_cursor
- input_keys('abc')
- input_keys("\C-b")
- assert_line_around_cursor('ab', 'c')
- input_key_by_symbol(:key_delete)
- assert_line_around_cursor('ab', '')
- end
-
- def test_em_next_word
- assert_line_around_cursor('', '')
- input_keys('abc def{bbb}ccc')
- input_keys("\C-a\eF")
- assert_line_around_cursor('abc', ' def{bbb}ccc')
- input_keys("\eF")
- assert_line_around_cursor('abc def', '{bbb}ccc')
- input_keys("\eF")
- assert_line_around_cursor('abc def{bbb', '}ccc')
- input_keys("\eF")
- assert_line_around_cursor('abc def{bbb}ccc', '')
- input_keys("\eF")
- assert_line_around_cursor('abc def{bbb}ccc', '')
- end
-
- def test_em_next_word_for_mbchar
- assert_line_around_cursor('', '')
- input_keys('あいう かきく{さしす}たちつ')
- input_keys("\C-a\eF")
- assert_line_around_cursor('あいう', ' かきく{さしす}たちつ')
- input_keys("\eF")
- assert_line_around_cursor('あいう かきく', '{さしす}たちつ')
- input_keys("\eF")
- assert_line_around_cursor('あいう かきく{さしす', '}たちつ')
- input_keys("\eF")
- assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
- input_keys("\eF")
- assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
- end
-
- def test_em_next_word_for_mbchar_by_plural_code_points
- omit_unless_utf8
- assert_line_around_cursor("", "")
- input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- input_keys("\C-a\eF")
- assert_line_around_cursor("あいう", " か\u3099き\u3099く\u3099{さしす}たちつ")
- input_keys("\eF")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099", "{さしす}たちつ")
- input_keys("\eF")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす", "}たちつ")
- input_keys("\eF")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "")
- input_keys("\eF")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "")
- end
-
- def test_em_prev_word
- input_keys('abc def{bbb}ccc')
- assert_line_around_cursor('abc def{bbb}ccc', '')
- input_keys("\eB")
- assert_line_around_cursor('abc def{bbb}', 'ccc')
- input_keys("\eB")
- assert_line_around_cursor('abc def{', 'bbb}ccc')
- input_keys("\eB")
- assert_line_around_cursor('abc ', 'def{bbb}ccc')
- input_keys("\eB")
- assert_line_around_cursor('', 'abc def{bbb}ccc')
- input_keys("\eB")
- assert_line_around_cursor('', 'abc def{bbb}ccc')
- end
-
- def test_em_prev_word_for_mbchar
- input_keys('あいう かきく{さしす}たちつ')
- assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
- input_keys("\eB")
- assert_line_around_cursor('あいう かきく{さしす}', 'たちつ')
- input_keys("\eB")
- assert_line_around_cursor('あいう かきく{', 'さしす}たちつ')
- input_keys("\eB")
- assert_line_around_cursor('あいう ', 'かきく{さしす}たちつ')
- input_keys("\eB")
- assert_line_around_cursor('', 'あいう かきく{さしす}たちつ')
- input_keys("\eB")
- assert_line_around_cursor('', 'あいう かきく{さしす}たちつ')
- end
-
- def test_em_prev_word_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "")
- input_keys("\eB")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", "たちつ")
- input_keys("\eB")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", "さしす}たちつ")
- input_keys("\eB")
- assert_line_around_cursor("あいう ", "か\u3099き\u3099く\u3099{さしす}たちつ")
- input_keys("\eB")
- assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- input_keys("\eB")
- assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- end
-
- def test_em_delete_next_word
- input_keys('abc def{bbb}ccc')
- input_keys("\C-a")
- assert_line_around_cursor('', 'abc def{bbb}ccc')
- input_keys("\ed")
- assert_line_around_cursor('', ' def{bbb}ccc')
- input_keys("\ed")
- assert_line_around_cursor('', '{bbb}ccc')
- input_keys("\ed")
- assert_line_around_cursor('', '}ccc')
- input_keys("\ed")
- assert_line_around_cursor('', '')
- end
-
- def test_em_delete_next_word_for_mbchar
- input_keys('あいう かきく{さしす}たちつ')
- input_keys("\C-a")
- assert_line_around_cursor('', 'あいう かきく{さしす}たちつ')
- input_keys("\ed")
- assert_line_around_cursor('', ' かきく{さしす}たちつ')
- input_keys("\ed")
- assert_line_around_cursor('', '{さしす}たちつ')
- input_keys("\ed")
- assert_line_around_cursor('', '}たちつ')
- input_keys("\ed")
- assert_line_around_cursor('', '')
- end
-
- def test_em_delete_next_word_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- input_keys("\C-a")
- assert_line_around_cursor('', "あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- input_keys("\ed")
- assert_line_around_cursor('', " か\u3099き\u3099く\u3099{さしす}たちつ")
- input_keys("\ed")
- assert_line_around_cursor('', '{さしす}たちつ')
- input_keys("\ed")
- assert_line_around_cursor('', '}たちつ')
- input_keys("\ed")
- assert_line_around_cursor('', '')
- end
-
- def test_ed_delete_prev_word
- input_keys('abc def{bbb}ccc')
- assert_line_around_cursor('abc def{bbb}ccc', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('abc def{bbb}', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('abc def{', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('abc ', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('', '')
- end
-
- def test_ed_delete_prev_word_for_mbchar
- input_keys('あいう かきく{さしす}たちつ')
- assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('あいう かきく{さしす}', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('あいう かきく{', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('あいう ', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('', '')
- end
-
- def test_ed_delete_prev_word_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '')
- input_keys("\e\C-H")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '')
- input_keys("\e\C-H")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '')
- input_keys("\e\C-H")
- assert_line_around_cursor('あいう ', '')
- input_keys("\e\C-H")
- assert_line_around_cursor('', '')
- end
-
- def test_ed_transpose_chars
- input_keys('abc')
- input_keys("\C-a")
- assert_line_around_cursor('', 'abc')
- input_keys("\C-t")
- assert_line_around_cursor('', 'abc')
- input_keys("\C-f\C-t")
- assert_line_around_cursor('ba', 'c')
- input_keys("\C-t")
- assert_line_around_cursor('bca', '')
- input_keys("\C-t")
- assert_line_around_cursor('bac', '')
- end
-
- def test_ed_transpose_chars_for_mbchar
- input_keys('あかさ')
- input_keys("\C-a")
- assert_line_around_cursor('', 'あかさ')
- input_keys("\C-t")
- assert_line_around_cursor('', 'あかさ')
- input_keys("\C-f\C-t")
- assert_line_around_cursor('かあ', 'さ')
- input_keys("\C-t")
- assert_line_around_cursor('かさあ', '')
- input_keys("\C-t")
- assert_line_around_cursor('かあさ', '')
- end
-
- def test_ed_transpose_chars_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("あか\u3099さ")
- input_keys("\C-a")
- assert_line_around_cursor('', "あか\u3099さ")
- input_keys("\C-t")
- assert_line_around_cursor('', "あか\u3099さ")
- input_keys("\C-f\C-t")
- assert_line_around_cursor("か\u3099あ", 'さ')
- input_keys("\C-t")
- assert_line_around_cursor("か\u3099さあ", '')
- input_keys("\C-t")
- assert_line_around_cursor("か\u3099あさ", '')
- end
-
- def test_ed_transpose_words
- input_keys('abc def')
- assert_line_around_cursor('abc def', '')
- input_keys("\et")
- assert_line_around_cursor('def abc', '')
- input_keys("\C-a\C-k")
- input_keys(' abc def ')
- input_keys("\C-b" * 4)
- assert_line_around_cursor(' abc de', 'f ')
- input_keys("\et")
- assert_line_around_cursor(' def abc', ' ')
- input_keys("\C-a\C-k")
- input_keys(' abc def ')
- input_keys("\C-b" * 6)
- assert_line_around_cursor(' abc ', 'def ')
- input_keys("\et")
- assert_line_around_cursor(' def abc', ' ')
- input_keys("\et")
- assert_line_around_cursor(' abc def', '')
- end
-
- def test_ed_transpose_words_for_mbchar
- input_keys('あいう かきく')
- assert_line_around_cursor('あいう かきく', '')
- input_keys("\et")
- assert_line_around_cursor('かきく あいう', '')
- input_keys("\C-a\C-k")
- input_keys(' あいう かきく ')
- input_keys("\C-b" * 4)
- assert_line_around_cursor(' あいう かき', 'く ')
- input_keys("\et")
- assert_line_around_cursor(' かきく あいう', ' ')
- input_keys("\C-a\C-k")
- input_keys(' あいう かきく ')
- input_keys("\C-b" * 6)
- assert_line_around_cursor(' あいう ', 'かきく ')
- input_keys("\et")
- assert_line_around_cursor(' かきく あいう', ' ')
- input_keys("\et")
- assert_line_around_cursor(' あいう かきく', '')
- end
-
- def test_ed_transpose_words_with_one_word
- input_keys('abc ')
- assert_line_around_cursor('abc ', '')
- input_keys("\et")
- assert_line_around_cursor('abc ', '')
- input_keys("\C-b")
- assert_line_around_cursor('abc ', ' ')
- input_keys("\et")
- assert_line_around_cursor('abc ', ' ')
- input_keys("\C-b" * 2)
- assert_line_around_cursor('ab', 'c ')
- input_keys("\et")
- assert_line_around_cursor('ab', 'c ')
- input_keys("\et")
- assert_line_around_cursor('ab', 'c ')
- end
-
- def test_ed_transpose_words_with_one_word_for_mbchar
- input_keys('あいう ')
- assert_line_around_cursor('あいう ', '')
- input_keys("\et")
- assert_line_around_cursor('あいう ', '')
- input_keys("\C-b")
- assert_line_around_cursor('あいう ', ' ')
- input_keys("\et")
- assert_line_around_cursor('あいう ', ' ')
- input_keys("\C-b" * 2)
- assert_line_around_cursor('あい', 'う ')
- input_keys("\et")
- assert_line_around_cursor('あい', 'う ')
- input_keys("\et")
- assert_line_around_cursor('あい', 'う ')
- end
-
- def test_ed_digit
- input_keys('0123')
- assert_line_around_cursor('0123', '')
- end
-
- def test_ed_next_and_prev_char
- input_keys('abc')
- assert_line_around_cursor('abc', '')
- input_keys("\C-b")
- assert_line_around_cursor('ab', 'c')
- input_keys("\C-b")
- assert_line_around_cursor('a', 'bc')
- input_keys("\C-b")
- assert_line_around_cursor('', 'abc')
- input_keys("\C-b")
- assert_line_around_cursor('', 'abc')
- input_keys("\C-f")
- assert_line_around_cursor('a', 'bc')
- input_keys("\C-f")
- assert_line_around_cursor('ab', 'c')
- input_keys("\C-f")
- assert_line_around_cursor('abc', '')
- input_keys("\C-f")
- assert_line_around_cursor('abc', '')
- end
-
- def test_ed_next_and_prev_char_for_mbchar
- input_keys('あいう')
- assert_line_around_cursor('あいう', '')
- input_keys("\C-b")
- assert_line_around_cursor('あい', 'う')
- input_keys("\C-b")
- assert_line_around_cursor('あ', 'いう')
- input_keys("\C-b")
- assert_line_around_cursor('', 'あいう')
- input_keys("\C-b")
- assert_line_around_cursor('', 'あいう')
- input_keys("\C-f")
- assert_line_around_cursor('あ', 'いう')
- input_keys("\C-f")
- assert_line_around_cursor('あい', 'う')
- input_keys("\C-f")
- assert_line_around_cursor('あいう', '')
- input_keys("\C-f")
- assert_line_around_cursor('あいう', '')
- end
-
- def test_ed_next_and_prev_char_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099く\u3099")
- assert_line_around_cursor("か\u3099き\u3099く\u3099", '')
- input_keys("\C-b")
- assert_line_around_cursor("か\u3099き\u3099", "く\u3099")
- input_keys("\C-b")
- assert_line_around_cursor("か\u3099", "き\u3099く\u3099")
- input_keys("\C-b")
- assert_line_around_cursor('', "か\u3099き\u3099く\u3099")
- input_keys("\C-b")
- assert_line_around_cursor('', "か\u3099き\u3099く\u3099")
- input_keys("\C-f")
- assert_line_around_cursor("か\u3099", "き\u3099く\u3099")
- input_keys("\C-f")
- assert_line_around_cursor("か\u3099き\u3099", "く\u3099")
- input_keys("\C-f")
- assert_line_around_cursor("か\u3099き\u3099く\u3099", '')
- input_keys("\C-f")
- assert_line_around_cursor("か\u3099き\u3099く\u3099", '')
- end
-
- def test_em_capitol_case
- input_keys('abc def{bbb}ccc')
- input_keys("\C-a\ec")
- assert_line_around_cursor('Abc', ' def{bbb}ccc')
- input_keys("\ec")
- assert_line_around_cursor('Abc Def', '{bbb}ccc')
- input_keys("\ec")
- assert_line_around_cursor('Abc Def{Bbb', '}ccc')
- input_keys("\ec")
- assert_line_around_cursor('Abc Def{Bbb}Ccc', '')
- end
-
- def test_em_capitol_case_with_complex_example
- input_keys('{}#* AaA!!!cCc ')
- input_keys("\C-a\ec")
- assert_line_around_cursor('{}#* Aaa', '!!!cCc ')
- input_keys("\ec")
- assert_line_around_cursor('{}#* Aaa!!!Ccc', ' ')
- input_keys("\ec")
- assert_line_around_cursor('{}#* Aaa!!!Ccc ', '')
- end
-
- def test_em_lower_case
- input_keys('AbC def{bBb}CCC')
- input_keys("\C-a\el")
- assert_line_around_cursor('abc', ' def{bBb}CCC')
- input_keys("\el")
- assert_line_around_cursor('abc def', '{bBb}CCC')
- input_keys("\el")
- assert_line_around_cursor('abc def{bbb', '}CCC')
- input_keys("\el")
- assert_line_around_cursor('abc def{bbb}ccc', '')
- end
-
- def test_em_lower_case_with_complex_example
- input_keys('{}#* AaA!!!cCc ')
- input_keys("\C-a\el")
- assert_line_around_cursor('{}#* aaa', '!!!cCc ')
- input_keys("\el")
- assert_line_around_cursor('{}#* aaa!!!ccc', ' ')
- input_keys("\el")
- assert_line_around_cursor('{}#* aaa!!!ccc ', '')
- end
-
- def test_em_upper_case
- input_keys('AbC def{bBb}CCC')
- input_keys("\C-a\eu")
- assert_line_around_cursor('ABC', ' def{bBb}CCC')
- input_keys("\eu")
- assert_line_around_cursor('ABC DEF', '{bBb}CCC')
- input_keys("\eu")
- assert_line_around_cursor('ABC DEF{BBB', '}CCC')
- input_keys("\eu")
- assert_line_around_cursor('ABC DEF{BBB}CCC', '')
- end
-
- def test_em_upper_case_with_complex_example
- input_keys('{}#* AaA!!!cCc ')
- input_keys("\C-a\eu")
- assert_line_around_cursor('{}#* AAA', '!!!cCc ')
- input_keys("\eu")
- assert_line_around_cursor('{}#* AAA!!!CCC', ' ')
- input_keys("\eu")
- assert_line_around_cursor('{}#* AAA!!!CCC ', '')
- end
-
- def test_em_delete_or_list
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_foo
- foo_bar
- foo_baz
- qux
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('fooo')
- assert_line_around_cursor('fooo', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-b")
- assert_line_around_cursor('foo', 'o')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_key_by_symbol(:em_delete_or_list)
- assert_line_around_cursor('foo', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_key_by_symbol(:em_delete_or_list)
- assert_line_around_cursor('foo', '')
- assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
- end
-
- def test_completion_duplicated_list
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_foo
- foo_foo
- foo_bar
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('foo_')
- assert_line_around_cursor('foo_', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list)
- end
-
- def test_completion
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_foo
- foo_bar
- foo_baz
- qux
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('fo')
- assert_line_around_cursor('fo', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
- input_keys('a')
- input_keys("\C-i")
- assert_line_around_cursor('foo_a', '')
- input_keys("\C-h")
- input_keys('b')
- input_keys("\C-i")
- assert_line_around_cursor('foo_ba', '')
- input_keys("\C-h")
- input_key_by_symbol(:complete)
- assert_line_around_cursor('foo_ba', '')
- input_keys("\C-h")
- input_key_by_symbol(:menu_complete)
- assert_line_around_cursor('foo_bar', '')
- input_key_by_symbol(:menu_complete)
- assert_line_around_cursor('foo_baz', '')
- input_keys("\C-h")
- input_key_by_symbol(:menu_complete_backward)
- assert_line_around_cursor('foo_baz', '')
- input_key_by_symbol(:menu_complete_backward)
- assert_line_around_cursor('foo_bar', '')
- end
-
- def test_autocompletion
- @config.autocompletion = true
- @line_editor.completion_proc = proc { |word|
- %w{
- Readline
- Regexp
- RegexpError
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('Re')
- assert_line_around_cursor('Re', '')
- input_keys("\C-i")
- assert_line_around_cursor('Readline', '')
- input_keys("\C-i")
- assert_line_around_cursor('Regexp', '')
- input_key_by_symbol(:completion_journey_up)
- assert_line_around_cursor('Readline', '')
- input_key_by_symbol(:complete)
- assert_line_around_cursor('Regexp', '')
- input_key_by_symbol(:menu_complete_backward)
- assert_line_around_cursor('Readline', '')
- input_key_by_symbol(:menu_complete)
- assert_line_around_cursor('Regexp', '')
- ensure
- @config.autocompletion = false
- end
-
- def test_completion_with_indent
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_foo
- foo_bar
- foo_baz
- qux
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys(' fo')
- assert_line_around_cursor(' fo', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor(' foo_', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor(' foo_', '')
- assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
- end
-
- def test_completion_with_perfect_match
- @line_editor.completion_proc = proc { |word|
- %w{
- foo
- foo_bar
- }.map { |i|
- i.encode(@encoding)
- }
- }
- matched = nil
- @line_editor.dig_perfect_match_proc = proc { |m|
- matched = m
- }
- input_keys('fo')
- assert_line_around_cursor('fo', '')
- assert_equal(Reline::LineEditor::CompletionState::NORMAL, @line_editor.instance_variable_get(:@completion_state))
- assert_equal(nil, matched)
- input_keys("\C-i")
- assert_line_around_cursor('foo', '')
- assert_equal(Reline::LineEditor::CompletionState::MENU_WITH_PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
- assert_equal(nil, matched)
- input_keys("\C-i")
- assert_line_around_cursor('foo', '')
- assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
- assert_equal(nil, matched)
- input_keys("\C-i")
- assert_line_around_cursor('foo', '')
- assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
- assert_equal('foo', matched)
- matched = nil
- input_keys('_')
- input_keys("\C-i")
- assert_line_around_cursor('foo_bar', '')
- assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
- assert_equal(nil, matched)
- input_keys("\C-i")
- assert_line_around_cursor('foo_bar', '')
- assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state))
- assert_equal('foo_bar', matched)
- end
-
- def test_continuous_completion_with_perfect_match
- @line_editor.completion_proc = proc { |word|
- word == 'f' ? ['foo'] : %w[foobar foobaz]
- }
- input_keys('f')
- input_keys("\C-i")
- assert_line_around_cursor('foo', '')
- input_keys("\C-i")
- assert_line_around_cursor('fooba', '')
- end
-
- def test_continuous_completion_disabled_with_perfect_match
- @line_editor.completion_proc = proc { |word|
- word == 'f' ? ['foo'] : %w[foobar foobaz]
- }
- @line_editor.dig_perfect_match_proc = proc {}
- input_keys('f')
- input_keys("\C-i")
- assert_line_around_cursor('foo', '')
- input_keys("\C-i")
- assert_line_around_cursor('foo', '')
- end
-
- def test_completion_append_character
- @line_editor.completion_proc = proc { |word|
- %w[foo_ foo_foo foo_bar].select { |s| s.start_with? word }
- }
- @line_editor.completion_append_character = 'X'
- input_keys('f')
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- input_keys('f')
- input_keys("\C-i")
- assert_line_around_cursor('foo_fooX', '')
- input_keys(' foo_bar')
- input_keys("\C-i")
- assert_line_around_cursor('foo_fooX foo_barX', '')
- end
-
- def test_completion_with_quote_append
- @line_editor.completion_proc = proc { |word|
- %w[foo bar baz].select { |s| s.start_with? word }
- }
- set_line_around_cursor('x = "b', '')
- input_keys("\C-i")
- assert_line_around_cursor('x = "ba', '')
- set_line_around_cursor('x = "f', ' ')
- input_keys("\C-i")
- assert_line_around_cursor('x = "foo', ' ')
- set_line_around_cursor("x = 'f", '')
- input_keys("\C-i")
- assert_line_around_cursor("x = 'foo'", '')
- set_line_around_cursor('"a "f', '')
- input_keys("\C-i")
- assert_line_around_cursor('"a "foo', '')
- set_line_around_cursor('"a\\" "f', '')
- input_keys("\C-i")
- assert_line_around_cursor('"a\\" "foo', '')
- set_line_around_cursor('"a" "f', '')
- input_keys("\C-i")
- assert_line_around_cursor('"a" "foo"', '')
- end
-
- def test_completion_with_completion_ignore_case
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_foo
- foo_bar
- Foo_baz
- qux
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('fo')
- assert_line_around_cursor('fo', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list)
- @config.completion_ignore_case = true
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(%w{foo_foo foo_bar Foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
- input_keys('a')
- input_keys("\C-i")
- assert_line_around_cursor('foo_a', '')
- input_keys("\C-h")
- input_keys('b')
- input_keys("\C-i")
- assert_line_around_cursor('foo_ba', '')
- input_keys('Z')
- input_keys("\C-i")
- assert_line_around_cursor('Foo_baz', '')
- end
-
- def test_completion_in_middle_of_line
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_foo
- foo_bar
- foo_baz
- qux
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('abcde fo ABCDE')
- assert_line_around_cursor('abcde fo ABCDE', '')
- input_keys("\C-b" * 6 + "\C-i")
- assert_line_around_cursor('abcde foo_', ' ABCDE')
- input_keys("\C-b" * 2 + "\C-i")
- assert_line_around_cursor('abcde foo_', 'o_ ABCDE')
- end
-
- def test_completion_with_nil_value
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_foo
- foo_bar
- Foo_baz
- qux
- }.map { |i|
- i.encode(@encoding)
- }.prepend(nil)
- }
- @config.completion_ignore_case = true
- input_keys('fo')
- assert_line_around_cursor('fo', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(nil, @line_editor.instance_variable_get(:@menu_info))
- input_keys("\C-i")
- assert_line_around_cursor('foo_', '')
- assert_equal(%w{foo_foo foo_bar Foo_baz}, @line_editor.instance_variable_get(:@menu_info).list)
- input_keys('a')
- input_keys("\C-i")
- assert_line_around_cursor('foo_a', '')
- input_keys("\C-h")
- input_keys('b')
- input_keys("\C-i")
- assert_line_around_cursor('foo_ba', '')
- end
-
- def test_em_kill_region
- input_keys('abc def{bbb}ccc ddd ')
- assert_line_around_cursor('abc def{bbb}ccc ddd ', '')
- input_keys("\C-w")
- assert_line_around_cursor('abc def{bbb}ccc ', '')
- input_keys("\C-w")
- assert_line_around_cursor('abc ', '')
- input_keys("\C-w")
- assert_line_around_cursor('', '')
- input_keys("\C-w")
- assert_line_around_cursor('', '')
- end
-
- def test_em_kill_region_mbchar
- input_keys('あ い う{う}う ')
- assert_line_around_cursor('あ い う{う}う ', '')
- input_keys("\C-w")
- assert_line_around_cursor('あ い ', '')
- input_keys("\C-w")
- assert_line_around_cursor('あ ', '')
- input_keys("\C-w")
- assert_line_around_cursor('', '')
- end
-
- def test_vi_search_prev
- Reline::HISTORY.concat(%w{abc 123 AAA})
- assert_line_around_cursor('', '')
- input_keys("\C-ra\C-j")
- assert_line_around_cursor('', 'abc')
- end
-
- def test_larger_histories_than_history_size
- history_size = @config.history_size
- @config.history_size = 2
- Reline::HISTORY.concat(%w{abc 123 AAA})
- assert_line_around_cursor('', '')
- input_keys("\C-p")
- assert_line_around_cursor('AAA', '')
- input_keys("\C-p")
- assert_line_around_cursor('123', '')
- input_keys("\C-p")
- assert_line_around_cursor('123', '')
- ensure
- @config.history_size = history_size
- end
-
- def test_search_history_to_back
- Reline::HISTORY.concat([
- '1235', # old
- '12aa',
- '1234' # new
- ])
- assert_line_around_cursor('', '')
- input_keys("\C-r123")
- assert_line_around_cursor('1234', '')
- input_keys("\C-ha")
- assert_line_around_cursor('12aa', '')
- input_keys("\C-h3")
- assert_line_around_cursor('1235', '')
- end
-
- def test_search_history_to_front
- Reline::HISTORY.concat([
- '1235', # old
- '12aa',
- '1234' # new
- ])
- assert_line_around_cursor('', '')
- input_keys("\C-s123")
- assert_line_around_cursor('1235', '')
- input_keys("\C-ha")
- assert_line_around_cursor('12aa', '')
- input_keys("\C-h3")
- assert_line_around_cursor('1234', '')
- end
-
- def test_search_history_front_and_back
- Reline::HISTORY.concat([
- '1235', # old
- '12aa',
- '1234' # new
- ])
- assert_line_around_cursor('', '')
- input_keys("\C-s12")
- assert_line_around_cursor('1235', '')
- input_keys("\C-s")
- assert_line_around_cursor('12aa', '')
- input_keys("\C-r")
- assert_line_around_cursor('12aa', '')
- input_keys("\C-r")
- assert_line_around_cursor('1235', '')
- end
-
- def test_search_history_back_and_front
- Reline::HISTORY.concat([
- '1235', # old
- '12aa',
- '1234' # new
- ])
- assert_line_around_cursor('', '')
- input_keys("\C-r12")
- assert_line_around_cursor('1234', '')
- input_keys("\C-r")
- assert_line_around_cursor('12aa', '')
- input_keys("\C-s")
- assert_line_around_cursor('12aa', '')
- input_keys("\C-s")
- assert_line_around_cursor('1234', '')
- end
-
- def test_search_history_to_back_in_the_middle_of_histories
- Reline::HISTORY.concat([
- '1235', # old
- '12aa',
- '1234' # new
- ])
- assert_line_around_cursor('', '')
- input_keys("\C-p\C-p")
- assert_line_around_cursor('12aa', '')
- input_keys("\C-r123")
- assert_line_around_cursor('1235', '')
- end
-
- def test_search_history_twice
- Reline::HISTORY.concat([
- '1235', # old
- '12aa',
- '1234' # new
- ])
- assert_line_around_cursor('', '')
- input_keys("\C-r123")
- assert_line_around_cursor('1234', '')
- input_keys("\C-r")
- assert_line_around_cursor('1235', '')
- end
-
- def test_search_history_by_last_determined
- Reline::HISTORY.concat([
- '1235', # old
- '12aa',
- '1234' # new
- ])
- assert_line_around_cursor('', '')
- input_keys("\C-r123")
- assert_line_around_cursor('1234', '')
- input_keys("\C-j")
- assert_line_around_cursor('', '1234')
- input_keys("\C-k") # delete
- assert_line_around_cursor('', '')
- input_keys("\C-r")
- assert_line_around_cursor('', '')
- input_keys("\C-r")
- assert_line_around_cursor('1235', '')
- end
-
- def test_search_history_with_isearch_terminator
- @config.read_lines(<<~LINES.split(/(?<=\n)/))
- set isearch-terminators "XYZ"
- LINES
- Reline::HISTORY.concat([
- '1235', # old
- '12aa',
- '1234' # new
- ])
- assert_line_around_cursor('', '')
- input_keys("\C-r12a")
- assert_line_around_cursor('12aa', '')
- input_keys('Y')
- assert_line_around_cursor('', '12aa')
- input_keys('x')
- assert_line_around_cursor('x', '12aa')
- end
-
- def test_em_set_mark_and_em_exchange_mark
- input_keys('aaa bbb ccc ddd')
- assert_line_around_cursor('aaa bbb ccc ddd', '')
- input_keys("\C-a\eF\eF")
- assert_line_around_cursor('aaa bbb', ' ccc ddd')
- assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer))
- input_keys("\x00") # C-Space
- assert_line_around_cursor('aaa bbb', ' ccc ddd')
- assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer))
- input_keys("\C-a")
- assert_line_around_cursor('', 'aaa bbb ccc ddd')
- assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer))
- input_key_by_symbol(:em_exchange_mark)
- assert_line_around_cursor('aaa bbb', ' ccc ddd')
- assert_equal([0, 0], @line_editor.instance_variable_get(:@mark_pointer))
- end
-
- def test_em_exchange_mark_without_mark
- input_keys('aaa bbb ccc ddd')
- assert_line_around_cursor('aaa bbb ccc ddd', '')
- input_keys("\C-a\ef")
- assert_line_around_cursor('aaa', ' bbb ccc ddd')
- assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer))
- input_key_by_symbol(:em_exchange_mark)
- assert_line_around_cursor('aaa', ' bbb ccc ddd')
- assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer))
- end
-
- def test_modify_lines_with_wrong_rs
- verbose, $VERBOSE = $VERBOSE, nil
- original_global_slash = $/
- $/ = 'b'
- $VERBOSE = verbose
- @line_editor.output_modifier_proc = proc { |output| Reline::Unicode.escape_for_print(output) }
- input_keys("abcdef\n")
- result = @line_editor.__send__(:modify_lines, @line_editor.whole_lines, @line_editor.finished?)
- $/ = nil
- assert_equal(['abcdef'], result)
- ensure
- $VERBOSE = nil
- $/ = original_global_slash
- $VERBOSE = verbose
- end
-
- def test_ed_search_prev_history
- Reline::HISTORY.concat([
- '12356', # old
- '12aaa',
- '12345' # new
- ])
- input_keys('123')
- # The ed_search_prev_history doesn't have default binding
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_around_cursor('123', '45')
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_around_cursor('123', '56')
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_around_cursor('123', '56')
- end
-
- def test_ed_search_prev_history_with_empty
- Reline::HISTORY.concat([
- '12356', # old
- '12aaa',
- '12345' # new
- ])
- # The ed_search_prev_history doesn't have default binding
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12345', '')
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12aaa', '')
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12356', '')
- input_key_by_symbol(:ed_search_next_history)
- assert_line_around_cursor('12aaa', '')
- input_key_by_symbol(:ed_prev_char)
- input_key_by_symbol(:ed_next_char)
- assert_line_around_cursor('12aaa', '')
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12aaa', '')
- 3.times { input_key_by_symbol(:ed_prev_char) }
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12', '356')
- end
-
- def test_ed_search_prev_history_without_match
- Reline::HISTORY.concat([
- '12356', # old
- '12aaa',
- '12345' # new
- ])
- input_keys('ABC')
- # The ed_search_prev_history doesn't have default binding
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_around_cursor('ABC', '')
- end
-
- def test_ed_search_next_history
- Reline::HISTORY.concat([
- '12356', # old
- '12aaa',
- '12345' # new
- ])
- input_keys('123')
- # The ed_search_prev_history and ed_search_next_history doesn't have default binding
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_around_cursor('123', '45')
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_around_cursor('123', '56')
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_around_cursor('123', '56')
- @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_line_around_cursor('123', '45')
- @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_line_around_cursor('123', '45')
- end
-
- def test_ed_search_next_history_with_empty
- Reline::HISTORY.concat([
- '12356', # old
- '12aaa',
- '12345' # new
- ])
- # The ed_search_prev_history and ed_search_next_history doesn't have default binding
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12345', '')
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12aaa', '')
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12356', '')
- input_key_by_symbol(:ed_search_next_history)
- assert_line_around_cursor('12aaa', '')
- input_key_by_symbol(:ed_search_next_history)
- assert_line_around_cursor('12345', '')
- input_key_by_symbol(:ed_search_prev_history)
- assert_line_around_cursor('12aaa', '')
- input_key_by_symbol(:ed_prev_char)
- input_key_by_symbol(:ed_next_char)
- input_key_by_symbol(:ed_search_next_history)
- assert_line_around_cursor('12aaa', '')
- 3.times { input_key_by_symbol(:ed_prev_char) }
- input_key_by_symbol(:ed_search_next_history)
- assert_line_around_cursor('12', '345')
- end
-
- def test_incremental_search_history_cancel_by_symbol_key
- # ed_prev_char should move cursor left and cancel incremental search
- input_keys("abc\C-r")
- input_key_by_symbol(:ed_prev_char, csi: true)
- input_keys('d')
- assert_line_around_cursor('abd', 'c')
- end
-
- def test_incremental_search_history_saves_and_restores_last_input
- Reline::HISTORY.concat(['abc', '123'])
- input_keys("abcd")
- # \C-j: terminate incremental search
- input_keys("\C-r12\C-j")
- assert_line_around_cursor('', '123')
- input_key_by_symbol(:ed_next_history)
- assert_line_around_cursor('abcd', '')
- # Most non-printable keys also terminates incremental search
- input_keys("\C-r12\C-i")
- assert_line_around_cursor('', '123')
- input_key_by_symbol(:ed_next_history)
- assert_line_around_cursor('abcd', '')
- # \C-g: cancel incremental search and restore input, cursor position and history index
- input_key_by_symbol(:ed_prev_history)
- input_keys("\C-b\C-b")
- assert_line_around_cursor('1', '23')
- input_keys("\C-rab\C-g")
- assert_line_around_cursor('1', '23')
- input_key_by_symbol(:ed_next_history)
- assert_line_around_cursor('abcd', '')
- end
-
- # Unicode emoji test
- def test_ed_insert_for_include_zwj_emoji
- omit_unless_utf8
- # U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466 is family: man, woman, girl, boy "👨‍👩‍👧‍👦"
- input_keys("\u{1F468}") # U+1F468 is man "👨"
- assert_line_around_cursor('👨', '')
- input_keys("\u200D") # U+200D is ZERO WIDTH JOINER
- assert_line_around_cursor('👨‍', '')
- input_keys("\u{1F469}") # U+1F469 is woman "👩"
- assert_line_around_cursor('👨‍👩', '')
- input_keys("\u200D") # U+200D is ZERO WIDTH JOINER
- assert_line_around_cursor('👨‍👩‍', '')
- input_keys("\u{1F467}") # U+1F467 is girl "👧"
- assert_line_around_cursor('👨‍👩‍👧', '')
- input_keys("\u200D") # U+200D is ZERO WIDTH JOINER
- assert_line_around_cursor('👨‍👩‍👧‍', '')
- input_keys("\u{1F466}") # U+1F466 is boy "👦"
- assert_line_around_cursor('👨‍👩‍👧‍👦', '')
- # U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466 is family: man, woman, girl, boy "👨‍👩‍👧‍👦"
- input_keys("\u{1F468 200D 1F469 200D 1F467 200D 1F466}")
- assert_line_around_cursor('👨‍👩‍👧‍👦👨‍👩‍👧‍👦', '')
- end
-
- def test_ed_insert_for_include_valiation_selector
- omit_unless_utf8
- # U+0030 U+FE00 is DIGIT ZERO + VARIATION SELECTOR-1 "0︀"
- input_keys("\u0030") # U+0030 is DIGIT ZERO
- assert_line_around_cursor('0', '')
- input_keys("\uFE00") # U+FE00 is VARIATION SELECTOR-1
- assert_line_around_cursor('0︀', '')
- end
-
- def test_em_yank_pop
- input_keys("def hoge\C-w\C-b\C-f\C-w")
- assert_line_around_cursor('', '')
- input_keys("\C-y")
- assert_line_around_cursor('def ', '')
- input_keys("\e\C-y")
- assert_line_around_cursor('hoge', '')
- end
-
- def test_em_kill_region_with_kill_ring
- input_keys("def hoge\C-b\C-b\C-b\C-b")
- assert_line_around_cursor('def ', 'hoge')
- input_keys("\C-k\C-w")
- assert_line_around_cursor('', '')
- input_keys("\C-y")
- assert_line_around_cursor('def hoge', '')
- end
-
- def test_ed_search_prev_next_history_in_multibyte
- Reline::HISTORY.concat([
- "def hoge\n 67890\n 12345\nend", # old
- "def aiu\n 0xDEADBEEF\nend",
- "def foo\n 12345\nend" # new
- ])
- @line_editor.multiline_on
- input_keys(' 123')
- # The ed_search_prev_history doesn't have default binding
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_whole_lines(['def foo', ' 12345', 'end'])
- assert_line_index(1)
- assert_whole_lines(['def foo', ' 12345', 'end'])
- assert_line_around_cursor(' 123', '45')
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_index(2)
- assert_whole_lines(['def hoge', ' 67890', ' 12345', 'end'])
- assert_line_around_cursor(' 123', '45')
- @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
- assert_line_index(2)
- assert_whole_lines(['def hoge', ' 67890', ' 12345', 'end'])
- assert_line_around_cursor(' 123', '45')
- @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_line_index(1)
- assert_whole_lines(['def foo', ' 12345', 'end'])
- assert_line_around_cursor(' 123', '45')
- @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
- assert_line_index(1)
- assert_whole_lines(['def foo', ' 12345', 'end'])
- assert_line_around_cursor(' 123', '45')
- end
-
- def test_ignore_NUL_by_ed_quoted_insert
- input_keys('"')
- input_key_by_symbol(:insert_raw_char, char: 0.chr)
- input_keys('"')
- assert_line_around_cursor('""', '')
- end
-
- def test_ed_argument_digit_by_meta_num
- input_keys('abcdef')
- assert_line_around_cursor('abcdef', '')
- input_keys("\e2")
- input_keys("\C-h")
- assert_line_around_cursor('abcd', '')
- end
-
- def test_ed_digit_with_ed_argument_digit
- input_keys('1' * 30)
- assert_line_around_cursor('1' * 30, '')
- input_keys("\e2")
- input_keys('3')
- input_keys("\C-h")
- input_keys('4')
- assert_line_around_cursor('1' * 7 + '4', '')
- end
-
- def test_halfwidth_kana_width_dakuten
- omit_unless_utf8
- input_keys('ガギゲゴ')
- assert_line_around_cursor('ガギゲゴ', '')
- input_keys("\C-b\C-b")
- assert_line_around_cursor('ガギ', 'ゲゴ')
- input_keys('グ')
- assert_line_around_cursor('ガギグ', 'ゲゴ')
- end
-
- def test_input_unknown_char
- omit_unless_utf8
- input_keys('͸') # U+0378 (unassigned)
- assert_line_around_cursor('͸', '')
- end
-
- def test_unix_line_discard
- input_keys("\C-u")
- assert_line_around_cursor('', '')
- input_keys('abc')
- assert_line_around_cursor('abc', '')
- input_keys("\C-b\C-u")
- assert_line_around_cursor('', 'c')
- input_keys("\C-f\C-u")
- assert_line_around_cursor('', '')
- end
-
- def test_vi_editing_mode
- @line_editor.__send__(:vi_editing_mode, nil)
- assert(@config.editing_mode_is?(:vi_insert))
- end
-
- def test_undo
- input_keys("\C-_")
- assert_line_around_cursor('', '')
- input_keys("aあb\C-h\C-h\C-h")
- assert_line_around_cursor('', '')
- input_keys("\C-_")
- assert_line_around_cursor('a', '')
- input_keys("\C-_")
- assert_line_around_cursor('aあ', '')
- input_keys("\C-_")
- assert_line_around_cursor('aあb', '')
- input_keys("\C-_")
- assert_line_around_cursor('aあ', '')
- input_keys("\C-_")
- assert_line_around_cursor('a', '')
- input_keys("\C-_")
- assert_line_around_cursor('', '')
- end
-
- def test_undo_with_cursor_position
- input_keys("abc\C-b\C-h")
- assert_line_around_cursor('a', 'c')
- input_keys("\C-_")
- assert_line_around_cursor('ab', 'c')
- input_keys("あいう\C-b\C-h")
- assert_line_around_cursor('abあ', 'うc')
- input_keys("\C-_")
- assert_line_around_cursor('abあい', 'うc')
- end
-
- def test_undo_with_multiline
- @line_editor.multiline_on
- @line_editor.confirm_multiline_termination_proc = proc {}
- input_keys("1\n2\n3")
- assert_whole_lines(["1", "2", "3"])
- assert_line_index(2)
- assert_line_around_cursor('3', '')
- input_keys("\C-p\C-h\C-h")
- assert_whole_lines(["1", "3"])
- assert_line_index(0)
- assert_line_around_cursor('1', '')
- input_keys("\C-_")
- assert_whole_lines(["1", "", "3"])
- assert_line_index(1)
- assert_line_around_cursor('', '')
- input_keys("\C-_")
- assert_whole_lines(["1", "2", "3"])
- assert_line_index(1)
- assert_line_around_cursor('2', '')
- input_keys("\C-_")
- assert_whole_lines(["1", "2", ""])
- assert_line_index(2)
- assert_line_around_cursor('', '')
- input_keys("\C-_")
- assert_whole_lines(["1", "2"])
- assert_line_index(1)
- assert_line_around_cursor('2', '')
- end
-
- def test_undo_with_many_times
- str = "a" + "b" * 99
- input_keys(str)
- 100.times { input_keys("\C-_") }
- assert_line_around_cursor('a', '')
- input_keys("\C-_")
- assert_line_around_cursor('a', '')
- end
-
- def test_redo
- input_keys("aあb")
- assert_line_around_cursor('aあb', '')
- input_keys("\e\C-_")
- assert_line_around_cursor('aあb', '')
- input_keys("\C-_")
- assert_line_around_cursor('aあ', '')
- input_keys("\C-_")
- assert_line_around_cursor('a', '')
- input_keys("\e\C-_")
- assert_line_around_cursor('aあ', '')
- input_keys("\e\C-_")
- assert_line_around_cursor('aあb', '')
- input_keys("\C-_")
- assert_line_around_cursor('aあ', '')
- input_keys("c")
- assert_line_around_cursor('aあc', '')
- input_keys("\e\C-_")
- assert_line_around_cursor('aあc', '')
- end
-
- def test_redo_with_cursor_position
- input_keys("abc\C-b\C-h")
- assert_line_around_cursor('a', 'c')
- input_keys("\e\C-_")
- assert_line_around_cursor('a', 'c')
- input_keys("\C-_")
- assert_line_around_cursor('ab', 'c')
- input_keys("\e\C-_")
- assert_line_around_cursor('a', 'c')
- end
-
- def test_redo_with_multiline
- @line_editor.multiline_on
- @line_editor.confirm_multiline_termination_proc = proc {}
- input_keys("1\n2\n3")
- assert_whole_lines(["1", "2", "3"])
- assert_line_index(2)
- assert_line_around_cursor('3', '')
-
- input_keys("\C-_")
- assert_whole_lines(["1", "2", ""])
- assert_line_index(2)
- assert_line_around_cursor('', '')
-
- input_keys("\C-_")
- assert_whole_lines(["1", "2"])
- assert_line_index(1)
- assert_line_around_cursor('2', '')
-
- input_keys("\e\C-_")
- assert_whole_lines(["1", "2", ""])
- assert_line_index(2)
- assert_line_around_cursor('', '')
-
- input_keys("\e\C-_")
- assert_whole_lines(["1", "2", "3"])
- assert_line_index(2)
- assert_line_around_cursor('3', '')
-
- input_keys("\C-p\C-h\C-h")
- assert_whole_lines(["1", "3"])
- assert_line_index(0)
- assert_line_around_cursor('1', '')
-
- input_keys("\C-n")
- assert_whole_lines(["1", "3"])
- assert_line_index(1)
- assert_line_around_cursor('3', '')
-
- input_keys("\C-_")
- assert_whole_lines(["1", "", "3"])
- assert_line_index(1)
- assert_line_around_cursor('', '')
-
- input_keys("\C-_")
- assert_whole_lines(["1", "2", "3"])
- assert_line_index(1)
- assert_line_around_cursor('2', '')
-
- input_keys("\e\C-_")
- assert_whole_lines(["1", "", "3"])
- assert_line_index(1)
- assert_line_around_cursor('', '')
-
- input_keys("\e\C-_")
- assert_whole_lines(["1", "3"])
- assert_line_index(1)
- assert_line_around_cursor('3', '')
- end
-
- def test_undo_redo_restores_indentation
- @line_editor.multiline_on
- @line_editor.confirm_multiline_termination_proc = proc {}
- input_keys(" 1")
- assert_whole_lines([' 1'])
- input_keys("2")
- assert_whole_lines([' 12'])
- @line_editor.auto_indent_proc = proc { 2 }
- input_keys("\C-_")
- assert_whole_lines([' 1'])
- input_keys("\e\C-_")
- assert_whole_lines([' 12'])
- end
-
- def test_redo_with_many_times
- str = "a" + "b" * 98 + "c"
- input_keys(str)
- 100.times { input_keys("\C-_") }
- assert_line_around_cursor('a', '')
- input_keys("\C-_")
- assert_line_around_cursor('a', '')
- 100.times { input_keys("\e\C-_") }
- assert_line_around_cursor(str, '')
- input_keys("\e\C-_")
- assert_line_around_cursor(str, '')
- end
-end
diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb
deleted file mode 100644
index 083433f9a8..0000000000
--- a/test/reline/test_key_actor_vi.rb
+++ /dev/null
@@ -1,967 +0,0 @@
-require_relative 'helper'
-
-class Reline::ViInsertTest < Reline::TestCase
- def setup
- Reline.send(:test_mode)
- @prompt = '> '
- @config = Reline::Config.new
- @config.read_lines(<<~LINES.split(/(?<=\n)/))
- set editing-mode vi
- LINES
- @encoding = Reline.core.encoding
- @line_editor = Reline::LineEditor.new(@config)
- @line_editor.reset(@prompt)
- end
-
- def editing_mode_label
- @config.instance_variable_get(:@editing_mode_label)
- end
-
- def teardown
- Reline.test_reset
- end
-
- def test_vi_command_mode
- input_keys("\C-[")
- assert_equal(:vi_command, editing_mode_label)
- end
-
- def test_vi_command_mode_with_input
- input_keys("abc\C-[")
- assert_equal(:vi_command, editing_mode_label)
- assert_line_around_cursor('ab', 'c')
- end
-
- def test_vi_insert
- assert_equal(:vi_insert, editing_mode_label)
- input_keys('i')
- assert_line_around_cursor('i', '')
- assert_equal(:vi_insert, editing_mode_label)
- input_keys("\C-[")
- assert_line_around_cursor('', 'i')
- assert_equal(:vi_command, editing_mode_label)
- input_keys('i')
- assert_line_around_cursor('', 'i')
- assert_equal(:vi_insert, editing_mode_label)
- end
-
- def test_vi_add
- assert_equal(:vi_insert, editing_mode_label)
- input_keys('a')
- assert_line_around_cursor('a', '')
- assert_equal(:vi_insert, editing_mode_label)
- input_keys("\C-[")
- assert_line_around_cursor('', 'a')
- assert_equal(:vi_command, editing_mode_label)
- input_keys('a')
- assert_line_around_cursor('a', '')
- assert_equal(:vi_insert, editing_mode_label)
- end
-
- def test_vi_insert_at_bol
- input_keys('I')
- assert_line_around_cursor('I', '')
- assert_equal(:vi_insert, editing_mode_label)
- input_keys("12345\C-[hh")
- assert_line_around_cursor('I12', '345')
- assert_equal(:vi_command, editing_mode_label)
- input_keys('I')
- assert_line_around_cursor('', 'I12345')
- assert_equal(:vi_insert, editing_mode_label)
- end
-
- def test_vi_add_at_eol
- input_keys('A')
- assert_line_around_cursor('A', '')
- assert_equal(:vi_insert, editing_mode_label)
- input_keys("12345\C-[hh")
- assert_line_around_cursor('A12', '345')
- assert_equal(:vi_command, editing_mode_label)
- input_keys('A')
- assert_line_around_cursor('A12345', '')
- assert_equal(:vi_insert, editing_mode_label)
- end
-
- def test_ed_insert_one
- input_keys('a')
- assert_line_around_cursor('a', '')
- end
-
- def test_ed_insert_two
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- end
-
- def test_ed_insert_mbchar_one
- input_keys('か')
- assert_line_around_cursor('か', '')
- end
-
- def test_ed_insert_mbchar_two
- input_keys('かき')
- assert_line_around_cursor('かき', '')
- end
-
- def test_ed_insert_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099")
- assert_line_around_cursor("か\u3099", '')
- end
-
- def test_ed_insert_for_plural_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099")
- assert_line_around_cursor("か\u3099き\u3099", '')
- end
-
- def test_ed_insert_ignore_in_vi_command
- input_keys("\C-[")
- chars_to_be_ignored = "\C-Oあ=".chars
- input_keys(chars_to_be_ignored.join)
- assert_line_around_cursor('', '')
- input_keys(chars_to_be_ignored.map {|c| "5#{c}" }.join)
- assert_line_around_cursor('', '')
- input_keys('iい')
- assert_line_around_cursor("い", '')
- end
-
- def test_ed_next_char
- input_keys("abcdef\C-[0")
- assert_line_around_cursor('', 'abcdef')
- input_keys('l')
- assert_line_around_cursor('a', 'bcdef')
- input_keys('2l')
- assert_line_around_cursor('abc', 'def')
- end
-
- def test_ed_prev_char
- input_keys("abcdef\C-[")
- assert_line_around_cursor('abcde', 'f')
- input_keys('h')
- assert_line_around_cursor('abcd', 'ef')
- input_keys('2h')
- assert_line_around_cursor('ab', 'cdef')
- end
-
- def test_history
- Reline::HISTORY.concat(%w{abc 123 AAA})
- input_keys("\C-[")
- assert_line_around_cursor('', '')
- input_keys('k')
- assert_line_around_cursor('', 'AAA')
- input_keys('2k')
- assert_line_around_cursor('', 'abc')
- input_keys('j')
- assert_line_around_cursor('', '123')
- input_keys('2j')
- assert_line_around_cursor('', '')
- end
-
- def test_vi_paste_prev
- input_keys("abcde\C-[3h")
- assert_line_around_cursor('a', 'bcde')
- input_keys('P')
- assert_line_around_cursor('a', 'bcde')
- input_keys('d$')
- assert_line_around_cursor('', 'a')
- input_keys('P')
- assert_line_around_cursor('bcd', 'ea')
- input_keys('2P')
- assert_line_around_cursor('bcdbcdbcd', 'eeea')
- end
-
- def test_vi_paste_next
- input_keys("abcde\C-[3h")
- assert_line_around_cursor('a', 'bcde')
- input_keys('p')
- assert_line_around_cursor('a', 'bcde')
- input_keys('d$')
- assert_line_around_cursor('', 'a')
- input_keys('p')
- assert_line_around_cursor('abcd', 'e')
- input_keys('2p')
- assert_line_around_cursor('abcdebcdebcd', 'e')
- end
-
- def test_vi_paste_prev_for_mbchar
- input_keys("あいうえお\C-[3h")
- assert_line_around_cursor('あ', 'いうえお')
- input_keys('P')
- assert_line_around_cursor('あ', 'いうえお')
- input_keys('d$')
- assert_line_around_cursor('', 'あ')
- input_keys('P')
- assert_line_around_cursor('いうえ', 'おあ')
- input_keys('2P')
- assert_line_around_cursor('いうえいうえいうえ', 'おおおあ')
- end
-
- def test_vi_paste_next_for_mbchar
- input_keys("あいうえお\C-[3h")
- assert_line_around_cursor('あ', 'いうえお')
- input_keys('p')
- assert_line_around_cursor('あ', 'いうえお')
- input_keys('d$')
- assert_line_around_cursor('', 'あ')
- input_keys('p')
- assert_line_around_cursor('あいうえ', 'お')
- input_keys('2p')
- assert_line_around_cursor('あいうえおいうえおいうえ', 'お')
- end
-
- def test_vi_paste_prev_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h")
- assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099")
- input_keys('P')
- assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099")
- input_keys('d$')
- assert_line_around_cursor('', "か\u3099")
- input_keys('P')
- assert_line_around_cursor("き\u3099く\u3099け\u3099", "こ\u3099か\u3099")
- input_keys('2P')
- assert_line_around_cursor("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099", "こ\u3099こ\u3099こ\u3099か\u3099")
- end
-
- def test_vi_paste_next_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h")
- assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099")
- input_keys('p')
- assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099")
- input_keys('d$')
- assert_line_around_cursor('', "か\u3099")
- input_keys('p')
- assert_line_around_cursor("か\u3099き\u3099く\u3099け\u3099", "こ\u3099")
- input_keys('2p')
- assert_line_around_cursor("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099", "こ\u3099")
- end
-
- def test_vi_prev_next_word
- input_keys("aaa b{b}b ccc\C-[0")
- assert_line_around_cursor('', 'aaa b{b}b ccc')
- input_keys('w')
- assert_line_around_cursor('aaa ', 'b{b}b ccc')
- input_keys('w')
- assert_line_around_cursor('aaa b', '{b}b ccc')
- input_keys('w')
- assert_line_around_cursor('aaa b{', 'b}b ccc')
- input_keys('w')
- assert_line_around_cursor('aaa b{b', '}b ccc')
- input_keys('w')
- assert_line_around_cursor('aaa b{b}', 'b ccc')
- input_keys('w')
- assert_line_around_cursor('aaa b{b}b ', 'ccc')
- input_keys('w')
- assert_line_around_cursor('aaa b{b}b cc', 'c')
- input_keys('b')
- assert_line_around_cursor('aaa b{b}b ', 'ccc')
- input_keys('b')
- assert_line_around_cursor('aaa b{b}', 'b ccc')
- input_keys('b')
- assert_line_around_cursor('aaa b{b', '}b ccc')
- input_keys('b')
- assert_line_around_cursor('aaa b{', 'b}b ccc')
- input_keys('b')
- assert_line_around_cursor('aaa b', '{b}b ccc')
- input_keys('b')
- assert_line_around_cursor('aaa ', 'b{b}b ccc')
- input_keys('b')
- assert_line_around_cursor('', 'aaa b{b}b ccc')
- input_keys('3w')
- assert_line_around_cursor('aaa b{', 'b}b ccc')
- input_keys('3w')
- assert_line_around_cursor('aaa b{b}b ', 'ccc')
- input_keys('3w')
- assert_line_around_cursor('aaa b{b}b cc', 'c')
- input_keys('3b')
- assert_line_around_cursor('aaa b{b', '}b ccc')
- input_keys('3b')
- assert_line_around_cursor('aaa ', 'b{b}b ccc')
- input_keys('3b')
- assert_line_around_cursor('', 'aaa b{b}b ccc')
- end
-
- def test_vi_end_word
- input_keys("aaa b{b}}}b ccc\C-[0")
- assert_line_around_cursor('', 'aaa b{b}}}b ccc')
- input_keys('e')
- assert_line_around_cursor('aa', 'a b{b}}}b ccc')
- input_keys('e')
- assert_line_around_cursor('aaa ', 'b{b}}}b ccc')
- input_keys('e')
- assert_line_around_cursor('aaa b', '{b}}}b ccc')
- input_keys('e')
- assert_line_around_cursor('aaa b{', 'b}}}b ccc')
- input_keys('e')
- assert_line_around_cursor('aaa b{b}}', '}b ccc')
- input_keys('e')
- assert_line_around_cursor('aaa b{b}}}', 'b ccc')
- input_keys('e')
- assert_line_around_cursor('aaa b{b}}}b cc', 'c')
- input_keys('e')
- assert_line_around_cursor('aaa b{b}}}b cc', 'c')
- input_keys('03e')
- assert_line_around_cursor('aaa b', '{b}}}b ccc')
- input_keys('3e')
- assert_line_around_cursor('aaa b{b}}}', 'b ccc')
- input_keys('3e')
- assert_line_around_cursor('aaa b{b}}}b cc', 'c')
- end
-
- def test_vi_prev_next_big_word
- input_keys("aaa b{b}b ccc\C-[0")
- assert_line_around_cursor('', 'aaa b{b}b ccc')
- input_keys('W')
- assert_line_around_cursor('aaa ', 'b{b}b ccc')
- input_keys('W')
- assert_line_around_cursor('aaa b{b}b ', 'ccc')
- input_keys('W')
- assert_line_around_cursor('aaa b{b}b cc', 'c')
- input_keys('B')
- assert_line_around_cursor('aaa b{b}b ', 'ccc')
- input_keys('B')
- assert_line_around_cursor('aaa ', 'b{b}b ccc')
- input_keys('B')
- assert_line_around_cursor('', 'aaa b{b}b ccc')
- input_keys('2W')
- assert_line_around_cursor('aaa b{b}b ', 'ccc')
- input_keys('2W')
- assert_line_around_cursor('aaa b{b}b cc', 'c')
- input_keys('2B')
- assert_line_around_cursor('aaa ', 'b{b}b ccc')
- input_keys('2B')
- assert_line_around_cursor('', 'aaa b{b}b ccc')
- end
-
- def test_vi_end_big_word
- input_keys("aaa b{b}}}b ccc\C-[0")
- assert_line_around_cursor('', 'aaa b{b}}}b ccc')
- input_keys('E')
- assert_line_around_cursor('aa', 'a b{b}}}b ccc')
- input_keys('E')
- assert_line_around_cursor('aaa b{b}}}', 'b ccc')
- input_keys('E')
- assert_line_around_cursor('aaa b{b}}}b cc', 'c')
- input_keys('E')
- assert_line_around_cursor('aaa b{b}}}b cc', 'c')
- end
-
- def test_ed_quoted_insert
- input_keys('ab')
- input_key_by_symbol(:insert_raw_char, char: "\C-a")
- assert_line_around_cursor("ab\C-a", '')
- end
-
- def test_ed_quoted_insert_with_vi_arg
- input_keys("ab\C-[3")
- input_key_by_symbol(:insert_raw_char, char: "\C-a")
- input_keys('4')
- input_key_by_symbol(:insert_raw_char, char: '1')
- assert_line_around_cursor("a\C-a\C-a\C-a1111", 'b')
- end
-
- def test_vi_replace_char
- input_keys("abcdef\C-[03l")
- assert_line_around_cursor('abc', 'def')
- input_keys('rz')
- assert_line_around_cursor('abc', 'zef')
- input_keys('2rx')
- assert_line_around_cursor('abcxx', 'f')
- end
-
- def test_vi_replace_char_with_mbchar
- input_keys("あいうえお\C-[0l")
- assert_line_around_cursor('あ', 'いうえお')
- input_keys('rx')
- assert_line_around_cursor('あ', 'xうえお')
- input_keys('l2ry')
- assert_line_around_cursor('あxyy', 'お')
- end
-
- def test_vi_next_char
- input_keys("abcdef\C-[0")
- assert_line_around_cursor('', 'abcdef')
- input_keys('fz')
- assert_line_around_cursor('', 'abcdef')
- input_keys('fe')
- assert_line_around_cursor('abcd', 'ef')
- end
-
- def test_vi_to_next_char
- input_keys("abcdef\C-[0")
- assert_line_around_cursor('', 'abcdef')
- input_keys('tz')
- assert_line_around_cursor('', 'abcdef')
- input_keys('te')
- assert_line_around_cursor('abc', 'def')
- end
-
- def test_vi_prev_char
- input_keys("abcdef\C-[")
- assert_line_around_cursor('abcde', 'f')
- input_keys('Fz')
- assert_line_around_cursor('abcde', 'f')
- input_keys('Fa')
- assert_line_around_cursor('', 'abcdef')
- end
-
- def test_vi_to_prev_char
- input_keys("abcdef\C-[")
- assert_line_around_cursor('abcde', 'f')
- input_keys('Tz')
- assert_line_around_cursor('abcde', 'f')
- input_keys('Ta')
- assert_line_around_cursor('a', 'bcdef')
- end
-
- def test_vi_delete_next_char
- input_keys("abc\C-[h")
- assert_line_around_cursor('a', 'bc')
- input_keys('x')
- assert_line_around_cursor('a', 'c')
- input_keys('x')
- assert_line_around_cursor('', 'a')
- input_keys('x')
- assert_line_around_cursor('', '')
- input_keys('x')
- assert_line_around_cursor('', '')
- end
-
- def test_vi_delete_next_char_for_mbchar
- input_keys("あいう\C-[h")
- assert_line_around_cursor('あ', 'いう')
- input_keys('x')
- assert_line_around_cursor('あ', 'う')
- input_keys('x')
- assert_line_around_cursor('', 'あ')
- input_keys('x')
- assert_line_around_cursor('', '')
- input_keys('x')
- assert_line_around_cursor('', '')
- end
-
- def test_vi_delete_next_char_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099く\u3099\C-[h")
- assert_line_around_cursor("か\u3099", "き\u3099く\u3099")
- input_keys('x')
- assert_line_around_cursor("か\u3099", "く\u3099")
- input_keys('x')
- assert_line_around_cursor('', "か\u3099")
- input_keys('x')
- assert_line_around_cursor('', '')
- input_keys('x')
- assert_line_around_cursor('', '')
- end
-
- def test_vi_delete_prev_char
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- input_keys("\C-h")
- assert_line_around_cursor('a', '')
- end
-
- def test_vi_delete_prev_char_for_mbchar
- input_keys('かき')
- assert_line_around_cursor('かき', '')
- input_keys("\C-h")
- assert_line_around_cursor('か', '')
- end
-
- def test_vi_delete_prev_char_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("か\u3099き\u3099")
- assert_line_around_cursor("か\u3099き\u3099", '')
- input_keys("\C-h")
- assert_line_around_cursor("か\u3099", '')
- end
-
- def test_ed_delete_prev_char
- input_keys("abcdefg\C-[h")
- assert_line_around_cursor('abcde', 'fg')
- input_keys('X')
- assert_line_around_cursor('abcd', 'fg')
- input_keys('3X')
- assert_line_around_cursor('a', 'fg')
- input_keys('p')
- assert_line_around_cursor('afbc', 'dg')
- end
-
- def test_ed_delete_prev_word
- input_keys('abc def{bbb}ccc')
- assert_line_around_cursor('abc def{bbb}ccc', '')
- input_keys("\C-w")
- assert_line_around_cursor('abc def{bbb}', '')
- input_keys("\C-w")
- assert_line_around_cursor('abc def{', '')
- input_keys("\C-w")
- assert_line_around_cursor('abc ', '')
- input_keys("\C-w")
- assert_line_around_cursor('', '')
- end
-
- def test_ed_delete_prev_word_for_mbchar
- input_keys('あいう かきく{さしす}たちつ')
- assert_line_around_cursor('あいう かきく{さしす}たちつ', '')
- input_keys("\C-w")
- assert_line_around_cursor('あいう かきく{さしす}', '')
- input_keys("\C-w")
- assert_line_around_cursor('あいう かきく{', '')
- input_keys("\C-w")
- assert_line_around_cursor('あいう ', '')
- input_keys("\C-w")
- assert_line_around_cursor('', '')
- end
-
- def test_ed_delete_prev_word_for_mbchar_by_plural_code_points
- omit_unless_utf8
- input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '')
- input_keys("\C-w")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '')
- input_keys("\C-w")
- assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '')
- input_keys("\C-w")
- assert_line_around_cursor('あいう ', '')
- input_keys("\C-w")
- assert_line_around_cursor('', '')
- end
-
- def test_ed_newline_with_cr
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- refute(@line_editor.finished?)
- input_keys("\C-m")
- assert_line_around_cursor('ab', '')
- assert(@line_editor.finished?)
- end
-
- def test_ed_newline_with_lf
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- refute(@line_editor.finished?)
- input_keys("\C-j")
- assert_line_around_cursor('ab', '')
- assert(@line_editor.finished?)
- end
-
- def test_vi_list_or_eof
- input_keys("\C-d") # quit from inputing
- assert_nil(@line_editor.line)
- assert(@line_editor.finished?)
- end
-
- def test_vi_list_or_eof_with_non_empty_line
- input_keys('ab')
- assert_line_around_cursor('ab', '')
- refute(@line_editor.finished?)
- input_keys("\C-d")
- assert_line_around_cursor('ab', '')
- assert(@line_editor.finished?)
- end
-
- def test_completion_journey
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_bar
- foo_bar_baz
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('foo')
- assert_line_around_cursor('foo', '')
- input_keys("\C-n")
- assert_line_around_cursor('foo_bar', '')
- input_keys("\C-n")
- assert_line_around_cursor('foo_bar_baz', '')
- input_keys("\C-n")
- assert_line_around_cursor('foo', '')
- input_keys("\C-n")
- assert_line_around_cursor('foo_bar', '')
- input_keys("_\C-n")
- assert_line_around_cursor('foo_bar_baz', '')
- input_keys("\C-n")
- assert_line_around_cursor('foo_bar_', '')
- end
-
- def test_completion_journey_reverse
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_bar
- foo_bar_baz
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('foo')
- assert_line_around_cursor('foo', '')
- input_keys("\C-p")
- assert_line_around_cursor('foo_bar_baz', '')
- input_keys("\C-p")
- assert_line_around_cursor('foo_bar', '')
- input_keys("\C-p")
- assert_line_around_cursor('foo', '')
- input_keys("\C-p")
- assert_line_around_cursor('foo_bar_baz', '')
- input_keys("\C-h\C-p")
- assert_line_around_cursor('foo_bar_baz', '')
- input_keys("\C-p")
- assert_line_around_cursor('foo_bar_ba', '')
- end
-
- def test_completion_journey_in_middle_of_line
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_bar
- foo_bar_baz
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('abcde fo ABCDE')
- assert_line_around_cursor('abcde fo ABCDE', '')
- input_keys("\C-[" + 'h' * 5 + "i\C-n")
- assert_line_around_cursor('abcde foo_bar', ' ABCDE')
- input_keys("\C-n")
- assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE')
- input_keys("\C-n")
- assert_line_around_cursor('abcde fo', ' ABCDE')
- input_keys("\C-n")
- assert_line_around_cursor('abcde foo_bar', ' ABCDE')
- input_keys("_\C-n")
- assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE')
- input_keys("\C-n")
- assert_line_around_cursor('abcde foo_bar_', ' ABCDE')
- input_keys("\C-n")
- assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE')
- end
-
- def test_completion
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_bar
- foo_bar_baz
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('foo')
- assert_line_around_cursor('foo', '')
- input_keys("\C-i")
- assert_line_around_cursor('foo_bar', '')
- end
-
- def test_autocompletion_with_upward_navigation
- @config.autocompletion = true
- @line_editor.completion_proc = proc { |word|
- %w{
- Readline
- Regexp
- RegexpError
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('Re')
- assert_line_around_cursor('Re', '')
- input_keys("\C-i")
- assert_line_around_cursor('Readline', '')
- input_keys("\C-i")
- assert_line_around_cursor('Regexp', '')
- input_key_by_symbol(:completion_journey_up)
- assert_line_around_cursor('Readline', '')
- ensure
- @config.autocompletion = false
- end
-
- def test_autocompletion_with_upward_navigation_and_menu_complete_backward
- @config.autocompletion = true
- @line_editor.completion_proc = proc { |word|
- %w{
- Readline
- Regexp
- RegexpError
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('Re')
- assert_line_around_cursor('Re', '')
- input_keys("\C-i")
- assert_line_around_cursor('Readline', '')
- input_keys("\C-i")
- assert_line_around_cursor('Regexp', '')
- input_key_by_symbol(:menu_complete_backward)
- assert_line_around_cursor('Readline', '')
- ensure
- @config.autocompletion = false
- end
-
- def test_completion_with_disable_completion
- @config.disable_completion = true
- @line_editor.completion_proc = proc { |word|
- %w{
- foo_bar
- foo_bar_baz
- }.map { |i|
- i.encode(@encoding)
- }
- }
- input_keys('foo')
- assert_line_around_cursor('foo', '')
- input_keys("\C-i")
- assert_line_around_cursor('foo', '')
- end
-
- def test_vi_first_print
- input_keys("abcde\C-[^")
- assert_line_around_cursor('', 'abcde')
- input_keys("0\C-ki")
- input_keys(" abcde\C-[^")
- assert_line_around_cursor(' ', 'abcde')
- input_keys("0\C-ki")
- input_keys(" abcde ABCDE \C-[^")
- assert_line_around_cursor(' ', 'abcde ABCDE ')
- end
-
- def test_ed_move_to_beg
- input_keys("abcde\C-[0")
- assert_line_around_cursor('', 'abcde')
- input_keys("0\C-ki")
- input_keys(" abcde\C-[0")
- assert_line_around_cursor('', ' abcde')
- input_keys("0\C-ki")
- input_keys(" abcde ABCDE \C-[0")
- assert_line_around_cursor('', ' abcde ABCDE ')
- end
-
- def test_vi_to_column
- input_keys("a一二三\C-[0")
- input_keys('1|')
- assert_line_around_cursor('', 'a一二三')
- input_keys('2|')
- assert_line_around_cursor('a', '一二三')
- input_keys('3|')
- assert_line_around_cursor('a', '一二三')
- input_keys('4|')
- assert_line_around_cursor('a一', '二三')
- input_keys('9|')
- assert_line_around_cursor('a一二', '三')
- end
-
- def test_vi_delete_meta
- input_keys("aaa bbb ccc ddd eee\C-[02w")
- assert_line_around_cursor('aaa bbb ', 'ccc ddd eee')
- input_keys('dw')
- assert_line_around_cursor('aaa bbb ', 'ddd eee')
- input_keys('db')
- assert_line_around_cursor('aaa ', 'ddd eee')
- end
-
- def test_vi_delete_meta_nothing
- input_keys("foo\C-[0")
- assert_line_around_cursor('', 'foo')
- input_keys('dhp')
- assert_line_around_cursor('', 'foo')
- end
-
- def test_vi_delete_meta_with_vi_next_word_at_eol
- input_keys("foo bar\C-[0w")
- assert_line_around_cursor('foo ', 'bar')
- input_keys('w')
- assert_line_around_cursor('foo ba', 'r')
- input_keys('0dw')
- assert_line_around_cursor('', 'bar')
- input_keys('dw')
- assert_line_around_cursor('', '')
- end
-
- def test_vi_delete_meta_with_vi_next_char
- input_keys("aaa bbb ccc ___ ddd\C-[02w")
- assert_line_around_cursor('aaa bbb ', 'ccc ___ ddd')
- input_keys('df_')
- assert_line_around_cursor('aaa bbb ', '__ ddd')
- end
-
- def test_vi_delete_meta_with_arg
- input_keys("aaa bbb ccc ddd\C-[03w")
- assert_line_around_cursor('aaa bbb ccc ', 'ddd')
- input_keys('2dl')
- assert_line_around_cursor('aaa bbb ccc ', 'd')
- input_keys('d2h')
- assert_line_around_cursor('aaa bbb cc', 'd')
- input_keys('2d3h')
- assert_line_around_cursor('aaa ', 'd')
- input_keys('dd')
- assert_line_around_cursor('', '')
- end
-
- def test_vi_change_meta
- input_keys("aaa bbb ccc ddd eee\C-[02w")
- assert_line_around_cursor('aaa bbb ', 'ccc ddd eee')
- input_keys('cwaiueo')
- assert_line_around_cursor('aaa bbb aiueo', ' ddd eee')
- input_keys("\C-[")
- assert_line_around_cursor('aaa bbb aiue', 'o ddd eee')
- input_keys('cb')
- assert_line_around_cursor('aaa bbb ', 'o ddd eee')
- end
-
- def test_vi_change_meta_with_vi_next_word
- input_keys("foo bar baz\C-[0w")
- assert_line_around_cursor('foo ', 'bar baz')
- input_keys('cwhoge')
- assert_line_around_cursor('foo hoge', ' baz')
- input_keys("\C-[")
- assert_line_around_cursor('foo hog', 'e baz')
- end
-
- def test_vi_waiting_operator_with_waiting_proc
- input_keys("foo foo foo foo foo\C-[0")
- input_keys('2d3fo')
- assert_line_around_cursor('', ' foo foo')
- input_keys('fo')
- assert_line_around_cursor(' f', 'oo foo')
- end
-
- def test_waiting_operator_arg_including_zero
- input_keys("a111111111111222222222222\C-[0")
- input_keys('10df1')
- assert_line_around_cursor('', '11222222222222')
- input_keys('d10f2')
- assert_line_around_cursor('', '22')
- end
-
- def test_vi_waiting_operator_cancel
- input_keys("aaa bbb ccc\C-[02w")
- assert_line_around_cursor('aaa bbb ', 'ccc')
- # dc dy should cancel delete_meta
- input_keys('dch')
- input_keys('dyh')
- # cd cy should cancel change_meta
- input_keys('cdh')
- input_keys('cyh')
- # yd yc should cancel yank_meta
- # P should not paste yanked text because yank_meta is canceled
- input_keys('ydhP')
- input_keys('ychP')
- assert_line_around_cursor('aa', 'a bbb ccc')
- end
-
- def test_cancel_waiting_with_symbol_key
- input_keys("aaa bbb lll\C-[0")
- assert_line_around_cursor('', 'aaa bbb lll')
- # ed_next_char should move cursor right and cancel vi_next_char
- input_keys('f')
- input_key_by_symbol(:ed_next_char, csi: true)
- input_keys('l')
- assert_line_around_cursor('aa', 'a bbb lll')
- # vi_delete_meta + ed_next_char should delete character
- input_keys('d')
- input_key_by_symbol(:ed_next_char, csi: true)
- input_keys('l')
- assert_line_around_cursor('aa ', 'bbb lll')
- end
-
- def test_unimplemented_vi_command_should_be_no_op
- input_keys("abc\C-[h")
- assert_line_around_cursor('a', 'bc')
- input_keys('@')
- assert_line_around_cursor('a', 'bc')
- end
-
- def test_vi_yank
- input_keys("foo bar\C-[2h")
- assert_line_around_cursor('foo ', 'bar')
- input_keys('y3l')
- assert_line_around_cursor('foo ', 'bar')
- input_keys('P')
- assert_line_around_cursor('foo ba', 'rbar')
- input_keys('3h3yhP')
- assert_line_around_cursor('foofo', 'o barbar')
- input_keys('yyP')
- assert_line_around_cursor('foofofoofoo barba', 'ro barbar')
- end
-
- def test_vi_yank_nothing
- input_keys("foo\C-[0")
- assert_line_around_cursor('', 'foo')
- input_keys('yhp')
- assert_line_around_cursor('', 'foo')
- end
-
- def test_vi_end_word_with_operator
- input_keys("foo bar\C-[0")
- assert_line_around_cursor('', 'foo bar')
- input_keys('de')
- assert_line_around_cursor('', ' bar')
- input_keys('de')
- assert_line_around_cursor('', '')
- input_keys('de')
- assert_line_around_cursor('', '')
- end
-
- def test_vi_end_big_word_with_operator
- input_keys("aaa b{b}}}b\C-[0")
- assert_line_around_cursor('', 'aaa b{b}}}b')
- input_keys('dE')
- assert_line_around_cursor('', ' b{b}}}b')
- input_keys('dE')
- assert_line_around_cursor('', '')
- input_keys('dE')
- assert_line_around_cursor('', '')
- end
-
- def test_vi_next_char_with_operator
- input_keys("foo bar\C-[0")
- assert_line_around_cursor('', 'foo bar')
- input_keys('df ')
- assert_line_around_cursor('', 'bar')
- end
-
- def test_ed_delete_next_char_at_eol
- input_keys('"あ"')
- assert_line_around_cursor('"あ"', '')
- input_keys("\C-[")
- assert_line_around_cursor('"あ', '"')
- input_keys('xa"')
- assert_line_around_cursor('"あ"', '')
- end
-
- def test_vi_kill_line_prev
- input_keys("\C-u")
- assert_line_around_cursor('', '')
- input_keys('abc')
- assert_line_around_cursor('abc', '')
- input_keys("\C-u")
- assert_line_around_cursor('', '')
- input_keys('abc')
- input_keys("\C-[\C-u")
- assert_line_around_cursor('', 'c')
- input_keys("\C-u")
- assert_line_around_cursor('', 'c')
- end
-
- def test_vi_change_to_eol
- input_keys("abcdef\C-[2hC")
- assert_line_around_cursor('abc', '')
- input_keys("\C-[0C")
- assert_line_around_cursor('', '')
- assert_equal(:vi_insert, editing_mode_label)
- end
-
- def test_vi_motion_operators
- assert_equal(:vi_insert, editing_mode_label)
-
- assert_nothing_raised do
- input_keys("test = { foo: bar }\C-[BBBldt}b")
- end
- end
-
- def test_emacs_editing_mode
- @line_editor.__send__(:emacs_editing_mode, nil)
- assert(@config.editing_mode_is?(:emacs))
- end
-end
diff --git a/test/reline/test_key_stroke.rb b/test/reline/test_key_stroke.rb
deleted file mode 100644
index fb2cb1c8b8..0000000000
--- a/test/reline/test_key_stroke.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require_relative 'helper'
-
-class Reline::KeyStroke::Test < Reline::TestCase
- def encoding
- Reline.core.encoding
- end
-
- def test_match_status
- config = Reline::Config.new
- {
- 'a' => 'xx',
- 'ab' => 'y',
- 'abc' => 'z',
- 'x' => 'rr'
- }.each_pair do |key, func|
- config.add_default_key_binding(key.bytes, func.bytes)
- end
- stroke = Reline::KeyStroke.new(config, encoding)
- assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("a".bytes))
- assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("ab".bytes))
- assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("abc".bytes))
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abz".bytes))
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abcx".bytes))
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("aa".bytes))
- assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("x".bytes))
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("xa".bytes))
- end
-
- def test_match_unknown
- config = Reline::Config.new
- config.add_default_key_binding("\e[9abc".bytes, 'x')
- stroke = Reline::KeyStroke.new(config, encoding)
- sequences = [
- "\e[9abc",
- "\e[9d",
- "\e[A", # Up
- "\e[1;1R", # Cursor position report
- "\e[15~", # F5
- "\eOP", # F1
- "\e\e[A", # Option+Up
- "\eX",
- "\e\eX"
- ]
- sequences.each do |seq|
- assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status(seq.bytes))
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(seq.bytes + [32]))
- (2...seq.size).each do |i|
- assert_equal(Reline::KeyStroke::MATCHING, stroke.match_status(seq.bytes.take(i)))
- end
- end
- end
-
- def test_expand
- config = Reline::Config.new
- {
- 'abc' => 'AB',
- 'ab' => "1\C-a"
- }.each_pair do |key, func|
- config.add_default_key_binding(key.bytes, func.bytes)
- end
- stroke = Reline::KeyStroke.new(config, encoding)
- assert_equal([[Reline::Key.new('A', :ed_insert, false), Reline::Key.new('B', :ed_insert, false)], 'de'.bytes], stroke.expand('abcde'.bytes))
- assert_equal([[Reline::Key.new('1', :ed_digit, false), Reline::Key.new("\C-a", :ed_move_to_beg, false)], 'de'.bytes], stroke.expand('abde'.bytes))
- # CSI sequence
- assert_equal([[], 'bc'.bytes], stroke.expand("\e[1;2;3;4;5abc".bytes))
- assert_equal([[], 'BC'.bytes], stroke.expand("\e\e[ABC".bytes))
- # SS3 sequence
- assert_equal([[], 'QR'.bytes], stroke.expand("\eOPQR".bytes))
- end
-
- def test_oneshot_key_bindings
- config = Reline::Config.new
- {
- 'abc'.bytes => '123',
- # IRB version <= 1.13.1 wrongly uses Reline::Key with wrong argument. It should be ignored without error.
- [Reline::Key.new(nil, 0xE4, true)] => '012',
- "\eda".bytes => 'abc', # Alt+d a
- [195, 164] => 'def'
- }.each_pair do |key, func|
- config.add_oneshot_key_binding(key, func.bytes)
- end
- stroke = Reline::KeyStroke.new(config, encoding)
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('zzz'.bytes))
- assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status('abc'.bytes))
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('da'.bytes))
- assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("\eda".bytes))
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(" \eda".bytes))
- assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status([195, 164]))
- end
-
- def test_multibyte_matching
- begin
- char = 'あ'.encode(encoding)
- rescue Encoding::UndefinedConversionError
- omit
- end
- config = Reline::Config.new
- stroke = Reline::KeyStroke.new(config, encoding)
- key = Reline::Key.new(char, :ed_insert, false)
- bytes = char.bytes
- assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status(bytes))
- assert_equal([[key], []], stroke.expand(bytes))
- assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(bytes * 2))
- assert_equal([[key], bytes], stroke.expand(bytes * 2))
- (1...bytes.size).each do |i|
- partial_bytes = bytes.take(i)
- assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status(partial_bytes))
- assert_equal([[], []], stroke.expand(partial_bytes))
- end
- end
-end
diff --git a/test/reline/test_kill_ring.rb b/test/reline/test_kill_ring.rb
deleted file mode 100644
index 9f6e0c3e74..0000000000
--- a/test/reline/test_kill_ring.rb
+++ /dev/null
@@ -1,268 +0,0 @@
-require_relative 'helper'
-
-class Reline::KillRing::Test < Reline::TestCase
- def setup
- @prompt = '> '
- @kill_ring = Reline::KillRing.new
- end
-
- def test_append_one
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('a', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('a', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['a', 'a'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['a', 'a'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_append_two
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('b')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('b', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('b', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['a', 'b'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['b', 'a'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_append_three
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('b')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('c')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('c', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('c', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['b', 'c'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['a', 'b'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['c', 'a'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_append_three_with_max_two
- @kill_ring = Reline::KillRing.new(2)
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('b')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('c')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('c', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('c', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['b', 'c'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['c', 'b'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['b', 'c'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_append_four_with_max_two
- @kill_ring = Reline::KillRing.new(2)
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('b')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('c')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('d')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('d', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('d', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['c', 'd'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['d', 'c'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['c', 'd'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_append_after
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('b')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('ab', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('ab', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['ab', 'ab'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['ab', 'ab'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_append_before
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('b', true)
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('ba', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('ba', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['ba', 'ba'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['ba', 'ba'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_append_chain_two
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('b')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('c')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('d')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('cd', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('cd', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['ab', 'cd'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['cd', 'ab'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_append_complex_chain
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('c')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('d')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('b', true)
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('e')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('a', true)
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('A')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.append('B')
- assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
- @kill_ring.process
- assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
- assert_equal('AB', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal('AB', @kill_ring.yank)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['abcde', 'AB'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- assert_equal(['AB', 'abcde'], @kill_ring.yank_pop)
- assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
- end
-
- def test_enumerable
- @kill_ring.append('a')
- @kill_ring.process
- @kill_ring.process
- @kill_ring.append('b')
- @kill_ring.process
- @kill_ring.process
- @kill_ring.append('c')
- @kill_ring.process
- assert_equal(%w{c b a}, @kill_ring.to_a)
- end
-end
diff --git a/test/reline/test_line_editor.rb b/test/reline/test_line_editor.rb
deleted file mode 100644
index 28fcbfa6df..0000000000
--- a/test/reline/test_line_editor.rb
+++ /dev/null
@@ -1,271 +0,0 @@
-require_relative 'helper'
-require 'reline/line_editor'
-require 'stringio'
-
-class Reline::LineEditor
-
- class CompletionBlockTest < Reline::TestCase
- def setup
- @original_quote_characters = Reline.completer_quote_characters
- @original_word_break_characters = Reline.completer_word_break_characters
- @line_editor = Reline::LineEditor.new(nil)
- end
-
- def retrieve_completion_block(lines, line_index, byte_pointer)
- @line_editor.instance_variable_set(:@buffer_of_lines, lines)
- @line_editor.instance_variable_set(:@line_index, line_index)
- @line_editor.instance_variable_set(:@byte_pointer, byte_pointer)
- @line_editor.retrieve_completion_block
- end
-
- def retrieve_completion_quote(line)
- _, _, _, quote = retrieve_completion_block([line], 0, line.bytesize)
- quote
- end
-
- def teardown
- Reline.completer_quote_characters = @original_quote_characters
- Reline.completer_word_break_characters = @original_word_break_characters
- end
-
- def test_retrieve_completion_block
- Reline.completer_word_break_characters = ' ([{'
- Reline.completer_quote_characters = ''
- assert_equal(['', '', 'foo', nil], retrieve_completion_block(['foo'], 0, 0))
- assert_equal(['', 'f', 'oo', nil], retrieve_completion_block(['foo'], 0, 1))
- assert_equal(['foo ', 'ba', 'r baz', nil], retrieve_completion_block(['foo bar baz'], 0, 6))
- assert_equal(['foo([', 'b', 'ar])baz', nil], retrieve_completion_block(['foo([bar])baz'], 0, 6))
- assert_equal(['foo([{', '', '}])baz', nil], retrieve_completion_block(['foo([{}])baz'], 0, 6))
- assert_equal(["abc\nfoo ", 'ba', "r baz\ndef", nil], retrieve_completion_block(['abc', 'foo bar baz', 'def'], 1, 6))
- end
-
- def test_retrieve_completion_block_with_quote_characters
- Reline.completer_word_break_characters = ' ([{'
- Reline.completer_quote_characters = ''
- assert_equal(['"" ', '"wo', 'rd', nil], retrieve_completion_block(['"" "word'], 0, 6))
- Reline.completer_quote_characters = '"'
- assert_equal(['"" "', 'wo', 'rd', nil], retrieve_completion_block(['"" "word'], 0, 6))
- end
-
- def test_retrieve_completion_quote
- Reline.completer_quote_characters = '"\''
- assert_equal('"', retrieve_completion_quote('"\''))
- assert_equal(nil, retrieve_completion_quote('""'))
- assert_equal("'", retrieve_completion_quote('""\'"'))
- assert_equal(nil, retrieve_completion_quote('""\'\''))
- assert_equal('"', retrieve_completion_quote('"\\"'))
- assert_equal(nil, retrieve_completion_quote('"\\""'))
- assert_equal(nil, retrieve_completion_quote('"\\\\"'))
- end
- end
-
- class CursorPositionTest < Reline::TestCase
- def setup
- @line_editor = Reline::LineEditor.new(nil)
- @line_editor.instance_variable_set(:@config, Reline::Config.new)
- end
-
- def test_cursor_position_with_escaped_input
- @line_editor.instance_variable_set(:@screen_size, [4, 16])
- @line_editor.instance_variable_set(:@prompt, "\e[1mprompt\e[0m> ")
- @line_editor.instance_variable_set(:@buffer_of_lines, ["\e[1m\0\1\2\3\4\5\6\7abcd"])
- @line_editor.instance_variable_set(:@line_index, 0)
- # prompt> ^[[1m^@^
- # A^B^C^D^E^F^Gabc
- # d
- @line_editor.instance_variable_set(:@byte_pointer, 0)
- assert_equal [8, 0], @line_editor.wrapped_cursor_position
- @line_editor.instance_variable_set(:@byte_pointer, 5)
- assert_equal [15, 0], @line_editor.wrapped_cursor_position
- @line_editor.instance_variable_set(:@byte_pointer, 6)
- assert_equal [1, 1], @line_editor.wrapped_cursor_position
- @line_editor.instance_variable_set(:@byte_pointer, 14)
- assert_equal [15, 1], @line_editor.wrapped_cursor_position
- @line_editor.instance_variable_set(:@byte_pointer, 15)
- assert_equal [0, 2], @line_editor.wrapped_cursor_position
- @line_editor.instance_variable_set(:@byte_pointer, 16)
- assert_equal [1, 2], @line_editor.wrapped_cursor_position
- end
- end
-
- class RenderLineDifferentialTest < Reline::TestCase
- class TestIO < Reline::IO
- def write(string)
- @output << string
- end
-
- def move_cursor_column(col)
- @output << "[COL_#{col}]"
- end
-
- def erase_after_cursor
- @output << '[ERASE]'
- end
- end
-
- def setup
- verbose, $VERBOSE = $VERBOSE, nil
- @line_editor = Reline::LineEditor.new(nil)
- @original_iogate = Reline::IOGate
- @output = StringIO.new
- @line_editor.instance_variable_set(:@screen_size, [24, 80])
- Reline.send(:remove_const, :IOGate)
- Reline.const_set(:IOGate, TestIO.new)
- Reline::IOGate.instance_variable_set(:@output, @output)
- ensure
- $VERBOSE = verbose
- end
-
- def assert_output(expected)
- @output.reopen(+'')
- yield
- actual = @output.string
- assert_equal(expected, actual.gsub("\e[0m", ''))
- end
-
- def teardown
- Reline.send(:remove_const, :IOGate)
- Reline.const_set(:IOGate, @original_iogate)
- end
-
- def test_line_increase_decrease
- assert_output '[COL_0]bb' do
- @line_editor.render_line_differential([[0, 1, 'a']], [[0, 2, 'bb']])
- end
-
- assert_output '[COL_0]b[COL_1][ERASE]' do
- @line_editor.render_line_differential([[0, 2, 'aa']], [[0, 1, 'b']])
- end
- end
-
- def test_dialog_appear_disappear
- assert_output '[COL_3]dialog' do
- @line_editor.render_line_differential([[0, 1, 'a']], [[0, 1, 'a'], [3, 6, 'dialog']])
- end
-
- assert_output '[COL_3]dialog' do
- @line_editor.render_line_differential([[0, 10, 'a' * 10]], [[0, 10, 'a' * 10], [3, 6, 'dialog']])
- end
-
- assert_output '[COL_1][ERASE]' do
- @line_editor.render_line_differential([[0, 1, 'a'], [3, 6, 'dialog']], [[0, 1, 'a']])
- end
-
- assert_output '[COL_3]aaaaaa' do
- @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10]])
- end
- end
-
- def test_dialog_change
- assert_output '[COL_3]DIALOG' do
- @line_editor.render_line_differential([[0, 2, 'a'], [3, 6, 'dialog']], [[0, 2, 'a'], [3, 6, 'DIALOG']])
- end
-
- assert_output '[COL_3]DIALOG' do
- @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10], [3, 6, 'DIALOG']])
- end
- end
-
- def test_update_under_dialog
- assert_output '[COL_0]b[COL_1] ' do
- @line_editor.render_line_differential([[0, 2, 'aa'], [4, 6, 'dialog']], [[0, 1, 'b'], [4, 6, 'dialog']])
- end
-
- assert_output '[COL_0]bbb[COL_9]b' do
- @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'b' * 10], [3, 6, 'dialog']])
- end
-
- assert_output '[COL_0]b[COL_1] [COL_9][ERASE]' do
- @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 1, 'b'], [3, 6, 'dialog']])
- end
- end
-
- def test_dialog_move
- assert_output '[COL_3]dialog[COL_9][ERASE]' do
- @line_editor.render_line_differential([[0, 1, 'a'], [4, 6, 'dialog']], [[0, 1, 'a'], [3, 6, 'dialog']])
- end
-
- assert_output '[COL_4] [COL_5]dialog' do
- @line_editor.render_line_differential([[0, 1, 'a'], [4, 6, 'dialog']], [[0, 1, 'a'], [5, 6, 'dialog']])
- end
-
- assert_output '[COL_2]dialog[COL_8]a' do
- @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10], [2, 6, 'dialog']])
- end
-
- assert_output '[COL_2]a[COL_3]dialog' do
- @line_editor.render_line_differential([[0, 10, 'a' * 10], [2, 6, 'dialog']], [[0, 10, 'a' * 10], [3, 6, 'dialog']])
- end
- end
-
- def test_multibyte
- base = [0, 12, '一二三一二三']
- left = [0, 3, 'LLL']
- right = [9, 3, 'RRR']
- front = [3, 6, 'FFFFFF']
- # 一 FFFFFF 三
- # 一二三一二三
- assert_output '[COL_2]二三一二' do
- @line_editor.render_line_differential([base, front], [base, nil])
- end
-
- # LLLFFFFFF 三
- # LLL 三一二三
- assert_output '[COL_3] 三一二' do
- @line_editor.render_line_differential([base, left, front], [base, left, nil])
- end
-
- # 一 FFFFFFRRR
- # 一二三一 RRR
- assert_output '[COL_2]二三一 ' do
- @line_editor.render_line_differential([base, right, front], [base, right, nil])
- end
-
- # LLLFFFFFFRRR
- # LLL 三一 RRR
- assert_output '[COL_3] 三一 ' do
- @line_editor.render_line_differential([base, left, right, front], [base, left, right, nil])
- end
- end
-
- def test_complicated
- state_a = [nil, [19, 7, 'bbbbbbb'], [15, 8, 'cccccccc'], [10, 5, 'ddddd'], [18, 4, 'eeee'], [1, 3, 'fff'], [17, 2, 'gg'], [7, 1, 'h']]
- state_b = [[5, 9, 'aaaaaaaaa'], nil, [15, 8, 'cccccccc'], nil, [18, 4, 'EEEE'], [25, 4, 'ffff'], [17, 2, 'gg'], [2, 2, 'hh']]
- # state_a: " fff h dddddccggeeecbbb"
- # state_b: " hh aaaaaaaaa ccggEEEc ffff"
-
- assert_output '[COL_1] [COL_2]hh[COL_5]aaaaaaaaa[COL_14] [COL_19]EEE[COL_23] [COL_25]ffff' do
- @line_editor.render_line_differential(state_a, state_b)
- end
-
- assert_output '[COL_1]fff[COL_5] [COL_7]h[COL_8] [COL_10]ddddd[COL_19]eee[COL_23]bbb[COL_26][ERASE]' do
- @line_editor.render_line_differential(state_b, state_a)
- end
- end
- end
-
- def test_menu_info_format
- list = %w[aa b c d e f g hhh i j k]
- col3 = [
- 'aa e i',
- 'b f j',
- 'c g k',
- 'd hhh'
- ]
- col2 = [
- 'aa g',
- 'b hhh',
- 'c i',
- 'd j',
- 'e k',
- 'f'
- ]
- assert_equal(col3, Reline::LineEditor::MenuInfo.new(list).lines(19))
- assert_equal(col3, Reline::LineEditor::MenuInfo.new(list).lines(15))
- assert_equal(col2, Reline::LineEditor::MenuInfo.new(list).lines(14))
- assert_equal(col2, Reline::LineEditor::MenuInfo.new(list).lines(10))
- assert_equal(list, Reline::LineEditor::MenuInfo.new(list).lines(9))
- assert_equal(list, Reline::LineEditor::MenuInfo.new(list).lines(0))
- assert_equal([], Reline::LineEditor::MenuInfo.new([]).lines(10))
- end
-end
diff --git a/test/reline/test_macro.rb b/test/reline/test_macro.rb
deleted file mode 100644
index cacdb76c60..0000000000
--- a/test/reline/test_macro.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-require_relative 'helper'
-
-class Reline::MacroTest < Reline::TestCase
- def setup
- Reline.send(:test_mode)
- @config = Reline::Config.new
- @encoding = Reline.core.encoding
- @line_editor = Reline::LineEditor.new(@config)
- @output = Reline::IOGate.output = File.open(IO::NULL, "w")
- end
-
- def teardown
- @output.close
- Reline.test_reset
- end
-
- def input_key(char, method_symbol = :ed_insert)
- @line_editor.input_key(Reline::Key.new(char, method_symbol, false))
- end
-
- def input(str)
- str.each_char {|c| input_key(c)}
- end
-
- def test_simple_input
- input('abc')
- assert_equal 'abc', @line_editor.line
- end
-
- def test_alias
- class << @line_editor
- alias delete_char ed_delete_prev_char
- end
- input('abc')
- assert_nothing_raised(ArgumentError) {
- input_key('x', :delete_char)
- }
- assert_equal 'ab', @line_editor.line
- end
-end
diff --git a/test/reline/test_reline.rb b/test/reline/test_reline.rb
deleted file mode 100644
index 691ed9ffda..0000000000
--- a/test/reline/test_reline.rb
+++ /dev/null
@@ -1,487 +0,0 @@
-require_relative 'helper'
-require 'reline'
-require 'stringio'
-begin
- require "pty"
-rescue LoadError # some platforms don't support PTY
-end
-
-class Reline::Test < Reline::TestCase
- class DummyCallbackObject
- def call; end
- end
-
- def setup
- Reline.send(:test_mode)
- Reline.output_modifier_proc = nil
- Reline.completion_proc = nil
- Reline.prompt_proc = nil
- Reline.auto_indent_proc = nil
- Reline.pre_input_hook = nil
- Reline.dig_perfect_match_proc = nil
- end
-
- def teardown
- Reline.test_reset
- end
-
- def test_completion_append_character
- completion_append_character = Reline.completion_append_character
-
- assert_equal(nil, Reline.completion_append_character)
-
- Reline.completion_append_character = ""
- assert_equal(nil, Reline.completion_append_character)
-
- Reline.completion_append_character = "a".encode(Encoding::ASCII)
- assert_equal("a", Reline.completion_append_character)
- assert_equal(get_reline_encoding, Reline.completion_append_character.encoding)
-
- Reline.completion_append_character = "ba".encode(Encoding::ASCII)
- assert_equal("b", Reline.completion_append_character)
- assert_equal(get_reline_encoding, Reline.completion_append_character.encoding)
-
- Reline.completion_append_character = "cba".encode(Encoding::ASCII)
- assert_equal("c", Reline.completion_append_character)
- assert_equal(get_reline_encoding, Reline.completion_append_character.encoding)
-
- Reline.completion_append_character = nil
- assert_equal(nil, Reline.completion_append_character)
- ensure
- Reline.completion_append_character = completion_append_character
- end
-
- def test_basic_word_break_characters
- basic_word_break_characters = Reline.basic_word_break_characters
-
- assert_equal(" \t\n`><=;|&{(", Reline.basic_word_break_characters)
-
- Reline.basic_word_break_characters = "[".encode(Encoding::ASCII)
- assert_equal("[", Reline.basic_word_break_characters)
- assert_equal(get_reline_encoding, Reline.basic_word_break_characters.encoding)
- ensure
- Reline.basic_word_break_characters = basic_word_break_characters
- end
-
- def test_completer_word_break_characters
- completer_word_break_characters = Reline.completer_word_break_characters
-
- assert_equal(" \t\n`><=;|&{(", Reline.completer_word_break_characters)
-
- Reline.completer_word_break_characters = "[".encode(Encoding::ASCII)
- assert_equal("[", Reline.completer_word_break_characters)
- assert_equal(get_reline_encoding, Reline.completer_word_break_characters.encoding)
-
- assert_nothing_raised { Reline.completer_word_break_characters = '' }
- ensure
- Reline.completer_word_break_characters = completer_word_break_characters
- end
-
- def test_basic_quote_characters
- basic_quote_characters = Reline.basic_quote_characters
-
- assert_equal('"\'', Reline.basic_quote_characters)
-
- Reline.basic_quote_characters = "`".encode(Encoding::ASCII)
- assert_equal("`", Reline.basic_quote_characters)
- assert_equal(get_reline_encoding, Reline.basic_quote_characters.encoding)
- ensure
- Reline.basic_quote_characters = basic_quote_characters
- end
-
- def test_completer_quote_characters
- completer_quote_characters = Reline.completer_quote_characters
-
- assert_equal('"\'', Reline.completer_quote_characters)
-
- Reline.completer_quote_characters = "`".encode(Encoding::ASCII)
- assert_equal("`", Reline.completer_quote_characters)
- assert_equal(get_reline_encoding, Reline.completer_quote_characters.encoding)
-
- assert_nothing_raised { Reline.completer_quote_characters = '' }
- ensure
- Reline.completer_quote_characters = completer_quote_characters
- end
-
- def test_filename_quote_characters
- filename_quote_characters = Reline.filename_quote_characters
-
- assert_equal('', Reline.filename_quote_characters)
-
- Reline.filename_quote_characters = "\'".encode(Encoding::ASCII)
- assert_equal("\'", Reline.filename_quote_characters)
- assert_equal(get_reline_encoding, Reline.filename_quote_characters.encoding)
- ensure
- Reline.filename_quote_characters = filename_quote_characters
- end
-
- def test_special_prefixes
- special_prefixes = Reline.special_prefixes
-
- assert_equal('', Reline.special_prefixes)
-
- Reline.special_prefixes = "\'".encode(Encoding::ASCII)
- assert_equal("\'", Reline.special_prefixes)
- assert_equal(get_reline_encoding, Reline.special_prefixes.encoding)
- ensure
- Reline.special_prefixes = special_prefixes
- end
-
- def test_completion_case_fold
- completion_case_fold = Reline.completion_case_fold
-
- assert_equal(nil, Reline.completion_case_fold)
-
- Reline.completion_case_fold = true
- assert_equal(true, Reline.completion_case_fold)
-
- Reline.completion_case_fold = "hoge".encode(Encoding::ASCII)
- assert_equal("hoge", Reline.completion_case_fold)
- ensure
- Reline.completion_case_fold = completion_case_fold
- end
-
- def test_completion_proc
- omit unless Reline.completion_proc == nil
- # Another test can set Reline.completion_proc
-
- # assert_equal(nil, Reline.completion_proc)
-
- dummy_proc = proc {}
- Reline.completion_proc = dummy_proc
- assert_equal(dummy_proc, Reline.completion_proc)
-
- l = lambda {}
- Reline.completion_proc = l
- assert_equal(l, Reline.completion_proc)
-
- assert_raise(ArgumentError) { Reline.completion_proc = 42 }
- assert_raise(ArgumentError) { Reline.completion_proc = "hoge" }
-
- dummy = DummyCallbackObject.new
- Reline.completion_proc = dummy
- assert_equal(dummy, Reline.completion_proc)
- end
-
- def test_output_modifier_proc
- assert_equal(nil, Reline.output_modifier_proc)
-
- dummy_proc = proc {}
- Reline.output_modifier_proc = dummy_proc
- assert_equal(dummy_proc, Reline.output_modifier_proc)
-
- l = lambda {}
- Reline.output_modifier_proc = l
- assert_equal(l, Reline.output_modifier_proc)
-
- assert_raise(ArgumentError) { Reline.output_modifier_proc = 42 }
- assert_raise(ArgumentError) { Reline.output_modifier_proc = "hoge" }
-
- dummy = DummyCallbackObject.new
- Reline.output_modifier_proc = dummy
- assert_equal(dummy, Reline.output_modifier_proc)
- end
-
- def test_prompt_proc
- assert_equal(nil, Reline.prompt_proc)
-
- dummy_proc = proc {}
- Reline.prompt_proc = dummy_proc
- assert_equal(dummy_proc, Reline.prompt_proc)
-
- l = lambda {}
- Reline.prompt_proc = l
- assert_equal(l, Reline.prompt_proc)
-
- assert_raise(ArgumentError) { Reline.prompt_proc = 42 }
- assert_raise(ArgumentError) { Reline.prompt_proc = "hoge" }
-
- dummy = DummyCallbackObject.new
- Reline.prompt_proc = dummy
- assert_equal(dummy, Reline.prompt_proc)
- end
-
- def test_auto_indent_proc
- assert_equal(nil, Reline.auto_indent_proc)
-
- dummy_proc = proc {}
- Reline.auto_indent_proc = dummy_proc
- assert_equal(dummy_proc, Reline.auto_indent_proc)
-
- l = lambda {}
- Reline.auto_indent_proc = l
- assert_equal(l, Reline.auto_indent_proc)
-
- assert_raise(ArgumentError) { Reline.auto_indent_proc = 42 }
- assert_raise(ArgumentError) { Reline.auto_indent_proc = "hoge" }
-
- dummy = DummyCallbackObject.new
- Reline.auto_indent_proc = dummy
- assert_equal(dummy, Reline.auto_indent_proc)
- end
-
- def test_pre_input_hook
- assert_equal(nil, Reline.pre_input_hook)
-
- dummy_proc = proc {}
- Reline.pre_input_hook = dummy_proc
- assert_equal(dummy_proc, Reline.pre_input_hook)
-
- l = lambda {}
- Reline.pre_input_hook = l
- assert_equal(l, Reline.pre_input_hook)
- end
-
- def test_dig_perfect_match_proc
- assert_equal(nil, Reline.dig_perfect_match_proc)
-
- dummy_proc = proc {}
- Reline.dig_perfect_match_proc = dummy_proc
- assert_equal(dummy_proc, Reline.dig_perfect_match_proc)
-
- l = lambda {}
- Reline.dig_perfect_match_proc = l
- assert_equal(l, Reline.dig_perfect_match_proc)
-
- assert_raise(ArgumentError) { Reline.dig_perfect_match_proc = 42 }
- assert_raise(ArgumentError) { Reline.dig_perfect_match_proc = "hoge" }
-
- dummy = DummyCallbackObject.new
- Reline.dig_perfect_match_proc = dummy
- assert_equal(dummy, Reline.dig_perfect_match_proc)
- end
-
- def test_insert_text
- assert_equal('', Reline.line_buffer)
- assert_equal(0, Reline.point)
- Reline.insert_text('abc')
- assert_equal('abc', Reline.line_buffer)
- assert_equal(3, Reline.point)
- end
-
- def test_delete_text
- assert_equal('', Reline.line_buffer)
- assert_equal(0, Reline.point)
- Reline.insert_text('abc')
- assert_equal('abc', Reline.line_buffer)
- assert_equal(3, Reline.point)
- Reline.delete_text()
- assert_equal('', Reline.line_buffer)
- assert_equal(0, Reline.point)
- Reline.insert_text('abc')
- Reline.delete_text(1)
- assert_equal('a', Reline.line_buffer)
- assert_equal(1, Reline.point)
- Reline.insert_text('defghi')
- Reline.delete_text(2, 2)
- assert_equal('adghi', Reline.line_buffer)
- assert_equal(5, Reline.point)
- end
-
- def test_set_input_and_output
- assert_raise(TypeError) do
- Reline.input = "This is not a file."
- end
- assert_raise(TypeError) do
- Reline.output = "This is not a file."
- end
-
- input, to_write = IO.pipe
- to_read, output = IO.pipe
- unless Reline.__send__(:input=, input)
- omit "Setting to input is not effective on #{Reline.core.io_gate}"
- end
- Reline.output = output
-
- to_write.write "a\n"
- result = Reline.readline
- to_write.close
- read_text = to_read.read_nonblock(100)
- assert_equal('a', result)
- refute(read_text.empty?)
- ensure
- input&.close
- output&.close
- to_read&.close
- end
-
- def test_vi_editing_mode
- Reline.vi_editing_mode
- assert_equal(:vi_insert, Reline.core.config.instance_variable_get(:@editing_mode_label))
- end
-
- def test_emacs_editing_mode
- Reline.emacs_editing_mode
- assert_equal(:emacs, Reline.core.config.instance_variable_get(:@editing_mode_label))
- end
-
- def test_add_dialog_proc
- dummy_proc = proc {}
- Reline.add_dialog_proc(:test_proc, dummy_proc)
- d = Reline.dialog_proc(:test_proc)
- assert_equal(dummy_proc, d.dialog_proc)
-
- dummy_proc_2 = proc {}
- Reline.add_dialog_proc(:test_proc, dummy_proc_2)
- d = Reline.dialog_proc(:test_proc)
- assert_equal(dummy_proc_2, d.dialog_proc)
-
- Reline.add_dialog_proc(:test_proc, nil)
- assert_nil(Reline.dialog_proc(:test_proc))
-
- l = lambda {}
- Reline.add_dialog_proc(:test_lambda, l)
- d = Reline.dialog_proc(:test_lambda)
- assert_equal(l, d.dialog_proc)
-
- assert_equal(nil, Reline.dialog_proc(:test_nothing))
-
- assert_raise(ArgumentError) { Reline.add_dialog_proc(:error, 42) }
- assert_raise(ArgumentError) { Reline.add_dialog_proc(:error, 'hoge') }
- assert_raise(ArgumentError) { Reline.add_dialog_proc('error', proc {} ) }
-
- dummy = DummyCallbackObject.new
- Reline.add_dialog_proc(:dummy, dummy)
- d = Reline.dialog_proc(:dummy)
- assert_equal(dummy, d.dialog_proc)
- end
-
- def test_add_dialog_proc_with_context
- dummy_proc = proc {}
- array = Array.new
- Reline.add_dialog_proc(:test_proc, dummy_proc, array)
- d = Reline.dialog_proc(:test_proc)
- assert_equal(dummy_proc, d.dialog_proc)
- assert_equal(array, d.context)
-
- Reline.add_dialog_proc(:test_proc, dummy_proc, nil)
- d = Reline.dialog_proc(:test_proc)
- assert_equal(dummy_proc, d.dialog_proc)
- assert_equal(nil, d.context)
- end
-
- def test_readmultiline
- # readmultiline is module function
- assert_include(Reline.methods, :readmultiline)
- assert_include(Reline.private_instance_methods, :readmultiline)
- end
-
- def test_readline
- # readline is module function
- assert_include(Reline.methods, :readline)
- assert_include(Reline.private_instance_methods, :readline)
- end
-
- def test_read_io
- # TODO in Reline::Core
- end
-
- def test_dumb_terminal
- lib = File.expand_path("../../lib", __dir__)
- out = IO.popen([{"TERM"=>"dumb"}, Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", "p Reline.core.io_gate"], &:read)
- assert_match(/#<Reline::Dumb/, out.chomp)
- end
-
- def test_print_prompt_before_everything_else
- pend if win?
- lib = File.expand_path("../../lib", __dir__)
- code = "p Reline::IOGate.class; p Reline.readline 'prompt> '"
- out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io|
- io.write "abc\n"
- io.close_write
- io.read
- end
- assert_match(/\AReline::ANSI\nprompt> /, out)
- end
-
- def test_read_eof_returns_input
- pend if win?
- lib = File.expand_path("../../lib", __dir__)
- code = "p result: Reline.readline"
- out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io|
- io.write "a\C-a"
- io.close_write
- io.read
- end
- assert_include(out, { result: 'a' }.inspect)
- end
-
- def test_read_eof_returns_nil_if_empty
- pend if win?
- lib = File.expand_path("../../lib", __dir__)
- code = "p result: Reline.readline"
- out = IO.popen([Reline.test_rubybin, "-I#{lib}", "-rreline", "-e", code], "r+") do |io|
- io.write "a\C-h"
- io.close_write
- io.read
- end
- assert_include(out, { result: nil }.inspect)
- end
-
- def test_require_reline_should_not_trigger_winsize
- pend if win?
- lib = File.expand_path("../../lib", __dir__)
- code = <<~RUBY
- require "io/console"
- def STDIN.tty?; true; end
- def STDOUT.tty?; true; end
- def STDIN.winsize; raise; end
- require("reline") && p(Reline.core.io_gate)
- RUBY
- out = IO.popen([{}, Reline.test_rubybin, "-I#{lib}", "-e", code], &:read)
- assert_include(out.chomp, "Reline::ANSI")
- end
-
- def win?
- /mswin|mingw/.match?(RUBY_PLATFORM)
- end
-
- def test_tty_amibuous_width
- omit unless defined?(PTY)
- ruby_file = Tempfile.create('rubyfile')
- ruby_file.write(<<~RUBY)
- require 'reline'
- Thread.new { sleep 2; puts 'timeout'; exit }
- p [Reline.ambiguous_width, gets.chomp]
- RUBY
- ruby_file.close
- lib = File.expand_path('../../lib', __dir__)
- cmd = [{ 'TERM' => 'xterm' }, Reline.test_rubybin, '-I', lib, ruby_file.to_path]
-
- # Calculate ambiguous width from cursor position
- [1, 2].each do |ambiguous_width|
- PTY.spawn(*cmd) do |r, w, pid|
- loop { break if r.readpartial(1024).include?("\e[6n") }
- w.puts "hello\e[10;#{ambiguous_width + 1}Rworld"
- assert_include(r.gets, [ambiguous_width, 'helloworld'].inspect)
- ensure
- r.close
- w.close
- Process.waitpid pid
- end
- end
-
- # Ambiguous width = 1 when cursor pos timed out
- PTY.spawn(*cmd) do |r, w, pid|
- loop { break if r.readpartial(1024).include?("\e[6n") }
- w.puts "hello\e[10;2Sworld"
- assert_include(r.gets, [1, "hello\e[10;2Sworld"].inspect)
- ensure
- r.close
- w.close
- Process.waitpid pid
- end
- ensure
- File.delete(ruby_file.path) if ruby_file
- end
-
- def get_reline_encoding
- if encoding = Reline.core.encoding
- encoding
- elsif win?
- Encoding::UTF_8
- else
- Encoding::default_external
- end
- end
-end
diff --git a/test/reline/test_reline_key.rb b/test/reline/test_reline_key.rb
deleted file mode 100644
index b6260d57d6..0000000000
--- a/test/reline/test_reline_key.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require_relative 'helper'
-require "reline"
-
-class Reline::TestKey < Reline::TestCase
- def test_match_symbol
- assert(Reline::Key.new('a', :key1, false).match?(:key1))
- refute(Reline::Key.new('a', :key1, false).match?(:key2))
- refute(Reline::Key.new('a', :key1, false).match?(nil))
- end
-end
diff --git a/test/reline/test_string_processing.rb b/test/reline/test_string_processing.rb
deleted file mode 100644
index a105be9aba..0000000000
--- a/test/reline/test_string_processing.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require_relative 'helper'
-
-class Reline::LineEditor::StringProcessingTest < Reline::TestCase
- def setup
- Reline.send(:test_mode)
- @prompt = '> '
- @config = Reline::Config.new
- Reline::HISTORY.instance_variable_set(:@config, @config)
- @line_editor = Reline::LineEditor.new(@config)
- @line_editor.reset(@prompt)
- end
-
- def teardown
- Reline.test_reset
- end
-
- def test_calculate_width
- width = @line_editor.send(:calculate_width, 'Ruby string')
- assert_equal('Ruby string'.size, width)
- end
-
- def test_calculate_width_with_escape_sequence
- width = @line_editor.send(:calculate_width, "\1\e[31m\2RubyColor\1\e[34m\2 default string \1\e[m\2>", true)
- assert_equal('RubyColor default string >'.size, width)
- end
-
- def test_completion_proc_with_preposing_and_postposing
- buf = ['def hoge', ' puts :aaa', 'end']
-
- @line_editor.instance_variable_set(:@is_multiline, true)
- @line_editor.instance_variable_set(:@buffer_of_lines, buf)
- @line_editor.instance_variable_set(:@byte_pointer, 6)
- @line_editor.instance_variable_set(:@line_index, 1)
- completion_proc_called = false
- @line_editor.instance_variable_set(:@completion_proc, proc { |target, pre, post|
- assert_equal('puts', target)
- assert_equal("def hoge\n ", pre)
- assert_equal(" :aaa\nend", post)
- completion_proc_called = true
- })
-
- assert_equal(["def hoge\n ", 'puts', " :aaa\nend", nil], @line_editor.retrieve_completion_block)
- @line_editor.__send__(:call_completion_proc, "def hoge\n ", 'puts', " :aaa\nend", nil)
- assert(completion_proc_called)
- end
-end
diff --git a/test/reline/test_unicode.rb b/test/reline/test_unicode.rb
deleted file mode 100644
index 0778306c32..0000000000
--- a/test/reline/test_unicode.rb
+++ /dev/null
@@ -1,286 +0,0 @@
-require_relative 'helper'
-require "reline/unicode"
-
-class Reline::Unicode::Test < Reline::TestCase
- def setup
- Reline.send(:test_mode)
- end
-
- def teardown
- Reline.test_reset
- end
-
- def test_get_mbchar_width
- assert_equal Reline.ambiguous_width, Reline::Unicode.get_mbchar_width('é')
- end
-
- def test_ambiguous_width
- assert_equal 1, Reline::Unicode.calculate_width('√', true)
- end
-
- def test_csi_regexp
- csi_sequences = ["\e[m", "\e[1m", "\e[12;34m", "\e[12;34H"]
- assert_equal(csi_sequences, "text#{csi_sequences.join('text')}text".scan(Reline::Unicode::CSI_REGEXP))
- end
-
- def test_osc_regexp
- osc_sequences = ["\e]1\a", "\e]0;OSC\a", "\e]1\e\\", "\e]0;OSC\e\\"]
- separator = "text\atext"
- assert_equal(osc_sequences, "#{separator}#{osc_sequences.join(separator)}#{separator}".scan(Reline::Unicode::OSC_REGEXP))
- end
-
- def test_split_by_width
- # IRB uses this method.
- assert_equal [['abc', 'de'], 2], Reline::Unicode.split_by_width('abcde', 3)
- end
-
- def test_split_line_by_width
- assert_equal ['abc', 'de'], Reline::Unicode.split_line_by_width('abcde', 3)
- assert_equal ['abc', 'def', ''], Reline::Unicode.split_line_by_width('abcdef', 3)
- assert_equal ['ab', 'あd', 'ef'], Reline::Unicode.split_line_by_width('abあdef', 3)
- assert_equal ['ab[zero]c', 'def', ''], Reline::Unicode.split_line_by_width("ab\1[zero]\2cdef", 3)
- assert_equal ["\e[31mabc", "\e[31md\e[42mef", "\e[31m\e[42mg"], Reline::Unicode.split_line_by_width("\e[31mabcd\e[42mefg", 3)
- assert_equal ["ab\e]0;1\ac", "\e]0;1\ad"], Reline::Unicode.split_line_by_width("ab\e]0;1\acd", 3)
- end
-
- def test_split_line_by_width_csi_reset_sgr_optimization
- assert_equal ["\e[1ma\e[mb\e[2mc", "\e[2md\e[0me\e[3mf", "\e[3mg"], Reline::Unicode.split_line_by_width("\e[1ma\e[mb\e[2mcd\e[0me\e[3mfg", 3)
- assert_equal ["\e[1ma\e[mzero\e[0m\e[2mb", "\e[1m\e[2mc"], Reline::Unicode.split_line_by_width("\e[1ma\1\e[mzero\e[0m\2\e[2mbc", 2)
- end
-
- def test_take_range
- assert_equal 'cdef', Reline::Unicode.take_range('abcdefghi', 2, 4)
- assert_equal 'あde', Reline::Unicode.take_range('abあdef', 2, 4)
- assert_equal '[zero]cdef', Reline::Unicode.take_range("ab\1[zero]\2cdef", 2, 4)
- assert_equal 'b[zero]cde', Reline::Unicode.take_range("ab\1[zero]\2cdef", 1, 4)
- assert_equal "\e[31mcd\e[42mef", Reline::Unicode.take_range("\e[31mabcd\e[42mefg", 2, 4)
- assert_equal "\e]0;1\acd", Reline::Unicode.take_range("ab\e]0;1\acd", 2, 3)
- assert_equal 'いう', Reline::Unicode.take_range('あいうえお', 2, 4)
- end
-
- def test_nonprinting_start_end
- # \1 and \2 should be removed
- assert_equal 'ab[zero]cd', Reline::Unicode.take_range("ab\1[zero]\2cdef", 0, 4)
- assert_equal ['ab[zero]cd', 'ef'], Reline::Unicode.split_line_by_width("ab\1[zero]\2cdef", 4)
- # CSI between \1 and \2 does not need to be applied to the sebsequent line
- assert_equal ["\e[31mab\e[32mcd", "\e[31mef"], Reline::Unicode.split_line_by_width("\e[31mab\1\e[32m\2cdef", 4)
- end
-
- def test_strip_non_printing_start_end
- assert_equal "ab[zero]cd[ze\1ro]ef[zero]", Reline::Unicode.strip_non_printing_start_end("ab\1[zero]\2cd\1[ze\1ro]\2ef\1[zero]")
- end
-
- def test_calculate_width
- assert_equal 9, Reline::Unicode.calculate_width('abcdefghi')
- assert_equal 9, Reline::Unicode.calculate_width('abcdefghi', true)
- assert_equal 7, Reline::Unicode.calculate_width('abあdef')
- assert_equal 7, Reline::Unicode.calculate_width('abあdef', true)
- assert_equal 16, Reline::Unicode.calculate_width("ab\1[zero]\2cdef")
- assert_equal 6, Reline::Unicode.calculate_width("ab\1[zero]\2cdef", true)
- assert_equal 19, Reline::Unicode.calculate_width("\e[31mabcd\e[42mefg")
- assert_equal 7, Reline::Unicode.calculate_width("\e[31mabcd\e[42mefg", true)
- assert_equal 12, Reline::Unicode.calculate_width("ab\e]0;1\acd")
- assert_equal 4, Reline::Unicode.calculate_width("ab\e]0;1\acd", true)
- assert_equal 10, Reline::Unicode.calculate_width('あいうえお')
- assert_equal 10, Reline::Unicode.calculate_width('あいうえお', true)
- end
-
- def test_take_mbchar_range
- assert_equal ['cdef', 2, 4], Reline::Unicode.take_mbchar_range('abcdefghi', 2, 4)
- assert_equal ['cdef', 2, 4], Reline::Unicode.take_mbchar_range('abcdefghi', 2, 4, padding: true)
- assert_equal ['cdef', 2, 4], Reline::Unicode.take_mbchar_range('abcdefghi', 2, 4, cover_begin: true)
- assert_equal ['cdef', 2, 4], Reline::Unicode.take_mbchar_range('abcdefghi', 2, 4, cover_end: true)
- assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 2, 4)
- assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 2, 4, padding: true)
- assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 2, 4, cover_begin: true)
- assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 2, 4, cover_end: true)
- assert_equal ['う', 4, 2], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4)
- assert_equal [' う ', 3, 4], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, padding: true)
- assert_equal ['いう', 2, 4], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, cover_begin: true)
- assert_equal ['うえ', 4, 4], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, cover_end: true)
- assert_equal ['いう ', 2, 5], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, cover_begin: true, padding: true)
- assert_equal [' うえ', 3, 5], Reline::Unicode.take_mbchar_range('あいうえお', 3, 4, cover_end: true, padding: true)
- assert_equal [' うえお ', 3, 10], Reline::Unicode.take_mbchar_range('あいうえお', 3, 10, padding: true)
- assert_equal [" \e[41mうえお\e[0m ", 3, 10], Reline::Unicode.take_mbchar_range("あい\e[41mうえお", 3, 10, padding: true)
- assert_equal ["\e[41m \e[42mい\e[43m ", 1, 4], Reline::Unicode.take_mbchar_range("\e[41mあ\e[42mい\e[43mう", 1, 4, padding: true)
- assert_equal ["\e[31mc[ABC]d\e[0mef", 2, 4], Reline::Unicode.take_mbchar_range("\e[31mabc\1[ABC]\2d\e[0mefghi", 2, 4)
- assert_equal ["\e[41m \e[42mい\e[43m ", 1, 4], Reline::Unicode.take_mbchar_range("\e[41mあ\e[42mい\e[43mう", 1, 4, padding: true)
- end
-
- def test_common_prefix
- assert_equal('', Reline::Unicode.common_prefix([]))
- assert_equal('abc', Reline::Unicode.common_prefix(['abc']))
- assert_equal('12', Reline::Unicode.common_prefix(['123', '123️⃣']))
- assert_equal('', Reline::Unicode.common_prefix(['abc', 'xyz']))
- assert_equal('ab', Reline::Unicode.common_prefix(['abcd', 'abc', 'abx', 'abcd']))
- assert_equal('A', Reline::Unicode.common_prefix(['AbcD', 'ABC', 'AbX', 'AbCD']))
- assert_equal('Ab', Reline::Unicode.common_prefix(['AbcD', 'ABC', 'AbX', 'AbCD'], ignore_case: true))
- end
-
- def test_encoding_conversion
- texts = [
- String.new("invalid\xFFutf8", encoding: 'utf-8'),
- String.new("invalid\xFFsjis", encoding: 'sjis'),
- "utf8#{33111.chr('sjis')}convertible",
- "utf8#{33222.chr('sjis')}inconvertible",
- "sjis->utf8->sjis#{60777.chr('sjis')}irreversible"
- ]
- utf8_texts = [
- 'invalid�utf8',
- 'invalid�sjis',
- 'utf8仝convertible',
- 'utf8�inconvertible',
- 'sjis->utf8->sjis劦irreversible'
- ]
- sjis_texts = [
- 'invalid?utf8',
- 'invalid?sjis',
- "utf8#{33111.chr('sjis')}convertible",
- 'utf8?inconvertible',
- "sjis->utf8->sjis#{60777.chr('sjis')}irreversible"
- ]
- assert_equal(utf8_texts, texts.map { |s| Reline::Unicode.safe_encode(s, 'utf-8') })
- assert_equal(utf8_texts, texts.map { |s| Reline::Unicode.safe_encode(s, Encoding::UTF_8) })
- assert_equal(sjis_texts, texts.map { |s| Reline::Unicode.safe_encode(s, 'sjis') })
- assert_equal(sjis_texts, texts.map { |s| Reline::Unicode.safe_encode(s, Encoding::Windows_31J) })
- end
-
- def test_em_forward_word
- assert_equal(12, Reline::Unicode.em_forward_word('abc---fooあbar-baz', 3))
- assert_equal(11, Reline::Unicode.em_forward_word('abc---fooあbar-baz'.encode('sjis'), 3))
- assert_equal(3, Reline::Unicode.em_forward_word('abcfoo', 3))
- assert_equal(3, Reline::Unicode.em_forward_word('abc---', 3))
- assert_equal(0, Reline::Unicode.em_forward_word('abc', 3))
- end
-
- def test_em_forward_word_with_capitalization
- assert_equal([12, '---Fooあbar'], Reline::Unicode.em_forward_word_with_capitalization('abc---foOあBar-baz', 3))
- assert_equal([11, '---Fooあbar'.encode('sjis')], Reline::Unicode.em_forward_word_with_capitalization('abc---foOあBar-baz'.encode('sjis'), 3))
- assert_equal([3, 'Foo'], Reline::Unicode.em_forward_word_with_capitalization('abcfOo', 3))
- assert_equal([3, '---'], Reline::Unicode.em_forward_word_with_capitalization('abc---', 3))
- assert_equal([0, ''], Reline::Unicode.em_forward_word_with_capitalization('abc', 3))
- assert_equal([6, 'Ii̇i̇'], Reline::Unicode.em_forward_word_with_capitalization('ıİİ', 0))
- end
-
- def test_em_backward_word
- assert_equal(12, Reline::Unicode.em_backward_word('abc foo-barあbaz--- xyz', 20))
- assert_equal(11, Reline::Unicode.em_backward_word('abc foo-barあbaz--- xyz'.encode('sjis'), 19))
- assert_equal(2, Reline::Unicode.em_backward_word(' ', 2))
- assert_equal(2, Reline::Unicode.em_backward_word('ab', 2))
- assert_equal(0, Reline::Unicode.em_backward_word('ab', 0))
- end
-
- def test_em_big_backward_word
- assert_equal(16, Reline::Unicode.em_big_backward_word('abc foo-barあbaz--- xyz', 20))
- assert_equal(15, Reline::Unicode.em_big_backward_word('abc foo-barあbaz--- xyz'.encode('sjis'), 19))
- assert_equal(2, Reline::Unicode.em_big_backward_word(' ', 2))
- assert_equal(2, Reline::Unicode.em_big_backward_word('ab', 2))
- assert_equal(0, Reline::Unicode.em_big_backward_word('ab', 0))
- end
-
- def test_ed_transpose_words
- # any value that does not trigger transpose
- assert_equal([0, 0, 0, 2], Reline::Unicode.ed_transpose_words('aa bb cc ', 1))
-
- assert_equal([0, 2, 3, 5], Reline::Unicode.ed_transpose_words('aa bb cc ', 2))
- assert_equal([0, 2, 3, 5], Reline::Unicode.ed_transpose_words('aa bb cc ', 4))
- assert_equal([3, 5, 6, 8], Reline::Unicode.ed_transpose_words('aa bb cc ', 5))
- assert_equal([3, 5, 6, 8], Reline::Unicode.ed_transpose_words('aa bb cc ', 7))
- assert_equal([3, 5, 6, 10], Reline::Unicode.ed_transpose_words('aa bb cc ', 8))
- assert_equal([3, 5, 6, 10], Reline::Unicode.ed_transpose_words('aa bb cc ', 9))
- ['sjis', 'utf-8'].each do |encoding|
- texts = ['fooあ', 'barあbaz', 'aaa -', '- -', '- bbb']
- word1, word2, left, middle, right = texts.map { |text| text.encode(encoding) }
- expected = [left.bytesize, (left + word1).bytesize, (left + word1 + middle).bytesize, (left + word1 + middle + word2).bytesize]
- assert_equal(expected, Reline::Unicode.ed_transpose_words(left + word1 + middle + word2 + right, left.bytesize + word1.bytesize))
- assert_equal(expected, Reline::Unicode.ed_transpose_words(left + word1 + middle + word2 + right, left.bytesize + word1.bytesize + middle.bytesize))
- assert_equal(expected, Reline::Unicode.ed_transpose_words(left + word1 + middle + word2 + right, left.bytesize + word1.bytesize + middle.bytesize + word2.bytesize - 1))
- end
- end
-
- def test_vi_big_forward_word
- assert_equal(18, Reline::Unicode.vi_big_forward_word('abc---fooあbar-baz xyz', 3))
- assert_equal(8, Reline::Unicode.vi_big_forward_word('abcfooあ --', 3))
- assert_equal(7, Reline::Unicode.vi_big_forward_word('abcfooあ --'.encode('sjis'), 3))
- assert_equal(6, Reline::Unicode.vi_big_forward_word('abcfooあ', 3))
- assert_equal(3, Reline::Unicode.vi_big_forward_word('abc- ', 3))
- assert_equal(0, Reline::Unicode.vi_big_forward_word('abc', 3))
- end
-
- def test_vi_big_forward_end_word
- assert_equal(4, Reline::Unicode.vi_big_forward_end_word('a bb c', 0))
- assert_equal(4, Reline::Unicode.vi_big_forward_end_word('- bb c', 0))
- assert_equal(1, Reline::Unicode.vi_big_forward_end_word('-a b', 0))
- assert_equal(1, Reline::Unicode.vi_big_forward_end_word('a- b', 0))
- assert_equal(1, Reline::Unicode.vi_big_forward_end_word('aa b', 0))
- assert_equal(3, Reline::Unicode.vi_big_forward_end_word(' aa b', 0))
- assert_equal(15, Reline::Unicode.vi_big_forward_end_word('abc---fooあbar-baz xyz', 3))
- assert_equal(14, Reline::Unicode.vi_big_forward_end_word('abc---fooあbar-baz xyz'.encode('sjis'), 3))
- assert_equal(3, Reline::Unicode.vi_big_forward_end_word('abcfooあ --', 3))
- assert_equal(3, Reline::Unicode.vi_big_forward_end_word('abcfooあ', 3))
- assert_equal(2, Reline::Unicode.vi_big_forward_end_word('abc- ', 3))
- assert_equal(0, Reline::Unicode.vi_big_forward_end_word('abc', 3))
- end
-
- def test_vi_big_backward_word
- assert_equal(16, Reline::Unicode.vi_big_backward_word('abc foo-barあbaz--- xyz', 20))
- assert_equal(15, Reline::Unicode.vi_big_backward_word('abc foo-barあbaz--- xyz'.encode('sjis'), 19))
- assert_equal(2, Reline::Unicode.vi_big_backward_word(' ', 2))
- assert_equal(2, Reline::Unicode.vi_big_backward_word('ab', 2))
- assert_equal(0, Reline::Unicode.vi_big_backward_word('ab', 0))
- end
-
- def test_vi_forward_word
- assert_equal(3, Reline::Unicode.vi_forward_word('abc---fooあbar-baz', 3))
- assert_equal(9, Reline::Unicode.vi_forward_word('abc---fooあbar-baz', 6))
- assert_equal(8, Reline::Unicode.vi_forward_word('abc---fooあbar-baz'.encode('sjis'), 6))
- assert_equal(6, Reline::Unicode.vi_forward_word('abcfooあ', 3))
- assert_equal(3, Reline::Unicode.vi_forward_word('abc---', 3))
- assert_equal(0, Reline::Unicode.vi_forward_word('abc', 3))
- assert_equal(2, Reline::Unicode.vi_forward_word('abc def', 1, true))
- assert_equal(5, Reline::Unicode.vi_forward_word('abc def', 1, false))
- end
-
- def test_vi_forward_end_word
- assert_equal(2, Reline::Unicode.vi_forward_end_word('abc---fooあbar-baz', 3))
- assert_equal(8, Reline::Unicode.vi_forward_end_word('abc---fooあbar-baz', 6))
- assert_equal(7, Reline::Unicode.vi_forward_end_word('abc---fooあbar-baz'.encode('sjis'), 6))
- assert_equal(3, Reline::Unicode.vi_forward_end_word('abcfooあ', 3))
- assert_equal(2, Reline::Unicode.vi_forward_end_word('abc---', 3))
- assert_equal(0, Reline::Unicode.vi_forward_end_word('abc', 3))
- end
-
- def test_vi_backward_word
- assert_equal(3, Reline::Unicode.vi_backward_word('abc foo-barあbaz--- xyz', 20))
- assert_equal(9, Reline::Unicode.vi_backward_word('abc foo-barあbaz--- xyz', 17))
- assert_equal(8, Reline::Unicode.vi_backward_word('abc foo-barあbaz--- xyz'.encode('sjis'), 16))
- assert_equal(2, Reline::Unicode.vi_backward_word(' ', 2))
- assert_equal(2, Reline::Unicode.vi_backward_word('ab', 2))
- assert_equal(0, Reline::Unicode.vi_backward_word('ab', 0))
- end
-
- def test_vi_first_print
- assert_equal(3, Reline::Unicode.vi_first_print(' abcdefg'))
- assert_equal(3, Reline::Unicode.vi_first_print(' '))
- assert_equal(0, Reline::Unicode.vi_first_print('abc'))
- assert_equal(0, Reline::Unicode.vi_first_print('あ'))
- assert_equal(0, Reline::Unicode.vi_first_print('あ'.encode('sjis')))
- assert_equal(0, Reline::Unicode.vi_first_print(''))
- end
-
- def test_character_type
- assert(Reline::Unicode.word_character?('a'))
- assert(Reline::Unicode.word_character?('あ'))
- assert(Reline::Unicode.word_character?('あ'.encode('sjis')))
- refute(Reline::Unicode.word_character?(33345.chr('sjis')))
- refute(Reline::Unicode.word_character?('-'))
- refute(Reline::Unicode.word_character?(nil))
-
- assert(Reline::Unicode.space_character?(' '))
- refute(Reline::Unicode.space_character?('あ'))
- refute(Reline::Unicode.space_character?('あ'.encode('sjis')))
- refute(Reline::Unicode.space_character?(33345.chr('sjis')))
- refute(Reline::Unicode.space_character?('-'))
- refute(Reline::Unicode.space_character?(nil))
- end
-end
diff --git a/test/reline/test_within_pipe.rb b/test/reline/test_within_pipe.rb
deleted file mode 100644
index e5d0c12b9c..0000000000
--- a/test/reline/test_within_pipe.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require_relative 'helper'
-
-class Reline::WithinPipeTest < Reline::TestCase
- def setup
- Reline.send(:test_mode)
- @encoding = Reline.core.encoding
- @input_reader, @writer = IO.pipe(@encoding)
- Reline.input = @input_reader
- @reader, @output_writer = IO.pipe(@encoding)
- @output = Reline.output = @output_writer
- @config = Reline.core.config
- @config.keyseq_timeout *= 600 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # for --jit-wait CI
- @line_editor = Reline.core.line_editor
- end
-
- def teardown
- Reline.input = STDIN
- Reline.output = STDOUT
- Reline.point = 0
- Reline.delete_text
- @input_reader.close
- @writer.close
- @reader.close
- @output_writer.close
- @config.reset
- Reline.test_reset
- end
-
- def test_simple_input
- @writer.write("abc\n")
- assert_equal 'abc', Reline.readmultiline(&proc{ true })
- end
-
- def test_unknown_macro
- @config.add_default_key_binding('abc'.bytes, :unknown_macro)
- @writer.write("abcd\n")
- assert_equal 'd', Reline.readmultiline(&proc{ true })
- end
-
- def test_macro_commands_for_moving
- @config.add_default_key_binding("\C-x\C-a".bytes, :beginning_of_line)
- @config.add_default_key_binding("\C-x\C-e".bytes, :end_of_line)
- @config.add_default_key_binding("\C-x\C-f".bytes, :forward_char)
- @config.add_default_key_binding("\C-x\C-b".bytes, :backward_char)
- @config.add_default_key_binding("\C-x\ef".bytes, :forward_word)
- @config.add_default_key_binding("\C-x\eb".bytes, :backward_word)
- @writer.write(" def\C-x\C-aabc\C-x\C-e ghi\C-x\C-a\C-x\C-f\C-x\C-f_\C-x\C-b\C-x\C-b_\C-x\C-f\C-x\C-f\C-x\C-f\C-x\ef_\C-x\eb\n")
- assert_equal 'a_b_c def_ ghi', Reline.readmultiline(&proc{ true })
- end
-
- def test_macro_commands_for_editing
- @config.add_default_key_binding("\C-x\C-d".bytes, :delete_char)
- @config.add_default_key_binding("\C-x\C-h".bytes, :backward_delete_char)
- @config.add_default_key_binding("\C-x\C-v".bytes, :quoted_insert)
- #@config.add_default_key_binding("\C-xa".bytes, :self_insert)
- @config.add_default_key_binding("\C-x\C-t".bytes, :transpose_chars)
- @config.add_default_key_binding("\C-x\et".bytes, :transpose_words)
- @config.add_default_key_binding("\C-x\eu".bytes, :upcase_word)
- @config.add_default_key_binding("\C-x\el".bytes, :downcase_word)
- @config.add_default_key_binding("\C-x\ec".bytes, :capitalize_word)
- @writer.write("abcde\C-b\C-b\C-b\C-x\C-d\C-x\C-h\C-x\C-v\C-a\C-f\C-f EF\C-x\C-t gh\C-x\et\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-x\eu\C-x\el\C-x\ec\n")
- assert_equal "a\C-aDE gh Fe", Reline.readmultiline(&proc{ true })
- end
-
- def test_delete_text_in_multiline
- @writer.write("abc\ndef\nxyz\n")
- result = Reline.readmultiline(&proc{ |str|
- if str.include?('xyz')
- Reline.delete_text
- true
- else
- false
- end
- })
- assert_equal "abc\ndef", result
- end
-end
diff --git a/test/reline/windows/test_key_event_record.rb b/test/reline/windows/test_key_event_record.rb
deleted file mode 100644
index 25c860606a..0000000000
--- a/test/reline/windows/test_key_event_record.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require_relative '../helper'
-return unless Reline.const_defined?(:Windows)
-
-class Reline::Windows
- class KeyEventRecord::Test < Reline::TestCase
-
- def setup
- # Ctrl+A
- @key = Reline::Windows::KeyEventRecord.new(0x41, 1, Reline::Windows::LEFT_CTRL_PRESSED)
- end
-
- def test_matches__with_no_arguments_raises_error
- assert_raise(ArgumentError) { @key.match? }
- end
-
- def test_matches_char_code
- assert @key.match?(char_code: 0x1)
- end
-
- def test_matches_virtual_key_code
- assert @key.match?(virtual_key_code: 0x41)
- end
-
- def test_matches_control_keys
- assert @key.match?(control_keys: :CTRL)
- end
-
- def test_doesnt_match_alt
- refute @key.match?(control_keys: :ALT)
- end
-
- def test_doesnt_match_empty_control_key
- refute @key.match?(control_keys: [])
- end
-
- def test_matches_control_keys_and_virtual_key_code
- assert @key.match?(control_keys: :CTRL, virtual_key_code: 0x41)
- end
-
- end
-end
diff --git a/test/reline/yamatanooroti/multiline_repl b/test/reline/yamatanooroti/multiline_repl
deleted file mode 100755
index 8b82be60f4..0000000000
--- a/test/reline/yamatanooroti/multiline_repl
+++ /dev/null
@@ -1,257 +0,0 @@
-#!/usr/bin/env ruby
-
-
-require 'bundler'
-Bundler.require
-
-require 'reline'
-require 'optparse'
-require_relative 'termination_checker'
-
-opt = OptionParser.new
-opt.on('--dynamic-prompt') {
- Reline.prompt_proc = proc { |lines|
- lines.each_with_index.map { |l, i|
- "\e[1m[%04d]>\e[m " % i
- }
- }
-}
-opt.on('--broken-dynamic-prompt') {
- Reline.prompt_proc = proc { |lines|
- range = lines.size > 1 ? (0..(lines.size - 2)) : (0..0)
- lines[range].each_with_index.map { |l, i|
- '[%04d]> ' % i
- }
- }
-}
-opt.on('--dynamic-prompt-returns-empty') {
- Reline.prompt_proc = proc { |l| [] }
-}
-opt.on('--dynamic-prompt-with-newline') {
- Reline.prompt_proc = proc { |lines|
- range = lines.size > 1 ? (0..(lines.size - 2)) : (0..0)
- lines[range].each_with_index.map { |l, i|
- '[%04d\n]> ' % i
- }
- }
-}
-opt.on('--broken-dynamic-prompt-assert-no-escape-sequence') {
- Reline.prompt_proc = proc { |lines|
- has_escape_sequence = lines.join.include?("\e")
- (lines.size + 1).times.map { |i|
- has_escape_sequence ? 'error>' : '[%04d]> ' % i
- }
- }
-}
-opt.on('--color-bold') {
- Reline.output_modifier_proc = ->(output, complete:){
- output.gsub(/./) { |c| "\e[1m#{c}\e[0m" }
- }
-}
-opt.on('--dynamic-prompt-show-line') {
- Reline.prompt_proc = proc { |lines|
- lines.map { |l|
- '[%4.4s]> ' % l
- }
- }
-}
-
-def assert_auto_indent_params(lines, line_index, byte_pointer, is_newline)
- raise 'Wrong lines type' unless lines.all?(String)
-
- line = lines[line_index]
- raise 'Wrong line_index value' unless line
-
- # The condition `byte_pointer <= line.bytesize` is not satisfied. Maybe bug.
- # Instead, loose constraint `byte_pointer <= line.bytesize + 1` seems to be satisfied when is_newline is false.
- return if is_newline
-
- raise 'byte_pointer out of bounds' unless byte_pointer <= line.bytesize + 1
- raise 'Invalid byte_pointer' unless line.byteslice(0, byte_pointer).valid_encoding?
-end
-
-opt.on('--auto-indent') {
- Reline.auto_indent_proc = lambda do |lines, line_index, byte_pointer, is_newline|
- assert_auto_indent_params(lines, line_index, byte_pointer, is_newline)
- AutoIndent.calculate_indent(lines, line_index, byte_pointer, is_newline)
- end
-}
-opt.on('--dialog VAL') { |v|
- Reline.add_dialog_proc(:simple_dialog, lambda {
- return nil if v.include?('nil')
- if v.include?('simple')
- contents = <<~RUBY.split("\n")
- Ruby is...
- A dynamic, open source programming
- language with a focus on simplicity
- and productivity. It has an elegant
- syntax that is natural to read and
- easy to write.
- RUBY
- elsif v.include?('long')
- contents = <<~RUBY.split("\n")
- Ruby is...
- A dynamic, open
- source programming
- language with a
- focus on simplicity
- and productivity.
- It has an elegant
- syntax that is
- natural to read
- and easy to write.
- RUBY
- elsif v.include?('fullwidth')
- contents = <<~RUBY.split("\n")
- Rubyとは...
-
- オープンソースの動的なプログラミン
- グ言語で、シンプルさと高い生産性を
- 備えています。エレガントな文法を持
- ち、自然に読み書きができます。
- RUBY
- end
- if v.include?('scrollkey')
- dialog.trap_key = nil
- if key and key.match?(dialog.name)
- if dialog.pointer.nil?
- dialog.pointer = 0
- elsif dialog.pointer >= (contents.size - 1)
- dialog.pointer = 0
- else
- dialog.pointer += 1
- end
- end
- dialog.trap_key = [?j.ord]
- height = 4
- end
- scrollbar = false
- if v.include?('scrollbar')
- scrollbar = true
- end
- if v.include?('alt-scrollbar')
- scrollbar = true
- end
- Reline::DialogRenderInfo.new(pos: cursor_pos, contents: contents, height: height, scrollbar: scrollbar, face: :completion_dialog)
- })
- if v.include?('alt-scrollbar')
- ENV['RELINE_ALT_SCROLLBAR'] = '1'
- end
-}
-opt.on('--complete') {
- Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
- %w{String ScriptError SyntaxError Signal}.select{ |c| c.start_with?(target) }
- }
-}
-opt.on('--complete-menu-with-perfect-match') {
- Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
- %w{abs abs2}.select{ |c| c.start_with?(target) }
- }
-}
-opt.on('--autocomplete') {
- Reline.autocompletion = true
- Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
- %w{String Struct Symbol ScriptError SyntaxError Signal}.select{ |c| c.start_with?(target) }
- }
-}
-opt.on('--autocomplete-empty') {
- Reline.autocompletion = true
- Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil| [] }
-}
-opt.on('--autocomplete-long') {
- Reline.autocompletion = true
- Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
- %w{
- String
- Struct
- Symbol
- StopIteration
- SystemCallError
- SystemExit
- SystemStackError
- ScriptError
- SyntaxError
- Signal
- SizedQueue
- Set
- SecureRandom
- Socket
- StringIO
- StringScanner
- Shellwords
- Syslog
- Singleton
- SDBM
- }.select{ |c| c.start_with?(target) }
- }
-}
-opt.on('--autocomplete-super-long') {
- Reline.autocompletion = true
- Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
- c = +'A'
- 2000.times.map{ s = "Str_#{c}"; c.succ!; s }.select{ |c| c.start_with?(target) }
- }
-}
-
-opt.on('--autocomplete-width-long') {
- Reline.autocompletion = true
- Reline.completion_proc = lambda { |target, preposing = nil, postposing = nil|
- %w{
- remove_instance_variable
- respond_to?
- ruby2_keywords
- rand
- readline
- readlines
- require
- require_relative
- raise
- respond_to_missing?
- redo
- rescue
- retry
- return
- }.select{ |c| c.start_with?(target) }
- }
-}
-opt.parse!(ARGV)
-
-begin
- stty_save = `stty -g`.chomp
-rescue
-end
-
-begin
- prompt = ENV['RELINE_TEST_PROMPT'] || "\e[1mprompt>\e[m "
- puts 'Multiline REPL.'
- while code = Reline.readmultiline(prompt, true) { |code| TerminationChecker.terminated?(code) }
- case code.chomp
- when 'exit', 'quit', 'q'
- exit 0
- when ''
- # NOOP
- else
- begin
- result = eval(code)
- puts "=> #{result.inspect}"
- rescue ScriptError, StandardError => e
- puts "Traceback (most recent call last):"
- e.backtrace.reverse_each do |f|
- puts " #{f}"
- end
- puts e.message
- end
- end
- end
-rescue Interrupt
- puts '^C'
- `stty #{stty_save}` if stty_save
- exit 0
-ensure
- `stty #{stty_save}` if stty_save
-end
-begin
- puts
-rescue Errno::EIO
- # Maybe the I/O has been closed.
-end
diff --git a/test/reline/yamatanooroti/termination_checker.rb b/test/reline/yamatanooroti/termination_checker.rb
deleted file mode 100644
index b97c798c59..0000000000
--- a/test/reline/yamatanooroti/termination_checker.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'ripper'
-
-module TerminationChecker
- def self.terminated?(code)
- Ripper.sexp(code) ? true : false
- end
-end
-
-module AutoIndent
- def self.calculate_indent(lines, line_index, byte_pointer, is_newline)
- if is_newline
- 2 * nesting_level(lines[0..line_index - 1])
- else
- lines = lines.dup
- lines[line_index] = lines[line_index]&.byteslice(0, byte_pointer)
- prev_level = nesting_level(lines[0..line_index - 1])
- level = nesting_level(lines[0..line_index])
- 2 * level if level < prev_level
- end
- end
-
- def self.nesting_level(lines)
- code = lines.join("\n")
- code.scan(/if|def|\(|\[|\{/).size - code.scan(/end|\)|\]|\}/).size
- end
-end
diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb
deleted file mode 100644
index aff5c0462b..0000000000
--- a/test/reline/yamatanooroti/test_rendering.rb
+++ /dev/null
@@ -1,1915 +0,0 @@
-require 'reline'
-
-begin
- require 'yamatanooroti'
-
- class Reline::RenderingTest < Yamatanooroti::TestCase
-
- FACE_CONFIGS = { no_config: "", valid_config: <<~VALID_CONFIG, incomplete_config: <<~INCOMPLETE_CONFIG }
- require "reline"
- Reline::Face.config(:completion_dialog) do |face|
- face.define :default, foreground: :white, background: :blue
- face.define :enhanced, foreground: :white, background: :magenta
- face.define :scrollbar, foreground: :white, background: :blue
- end
- VALID_CONFIG
- require "reline"
- Reline::Face.config(:completion_dialog) do |face|
- face.define :default, foreground: :white, background: :black
- face.define :scrollbar, foreground: :white, background: :cyan
- end
- INCOMPLETE_CONFIG
-
- def iterate_over_face_configs(&block)
- FACE_CONFIGS.each do |config_name, face_config|
- config_file = Tempfile.create(%w{face_config- .rb})
- config_file.write face_config
- block.call(config_name, config_file)
- ensure
- config_file.close
- File.delete(config_file)
- end
- end
-
- def setup
- @pwd = Dir.pwd
- suffix = '%010d' % Random.rand(0..65535)
- @tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_reline_config_#{$$}_#{suffix}")
- begin
- Dir.mkdir(@tmpdir)
- rescue Errno::EEXIST
- FileUtils.rm_rf(@tmpdir)
- Dir.mkdir(@tmpdir)
- end
- @inputrc_backup = ENV['INPUTRC']
- @inputrc_file = ENV['INPUTRC'] = File.join(@tmpdir, 'temporaty_inputrc')
- File.unlink(@inputrc_file) if File.exist?(@inputrc_file)
- end
-
- def teardown
- FileUtils.rm_rf(@tmpdir)
- ENV['INPUTRC'] = @inputrc_backup
- ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT']
- end
-
- def test_history_back
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(":a\n")
- write("\C-p")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> :a
- => :a
- prompt> :a
- EOC
- close
- end
-
- def test_backspace
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(":abc\C-h\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> :ab
- => :ab
- prompt>
- EOC
- close
- end
-
- def test_autowrap
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write('01234567890123456789012')
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> 0123456789012345678901
- 2
- EOC
- close
- end
-
- def test_fullwidth
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(":あ\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> :あ
- => :あ
- prompt>
- EOC
- close
- end
-
- def test_two_fullwidth
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(":あい\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> :あい
- => :あい
- prompt>
- EOC
- close
- end
-
- def test_finish_autowrapped_line
- start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("[{'user'=>{'email'=>'a@a', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}]\n")
- expected = [{'user'=>{'email'=>'a@a', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}].inspect
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> [{'user'=>{'email'=>'a@a', 'id'=
- >'ABC'}, 'version'=>4, 'status'=>'succee
- ded'}]
- #{fold_multiline("=> " + expected, 40)}
- prompt>
- EOC
- close
- end
-
- def test_finish_autowrapped_line_in_the_middle_of_lines
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("[{'user'=>{'email'=>'abcdef@abcdef', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}]#{"\C-b"*7}")
- write("\n")
- expected = [{'user'=>{'email'=>'abcdef@abcdef', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}].inspect
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> [{'user'=>{'email'=>'a
- bcdef@abcdef', 'id'=>'ABC'}, '
- version'=>4, 'status'=>'succee
- ded'}]
- #{fold_multiline("=> " + expected, 30)}
- prompt>
- EOC
- close
- end
-
- def test_finish_autowrapped_line_in_the_middle_of_multilines
- omit if RUBY_VERSION < '2.7'
- start_terminal(30, 16, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("<<~EOM\n ABCDEFG\nEOM\n")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> <<~EOM
- prompt> ABCDEF
- G
- prompt> EOM
- => "ABCDEFG\n"
- prompt>
- EOC
- close
- end
-
- def test_prompt
- write_inputrc <<~'LINES'
- "abc": "123"
- LINES
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("abc\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> 123
- => 123
- prompt>
- EOC
- close
- end
-
- def test_mode_string_emacs
- write_inputrc <<~LINES
- set show-mode-in-prompt on
- LINES
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- assert_screen(<<~EOC)
- Multiline REPL.
- @prompt>
- EOC
- close
- end
-
- def test_mode_string_vi
- write_inputrc <<~LINES
- set editing-mode vi
- set show-mode-in-prompt on
- LINES
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(":a\n\C-[k")
- write("i\n:a")
- write("\C-[h")
- assert_screen(<<~EOC)
- (ins)prompt> :a
- => :a
- (ins)prompt> :a
- => :a
- (cmd)prompt> :a
- EOC
- close
- end
-
- def test_original_mode_string_emacs
- write_inputrc <<~LINES
- set show-mode-in-prompt on
- set emacs-mode-string [emacs]
- LINES
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- assert_screen(<<~EOC)
- Multiline REPL.
- [emacs]prompt>
- EOC
- close
- end
-
- def test_original_mode_string_with_quote
- write_inputrc <<~LINES
- set show-mode-in-prompt on
- set emacs-mode-string "[emacs]"
- LINES
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- assert_screen(<<~EOC)
- Multiline REPL.
- [emacs]prompt>
- EOC
- close
- end
-
- def test_original_mode_string_vi
- write_inputrc <<~LINES
- set editing-mode vi
- set show-mode-in-prompt on
- set vi-ins-mode-string "{InS}"
- set vi-cmd-mode-string "{CmD}"
- LINES
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(":a\n\C-[k")
- assert_screen(<<~EOC)
- Multiline REPL.
- {InS}prompt> :a
- => :a
- {CmD}prompt> :a
- EOC
- close
- end
-
- def test_mode_string_vi_changing
- write_inputrc <<~LINES
- set editing-mode vi
- set show-mode-in-prompt on
- LINES
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(":a\C-[ab\C-[ac\C-h\C-h\C-h\C-h:a")
- assert_screen(<<~EOC)
- Multiline REPL.
- (ins)prompt> :a
- EOC
- close
- end
-
- def test_esc_input
- omit if Reline::IOGate.win?
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def\C-aabc")
- write("\e") # single ESC
- sleep 1
- write("A")
- write("B\eAC") # ESC + A (M-A, no key specified in Reline::KeyActor::Emacs)
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> abcABCdef
- EOC
- close
- end
-
- def test_prompt_with_escape_sequence
- ENV['RELINE_TEST_PROMPT'] = "\1\e[30m\2prompt> \1\e[m\2"
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("123\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> 123
- => 123
- prompt>
- EOC
- close
- end
-
- def test_prompt_with_escape_sequence_and_autowrap
- ENV['RELINE_TEST_PROMPT'] = "\1\e[30m\2prompt> \1\e[m\2"
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("1234567890123\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> 123456789012
- 3
- => 1234567890123
- prompt>
- EOC
- close
- end
-
- def test_readline_with_multiline_input
- start_terminal(5, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt}, startup_message: 'Multiline REPL.')
- write("def foo\n bar\nend\n")
- write("Reline.readline('prompt> ')\n")
- write("\C-p\C-p")
- assert_screen(<<~EOC)
- => :foo
- [0000]> Reline.readline('prompt> ')
- prompt> def foo
- bar
- end
- EOC
- close
- end
-
- def test_multiline_and_autowrap
- start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def aaaaaaaaaa\n 33333333\n end\C-a\C-pputs\C-e\e\C-m888888888888888")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def aaaaaaaa
- aa
- prompt> puts 333333
- 33
- prompt> 888888888888
- 888
- prompt> e
- nd
- EOC
- close
- end
-
- def test_multiline_add_new_line_and_autowrap
- start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def aaaaaaaaaa")
- write("\n")
- write(" bbbbbbbbbbbb")
- write("\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def aaaaaaaa
- aa
- prompt> bbbbbbbbbb
- bb
- prompt>
- EOC
- close
- end
-
- def test_clear
- start_terminal(10, 15, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("3\C-l")
- assert_screen(<<~EOC)
- prompt> 3
- EOC
- close
- end
-
- def test_clear_multiline_and_autowrap
- start_terminal(10, 15, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def aaaaaa\n 3\n\C-lend")
- assert_screen(<<~EOC)
- prompt> def aaa
- aaa
- prompt> 3
- prompt> end
- EOC
- close
- end
-
- def test_nearest_cursor
- start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def ああ\n :いい\nend\C-pbb\C-pcc")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def ccああ
- prompt> :bbいい
- prompt> end
- EOC
- close
- end
-
- def test_delete_line
- start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def a\n\nend\C-p\C-h")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def a
- prompt> end
- EOC
- close
- end
-
- def test_last_line_of_screen
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("\n\n\n\n\ndef a\nend")
- assert_screen(<<~EOC)
- prompt>
- prompt>
- prompt>
- prompt> def a
- prompt> end
- EOC
- close
- end
-
- # c17a09b7454352e2aff5a7d8722e80afb73e454b
- def test_autowrap_at_last_line_of_screen
- start_terminal(5, 15, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def a\nend\n\C-p")
- assert_screen(<<~EOC)
- prompt> def a
- prompt> end
- => :a
- prompt> def a
- prompt> end
- EOC
- close
- end
-
- # f002483b27cdb325c5edf9e0fe4fa4e1c71c4b0e
- def test_insert_line_in_the_middle_of_line
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("333\C-b\C-b\e\C-m8")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> 3
- prompt> 833
- EOC
- close
- end
-
- # 9d8978961c5de5064f949d56d7e0286df9e18f43
- def test_insert_line_in_the_middle_of_line_at_last_line_of_screen
- start_terminal(3, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("333333333333333\C-a\C-f\e\C-m")
- assert_screen(<<~EOC)
- prompt> 3
- prompt> 333333333333
- 33
- EOC
- close
- end
-
- def test_insert_after_clear
- start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def a\n 01234\nend\C-l\C-p5678")
- assert_screen(<<~EOC)
- prompt> def a
- prompt> 056781234
- prompt> end
- EOC
- close
- end
-
- def test_foced_newline_insertion
- start_terminal(10, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- #write("def a\nend\C-p\C-e\e\C-m 3")
- write("def a\nend\C-p\C-e\e\x0D")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def a
- prompt>
- prompt> end
- EOC
- close
- end
-
- def test_multiline_incremental_search
- start_terminal(6, 25, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def a\n 8\nend\ndef b\n 3\nend\C-s8")
- assert_screen(<<~EOC)
- prompt> 8
- prompt> end
- => :a
- (i-search)`8'def a
- (i-search)`8' 8
- (i-search)`8'end
- EOC
- close
- end
-
- def test_multiline_incremental_search_finish
- start_terminal(6, 25, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def a\n 8\nend\ndef b\n 3\nend\C-r8\C-j")
- assert_screen(<<~EOC)
- prompt> 8
- prompt> end
- => :a
- prompt> def a
- prompt> 8
- prompt> end
- EOC
- close
- end
-
- def test_binding_for_vi_movement_mode
- write_inputrc <<~LINES
- set editing-mode vi
- "\\C-a": vi-movement-mode
- LINES
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(":1234\C-ahhhi0")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> :01234
- EOC
- close
- end
-
- def test_broken_prompt_list
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --broken-dynamic-prompt}, startup_message: 'Multiline REPL.')
- write("def hoge\n 3\nend")
- assert_screen(<<~EOC)
- Multiline REPL.
- [0000]> def hoge
- [0001]> 3
- [0001]> end
- EOC
- close
- end
-
- def test_no_escape_sequence_passed_to_dynamic_prompt
- start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete --color-bold --broken-dynamic-prompt-assert-no-escape-sequence}, startup_message: 'Multiline REPL.')
- write("%[ S")
- write("\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- [0000]> %[ S
- [0001]>
- EOC
- close
- end
-
- def test_bracketed_paste
- omit if Reline.core.io_gate.win?
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("\e[200~def hoge\r\t3\rend\e[201~")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def hoge
- prompt> 3
- prompt> end
- EOC
- write("\e[200~.tap do\r\t4\r\t5\rend\e[201~")
- assert_screen(<<~EOC)
- prompt> 3
- prompt> end.tap do
- prompt> 4
- prompt> 5
- prompt> end
- EOC
- close
- end
-
- def test_bracketed_paste_with_undo_redo
- omit if Reline.core.io_gate.win?
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("abc")
- write("\e[200~def hoge\r\t3\rend\e[201~")
- write("\C-_")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> abc
- EOC
- write("\e\C-_")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> abcdef hoge
- prompt> 3
- prompt> end
- EOC
- close
- end
-
- def test_backspace_until_returns_to_initial
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("ABC")
- write("\C-h\C-h\C-h")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt>
- EOC
- close
- end
-
- def test_longer_than_screen_height
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(<<~EOC.chomp)
- def each_top_level_statement
- initialize_input
- catch(:TERM_INPUT) do
- loop do
- begin
- prompt
- unless l = lex
- throw :TERM_INPUT if @line == ''
- else
- @line_no += l.count("\n")
- next if l == "\n"
- @line.concat l
- if @code_block_open or @ltype or @continue or @indent > 0
- next
- end
- end
- if @line != "\n"
- @line.force_encoding(@io.encoding)
- yield @line, @exp_line_no
- end
- break if @io.eof?
- @line = ''
- @exp_line_no = @line_no
- #
- @indent = 0
- rescue TerminateLineInput
- initialize_input
- prompt
- end
- end
- end
- end
- EOC
- assert_screen(<<~EOC)
- prompt> prompt
- prompt> end
- prompt> end
- prompt> end
- prompt> end
- EOC
- write("\C-p" * 6)
- assert_screen(<<~EOC)
- prompt> rescue Terminate
- LineInput
- prompt> initialize_inp
- ut
- prompt> prompt
- EOC
- write("\C-n" * 4)
- assert_screen(<<~EOC)
- prompt> initialize_inp
- ut
- prompt> prompt
- prompt> end
- prompt> end
- EOC
- close
- end
-
- def test_longer_than_screen_height_nearest_cursor_with_scroll_back
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write(<<~EOC.chomp)
- if 1
- if 2
- if 3
- if 4
- puts
- end
- end
- end
- end
- EOC
- write("\C-p" * 4 + "\C-e" + "\C-p" * 4)
- write("2")
- assert_screen(<<~EOC)
- prompt> if 12
- prompt> if 2
- prompt> if 3
- prompt> if 4
- prompt> puts
- EOC
- close
- end
-
- def test_update_cursor_correctly_when_just_cursor_moving
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def hoge\n 01234678")
- write("\C-p")
- write("\C-b")
- write("\C-n")
- write('5')
- write("\C-e")
- write('9')
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def hoge
- prompt> 0123456789
- EOC
- close
- end
-
- def test_auto_indent
- start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- "def hoge\nputs(\n1,\n2\n)\nend".lines do |line|
- write line
- end
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def hoge
- prompt> puts(
- prompt> 1,
- prompt> 2
- prompt> )
- prompt> end
- EOC
- close
- end
-
- def test_auto_indent_when_inserting_line
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- write 'aa(bb(cc(dd(ee('
- write "\C-b" * 5 + "\n"
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> aa(bb(cc(d
- prompt> d(ee(
- EOC
- close
- end
-
- def test_auto_indent_multibyte_insert_line
- start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- write "if true\n"
- write "あいうえお\n"
- 4.times { write "\C-b\C-b\C-b\C-b\e\r" }
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> if true
- prompt> あ
- prompt> い
- prompt> う
- prompt> え
- prompt> お
- prompt>
- EOC
- close
- end
-
- def test_newline_after_wrong_indent
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- write "if 1\n aa"
- write "\n"
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> if 1
- prompt> aa
- prompt>
- EOC
- close
- end
-
- def test_suppress_auto_indent_just_after_pasted
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- write("def hoge\n [[\n 3]]\ned")
- write("\C-bn")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def hoge
- prompt> [[
- prompt> 3]]
- prompt> end
- EOC
- close
- end
-
- def test_suppress_auto_indent_for_adding_newlines_in_pasting
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- write("<<~Q\n")
- write("{\n #\n}")
- write("#")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> <<~Q
- prompt> {
- prompt> #
- prompt> }#
- EOC
- close
- end
-
- def test_auto_indent_with_various_spaces
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- write "(\n\C-v"
- write "\C-k\n\C-v"
- write "\C-k)"
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> (
- prompt> ^K
- prompt> )
- EOC
- close
- end
-
- def test_autowrap_in_the_middle_of_a_line
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def abcdefg; end\C-b\C-b\C-b\C-b\C-b")
- %w{h i}.each do |c|
- write(c)
- end
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def abcdefgh
- i; end
- EOC
- close
- end
-
- def test_newline_in_the_middle_of_lines
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def hoge\n 1\n 2\n 3\n 4\nend\n")
- write("\C-p\C-p\C-p\C-e\n")
- assert_screen(<<~EOC)
- prompt> def hoge
- prompt> 1
- prompt> 2
- prompt> 3
- prompt>
- EOC
- close
- end
-
- def test_ed_force_submit_in_the_middle_of_lines
- write_inputrc <<~LINES
- "\\C-a": ed_force_submit
- LINES
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def hoge\nend")
- write("\C-p\C-a")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def hoge
- prompt> end
- => :hoge
- prompt>
- EOC
- close
- end
-
- def test_dynamic_prompt_returns_empty
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt-returns-empty}, startup_message: 'Multiline REPL.')
- write("def hoge\nend\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def hoge
- prompt> end
- => :hoge
- prompt>
- EOC
- close
- end
-
- def test_reset_rest_height_when_clear_screen
- start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("\n\n\n\C-l3\n")
- assert_screen(<<~EOC)
- prompt> 3
- => 3
- prompt>
- EOC
- close
- end
-
- def test_meta_key
- start_terminal(30, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def ge\ebho")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def hoge
- EOC
- close
- end
-
- def test_not_meta_key
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("おだんご") # "だ" in UTF-8 contains "\xA0"
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> おだんご
- EOC
- close
- end
-
- def test_force_enter
- start_terminal(30, 120, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def hoge\nend\C-p\C-e")
- write("\e\x0D")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> def hoge
- prompt>
- prompt> end
- EOC
- close
- end
-
- def test_nontty
- omit if Reline.core.io_gate.win?
- cmd = %Q{ruby -e 'puts(%Q{ello\C-ah\C-e})' | ruby -I#{@pwd}/lib -rreline -e 'p Reline.readline(%{> })' | ruby -e 'print STDIN.read'}
- start_terminal(40, 50, ['bash', '-c', cmd])
- assert_screen(<<~'EOC')
- > hello
- "hello"
- EOC
- close
- end
-
- def test_eof_with_newline
- omit if Reline.core.io_gate.win?
- cmd = %Q{ruby -e 'print(%Q{abc def \\e\\r})' | ruby -I#{@pwd}/lib -rreline -e 'p Reline.readline(%{> })'}
- start_terminal(40, 50, ['bash', '-c', cmd])
- assert_screen(<<~'EOC')
- > abc def
- "abc def "
- EOC
- close
- end
-
- def test_eof_without_newline
- omit if Reline.core.io_gate.win?
- cmd = %Q{ruby -e 'print(%{hello})' | ruby -I#{@pwd}/lib -rreline -e 'p Reline.readline(%{> })'}
- start_terminal(40, 50, ['bash', '-c', cmd])
- assert_screen(<<~'EOC')
- > hello
- "hello"
- EOC
- close
- end
-
- def test_em_set_mark_and_em_exchange_mark
- start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("aaa bbb ccc ddd\eb\eb\e\x20\eb\C-x\C-xX\C-x\C-xY")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> aaa Ybbb Xccc ddd
- EOC
- close
- end
-
- def test_multiline_completion
- start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.')
- write("def hoge\n St\n St\C-p\t")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> def hoge
- prompt> String
- prompt> St
- EOC
- close
- end
-
- def test_completion_journey_2nd_line
- write_inputrc <<~LINES
- set editing-mode vi
- LINES
- start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.')
- write("def hoge\n S\C-n")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> def hoge
- prompt> String
- EOC
- close
- end
-
- def test_completion_journey_with_empty_line
- write_inputrc <<~LINES
- set editing-mode vi
- LINES
- start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.')
- write("\C-n\C-p")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt>
- EOC
- close
- end
-
- def test_completion_menu_is_displayed_horizontally
- start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.')
- write("S\t\t")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> S
- ScriptError String
- Signal SyntaxError
- EOC
- close
- end
-
- def test_show_all_if_ambiguous_on
- write_inputrc <<~LINES
- set show-all-if-ambiguous on
- LINES
- start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete}, startup_message: 'Multiline REPL.')
- write("S\t")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> S
- ScriptError String
- Signal SyntaxError
- EOC
- close
- end
-
- def test_show_all_if_ambiguous_on_and_menu_with_perfect_match
- write_inputrc <<~LINES
- set show-all-if-ambiguous on
- LINES
- start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --complete-menu-with-perfect-match}, startup_message: 'Multiline REPL.')
- write("a\t")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> abs
- abs abs2
- EOC
- close
- end
-
- def test_simple_dialog
- iterate_over_face_configs do |config_name, config_file|
- start_terminal(20, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
- write('a')
- write('b')
- write('c')
- write("\C-h")
- close
- assert_screen(<<~'EOC', "Failed with `#{config_name}` in Face")
- Multiline REPL.
- prompt> ab
- Ruby is...
- A dynamic, open source programming
- language with a focus on simplicity
- and productivity. It has an elegant
- syntax that is natural to read and
- easy to write.
- EOC
- end
- end
-
- def test_simple_dialog_at_right_edge
- iterate_over_face_configs do |config_name, config_file|
- start_terminal(20, 40, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
- write('a')
- write('b')
- write('c')
- write("\C-h")
- close
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> ab
- Ruby is...
- A dynamic, open source programming
- language with a focus on simplicity
- and productivity. It has an elegant
- syntax that is natural to read and
- easy to write.
- EOC
- end
- end
-
- def test_dialog_scroll_pushup_condition
- iterate_over_face_configs do |config_name, config_file|
- start_terminal(10, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("\n" * 10)
- write("if 1\n sSts\nend")
- write("\C-p\C-h\C-e\C-h")
- assert_screen(<<~'EOC')
- prompt>
- prompt>
- prompt>
- prompt>
- prompt>
- prompt>
- prompt> if 1
- prompt> St
- prompt> enString
- Struct
- EOC
- close
- end
- end
-
- def test_simple_dialog_with_scroll_screen
- iterate_over_face_configs do |config_name, config_file|
- start_terminal(5, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: /prompt>/)
- write("if 1\n 2\n 3\n 4\n 5\n 6")
- write("\C-p\C-n\C-p\C-p\C-p#")
- close
- assert_screen(<<~'EOC')
- prompt> 2
- prompt> 3#
- prompt> 4
- prompt> 5 Ruby is...
- prompt> 6 A dynamic, open source programming
- EOC
- end
- end
-
- def test_autocomplete_at_bottom
- start_terminal(15, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write('def hoge' + "\C-m" * 10 + "end\C-p ")
- write('S')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> def hoge
- prompt>
- prompt>
- prompt> String
- prompt> Struct
- prompt> Symbol
- prompt> ScriptError
- prompt> SyntaxError
- prompt> Signal
- prompt> S
- prompt> end
- EOC
- close
- end
-
- def test_autocomplete_return_to_original
- start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write('S')
- write('t')
- write('r')
- 3.times{ write("\C-i") }
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> Str
- String
- Struct
- EOC
- close
- end
-
- def test_autocomplete_target_is_wrapped
- start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write(' ')
- write('S')
- write('t')
- write('r')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> St
- r
- String
- Struct
- EOC
- close
- end
-
- def test_autocomplete_target_at_end_of_line
- start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write(' ')
- write('Str')
- write("\C-i")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> Str
- ing String
- Struct
- EOC
- close
- end
-
- def test_autocomplete_completed_input_is_wrapped
- start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write(' ')
- write('Str')
- write("\C-i")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> Stri
- ng String
- Struct
- EOC
- close
- end
-
- def test_force_insert_before_autocomplete
- start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write('Sy')
- write(";St\t\t")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> Sy;Struct
- String
- Struct
- EOC
- close
- end
-
- def test_simple_dialog_with_scroll_key
- start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog long,scrollkey}, startup_message: 'Multiline REPL.')
- write('a')
- 5.times{ write('j') }
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> a
- A dynamic, open
- source programming
- language with a
- focus on simplicity
- EOC
- close
- end
-
- def test_simple_dialog_scrollbar_with_moving_to_right
- start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog long,scrollkey,scrollbar}, startup_message: 'Multiline REPL.')
- 6.times{ write('j') }
- write('a')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> a
- source programming ▄
- language with a █
- focus on simplicity
- and productivity.
- EOC
- close
- end
-
- def test_simple_dialog_scrollbar_with_moving_to_left
- start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog long,scrollkey,scrollbar}, startup_message: 'Multiline REPL.')
- write('a')
- 6.times{ write('j') }
- write("\C-h")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt>
- source programming ▄
- language with a █
- focus on simplicity
- and productivity.
- EOC
- close
- end
-
- def test_dialog_with_fullwidth_chars
- ENV['RELINE_TEST_PROMPT'] = '> '
- start_terminal(20, 5, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog fullwidth,scrollkey,scrollbar}, startup_message: 'Multiline REPL.')
- 6.times{ write('j') }
- assert_screen(<<~'EOC')
- Multi
- line
- REPL.
- >
- オー
- グ言▄
- 備え█
- ち、█
- EOC
- close
- end
-
- def test_dialog_with_fullwidth_chars_split
- ENV['RELINE_TEST_PROMPT'] = '> '
- start_terminal(20, 6, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog fullwidth,scrollkey,scrollbar}, startup_message: 'Multiline REPL.')
- 6.times{ write('j') }
- assert_screen(<<~'EOC')
- Multil
- ine RE
- PL.
- >
- オー
- グ言 ▄
- 備え █
- ち、 █
- EOC
- close
- end
-
- def test_autocomplete_empty
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write('Street')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> Street
- EOC
- close
- end
-
- def test_autocomplete
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write('Str')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> Str
- String
- Struct
- EOC
- close
- end
-
- def test_autocomplete_empty_string
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("\C-i")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> String
- String █
- Struct ▀
- Symbol
- EOC
- close
- end
-
- def test_paste_code_with_tab_indent_does_not_fail
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-empty}, startup_message: 'Multiline REPL.')
- write("2.times do\n\tputs\n\tputs\nend")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> 2.times do
- prompt> puts
- prompt> puts
- prompt> end
- EOC
- close
- end
-
- def test_autocomplete_after_2nd_line
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("def hoge\n Str")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> def hoge
- prompt> Str
- String
- Struct
- EOC
- close
- end
-
- def test_autocomplete_rerender_under_dialog
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("def hoge\n\n 123456\n 456789\nend\C-p\C-p\C-p a = Str")
- write('i')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> def hoge
- prompt> a = Stri
- prompt> 1234String
- prompt> 456789
- prompt> end
- EOC
- close
- end
-
- def test_rerender_multiple_dialog
- start_terminal(20, 60, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete --dialog simple}, startup_message: 'Multiline REPL.')
- write("if\n abcdef\n 123456\n 456789\nend\C-p\C-p\C-p\C-p Str")
- write("\t")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> if String
- prompt> aStringRuby is...
- prompt> 1StructA dynamic, open source programming
- prompt> 456789 language with a focus on simplicity
- prompt> end and productivity. It has an elegant
- syntax that is natural to read and
- easy to write.
- EOC
- close
- end
-
- def test_autocomplete_long_with_scrollbar
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-long}, startup_message: 'Multiline REPL.')
- write('S')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> S
- String █
- Struct █
- Symbol █
- StopIteration █
- SystemCallError █
- SystemExit █
- SystemStackError█
- ScriptError █
- SyntaxError █
- Signal █
- SizedQueue █
- Set
- SecureRandom
- Socket
- StringIO
- EOC
- write("\C-i" * 16)
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> StringScanner
- Struct ▄
- Symbol █
- StopIteration █
- SystemCallError █
- SystemExit █
- SystemStackError█
- ScriptError █
- SyntaxError █
- Signal █
- SizedQueue █
- Set █
- SecureRandom ▀
- Socket
- StringIO
- StringScanner
- EOC
- close
- end
-
- def test_autocomplete_super_long_scroll_to_bottom
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-super-long}, startup_message: 'Multiline REPL.')
- shift_tab = [27, 91, 90]
- write('S' + shift_tab.map(&:chr).join)
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> Str_BXX
- Str_BXJ
- Str_BXK
- Str_BXL
- Str_BXM
- Str_BXN
- Str_BXO
- Str_BXP
- Str_BXQ
- Str_BXR
- Str_BXS
- Str_BXT
- Str_BXU
- Str_BXV
- Str_BXW
- Str_BXX▄
- EOC
- close
- end
-
- def test_autocomplete_super_long_and_backspace
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-super-long}, startup_message: 'Multiline REPL.')
- shift_tab = [27, 91, 90]
- write('S' + shift_tab.map(&:chr).join)
- write("\C-h")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> Str_BX
- Str_BX █
- Str_BXA█
- Str_BXB█
- Str_BXC█
- Str_BXD█
- Str_BXE█
- Str_BXF█
- Str_BXG█
- Str_BXH█
- Str_BXI
- Str_BXJ
- Str_BXK
- Str_BXL
- Str_BXM
- Str_BXN
- EOC
- close
- end
-
- def test_dialog_callback_returns_nil
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog nil}, startup_message: 'Multiline REPL.')
- write('a')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> a
- EOC
- close
- end
-
- def test_dialog_narrower_than_screen
- start_terminal(20, 11, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
- assert_screen(<<~'EOC')
- Multiline R
- EPL.
- prompt>
- Ruby is...
- A dynamic,
- language wi
- and product
- syntax that
- easy to wri
- EOC
- close
- end
-
- def test_dialog_narrower_than_screen_with_scrollbar
- start_terminal(20, 11, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-long}, startup_message: 'Multiline REPL.')
- write('S' + "\C-i" * 3)
- assert_screen(<<~'EOC')
- Multiline R
- EPL.
- prompt> Sym
- String █
- Struct █
- Symbol █
- StopIterat█
- SystemCall█
- SystemExit█
- SystemStac█
- ScriptErro█
- SyntaxErro█
- Signal █
- SizedQueue█
- Set
- SecureRand
- Socket
- StringIO
- EOC
- close
- end
-
- def test_dialog_with_fullwidth_scrollbar
- start_terminal(20, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple,scrollkey,alt-scrollbar}, startup_message: 'Multiline REPL.')
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt>
- Ruby is... ::
- A dynamic, open source programming ::
- language with a focus on simplicity''
- and productivity. It has an elegant
- EOC
- close
- end
-
- def test_rerender_argument_prompt_after_pasting
- start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write('abcdef')
- write("\e3\C-h")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> abc
- EOC
- close
- end
-
- def test_autocomplete_old_dialog_width_greater_than_dialog_width
- start_terminal(40, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-width-long}, startup_message: 'Multiline REPL.')
- write("0+ \n12345678901234")
- write("\C-p")
- write("r")
- write("a")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> 0+ ra
- prompt> 123rand 901234
- raise
- EOC
- close
- end
-
- def test_scroll_at_bottom_for_dialog
- start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("\n\n\n\n\n\n\n\n\n\n\n")
- write("def hoge\n\nend\C-p\C-e")
- write(" S")
- assert_screen(<<~'EOC')
- prompt>
- prompt>
- prompt>
- prompt>
- prompt>
- prompt> def hoge
- prompt> S
- prompt> enString █
- Struct ▀
- Symbol
- EOC
- close
- end
-
- def test_clear_dialog_in_pasting
- start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("S")
- write("tring ")
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt> String
- EOC
- close
- end
-
- def test_prompt_with_newline
- ENV['RELINE_TEST_PROMPT'] = "::\n> "
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("def hoge\n 3\nend")
- assert_screen(<<~'EOC')
- Multiline REPL.
- ::\n> def hoge
- ::\n> 3
- ::\n> end
- EOC
- close
- end
-
- def test_dynamic_prompt_with_newline
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt-with-newline}, startup_message: 'Multiline REPL.')
- write("def hoge\n 3\nend")
- assert_screen(<<~'EOC')
- Multiline REPL.
- [0000\n]> def hoge
- [0001\n]> 3
- [0001\n]> end
- EOC
- close
- end
-
- def test_lines_passed_to_dynamic_prompt
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dynamic-prompt-show-line}, startup_message: 'Multiline REPL.')
- write("if true")
- write("\n")
- assert_screen(<<~EOC)
- Multiline REPL.
- [if t]> if true
- [ ]>
- EOC
- close
- end
-
- def test_clear_dialog_when_just_move_cursor_at_last_line
- start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("class A\n 3\nend\n\n\n")
- write("\C-p\C-p\C-e; S")
- assert_screen(/String/)
- write("\C-n")
- write(";")
- assert_screen(<<~'EOC')
- prompt> 3
- prompt> end
- => 3
- prompt>
- prompt>
- prompt> class A
- prompt> 3; S
- prompt> end;
- EOC
- close
- end
-
- def test_clear_dialog_when_adding_new_line_to_end_of_buffer
- start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("class A\n def a\n 3\n 3\n end\nend")
- write("\n")
- write("class S")
- write("\n")
- write(" 3")
- assert_screen(<<~'EOC')
- prompt> def a
- prompt> 3
- prompt> 3
- prompt> end
- prompt> end
- => :a
- prompt> class S
- prompt> 3
- EOC
- close
- end
-
- def test_insert_newline_in_the_middle_of_buffer_just_after_dialog
- start_terminal(10, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("class A\n def a\n 3\n end\nend")
- write("\n")
- write("\C-p\C-p\C-p\C-p\C-p\C-e\C-hS")
- write("\e\x0D")
- write(" 3")
- assert_screen(<<~'EOC')
- prompt> 3
- prompt> end
- prompt> end
- => :a
- prompt> class S
- prompt> 3
- prompt> def a
- prompt> 3
- prompt> end
- prompt> end
- EOC
- close
- end
-
- def test_incremental_search_on_not_last_line
- start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
- write("def abc\nend\n")
- write("def def\nend\n")
- write("\C-p\C-p\C-e")
- write("\C-r")
- write("a")
- write("\n\n")
- assert_screen(<<~'EOC')
- prompt> def abc
- prompt> end
- => :abc
- prompt> def def
- prompt> end
- => :def
- prompt> def abc
- prompt> end
- => :abc
- prompt>
- EOC
- close
- end
-
- def test_bracket_newline_indent
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- write("[\n")
- write("1")
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> [
- prompt> 1
- EOC
- close
- end
-
- def test_repeated_input_delete
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write("a\C-h" * 4000)
- assert_screen(<<~'EOC')
- Multiline REPL.
- prompt>
- EOC
- close
- end
-
- def test_exit_with_ctrl_d
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- begin
- write("\C-d")
- close
- rescue EOFError
- # EOFError is raised when process terminated.
- end
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt>
- EOC
- close
- end
-
- def test_quoted_insert_intr_keys
- omit if Reline.core.io_gate.win?
- start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.')
- write '"'
- write "\C-v"
- write "\C-c"
- write "\C-v"
- write "\C-z"
- write "\C-v"
- write "\C-\\"
- write "\".bytes\n"
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> "^C^Z^\\\".bytes
- => [3, 26, 28]
- prompt>
- EOC
- close
- end
-
- def test_print_before_readline
- code = <<~RUBY
- puts 'Multiline REPL.'
- 2.times do
- print 'a' * 10
- Reline.readline '>'
- end
- RUBY
- start_terminal(6, 30, ['ruby', "-I#{@pwd}/lib", '-rreline', '-e', code], startup_message: 'Multiline REPL.')
- write "x\n"
- assert_screen(<<~EOC)
- Multiline REPL.
- >x
- >
- EOC
- close
- end
-
- def test_pre_input_hook_with_redisplay
- code = <<~'RUBY'
- puts 'Multiline REPL.'
- Reline.pre_input_hook = -> do
- Reline.insert_text 'abc'
- Reline.redisplay # Reline doesn't need this but Readline requires calling redisplay
- end
- Reline.readline('prompt> ')
- RUBY
- start_terminal(6, 30, ['ruby', "-I#{@pwd}/lib", '-rreline', '-e', code], startup_message: 'Multiline REPL.')
- assert_screen(<<~EOC)
- Multiline REPL.
- prompt> abc
- EOC
- close
- end
-
- def test_pre_input_hook_with_multiline_text_insert
- # Frequently used pattern of pre_input_hook
- code = <<~'RUBY'
- puts 'Multiline REPL.'
- Reline.pre_input_hook = -> do
- Reline.insert_text "abc\nef"
- end
- Reline.readline('>')
- RUBY
- start_terminal(6, 30, ['ruby', "-I#{@pwd}/lib", '-rreline', '-e', code], startup_message: 'Multiline REPL.')
- write("\C-ad")
- assert_screen(<<~EOC)
- Multiline REPL.
- >abc
- def
- EOC
- close
- end
-
- def test_thread_safe
- start_terminal(6, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --auto-indent}, startup_message: 'Multiline REPL.')
- write("[Thread.new{Reline.readline'>'},Thread.new{Reline.readmultiline('>'){true}}].map(&:join).size\n")
- write("exit\n")
- write("exit\n")
- write("42\n")
- assert_screen(<<~EOC)
- >exit
- >exit
- => 2
- prompt> 42
- => 42
- prompt>
- EOC
- close
- end
-
- def test_user_defined_winch
- omit if Reline.core.io_gate.win?
- pidfile = Tempfile.create('pidfile')
- rubyfile = Tempfile.create('rubyfile')
- rubyfile.write <<~RUBY
- File.write(#{pidfile.path.inspect}, Process.pid)
- winch_called = false
- Signal.trap(:WINCH, ->(_arg){ winch_called = true })
- p Reline.readline('>')
- puts "winch: \#{winch_called}"
- RUBY
- rubyfile.close
-
- start_terminal(10, 50, %W{ruby -I#{@pwd}/lib -rreline #{rubyfile.path}})
- assert_screen(/^>/)
- write 'a'
- assert_screen(/^>a/)
- pid = pidfile.tap(&:rewind).read.to_i
- Process.kill(:WINCH, pid) unless pid.zero?
- write "b\n"
- assert_screen(/"ab"\nwinch: true/)
- close
- ensure
- File.delete(rubyfile.path) if rubyfile
- pidfile.close if pidfile
- File.delete(pidfile.path) if pidfile
- end
-
- def test_stop_continue
- omit if Reline.core.io_gate.win?
- pidfile = Tempfile.create('pidfile')
- rubyfile = Tempfile.create('rubyfile')
- rubyfile.write <<~RUBY
- File.write(#{pidfile.path.inspect}, Process.pid)
- cont_called = false
- Signal.trap(:CONT, ->(_arg){ cont_called = true })
- Reline.readmultiline('>'){|input| input.match?(/ghi/) }
- puts "cont: \#{cont_called}"
- RUBY
- rubyfile.close
- start_terminal(10, 50, ['bash'])
- write "ruby -I#{@pwd}/lib -rreline #{rubyfile.path}\n"
- assert_screen(/^>/)
- write "abc\ndef\nhi"
- pid = pidfile.tap(&:rewind).read.to_i
- Process.kill(:STOP, pid) unless pid.zero?
- write "fg\n"
- assert_screen(/fg\n.*>/m)
- write "\ebg"
- assert_screen(/>abc\n>def\n>ghi\n/)
- write "\n"
- assert_screen(/cont: true/)
- close
- ensure
- File.delete(rubyfile.path) if rubyfile
- pidfile.close if pidfile
- File.delete(pidfile.path) if pidfile
- end
-
- def write_inputrc(content)
- File.open(@inputrc_file, 'w') do |f|
- f.write content
- end
- end
-
- def fold_multiline(str, width)
- str.scan(/.{1,#{width}}/).each(&:rstrip!).join("\n")
- end
- end
-rescue LoadError, NameError
- # On Ruby repository, this test suit doesn't run because Ruby repo doesn't
- # have the yamatanooroti gem.
-end
diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb
index 0a06fba3e7..0b81118c8c 100644
--- a/test/resolv/test_dns.rb
+++ b/test/resolv/test_dns.rb
@@ -525,6 +525,8 @@ class TestResolvDNS < Test::Unit::TestCase
if RUBY_PLATFORM.match?(/mingw/)
# cannot repo locally
omit 'Timeout Error on MinGW CI'
+ elsif macos?([26,1]..[])
+ omit 'Timeout Error on macOS 26.1+'
else
raise Timeout::Error
end
@@ -627,6 +629,13 @@ class TestResolvDNS < Test::Unit::TestCase
assert_operator(2**14, :<, m.to_s.length)
end
+ def test_too_long_address
+ too_long_address_message = [0, 0, 1, 0, 0, 0].pack("n*") + "\x01x" * 129 + [0, 0, 0].pack("cnn")
+ assert_raise_with_message(Resolv::DNS::DecodeError, /name label data exceed 255 octets/) do
+ Resolv::DNS::Message.decode too_long_address_message
+ end
+ end
+
def assert_no_fd_leak
socket = assert_throw(self) do |tag|
Resolv::DNS.stub(:bind_random_port, ->(s, *) {throw(tag, s)}) do
@@ -712,13 +721,14 @@ class TestResolvDNS < Test::Unit::TestCase
client_thread = Thread.new do
Resolv::DNS.open(nameserver_port: [[server1_address, server1_port], [server2_address, server2_port]]) do |dns|
- dns.timeouts = [0.1, 0.2]
+ dns.timeouts = [EnvUtil.apply_timeout_scale(0.5),
+ EnvUtil.apply_timeout_scale(1)]
dns.getresources('foo.example.org', Resolv::DNS::Resource::IN::A)
end
end
udp_server1_thread = Thread.new do
- msg, (_, client_port, _, client_address) = Timeout.timeout(5) { u1.recvfrom(4096) }
+ msg, (_, client_port, _, client_address) = Timeout.timeout(EnvUtil.apply_timeout_scale(5)) { u1.recvfrom(4096) }
id, word2, _qdcount, _ancount, _nscount, _arcount = msg.unpack('nnnnnn')
opcode = (word2 & 0x7800) >> 11
rd = (word2 & 0x0100) >> 8
@@ -813,4 +823,124 @@ class TestResolvDNS < Test::Unit::TestCase
end
end
end
+
+ def test_tcp_connection_closed_before_length
+ with_tcp('127.0.0.1', 0) do |t|
+ _, server_port, _, server_address = t.addr
+
+ server_thread = Thread.new do
+ ct = t.accept
+ ct.recv(512)
+ ct.close
+ end
+
+ client_thread = Thread.new do
+ requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
+ begin
+ msg = Resolv::DNS::Message.new
+ msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
+ sender = requester.sender(msg, msg)
+ assert_raise(Resolv::ResolvTimeout) do
+ requester.request(sender, 2)
+ end
+ ensure
+ requester.close
+ end
+ end
+
+ server_thread.join
+ client_thread.join
+ end
+ end
+
+ def test_tcp_connection_closed_after_length
+ with_tcp('127.0.0.1', 0) do |t|
+ _, server_port, _, server_address = t.addr
+
+ server_thread = Thread.new do
+ ct = t.accept
+ ct.recv(512)
+ ct.send([100].pack('n'), 0)
+ ct.close
+ end
+
+ client_thread = Thread.new do
+ requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
+ begin
+ msg = Resolv::DNS::Message.new
+ msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
+ sender = requester.sender(msg, msg)
+ assert_raise(Resolv::ResolvTimeout) do
+ requester.request(sender, 2)
+ end
+ ensure
+ requester.close
+ end
+ end
+
+ server_thread.join
+ client_thread.join
+ end
+ end
+
+ def test_tcp_connection_closed_with_partial_length_prefix
+ with_tcp('127.0.0.1', 0) do |t|
+ _, server_port, _, server_address = t.addr
+
+ server_thread = Thread.new do
+ ct = t.accept
+ ct.recv(512)
+ ct.write "A" # 1 byte
+ ct.close
+ end
+
+ client_thread = Thread.new do
+ requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
+ begin
+ msg = Resolv::DNS::Message.new
+ msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
+ sender = requester.sender(msg, msg)
+ assert_raise(Resolv::ResolvTimeout) do
+ requester.request(sender, 2)
+ end
+ ensure
+ requester.close
+ end
+ end
+
+ server_thread.join
+ client_thread.join
+ end
+ end
+
+ def test_tcp_connection_closed_with_partial_message_body
+ with_tcp('127.0.0.1', 0) do |t|
+ _, server_port, _, server_address = t.addr
+
+ server_thread = Thread.new do
+ ct = t.accept
+ ct.recv(512)
+ ct.write([10].pack('n')) # length 10
+ ct.write "12345" # 5 bytes (partial)
+ ct.close
+ end
+
+ client_thread = Thread.new do
+ requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
+ begin
+ msg = Resolv::DNS::Message.new
+ msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
+ sender = requester.sender(msg, msg)
+ assert_raise(Resolv::ResolvTimeout) do
+ requester.request(sender, 2)
+ end
+ ensure
+ requester.close
+ end
+ end
+
+ server_thread.join
+ client_thread.join
+ end
+ end
end
diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb
index 434380236e..3a1c9ae3c3 100644
--- a/test/resolv/test_resource.rb
+++ b/test/resolv/test_resource.rb
@@ -20,10 +20,6 @@ class TestResolvResource < Test::Unit::TestCase
assert_equal(@name1.hash, @name2.hash, bug10857)
end
- def test_coord
- Resolv::LOC::Coord.create('1 2 1.1 N')
- end
-
def test_srv_no_compress
# Domain name in SRV RDATA should not be compressed
issue29 = 'https://github.com/ruby/resolv/issues/29'
@@ -33,6 +29,76 @@ class TestResolvResource < Test::Unit::TestCase
end
end
+class TestResolvResourceLOC < Test::Unit::TestCase
+ def test_size_create
+ assert_size("0.0m", 0, 0)
+ assert_size("0.01m", 1, 0)
+ assert_size("0.09m", 9, 0)
+ assert_size("0.11m", 1, 1)
+ assert_size("1.0m", 1, 2)
+ assert_size("1234.56m", 1, 5)
+ assert_size("12345678.90m", 1, 9)
+ assert_size("98765432.10m", 9, 9)
+ assert_raise(ArgumentError) {Resolv::LOC::Size.create("100000000.00m")}
+ end
+
+ private def assert_size(input, base, power)
+ size = Resolv::LOC::Size.create(input)
+ assert_equal([(base << 4) + power], size.scalar.unpack("C"))
+ assert_equal(size, Resolv::LOC::Size.create(size.to_s))
+ end
+
+ def test_coord
+ assert_coord('1 2 1.1 N', 'lat', 0x8038c78c)
+ assert_coord('42 21 43.952 N', 'lat', 0x89170690)
+ assert_coord('71 5 6.344 W', 'lon', 0x70bf2dd8)
+ assert_coord('52 14 05.000 N', 'lat', 0x8b3556c8)
+ assert_coord('90 0 0.000 N', 'lat', 0x934fd900)
+ assert_coord('90 0 0.000 S', 'lat', 0x6cb02700)
+ assert_coord('00 8 50.000 E', 'lon', 0x80081650)
+ assert_coord('0 8 50.001 E', 'lon', 0x80081651)
+ assert_coord('32 07 19.000 S', 'lat', 0x791b7d28)
+ assert_coord('116 02 25.000 E', 'lon', 0x98e64868)
+ assert_coord('116 02 25.000 W', 'lon', 0x6719b798)
+ assert_coord('180 00 00.000 E', 'lon', 0xa69fb200)
+ assert_coord('180 00 00.000 W', 'lon', 0x59604e00)
+ assert_raise(ArgumentError) {Resolv::LOC::Coord.create('90 0 0.001 N')}
+ assert_raise(ArgumentError) {Resolv::LOC::Coord.create('90 0 0.001 S')}
+ assert_raise(ArgumentError) {Resolv::LOC::Coord.create('180 0 0.001 E')}
+ assert_raise(ArgumentError) {Resolv::LOC::Coord.create('180 0 0.001 W')}
+ end
+
+ private def assert_coord(input, orientation, coordinate)
+ coord = Resolv::LOC::Coord.create(input)
+
+ assert_equal(orientation, coord.orientation)
+ assert_equal([coordinate].pack("N"), coord.coordinates)
+ assert_equal(coord, Resolv::LOC::Coord.create(coord.to_s))
+ end
+
+ def test_alt
+ assert_alt("0.0m", 0)
+ assert_alt("+0.0m", 0)
+ assert_alt("-0.0m", 0)
+ assert_alt("+0.01m", 1)
+ assert_alt("1.0m", 100)
+ assert_alt("+1.0m", 100)
+ assert_alt("100000.0m", +10000000)
+ assert_alt("+100000.0m", +10000000)
+ assert_alt("-100000.0m", -10000000)
+ assert_alt("+42849672.95m", 0xffff_ffff-100_000_00)
+ assert_raise(ArgumentError) {Resolv::LOC::Alt.create("-100000.01m")}
+ assert_raise(ArgumentError) {Resolv::LOC::Alt.create("+42849672.96m")}
+ end
+
+ private def assert_alt(input, altitude)
+ alt = Resolv::LOC::Alt.create(input)
+
+ assert_equal([altitude + 1e7].pack("N"), alt.altitude)
+ assert_equal(alt, Resolv::LOC::Alt.create(alt.to_s))
+ end
+end
+
class TestResolvResourceCAA < Test::Unit::TestCase
def test_caa_roundtrip
raw_msg = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x03new\x07example\x03com\x00\x01\x01\x00\x01\x00\x00\x00\x00\x00\x16\x00\x05issueca1.example.net\xC0\x0C\x01\x01\x00\x01\x00\x00\x00\x00\x00\x0C\x80\x03tbsUnknown".b
diff --git a/test/resolv/test_win32_config.rb b/test/resolv/test_win32_config.rb
new file mode 100644
index 0000000000..6167af6605
--- /dev/null
+++ b/test/resolv/test_win32_config.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'resolv'
+
+if defined?(Win32::Resolve)
+ class TestWin32Config < Test::Unit::TestCase
+ def test_get_item_property_string
+ # Test reading a string registry value
+ result = Win32::Resolv.send(:get_hosts_dir)
+
+ # Should return a string (empty or with a path)
+ assert_instance_of String, result
+ end
+
+ # Test reading a non-existent registry key
+ def test_nonexistent_key
+ assert_nil(Win32::Resolv.send(:tcpip_params) {|reg| reg.open('NonExistentKeyThatShouldNotExist')})
+ end
+
+ # Test reading a non-existent registry value
+ def test_nonexistent_value
+ assert_nil(Win32::Resolv.send(:tcpip_params) {|reg| reg.value('NonExistentKeyThatShouldNotExist')})
+ end
+ end
+end
diff --git a/test/ripper/assert_parse_files.rb b/test/ripper/assert_parse_files.rb
index 0d583a99e3..4f08589e41 100644
--- a/test/ripper/assert_parse_files.rb
+++ b/test/ripper/assert_parse_files.rb
@@ -40,6 +40,7 @@ class TestRipper::Generic < Test::Unit::TestCase
end
}
end
+ assert(true) if scripts.empty?
end;
end
end
diff --git a/test/ripper/test_lexer.rb b/test/ripper/test_lexer.rb
index a371e8c42d..4bc6fd7ced 100644
--- a/test/ripper/test_lexer.rb
+++ b/test/ripper/test_lexer.rb
@@ -344,6 +344,47 @@ world"
]
assert_lexer(expected, code)
+
+ code = <<~'HEREDOC'
+ <<H1
+ #{<<H2}a
+ H2
+ b
+ HEREDOC
+
+ expected = [
+ [[1, 0], :on_heredoc_beg, "<<H1", state(:EXPR_BEG)],
+ [[1, 4], :on_nl, "\n", state(:EXPR_BEG)],
+ [[2, 0], :on_embexpr_beg, "\#{", state(:EXPR_BEG)],
+ [[2, 2], :on_heredoc_beg, "<<H2", state(:EXPR_BEG)],
+ [[2, 6], :on_embexpr_end, "}", state(:EXPR_END)],
+ [[2, 7], :on_tstring_content, "a\n", state(:EXPR_BEG)],
+ [[3, 0], :on_heredoc_end, "H2\n", state(:EXPR_BEG)],
+ [[4, 0], :on_tstring_content, "b\n", state(:EXPR_BEG)]
+ ]
+
+ assert_lexer(expected, code)
+
+ code = <<~'HEREDOC'
+ <<H1
+ #{<<H2}a
+ H2
+ b
+ c
+ HEREDOC
+
+ expected = [
+ [[1, 0], :on_heredoc_beg, "<<H1", state(:EXPR_BEG)],
+ [[1, 4], :on_nl, "\n", state(:EXPR_BEG)],
+ [[2, 0], :on_embexpr_beg, "\#{", state(:EXPR_BEG)],
+ [[2, 2], :on_heredoc_beg, "<<H2", state(:EXPR_BEG)],
+ [[2, 6], :on_embexpr_end, "}", state(:EXPR_END)],
+ [[2, 7], :on_tstring_content, "a\n", state(:EXPR_BEG)],
+ [[3, 0], :on_heredoc_end, "H2\n", state(:EXPR_BEG)],
+ [[4, 0], :on_tstring_content, "b\nc\n", state(:EXPR_BEG)]
+ ]
+
+ assert_lexer(expected, code)
end
def test_invalid_escape_ctrl_mbchar
@@ -545,6 +586,58 @@ world"
assert_lexer(expected, code)
end
+ def test_fluent_and
+ code = "foo\n" "and"
+ expected = [
+ [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
+ [[1, 3], :on_ignored_nl, "\n", state(:EXPR_CMDARG)],
+ [[2, 0], :on_kw, "and", state(:EXPR_BEG)],
+ ]
+ assert_lexer(expected, code)
+
+ code = "foo\n" "and?"
+ expected = [
+ [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
+ [[1, 3], :on_nl, "\n", state(:EXPR_BEG)],
+ [[2, 0], :on_ident, "and?", state(:EXPR_CMDARG)],
+ ]
+ assert_lexer(expected, code)
+
+ code = "foo\n" "and!"
+ expected = [
+ [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
+ [[1, 3], :on_nl, "\n", state(:EXPR_BEG)],
+ [[2, 0], :on_ident, "and!", state(:EXPR_CMDARG)],
+ ]
+ assert_lexer(expected, code)
+ end
+
+ def test_fluent_or
+ code = "foo\n" "or"
+ expected = [
+ [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
+ [[1, 3], :on_ignored_nl, "\n", state(:EXPR_CMDARG)],
+ [[2, 0], :on_kw, "or", state(:EXPR_BEG)],
+ ]
+ assert_lexer(expected, code)
+
+ code = "foo\n" "or?"
+ expected = [
+ [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
+ [[1, 3], :on_nl, "\n", state(:EXPR_BEG)],
+ [[2, 0], :on_ident, "or?", state(:EXPR_CMDARG)],
+ ]
+ assert_lexer(expected, code)
+
+ code = "foo\n" "or!"
+ expected = [
+ [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)],
+ [[1, 3], :on_nl, "\n", state(:EXPR_BEG)],
+ [[2, 0], :on_ident, "or!", state(:EXPR_CMDARG)],
+ ]
+ assert_lexer(expected, code)
+ end
+
def assert_lexer(expected, code)
assert_equal(code, Ripper.tokenize(code).join(""))
assert_equal(expected, result = Ripper.lex(code),
diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb
index aa7434c083..3e72c7a331 100644
--- a/test/ripper/test_parser_events.rb
+++ b/test/ripper/test_parser_events.rb
@@ -482,6 +482,13 @@ class TestRipper::ParserEvents < Test::Unit::TestCase
thru_call = false
assert_nothing_raised {
+ tree = parse("a b do end.()", :on_call) {thru_call = true}
+ }
+ assert_equal true, thru_call
+ assert_equal "[call(command(a,[vcall(b)],&do_block(,bodystmt([void()]))),.,call,[])]", tree
+
+ thru_call = false
+ assert_nothing_raised {
tree = parse("self::foo", :on_call) {thru_call = true}
}
assert_equal true, thru_call
diff --git a/test/ruby/box/a.1_1_0.rb b/test/ruby/box/a.1_1_0.rb
new file mode 100644
index 0000000000..0322585097
--- /dev/null
+++ b/test/ruby/box/a.1_1_0.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class BOX_A
+ VERSION = "1.1.0"
+
+ def yay
+ "yay #{VERSION}"
+ end
+end
+
+module BOX_B
+ VERSION = "1.1.0"
+
+ def self.yay
+ "yay_b1"
+ end
+end
diff --git a/test/ruby/box/a.1_2_0.rb b/test/ruby/box/a.1_2_0.rb
new file mode 100644
index 0000000000..29813ea57b
--- /dev/null
+++ b/test/ruby/box/a.1_2_0.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class BOX_A
+ VERSION = "1.2.0"
+
+ def yay
+ "yay #{VERSION}"
+ end
+end
+
+module BOX_B
+ VERSION = "1.2.0"
+
+ def self.yay
+ "yay_b1"
+ end
+end
diff --git a/test/ruby/box/a.rb b/test/ruby/box/a.rb
new file mode 100644
index 0000000000..26a622c92b
--- /dev/null
+++ b/test/ruby/box/a.rb
@@ -0,0 +1,15 @@
+class BOX_A
+ FOO = "foo_a1"
+
+ def yay
+ "yay_a1"
+ end
+end
+
+module BOX_B
+ BAR = "bar_b1"
+
+ def self.yay
+ "yay_b1"
+ end
+end
diff --git a/test/ruby/box/autoloading.rb b/test/ruby/box/autoloading.rb
new file mode 100644
index 0000000000..cba57ab377
--- /dev/null
+++ b/test/ruby/box/autoloading.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+autoload :BOX_A, File.join(__dir__, 'a.1_1_0')
+BOX_A.new.yay
+
+module BOX_B
+ autoload :BAR, File.join(__dir__, 'a')
+end
diff --git a/test/ruby/box/blank.rb b/test/ruby/box/blank.rb
new file mode 100644
index 0000000000..6d201b0966
--- /dev/null
+++ b/test/ruby/box/blank.rb
@@ -0,0 +1,2 @@
+module Blank1
+end
diff --git a/test/ruby/box/blank1.rb b/test/ruby/box/blank1.rb
new file mode 100644
index 0000000000..6d201b0966
--- /dev/null
+++ b/test/ruby/box/blank1.rb
@@ -0,0 +1,2 @@
+module Blank1
+end
diff --git a/test/ruby/box/blank2.rb b/test/ruby/box/blank2.rb
new file mode 100644
index 0000000000..ba38c1d6db
--- /dev/null
+++ b/test/ruby/box/blank2.rb
@@ -0,0 +1,2 @@
+module Blank2
+end
diff --git a/test/ruby/box/box.rb b/test/ruby/box/box.rb
new file mode 100644
index 0000000000..3b7da14e9d
--- /dev/null
+++ b/test/ruby/box/box.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+BOX1 = Ruby::Box.new
+BOX1.require_relative('a.1_1_0')
+
+def yay
+ BOX1::BOX_B::yay
+end
+
+yay
diff --git a/test/ruby/box/call_proc.rb b/test/ruby/box/call_proc.rb
new file mode 100644
index 0000000000..8acf538fc1
--- /dev/null
+++ b/test/ruby/box/call_proc.rb
@@ -0,0 +1,5 @@
+module Bar
+ def self.caller(proc_value)
+ proc_value.call
+ end
+end
diff --git a/test/ruby/box/call_toplevel.rb b/test/ruby/box/call_toplevel.rb
new file mode 100644
index 0000000000..c311a37028
--- /dev/null
+++ b/test/ruby/box/call_toplevel.rb
@@ -0,0 +1,8 @@
+foo
+
+#### TODO: this code should be valid, but can't be for now
+# module Foo
+# def self.wow
+# foo
+# end
+# end
diff --git a/test/ruby/box/consts.rb b/test/ruby/box/consts.rb
new file mode 100644
index 0000000000..e40cd5c50c
--- /dev/null
+++ b/test/ruby/box/consts.rb
@@ -0,0 +1,148 @@
+$VERBOSE = nil
+class String
+ STR_CONST1 = 111
+ STR_CONST2 = 222
+ STR_CONST3 = 333
+end
+
+class String
+ STR_CONST1 = 112
+
+ def self.set0(val)
+ const_set(:STR_CONST0, val)
+ end
+
+ def self.remove0
+ remove_const(:STR_CONST0)
+ end
+
+ def refer0
+ STR_CONST0
+ end
+
+ def refer1
+ STR_CONST1
+ end
+
+ def refer2
+ STR_CONST2
+ end
+
+ def refer3
+ STR_CONST3
+ end
+end
+
+module ForConsts
+ CONST1 = 111
+end
+
+TOP_CONST = 10
+
+module ForConsts
+ CONST1 = 112
+ CONST2 = 222
+ CONST3 = 333
+
+ def self.refer_all
+ ForConsts::CONST1
+ ForConsts::CONST2
+ ForConsts::CONST3
+ String::STR_CONST1
+ String::STR_CONST2
+ String::STR_CONST3
+ end
+
+ def self.refer1
+ CONST1
+ end
+
+ def self.get1
+ const_get(:CONST1)
+ end
+
+ def self.refer2
+ CONST2
+ end
+
+ def self.get2
+ const_get(:CONST2)
+ end
+
+ def self.refer3
+ CONST3
+ end
+
+ def self.get3
+ const_get(:CONST3)
+ end
+
+ def self.refer_top_const
+ TOP_CONST
+ end
+
+ # for String
+ class Proxy
+ def call_str_refer0
+ String.new.refer0
+ end
+
+ def call_str_get0
+ String.const_get(:STR_CONST0)
+ end
+
+ def call_str_set0(val)
+ String.set0(val)
+ end
+
+ def call_str_remove0
+ String.remove0
+ end
+
+ def call_str_refer1
+ String.new.refer1
+ end
+
+ def call_str_get1
+ String.const_get(:STR_CONST1)
+ end
+
+ String::STR_CONST2 = 223
+
+ def call_str_refer2
+ String.new.refer2
+ end
+
+ def call_str_get2
+ String.const_get(:STR_CONST2)
+ end
+
+ def call_str_set3
+ String.const_set(:STR_CONST3, 334)
+ end
+
+ def call_str_refer3
+ String.new.refer3
+ end
+
+ def call_str_get3
+ String.const_get(:STR_CONST3)
+ end
+
+ # for Integer
+ Integer::INT_CONST1 = 1
+
+ def refer_int_const1
+ Integer::INT_CONST1
+ end
+ end
+end
+
+# should not raise errors
+ForConsts.refer_all
+String::STR_CONST1
+Integer::INT_CONST1
+
+# If we execute this sentence once, the constant value will be cached on ISeq inline constant cache.
+# And it changes the behavior of ForConsts.refer_consts_directly called from global.
+# ForConsts.refer_consts_directly # should not raise errors too
diff --git a/test/ruby/box/define_toplevel.rb b/test/ruby/box/define_toplevel.rb
new file mode 100644
index 0000000000..aa77db3a13
--- /dev/null
+++ b/test/ruby/box/define_toplevel.rb
@@ -0,0 +1,5 @@
+def foo
+ "foooooooooo"
+end
+
+foo # should not raise errors
diff --git a/test/ruby/box/global_vars.rb b/test/ruby/box/global_vars.rb
new file mode 100644
index 0000000000..590363f617
--- /dev/null
+++ b/test/ruby/box/global_vars.rb
@@ -0,0 +1,37 @@
+module LineSplitter
+ def self.read
+ $-0
+ end
+
+ def self.write(char)
+ $-0 = char
+ end
+end
+
+module FieldSplitter
+ def self.read
+ $,
+ end
+
+ def self.write(char)
+ $, = char
+ end
+end
+
+module UniqueGvar
+ def self.read
+ $used_only_in_box
+ end
+
+ def self.write(val)
+ $used_only_in_box = val
+ end
+
+ def self.write_only(val)
+ $write_only_var_in_box = val
+ end
+
+ def self.gvars_in_box
+ global_variables
+ end
+end
diff --git a/test/ruby/box/instance_variables.rb b/test/ruby/box/instance_variables.rb
new file mode 100644
index 0000000000..1562ad5d45
--- /dev/null
+++ b/test/ruby/box/instance_variables.rb
@@ -0,0 +1,21 @@
+class String
+ class << self
+ attr_reader :str_ivar1
+
+ def str_ivar2
+ @str_ivar2
+ end
+ end
+
+ @str_ivar1 = 111
+ @str_ivar2 = 222
+end
+
+class StringDelegator < BasicObject
+private
+ def method_missing(...)
+ ::String.public_send(...)
+ end
+end
+
+StringDelegatorObj = StringDelegator.new
diff --git a/test/ruby/box/line_splitter.rb b/test/ruby/box/line_splitter.rb
new file mode 100644
index 0000000000..2596975ad7
--- /dev/null
+++ b/test/ruby/box/line_splitter.rb
@@ -0,0 +1,9 @@
+module LineSplitter
+ def self.read
+ $-0
+ end
+
+ def self.write(char)
+ $-0 = char
+ end
+end
diff --git a/test/ruby/box/load_path.rb b/test/ruby/box/load_path.rb
new file mode 100644
index 0000000000..7e5a83ef96
--- /dev/null
+++ b/test/ruby/box/load_path.rb
@@ -0,0 +1,26 @@
+module LoadPathCheck
+ FIRST_LOAD_PATH = $LOAD_PATH.dup
+ FIRST_LOAD_PATH_RESPOND_TO_RESOLVE = $LOAD_PATH.respond_to?(:resolve_feature_path)
+ FIRST_LOADED_FEATURES = $LOADED_FEATURES.dup
+
+ HERE = File.dirname(__FILE__)
+
+ def self.current_load_path
+ $LOAD_PATH
+ end
+
+ def self.current_loaded_features
+ $LOADED_FEATURES
+ end
+
+ def self.require_blank1
+ $LOAD_PATH << HERE
+ require 'blank1'
+ end
+
+ def self.require_blank2
+ require 'blank2'
+ end
+end
+
+LoadPathCheck.require_blank1
diff --git a/test/ruby/box/open_class_with_include.rb b/test/ruby/box/open_class_with_include.rb
new file mode 100644
index 0000000000..ad8fd58ea0
--- /dev/null
+++ b/test/ruby/box/open_class_with_include.rb
@@ -0,0 +1,31 @@
+module StringExt
+ FOO = "foo 1"
+ def say_foo
+ "I'm saying " + FOO
+ end
+end
+
+class String
+ include StringExt
+ def say
+ say_foo
+ end
+end
+
+module OpenClassWithInclude
+ def self.say
+ String.new.say
+ end
+
+ def self.say_foo
+ String.new.say_foo
+ end
+
+ def self.say_with_obj(str)
+ str.say
+ end
+
+ def self.refer_foo
+ String::FOO
+ end
+end
diff --git a/test/ruby/box/proc_callee.rb b/test/ruby/box/proc_callee.rb
new file mode 100644
index 0000000000..d30ab5d9f3
--- /dev/null
+++ b/test/ruby/box/proc_callee.rb
@@ -0,0 +1,14 @@
+module Target
+ def self.foo
+ "fooooo"
+ end
+end
+
+module Foo
+ def self.callee
+ lambda do
+ Target.foo
+ end
+ end
+end
+
diff --git a/test/ruby/box/proc_caller.rb b/test/ruby/box/proc_caller.rb
new file mode 100644
index 0000000000..8acf538fc1
--- /dev/null
+++ b/test/ruby/box/proc_caller.rb
@@ -0,0 +1,5 @@
+module Bar
+ def self.caller(proc_value)
+ proc_value.call
+ end
+end
diff --git a/test/ruby/box/procs.rb b/test/ruby/box/procs.rb
new file mode 100644
index 0000000000..1c39a8231b
--- /dev/null
+++ b/test/ruby/box/procs.rb
@@ -0,0 +1,64 @@
+class String
+ FOO = "foo"
+ def yay
+ "yay"
+ end
+end
+
+module ProcLookupTestA
+ module B
+ VALUE = 222
+ end
+end
+
+module ProcInBox
+ def self.make_proc_from_block(&b)
+ b
+ end
+
+ def self.call_proc(proc_arg)
+ proc_arg.call
+ end
+
+ def self.make_str_proc(type)
+ case type
+ when :proc_new then Proc.new { String.new.yay }
+ when :proc_f then proc { String.new.yay }
+ when :lambda_f then lambda { String.new.yay }
+ when :lambda_l then ->(){ String.new.yay }
+ when :block then make_proc_from_block { String.new.yay }
+ else
+ raise "invalid type :#{type}"
+ end
+ end
+
+ def self.make_const_proc(type)
+ case type
+ when :proc_new then Proc.new { ProcLookupTestA::B::VALUE }
+ when :proc_f then proc { ProcLookupTestA::B::VALUE }
+ when :lambda_f then lambda { ProcLookupTestA::B::VALUE }
+ when :lambda_l then ->(){ ProcLookupTestA::B::VALUE }
+ when :block then make_proc_from_block { ProcLookupTestA::B::VALUE }
+ else
+ raise "invalid type :#{type}"
+ end
+ end
+
+ def self.make_str_const_proc(type)
+ case type
+ when :proc_new then Proc.new { String::FOO }
+ when :proc_f then proc { String::FOO }
+ when :lambda_f then lambda { String::FOO }
+ when :lambda_l then ->(){ String::FOO }
+ when :block then make_proc_from_block { String::FOO }
+ else
+ raise "invalid type :#{type}"
+ end
+ end
+
+ CONST_PROC_NEW = Proc.new { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+ CONST_PROC_F = proc { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+ CONST_LAMBDA_F = lambda { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+ CONST_LAMBDA_L = ->() { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+ CONST_BLOCK = make_proc_from_block { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') }
+end
diff --git a/test/ruby/box/raise.rb b/test/ruby/box/raise.rb
new file mode 100644
index 0000000000..efb67f85c5
--- /dev/null
+++ b/test/ruby/box/raise.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+raise "Yay!"
diff --git a/test/ruby/box/returns_proc.rb b/test/ruby/box/returns_proc.rb
new file mode 100644
index 0000000000..bb816e5024
--- /dev/null
+++ b/test/ruby/box/returns_proc.rb
@@ -0,0 +1,12 @@
+module Foo
+ def self.foo
+ "fooooo"
+ end
+
+ def self.callee
+ lambda do
+ Foo.foo
+ end
+ end
+end
+
diff --git a/test/ruby/box/singleton_methods.rb b/test/ruby/box/singleton_methods.rb
new file mode 100644
index 0000000000..05470932d2
--- /dev/null
+++ b/test/ruby/box/singleton_methods.rb
@@ -0,0 +1,65 @@
+class String
+ def self.greeting
+ "Good evening!"
+ end
+end
+
+class Integer
+ class << self
+ def answer
+ 42
+ end
+ end
+end
+
+class Array
+ def a
+ size
+ end
+ def self.blank
+ []
+ end
+ def b
+ size
+ end
+end
+
+class Hash
+ def a
+ size
+ end
+ class << self
+ def http_200
+ {status: 200, body: 'OK'}
+ end
+ end
+ def b
+ size
+ end
+end
+
+module SingletonMethods
+ def self.string_greeing
+ String.greeting
+ end
+
+ def self.integer_answer
+ Integer.answer
+ end
+
+ def self.array_blank
+ Array.blank
+ end
+
+ def self.hash_http_200
+ Hash.http_200
+ end
+
+ def self.array_instance_methods_return_size(ary)
+ [ary.a, ary.b]
+ end
+
+ def self.hash_instance_methods_return_size(hash)
+ [hash.a, hash.b]
+ end
+end
diff --git a/test/ruby/box/string_ext.rb b/test/ruby/box/string_ext.rb
new file mode 100644
index 0000000000..d8c5a3d661
--- /dev/null
+++ b/test/ruby/box/string_ext.rb
@@ -0,0 +1,13 @@
+class String
+ def yay
+ "yay"
+ end
+end
+
+String.new.yay # check this doesn't raise NoMethodError
+
+module Bar
+ def self.yay
+ String.new.yay
+ end
+end
diff --git a/test/ruby/box/string_ext_caller.rb b/test/ruby/box/string_ext_caller.rb
new file mode 100644
index 0000000000..b8345d98ed
--- /dev/null
+++ b/test/ruby/box/string_ext_caller.rb
@@ -0,0 +1,5 @@
+module Foo
+ def self.yay
+ String.new.yay
+ end
+end
diff --git a/test/ruby/box/string_ext_calling.rb b/test/ruby/box/string_ext_calling.rb
new file mode 100644
index 0000000000..6467b728dd
--- /dev/null
+++ b/test/ruby/box/string_ext_calling.rb
@@ -0,0 +1 @@
+Foo.yay
diff --git a/test/ruby/box/string_ext_eval_caller.rb b/test/ruby/box/string_ext_eval_caller.rb
new file mode 100644
index 0000000000..0e6b20c19f
--- /dev/null
+++ b/test/ruby/box/string_ext_eval_caller.rb
@@ -0,0 +1,12 @@
+module Baz
+ def self.yay
+ eval 'String.new.yay'
+ end
+
+ def self.yay_with_binding
+ suffix = ", yay!"
+ eval 'String.new.yay + suffix', binding
+ end
+end
+
+Baz.yay # should not raise NeMethodError
diff --git a/test/ruby/box/top_level.rb b/test/ruby/box/top_level.rb
new file mode 100644
index 0000000000..90df145578
--- /dev/null
+++ b/test/ruby/box/top_level.rb
@@ -0,0 +1,33 @@
+def yaaay
+ "yay!"
+end
+
+module Foo
+ def self.foo
+ yaaay
+ end
+end
+
+eval 'def foo; "foo"; end'
+
+Foo.foo # Should not raise NameError
+
+foo
+
+module Bar
+ def self.bar
+ foo
+ end
+end
+
+Bar.bar
+
+$def_retval_in_namespace = def boooo
+ "boo"
+end
+
+module Baz
+ def self.baz
+ raise "#{$def_retval_in_namespace}"
+ end
+end
diff --git a/test/ruby/enc/test_emoji_breaks.rb b/test/ruby/enc/test_emoji_breaks.rb
index bb5114680e..0873e681c3 100644
--- a/test/ruby/enc/test_emoji_breaks.rb
+++ b/test/ruby/enc/test_emoji_breaks.rb
@@ -53,7 +53,7 @@ class TestEmojiBreaks < Test::Unit::TestCase
EMOJI_DATA_FILES = %w[emoji-sequences emoji-test emoji-zwj-sequences].map do |basename|
BreakFile.new(basename, EMOJI_DATA_PATH, EMOJI_VERSION)
end
- UNICODE_DATA_FILE = BreakFile.new('emoji-variation-sequences', UNICODE_DATA_PATH, UNICODE_VERSION)
+ UNICODE_DATA_FILE = BreakFile.new('emoji-variation-sequences', UNICODE_DATA_PATH, EMOJI_VERSION)
EMOJI_DATA_FILES << UNICODE_DATA_FILE
def self.data_files_available?
diff --git a/test/ruby/rjit/test_assembler.rb b/test/ruby/rjit/test_assembler.rb
deleted file mode 100644
index fbf780d6c3..0000000000
--- a/test/ruby/rjit/test_assembler.rb
+++ /dev/null
@@ -1,368 +0,0 @@
-require 'test/unit'
-require_relative '../../lib/jit_support'
-
-return unless JITSupport.rjit_supported?
-return unless RubyVM::RJIT.enabled?
-return unless RubyVM::RJIT::C.HAVE_LIBCAPSTONE
-
-require 'stringio'
-require 'ruby_vm/rjit/assembler'
-
-module RubyVM::RJIT
- class TestAssembler < Test::Unit::TestCase
- MEM_SIZE = 16 * 1024
-
- def setup
- @mem_block ||= C.mmap(MEM_SIZE)
- @cb = CodeBlock.new(mem_block: @mem_block, mem_size: MEM_SIZE)
- end
-
- def test_add
- asm = Assembler.new
- asm.add([:rcx], 1) # ADD r/m64, imm8 (Mod 00: [reg])
- asm.add(:rax, 0x7f) # ADD r/m64, imm8 (Mod 11: reg)
- asm.add(:rbx, 0x7fffffff) # ADD r/m64 imm32 (Mod 11: reg)
- asm.add(:rsi, :rdi) # ADD r/m64, r64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: add qword ptr [rcx], 1
- 0x4: add rax, 0x7f
- 0x8: add rbx, 0x7fffffff
- 0xf: add rsi, rdi
- EOS
- end
-
- def test_and
- asm = Assembler.new
- asm.and(:rax, 0) # AND r/m64, imm8 (Mod 11: reg)
- asm.and(:rbx, 0x7fffffff) # AND r/m64, imm32 (Mod 11: reg)
- asm.and(:rcx, [:rdi, 8]) # AND r64, r/m64 (Mod 01: [reg]+disp8)
- assert_compile(asm, <<~EOS)
- 0x0: and rax, 0
- 0x4: and rbx, 0x7fffffff
- 0xb: and rcx, qword ptr [rdi + 8]
- EOS
- end
-
- def test_call
- asm = Assembler.new
- asm.call(rel32(0xff)) # CALL rel32
- asm.call(:rax) # CALL r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: call 0xff
- 0x5: call rax
- EOS
- end
-
- def test_cmove
- asm = Assembler.new
- asm.cmove(:rax, :rcx) # CMOVE r64, r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: cmove rax, rcx
- EOS
- end
-
- def test_cmovg
- asm = Assembler.new
- asm.cmovg(:rbx, :rdi) # CMOVG r64, r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: cmovg rbx, rdi
- EOS
- end
-
- def test_cmovge
- asm = Assembler.new
- asm.cmovge(:rsp, :rbp) # CMOVGE r64, r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: cmovge rsp, rbp
- EOS
- end
-
- def test_cmovl
- asm = Assembler.new
- asm.cmovl(:rdx, :rsp) # CMOVL r64, r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: cmovl rdx, rsp
- EOS
- end
-
- def test_cmovle
- asm = Assembler.new
- asm.cmovle(:rax, :rax) # CMOVLE r64, r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: cmovle rax, rax
- EOS
- end
-
- def test_cmovne
- asm = Assembler.new
- asm.cmovne(:rax, :rbx) # CMOVNE r64, r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS) # cmovne == cmovnz
- 0x0: cmovne rax, rbx
- EOS
- end
-
- def test_cmovnz
- asm = Assembler.new
- asm.cmovnz(:rax, :rbx) # CMOVNZ r64, r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS) # cmovne == cmovnz
- 0x0: cmovne rax, rbx
- EOS
- end
-
- def test_cmovz
- asm = Assembler.new
- asm.cmovz(:rax, :rbx) # CMOVZ r64, r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS) # cmove == cmovz
- 0x0: cmove rax, rbx
- EOS
- end
-
- def test_cmp
- asm = Assembler.new
- asm.cmp(BytePtr[:rax, 8], 8) # CMP r/m8, imm8 (Mod 01: [reg]+disp8)
- asm.cmp(DwordPtr[:rax, 8], 0x100) # CMP r/m32, imm32 (Mod 01: [reg]+disp8)
- asm.cmp([:rax, 8], 8) # CMP r/m64, imm8 (Mod 01: [reg]+disp8)
- asm.cmp([:rbx, 8], 0x100) # CMP r/m64, imm32 (Mod 01: [reg]+disp8)
- asm.cmp([:rax, 0x100], 8) # CMP r/m64, imm8 (Mod 10: [reg]+disp32)
- asm.cmp(:rax, 8) # CMP r/m64, imm8 (Mod 11: reg)
- asm.cmp(:rax, 0x100) # CMP r/m64, imm32 (Mod 11: reg)
- asm.cmp([:rax, 8], :rbx) # CMP r/m64, r64 (Mod 01: [reg]+disp8)
- asm.cmp([:rax, -0x100], :rbx) # CMP r/m64, r64 (Mod 10: [reg]+disp32)
- asm.cmp(:rax, :rbx) # CMP r/m64, r64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: cmp byte ptr [rax + 8], 8
- 0x4: cmp dword ptr [rax + 8], 0x100
- 0xb: cmp qword ptr [rax + 8], 8
- 0x10: cmp qword ptr [rbx + 8], 0x100
- 0x18: cmp qword ptr [rax + 0x100], 8
- 0x20: cmp rax, 8
- 0x24: cmp rax, 0x100
- 0x2b: cmp qword ptr [rax + 8], rbx
- 0x2f: cmp qword ptr [rax - 0x100], rbx
- 0x36: cmp rax, rbx
- EOS
- end
-
- def test_jbe
- asm = Assembler.new
- asm.jbe(rel32(0xff)) # JBE rel32
- assert_compile(asm, <<~EOS)
- 0x0: jbe 0xff
- EOS
- end
-
- def test_je
- asm = Assembler.new
- asm.je(rel32(0xff)) # JE rel32
- assert_compile(asm, <<~EOS)
- 0x0: je 0xff
- EOS
- end
-
- def test_jl
- asm = Assembler.new
- asm.jl(rel32(0xff)) # JL rel32
- assert_compile(asm, <<~EOS)
- 0x0: jl 0xff
- EOS
- end
-
- def test_jmp
- asm = Assembler.new
- label = asm.new_label('label')
- asm.jmp(label) # JZ rel8
- asm.write_label(label)
- asm.jmp(rel32(0xff)) # JMP rel32
- asm.jmp([:rax, 8]) # JMP r/m64 (Mod 01: [reg]+disp8)
- asm.jmp(:rax) # JMP r/m64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: jmp 2
- 0x2: jmp 0xff
- 0x7: jmp qword ptr [rax + 8]
- 0xa: jmp rax
- EOS
- end
-
- def test_jne
- asm = Assembler.new
- asm.jne(rel32(0xff)) # JNE rel32
- assert_compile(asm, <<~EOS)
- 0x0: jne 0xff
- EOS
- end
-
- def test_jnz
- asm = Assembler.new
- asm.jnz(rel32(0xff)) # JNZ rel32
- assert_compile(asm, <<~EOS)
- 0x0: jne 0xff
- EOS
- end
-
- def test_jo
- asm = Assembler.new
- asm.jo(rel32(0xff)) # JO rel32
- assert_compile(asm, <<~EOS)
- 0x0: jo 0xff
- EOS
- end
-
- def test_jz
- asm = Assembler.new
- asm.jz(rel32(0xff)) # JZ rel32
- assert_compile(asm, <<~EOS)
- 0x0: je 0xff
- EOS
- end
-
- def test_lea
- asm = Assembler.new
- asm.lea(:rax, [:rax, 8]) # LEA r64,m (Mod 01: [reg]+disp8)
- asm.lea(:rax, [:rax, 0xffff]) # LEA r64,m (Mod 10: [reg]+disp32)
- assert_compile(asm, <<~EOS)
- 0x0: lea rax, [rax + 8]
- 0x4: lea rax, [rax + 0xffff]
- EOS
- end
-
- def test_mov
- asm = Assembler.new
- asm.mov(:eax, DwordPtr[:rbx, 8]) # MOV r32 r/m32 (Mod 01: [reg]+disp8)
- asm.mov(:eax, 0x100) # MOV r32, imm32 (Mod 11: reg)
- asm.mov(:rax, [:rbx]) # MOV r64, r/m64 (Mod 00: [reg])
- asm.mov(:rax, [:rbx, 8]) # MOV r64, r/m64 (Mod 01: [reg]+disp8)
- asm.mov(:rax, [:rbx, 0x100]) # MOV r64, r/m64 (Mod 10: [reg]+disp32)
- asm.mov(:rax, :rbx) # MOV r64, r/m64 (Mod 11: reg)
- asm.mov(:rax, 0x100) # MOV r/m64, imm32 (Mod 11: reg)
- asm.mov(:rax, 0x100000000) # MOV r64, imm64
- asm.mov(DwordPtr[:rax, 8], 0x100) # MOV r/m32, imm32 (Mod 01: [reg]+disp8)
- asm.mov([:rax], 0x100) # MOV r/m64, imm32 (Mod 00: [reg])
- asm.mov([:rax], :rbx) # MOV r/m64, r64 (Mod 00: [reg])
- asm.mov([:rax, 8], 0x100) # MOV r/m64, imm32 (Mod 01: [reg]+disp8)
- asm.mov([:rax, 8], :rbx) # MOV r/m64, r64 (Mod 01: [reg]+disp8)
- asm.mov([:rax, 0x100], 0x100) # MOV r/m64, imm32 (Mod 10: [reg]+disp32)
- asm.mov([:rax, 0x100], :rbx) # MOV r/m64, r64 (Mod 10: [reg]+disp32)
- assert_compile(asm, <<~EOS)
- 0x0: mov eax, dword ptr [rbx + 8]
- 0x3: mov eax, 0x100
- 0x8: mov rax, qword ptr [rbx]
- 0xb: mov rax, qword ptr [rbx + 8]
- 0xf: mov rax, qword ptr [rbx + 0x100]
- 0x16: mov rax, rbx
- 0x19: mov rax, 0x100
- 0x20: movabs rax, 0x100000000
- 0x2a: mov dword ptr [rax + 8], 0x100
- 0x31: mov qword ptr [rax], 0x100
- 0x38: mov qword ptr [rax], rbx
- 0x3b: mov qword ptr [rax + 8], 0x100
- 0x43: mov qword ptr [rax + 8], rbx
- 0x47: mov qword ptr [rax + 0x100], 0x100
- 0x52: mov qword ptr [rax + 0x100], rbx
- EOS
- end
-
- def test_or
- asm = Assembler.new
- asm.or(:rax, 0) # OR r/m64, imm8 (Mod 11: reg)
- asm.or(:rax, 0xffff) # OR r/m64, imm32 (Mod 11: reg)
- asm.or(:rax, [:rbx, 8]) # OR r64, r/m64 (Mod 01: [reg]+disp8)
- assert_compile(asm, <<~EOS)
- 0x0: or rax, 0
- 0x4: or rax, 0xffff
- 0xb: or rax, qword ptr [rbx + 8]
- EOS
- end
-
- def test_push
- asm = Assembler.new
- asm.push(:rax) # PUSH r64
- assert_compile(asm, <<~EOS)
- 0x0: push rax
- EOS
- end
-
- def test_pop
- asm = Assembler.new
- asm.pop(:rax) # POP r64
- assert_compile(asm, <<~EOS)
- 0x0: pop rax
- EOS
- end
-
- def test_ret
- asm = Assembler.new
- asm.ret # RET
- assert_compile(asm, "0x0: ret \n")
- end
-
- def test_sar
- asm = Assembler.new
- asm.sar(:rax, 0) # SAR r/m64, imm8 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: sar rax, 0
- EOS
- end
-
- def test_sub
- asm = Assembler.new
- asm.sub(:rax, 8) # SUB r/m64, imm8 (Mod 11: reg)
- asm.sub(:rax, :rbx) # SUB r/m64, r64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: sub rax, 8
- 0x4: sub rax, rbx
- EOS
- end
-
- def test_test
- asm = Assembler.new
- asm.test(BytePtr[:rax, 8], 16) # TEST r/m8*, imm8 (Mod 01: [reg]+disp8)
- asm.test([:rax, 8], 8) # TEST r/m64, imm32 (Mod 01: [reg]+disp8)
- asm.test([:rax, 0xffff], 0xffff) # TEST r/m64, imm32 (Mod 10: [reg]+disp32)
- asm.test(:rax, 0xffff) # TEST r/m64, imm32 (Mod 11: reg)
- asm.test(:eax, :ebx) # TEST r/m32, r32 (Mod 11: reg)
- asm.test(:rax, :rbx) # TEST r/m64, r64 (Mod 11: reg)
- assert_compile(asm, <<~EOS)
- 0x0: test byte ptr [rax + 8], 0x10
- 0x4: test qword ptr [rax + 8], 8
- 0xc: test qword ptr [rax + 0xffff], 0xffff
- 0x17: test rax, 0xffff
- 0x1e: test eax, ebx
- 0x20: test rax, rbx
- EOS
- end
-
- def test_xor
- asm = Assembler.new
- asm.xor(:rax, :rbx)
- assert_compile(asm, <<~EOS)
- 0x0: xor rax, rbx
- EOS
- end
-
- private
-
- def rel32(offset)
- @cb.write_addr + 0xff
- end
-
- def assert_compile(asm, expected)
- actual = compile(asm)
- assert_equal expected, actual, "---\n#{actual}---"
- end
-
- def compile(asm)
- start_addr = @cb.write_addr
- @cb.write(asm)
- end_addr = @cb.write_addr
-
- io = StringIO.new
- @cb.dump_disasm(start_addr, end_addr, io:, color: false, test: true)
- io.seek(0)
- disasm = io.read
-
- disasm.gsub!(/^ /, '')
- disasm.sub!(/\n\z/, '')
- disasm
- end
- end
-end
diff --git a/test/ruby/sentence.rb b/test/ruby/sentence.rb
index 9bfd7c7599..99ced05d2f 100644
--- a/test/ruby/sentence.rb
+++ b/test/ruby/sentence.rb
@@ -211,7 +211,7 @@ class Sentence
# returns new sentence object which
# _target_ is substituted by the block.
#
- # Sentence#subst invokes <tt>_target_ === _string_</tt> for each
+ # Sentence#subst invokes <tt>target === string</tt> for each
# string in the sentence.
# The strings which === returns true are substituted by the block.
# The block is invoked with the substituting string.
diff --git a/test/ruby/test_alias.rb b/test/ruby/test_alias.rb
index 6d4fcc085b..539cd49488 100644
--- a/test/ruby/test_alias.rb
+++ b/test/ruby/test_alias.rb
@@ -328,4 +328,17 @@ class TestAlias < Test::Unit::TestCase
}
end;
end
+
+ def test_undef_method_error_message_with_zsuper_method
+ modules = [
+ Module.new { private :class },
+ Module.new { prepend Module.new { private :class } },
+ ]
+ message = "undefined method 'class' for module '%s'"
+ modules.each do |mod|
+ assert_raise_with_message(NameError, message % mod) do
+ mod.alias_method :xyz, :class
+ end
+ end
+ end
end
diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb
index 9ba01dfcf9..90d7c04f9b 100644
--- a/test/ruby/test_allocation.rb
+++ b/test/ruby/test_allocation.rb
@@ -2,6 +2,12 @@
require 'test/unit'
class TestAllocation < Test::Unit::TestCase
+ def setup
+ # The namespace changes on i686 platform triggers a bug to allocate objects unexpectedly.
+ # For now, skip these tests only on i686
+ pend if RUBY_PLATFORM =~ /^i686/
+ end
+
def munge_checks(checks)
checks
end
@@ -60,9 +66,7 @@ class TestAllocation < Test::Unit::TestCase
#{checks}
- unless failures.empty?
- assert_equal(true, false, failures.join("\n"))
- end
+ assert_empty(failures)
RUBY
end
@@ -94,13 +98,14 @@ class TestAllocation < Test::Unit::TestCase
def block
''
end
+ alias only_block block
def test_no_parameters
- only_block = block.empty? ? block : block[2..]
check_allocations(<<~RUBY)
def self.none(#{only_block}); end
check_allocations(0, 0, "none(#{only_block})")
+ check_allocations(0, 0, "none(*nil#{block})")
check_allocations(0, 0, "none(*empty_array#{block})")
check_allocations(0, 0, "none(**empty_hash#{block})")
check_allocations(0, 0, "none(*empty_array, **empty_hash#{block})")
@@ -156,6 +161,9 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 0, "optional(*r2k_empty_array1#{block})")
check_allocations(0, 1, "optional(*r2k_array#{block})")
+ check_allocations(0, 0, "optional(*empty_array#{block})")
+ check_allocations(0, 0, "optional(*nil#{block})")
+ check_allocations(0, 0, "optional(#{only_block})")
check_allocations(0, 1, "optional(*empty_array, **hash1, **empty_hash#{block})")
RUBY
end
@@ -179,6 +187,8 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 0, "splat(1, *array1, **empty_hash#{block})")
check_allocations(1, 0, "splat(1, *array1, *empty_array, **empty_hash#{block})")
+ check_allocations(1, 0, "splat(*nil#{block})")
+ check_allocations(1, 0, "splat(#{only_block})")
check_allocations(1, 1, "splat(**hash1#{block})")
check_allocations(1, 1, "splat(**hash1, **empty_hash#{block})")
@@ -196,6 +206,7 @@ class TestAllocation < Test::Unit::TestCase
def self.req_splat(x, *y#{block}); end
check_allocations(1, 0, "req_splat(1#{block})")
+ check_allocations(1, 0, "req_splat(1, *nil#{block})")
check_allocations(1, 0, "req_splat(1, *empty_array#{block})")
check_allocations(1, 0, "req_splat(1, **empty_hash#{block})")
check_allocations(1, 0, "req_splat(1, *empty_array, **empty_hash#{block})")
@@ -226,6 +237,7 @@ class TestAllocation < Test::Unit::TestCase
def self.splat_post(*x, y#{block}); end
check_allocations(1, 0, "splat_post(1#{block})")
+ check_allocations(1, 0, "splat_post(1, *nil#{block})")
check_allocations(1, 0, "splat_post(1, *empty_array#{block})")
check_allocations(1, 0, "splat_post(1, **empty_hash#{block})")
check_allocations(1, 0, "splat_post(1, *empty_array, **empty_hash#{block})")
@@ -267,6 +279,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})")
check_allocations(0, 1, "keyword(**empty_hash, **hash1#{block})")
+ check_allocations(0, 0, "keyword(*nil#{block})")
check_allocations(0, 0, "keyword(*empty_array#{block})")
check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})")
@@ -294,6 +307,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})")
check_allocations(0, 1, "keyword_splat(**empty_hash, **hash1#{block})")
+ check_allocations(0, 1, "keyword_splat(*nil#{block})")
check_allocations(0, 1, "keyword_splat(*empty_array#{block})")
check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
@@ -321,6 +335,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})")
check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, **hash1#{block})")
+ check_allocations(0, 1, "keyword_and_keyword_splat(*nil#{block})")
check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})")
check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})")
@@ -348,6 +363,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "required_and_keyword(1, **empty_hash, **hash1#{block})")
+ check_allocations(0, 0, "required_and_keyword(1, *nil#{block})")
check_allocations(0, 0, "required_and_keyword(1, *empty_array#{block})")
check_allocations(1, 0, "required_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})")
@@ -391,6 +407,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})")
check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, **hash1#{block})")
+ check_allocations(1, 0, "splat_and_keyword(1, *nil#{block})")
check_allocations(1, 0, "splat_and_keyword(1, *empty_array#{block})")
check_allocations(1, 0, "splat_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})")
@@ -436,6 +453,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, **hash1#{block})")
+ check_allocations(0, 1, "required_and_keyword_splat(1, *nil#{block})")
check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array#{block})")
check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
@@ -479,6 +497,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})")
check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, **hash1#{block})")
+ check_allocations(1, 1, "splat_and_keyword_splat(1, *nil#{block})")
check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array#{block})")
check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
@@ -508,7 +527,61 @@ class TestAllocation < Test::Unit::TestCase
RUBY
end
+ def test_anonymous_splat_parameter
+ only_block = block.empty? ? block : block[2..]
+ check_allocations(<<~RUBY)
+ def self.anon_splat(*#{block}); end
+
+ check_allocations(1, 1, "anon_splat(1, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(1, a:2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **empty_hash, a: 2#{block})")
+
+ check_allocations(1, 0, "anon_splat(1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat(1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(1, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, **empty_hash, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat(1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat(1, *empty_array, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(*array1, a: 2#{block})")
+
+ check_allocations(0, 0, "anon_splat(*nil, **nill#{block})")
+ check_allocations(0, 0, "anon_splat(*array1, **nill#{block})")
+ check_allocations(0, 0, "anon_splat(*array1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **hash1#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1#{block})")
+
+ check_allocations(1, 0, "anon_splat(*array1, *empty_array#{block})")
+ check_allocations(1, 0, "anon_splat(*array1, *empty_array, **empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+
+ check_allocations(0, 0, "anon_splat(#{only_block})")
+ check_allocations(1, 1, "anon_splat(a: 2#{block})")
+ check_allocations(0, 0, "anon_splat(**empty_hash#{block})")
+
+ check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **empty_hash, a: 2#{block})")
+ check_allocations(1, 1, "anon_splat(*array1, **hash1, **empty_hash#{block})")
+
+ unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
+ check_allocations(0, 0, "anon_splat(*array1, **nil#{block})")
+ check_allocations(1, 0, "anon_splat(*r2k_empty_array#{block})")
+ check_allocations(1, 1, "anon_splat(*r2k_array#{block})")
+ check_allocations(1, 0, "anon_splat(*r2k_empty_array1#{block})")
+ check_allocations(1, 1, "anon_splat(*r2k_array1#{block})")
+ end
+ RUBY
+ end
+
def test_anonymous_splat_and_anonymous_keyword_splat_parameters
+ only_block = block.empty? ? block : block[2..]
check_allocations(<<~RUBY)
def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end
@@ -529,6 +602,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*nil, **nill#{block})")
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})")
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})")
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})")
@@ -540,6 +614,10 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(#{only_block})")
+ check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(a: 2#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(**empty_hash#{block})")
+
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
@@ -554,6 +632,7 @@ class TestAllocation < Test::Unit::TestCase
end
def test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters
+ only_block = block.empty? ? block : block[2..]
check_allocations(<<~RUBY)
def self.t(*, **#{block}); end
def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) end
@@ -575,6 +654,7 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*nil, **nill#{block})")
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})")
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})")
check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})")
@@ -586,6 +666,10 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(#{only_block})")
+ check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(a: 2#{block})")
+ check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(**empty_hash#{block})")
+
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})")
check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})")
check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})")
@@ -620,6 +704,8 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})")
+ check_allocations(0, 0, "argument_forwarding(**nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*nil, **nill#{block})")
check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})")
check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})")
check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})")
@@ -666,6 +752,8 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})")
+ check_allocations(0, 0, "argument_forwarding(**nill#{block})")
+ check_allocations(0, 0, "argument_forwarding(*nil, **nill#{block})")
check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})")
check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})")
check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})")
@@ -712,6 +800,8 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 1, "r2k(*array1, a: 2#{block})")
+ check_allocations(1, 0, "r2k(**nill#{block})")
+ check_allocations(1, 0, "r2k(*nil, **nill#{block})")
check_allocations(1, 0, "r2k(*array1, **nill#{block})")
check_allocations(1, 0, "r2k(*array1, **empty_hash#{block})")
check_allocations(1, 1, "r2k(*array1, **hash1#{block})")
@@ -730,9 +820,9 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(1, 0, "r2k(*array1, **nil#{block})")
check_allocations(1, 0, "r2k(*r2k_empty_array#{block})")
- check_allocations(1, 1, "r2k(*r2k_array#{block})")
unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
# YJIT may or may not allocate depending on arch?
+ check_allocations(1, 1, "r2k(*r2k_array#{block})")
check_allocations(1, 0, "r2k(*r2k_empty_array1#{block})")
check_allocations(1, 1, "r2k(*r2k_array1#{block})")
end
@@ -742,13 +832,16 @@ class TestAllocation < Test::Unit::TestCase
def test_no_array_allocation_with_splat_and_nonstatic_keywords
check_allocations(<<~RUBY)
def self.keyword(a: nil, b: nil#{block}); end
+ def self.Object; Object end
+ check_allocations(0, 1, "keyword(*nil, a: empty_array#{block})") # LVAR
check_allocations(0, 1, "keyword(*empty_array, a: empty_array#{block})") # LVAR
check_allocations(0, 1, "->{keyword(*empty_array, a: empty_array#{block})}.call") # DVAR
check_allocations(0, 1, "$x = empty_array; keyword(*empty_array, a: $x#{block})") # GVAR
check_allocations(0, 1, "@x = empty_array; keyword(*empty_array, a: @x#{block})") # IVAR
check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword(*empty_array, a: X#{block})") # CONST
- check_allocations(0, 1, "keyword(*empty_array, a: Object::X#{block})") # COLON2
+ check_allocations(0, 1, "keyword(*empty_array, a: Object::X#{block})") # COLON2 - safe
+ check_allocations(1, 1, "keyword(*empty_array, a: Object()::X#{block})") # COLON2 - unsafe
check_allocations(0, 1, "keyword(*empty_array, a: ::X#{block})") # COLON3
check_allocations(0, 1, "T = self; #{'B = block' unless block.empty?}; class Object; @@x = X; T.keyword(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR
check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1#{block})") # INTEGER
@@ -765,6 +858,13 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword(*empty_array, a: ->{}#{block})") # LAMBDA
check_allocations(0, 1, "keyword(*empty_array, a: $1#{block})") # NTH_REF
check_allocations(0, 1, "keyword(*empty_array, a: $`#{block})") # BACK_REF
+
+ # LIST: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array)
+ check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c]#{block})")
+ check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x]#{block})")
+ # LIST unsafe: 2 (one for [Object()] and one for *empty_array)
+ check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [Object()]#{block})")
+ check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})")
RUBY
end
@@ -772,6 +872,9 @@ class TestAllocation < Test::Unit::TestCase
def block
', &block'
end
+ def only_block
+ '&block'
+ end
end
end
@@ -807,13 +910,15 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(<<~RUBY)
keyword = keyword = proc{ |a: nil, b: nil #{block}| }
+ def self.Object; Object end
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array#{block})") # LVAR
check_allocations(0, 1, "->{keyword.(*empty_array, a: empty_array#{block})}.call") # DVAR
check_allocations(0, 1, "$x = empty_array; keyword.(*empty_array, a: $x#{block})") # GVAR
check_allocations(0, 1, "@x = empty_array; keyword.(*empty_array, a: @x#{block})") # IVAR
check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword.(*empty_array, a: X#{block})") # CONST
- check_allocations(0, 1, "keyword.(*empty_array, a: Object::X#{block})") # COLON2
+ check_allocations(0, 1, "keyword.(*empty_array, a: Object::X#{block})") # COLON2 - safe
+ check_allocations(1, 1, "keyword.(*empty_array, a: Object()::X#{block})") # COLON2 - unsafe
check_allocations(0, 1, "keyword.(*empty_array, a: ::X#{block})") # COLON3
check_allocations(0, 1, "T = keyword; #{'B = block' unless block.empty?}; class Object; @@x = X; T.(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR
check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1#{block})") # INTEGER
@@ -830,6 +935,13 @@ class TestAllocation < Test::Unit::TestCase
check_allocations(0, 1, "keyword.(*empty_array, a: ->{}#{block})") # LAMBDA
check_allocations(0, 1, "keyword.(*empty_array, a: $1#{block})") # NTH_REF
check_allocations(0, 1, "keyword.(*empty_array, a: $`#{block})") # BACK_REF
+
+ # LIST safe: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array)
+ check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c]#{block})")
+ check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x]#{block})")
+ # LIST unsafe: 2 (one for [:c] and one for *empty_array)
+ check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [Object()]#{block})")
+ check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})")
RUBY
end
@@ -837,6 +949,9 @@ class TestAllocation < Test::Unit::TestCase
def block
', &block'
end
+ def only_block
+ '&block'
+ end
end
end
end
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index 797ae95e97..76455187a5 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -1309,32 +1309,7 @@ class TestArray < Test::Unit::TestCase
assert_equal(ary.join(':'), ary2.join(':'))
assert_not_nil(x =~ /def/)
-=begin
- skipping "Not tested:
- D,d & double-precision float, native format\\
- E & double-precision float, little-endian byte order\\
- e & single-precision float, little-endian byte order\\
- F,f & single-precision float, native format\\
- G & double-precision float, network (big-endian) byte order\\
- g & single-precision float, network (big-endian) byte order\\
- I & unsigned integer\\
- i & integer\\
- L & unsigned long\\
- l & long\\
-
- N & long, network (big-endian) byte order\\
- n & short, network (big-endian) byte-order\\
- P & pointer to a structure (fixed-length string)\\
- p & pointer to a null-terminated string\\
- S & unsigned short\\
- s & short\\
- V & long, little-endian byte order\\
- v & short, little-endian byte order\\
- X & back up a byte\\
- x & null byte\\
- Z & ASCII string (null padded, count is width)\\
-"
-=end
+ # more comprehensive tests are in test_pack.rb
end
def test_pack_with_buffer
@@ -1361,6 +1336,28 @@ class TestArray < Test::Unit::TestCase
assert_equal(@cls[@cls[1,2], nil, 'dog', 'cat'], a.prepend(@cls[1, 2]))
end
+ def test_tolerant_to_redefinition
+ *code = __FILE__, __LINE__+1, "#{<<-"{#"}\n#{<<-'};'}"
+ {#
+ module M
+ def <<(a)
+ super(a * 2)
+ end
+ end
+ class Array; prepend M; end
+ ary = [*1..10]
+ mapped = ary.map {|i| i}
+ selected = ary.select {true}
+ module M
+ remove_method :<<
+ end
+ assert_equal(ary, mapped)
+ assert_equal(ary, selected)
+ };
+ assert_separately(%w[--disable-yjit], *code)
+ assert_separately(%w[--enable-yjit], *code)
+ end
+
def test_push
a = @cls[1, 2, 3]
assert_equal(@cls[1, 2, 3, 4, 5], a.push(4, 5))
@@ -1849,19 +1846,21 @@ class TestArray < Test::Unit::TestCase
assert_equal([1, 2, 3, 4], a)
end
- def test_freeze_inside_sort!
+ def test_freeze_inside_sort_bang
array = [1, 2, 3, 4, 5]
frozen_array = nil
assert_raise(FrozenError) do
count = 0
array.sort! do |a, b|
- array.freeze if (count += 1) == 6
+ array.freeze if (count += 1) == 3
frozen_array ||= array.map.to_a if array.frozen?
b <=> a
end
end
assert_equal(frozen_array, array)
+ end
+ def test_freeze_inside_sort_bang_non_numeric_block
object = Object.new
array = [1, 2, 3, 4, 5]
object.define_singleton_method(:>){|_| array.freeze; true}
@@ -1870,7 +1869,9 @@ class TestArray < Test::Unit::TestCase
object
end
end
+ end
+ def test_freeze_inside_sort_bang_non_numeric_no_block
object = Object.new
array = [object, object]
object.define_singleton_method(:>){|_| array.freeze; true}
@@ -2716,6 +2717,18 @@ class TestArray < Test::Unit::TestCase
assert_equal(2, [0, 1].fetch(2, 2))
end
+ def test_fetch_values
+ ary = @cls[1, 2, 3]
+ assert_equal([], ary.fetch_values())
+ assert_equal([1], ary.fetch_values(0))
+ assert_equal([3, 1, 3], ary.fetch_values(2, 0, -1))
+ assert_raise(TypeError) {ary.fetch_values("")}
+ assert_raise(IndexError) {ary.fetch_values(10)}
+ assert_raise(IndexError) {ary.fetch_values(-20)}
+ assert_equal(["10 not found"], ary.fetch_values(10) {|i| "#{i} not found"})
+ assert_equal(["10 not found", 3], ary.fetch_values(10, 2) {|i| "#{i} not found"})
+ end
+
def test_index2
a = [0, 1, 2]
assert_equal(a, a.index.to_a)
@@ -3032,13 +3045,12 @@ class TestArray < Test::Unit::TestCase
end
end
- def test_shuffle_random
- gen = proc do
- 10000000
- end
- class << gen
- alias rand call
- end
+ def test_shuffle_random_out_of_range
+ gen = random_generator {10000000}
+ assert_raise(RangeError) {
+ [*0..2].shuffle(random: gen)
+ }
+ gen = random_generator {-1}
assert_raise(RangeError) {
[*0..2].shuffle(random: gen)
}
@@ -3046,27 +3058,16 @@ class TestArray < Test::Unit::TestCase
def test_shuffle_random_clobbering
ary = (0...10000).to_a
- gen = proc do
+ gen = random_generator do
ary.replace([])
0.5
end
- class << gen
- alias rand call
- end
assert_raise(RuntimeError) {ary.shuffle!(random: gen)}
end
def test_shuffle_random_zero
- zero = Object.new
- def zero.to_int
- 0
- end
- gen_to_int = proc do |max|
- zero
- end
- class << gen_to_int
- alias rand call
- end
+ zero = Struct.new(:to_int).new(0)
+ gen_to_int = random_generator {|max| zero}
ary = (0...10000).to_a
assert_equal(ary.rotate, ary.shuffle(random: gen_to_int))
end
@@ -3134,19 +3135,11 @@ class TestArray < Test::Unit::TestCase
def test_sample_random_generator
ary = (0...10000).to_a
assert_raise(ArgumentError) {ary.sample(1, 2, random: nil)}
- gen0 = proc do |max|
- max/2
- end
- class << gen0
- alias rand call
- end
- gen1 = proc do |max|
+ gen0 = random_generator {|max| max/2}
+ gen1 = random_generator do |max|
ary.replace([])
max/2
end
- class << gen1
- alias rand call
- end
assert_equal(5000, ary.sample(random: gen0))
assert_nil(ary.sample(random: gen1))
assert_equal([], ary)
@@ -3177,20 +3170,23 @@ class TestArray < Test::Unit::TestCase
end
def test_sample_random_generator_half
- half = Object.new
- def half.to_int
- 5000
- end
- gen_to_int = proc do |max|
- half
- end
- class << gen_to_int
- alias rand call
- end
+ half = Struct.new(:to_int).new(5000)
+ gen_to_int = random_generator {|max| half}
ary = (0...10000).to_a
assert_equal(5000, ary.sample(random: gen_to_int))
end
+ def test_sample_random_out_of_range
+ gen = random_generator {10000000}
+ assert_raise(RangeError) {
+ [*0..2].sample(random: gen)
+ }
+ gen = random_generator {-1}
+ assert_raise(RangeError) {
+ [*0..2].sample(random: gen)
+ }
+ end
+
def test_sample_random_invalid_generator
ary = (0..10).to_a
assert_raise(NoMethodError) {
@@ -3554,6 +3550,7 @@ class TestArray < Test::Unit::TestCase
assert_float_equal(3.5, [3].sum(0.5))
assert_float_equal(8.5, [3.5, 5].sum)
assert_float_equal(10.5, [2, 8.5].sum)
+ assert_float_equal(1_000 * 0.1, Array.new(1_000, 0.1).sum(0.0))
assert_float_equal((FIXNUM_MAX+1).to_f, [FIXNUM_MAX, 1, 0.0].sum)
assert_float_equal((FIXNUM_MAX+1).to_f, [0.0, FIXNUM_MAX+1].sum)
@@ -3614,6 +3611,23 @@ class TestArray < Test::Unit::TestCase
assert_equal((1..67).to_a.reverse, var_0)
end
+ def test_find
+ ary = [1, 2, 3, 4, 5]
+ assert_equal(2, ary.find {|x| x % 2 == 0 })
+ assert_equal(nil, ary.find {|x| false })
+ assert_equal(:foo, ary.find(proc { :foo }) {|x| false })
+ end
+
+ def test_rfind
+ ary = [1, 2, 3, 4, 5]
+ assert_equal(4, ary.rfind {|x| x % 2 == 0 })
+ assert_equal(1, ary.rfind {|x| x < 2 })
+ assert_equal(5, ary.rfind {|x| x > 4 })
+ assert_equal(nil, ary.rfind {|x| false })
+ assert_equal(:foo, ary.rfind(proc { :foo }) {|x| false })
+ assert_equal(nil, ary.rfind {|x| ary.clear; false })
+ end
+
private
def need_continuation
unless respond_to?(:callcc, true)
@@ -3621,6 +3635,13 @@ class TestArray < Test::Unit::TestCase
end
omit 'requires callcc support' unless respond_to?(:callcc, true)
end
+
+ def random_generator(&block)
+ class << block
+ alias rand call
+ end
+ block
+ end
end
class TestArraySubclass < TestArray
diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb
index 68db0df6a2..8b9a3f615d 100644
--- a/test/ruby/test_ast.rb
+++ b/test/ruby/test_ast.rb
@@ -48,7 +48,7 @@ class TestAst < Test::Unit::TestCase
@path = path
@errors = []
@debug = false
- @ast = RubyVM::AbstractSyntaxTree.parse(src) if src
+ @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse(src) } if src
end
def validate_range
@@ -67,7 +67,7 @@ class TestAst < Test::Unit::TestCase
def ast
return @ast if defined?(@ast)
- @ast = RubyVM::AbstractSyntaxTree.parse_file(@path)
+ @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file(@path) }
end
private
@@ -135,7 +135,7 @@ class TestAst < Test::Unit::TestCase
Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
define_method("test_all_tokens:#{path}") do
- node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true)
+ node = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true) }
tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] }
tokens_bytes = tokens.map { _1[2]}.join.bytes
source_bytes = File.read("#{SRCDIR}/#{path}").bytes
@@ -215,6 +215,17 @@ class TestAst < Test::Unit::TestCase
end
end
+ def test_cdecl_children_with_toplevel_constant_path
+ # [Bug #21974]
+ children = parse("::Foo = 1").children[2].children
+
+ assert_equal(:COLON3, children[0].type)
+ assert_equal([:Foo], children[0].children)
+ assert_equal(:Foo, children[1])
+ assert_equal(:INTEGER, children[2].type)
+ assert_equal([1], children[2].children)
+ end
+
def assert_parse(code, warning: '')
node = assert_warning(warning) {RubyVM::AbstractSyntaxTree.parse(code)}
assert_kind_of(RubyVM::AbstractSyntaxTree::Node, node, code)
@@ -244,7 +255,8 @@ class TestAst < Test::Unit::TestCase
assert_invalid_parse(msg, "#{code}")
assert_invalid_parse(msg, "def m; #{code}; end")
assert_invalid_parse(msg, "begin; #{code}; end")
- assert_parse("END {#{code}}")
+ assert_invalid_parse(msg, "BEGIN {#{code}}")
+ assert_invalid_parse(msg, "END {#{code}}")
assert_parse("!defined?(#{code})")
assert_parse("def m; defined?(#{code}); end")
@@ -337,6 +349,19 @@ class TestAst < Test::Unit::TestCase
assert_parse("END {defined? yield}")
end
+ def test_invalid_yield_no_memory_leak
+ # [Bug #21383]
+ assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ code = proc do
+ eval("class C; yield; end")
+ rescue SyntaxError
+ end
+ 1_000.times(&code)
+ begin;
+ 100_000.times(&code)
+ end;
+ end
+
def test_node_id_for_location
omit if ParserSupport.prism_enabled?
@@ -352,6 +377,50 @@ class TestAst < Test::Unit::TestCase
assert_equal node.node_id, node_id
end
+ def add(x, y)
+ end
+
+ def test_node_id_for_backtrace_location_of_method_definition
+ omit if ParserSupport.prism_enabled?
+
+ begin
+ add(1)
+ rescue ArgumentError => exc
+ loc = exc.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(method(:add))
+ assert_equal node.node_id, node_id
+ end
+ end
+
+ def test_node_id_for_backtrace_location_of_lambda
+ omit if ParserSupport.prism_enabled?
+
+ v = -> {}
+ begin
+ v.call(1)
+ rescue ArgumentError => exc
+ loc = exc.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(v)
+ assert_equal node.node_id, node_id
+ end
+ end
+
+ def test_node_id_for_backtrace_location_of_lambda_method
+ omit if ParserSupport.prism_enabled?
+
+ v = lambda {}
+ begin
+ v.call(1)
+ rescue ArgumentError => exc
+ loc = exc.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(v)
+ assert_equal node.node_id, node_id
+ end
+ end
+
def test_node_id_for_backtrace_location_raises_argument_error
bug19262 = '[ruby-core:111435]'
@@ -652,6 +721,7 @@ class TestAst < Test::Unit::TestCase
assert_equal(nil, block_arg.call(''))
assert_equal(:block, block_arg.call('&block'))
assert_equal(:&, block_arg.call('&'))
+ assert_equal(false, block_arg.call('&nil'))
end
def test_keyword_rest
@@ -788,7 +858,7 @@ dummy
node_proc = RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: true)
node_method = RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: true)
- assert_equal("{ 1 + 2 }", node_proc.source)
+ assert_equal("Proc.new { 1 + 2 }", node_proc.source)
assert_equal("def test_keep_script_lines_for_of\n", node_method.source.lines.first)
end
@@ -865,7 +935,7 @@ dummy
omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"],
- "", [":SCOPE"], [])
+ "", [":DEFN"], [])
end
def test_error_tolerant
@@ -1173,7 +1243,7 @@ dummy
args: nil
body:
(LAMBDA@1:0-2:3
- (SCOPE@1:2-2:3
+ (SCOPE@1:0-2:3
tbl: []
args:
(ARGS@1:2-1:2
@@ -1376,6 +1446,40 @@ dummy
assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]])
end
+ def test_class_locations
+ node = ast_parse("class A end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 0, 1, 5], nil, [1, 8, 1, 11]])
+
+ node = ast_parse("class A < B; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 16], [1, 0, 1, 5], [1, 8, 1, 9], [1, 13, 1, 16]])
+ end
+
+ def test_colon2_locations
+ node = ast_parse("A::B")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]])
+
+ node = ast_parse("A::B::C")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 4, 1, 6], [1, 6, 1, 7]])
+ assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]])
+ end
+
+ def test_colon3_locations
+ node = ast_parse("::A")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]])
+
+ node = ast_parse("::A::B")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 3, 1, 5], [1, 5, 1, 6]])
+ assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]])
+ end
+
+ def test_defined_locations
+ node = ast_parse("defined? x")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 0, 1, 8]])
+
+ node = ast_parse("defined?(x)")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 0, 1, 8]])
+ end
+
def test_dot2_locations
node = ast_parse("1..2")
assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3]])
@@ -1444,6 +1548,11 @@ dummy
assert_locations(node.children[-1].locations, [[1, 0, 1, 20], [1, 0, 1, 2], [1, 10, 1, 12], [1, 17, 1, 20]])
end
+ def test_module_locations
+ node = ast_parse('module A end')
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 12], [1, 0, 1, 6], [1, 9, 1, 12]])
+ end
+
def test_if_locations
node = ast_parse("if cond then 1 else 2 end")
assert_locations(node.children[-1].locations, [[1, 0, 1, 25], [1, 0, 1, 2], [1, 8, 1, 12], [1, 22, 1, 25]])
@@ -1462,6 +1571,20 @@ dummy
assert_locations(node.children[-1].children[1].children[0].locations, [[1, 11, 1, 17], [1, 13, 1, 15], nil, nil])
end
+ def test_in_locations
+ node = ast_parse("case 1; in 2 then 3; end")
+ assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 20], [1, 8, 1, 10], [1, 13, 1, 17], nil])
+
+ node = ast_parse("1 => a")
+ assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], nil, nil, [1, 2, 1, 4]])
+
+ node = ast_parse("1 in a")
+ assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], [1, 2, 1, 4], nil, nil])
+
+ node = ast_parse("case 1; in 2; 3; end")
+ assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 16], [1, 8, 1, 10], [1, 12, 1, 13], nil])
+ end
+
def test_next_locations
node = ast_parse("loop { next 1 }")
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 13], [1, 7, 1, 11]])
@@ -1497,6 +1620,14 @@ dummy
assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 15], [1, 8, 1, 9], [1, 9, 1, 10], [1, 11, 1, 13]])
end
+ def test_postexe_locations
+ node = ast_parse("END { }")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 3], [1, 4, 1, 5], [1, 7, 1, 8]])
+
+ node = ast_parse("END { 1 }")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 3], [1, 4, 1, 5], [1, 8, 1, 9]])
+ end
+
def test_redo_locations
node = ast_parse("loop { redo }")
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 11], [1, 7, 1, 11]])
@@ -1518,6 +1649,14 @@ dummy
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 6]])
end
+ def test_sclass_locations
+ node = ast_parse("class << self; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 18], [1, 0, 1, 5], [1, 6, 1, 8], [1, 15, 1, 18]])
+
+ node = ast_parse("class << obj; foo; end")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 22], [1, 0, 1, 5], [1, 6, 1, 8], [1, 19, 1, 22]])
+ end
+
def test_splat_locations
node = ast_parse("a = *1")
assert_locations(node.children[-1].children[1].locations, [[1, 4, 1, 6], [1, 4, 1, 5]])
@@ -1562,6 +1701,15 @@ dummy
node = ast_parse("alias $foo $&")
assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
+
+ node = ast_parse("alias $foo $`")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
+
+ node = ast_parse("alias $foo $'")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
+
+ node = ast_parse("alias $foo $+")
+ assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
end
def test_when_locations
@@ -1597,7 +1745,20 @@ dummy
node = ast_parse("def foo; yield(1, 2) end")
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 20], [1, 9, 1, 14], [1, 14, 1, 15], [1, 19, 1, 20]])
- end
+ end
+
+ def test_negative_numeric_locations
+ node = ast_parse("-1")
+ assert_locations(node.children.last.locations, [[1, 0, 1, 2]])
+ end
+
+ def test_numeric_location_with_nonsuffix
+ node = ast_parse("1if true")
+ assert_locations(node.children.last.children[1].locations, [[1, 0, 1, 1]])
+
+ node = ast_parse("1q", error_tolerant: true)
+ assert_locations(node.children.last.locations, [[1, 0, 1, 1]])
+ end
private
def ast_parse(src, **options)
@@ -1612,7 +1773,7 @@ dummy
def assert_locations(locations, expected)
ary = locations.map {|loc| loc && [loc.first_lineno, loc.first_column, loc.last_lineno, loc.last_column] }
- assert_equal(ary, expected)
+ assert_equal(expected, ary)
end
end
end
diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb
index ca3e3d5f7f..de08be96e4 100644
--- a/test/ruby/test_autoload.rb
+++ b/test/ruby/test_autoload.rb
@@ -224,11 +224,18 @@ p Foo::Bar
Kernel.module_eval do
alias old_require require
end
+ Ruby::Box.module_eval do
+ alias old_require require
+ end
called_with = []
Kernel.send :define_method, :require do |path|
called_with << path
old_require path
end
+ Ruby::Box.send :define_method, :require do |path|
+ called_with << path
+ old_require path
+ end
yield called_with
ensure
Kernel.module_eval do
@@ -236,6 +243,11 @@ p Foo::Bar
alias require old_require
undef old_require
end
+ Ruby::Box.module_eval do
+ undef require
+ alias require old_require
+ undef old_require
+ end
end
def test_require_implemented_in_ruby_is_called
@@ -249,7 +261,8 @@ p Foo::Bar
ensure
remove_autoload_constant
end
- assert_equal [file.path], called_with
+ # .dup to prevent breaking called_with by autoloading pp, etc
+ assert_equal [file.path], called_with.dup
}
end
end
@@ -267,7 +280,8 @@ p Foo::Bar
ensure
remove_autoload_constant
end
- assert_equal [a.path, b.path], called_with
+ # .dup to prevent breaking called_with by autoloading pp, etc
+ assert_equal [a.path, b.path], called_with.dup
end
end
end
@@ -560,7 +574,7 @@ p Foo::Bar
autoload_path = File.join(tmpdir, "autoload_parallel_race.rb")
File.write(autoload_path, 'module Foo; end; module Bar; end')
- assert_separately([], <<-RUBY, timeout: 100)
+ assert_ruby_status([], <<-RUBY, timeout: 100)
autoload_path = #{File.realpath(autoload_path).inspect}
# This should work with no errors or failures.
@@ -599,4 +613,103 @@ p Foo::Bar
RUBY
end
end
+
+ def test_autoload_relative_toplevel
+ Dir.mktmpdir('autoload_relative') do |tmpdir|
+ main_file = File.join(tmpdir, 'main.rb')
+ module_file = File.join(tmpdir, 'test_module.rb')
+
+ File.write(module_file, <<-RUBY)
+ module AutoloadRelativeTest
+ VERSION = '1.0'
+ end
+ RUBY
+
+ File.write(main_file, <<-RUBY)
+ autoload_relative :AutoloadRelativeTest, 'test_module.rb'
+ puts AutoloadRelativeTest::VERSION
+ RUBY
+
+ assert_in_out_err([main_file], '', ['1.0'], [])
+ end
+ end
+
+ def test_autoload_relative_module_level
+ Dir.mktmpdir('autoload_relative') do |tmpdir|
+ main_file = File.join(tmpdir, 'main_mod.rb')
+ module_file = File.join(tmpdir, 'nested_module.rb')
+
+ File.write(module_file, <<-RUBY)
+ module Container
+ module NestedModule
+ MSG = 'loaded'
+ end
+ end
+ RUBY
+
+ File.write(main_file, <<-RUBY)
+ module Container
+ autoload_relative :NestedModule, 'nested_module.rb'
+ end
+ puts Container::NestedModule::MSG
+ RUBY
+
+ assert_in_out_err([main_file], '', ['loaded'], [])
+ end
+ end
+
+ def test_autoload_relative_query
+ Dir.mktmpdir('autoload_relative') do |tmpdir|
+ main_file = File.join(tmpdir, 'query_test.rb')
+ module_file = File.join(tmpdir, 'query_module.rb')
+
+ File.write(module_file, 'module QueryModule; end')
+
+ File.write(main_file, <<-RUBY)
+ autoload_relative :QueryModule, 'query_module.rb'
+ path = autoload?(:QueryModule)
+ # Use realpath for comparison to handle symlinks (e.g., /var -> /private/var on macOS)
+ real_tmpdir = File.realpath('#{tmpdir}')
+ puts path.start_with?(real_tmpdir) && path.end_with?('query_module.rb')
+ RUBY
+
+ assert_in_out_err([main_file], '', ['true'], [])
+ end
+ end
+
+ def test_autoload_relative_nested_directory
+ Dir.mktmpdir('autoload_relative') do |tmpdir|
+ nested_dir = File.join(tmpdir, 'nested')
+ Dir.mkdir(nested_dir)
+
+ main_file = File.join(tmpdir, 'nested_test.rb')
+ module_file = File.join(nested_dir, 'deep_module.rb')
+
+ File.write(module_file, 'module DeepModule; VALUE = 42; end')
+
+ File.write(main_file, <<-RUBY)
+ autoload_relative :DeepModule, 'nested/deep_module.rb'
+ puts DeepModule::VALUE
+ RUBY
+
+ assert_in_out_err([main_file], '', ['42'], [])
+ end
+ end
+
+ def test_autoload_relative_no_basepath
+ # Test that autoload_relative raises an error when called from eval without file context
+ assert_raise(LoadError) do
+ eval('autoload_relative :TestConst, "test.rb"')
+ end
+ end
+
+ private
+
+ def assert_separately(*args, **kwargs)
+ super(*args, timeout: 60, **kwargs)
+ end
+
+ def assert_ruby_status(*args, **kwargs)
+ super(*args, timeout: 60, **kwargs)
+ end
end
diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb
index fca7b62030..332d76c58e 100644
--- a/test/ruby/test_backtrace.rb
+++ b/test/ruby/test_backtrace.rb
@@ -191,6 +191,16 @@ class TestBacktrace < Test::Unit::TestCase
assert_equal(cl.map(&:to_s), ary.map(&:to_s))
end
+ def test_each_caller_location_single_cfunc_frame
+ assert_normal_exit <<~'RUBY'
+ tap { Thread.each_caller_location(1, 1) { |loc| loc.label } }
+ RUBY
+
+ cl = nil; ary = []
+ tap { cl = caller_locations(1, 1); Thread.each_caller_location(1, 1) { |x| ary << x } }
+ assert_equal(cl.map(&:to_s), ary.map(&:to_s))
+ end
+
def test_caller_locations_first_label
def self.label
caller_locations.first.label
@@ -454,4 +464,16 @@ class TestBacktrace < Test::Unit::TestCase
foo::Bar.baz
end;
end
+
+ def test_backtrace_internal_frame
+ backtrace = tap { break caller_locations(0) }
+ assert_equal(__FILE__, backtrace[1].path) # not "<internal:kernel>"
+ assert_equal("Kernel#tap", backtrace[1].label)
+ end
+
+ def test_backtrace_on_argument_error
+ lineno = __LINE__; [1, 2].inject(:tap)
+ rescue ArgumentError
+ assert_equal("#{ __FILE__ }:#{ lineno }:in 'Kernel#tap'", $!.backtrace[0].to_s)
+ end
end
diff --git a/test/ruby/test_beginendblock.rb b/test/ruby/test_beginendblock.rb
index 3706efab52..74da11abf4 100644
--- a/test/ruby/test_beginendblock.rb
+++ b/test/ruby/test_beginendblock.rb
@@ -40,7 +40,8 @@ class TestBeginEndBlock < Test::Unit::TestCase
assert_in_out_err([], "#{<<~"begin;"}#{<<~'end;'}", [], ['-:2: warning: END in method; use at_exit'])
begin;
def end1
- END {}
+ END {
+ }
end
end;
end
diff --git a/test/ruby/test_bignum.rb b/test/ruby/test_bignum.rb
index beef33e2a6..c366f794b2 100644
--- a/test/ruby/test_bignum.rb
+++ b/test/ruby/test_bignum.rb
@@ -605,6 +605,49 @@ class TestBignum < Test::Unit::TestCase
assert_equal(1, (-2**(BIGNUM_MIN_BITS*4))[BIGNUM_MIN_BITS*4])
end
+ def test_aref2
+ x = (0x123456789abcdef << (BIGNUM_MIN_BITS + 32)) | 0x12345678
+ assert_equal(x, x[0, x.bit_length])
+ assert_equal(x >> 10, x[10, x.bit_length])
+ assert_equal(0x45678, x[0, 20])
+ assert_equal(0x6780, x[-4, 16])
+ assert_equal(0x123456, x[x.bit_length - 21, 40])
+ assert_equal(0x6789ab, x[x.bit_length - 41, 24])
+ assert_equal(0, x[-20, 10])
+ assert_equal(0, x[x.bit_length + 10, 10])
+
+ assert_equal(0, x[5, 0])
+ assert_equal(0, (-x)[5, 0])
+
+ assert_equal(x >> 5, x[5, -1])
+ assert_equal(x << 5, x[-5, -1])
+ assert_equal((-x) >> 5, (-x)[5, -1])
+ assert_equal((-x) << 5, (-x)[-5, -1])
+
+ assert_equal(x << 5, x[-5, FIXNUM_MAX])
+ assert_equal(x >> 5, x[5, FIXNUM_MAX])
+ assert_equal(0, x[FIXNUM_MIN, 100])
+ assert_equal(0, (-x)[FIXNUM_MIN, 100])
+
+ y = (x << 160) | 0x1234_0000_0000_0000_1234_0000_0000_0000
+ assert_equal(0xffffedcc00, (-y)[40, 40])
+ assert_equal(0xfffffffedc, (-y)[52, 40])
+ assert_equal(0xffffedcbff, (-y)[104, 40])
+ assert_equal(0xfffff6e5d4, (-y)[y.bit_length - 20, 40])
+ assert_equal(0, (-y)[-20, 10])
+ assert_equal(0xfff, (-y)[y.bit_length + 10, 12])
+
+ z = (1 << (BIGNUM_MIN_BITS * 2)) - 1
+ assert_equal(0x400, (-z)[-10, 20])
+ assert_equal(1, (-z)[0, 20])
+ assert_equal(0, (-z)[10, 20])
+ assert_equal(1, (-z)[0, z.bit_length])
+ assert_equal(0, (-z)[z.bit_length - 10, 10])
+ assert_equal(0x400, (-z)[z.bit_length - 10, 11])
+ assert_equal(0xfff, (-z)[z.bit_length, 12])
+ assert_equal(0xfff00, (-z)[z.bit_length - 8, 20])
+ end
+
def test_hash
assert_nothing_raised { T31P.hash }
end
@@ -778,6 +821,9 @@ class TestBignum < Test::Unit::TestCase
assert_equal([7215, 2413, 6242], T1024P.digits(10_000).first(3))
assert_equal([11], 11.digits(T1024P))
assert_equal([T1024P - 1, 1], (T1024P + T1024P - 1).digits(T1024P))
+ bug21680 = '[ruby-core:123769] [Bug #21680]'
+ assert_equal([0] * 64 + [1], (2**512).digits(256), bug21680)
+ assert_equal([0] * 128 + [1], (123**128).digits(123), bug21680)
end
def test_digits_for_negative_numbers
diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb
new file mode 100644
index 0000000000..a425c5eb7d
--- /dev/null
+++ b/test/ruby/test_box.rb
@@ -0,0 +1,1219 @@
+# frozen_string_literal: true
+
+require 'test/unit'
+require 'rbconfig'
+require 'tempfile'
+
+class TestBox < Test::Unit::TestCase
+ EXPERIMENTAL_WARNING_LINE_PATTERNS = [
+ /#{RbConfig::CONFIG["ruby_install_name"] || "ruby"}(\.exe)?: warning: Ruby::Box is experimental, and the behavior may change in the future!/,
+ %r{See https://docs.ruby-lang.org/en/(master|\d\.\d)/Ruby/Box.html for known issues, etc.}
+ ]
+ ENV_ENABLE_BOX = {'RUBY_BOX' => '1', 'TEST_DIR' => __dir__}
+
+ def setup
+ @box = nil
+ @dir = __dir__
+ end
+
+ def teardown
+ @box = nil
+ end
+
+ def setup_box
+ pend unless Ruby::Box.enabled?
+ @box = Ruby::Box.new
+ end
+
+ def test_box_availability_in_default
+ assert_separately(['RUBY_BOX'=>nil], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ assert_nil ENV['RUBY_BOX']
+ assert_not_predicate Ruby::Box, :enabled?
+ end;
+ end
+
+ def test_box_availability_when_enabled
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ assert_equal '1', ENV['RUBY_BOX']
+ assert_predicate Ruby::Box, :enabled?
+ end;
+ end
+
+ def test_current_box_in_main
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ assert_equal Ruby::Box.main, Ruby::Box.current
+ assert_predicate Ruby::Box.main, :main?
+ end;
+ end
+
+ def test_require_rb_separately
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+
+ @box.require(File.join(__dir__, 'box', 'a.1_1_0'))
+
+ assert_not_nil @box::BOX_A
+ assert_not_nil @box::BOX_B
+ assert_equal "1.1.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.1.0", @box::BOX_A.new.yay
+ assert_equal "1.1.0", @box::BOX_B::VERSION
+ assert_equal "yay_b1", @box::BOX_B.yay
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_require_relative_rb_separately
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+
+ @box.require_relative('box/a.1_1_0')
+
+ assert_not_nil @box::BOX_A
+ assert_not_nil @box::BOX_B
+ assert_equal "1.1.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.1.0", @box::BOX_A.new.yay
+ assert_equal "1.1.0", @box::BOX_B::VERSION
+ assert_equal "yay_b1", @box::BOX_B.yay
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_load_separately
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+
+ @box.load(File.join(__dir__, 'box', 'a.1_1_0.rb'))
+
+ assert_not_nil @box::BOX_A
+ assert_not_nil @box::BOX_B
+ assert_equal "1.1.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.1.0", @box::BOX_A.new.yay
+ assert_equal "1.1.0", @box::BOX_B::VERSION
+ assert_equal "yay_b1", @box::BOX_B.yay
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_box_in_box
+ setup_box
+
+ assert_raise(NameError) { BOX1 }
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+
+ @box.require_relative('box/box')
+
+ assert_not_nil @box::BOX1
+ assert_not_nil @box::BOX1::BOX_A
+ assert_not_nil @box::BOX1::BOX_B
+ assert_equal "1.1.0", @box::BOX1::BOX_A::VERSION
+ assert_equal "yay 1.1.0", @box::BOX1::BOX_A.new.yay
+ assert_equal "1.1.0", @box::BOX1::BOX_B::VERSION
+ assert_equal "yay_b1", @box::BOX1::BOX_B.yay
+
+ assert_raise(NameError) { BOX1 }
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_require_rb_2versiobox
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+
+ @box.require(File.join(__dir__, 'box', 'a.1_2_0'))
+ assert_equal "1.2.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.2.0", @box::BOX_A.new.yay
+
+ n2 = Ruby::Box.new
+ n2.require(File.join(__dir__, 'box', 'a.1_1_0'))
+ assert_equal "1.1.0", n2::BOX_A::VERSION
+ assert_equal "yay 1.1.0", n2::BOX_A.new.yay
+
+ # recheck @box is not affected by the following require
+ assert_equal "1.2.0", @box::BOX_A::VERSION
+ assert_equal "yay 1.2.0", @box::BOX_A.new.yay
+
+ assert_raise(NameError) { BOX_A }
+ end
+
+ def test_raising_errors_in_require
+ setup_box
+
+ assert_raise(RuntimeError, "Yay!") { @box.require(File.join(__dir__, 'box', 'raise')) }
+ assert_include Ruby::Box.current.inspect, "main"
+ end
+
+ def test_class_variables_in_root_are_invisible_in_other_boxes
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ Ruby::Box.root.eval(<<~RUBY)
+ module M
+ @@x = 1
+ end
+
+ class A
+ include M
+ end
+
+ class B < A
+ end
+ RUBY
+
+ code = <<~REPRO
+ class ::B
+ @@x += 1
+ end
+ REPRO
+
+ b1 = Ruby::Box.new
+ assert_raise(NameError, "uninitialized class variable @@x in B") {
+ b1.eval(code)
+ }
+ end;
+ end
+
+ def test_autoload_in_box
+ setup_box
+
+ assert_raise(NameError) { BOX_A }
+
+ @box.require_relative('box/autoloading')
+ # autoloaded A is visible from global
+ assert_equal '1.1.0', @box::BOX_A::VERSION
+
+ assert_raise(NameError) { BOX_A }
+
+ # autoload trigger BOX_B::BAR is valid even from global
+ assert_equal 'bar_b1', @box::BOX_B::BAR
+
+ assert_raise(NameError) { BOX_A }
+ assert_raise(NameError) { BOX_B }
+ end
+
+ def test_continuous_top_level_method_in_a_box
+ setup_box
+
+ @box.require_relative('box/define_toplevel')
+ @box.require_relative('box/call_toplevel')
+
+ assert_raise(NameError) { foo }
+ end
+
+ def test_top_level_methods_in_box
+ pend # TODO: fix loading/current box detection
+ setup_box
+ @box.require_relative('box/top_level')
+ assert_equal "yay!", @box::Foo.foo
+ assert_raise(NameError) { yaaay }
+ assert_equal "foo", @box::Bar.bar
+ assert_raise_with_message(RuntimeError, "boooo") { @box::Baz.baz }
+ end
+
+ def test_proc_defined_in_box_refers_module_in_box
+ setup_box
+
+ # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box1 = Ruby::Box.new
+ box1.require("#{here}/box/proc_callee")
+ proc_v = box1::Foo.callee
+ assert_raise(NameError) { Target }
+ assert box1::Target
+ assert_equal "fooooo", proc_v.call # refers Target in the box box1
+ box1.require("#{here}/box/proc_caller")
+ assert_equal "fooooo", box1::Bar.caller(proc_v)
+
+ box2 = Ruby::Box.new
+ box2.require("#{here}/box/proc_caller")
+ assert_raise(NameError) { box2::Target }
+ assert_equal "fooooo", box2::Bar.caller(proc_v) # refers Target in the box box1
+ end;
+ end
+
+ def test_proc_defined_globally_refers_global_module
+ setup_box
+
+ # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ require("#{here}/box/proc_callee")
+ def Target.foo
+ "yay"
+ end
+ proc_v = Foo.callee
+ assert Target
+ assert_equal "yay", proc_v.call # refers global Foo
+ box1 = Ruby::Box.new
+ box1.require("#{here}/box/proc_caller")
+ assert_equal "yay", box1::Bar.caller(proc_v)
+
+ box2 = Ruby::Box.new
+ box2.require("#{here}/box/proc_callee")
+ box2.require("#{here}/box/proc_caller")
+ assert_equal "fooooo", box2::Foo.callee.call
+ assert_equal "yay", box2::Bar.caller(proc_v) # should refer the global Target, not Foo in box2
+ end;
+ end
+
+ def test_instance_variable
+ setup_box
+
+ @box.require_relative('box/instance_variables')
+
+ assert_equal [], String.instance_variables
+ assert_equal [:@str_ivar1, :@str_ivar2], @box::StringDelegatorObj.instance_variables
+ assert_equal 111, @box::StringDelegatorObj.str_ivar1
+ assert_equal 222, @box::StringDelegatorObj.str_ivar2
+ assert_equal 222, @box::StringDelegatorObj.instance_variable_get(:@str_ivar2)
+
+ @box::StringDelegatorObj.instance_variable_set(:@str_ivar3, 333)
+ assert_equal 333, @box::StringDelegatorObj.instance_variable_get(:@str_ivar3)
+ @box::StringDelegatorObj.remove_instance_variable(:@str_ivar1)
+ assert_nil @box::StringDelegatorObj.str_ivar1
+ assert_equal [:@str_ivar2, :@str_ivar3], @box::StringDelegatorObj.instance_variables
+
+ assert_equal [], String.instance_variables
+ end
+
+ def test_methods_added_in_box_are_invisible_globally
+ setup_box
+
+ @box.require_relative('box/string_ext')
+
+ assert_equal "yay", @box::Bar.yay
+
+ assert_raise(NoMethodError){ String.new.yay }
+ end
+
+ def test_continuous_method_definitions_in_a_box
+ setup_box
+
+ @box.require_relative('box/string_ext')
+ assert_equal "yay", @box::Bar.yay
+
+ @box.require_relative('box/string_ext_caller')
+ assert_equal "yay", @box::Foo.yay
+
+ @box.require_relative('box/string_ext_calling')
+ end
+
+ def test_methods_added_in_box_later_than_caller_code
+ setup_box
+
+ @box.require_relative('box/string_ext_caller')
+ @box.require_relative('box/string_ext')
+
+ assert_equal "yay", @box::Bar.yay
+ assert_equal "yay", @box::Foo.yay
+ end
+
+ def test_method_added_in_box_are_available_on_eval
+ setup_box
+
+ @box.require_relative('box/string_ext')
+ @box.require_relative('box/string_ext_eval_caller')
+
+ assert_equal "yay", @box::Baz.yay
+ end
+
+ def test_method_added_in_box_are_available_on_eval_with_binding
+ setup_box
+
+ @box.require_relative('box/string_ext')
+ @box.require_relative('box/string_ext_eval_caller')
+
+ assert_equal "yay, yay!", @box::Baz.yay_with_binding
+ end
+
+ def test_methods_and_constants_added_by_include
+ setup_box
+
+ @box.require_relative('box/open_class_with_include')
+
+ assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say
+ assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say_foo
+ assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say_with_obj("wow")
+
+ assert_raise(NameError) { String::FOO }
+
+ assert_equal "foo 1", @box::OpenClassWithInclude.refer_foo
+ end
+end
+
+module ProcLookupTestA
+ module B
+ VALUE = 111
+ end
+end
+
+class TestBox < Test::Unit::TestCase
+ def make_proc_from_block(&b)
+ b
+ end
+
+ def test_proc_from_main_works_with_global_definitions
+ setup_box
+
+ @box.require_relative('box/procs')
+
+ proc_and_labels = [
+ [Proc.new { String.new.yay }, "Proc.new"],
+ [proc { String.new.yay }, "proc{}"],
+ [lambda { String.new.yay }, "lambda{}"],
+ [->(){ String.new.yay }, "->(){}"],
+ [make_proc_from_block { String.new.yay }, "make_proc_from_block"],
+ [@box::ProcInBox.make_proc_from_block { String.new.yay }, "make_proc_from_block in @box"],
+ ]
+
+ proc_and_labels.each do |str_pr|
+ pr, pr_label = str_pr
+ assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in main") { pr.call }
+ assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in @box") { @box::ProcInBox.call_proc(pr) }
+ end
+
+ const_and_labels = [
+ [Proc.new { ProcLookupTestA::B::VALUE }, "Proc.new"],
+ [proc { ProcLookupTestA::B::VALUE }, "proc{}"],
+ [lambda { ProcLookupTestA::B::VALUE }, "lambda{}"],
+ [->(){ ProcLookupTestA::B::VALUE }, "->(){}"],
+ [make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block"],
+ [@box::ProcInBox.make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block in @box"],
+ ]
+
+ const_and_labels.each do |const_pr|
+ pr, pr_label = const_pr
+ assert_equal 111, pr.call, "111 expected, #{pr_label} called in main"
+ assert_equal 111, @box::ProcInBox.call_proc(pr), "111 expected, #{pr_label} called in @box"
+ end
+ end
+
+ def test_proc_from_box_works_with_definitions_in_box
+ setup_box
+
+ @box.require_relative('box/procs')
+
+ proc_types = [:proc_new, :proc_f, :lambda_f, :lambda_l, :block]
+
+ proc_types.each do |proc_type|
+ assert_equal 222, @box::ProcInBox.make_const_proc(proc_type).call, "ProcLookupTestA::B::VALUE should be 222 in @box"
+ assert_equal "foo", @box::ProcInBox.make_str_const_proc(proc_type).call, "String::FOO should be \"foo\" in @box"
+ assert_equal "yay", @box::ProcInBox.make_str_proc(proc_type).call, "String#yay should be callable in @box"
+ #
+ # TODO: method calls not-in-methods nor procs can't handle the current box correctly.
+ #
+ # assert_equal "yay,foo,222",
+ # @box::ProcInBox.const_get(('CONST_' + proc_type.to_s.upcase).to_sym).call,
+ # "Proc assigned to constants should refer constants correctly in @box"
+ end
+ end
+
+ def test_class_module_singleton_methods
+ setup_box
+
+ @box.require_relative('box/singleton_methods')
+
+ assert_equal "Good evening!", @box::SingletonMethods.string_greeing # def self.greeting
+ assert_equal 42, @box::SingletonMethods.integer_answer # class << self; def answer
+ assert_equal([], @box::SingletonMethods.array_blank) # def self.blank w/ instance methods
+ assert_equal({status: 200, body: 'OK'}, @box::SingletonMethods.hash_http_200) # class << self; def ... w/ instance methods
+
+ assert_equal([4, 4], @box::SingletonMethods.array_instance_methods_return_size([1, 2, 3, 4]))
+ assert_equal([3, 3], @box::SingletonMethods.hash_instance_methods_return_size({a: 2, b: 4, c: 8}))
+
+ assert_raise(NoMethodError) { String.greeting }
+ assert_raise(NoMethodError) { Integer.answer }
+ assert_raise(NoMethodError) { Array.blank }
+ assert_raise(NoMethodError) { Hash.http_200 }
+ end
+
+ def test_add_constants_in_box
+ setup_box
+
+ @box.require('envutil')
+
+ String.const_set(:STR_CONST0, 999)
+ assert_equal 999, String::STR_CONST0
+ assert_equal 999, String.const_get(:STR_CONST0)
+
+ assert_raise(NameError) { String.const_get(:STR_CONST1) }
+ assert_raise(NameError) { String::STR_CONST2 }
+ assert_raise(NameError) { String::STR_CONST3 }
+ assert_raise(NameError) { Integer.const_get(:INT_CONST1) }
+
+ EnvUtil.verbose_warning do
+ @box.require_relative('box/consts')
+ end
+
+ assert_equal 999, String::STR_CONST0
+ assert_raise(NameError) { String::STR_CONST1 }
+ assert_raise(NameError) { String::STR_CONST2 }
+ assert_raise(NameError) { Integer::INT_CONST1 }
+
+ assert_not_nil @box::ForConsts.refer_all
+
+ assert_equal 112, @box::ForConsts.refer1
+ assert_equal 112, @box::ForConsts.get1
+ assert_equal 112, @box::ForConsts::CONST1
+ assert_equal 222, @box::ForConsts.refer2
+ assert_equal 222, @box::ForConsts.get2
+ assert_equal 222, @box::ForConsts::CONST2
+ assert_equal 333, @box::ForConsts.refer3
+ assert_equal 333, @box::ForConsts.get3
+ assert_equal 333, @box::ForConsts::CONST3
+
+ @box::EnvUtil.suppress_warning do
+ @box::ForConsts.const_set(:CONST3, 334)
+ end
+ assert_equal 334, @box::ForConsts::CONST3
+ assert_equal 334, @box::ForConsts.refer3
+ assert_equal 334, @box::ForConsts.get3
+
+ assert_equal 10, @box::ForConsts.refer_top_const
+
+ # use Proxy object to use usual methods instead of singleton methods
+ proxy = @box::ForConsts::Proxy.new
+
+ assert_raise(NameError){ proxy.call_str_refer0 }
+ assert_raise(NameError){ proxy.call_str_get0 }
+
+ proxy.call_str_set0(30)
+ assert_equal 30, proxy.call_str_refer0
+ assert_equal 30, proxy.call_str_get0
+ assert_equal 999, String::STR_CONST0
+
+ proxy.call_str_remove0
+ assert_raise(NameError){ proxy.call_str_refer0 }
+ assert_raise(NameError){ proxy.call_str_get0 }
+
+ assert_equal 112, proxy.call_str_refer1
+ assert_equal 112, proxy.call_str_get1
+ assert_equal 223, proxy.call_str_refer2
+ assert_equal 223, proxy.call_str_get2
+ assert_equal 333, proxy.call_str_refer3
+ assert_equal 333, proxy.call_str_get3
+
+ EnvUtil.suppress_warning do
+ proxy.call_str_set3
+ end
+ assert_equal 334, proxy.call_str_refer3
+ assert_equal 334, proxy.call_str_get3
+
+ assert_equal 1, proxy.refer_int_const1
+
+ assert_equal 999, String::STR_CONST0
+ assert_raise(NameError) { String::STR_CONST1 }
+ assert_raise(NameError) { String::STR_CONST2 }
+ assert_raise(NameError) { String::STR_CONST3 }
+ assert_raise(NameError) { Integer::INT_CONST1 }
+ end
+
+ def test_global_variables
+ default_l = $-0
+ default_f = $,
+
+ setup_box
+
+ assert_equal "\n", $-0 # equal to $/, line splitter
+ assert_equal nil, $, # field splitter
+
+ @box.require_relative('box/global_vars')
+
+ # read first
+ assert_equal "\n", @box::LineSplitter.read
+ @box::LineSplitter.write("\r\n")
+ assert_equal "\r\n", @box::LineSplitter.read
+ assert_equal "\n", $-0
+
+ # write first
+ @box::FieldSplitter.write(",")
+ assert_equal ",", @box::FieldSplitter.read
+ assert_equal nil, $,
+
+ # used only in box
+ assert_not_include? global_variables, :$used_only_in_box
+ @box::UniqueGvar.write(123)
+ assert_equal 123, @box::UniqueGvar.read
+ assert_nil $used_only_in_box
+
+ # Kernel#global_variables returns the sum of all gvars.
+ global_gvars = global_variables.sort
+ assert_equal global_gvars, @box::UniqueGvar.gvars_in_box.sort
+ @box::UniqueGvar.write_only(456)
+ assert_equal (global_gvars + [:$write_only_var_in_box]).sort, @box::UniqueGvar.gvars_in_box.sort
+ assert_equal (global_gvars + [:$write_only_var_in_box]).sort, global_variables.sort
+ ensure
+ EnvUtil.suppress_warning do
+ $-0 = default_l
+ $, = default_f
+ end
+ end
+
+ def test_match_variables_are_not_cached_in_box
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ /(?<a>foo)/ =~ 'bar'
+ /(?<b>baz)/ =~ 'baz'
+ assert_equal "baz", b
+ assert_equal "baz", $~.to_s
+
+ /foo/ =~ 'bar'
+ assert_nil $~
+ /(?<word>foo)(bar)?/ =~ 'foo'
+ assert_equal "foo", word
+ assert_equal "foo", $~.to_s
+ assert_equal "foo", $&
+ assert_equal "", $`
+ assert_equal "", $'
+ assert_equal "foo", $+
+ end;
+ end
+
+ def test_lastline_not_cached_in_box
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ r, w = IO.pipe
+ w.write("first\nsecond\n")
+ w.close
+ STDIN.reopen(r)
+ via_gets = Ruby::Box.new.eval(<<~'CODE')
+ gets
+ _ = $_
+ gets
+ $_
+ CODE
+ assert_equal "second\n", via_gets
+ end;
+ end
+
+ def test_lastline_not_cached_in_nested_boxes
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ r, w = IO.pipe
+ w.write("outer1\ninner1\ninner2\nouter2\n")
+ w.close
+ STDIN.reopen(r)
+ inner_via_gets, outer_via_gets = Ruby::Box.new.eval(<<~'CODE')
+ gets
+ _ = $_
+
+ inner_result = Ruby::Box.new.eval(<<~'INNER')
+ gets
+ _ = $_
+ gets
+ $_
+ INNER
+
+ gets
+ [inner_result, $_]
+ CODE
+ assert_equal "inner2\n", inner_via_gets
+ assert_equal "outer2\n", outer_via_gets
+ end;
+ end
+
+ def test_errinfo_not_cached_in_box
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ first, second = Ruby::Box.new.eval(<<~'CODE')
+ a = begin; raise "first"; rescue RuntimeError; $!.message; end
+ b = begin; raise "second"; rescue RuntimeError; $!.message; end
+ [a, b]
+ CODE
+ assert_equal "first", first
+ assert_equal "second", second
+ end;
+ end
+
+ def test_errinfo_not_cached_in_nested_boxes
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ inner_msg, outer_msg = Ruby::Box.new.eval(<<~'CODE')
+ outer_a = begin; raise "outer1"; rescue RuntimeError; $!.message; end
+
+ inner_msg = Ruby::Box.new.eval(<<~'INNER')
+ begin; raise "inner1"; rescue RuntimeError; $!; end
+ begin; raise "inner2"; rescue RuntimeError; $!.message; end
+ INNER
+
+ outer_b = begin; raise "outer2"; rescue RuntimeError; $!.message; end
+ [inner_msg, outer_b]
+ CODE
+ assert_equal "inner2", inner_msg
+ assert_equal "outer2", outer_msg
+ end;
+ end
+
+ def test_backtrace_not_cached_in_box
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ a_actual, b_actual = Ruby::Box.new.eval(<<~'CODE')
+ a_actual = begin; raise "first"; rescue RuntimeError; $@.first[/:(\d+):/, 1].to_i; end
+ b_actual = begin; raise "second"; rescue RuntimeError; $@.first[/:(\d+):/, 1].to_i; end
+ [a_actual, b_actual]
+ CODE
+ assert_equal 1, a_actual
+ assert_equal 2, b_actual
+ end;
+ end
+
+ def test_backtrace_not_cached_in_nested_boxes
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ inner_actual, outer_actual = Ruby::Box.new.eval(<<~'CODE')
+ begin; raise "outer1"; rescue RuntimeError; $@; end
+ inner_actual = Ruby::Box.new.eval(<<~'INNER')
+ begin; raise "inner1"; rescue RuntimeError; $@; end
+ begin; raise "inner2"; rescue RuntimeError; $@.first[/:(\d+):/, 1].to_i; end
+ INNER
+ outer_actual = begin; raise "outer2"; rescue RuntimeError; $@.first[/:(\d+):/, 1].to_i; end
+ [inner_actual, outer_actual]
+ CODE
+ assert_equal 2, inner_actual
+ assert_equal 6, outer_actual
+ end;
+ end
+
+ def test_errinfo_isolated_between_boxes
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box_a = Ruby::Box.new
+ box_b = Ruby::Box.new
+
+ a = box_a.eval('begin; raise "a"; rescue; $!.message; end')
+ b = box_b.eval('begin; raise "b"; rescue; $!.message; end')
+
+ assert_equal "a", a
+ assert_equal "b", b
+ end;
+ end
+
+ def test_backtrace_isolated_between_boxes
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box_a = Ruby::Box.new
+ box_b = Ruby::Box.new
+
+ a_line = box_a.eval("\nbegin; raise; rescue; $@.first[/:(\\d+):/, 1].to_i; end")
+ b_line = box_b.eval('begin; raise; rescue; $@.first[/:(\d+):/, 1].to_i; end')
+
+ assert_equal 2, a_line
+ assert_equal 1, b_line
+ end;
+ end
+
+ def test_inner_box_rescue_does_not_disturb_outer_box_errinfo
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box_a = Ruby::Box.new
+ errinfo_in_inner_rescue, errinfo_after_inner_rescue, errinfo_back_in_outer_rescue = box_a.eval(<<~'A')
+ errinfo_in_inner_rescue = errinfo_after_inner_rescue = errinfo_back_in_outer_rescue = nil
+ begin
+ raise "outer"
+ rescue
+ errinfo_in_inner_rescue, errinfo_after_inner_rescue = Ruby::Box.new.eval(<<~'B')
+ in_rescue = after_rescue = nil
+ begin
+ raise "inner"
+ rescue
+ in_rescue = $! && $!.message
+ end
+ after_rescue = $! && $!.message
+ [in_rescue, after_rescue]
+ B
+ errinfo_back_in_outer_rescue = $! && $!.message
+ end
+ [errinfo_in_inner_rescue, errinfo_after_inner_rescue, errinfo_back_in_outer_rescue]
+ A
+
+ assert_equal "inner", errinfo_in_inner_rescue
+ assert_equal "outer", errinfo_after_inner_rescue
+ assert_equal "outer", errinfo_back_in_outer_rescue
+ end;
+ end
+
+ def test_load_path_and_loaded_features
+ setup_box
+
+ assert_respond_to $LOAD_PATH, :resolve_feature_path
+
+ @box.require_relative('box/load_path')
+
+ assert_not_equal $LOAD_PATH, @box::LoadPathCheck::FIRST_LOAD_PATH
+
+ assert @box::LoadPathCheck::FIRST_LOAD_PATH_RESPOND_TO_RESOLVE
+
+ box_dir = File.join(__dir__, 'box')
+ # TODO: $LOADED_FEATURES in method calls should refer the current box in addition to the loading box.
+ # assert_include @box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank1.rb')
+ # assert_not_include @box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank2.rb')
+ # assert_predicate @box::LoadPathCheck, :require_blank2
+ # assert_include(@box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank2.rb'))
+
+ assert_not_include $LOADED_FEATURES, File.join(box_dir, 'blank1.rb')
+ assert_not_include $LOADED_FEATURES, File.join(box_dir, 'blank2.rb')
+ end
+
+ def test_eval_basic
+ setup_box
+
+ # Test basic evaluation
+ result = @box.eval("1 + 1")
+ assert_equal 2, result
+
+ # Test string evaluation
+ result = @box.eval("'hello ' + 'world'")
+ assert_equal "hello world", result
+ end
+
+ def test_eval_with_constants
+ setup_box
+
+ # Define a constant in the box via eval
+ @box.eval("TEST_CONST = 42")
+ assert_equal 42, @box::TEST_CONST
+
+ # Constant should not be visible in main box
+ assert_raise(NameError) { TEST_CONST }
+ end
+
+ def test_eval_with_classes
+ setup_box
+
+ # Define a class in the box via eval
+ @box.eval("class TestClass; def hello; 'from box'; end; end")
+
+ # Class should be accessible in the box
+ instance = @box::TestClass.new
+ assert_equal "from box", instance.hello
+
+ # Class should not be visible in main box
+ assert_raise(NameError) { TestClass }
+ end
+
+ def test_eval_isolation
+ setup_box
+
+ # Create another box
+ n2 = Ruby::Box.new
+
+ # Define different constants in each box
+ @box.eval("ISOLATION_TEST = 'first'")
+ n2.eval("ISOLATION_TEST = 'second'")
+
+ # Each box should have its own constant
+ assert_equal "first", @box::ISOLATION_TEST
+ assert_equal "second", n2::ISOLATION_TEST
+
+ # Constants should not interfere with each other
+ assert_not_equal @box::ISOLATION_TEST, n2::ISOLATION_TEST
+ end
+
+ def test_eval_with_variables
+ setup_box
+
+ # Test local variable access (should work within the eval context)
+ result = @box.eval("x = 10; y = 20; x + y")
+ assert_equal 30, result
+ end
+
+ def test_eval_error_handling
+ setup_box
+
+ # Test syntax error
+ assert_raise(SyntaxError) { @box.eval("1 +") }
+
+ # Test name error
+ assert_raise(NameError) { @box.eval("undefined_variable") }
+
+ # Test that box is properly restored after error
+ begin
+ @box.eval("raise RuntimeError, 'test error'")
+ rescue RuntimeError
+ # Should be able to continue using the box
+ result = @box.eval("2 + 2")
+ assert_equal 4, result
+ end
+ end
+
+ # Tests which run always (w/o RUBY_BOX=1 globally)
+
+ def test_prelude_gems_and_loaded_features
+ assert_in_out_err([ENV_ENABLE_BOX, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
+ begin;
+ puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join
+ puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join
+
+ require "error_highlight"
+
+ puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join
+ puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join
+ end;
+
+ # No additional warnings except for experimental warnings
+ assert_equal 2, error.size
+ assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[0], error[0]
+ assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1]
+
+ assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb'
+ assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb'
+ assert_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb'
+ assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb'
+ end
+ end
+
+ def test_prelude_gems_and_loaded_features_with_disable_gems
+ assert_in_out_err([ENV_ENABLE_BOX, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
+ begin;
+ puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join
+ puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join
+
+ require "error_highlight"
+
+ puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join
+ puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join
+ end;
+
+ assert_equal 2, error.size
+ assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[0], error[0]
+ assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1]
+
+ refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb'
+ refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb'
+ refute_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb'
+ assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb'
+ end
+ end
+
+ def test_calling_root_box_methods_does_not_change_user_boxes_newly_created
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true, timeout: 60)
+ begin;
+ assert_not_include Object.constants.sort, :Find # required by Pathname#find
+ assert_not_include Ruby::Box.root.eval("Object.constants.sort"), :Find
+ b1 = Ruby::Box.new
+ assert_not_include b1.eval("Object.constants.sort"), :Find
+
+ require 'pathname'
+ Pathname.new('.').find{|path| path.directory?}
+ assert_include Object.constants.sort, :Find # required by Pathname#find
+
+ assert_not_include Ruby::Box.root.eval("Object.constants.sort"), :Find
+ assert_not_include b1.eval("Object.constants.sort"), :Find
+
+ Ruby::Box.root.eval("require 'pathname'; Pathname.new('.').find{|path| path.directory? }")
+ assert_include Ruby::Box.root.eval("Object.constants.sort"), :Find
+
+ assert_not_include b1.eval("Object.constants.sort"), :Find
+ b2 = Ruby::Box.new
+ assert_not_include b2.eval("Object.constants.sort"), :Find
+ end;
+ end
+
+ def test_boxes_have_different_rubygems
+ # assert_separately w/ ENV_ENABLE_BOX and --enable=gems causes timeouts on CI @ Windows
+ assert_in_out_err([ENV_ENABLE_BOX, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
+ begin;
+ require "json"
+ h = {main: Gem.object_id, root: Ruby::Box.root.eval("Gem").object_id, box: Ruby::Box.new.eval("Gem").object_id}
+ puts h.to_json
+ end;
+ require "json"
+ result = JSON.parse(output.first, symbolize_names: true)
+ assert_not_equal result[:main], result[:root]
+ assert_not_equal result[:box], result[:root]
+ assert_not_equal result[:main], result[:box]
+ end
+ end
+
+ def test_require_list_loaded_only_in_main_box
+ Tempfile.create(["req_a", ".rb"]) do |t1|
+ Tempfile.create(["req_b", ".rb"]) do |t2|
+ t1.puts "module FooBarA; end"
+ t1.close
+ t2.puts "module FooBarB; end"
+ t2.close
+
+ opts = [ENV_ENABLE_BOX, "-r#{t1.path}", "-r#{t2.path}"]
+ assert_separately(opts, __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ main_constants = Object.constants
+ assert_include main_constants, :FooBarA
+ assert_include main_constants, :FooBarB
+
+ root_constants = Ruby::Box.root.eval("Object.constants.sort")
+ master_constants = Ruby::Box.master.eval("Object.constants.sort")
+ assert_not_include root_constants, :FooBarA
+ assert_not_include root_constants, :FooBarB
+ assert_not_include master_constants, :FooBarA
+ assert_not_include master_constants, :FooBarB
+ end;
+ end
+ end
+ end
+
+ def test_root_and_main_methods
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ pend unless Ruby::Box.respond_to?(:root) and Ruby::Box.respond_to?(:main) # for RUBY_DEBUG > 0
+
+ assert_respond_to Ruby::Box.root, :root?
+ assert_respond_to Ruby::Box.main, :main?
+
+ assert_predicate Ruby::Box.root, :root?
+ assert_predicate Ruby::Box.main, :main?
+ assert_equal Ruby::Box.main, Ruby::Box.current
+
+ $a = 1
+ $LOADED_FEATURES.push("/tmp/foobar")
+
+ assert_equal 2, Ruby::Box.root.eval('$a = 2; $a')
+ assert_not_include Ruby::Box.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES'), "/tmp/foobar"
+ assert_equal "FooClass", Ruby::Box.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s')
+
+ assert_equal 1, $a
+ assert_not_include $LOADED_FEATURES, "/tmp/barbaz"
+ assert_not_operator Object, :const_defined?, :FooClass
+ end;
+ end
+
+ def test_basic_box_detections
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box = Ruby::Box.new
+ $gvar1 = 'bar'
+ code = <<~EOC
+ BOX1 = Ruby::Box.current
+ $gvar1 = 'foo'
+
+ def toplevel = $gvar1
+
+ class Foo
+ BOX2 = Ruby::Box.current
+ BOX2_proc = ->(){ BOX2 }
+ BOX3_proc = ->(){ Ruby::Box.current }
+
+ def box4 = Ruby::Box.current
+ def self.box5 = BOX2
+ def self.box6 = Ruby::Box.current
+ def self.box6_proc = ->(){ Ruby::Box.current }
+ def self.box7
+ res = []
+ [1,2].chunk{ it.even? }.each do |bool, members|
+ res << Ruby::Box.current.object_id.to_s + ":" + bool.to_s + ":" + members.map(&:to_s).join(",")
+ end
+ res
+ end
+
+ def self.yield_block = yield
+ def self.call_block(&b) = b.call
+
+ def self.gvar1 = $gvar1
+ def self.call_toplevel = toplevel
+ end
+ FOO_NAME = Foo.name
+
+ module Kernel
+ def foo_box = Ruby::Box.current
+ module_function :foo_box
+ end
+
+ BOX_X = Foo.new.box4
+ BOX_Y = foo_box
+ EOC
+ box.eval(code)
+ outer = Ruby::Box.current
+ assert_equal box, box::BOX1 # on TOP frame
+ assert_equal box, box::Foo::BOX2 # on CLASS frame
+ assert_equal box, box::Foo::BOX2_proc.call # proc -> a const on CLASS
+ assert_equal box, box::Foo::BOX3_proc.call # proc -> the current
+ assert_equal box, box::Foo.new.box4 # instance method -> the current
+ assert_equal box, box::Foo.box5 # singleton method -> a const on CLASS
+ assert_equal box, box::Foo.box6 # singleton method -> the current
+ assert_equal box, box::Foo.box6_proc.call # method returns a proc -> the current
+
+ # a block after CFUNC/IFUNC in a method -> the current
+ assert_equal ["#{box.object_id}:false:1", "#{box.object_id}:true:2"], box::Foo.box7
+
+ assert_equal outer, box::Foo.yield_block{ Ruby::Box.current } # method yields
+ assert_equal outer, box::Foo.call_block{ Ruby::Box.current } # method calls a block
+
+ assert_equal 'foo', box::Foo.gvar1 # method refers gvar
+ assert_equal 'bar', $gvar1 # gvar value out of the box
+ assert_equal 'foo', box::Foo.call_toplevel # toplevel method referring gvar
+
+ assert_equal box, box::BOX_X # on TOP frame, referring a class in the current
+ assert_equal box, box::BOX_Y # on TOP frame, referring Kernel method defined by a CFUNC method
+
+ assert_equal "Foo", box::FOO_NAME
+ assert_equal "Foo", box::Foo.name
+ end;
+ end
+
+ def test_very_basic_method_calls_and_constants
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ code = <<~EOC
+ consts = Object.constants
+ [consts.include?(:String), consts.include?(:Array)]
+ EOC
+ assert_equal([true, true], Ruby::Box.current.eval(code))
+ assert_equal([true, true], Ruby::Box.root.eval(code))
+ end;
+ end
+
+ def test_loading_extension_libs_in_main_box_1
+ pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ require "prism"
+ require "optparse"
+ require "date"
+ require "time"
+ require "delegate"
+ require "singleton"
+ require "pp"
+ require "fileutils"
+ require "tempfile"
+ require "tmpdir"
+ require "json"
+ require "psych"
+ require "yaml"
+ expected = 1
+ assert_equal expected, 1
+ end;
+ end
+
+ def test_loading_extension_libs_in_main_box_2
+ pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ require "zlib"
+ require "open3"
+ require "ipaddr"
+ require "net/http"
+ require "openssl"
+ require "socket"
+ require "uri"
+ require "digest"
+ require "erb"
+ require "stringio"
+ require "monitor"
+ require "timeout"
+ require "securerandom"
+ expected = 1
+ assert_equal expected, 1
+ end;
+ end
+
+ def test_mark_box_object_referred_only_from_binding
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ box = Ruby::Box.new
+ box.eval('class Integer; def +(*)=42; end')
+ b = box.eval('binding')
+ box = nil # remove direct reference to the box
+
+ assert_equal 42, b.eval('1+2')
+
+ GC.stress = true
+ GC.start
+
+ assert_equal 42, b.eval('1+2')
+ end;
+ end
+
+ def test_loaded_extension_deleted_in_user_box
+ require 'tmpdir'
+ Dir.mktmpdir do |tmpdir|
+ env = ENV_ENABLE_BOX.merge({'TMPDIR'=>tmpdir})
+ assert_ruby_status([env], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ require "json"
+ end;
+ assert_empty(Dir.children(tmpdir))
+ end
+ end
+
+ def test_root_box_iclasses_should_be_boxable
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ Ruby::Box.root.eval("class IMath; include Math; end") # (*)
+ module Math
+ def foo = :foo
+ end
+ # This test crashes here if iclasses (created at the line (*) is not boxable)
+ class IMath2; include Math; end
+ assert_equal :foo, IMath2.new.foo
+ assert_raise NoMethodError do
+ Ruby::Box.root.eval("IMath.new.foo")
+ end
+ end;
+ end
+
+ def test_user_box_iclass_with_module_modified_in_another_box
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ # A user box creates a class that includes a core module.
+ # The ICLASS is allocated in the user box context (non-boxable).
+ box1 = Ruby::Box.new
+ box1.eval("class IMath; include Math; end")
+
+ # A second user box adds an instance method on that module,
+ # triggering classext duplication which iterates the module's
+ # subclass list and encounters box1's non-boxable ICLASS.
+ box2 = Ruby::Box.new
+ box2.eval("module Math; def box2_test = :box2; end")
+
+ assert_equal :box2, box2.eval("Class.new { include Math }.new.box2_test")
+ end;
+ end
+
+ def test_method_invalidation_between_boxes_1
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ b = Ruby::Box.new
+ b.eval(<<~'RUBY')
+ Module.prepend(Module.new)
+ class C; end
+ class D < C; end
+ def C.===(x) = true
+ RUBY
+
+ assert String === "x"
+ assert b # to prevent GCing b
+ end;
+ end
+
+ def test_method_invalidation_between_boxes_2
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ PrepM = Module.new
+ Module.prepend(PrepM)
+ Module.new.include?(Module.new)
+
+ b = Ruby::Box.new
+ b.eval(<<~'RUBY')
+ Module.class_eval { def _test_method; end }
+
+ class C; end
+ class D < C; end
+ def C.include?(x) = true
+ RUBY
+
+ Module.new.include?(Module.new)
+ end;
+ end
+end
diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb
index ffbda1fdb9..dd1936c4e2 100644
--- a/test/ruby/test_call.rb
+++ b/test/ruby/test_call.rb
@@ -123,6 +123,25 @@ class TestCall < Test::Unit::TestCase
assert_equal([1, 2, {kw: 3}], f(*a, kw: 3))
end
+ def test_forward_argument_init
+ o = Object.new
+ def o.simple_forward_argument_init(a=eval('b'), b=1)
+ [a, b]
+ end
+
+ def o.complex_forward_argument_init(a=eval('b'), b=eval('kw'), kw: eval('kw2'), kw2: 3)
+ [a, b, kw, kw2]
+ end
+
+ def o.keyword_forward_argument_init(a: eval('b'), b: eval('kw'), kw: eval('kw2'), kw2: 3)
+ [a, b, kw, kw2]
+ end
+
+ assert_equal [nil, 1], o.simple_forward_argument_init
+ assert_equal [nil, nil, 3, 3], o.complex_forward_argument_init
+ assert_equal [nil, nil, 3, 3], o.keyword_forward_argument_init
+ end
+
def test_call_bmethod_proc
pr = proc{|sym| sym}
define_singleton_method(:a, &pr)
@@ -374,6 +393,84 @@ class TestCall < Test::Unit::TestCase
assert_equal({splat_modified: false}, b)
end
+ def test_anon_splat
+ r2kh = Hash.ruby2_keywords_hash(kw: 2)
+ r2kea = [r2kh]
+ r2ka = [1, r2kh]
+
+ def self.s(*) ->(*a){a}.call(*) end
+ assert_equal([], s)
+ assert_equal([1], s(1))
+ assert_equal([{kw: 2}], s(kw: 2))
+ assert_equal([{kw: 2}], s(**{kw: 2}))
+ assert_equal([1, {kw: 2}], s(1, kw: 2))
+ assert_equal([1, {kw: 2}], s(1, **{kw: 2}))
+ assert_equal([{kw: 2}], s(*r2kea))
+ assert_equal([1, {kw: 2}], s(*r2ka))
+
+ singleton_class.remove_method(:s)
+ def self.s(*, kw: 0) [*->(*a){a}.call(*), kw] end
+ assert_equal([0], s)
+ assert_equal([1, 0], s(1))
+ assert_equal([2], s(kw: 2))
+ assert_equal([2], s(**{kw: 2}))
+ assert_equal([1, 2], s(1, kw: 2))
+ assert_equal([1, 2], s(1, **{kw: 2}))
+ assert_equal([2], s(*r2kea))
+ assert_equal([1, 2], s(*r2ka))
+
+ singleton_class.remove_method(:s)
+ def self.s(*, **kw) [*->(*a){a}.call(*), kw] end
+ assert_equal([{}], s)
+ assert_equal([1, {}], s(1))
+ assert_equal([{kw: 2}], s(kw: 2))
+ assert_equal([{kw: 2}], s(**{kw: 2}))
+ assert_equal([1, {kw: 2}], s(1, kw: 2))
+ assert_equal([1, {kw: 2}], s(1, **{kw: 2}))
+ assert_equal([{kw: 2}], s(*r2kea))
+ assert_equal([1, {kw: 2}], s(*r2ka))
+
+ singleton_class.remove_method(:s)
+ def self.s(*, kw: 0, **kws) [*->(*a){a}.call(*), kw, kws] end
+ assert_equal([0, {}], s)
+ assert_equal([1, 0, {}], s(1))
+ assert_equal([2, {}], s(kw: 2))
+ assert_equal([2, {}], s(**{kw: 2}))
+ assert_equal([1, 2, {}], s(1, kw: 2))
+ assert_equal([1, 2, {}], s(1, **{kw: 2}))
+ assert_equal([2, {}], s(*r2kea))
+ assert_equal([1, 2, {}], s(*r2ka))
+ end
+
+ def test_anon_splat_mutated_bug_21757
+ args = [1, 2]
+ kw = {bug: true}
+
+ def self.m(*); end
+ m(*args, bug: true)
+ assert_equal(2, args.length)
+
+ proc = ->(*) { }
+ proc.(*args, bug: true)
+ assert_equal(2, args.length)
+
+ def self.m2(*); end
+ m2(*args, **kw)
+ assert_equal(2, args.length)
+
+ proc = ->(*) { }
+ proc.(*args, **kw)
+ assert_equal(2, args.length)
+
+ def self.m3(*, **nil); end
+ assert_raise(ArgumentError) { m3(*args, bug: true) }
+ assert_equal(2, args.length)
+
+ proc = ->(*, **nil) { }
+ assert_raise(ArgumentError) { proc.(*args, bug: true) }
+ assert_equal(2, args.length)
+ end
+
def test_kwsplat_block_eval_order
def self.t(**kw, &b) [kw, b] end
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb
index 456362ef21..82199876ec 100644
--- a/test/ruby/test_class.rb
+++ b/test/ruby/test_class.rb
@@ -259,6 +259,46 @@ class TestClass < Test::Unit::TestCase
assert_raise(TypeError) { BasicObject.dup }
end
+ def test_class_hierarchy_inside_initialize_dup_bug_21538
+ ancestors = sc_ancestors = nil
+ b = Class.new
+ b.define_singleton_method(:initialize_dup) do |x|
+ ancestors = self.ancestors
+ sc_ancestors = singleton_class.ancestors
+ super(x)
+ end
+
+ a = Class.new(b)
+
+ c = a.dup
+
+ expected_ancestors = [c, b, *Object.ancestors]
+ expected_sc_ancestors = [c.singleton_class, b.singleton_class, *Object.singleton_class.ancestors]
+ assert_equal expected_ancestors, ancestors
+ assert_equal expected_sc_ancestors, sc_ancestors
+ assert_equal expected_ancestors, c.ancestors
+ assert_equal expected_sc_ancestors, c.singleton_class.ancestors
+ end
+
+ def test_class_hierarchy_inside_initialize_clone_bug_21538
+ ancestors = sc_ancestors = nil
+ a = Class.new
+ a.define_singleton_method(:initialize_clone) do |x|
+ ancestors = self.ancestors
+ sc_ancestors = singleton_class.ancestors
+ super(x)
+ end
+
+ c = a.clone
+
+ expected_ancestors = [c, *Object.ancestors]
+ expected_sc_ancestors = [c.singleton_class, *Object.singleton_class.ancestors]
+ assert_equal expected_ancestors, ancestors
+ assert_equal expected_sc_ancestors, sc_ancestors
+ assert_equal expected_ancestors, c.ancestors
+ assert_equal expected_sc_ancestors, c.singleton_class.ancestors
+ end
+
def test_singleton_class
assert_raise(TypeError) { 1.extend(Module.new) }
assert_raise(TypeError) { 1.0.extend(Module.new) }
@@ -283,12 +323,8 @@ class TestClass < Test::Unit::TestCase
assert_raise(TypeError, bug6863) { Class.new(Class.allocate) }
allocator = Class.instance_method(:allocate)
- assert_raise_with_message(TypeError, /prohibited/) {
- allocator.bind(Rational).call
- }
- assert_raise_with_message(TypeError, /prohibited/) {
- allocator.bind_call(Rational)
- }
+ assert_nothing_raised { allocator.bind(Rational).call }
+ assert_nothing_raised { allocator.bind_call(Rational) }
end
def test_nonascii_name
@@ -399,21 +435,24 @@ class TestClass < Test::Unit::TestCase
end
class CloneTest
+ TEST = :C0
def foo; TEST; end
end
CloneTest1 = CloneTest.clone
CloneTest2 = CloneTest.clone
class CloneTest1
+ remove_const :TEST
TEST = :C1
end
class CloneTest2
+ remove_const :TEST
TEST = :C2
end
def test_constant_access_from_method_in_cloned_class
- assert_equal :C1, CloneTest1.new.foo, '[Bug #15877]'
- assert_equal :C2, CloneTest2.new.foo, '[Bug #15877]'
+ assert_equal :C0, CloneTest1.new.foo, 'originally [Bug #15877], but behaviour changed'
+ assert_equal :C0, CloneTest2.new.foo, 'originally [Bug #15877], but behaviour changed'
end
def test_invalid_superclass
@@ -565,7 +604,7 @@ class TestClass < Test::Unit::TestCase
obj = Object.new
c = obj.singleton_class
obj.freeze
- assert_raise_with_message(FrozenError, /frozen object/) {
+ assert_raise_with_message(FrozenError, /frozen Object/) {
c.class_eval {def f; end}
}
end
@@ -697,12 +736,37 @@ class TestClass < Test::Unit::TestCase
}
end
+ def test_dynamic_module_cpath_constant_namespace # [Bug #20948]
+ assert_separately([], <<~'RUBY')
+ module M1
+ module Foo
+ X = 1
+ end
+ end
+
+ module M2
+ module Foo
+ X = 2
+ end
+ end
+
+ results = [M1, M2].map do
+ module it::Foo
+ X
+ end
+ end
+ assert_equal([1, 2], results)
+ RUBY
+ end
+
def test_namescope_error_message
m = Module.new
o = m.module_eval "class A\u{3042}; self; end.new"
- assert_raise_with_message(TypeError, /A\u{3042}/) {
- o::Foo
- }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /A\u{3042}/) {
+ o::Foo
+ }
+ end
end
def test_redefinition_mismatch
@@ -841,4 +905,80 @@ CODE
klass.define_method(:bar) {}
assert_equal klass, klass.remove_method(:bar), '[Bug #19164]'
end
+
+ def test_method_table_assignment_just_after_class_init
+ assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", 'm_tbl assignment should be done only when Class object is not promoted'
+ begin;
+ GC.stress = true
+ class C; end
+ end;
+ end
+
+ def test_define_singleton_initialize
+ assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ class C
+ def self.initialize
+ end
+ end
+ end;
+ end
+
+ def test_singleton_cc_invalidation
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
+ class T
+ def hi
+ "hi"
+ end
+ end
+
+ t = T.new
+ t.singleton_class
+
+ def hello(t)
+ t.hi
+ end
+
+ 5.times do
+ hello(t) # populate inline cache on `t.singleton_class`.
+ end
+
+ class T
+ remove_method :hi # invalidate `t.singleton_class` ccs for `hi`
+ end
+
+ assert_raise NoMethodError do
+ hello(t)
+ end
+ end;
+ end
+
+ def test_safe_multi_ractor_subclasses_list_mutation
+ assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}", signal: :SEGV
+ begin;
+ 4.times.map do
+ Ractor.new do
+ 20_000.times do
+ Object.new.singleton_class
+ end
+ end
+ end.each(&:join)
+ end;
+ end
+
+ def test_safe_multi_ractor_singleton_class_access
+ assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}"
+ begin;
+ class A; end
+ 4.times.map do
+ Ractor.new do
+ a = A
+ 100.times do
+ a = a.singleton_class
+ end
+ end
+ end.each(&:join)
+ end;
+ end
end
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
index 819d0d35aa..c017111c0a 100644
--- a/test/ruby/test_compile_prism.rb
+++ b/test/ruby/test_compile_prism.rb
@@ -1046,13 +1046,19 @@ module Prism
end
def test_ForNode
- assert_prism_eval("for i in [1,2] do; i; end")
- assert_prism_eval("for @i in [1,2] do; @i; end")
- assert_prism_eval("for $i in [1,2] do; $i; end")
+ assert_prism_eval("r = []; for i in [1,2] do; r << i; end; r")
+ assert_prism_eval("r = []; for @i in [1,2] do; r << @i; end; r")
+ assert_prism_eval("r = []; for $i in [1,2] do; r << $i; end; r")
- assert_prism_eval("for foo, in [1,2,3] do end")
+ assert_prism_eval("r = []; for foo, in [1,2,3] do r << foo end; r")
- assert_prism_eval("for i, j in {a: 'b'} do; i; j; end")
+ assert_prism_eval("r = []; for i, j in {a: 'b'} do; r << [i, j]; end; r")
+
+ # Test splat node as index in for loop
+ assert_prism_eval("r = []; for *x in [[1,2], [3,4]] do; r << x; end; r")
+ assert_prism_eval("r = []; for * in [[1,2], [3,4]] do; r << 'ok'; end; r")
+ assert_prism_eval("r = []; for x, * in [[1,2], [3,4]] do; r << x; end; r")
+ assert_prism_eval("r = []; for x, *y in [[1,2], [3,4]] do; r << [x, y]; end; r")
end
############################################################################
@@ -2180,6 +2186,56 @@ end
RUBY
end
+ def test_ForwardingArgumentsNode_instruction_sequence_consistency
+ # Test that both parsers generate identical instruction sequences for forwarding arguments
+ # This prevents regressions like the one fixed in prism_compile.c for PM_FORWARDING_ARGUMENTS_NODE
+
+ # Test case from the bug report: def bar(buz, ...) = foo(buz, ...)
+ source = <<~RUBY
+ def foo(*, &block) = block
+ def bar(buz, ...) = foo(buz, ...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test simple forwarding
+ source = <<~RUBY
+ def target(...) = nil
+ def forwarder(...) = target(...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test mixed forwarding with regular arguments
+ source = <<~RUBY
+ def target(a, b, c) = [a, b, c]
+ def forwarder(x, ...) = target(x, ...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test forwarding with splat
+ source = <<~RUBY
+ def target(a, b, c) = [a, b, c]
+ def forwarder(x, ...); target(*x, ...); end
+ RUBY
+
+ compare_instruction_sequences(source)
+ end
+
+ private
+
+ def compare_instruction_sequences(source)
+ # Get instruction sequences from both parsers
+ parsey_iseq = RubyVM::InstructionSequence.compile_parsey(source)
+ prism_iseq = RubyVM::InstructionSequence.compile_prism(source)
+
+ # Compare instruction sequences
+ assert_equal parsey_iseq.disasm, prism_iseq.disasm
+ end
+
+ public
+
def test_ForwardingSuperNode
assert_prism_eval("class Forwarding; def to_s; super; end; end")
assert_prism_eval("class Forwarding; def eval(code); super { code }; end; end")
@@ -2638,7 +2694,7 @@ end
# Errors #
############################################################################
- def test_MissingNode
+ def test_ErrorRecoveryNode
# TODO
end
@@ -2665,6 +2721,12 @@ end
assert_raise TypeError do
RubyVM::InstructionSequence.compile_file_prism(nil)
end
+
+ assert_nothing_raised(Errno::EMFILE, Errno::ENFILE) do
+ 10000.times do
+ RubyVM::InstructionSequence.compile_file_prism(File::NULL)
+ end
+ end
end
private
diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb
index bb38f8ec91..4818c8acb7 100644
--- a/test/ruby/test_data.rb
+++ b/test/ruby/test_data.rb
@@ -69,15 +69,33 @@ class TestData < Test::Unit::TestCase
assert_equal(1, test_kw.foo)
assert_equal(2, test_kw.bar)
assert_equal(test_kw, klass.new(foo: 1, bar: 2))
+ assert_equal(test_kw, klass.new('foo' => 1, 'bar' => 2))
assert_equal(test_kw, test)
# Wrong protocol
assert_raise(ArgumentError) { klass.new(1) }
assert_raise(ArgumentError) { klass.new(1, 2, 3) }
- assert_raise(ArgumentError) { klass.new(foo: 1) }
- assert_raise(ArgumentError) { klass.new(foo: 1, bar: 2, baz: 3) }
- # Could be converted to foo: 1, bar: 2, but too smart is confusing
- assert_raise(ArgumentError) { klass.new(1, bar: 2) }
+ assert_raise(TypeError) do
+ klass.new(0 => 1, 1 => 2)
+ end
+ assert_raise(TypeError) do
+ klass.new(foo: 0, bar: 2, 0 => 1)
+ end
+ assert_raise_with_message(ArgumentError, "missing keyword: :bar") do
+ klass.new(foo: 1)
+ end
+ assert_raise_with_message(ArgumentError, "missing keyword: :bar") do
+ klass.new('foo' => 1)
+ end
+ assert_raise_with_message(ArgumentError, "missing keyword: :bar") do
+ klass.new(foo: 1, 'foo' => 1)
+ end
+ assert_raise_with_message(ArgumentError, "missing keywords: :foo, :bar") do
+ klass.new(x: 1, y: 2)
+ end
+ assert_raise_with_message(ArgumentError, "unknown keyword: :baz") do
+ klass.new(foo: 1, bar: 2, baz: 3)
+ end
end
def test_initialize_redefine
@@ -259,9 +277,10 @@ class TestData < Test::Unit::TestCase
assert_equal(klass.new, test)
assert_not_equal(Data.define.new, test)
- assert_equal('#<data >', test.inspect)
+ assert_equal('#<data>', test.inspect)
assert_equal([], test.members)
assert_equal({}, test.to_h)
+ assert_predicate(test, :frozen?)
end
def test_dup
@@ -280,4 +299,10 @@ class TestData < Test::Unit::TestCase
assert_not_same(test, loaded)
assert_predicate(loaded, :frozen?)
end
+
+ def test_frozen_subclass
+ test = Class.new(Data.define(:a)).freeze.new(a: 0)
+ assert_kind_of(Data, test)
+ assert_equal([:a], test.members)
+ end
end
diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb
index 3a8065d959..75ed1a7534 100644
--- a/test/ruby/test_defined.rb
+++ b/test/ruby/test_defined.rb
@@ -62,6 +62,34 @@ class TestDefined < Test::Unit::TestCase
f.bar(Class.new(Foo).new) { |v| assert(v, "inherited protected method") }
end
+ module ProtectedInModule
+ def m
+ :m
+ end
+ protected :m
+ def call_m(o)
+ o.m
+ end
+ def defined_m(o)
+ defined?(o.m)
+ end
+ end
+ class ProtectedIncluderA
+ include ProtectedInModule
+ end
+ class ProtectedIncluderB
+ include ProtectedInModule
+ end
+
+ def test_defined_protected_method_in_included_module
+ a = ProtectedIncluderA.new
+ b = ProtectedIncluderB.new
+ assert_equal(:m, a.call_m(a))
+ assert_equal(:m, a.call_m(b))
+ assert_equal("method", a.defined_m(a))
+ assert_equal("method", a.defined_m(b))
+ end
+
def test_defined_undefined_method
f = Foo.new
assert_nil(defined?(f.quux)) # undefined method
@@ -243,6 +271,26 @@ class TestDefined < Test::Unit::TestCase
assert_nil(defined?(p () + 1))
end
+ def test_defined_paren_void_stmts
+ assert_equal("expression", defined? (;x))
+ assert_equal("expression", defined? (x;))
+ assert_nil(defined? (
+
+ x
+
+ ))
+
+ x = 1
+
+ assert_equal("expression", defined? (;x))
+ assert_equal("expression", defined? (x;))
+ assert_equal("local-variable", defined? (
+
+ x
+
+ ))
+ end
+
def test_defined_impl_specific
feature7035 = '[ruby-core:47558]' # not spec
assert_predicate(defined?(Foo), :frozen?, feature7035)
diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb
index 78371a096b..edb5210af1 100644
--- a/test/ruby/test_dir.rb
+++ b/test/ruby/test_dir.rb
@@ -641,6 +641,21 @@ class TestDir < Test::Unit::TestCase
assert_equal("C:/ruby/homepath", Dir.home)
end;
end
+
+ def test_children_long_name
+ Dir.mktmpdir do |dirname|
+ longest_possible_component = "b" * 255
+ long_path = File.join(dirname, longest_possible_component)
+ Dir.mkdir(long_path)
+ File.write("#{long_path}/c", "")
+ assert_equal(%w[c], Dir.children(long_path))
+ ensure
+ File.unlink("#{long_path}/c")
+ Dir.rmdir(long_path)
+ end
+ rescue Errno::ENOENT
+ omit "File system does not support long file name"
+ end
end
def test_home
diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb
index 388b94df39..0cd5bf49dc 100644
--- a/test/ruby/test_encoding.rb
+++ b/test/ruby/test_encoding.rb
@@ -33,7 +33,7 @@ class TestEncoding < Test::Unit::TestCase
encodings.each do |e|
assert_raise(TypeError) { e.dup }
assert_raise(TypeError) { e.clone }
- assert_equal(e.object_id, Marshal.load(Marshal.dump(e)).object_id)
+ assert_same(e, Marshal.load(Marshal.dump(e)))
end
end
@@ -130,10 +130,50 @@ class TestEncoding < Test::Unit::TestCase
def test_ractor_load_encoding
assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
begin;
- Ractor.new{}.take
+ Ractor.new{}.join
$-w = nil
Encoding.default_external = Encoding::ISO8859_2
assert "[Bug #19562]"
end;
end
+
+ def test_ractor_lazy_load_encoding_concurrently
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ rs = []
+ autoload_encodings = Encoding.list.select { |e| e.inspect.include?("(autoload)") }.freeze
+ 7.times do
+ rs << Ractor.new(autoload_encodings) do |encodings|
+ str = "abc".dup
+ encodings.each do |enc|
+ str.force_encoding(enc)
+ end
+ end
+ end
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
+ def test_ractor_set_default_external_string
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $-w = nil
+ rs = []
+ 7.times do |i|
+ rs << Ractor.new(i) do |i|
+ Encoding.default_external = "us-ascii"
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
end
diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb
index 237bdc8a4d..32ec4f5779 100644
--- a/test/ruby/test_enum.rb
+++ b/test/ruby/test_enum.rb
@@ -69,11 +69,11 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(['z', 42, nil], [:a, 'b', 'z', :c, 42, nil].grep_v(/[a-d]/), bug17030)
assert_equal('match', $1, bug17030)
- regexp = Regexp.new('x')
- assert_equal([], @obj.grep(regexp), bug17030) # sanity check
- def regexp.===(other)
- true
- end
+ regexp = Class.new(Regexp) {
+ def ===(other)
+ true
+ end
+ }.new('x')
assert_equal([1, 2, 3, 1, 2], @obj.grep(regexp), bug17030)
o = Object.new
diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb
index cd62cd8acb..9b972d7b22 100644
--- a/test/ruby/test_enumerator.rb
+++ b/test/ruby/test_enumerator.rb
@@ -886,6 +886,7 @@ class TestEnumerator < Test::Unit::TestCase
def test_produce
assert_raise(ArgumentError) { Enumerator.produce }
+ assert_raise(ArgumentError) { Enumerator.produce(a: 1, b: 1) {} }
# Without initial object
passed_args = []
@@ -903,14 +904,6 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal [1, 2, 3], enum.take(3)
assert_equal [1, 2], passed_args
- # With initial keyword arguments
- passed_args = []
- enum = Enumerator.produce(a: 1, b: 1) { |obj| passed_args << obj; obj.shift if obj.respond_to?(:shift)}
- assert_instance_of(Enumerator, enum)
- assert_equal Float::INFINITY, enum.size
- assert_equal [{b: 1}, [1], :a, nil], enum.take(4)
- assert_equal [{b: 1}, [1], :a], passed_args
-
# Raising StopIteration
words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/)
enum = Enumerator.produce { words.shift or raise StopIteration }
@@ -935,6 +928,25 @@ class TestEnumerator < Test::Unit::TestCase
"abc",
], enum.to_a
}
+
+ # With size keyword argument
+ enum = Enumerator.produce(1, size: 10) { |obj| obj.succ }
+ assert_equal 10, enum.size
+ assert_equal [1, 2, 3], enum.take(3)
+
+ enum = Enumerator.produce(1, size: -> { 5 }) { |obj| obj.succ }
+ assert_equal 5, enum.size
+
+ enum = Enumerator.produce(1, size: nil) { |obj| obj.succ }
+ assert_equal nil, enum.size
+
+ enum = Enumerator.produce(1, size: Float::INFINITY) { |obj| obj.succ }
+ assert_equal Float::INFINITY, enum.size
+
+ # Without initial value but with size
+ enum = Enumerator.produce(size: 3) { |obj| (obj || 0).succ }
+ assert_equal 3, enum.size
+ assert_equal [1, 2, 3], enum.take(3)
end
def test_chain_each_lambda
diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb
index c9ec920ea9..dd526544af 100644
--- a/test/ruby/test_env.rb
+++ b/test/ruby/test_env.rb
@@ -281,6 +281,26 @@ class TestEnv < Test::Unit::TestCase
assert_equal(["foo", "foo"], ENV.values_at("test", "test"))
end
+ def test_fetch_values
+ ENV["test"] = "foo"
+ ENV["test2"] = "bar"
+ assert_equal(["foo", "bar"], ENV.fetch_values("test", "test2"))
+ assert_equal(["foo", "foo"], ENV.fetch_values("test", "test"))
+ assert_equal([], ENV.fetch_values)
+
+ ENV.delete("test2")
+ assert_raise(KeyError) { ENV.fetch_values("test", "test2") }
+
+ assert_equal(["foo", "default"], ENV.fetch_values("test", "test2") { "default" })
+ assert_equal(["foo", "TEST2"], ENV.fetch_values("test", "test2") { |k| k.upcase })
+
+ e = assert_raise(KeyError) { ENV.fetch_values("test2") }
+ assert_same(ENV, e.receiver)
+ assert_equal("test2", e.key)
+
+ assert_invalid_env {|v| ENV.fetch_values(v)}
+ end
+
def test_select
ENV["test"] = "foo"
h = ENV.select {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
@@ -601,13 +621,13 @@ class TestEnv < Test::Unit::TestCase
rescue Exception => e
#{exception_var} = e
end
- Ractor.yield #{exception_var}.class
+ port.send #{exception_var}.class
end;
end
def str_for_assert_raise_on_yielded_exception_class(expected_error_class, ractor_var)
<<-"end;"
- error_class = #{ractor_var}.take
+ error_class = #{ractor_var}.receive
assert_raise(#{expected_error_class}) do
if error_class < Exception
raise error_class
@@ -649,100 +669,101 @@ class TestEnv < Test::Unit::TestCase
def test_bracket_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- Ractor.yield ENV['test']
- Ractor.yield ENV['TEST']
+ Ractor.new port = Ractor::Port.new do |port|
+ port << ENV['test']
+ port << ENV['TEST']
ENV['test'] = 'foo'
- Ractor.yield ENV['test']
- Ractor.yield ENV['TEST']
+ port << ENV['test']
+ port << ENV['TEST']
ENV['TEST'] = 'bar'
- Ractor.yield ENV['TEST']
- Ractor.yield ENV['test']
+ port << ENV['TEST']
+ port << ENV['test']
#{str_for_yielding_exception_class("ENV[1]")}
#{str_for_yielding_exception_class("ENV[1] = 'foo'")}
#{str_for_yielding_exception_class("ENV['test'] = 0")}
end
- assert_nil(r.take)
- assert_nil(r.take)
- assert_equal('foo', r.take)
+ assert_nil(port.receive)
+ assert_nil(port.receive)
+ assert_equal('foo', port.receive)
if #{ignore_case_str}
- assert_equal('foo', r.take)
+ assert_equal('foo', port.receive)
else
- assert_nil(r.take)
+ assert_nil(port.receive)
end
- assert_equal('bar', r.take)
+ assert_equal('bar', port.receive)
if #{ignore_case_str}
- assert_equal('bar', r.take)
+ assert_equal('bar', port.receive)
else
- assert_equal('foo', r.take)
+ assert_equal('foo', port.receive)
end
3.times do
- #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")}
end
end;
end
def test_dup_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_for_yielding_exception_class("ENV.dup")}
end
- #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")}
end;
end
def test_has_value_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ port = Ractor::Port.new
+ Ractor.new port do |port|
val = 'a'
val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
ENV['test'] = val[0...-1]
- Ractor.yield(ENV.has_value?(val))
- Ractor.yield(ENV.has_value?(val.upcase))
+ port.send(ENV.has_value?(val))
+ port.send(ENV.has_value?(val.upcase))
ENV['test'] = val
- Ractor.yield(ENV.has_value?(val))
- Ractor.yield(ENV.has_value?(val.upcase))
+ port.send(ENV.has_value?(val))
+ port.send(ENV.has_value?(val.upcase))
ENV['test'] = val.upcase
- Ractor.yield ENV.has_value?(val)
- Ractor.yield ENV.has_value?(val.upcase)
- end
- assert_equal(false, r.take)
- assert_equal(false, r.take)
- assert_equal(true, r.take)
- assert_equal(false, r.take)
- assert_equal(false, r.take)
- assert_equal(true, r.take)
+ port.send ENV.has_value?(val)
+ port.send ENV.has_value?(val.upcase)
+ end
+ assert_equal(false, port.receive)
+ assert_equal(false, port.receive)
+ assert_equal(true, port.receive)
+ assert_equal(false, port.receive)
+ assert_equal(false, port.receive)
+ assert_equal(true, port.receive)
end;
end
def test_key_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
val = 'a'
val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
ENV['test'] = val[0...-1]
- Ractor.yield ENV.key(val)
- Ractor.yield ENV.key(val.upcase)
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
ENV['test'] = val
- Ractor.yield ENV.key(val)
- Ractor.yield ENV.key(val.upcase)
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
ENV['test'] = val.upcase
- Ractor.yield ENV.key(val)
- Ractor.yield ENV.key(val.upcase)
+ port.send ENV.key(val)
+ port.send ENV.key(val.upcase)
end
- assert_nil(r.take)
- assert_nil(r.take)
+ assert_nil(port.receive)
+ assert_nil(port.receive)
if #{ignore_case_str}
- assert_equal('TEST', r.take.upcase)
+ assert_equal('TEST', port.receive.upcase)
else
- assert_equal('test', r.take)
+ assert_equal('test', port.receive)
end
- assert_nil(r.take)
- assert_nil(r.take)
+ assert_nil(port.receive)
+ assert_nil(port.receive)
if #{ignore_case_str}
- assert_equal('TEST', r.take.upcase)
+ assert_equal('TEST', port.receive.upcase)
else
- assert_equal('test', r.take)
+ assert_equal('test', port.receive)
end
end;
@@ -750,87 +771,87 @@ class TestEnv < Test::Unit::TestCase
def test_delete_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_to_yield_invalid_envvar_errors("v", "ENV.delete(v)")}
- Ractor.yield ENV.delete("TEST")
+ port.send ENV.delete("TEST")
#{str_for_yielding_exception_class("ENV.delete('#{PATH_ENV}')")}
- Ractor.yield(ENV.delete("TEST"){|name| "NO "+name})
+ port.send(ENV.delete("TEST"){|name| "NO "+name})
end
- #{str_to_receive_invalid_envvar_errors("r")}
- assert_nil(r.take)
- exception_class = r.take
+ #{str_to_receive_invalid_envvar_errors("port")}
+ assert_nil(port.receive)
+ exception_class = port.receive
assert_equal(NilClass, exception_class)
- assert_equal("NO TEST", r.take)
+ assert_equal("NO TEST", port.receive)
end;
end
def test_getenv_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_to_yield_invalid_envvar_errors("v", "ENV[v]")}
ENV["#{PATH_ENV}"] = ""
- Ractor.yield ENV["#{PATH_ENV}"]
- Ractor.yield ENV[""]
+ port.send ENV["#{PATH_ENV}"]
+ port.send ENV[""]
end
- #{str_to_receive_invalid_envvar_errors("r")}
- assert_equal("", r.take)
- assert_nil(r.take)
+ #{str_to_receive_invalid_envvar_errors("port")}
+ assert_equal("", port.receive)
+ assert_nil(port.receive)
end;
end
def test_fetch_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["test"] = "foo"
- Ractor.yield ENV.fetch("test")
+ port.send ENV.fetch("test")
ENV.delete("test")
#{str_for_yielding_exception_class("ENV.fetch('test')", exception_var: "ex")}
- Ractor.yield ex.receiver.object_id
- Ractor.yield ex.key
- Ractor.yield ENV.fetch("test", "foo")
- Ractor.yield(ENV.fetch("test"){"bar"})
+ port.send ex.receiver.object_id
+ port.send ex.key
+ port.send ENV.fetch("test", "foo")
+ port.send(ENV.fetch("test"){"bar"})
#{str_to_yield_invalid_envvar_errors("v", "ENV.fetch(v)")}
#{str_for_yielding_exception_class("ENV.fetch('#{PATH_ENV}', 'foo')")}
ENV['#{PATH_ENV}'] = ""
- Ractor.yield ENV.fetch('#{PATH_ENV}')
- end
- assert_equal("foo", r.take)
- #{str_for_assert_raise_on_yielded_exception_class(KeyError, "r")}
- assert_equal(ENV.object_id, r.take)
- assert_equal("test", r.take)
- assert_equal("foo", r.take)
- assert_equal("bar", r.take)
- #{str_to_receive_invalid_envvar_errors("r")}
- exception_class = r.take
+ port.send ENV.fetch('#{PATH_ENV}')
+ end
+ assert_equal("foo", port.receive)
+ #{str_for_assert_raise_on_yielded_exception_class(KeyError, "port")}
+ assert_equal(ENV.object_id, port.receive)
+ assert_equal("test", port.receive)
+ assert_equal("foo", port.receive)
+ assert_equal("bar", port.receive)
+ #{str_to_receive_invalid_envvar_errors("port")}
+ exception_class = port.receive
assert_equal(NilClass, exception_class)
- assert_equal("", r.take)
+ assert_equal("", port.receive)
end;
end
def test_aset_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_for_yielding_exception_class("ENV['test'] = nil")}
ENV["test"] = nil
- Ractor.yield ENV["test"]
+ port.send ENV["test"]
#{str_to_yield_invalid_envvar_errors("v", "ENV[v] = 'test'")}
#{str_to_yield_invalid_envvar_errors("v", "ENV['test'] = v")}
end
- exception_class = r.take
+ exception_class = port.receive
assert_equal(NilClass, exception_class)
- assert_nil(r.take)
- #{str_to_receive_invalid_envvar_errors("r")}
- #{str_to_receive_invalid_envvar_errors("r")}
+ assert_nil(port.receive)
+ #{str_to_receive_invalid_envvar_errors("port")}
+ #{str_to_receive_invalid_envvar_errors("port")}
end;
end
def test_keys_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
a = ENV.keys
- Ractor.yield a
+ port.send a
end
- a = r.take
+ a = port.receive
assert_kind_of(Array, a)
a.each {|k| assert_kind_of(String, k) }
end;
@@ -839,11 +860,11 @@ class TestEnv < Test::Unit::TestCase
def test_each_key_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- ENV.each_key {|k| Ractor.yield(k)}
- Ractor.yield "finished"
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_key {|k| port.send(k)}
+ port.send "finished"
end
- while((x=r.take) != "finished")
+ while((x=port.receive) != "finished")
assert_kind_of(String, x)
end
end;
@@ -851,11 +872,11 @@ class TestEnv < Test::Unit::TestCase
def test_values_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
a = ENV.values
- Ractor.yield a
+ port.send a
end
- a = r.take
+ a = port.receive
assert_kind_of(Array, a)
a.each {|k| assert_kind_of(String, k) }
end;
@@ -863,11 +884,11 @@ class TestEnv < Test::Unit::TestCase
def test_each_value_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- ENV.each_value {|k| Ractor.yield(k)}
- Ractor.yield "finished"
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_value {|k| port.send(k)}
+ port.send "finished"
end
- while((x=r.take) != "finished")
+ while((x=port.receive) != "finished")
assert_kind_of(String, x)
end
end;
@@ -875,11 +896,11 @@ class TestEnv < Test::Unit::TestCase
def test_each_pair_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- ENV.each_pair {|k, v| Ractor.yield([k,v])}
- Ractor.yield "finished"
+ Ractor.new port = Ractor::Port.new do |port|
+ ENV.each_pair {|k, v| port.send([k,v])}
+ port.send "finished"
end
- while((k,v=r.take) != "finished")
+ while((k,v=port.receive) != "finished")
assert_kind_of(String, k)
assert_kind_of(String, v)
end
@@ -888,116 +909,116 @@ class TestEnv < Test::Unit::TestCase
def test_reject_bang_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
+ port.send [h1, h2]
+ port.send(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_nil(r.take)
+ assert_nil(port.receive)
end;
end
def test_delete_if_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }).object_id
+ port.send [h1, h2]
+ port.send (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_equal(ENV.object_id, r.take)
+ assert_same(ENV, port.receive)
end;
end
def test_select_bang_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ port.send [h1, h2]
+ port.send(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_nil(r.take)
+ assert_nil(port.receive)
end;
end
def test_filter_bang_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ port.send [h1, h2]
+ port.send(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_nil(r.take)
+ assert_nil(port.receive)
end;
end
def test_keep_if_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
h2 = {}
ENV.each_pair {|k, v| h2[k] = v }
- Ractor.yield [h1, h2]
- Ractor.yield (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }).object_id
+ port.send [h1, h2]
+ port.send (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
- assert_equal(ENV.object_id, r.take)
+ assert_equal(ENV, port.receive)
end;
end
def test_values_at_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["test"] = "foo"
- Ractor.yield ENV.values_at("test", "test")
+ port.send ENV.values_at("test", "test")
end
- assert_equal(["foo", "foo"], r.take)
+ assert_equal(["foo", "foo"], port.receive)
end;
end
def test_select_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["test"] = "foo"
h = ENV.select {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
- Ractor.yield h.size
+ port.send h.size
k = h.keys.first
v = h.values.first
- Ractor.yield [k, v]
+ port.send [k, v]
end
- assert_equal(1, r.take)
- k, v = r.take
+ assert_equal(1, port.receive)
+ k, v = port.receive
if #{ignore_case_str}
assert_equal("TEST", k.upcase)
assert_equal("FOO", v.upcase)
@@ -1010,16 +1031,16 @@ class TestEnv < Test::Unit::TestCase
def test_filter_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["test"] = "foo"
h = ENV.filter {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
- Ractor.yield(h.size)
+ port.send(h.size)
k = h.keys.first
v = h.values.first
- Ractor.yield [k, v]
+ port.send [k, v]
end
- assert_equal(1, r.take)
- k, v = r.take
+ assert_equal(1, port.receive)
+ k, v = port.receive
if #{ignore_case_str}
assert_equal("TEST", k.upcase)
assert_equal("FOO", v.upcase)
@@ -1032,49 +1053,49 @@ class TestEnv < Test::Unit::TestCase
def test_slice_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV["bar"] = "rab"
- Ractor.yield(ENV.slice())
- Ractor.yield(ENV.slice(""))
- Ractor.yield(ENV.slice("unknown"))
- Ractor.yield(ENV.slice("foo", "baz"))
- end
- assert_equal({}, r.take)
- assert_equal({}, r.take)
- assert_equal({}, r.take)
- assert_equal({"foo"=>"bar", "baz"=>"qux"}, r.take)
+ port.send(ENV.slice())
+ port.send(ENV.slice(""))
+ port.send(ENV.slice("unknown"))
+ port.send(ENV.slice("foo", "baz"))
+ end
+ assert_equal({}, port.receive)
+ assert_equal({}, port.receive)
+ assert_equal({}, port.receive)
+ assert_equal({"foo"=>"bar", "baz"=>"qux"}, port.receive)
end;
end
def test_except_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV["bar"] = "rab"
- Ractor.yield ENV.except()
- Ractor.yield ENV.except("")
- Ractor.yield ENV.except("unknown")
- Ractor.yield ENV.except("foo", "baz")
- end
- assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
- assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
- assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
- assert_equal({"bar"=>"rab"}, r.take)
+ port.send ENV.except()
+ port.send ENV.except("")
+ port.send ENV.except("unknown")
+ port.send ENV.except("foo", "baz")
+ end
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive)
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive)
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive)
+ assert_equal({"bar"=>"rab"}, port.receive)
end;
end
def test_clear_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
- Ractor.yield ENV.size
+ port.send ENV.size
end
- assert_equal(0, r.take)
+ assert_equal(0, port.receive)
end;
end
@@ -1083,20 +1104,20 @@ class TestEnv < Test::Unit::TestCase
r = Ractor.new do
ENV.to_s
end
- assert_equal("ENV", r.take)
+ assert_equal("ENV", r.value)
end;
end
def test_inspect_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
s = ENV.inspect
- Ractor.yield s
+ port.send s
end
- s = r.take
+ s = port.receive
expected = ['"foo" => "bar"', '"baz" => "qux"']
unless s.start_with?(/\{"foo"/i)
expected.reverse!
@@ -1112,14 +1133,14 @@ class TestEnv < Test::Unit::TestCase
def test_to_a_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
a = ENV.to_a
- Ractor.yield a
+ port.send a
end
- a = r.take
+ a = port.receive
assert_equal(2, a.size)
expected = [%w(baz qux), %w(foo bar)]
if #{ignore_case_str}
@@ -1136,59 +1157,59 @@ class TestEnv < Test::Unit::TestCase
r = Ractor.new do
ENV.rehash
end
- assert_nil(r.take)
+ assert_nil(r.value)
end;
end
def test_size_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
s = ENV.size
ENV["test"] = "foo"
- Ractor.yield [s, ENV.size]
+ port.send [s, ENV.size]
end
- s, s2 = r.take
+ s, s2 = port.receive
assert_equal(s + 1, s2)
end;
end
def test_empty_p_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
- Ractor.yield ENV.empty?
+ port.send ENV.empty?
ENV["test"] = "foo"
- Ractor.yield ENV.empty?
+ port.send ENV.empty?
end
- assert r.take
- assert !r.take
+ assert port.receive
+ assert !port.receive
end;
end
def test_has_key_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- Ractor.yield ENV.has_key?("test")
+ Ractor.new port = Ractor::Port.new do |port|
+ port.send ENV.has_key?("test")
ENV["test"] = "foo"
- Ractor.yield ENV.has_key?("test")
+ port.send ENV.has_key?("test")
#{str_to_yield_invalid_envvar_errors("v", "ENV.has_key?(v)")}
end
- assert !r.take
- assert r.take
- #{str_to_receive_invalid_envvar_errors("r")}
+ assert !port.receive
+ assert port.receive
+ #{str_to_receive_invalid_envvar_errors("port")}
end;
end
def test_assoc_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- Ractor.yield ENV.assoc("test")
+ Ractor.new port = Ractor::Port.new do |port|
+ port.send ENV.assoc("test")
ENV["test"] = "foo"
- Ractor.yield ENV.assoc("test")
+ port.send ENV.assoc("test")
#{str_to_yield_invalid_envvar_errors("v", "ENV.assoc(v)")}
end
- assert_nil(r.take)
- k, v = r.take
+ assert_nil(port.receive)
+ k, v = port.receive
if #{ignore_case_str}
assert_equal("TEST", k.upcase)
assert_equal("FOO", v.upcase)
@@ -1196,7 +1217,7 @@ class TestEnv < Test::Unit::TestCase
assert_equal("test", k)
assert_equal("foo", v)
end
- #{str_to_receive_invalid_envvar_errors("r")}
+ #{str_to_receive_invalid_envvar_errors("port")}
encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale")
assert_equal(encoding, v.encoding)
end;
@@ -1204,29 +1225,29 @@ class TestEnv < Test::Unit::TestCase
def test_has_value2_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
- Ractor.yield ENV.has_value?("foo")
+ port.send ENV.has_value?("foo")
ENV["test"] = "foo"
- Ractor.yield ENV.has_value?("foo")
+ port.send ENV.has_value?("foo")
end
- assert !r.take
- assert r.take
+ assert !port.receive
+ assert port.receive
end;
end
def test_rassoc_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
- Ractor.yield ENV.rassoc("foo")
+ port.send ENV.rassoc("foo")
ENV["foo"] = "bar"
ENV["test"] = "foo"
ENV["baz"] = "qux"
- Ractor.yield ENV.rassoc("foo")
+ port.send ENV.rassoc("foo")
end
- assert_nil(r.take)
- k, v = r.take
+ assert_nil(port.receive)
+ k, v = port.receive
if #{ignore_case_str}
assert_equal("TEST", k.upcase)
assert_equal("FOO", v.upcase)
@@ -1239,39 +1260,39 @@ class TestEnv < Test::Unit::TestCase
def test_to_hash_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h = {}
ENV.each {|k, v| h[k] = v }
- Ractor.yield [h, ENV.to_hash]
+ port.send [h, ENV.to_hash]
end
- h, h2 = r.take
+ h, h2 = port.receive
assert_equal(h, h2)
end;
end
def test_to_h_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
- Ractor.yield [ENV.to_hash, ENV.to_h]
- Ractor.yield [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}]
+ Ractor.new port = Ractor::Port.new do |port|
+ port.send [ENV.to_hash, ENV.to_h]
+ port.send [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}]
end
- a, b = r.take
+ a, b = port.receive
assert_equal(a,b)
- c, d = r.take
+ c, d = port.receive
assert_equal(c,d)
end;
end
def test_reject_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
h1 = {}
ENV.each_pair {|k, v| h1[k] = v }
ENV["test"] = "foo"
h2 = ENV.reject {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
- Ractor.yield [h1, h2]
+ port.send [h1, h2]
end
- h1, h2 = r.take
+ h1, h2 = port.receive
assert_equal(h1, h2)
end;
end
@@ -1279,86 +1300,86 @@ class TestEnv < Test::Unit::TestCase
def test_shift_in_ractor
assert_ractor(<<-"end;")
#{STR_DEFINITION_FOR_CHECK}
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
a = ENV.shift
b = ENV.shift
- Ractor.yield [a,b]
- Ractor.yield ENV.shift
+ port.send [a,b]
+ port.send ENV.shift
end
- a,b = r.take
+ a,b = port.receive
check([a, b], [%w(foo bar), %w(baz qux)])
- assert_nil(r.take)
+ assert_nil(port.receive)
end;
end
def test_invert_in_ractor
assert_ractor(<<-"end;")
#{STR_DEFINITION_FOR_CHECK}
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
- Ractor.yield(ENV.invert)
+ port.send(ENV.invert)
end
- check(r.take.to_a, [%w(bar foo), %w(qux baz)])
+ check(port.receive.to_a, [%w(bar foo), %w(qux baz)])
end;
end
def test_replace_in_ractor
assert_ractor(<<-"end;")
#{STR_DEFINITION_FOR_CHECK}
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["foo"] = "xxx"
ENV.replace({"foo"=>"bar", "baz"=>"qux"})
- Ractor.yield ENV.to_hash
+ port.send ENV.to_hash
ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"})
- Ractor.yield ENV.to_hash
+ port.send ENV.to_hash
end
- check(r.take.to_a, [%w(foo bar), %w(baz qux)])
- check(r.take.to_a, [%w(Foo Bar), %w(Baz Qux)])
+ check(port.receive.to_a, [%w(foo bar), %w(baz qux)])
+ check(port.receive.to_a, [%w(Foo Bar), %w(Baz Qux)])
end;
end
def test_update_in_ractor
assert_ractor(<<-"end;")
#{STR_DEFINITION_FOR_CHECK}
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"})
- Ractor.yield ENV.to_hash
+ port.send ENV.to_hash
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
- Ractor.yield ENV.to_hash
+ port.send ENV.to_hash
end
- check(r.take.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
- check(r.take.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ check(port.receive.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ check(port.receive.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
end;
end
def test_huge_value_in_ractor
assert_ractor(<<-"end;")
huge_value = "bar" * 40960
- r = Ractor.new huge_value do |v|
+ Ractor.new port = Ractor::Port.new, huge_value do |port, v|
ENV["foo"] = "bar"
#{str_for_yielding_exception_class("ENV['foo'] = v ")}
- Ractor.yield ENV["foo"]
+ port.send ENV["foo"]
end
if /mswin|ucrt/ =~ RUBY_PLATFORM
- #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "r")}
- result = r.take
+ #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "port")}
+ result = port.receive
assert_equal("bar", result)
else
- exception_class = r.take
+ exception_class = port.receive
assert_equal(NilClass, exception_class)
- result = r.take
+ result = port.receive
assert_equal(huge_value, result)
end
end;
@@ -1366,42 +1387,43 @@ class TestEnv < Test::Unit::TestCase
def test_frozen_env_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
#{str_for_yielding_exception_class("ENV.freeze")}
end
- #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")}
end;
end
def test_frozen_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
ENV["#{PATH_ENV}"] = "/"
ENV.each do |k, v|
- Ractor.yield [k.frozen?]
- Ractor.yield [v.frozen?]
+ port.send [k]
+ port.send [v]
end
ENV.each_key do |k|
- Ractor.yield [k.frozen?]
+ port.send [k]
end
ENV.each_value do |v|
- Ractor.yield [v.frozen?]
+ port.send [v]
end
ENV.each_key do |k|
- Ractor.yield [ENV[k].frozen?, "[\#{k.dump}]"]
- Ractor.yield [ENV.fetch(k).frozen?, "fetch(\#{k.dump})"]
+ port.send [ENV[k], "[\#{k.dump}]"]
+ port.send [ENV.fetch(k), "fetch(\#{k.dump})"]
end
- Ractor.yield "finished"
+ port.send "finished"
end
- while((params=r.take) != "finished")
- assert(*params)
+ while((params=port.receive) != "finished")
+ value, *params = params
+ assert_predicate(value, :frozen?, *params)
end
end;
end
def test_shared_substring_in_ractor
assert_ractor(<<-"end;")
- r = Ractor.new do
+ Ractor.new port = Ractor::Port.new do |port|
bug12475 = '[ruby-dev:49655] [Bug #12475]'
n = [*"0".."9"].join("")*3
e0 = ENV[n0 = "E\#{n}"]
@@ -1411,9 +1433,9 @@ class TestEnv < Test::Unit::TestCase
ENV[n1.chop] = "T\#{n}.".chop
ENV[n0], e0 = e0, ENV[n0]
ENV[n1], e1 = e1, ENV[n1]
- Ractor.yield [n, e0, e1, bug12475]
+ port.send [n, e0, e1, bug12475]
end
- n, e0, e1, bug12475 = r.take
+ n, e0, e1, bug12475 = port.receive
assert_equal("T\#{n}", e0, bug12475)
assert_nil(e1, bug12475)
end;
@@ -1429,7 +1451,7 @@ class TestEnv < Test::Unit::TestCase
rescue Ractor::IsolationError => e
e
end
- assert_equal Ractor::IsolationError, r_get.take.class
+ assert_equal Ractor::IsolationError, r_get.value.class
r_get = Ractor.new do
ENV.instance_eval{ @a }
@@ -1437,7 +1459,7 @@ class TestEnv < Test::Unit::TestCase
e
end
- assert_equal Ractor::IsolationError, r_get.take.class
+ assert_equal Ractor::IsolationError, r_get.value.class
r_set = Ractor.new do
ENV.instance_eval{ @b = "hello" }
@@ -1445,7 +1467,7 @@ class TestEnv < Test::Unit::TestCase
e
end
- assert_equal Ractor::IsolationError, r_set.take.class
+ assert_equal Ractor::IsolationError, r_set.value.class
RUBY
end
diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb
index 84581180b6..4365150a13 100644
--- a/test/ruby/test_exception.rb
+++ b/test/ruby/test_exception.rb
@@ -992,7 +992,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_equal 1, outs.size
assert_equal 0, errs.size
err = outs.first.force_encoding('utf-8')
- assert err.valid_encoding?, 'must be valid encoding'
+ assert_predicate err, :valid_encoding?
assert_match %r/\u3042/, err
end
end
@@ -1525,4 +1525,31 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_in_out_err(%W[-r#{lib} #{main}], "", [], [:*, "\n""path=#{main}\n", :*])
end
end
+
+ class Ex; end
+
+ def test_exception_message_for_unexpected_implicit_conversion_type
+ a = Ex.new
+ def self.x(a) = nil
+
+ assert_raise_with_message(TypeError, "no implicit conversion of TestException::Ex into Hash") do
+ x(**a)
+ end
+ assert_raise_with_message(TypeError, "no implicit conversion of TestException::Ex into Proc") do
+ x(&a)
+ end
+
+ def a.to_a = 1
+ def a.to_hash = 1
+ def a.to_proc = 1
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex into Array (TestException::Ex#to_a gives Integer)") do
+ x(*a)
+ end
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex into Hash (TestException::Ex#to_hash gives Integer)") do
+ x(**a)
+ end
+ assert_raise_with_message(TypeError, "can't convert TestException::Ex into Proc (TestException::Ex#to_proc gives Integer)") do
+ x(&a)
+ end
+ end
end
diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb
index 19cd52f7c8..6976bd9742 100644
--- a/test/ruby/test_fiber.rb
+++ b/test/ruby/test_fiber.rb
@@ -34,7 +34,6 @@ class TestFiber < Test::Unit::TestCase
end
def test_many_fibers
- omit 'This is unstable on GitHub Actions --jit-wait. TODO: debug it' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
max = 1000
assert_equal(max, max.times{
Fiber.new{}
@@ -50,7 +49,7 @@ class TestFiber < Test::Unit::TestCase
end
def test_many_fibers_with_threads
- assert_normal_exit <<-SRC, timeout: (/solaris/i =~ RUBY_PLATFORM ? 1000 : 60)
+ assert_normal_exit <<-SRC, timeout: 60
max = 1000
@cnt = 0
(1..100).map{|ti|
@@ -499,7 +498,7 @@ class TestFiber < Test::Unit::TestCase
end
def test_machine_stack_gc
- assert_normal_exit <<-RUBY, '[Bug #14561]', timeout: 10
+ assert_normal_exit <<-RUBY, '[Bug #14561]', timeout: 60
enum = Enumerator.new { |y| y << 1 }
thread = Thread.new { enum.peek }
thread.join
@@ -507,4 +506,45 @@ class TestFiber < Test::Unit::TestCase
GC.start
RUBY
end
+
+ def test_fiber_pool_stack_acquire_failure
+ environment = {
+ "RUBY_SHARED_FIBER_POOL_MINIMUM_COUNT" => "0",
+ "RUBY_SHARED_FIBER_POOL_MAXIMUM_COUNT" => "128"
+ }
+
+ # This program requires, effectively, at most one fiber stack, since the fiber immediately becomes unreachable.
+ assert_separately([environment], <<~RUBY, timeout: 30)
+ GC.disable
+ count_before = GC.count
+
+ # Create more fibers than the pool can handle (but they become immediately unreachable):
+ assert_nothing_raised do
+ 256.times do
+ Fiber.new{Fiber.yield}.resume
+ end
+ end
+
+ # Major GC should have happened at least once:
+ assert_operator(GC.count, :>, count_before)
+ RUBY
+ end
+
+ def test_fiber_pool_stack_acquire_failure_at_maximum_count
+ environment = {
+ "RUBY_SHARED_FIBER_POOL_MAXIMUM_COUNT" => "128"
+ }
+
+ assert_separately([environment], <<~RUBY, timeout: 30)
+ GC.disable
+ fibers = []
+ assert_raise(FiberError) do
+ loop do
+ Fiber.new{fibers << Fiber.current; Fiber.yield}.resume
+ raise "expected FiberError before this" if fibers.size > 128
+ end
+ end
+ assert_operator fibers.size, :>=, 128
+ RUBY
+ end
end
diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb
index eae9a8e7b0..a3d6221c0f 100644
--- a/test/ruby/test_file.rb
+++ b/test/ruby/test_file.rb
@@ -372,9 +372,9 @@ class TestFile < Test::Unit::TestCase
end
def test_stat
- tb = Process.clock_gettime(Process::CLOCK_REALTIME)
+ btime = Process.clock_gettime(Process::CLOCK_REALTIME)
Tempfile.create("stat") {|file|
- tb = (tb + Process.clock_gettime(Process::CLOCK_REALTIME)) / 2
+ btime = (btime + Process.clock_gettime(Process::CLOCK_REALTIME)) / 2
file.close
path = file.path
@@ -384,33 +384,32 @@ class TestFile < Test::Unit::TestCase
sleep 2
- t1 = measure_time do
+ mtime = measure_time do
File.write(path, "bar")
end
sleep 2
- t2 = measure_time do
- File.read(path)
+ ctime = measure_time do
File.chmod(0644, path)
end
sleep 2
- t3 = measure_time do
+ atime = measure_time do
File.read(path)
end
delta = 1
stat = File.stat(path)
- assert_in_delta tb, stat.birthtime.to_f, delta
- assert_in_delta t1, stat.mtime.to_f, delta
+ assert_in_delta btime, stat.birthtime.to_f, delta
+ assert_in_delta mtime, stat.mtime.to_f, delta
if stat.birthtime != stat.ctime
- assert_in_delta t2, stat.ctime.to_f, delta
+ assert_in_delta ctime, stat.ctime.to_f, delta
end
if /mswin|mingw/ !~ RUBY_PLATFORM && !Bug::File::Fs.noatime?(path)
# Windows delays updating atime
- assert_in_delta t3, stat.atime.to_f, delta
+ assert_in_delta atime, stat.atime.to_f, delta
end
}
rescue NotImplementedError
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index f3068cb189..6e7973897c 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -6,7 +6,8 @@ require "socket"
require '-test-/file'
class TestFileExhaustive < Test::Unit::TestCase
- DRIVE = Dir.pwd[%r'\A(?:[a-z]:|//[^/]+/[^/]+)'i]
+ ROOT_REGEXP = %r'\A(?:[a-z]:(?=(/))|//[^/]+/[^/]+)'i
+ DRIVE = Dir.pwd[ROOT_REGEXP]
POSIX = /cygwin|mswin|bccwin|mingw|emx/ !~ RUBY_PLATFORM
NTFS = !(/mingw|mswin|bccwin/ !~ RUBY_PLATFORM)
@@ -196,12 +197,32 @@ class TestFileExhaustive < Test::Unit::TestCase
[regular_file, utf8_file].each do |file|
assert_equal(file, File.open(file) {|f| f.path})
assert_equal(file, File.path(file))
- o = Object.new
- class << o; self; end.class_eval do
- define_method(:to_path) { file }
- end
+ o = Struct.new(:to_path).new(file)
+ assert_equal(file, File.path(o))
+ o = Struct.new(:to_str).new(file)
assert_equal(file, File.path(o))
end
+
+ conv_error = ->(method, msg = "converting with #{method}") {
+ test = ->(&new) do
+ o = new.(42)
+ assert_raise(TypeError, msg) {File.path(o)}
+
+ o = new.("abc".encode(Encoding::UTF_32BE))
+ assert_raise(Encoding::CompatibilityError, msg) {File.path(o)}
+
+ ["\0", "a\0", "a\0c"].each do |path|
+ o = new.(path)
+ assert_raise(ArgumentError, msg) {File.path(o)}
+ end
+ end
+
+ test.call(&:itself)
+ test.call(&Struct.new(method).method(:new))
+ }
+
+ conv_error[:to_path]
+ conv_error[:to_str]
end
def assert_integer(n)
@@ -876,10 +897,12 @@ class TestFileExhaustive < Test::Unit::TestCase
bug9934 = '[ruby-core:63114] [Bug #9934]'
require "objspace"
path = File.expand_path("/foo")
- assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934)
+ slot_size = Integer(ObjectSpace.dump(path)[/"slot_size":(\d+)/, 1])
+ assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + slot_size, bug9934)
path = File.expand_path("/a"*25)
+ slot_size = Integer(ObjectSpace.dump(path)[/"slot_size":(\d+)/, 1])
assert_operator(ObjectSpace.memsize_of(path), :<=,
- (path.bytesize + 1) * 2 + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934)
+ (path.bytesize + 1) * 2 + slot_size, bug9934)
end
def test_expand_path_encoding
@@ -1214,6 +1237,7 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal("foo", File.basename("foo", ".ext"))
assert_equal("foo", File.basename("foo.ext", ".ext"))
assert_equal("foo", File.basename("foo.ext", ".*"))
+ assert_raise(ArgumentError) {File.basename("", "\0")}
end
if NTFS
@@ -1278,9 +1302,10 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(regular_file, File.dirname(regular_file, 0))
assert_equal(@dir, File.dirname(regular_file, 1))
assert_equal(File.dirname(@dir), File.dirname(regular_file, 2))
- return if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # rootdir and tmpdir are in different drives
- assert_equal(rootdir, File.dirname(regular_file, regular_file.count('/')))
assert_raise(ArgumentError) {File.dirname(regular_file, -1)}
+ root = "#{@dir[ROOT_REGEXP]||?/}#{$1}"
+ assert_equal(root, File.dirname(regular_file, regular_file.count('/')))
+ assert_equal(root, File.dirname(regular_file, regular_file.count('/') + 100))
end
def test_dirname_encoding
@@ -1335,14 +1360,19 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_join
- s = "foo" + File::SEPARATOR + "bar" + File::SEPARATOR + "baz"
+ sep = File::SEPARATOR
+ s = "foo" + sep + "bar" + sep + "baz"
assert_equal(s, File.join("foo", "bar", "baz"))
assert_equal(s, File.join(["foo", "bar", "baz"]))
+ assert_equal(s, File.join("foo" + sep, "bar", sep + "baz"))
+ assert_equal(s, File.join("foo" + sep, sep + "bar" + sep, sep + "baz"))
o = Object.new
def o.to_path; "foo"; end
assert_equal(s, File.join(o, "bar", "baz"))
- assert_equal(s, File.join("foo" + File::SEPARATOR, "bar", File::SEPARATOR + "baz"))
+
+ s = sep + "foo"
+ assert_equal(s, File.join(sep, s))
end
def test_join_alt_separator
@@ -1475,6 +1505,7 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_test
+ omit 'timestamp check is unstable on macOS' if RUBY_PLATFORM =~ /darwin/
fn1 = regular_file
hardlinkfile
sleep(1.1)
diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb
index f2c56d1b41..c01e8bb80b 100644
--- a/test/ruby/test_float.rb
+++ b/test/ruby/test_float.rb
@@ -492,6 +492,22 @@ class TestFloat < Test::Unit::TestCase
assert_equal(-1.26, -1.255.round(2))
end
+ def test_round_ndigits
+ bug14635 = "[ruby-core:86323]"
+ f = 0.5
+ 31.times do |i|
+ assert_equal(0.5, f.round(i+1), bug14635 + " (argument: #{i+1})")
+ end
+ end
+
+ def test_round_with_precision_min
+ (0..3).each do |n|
+ n -= Float::MIN_10_EXP
+ f = Float::MIN.round(n)
+ assert_include([Float::MIN.floor(n), Float::MIN.ceil(n)], f, "round(#{n})")
+ end
+ end
+
def test_round_half_even_with_precision
assert_equal(767573.18759, 767573.1875850001.round(5, half: :even))
assert_equal(767573.18758, 767573.187585.round(5, half: :even))
@@ -536,6 +552,16 @@ class TestFloat < Test::Unit::TestCase
assert_equal(-100000000000000000000000000000000000000000000000000, -1.0.floor(-50), "[Bug #20654]")
end
+ def test_floor_with_precision_min
+ min = Float::MIN
+ (0..3).each do |n|
+ n -= Float::MIN_10_EXP
+ f = min.floor(n)
+ assert_operator(f, :<=, Float::MIN, "floor(#{n})")
+ assert_operator(f, :>=, Float::MIN.floor(n-1), "ceil(#{n})")
+ end
+ end
+
def test_ceil_with_precision
assert_equal(+0.1, +0.001.ceil(1))
assert_equal(-0.0, -0.001.ceil(1))
@@ -567,6 +593,19 @@ class TestFloat < Test::Unit::TestCase
assert_equal(100000000000000000000000000000000000000000000000000, 1.0.ceil(-50), "[Bug #20654]")
end
+ def test_ceil_with_precision_min
+ min = Float::MIN
+ (-Float::MIN_10_EXP).times do |n|
+ assert_equal(10.pow(-n), min.ceil(n))
+ end
+ (0..3).each do |n|
+ n -= Float::MIN_10_EXP
+ f = min.ceil(n)
+ assert_operator(f, :>=, Float::MIN, "ceil(#{n})")
+ assert_operator(f, :<=, Float::MIN.ceil(n-1), "ceil(#{n})")
+ end
+ end
+
def test_truncate_with_precision
assert_equal(1.100, 1.111.truncate(1))
assert_equal(1.110, 1.111.truncate(2))
@@ -838,6 +877,10 @@ class TestFloat < Test::Unit::TestCase
assert_equal(15, Float('0xf.p0'))
assert_equal(15.9375, Float('0xf.f'))
assert_raise(ArgumentError) { Float('0xf.fp') }
+ assert_equal(0x10a, Float("0x1_0a"))
+ assert_equal(1.625, Float("0x1.a_0"))
+ assert_equal(3.25, Float("0x1.ap0_1"))
+ assert_raise(ArgumentError) { Float("0x1.ap0a") }
begin
verbose_bak, $VERBOSE = $VERBOSE, nil
assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000'))
@@ -857,7 +900,9 @@ class TestFloat < Test::Unit::TestCase
assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-32le"))}
assert_raise(Encoding::CompatibilityError) {Float("0".encode("iso-2022-jp"))}
- assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Float("\u{1f4a1}")}
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Float("\u{1f4a1}")}
+ end
end
def test_invalid_str
diff --git a/test/ruby/test_frozen.rb b/test/ruby/test_frozen.rb
index 2918a2afd8..6721cb1128 100644
--- a/test/ruby/test_frozen.rb
+++ b/test/ruby/test_frozen.rb
@@ -27,4 +27,20 @@ class TestFrozen < Test::Unit::TestCase
str.freeze
assert_raise(FrozenError) { str.instance_variable_set(:@b, 1) }
end
+
+ def test_setting_ivar_on_frozen_string_with_singleton_class
+ str = "str"
+ str.singleton_class
+ str.freeze
+ assert_raise_with_message(FrozenError, "can't modify frozen String: \"str\"") { str.instance_variable_set(:@a, 1) }
+ end
+
+ class A
+ freeze
+ end
+
+ def test_setting_ivar_on_frozen_class
+ assert_raise_with_message(FrozenError, "can't modify frozen Class: TestFrozen::A") { A.instance_variable_set(:@a, 1) }
+ assert_raise_with_message(FrozenError, "can't modify frozen Class: #<Class:TestFrozen::A>") { A.singleton_class.instance_variable_set(:@a, 1) }
+ end
end
diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb
index 72fab5c43c..21448294c2 100644
--- a/test/ruby/test_gc.rb
+++ b/test/ruby/test_gc.rb
@@ -75,12 +75,9 @@ class TestGc < Test::Unit::TestCase
GC.start
end
- def test_gc_config_setting_returns_nil_for_missing_keys
- missing_value = GC.config(no_such_key: true)[:no_such_key]
- assert_nil(missing_value)
- ensure
- GC.config(full_mark: true)
- GC.start
+ def test_gc_config_setting_returns_config_hash
+ hash = GC.config(no_such_key: true)
+ assert_equal(GC.config, hash)
end
def test_gc_config_disable_major
@@ -211,7 +208,7 @@ class TestGc < Test::Unit::TestCase
assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages]
assert_equal stat[:heap_available_slots], stat[:heap_live_slots] + stat[:heap_free_slots] + stat[:heap_final_slots]
assert_equal stat[:heap_live_slots], stat[:total_allocated_objects] - stat[:total_freed_objects] - stat[:heap_final_slots]
- assert_equal stat[:heap_allocated_pages], stat[:heap_eden_pages]
+ assert_equal stat[:heap_allocated_pages], stat[:heap_eden_pages] + stat[:heap_empty_pages]
if use_rgengc?
assert_equal stat[:count], stat[:major_gc_count] + stat[:minor_gc_count]
@@ -233,7 +230,10 @@ class TestGc < Test::Unit::TestCase
GC.stat(stat)
end
- assert_equal (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) * (2**i), stat_heap[:slot_size]
+ assert_equal GC.stat_heap(i, :slot_size), stat_heap[:slot_size]
+ assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots]
+ assert_operator stat_heap[:heap_free_slots], :<=, stat[:heap_free_slots]
+ assert_operator stat_heap[:heap_final_slots], :<=, stat[:heap_final_slots]
assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages]
assert_operator stat_heap[:heap_eden_slots], :>=, 0
assert_operator stat_heap[:total_allocated_pages], :>=, 0
@@ -253,7 +253,6 @@ class TestGc < Test::Unit::TestCase
end
def test_stat_heap_all
- omit "flaky with RJIT, which allocates objects itself" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
stat_heap_all = {}
stat_heap = {}
# Initialize to prevent GC in future calls
@@ -265,7 +264,7 @@ class TestGc < Test::Unit::TestCase
GC.stat_heap(i, stat_heap)
# Remove keys that can vary between invocations
- %i(total_allocated_objects).each do |sym|
+ %i(total_allocated_objects heap_live_slots heap_free_slots).each do |sym|
stat_heap[sym] = stat_heap_all[i][sym] = 0
end
@@ -290,6 +289,9 @@ class TestGc < Test::Unit::TestCase
hash.each { |k, v| stat_heap_sum[k] += v }
end
+ assert_equal stat[:heap_live_slots], stat_heap_sum[:heap_live_slots]
+ assert_equal stat[:heap_free_slots], stat_heap_sum[:heap_free_slots]
+ assert_equal stat[:heap_final_slots], stat_heap_sum[:heap_final_slots]
assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages]
assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots]
assert_equal stat[:total_allocated_objects], stat_heap_sum[:total_allocated_objects]
@@ -297,7 +299,7 @@ class TestGc < Test::Unit::TestCase
end
def test_measure_total_time
- assert_separately([], __FILE__, __LINE__, <<~RUBY)
+ assert_separately([], __FILE__, __LINE__, <<~RUBY, timeout: 60)
GC.measure_total_time = false
time_before = GC.stat(:time)
@@ -316,9 +318,9 @@ class TestGc < Test::Unit::TestCase
def test_latest_gc_info
omit 'stress' if GC.stress
- assert_separately([], __FILE__, __LINE__, <<-'RUBY')
+ assert_separately([{"RUBY_GC_HEAP_INIT_BYTES" => "409600"}, "-W0"], __FILE__, __LINE__, <<-'RUBY')
GC.start
- count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_slots)
+ count = GC.stat(:heap_free_slots) + GC.stat_heap(0, :heap_allocatable_slots)
count.times{ "a" + "b" }
assert_equal :newobj, GC.latest_gc_info[:gc_by]
RUBY
@@ -357,13 +359,14 @@ class TestGc < Test::Unit::TestCase
3.times { GC.start }
assert_nil GC.latest_gc_info(:need_major_by)
- # allocate objects until need_major_by is set or major GC happens
- objects = []
- while GC.latest_gc_info(:need_major_by).nil?
- objects.append(100.times.map { '*' })
- end
-
EnvUtil.without_gc do
+ # allocate objects until need_major_by is set or major GC happens
+ objects = []
+ while GC.latest_gc_info(:need_major_by).nil?
+ objects.append(100.times.map { '*' })
+ GC.start(full_mark: false)
+ end
+
# We need to ensure that no GC gets ran before the call to GC.start since
# it would trigger a major GC. Assertions could allocate objects and
# trigger a GC so we don't run assertions until we perform the major GC.
@@ -379,51 +382,36 @@ class TestGc < Test::Unit::TestCase
def test_latest_gc_info_weak_references_count
assert_separately([], __FILE__, __LINE__, <<~RUBY)
GC.disable
- count = 10_000
+ COUNT = 10_000
# Some weak references may be created, so allow some margin of error
error_tolerance = 100
- # Run full GC to clear out weak references
- GC.start
- # Run full GC again to collect stats about weak references
+ # Run full GC to collect stats about weak references
GC.start
before_weak_references_count = GC.latest_gc_info(:weak_references_count)
- before_retained_weak_references_count = GC.latest_gc_info(:retained_weak_references_count)
- # Create some objects and place it in a WeakMap
- wmap = ObjectSpace::WeakMap.new
- ary = Array.new(count)
- enum = count.times
- enum.each.with_index do |i|
- obj = Object.new
- ary[i] = obj
- wmap[obj] = nil
+ # Create some WeakMaps
+ ary = Array.new(COUNT)
+ COUNT.times.with_index do |i|
+ ary[i] = ObjectSpace::WeakMap.new
end
# Run full GC to collect stats about weak references
GC.start
- assert_operator(GC.latest_gc_info(:weak_references_count), :>=, before_weak_references_count + count - error_tolerance)
- assert_operator(GC.latest_gc_info(:retained_weak_references_count), :>=, before_retained_weak_references_count + count - error_tolerance)
- assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, GC.latest_gc_info(:weak_references_count))
+ assert_operator(GC.latest_gc_info(:weak_references_count), :>=, before_weak_references_count + COUNT - error_tolerance)
before_weak_references_count = GC.latest_gc_info(:weak_references_count)
- before_retained_weak_references_count = GC.latest_gc_info(:retained_weak_references_count)
+ # Clear ary, so if ary itself is somewhere on the stack, it won't hold all references
+ ary.clear
ary = nil
- # Free ary, which should empty out the wmap
- GC.start
- # Run full GC again to collect stats about weak references
+ # Free ary, which should GC all the WeakMaps
GC.start
- # Sometimes the WeakMap has one element, which might be held on by registers.
- assert_operator(wmap.size, :<=, 1)
-
- assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - count + error_tolerance)
- assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, before_retained_weak_references_count - count + error_tolerance)
- assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, GC.latest_gc_info(:weak_references_count))
+ assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - COUNT + error_tolerance)
RUBY
end
@@ -450,7 +438,7 @@ class TestGc < Test::Unit::TestCase
end
def test_singleton_method_added
- assert_in_out_err([], <<-EOS, [], [], "[ruby-dev:44436]")
+ assert_in_out_err([], <<-EOS, [], [], "[ruby-dev:44436]", timeout: 30)
class BasicObject
undef singleton_method_added
def singleton_method_added(mid)
@@ -465,32 +453,19 @@ class TestGc < Test::Unit::TestCase
end
def test_gc_parameter
- env = {
- "RUBY_GC_HEAP_INIT_SLOTS" => "100"
- }
- assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [])
- assert_in_out_err([env, "-W:deprecated", "-e", "exit"], "", [],
- /The environment variable RUBY_GC_HEAP_INIT_SLOTS is deprecated; use environment variables RUBY_GC_HEAP_%d_INIT_SLOTS instead/)
-
- env = {}
- GC.stat_heap.keys.each do |heap|
- env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "200000"
- end
+ env = { "RUBY_GC_HEAP_INIT_BYTES" => "#{200000 * 40}" }
assert_normal_exit("exit", "", :child_env => env)
- env = {}
- GC.stat_heap.keys.each do |heap|
- env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "0"
- end
+ env = { "RUBY_GC_HEAP_INIT_BYTES" => "0" }
assert_normal_exit("exit", "", :child_env => env)
env = {
"RUBY_GC_HEAP_GROWTH_FACTOR" => "2.0",
- "RUBY_GC_HEAP_GROWTH_MAX_SLOTS" => "10000"
+ "RUBY_GC_HEAP_GROWTH_MAX_BYTES" => "409600"
}
assert_normal_exit("exit", "", :child_env => env)
assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_FACTOR=2.0/, "")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_MAX_SLOTS=10000/, "[ruby-core:57928]")
+ assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_MAX_BYTES=409600/, "[ruby-core:57928]")
if use_rgengc?
env = {
@@ -532,16 +507,19 @@ class TestGc < Test::Unit::TestCase
end
end
- def test_gc_parameter_init_slots
+ def test_gc_parameter_init_bytes
+ omit "[Bug #21203] This test is flaky and intermittently failing now"
+
assert_separately([], __FILE__, __LINE__, <<~RUBY, timeout: 60)
- # Constant from gc.c.
- GC_HEAP_INIT_SLOTS = 10_000
+ GC_HEAP_INIT_BYTES = 2560 * 1024
gc_count = GC.stat(:count)
- # Fill up all of the size pools to the init slots
+ # Fill up all heaps to the byte-derived init slot count
GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i|
- capa = (GC.stat_heap(i, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] - (2 * RbConfig::SIZEOF["void*"])) / RbConfig::SIZEOF["void*"]
- while GC.stat_heap(i, :heap_eden_slots) < GC_HEAP_INIT_SLOTS
+ slot_size = GC.stat_heap(i, :slot_size)
+ init_slots = GC_HEAP_INIT_BYTES / slot_size
+ capa = (slot_size - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] - (2 * RbConfig::SIZEOF["void*"])) / RbConfig::SIZEOF["void*"]
+ while GC.stat_heap(i, :heap_eden_slots) < init_slots
Array.new(capa)
end
end
@@ -549,19 +527,17 @@ class TestGc < Test::Unit::TestCase
assert_equal gc_count, GC.stat(:count)
RUBY
- env = {}
- sizes = GC.stat_heap.keys.reverse.map { 20_000 }
- GC.stat_heap.keys.each do |heap|
- env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = sizes[heap].to_s
- end
+ env = { "RUBY_GC_HEAP_INIT_BYTES" => "#{800 * 1024}" }
assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY, timeout: 60)
- SIZES = #{sizes}
+ GC_HEAP_INIT_BYTES = 800 * 1024
gc_count = GC.stat(:count)
- # Fill up all of the size pools to the init slots
+ # Fill up all heaps to the byte-derived init slot count
GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i|
- capa = (GC.stat_heap(i, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] - (2 * RbConfig::SIZEOF["void*"])) / RbConfig::SIZEOF["void*"]
- while GC.stat_heap(i, :heap_eden_slots) < SIZES[i]
+ slot_size = GC.stat_heap(i, :slot_size)
+ init_slots = GC_HEAP_INIT_BYTES / slot_size
+ capa = (slot_size - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] - (2 * RbConfig::SIZEOF["void*"])) / RbConfig::SIZEOF["void*"]
+ while GC.stat_heap(i, :heap_eden_slots) < init_slots
Array.new(capa)
end
end
@@ -676,17 +652,40 @@ class TestGc < Test::Unit::TestCase
debug_msg = "before_stats: #{before_stats}\nbefore_stat_heap: #{before_stat_heap}\nafter_stats: #{after_stats}\nafter_stat_heap: #{after_stat_heap}"
# Should not be thrashing in page creation
- assert_equal before_stats[:heap_allocated_pages], after_stats[:heap_allocated_pages], debug_msg
- assert_equal 0, after_stats[:heap_empty_pages], debug_msg
+ assert_in_epsilon before_stats[:heap_allocated_pages], after_stats[:heap_allocated_pages], 0.5, debug_msg
assert_equal 0, after_stats[:total_freed_pages], debug_msg
- # Only young objects, so should not trigger major GC
- assert_equal before_stats[:major_gc_count], after_stats[:major_gc_count], debug_msg
+ RUBY
+ end
+
+ def test_heaps_grow_independently
+ # [Bug #21214]
+
+ assert_separately([], __FILE__, __LINE__, <<-'RUBY', timeout: 60)
+ COUNT = 1_000_000
+
+ def allocate_small_object = []
+ def allocate_large_object = Array.new(10)
+
+ @arys = Array.new(COUNT) do
+ # Allocate 10 small transient objects
+ 10.times { allocate_small_object }
+ # Allocate 1 large object that is persistent
+ allocate_large_object
+ end
+
+ # Running GC here is required to prevent this test from being flaky because
+ # the heap for the small transient objects may not have been cleared by the
+ # GC causing heap_available_slots to be slightly over 2 * COUNT.
+ GC.start
+
+ heap_available_slots = GC.stat(:heap_available_slots)
+
+ assert_operator(heap_available_slots, :<, COUNT * 2, "GC.stat: #{GC.stat}\nGC.stat_heap: #{GC.stat_heap}")
RUBY
end
def test_gc_internals
- assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
- assert_not_nil GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
+ assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_COUNT]
end
def test_sweep_in_finalizer
@@ -717,6 +716,7 @@ class TestGc < Test::Unit::TestCase
end
def test_interrupt_in_finalizer
+ omit 'randomly hangs on many platforms' if ENV.key?('GITHUB_ACTIONS')
bug10595 = '[ruby-core:66825] [Bug #10595]'
src = <<-'end;'
Signal.trap(:INT, 'DEFAULT')
@@ -732,7 +732,7 @@ class TestGc < Test::Unit::TestCase
ObjectSpace.define_finalizer(Object.new, f)
end
end;
- out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV) do |*result|
+ out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV, timeout: 100) do |*result|
break result
end
unless /mswin|mingw/ =~ RUBY_PLATFORM
@@ -773,7 +773,7 @@ class TestGc < Test::Unit::TestCase
end
def test_gc_stress_at_startup
- assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 60)
+ assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 120)
end
def test_gc_disabled_start
@@ -799,6 +799,8 @@ class TestGc < Test::Unit::TestCase
end
def test_exception_in_finalizer_procs
+ require '-test-/stack'
+ omit 'failing with ASAN' if Thread.asan?
assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2])
c1 = proc do
puts "c1"
@@ -819,6 +821,8 @@ class TestGc < Test::Unit::TestCase
end
def test_exception_in_finalizer_method
+ require '-test-/stack'
+ omit 'failing with ASAN' if Thread.asan?
assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2])
def self.c1(x)
puts "c1"
@@ -884,4 +888,25 @@ class TestGc < Test::Unit::TestCase
assert_include ObjectSpace.dump(young_obj), '"old":true'
end
end
+
+ def test_finalizer_not_run_with_vm_lock
+ assert_ractor(<<~'RUBY', timeout: 30)
+ Thread.new do
+ loop do
+ Encoding.list.each do |enc|
+ enc.names
+ end
+ end
+ end
+
+ o = Object.new
+ ObjectSpace.define_finalizer(o, proc do
+ sleep 0.5 # finalizer shouldn't be run with VM lock, otherwise this context switch will crash
+ end)
+ o = nil
+ 4.times do
+ GC.start
+ end
+ RUBY
+ end
end
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
index 4c8aa20215..cb5e9d6ccb 100644
--- a/test/ruby/test_gc_compact.rb
+++ b/test/ruby/test_gc_compact.rb
@@ -30,7 +30,7 @@ class TestGCCompact < Test::Unit::TestCase
def test_enable_autocompact
before = GC.auto_compact
GC.auto_compact = true
- assert GC.auto_compact
+ assert_predicate GC, :auto_compact
ensure
GC.auto_compact = before
end
@@ -151,12 +151,12 @@ class TestGCCompact < Test::Unit::TestCase
def walk_ast ast
children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node)
children.each do |child|
- assert child.type
+ assert_predicate child, :type
walk_ast child
end
end
ast = RubyVM::AbstractSyntaxTree.parse_file #{__FILE__.dump}
- assert GC.compact
+ assert_predicate GC, :compact
walk_ast ast
end;
end
@@ -207,7 +207,7 @@ class TestGCCompact < Test::Unit::TestCase
end
def test_updating_references_for_embed_shared_arrays
- omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
begin;
@@ -256,7 +256,7 @@ class TestGCCompact < Test::Unit::TestCase
end
def test_updating_references_for_embed_frozen_shared_arrays
- omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
begin;
@@ -284,7 +284,7 @@ class TestGCCompact < Test::Unit::TestCase
end
def test_moving_arrays_down_heaps
- omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
begin;
@@ -300,13 +300,13 @@ class TestGCCompact < Test::Unit::TestCase
}.resume
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats.dig(:moved_down, :T_ARRAY) || 0, :>=, ARY_COUNT - 10)
+ assert_operator(stats.dig(:moved_down, :T_ARRAY) || 0, :>=, ARY_COUNT - 25)
refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
end;
end
def test_moving_arrays_up_heaps
- omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
begin;
@@ -315,7 +315,7 @@ class TestGCCompact < Test::Unit::TestCase
GC.verify_compaction_references(expand_heap: true, toward: :empty)
Fiber.new {
- ary = "hello".chars
+ ary = "hello world".chars # > 6 elements to exceed pool 0 embed capacity
$arys = ARY_COUNT.times.map do
x = []
ary.each { |e| x << e }
@@ -324,13 +324,13 @@ class TestGCCompact < Test::Unit::TestCase
}.resume
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, ARY_COUNT - 10)
+ assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, (0.9995 * ARY_COUNT).to_i)
refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
end;
end
def test_moving_objects_between_heaps
- omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 60)
begin;
@@ -356,13 +356,29 @@ class TestGCCompact < Test::Unit::TestCase
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats.dig(:moved_up, :T_OBJECT) || 0, :>=, OBJ_COUNT - 10)
+ assert_operator(stats.dig(:moved_up, :T_OBJECT) || 0, :>=, OBJ_COUNT - 25)
refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
end;
end
+ def test_compact_objects_of_varying_sizes
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
+
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10)
+ begin;
+ $objects = []
+ 160.times do |n|
+ obj = Class.new.new
+ n.times { |i| obj.instance_variable_set("@foo" + i.to_s, 0) }
+ $objects << obj
+ end
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ end;
+ end
+
def test_moving_strings_up_heaps
- omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30)
begin;
@@ -371,19 +387,19 @@ class TestGCCompact < Test::Unit::TestCase
GC.verify_compaction_references(expand_heap: true, toward: :empty)
Fiber.new {
- str = "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4
+ str = "a" * GC.stat_heap(0, :slot_size) * 4
$ary = STR_COUNT.times.map { +"" << str }
}.resume
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT - 10)
+ assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT - 25)
refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
end;
end
def test_moving_strings_down_heaps
- omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30)
begin;
@@ -392,18 +408,18 @@ class TestGCCompact < Test::Unit::TestCase
GC.verify_compaction_references(expand_heap: true, toward: :empty)
Fiber.new {
- $ary = STR_COUNT.times.map { ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4).squeeze! }
+ $ary = STR_COUNT.times.map { ("a" * GC.stat_heap(0, :slot_size) * 4).squeeze! }
}.resume
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats[:moved_down][:T_STRING], :>=, STR_COUNT - 10)
+ assert_operator(stats[:moved_down][:T_STRING], :>=, STR_COUNT - 25)
refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') })
end;
end
def test_moving_hashes_down_heaps
- omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
# AR and ST hashes are in the same size pool on 32 bit
omit unless RbConfig::SIZEOF["uint64_t"] <= RbConfig::SIZEOF["void*"]
@@ -421,7 +437,7 @@ class TestGCCompact < Test::Unit::TestCase
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
- assert_operator(stats[:moved_down][:T_HASH], :>=, HASH_COUNT - 10)
+ assert_operator(stats[:moved_down][:T_HASH], :>=, HASH_COUNT - 25)
end;
end
@@ -452,4 +468,21 @@ class TestGCCompact < Test::Unit::TestCase
assert_raise(FrozenError) { a.set_a }
end;
end
+
+ def test_moving_complex_generic_ivar
+ omit "not compiled with SHAPE_DEBUG" unless defined?(RubyVM::Shape)
+
+ assert_separately([], <<~RUBY)
+ RubyVM::Shape.exhaust_shapes
+
+ obj = []
+ obj.instance_variable_set(:@fixnum, 123)
+ obj.instance_variable_set(:@str, "hello")
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_equal(123, obj.instance_variable_get(:@fixnum))
+ assert_equal("hello", obj.instance_variable_get(:@str))
+ RUBY
+ end
end
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index 87eb1912d9..2d1b513c70 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -465,10 +465,10 @@ class TestHash < Test::Unit::TestCase
def test_each_value
res = []
@cls[].each_value { |v| res << v }
- assert_equal(0, [].length)
+ assert_equal(0, res.length)
@h.each_value { |v| res << v }
- assert_equal(0, [].length)
+ assert_equal(@h.size, res.length)
expected = []
@h.each { |k, v| expected << v }
@@ -880,21 +880,20 @@ class TestHash < Test::Unit::TestCase
assert_equal(quote1, eval(quote1).inspect)
assert_equal(quote2, eval(quote2).inspect)
assert_equal(quote3, eval(quote3).inspect)
- begin
- verbose_bak, $VERBOSE = $VERBOSE, nil
- enc = Encoding.default_external
- Encoding.default_external = Encoding::ASCII
+
+ EnvUtil.with_default_external(Encoding::ASCII) do
utf8_ascii_hash = '{"\\u3042": 1}'
assert_equal(eval(utf8_ascii_hash).inspect, utf8_ascii_hash)
- Encoding.default_external = Encoding::UTF_8
+ end
+
+ EnvUtil.with_default_external(Encoding::UTF_8) do
utf8_hash = "{\u3042: 1}"
assert_equal(eval(utf8_hash).inspect, utf8_hash)
- Encoding.default_external = Encoding::Windows_31J
+ end
+
+ EnvUtil.with_default_external(Encoding::Windows_31J) do
sjis_hash = "{\x87]: 1}".force_encoding('sjis')
assert_equal(eval(sjis_hash).inspect, sjis_hash)
- ensure
- Encoding.default_external = enc
- $VERBOSE = verbose_bak
end
end
@@ -1297,6 +1296,17 @@ class TestHash < Test::Unit::TestCase
assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h)
end
+ def test_update_modify_in_block
+ a = @cls[]
+ (1..1337).each {|k| a[k] = k}
+ b = {1=>1338}
+ assert_raise_with_message(RuntimeError, /rehash during iteration/) do
+ a.update(b) {|k, o, n|
+ a.rehash
+ }
+ end
+ end
+
def test_update_on_identhash
key = +'a'
i = @cls[].compare_by_identity
@@ -1853,6 +1863,14 @@ class TestHash < Test::Unit::TestCase
end
end
assert_equal(@cls[a: 2, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], x)
+
+ x = (1..1337).to_h {|k| [k, k]}
+ assert_raise_with_message(RuntimeError, /rehash during iteration/) do
+ x.transform_values! {|v|
+ x.rehash if v == 1337
+ v * 2
+ }
+ end
end
def hrec h, n, &b
@@ -1986,9 +2004,12 @@ class TestHashOnly < Test::Unit::TestCase
ObjectSpace.count_objects
h = {"abc" => 1}
- before = ObjectSpace.count_objects[:T_STRING]
- 5.times{ h["abc"] }
- assert_equal before, ObjectSpace.count_objects[:T_STRING]
+
+ EnvUtil.without_gc do
+ before = ObjectSpace.count_objects[:T_STRING]
+ 5.times{ h["abc".freeze] }
+ assert_equal before, ObjectSpace.count_objects[:T_STRING]
+ end
end
def test_AREF_fstring_key_default_proc
@@ -2116,7 +2137,9 @@ class TestHashOnly < Test::Unit::TestCase
def test_iterlevel_in_ivar_bug19589
h = { a: nil }
- hash_iter_recursion(h, 200)
+ # Recursion level should be over 127 to actually test iterlevel being set in an instance variable,
+ # but it should be under 131 not to overflow the stack under MN threads/ractors.
+ hash_iter_recursion(h, 130)
assert true
end
@@ -2333,6 +2356,11 @@ class TestHashOnly < Test::Unit::TestCase
end
end
+ def test_bug_21357
+ h = {x: []}.merge(x: nil) { |_k, v1, _v2| v1 }
+ assert_equal({x: []}, h)
+ end
+
def test_any_hash_fixable
20.times do
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
@@ -2389,4 +2417,18 @@ class TestHashOnly < Test::Unit::TestCase
end
end;
end
+
+ def test_ar_to_st_reserved_value
+ klass = Class.new do
+ attr_reader :hash
+ def initialize(val) = @hash = val
+ end
+
+ values = 0.downto(-16).to_a
+ hash = {}
+ values.each do |val|
+ hash[klass.new(val)] = val
+ end
+ assert_equal values, hash.values, "[ruby-core:121239] [Bug #21170]"
+ end
end
diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb
index 1dbb3fbb45..c3d9d311c8 100644
--- a/test/ruby/test_integer.rb
+++ b/test/ruby/test_integer.rb
@@ -158,7 +158,9 @@ class TestInteger < Test::Unit::TestCase
assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-32le"))}
assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("iso-2022-jp"))}
- assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Integer("\u{1f4a1}")}
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Integer("\u{1f4a1}")}
+ end
obj = Struct.new(:s).new(%w[42 not-an-integer])
def obj.to_str; s.shift; end
@@ -708,6 +710,10 @@ class TestInteger < Test::Unit::TestCase
assert_equal(x, Integer.sqrt(x ** 2), "[ruby-core:95453]")
end
+ def test_bug_21217
+ assert_equal(0x10000 * 2**10, Integer.sqrt(0x100000008 * 2**20))
+ end
+
def test_fdiv
assert_equal(1.0, 1.fdiv(1))
assert_equal(0.5, 1.fdiv(2))
@@ -745,7 +751,7 @@ class TestInteger < Test::Unit::TestCase
o = Object.new
def o.to_int; Object.new; end
- assert_raise_with_message(TypeError, /can't convert Object to Integer/) {Integer.try_convert(o)}
+ assert_raise_with_message(TypeError, /can't convert Object into Integer/) {Integer.try_convert(o)}
end
def test_ceildiv
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index ec080080c5..a78527d40e 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -467,6 +467,24 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_each_codepoint_with_ungetc
+ bug21562 = '[ruby-core:123176] [Bug #21562]'
+ with_read_pipe("") {|p|
+ p.binmode
+ p.ungetc("aa")
+ a = ""
+ p.each_codepoint { |c| a << c }
+ assert_equal("aa", a, bug21562)
+ }
+ with_read_pipe("") {|p|
+ p.set_encoding("ascii-8bit", universal_newline: true)
+ p.ungetc("aa")
+ a = ""
+ p.each_codepoint { |c| a << c }
+ assert_equal("aa", a, bug21562)
+ }
+ end
+
def test_rubydev33072
t = make_tempfile
path = t.path
@@ -681,7 +699,6 @@ class TestIO < Test::Unit::TestCase
if have_nonblock?
def test_copy_stream_no_busy_wait
- omit "RJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
omit "multiple threads already active" if Thread.list.size > 1
msg = 'r58534 [ruby-core:80969] [Backport #13533]'
@@ -1142,6 +1159,34 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_copy_stream_dup_buffer
+ bug21131 = '[ruby-core:120961] [Bug #21131]'
+ mkcdtmpdir do
+ dst_class = Class.new do
+ def initialize(&block)
+ @block = block
+ end
+
+ def write(data)
+ @block.call(data.dup)
+ data.bytesize
+ end
+ end
+
+ rng = Random.new(42)
+ body = Tempfile.new("ruby-bug", binmode: true)
+ body.write(rng.bytes(16_385))
+ body.rewind
+
+ payload = []
+ IO.copy_stream(body, dst_class.new{payload << it})
+ body.rewind
+ assert_equal(body.read, payload.join, bug21131)
+ ensure
+ body&.close
+ end
+ end
+
def test_copy_stream_write_in_binmode
bug8767 = '[ruby-core:56518] [Bug #8767]'
mkcdtmpdir {
@@ -1348,10 +1393,6 @@ class TestIO < Test::Unit::TestCase
args = ['-e', '$>.write($<.read)'] if args.empty?
ruby = EnvUtil.rubybin
opts = {}
- if defined?(Process::RLIMIT_NPROC)
- lim = Process.getrlimit(Process::RLIMIT_NPROC)[1]
- opts[:rlimit_nproc] = [lim, 2048].min
- end
f = IO.popen([ruby] + args, 'r+', opts)
pid = f.pid
yield(f)
@@ -1705,7 +1746,6 @@ class TestIO < Test::Unit::TestCase
end if have_nonblock?
def test_read_nonblock_no_exceptions
- omit '[ruby-core:90895] RJIT worker may leave fd open in a forked child' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # TODO: consider acquiring GVL from RJIT worker.
with_pipe {|r, w|
assert_equal :wait_readable, r.read_nonblock(4096, exception: false)
w.puts "HI!"
@@ -2515,10 +2555,6 @@ class TestIO < Test::Unit::TestCase
end
def test_autoclose_true_closed_by_finalizer
- # http://ci.rvm.jp/results/trunk-rjit@silicon-docker/1465760
- # http://ci.rvm.jp/results/trunk-rjit@silicon-docker/1469765
- omit 'this randomly fails with RJIT' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
-
feature2250 = '[ruby-core:26222]'
pre = 'ft2250'
t = Tempfile.new(pre)
@@ -2579,36 +2615,15 @@ class TestIO < Test::Unit::TestCase
assert_equal({:a=>1}, open(o, {a: 1}))
end
- def test_open_pipe
- assert_deprecated_warning(/Kernel#open with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- open("|" + EnvUtil.rubybin, "r+") do |f|
- f.puts "puts 'foo'"
- f.close_write
- assert_equal("foo\n", f.read)
- end
- end
- end
+ def test_path_with_pipe
+ mkcdtmpdir do
+ cmd = "|echo foo"
+ assert_file.not_exist?(cmd)
- def test_read_command
- assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- assert_equal("foo\n", IO.read("|echo foo"))
- end
- assert_raise(Errno::ENOENT, Errno::EINVAL) do
- File.read("|#{EnvUtil.rubybin} -e puts")
- end
- assert_raise(Errno::ENOENT, Errno::EINVAL) do
- File.binread("|#{EnvUtil.rubybin} -e puts")
- end
- assert_raise(Errno::ENOENT, Errno::EINVAL) do
- Class.new(IO).read("|#{EnvUtil.rubybin} -e puts")
- end
- assert_raise(Errno::ENOENT, Errno::EINVAL) do
- Class.new(IO).binread("|#{EnvUtil.rubybin} -e puts")
- end
- assert_raise(Errno::ESPIPE) do
- assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- IO.read("|#{EnvUtil.rubybin} -e 'puts :foo'", 1, 1)
- end
+ pipe_errors = [Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EPERM]
+ assert_raise(*pipe_errors) { open(cmd, "r+") }
+ assert_raise(*pipe_errors) { IO.read(cmd) }
+ assert_raise(*pipe_errors) { IO.foreach(cmd) {|x| assert false } }
end
end
@@ -2813,19 +2828,6 @@ class TestIO < Test::Unit::TestCase
end
def test_foreach
- a = []
-
- assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x }
- end
- assert_equal(["foo\n", "bar\n", "baz\n"], a)
-
- a = []
- assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630
- IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x }
- end
- assert_equal(["zot\n"], a)
-
make_tempfile {|t|
a = []
IO.foreach(t.path) {|x| a << x }
@@ -2901,10 +2903,10 @@ class TestIO < Test::Unit::TestCase
end
def test_print_separators
- EnvUtil.suppress_warning {
- $, = ':'
- $\ = "\n"
- }
+ assert_deprecated_warning(/non-nil '\$,'/) {$, = ":"}
+ assert_raise(TypeError) {$, = 1}
+ assert_deprecated_warning(/non-nil '\$\\'/) {$\ = "\n"}
+ assert_raise(TypeError) {$/ = 1}
pipe(proc do |w|
w.print('a')
EnvUtil.suppress_warning {w.print('a','b','c')}
@@ -3804,7 +3806,7 @@ __END__
end
tempfiles = []
- (0..fd_setsize+1).map {|i|
+ (0...fd_setsize).map {|i|
tempfiles << Tempfile.create("test_io_select_with_many_files")
}
@@ -4240,6 +4242,23 @@ __END__
end
end if Socket.const_defined?(:MSG_OOB)
+ def test_select_timeout
+ assert_equal(nil, IO.select(nil,nil,nil,0))
+ assert_equal(nil, IO.select(nil,nil,nil,0.0))
+ assert_raise(TypeError) { IO.select(nil,nil,nil,"invalid-timeout") }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-1) }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-0.1) }
+ assert_raise(ArgumentError) { IO.select(nil,nil,nil,-Float::INFINITY) }
+ assert_raise(RangeError) { IO.select(nil,nil,nil,Float::NAN) }
+ IO.pipe {|r, w|
+ w << "x"
+ ret = [[r], [], []]
+ assert_equal(ret, IO.select([r],nil,nil,0.1))
+ assert_equal(ret, IO.select([r],nil,nil,1))
+ assert_equal(ret, IO.select([r],nil,nil,Float::INFINITY))
+ }
+ end
+
def test_recycled_fd_close
dot = -'.'
IO.pipe do |sig_rd, sig_wr|
@@ -4351,4 +4370,55 @@ __END__
end
end
end
+
+ def test_blocking_timeout
+ assert_separately([], <<~'RUBY')
+ IO.pipe do |r, w|
+ trap(:INT) do
+ w.puts "INT"
+ end
+
+ main = Thread.current
+ thread = Thread.new do
+ # Wait until the main thread has entered `$stdin.gets`:
+ Thread.pass until main.status == 'sleep'
+
+ # Cause an interrupt while handling `$stdin.gets`:
+ Process.kill :INT, $$
+ end
+
+ r.timeout = 1
+ assert_equal("INT", r.gets.chomp)
+ rescue IO::TimeoutError
+ # Ignore - some platforms don't support interrupting `gets`.
+ ensure
+ thread&.join
+ end
+ RUBY
+ end
+
+ def test_fork_close
+ omit "fork is not supported" unless Process.respond_to?(:fork)
+
+ assert_separately([], <<~'RUBY')
+ r, w = IO.pipe
+
+ thread = Thread.new do
+ r.read
+ end
+
+ Thread.pass until thread.status == "sleep"
+
+ pid = fork do
+ r.close
+ end
+
+ w.close
+
+ status = Process.wait2(pid).last
+ thread.join
+
+ assert_predicate(status, :success?)
+ RUBY
+ end
end
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 55296c1f23..b6372f25b8 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: false
require 'tempfile'
+require 'rbconfig/sizeof'
class TestIOBuffer < Test::Unit::TestCase
experimental = Warning[:experimental]
@@ -45,22 +46,22 @@ class TestIOBuffer < Test::Unit::TestCase
def test_new_internal
buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL)
assert_equal 1024, buffer.size
- refute buffer.external?
- assert buffer.internal?
- refute buffer.mapped?
+ refute_predicate buffer, :external?
+ assert_predicate buffer, :internal?
+ refute_predicate buffer, :mapped?
end
def test_new_mapped
buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED)
assert_equal 1024, buffer.size
- refute buffer.external?
- refute buffer.internal?
- assert buffer.mapped?
+ refute_predicate buffer, :external?
+ refute_predicate buffer, :internal?
+ assert_predicate buffer, :mapped?
end
def test_new_readonly
buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::READONLY)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
assert_raise IO::Buffer::AccessError do
buffer.set_string("")
@@ -73,12 +74,64 @@ class TestIOBuffer < Test::Unit::TestCase
def test_file_mapped
buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)}
- contents = buffer.get_string
+ assert_equal File.size(__FILE__), buffer.size
+ contents = buffer.get_string
assert_include contents, "Hello World"
assert_equal Encoding::BINARY, contents.encoding
end
+ def test_file_mapped_with_size
+ buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, 30, 0, IO::Buffer::READONLY)}
+ assert_equal 30, buffer.size
+
+ contents = buffer.get_string
+ assert_equal "# frozen_string_literal: false", contents
+ assert_equal Encoding::BINARY, contents.encoding
+ end
+
+ def test_file_mapped_size_too_large
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 200_000, 0, IO::Buffer::READONLY)}
+ end
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, File.size(__FILE__) + 1, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_size_just_enough
+ File.open(__FILE__) {|file|
+ assert_equal File.size(__FILE__), IO::Buffer.map(file, File.size(__FILE__), 0, IO::Buffer::READONLY).size
+ }
+ end
+
+ def test_file_mapped_offset_too_large
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, nil, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)}
+ end
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 20, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_zero_size
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 0, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_negative_size
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, -10, 0, IO::Buffer::READONLY)}
+ end
+ end
+
+ def test_file_mapped_negative_offset
+ assert_raise ArgumentError do
+ File.open(__FILE__) {|file| IO::Buffer.map(file, 20, -1, IO::Buffer::READONLY)}
+ end
+ end
+
def test_file_mapped_invalid
assert_raise TypeError do
IO::Buffer.map("foobar")
@@ -88,19 +141,19 @@ class TestIOBuffer < Test::Unit::TestCase
def test_string_mapped
string = "Hello World"
buffer = IO::Buffer.for(string)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
end
def test_string_mapped_frozen
string = "Hello World".freeze
buffer = IO::Buffer.for(string)
- assert buffer.readonly?
+ assert_predicate buffer, :readonly?
end
def test_string_mapped_mutable
string = "Hello World"
IO::Buffer.for(string) do |buffer|
- refute buffer.readonly?
+ refute_predicate buffer, :readonly?
buffer.set_value(:U8, 0, "h".ord)
@@ -121,6 +174,16 @@ class TestIOBuffer < Test::Unit::TestCase
end
end
+ def test_string_mapped_buffer_frozen
+ string = "Hello World".freeze
+ IO::Buffer.for(string) do |buffer|
+ assert_raise IO::Buffer::AccessError, "Buffer is not writable!" do
+ buffer.set_string("abc")
+ end
+ assert_equal "H".ord, buffer.get_value(:U8, 0)
+ end
+ end
+
def test_non_string
not_string = Object.new
@@ -343,10 +406,17 @@ class TestIOBuffer < Test::Unit::TestCase
:u64 => [0, 2**64-1],
:s64 => [-2**63, 0, 2**63-1],
+ :U128 => [0, 2**64, 2**127-1, 2**128-1],
+ :S128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1],
+ :u128 => [0, 2**64, 2**127-1, 2**128-1],
+ :s128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1],
+
:F32 => [-1.0, 0.0, 0.5, 1.0, 128.0],
:F64 => [-1.0, 0.0, 0.5, 1.0, 128.0],
}
+ SIZE_MAX = RbConfig::LIMITS["SIZE_MAX"]
+
def test_get_set_value
buffer = IO::Buffer.new(128)
@@ -355,6 +425,16 @@ class TestIOBuffer < Test::Unit::TestCase
buffer.set_value(data_type, 0, value)
assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}."
end
+ assert_raise(ArgumentError) {buffer.get_value(data_type, 128)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, 128, 0)}
+ case data_type
+ when :U8, :S8
+ else
+ assert_raise(ArgumentError) {buffer.get_value(data_type, 127)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, 127, 0)}
+ assert_raise(ArgumentError) {buffer.get_value(data_type, SIZE_MAX)}
+ assert_raise(ArgumentError) {buffer.set_value(data_type, SIZE_MAX, 0)}
+ end
end
end
@@ -411,6 +491,15 @@ class TestIOBuffer < Test::Unit::TestCase
buffer = IO::Buffer.for(string)
assert_equal string.bytes, buffer.each_byte.to_a
+ assert_equal string.bytes[3, 5], buffer.each_byte(3, 5).to_a
+ end
+
+ def test_each_byte_bounds_error
+ buffer = IO::Buffer.for("A")
+
+ assert_raise(ArgumentError) { buffer.each_byte(0, 2).to_a }
+ assert_raise(ArgumentError) { buffer.each_byte(1, 1).to_a }
+ assert_raise(ArgumentError) { buffer.each_byte(SIZE_MAX, 0).to_a }
end
def test_zero_length_each_byte
@@ -421,7 +510,21 @@ class TestIOBuffer < Test::Unit::TestCase
def test_clear
buffer = IO::Buffer.new(16)
- buffer.set_string("Hello World!")
+ assert_equal "\0" * 16, buffer.get_string
+ buffer.clear(1)
+ assert_equal "\1" * 16, buffer.get_string
+ buffer.clear(2, 1, 2)
+ assert_equal "\1" + "\2"*2 + "\1"*13, buffer.get_string
+ buffer.clear(2, 1)
+ assert_equal "\1" + "\2"*15, buffer.get_string
+ buffer.clear(260)
+ assert_equal "\4" * 16, buffer.get_string
+ assert_raise(TypeError) {buffer.clear("x")}
+
+ assert_raise(ArgumentError) {buffer.clear(0, 20)}
+ assert_raise(ArgumentError) {buffer.clear(0, 0, 20)}
+ assert_raise(ArgumentError) {buffer.clear(0, 10, 10)}
+ assert_raise(ArgumentError) {buffer.clear(0, SIZE_MAX-7, 10)}
end
def test_invalidation
@@ -599,6 +702,59 @@ class TestIOBuffer < Test::Unit::TestCase
assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not!
end
+ def test_operators_raise_on_freed_self
+ inner = IO::Buffer.new(IO::Buffer::PAGE_SIZE)
+ slice = inner.slice(0, 8)
+ inner.free
+
+ mask = IO::Buffer.for("ABCDEFGH")
+ assert_raise(IO::Buffer::InvalidatedError) { slice & mask }
+ assert_raise(IO::Buffer::InvalidatedError) { slice | mask }
+ assert_raise(IO::Buffer::InvalidatedError) { slice ^ mask }
+ assert_raise(IO::Buffer::InvalidatedError) { ~slice }
+ end
+
+ def test_operators_raise_on_freed_mask
+ inner = IO::Buffer.new(IO::Buffer::PAGE_SIZE)
+ mask_slice = inner.slice(0, 8)
+ inner.free
+
+ source = IO::Buffer.for("ABCDEFGH")
+ assert_raise(IO::Buffer::InvalidatedError) { source & mask_slice }
+ assert_raise(IO::Buffer::InvalidatedError) { source | mask_slice }
+ assert_raise(IO::Buffer::InvalidatedError) { source ^ mask_slice }
+ end
+
+ def test_bit_count
+ # All ones: 8 bits set per byte
+ assert_equal 8, IO::Buffer.for("\xFF").bit_count
+ # All zeros: no bits set
+ assert_equal 0, IO::Buffer.for("\x00").bit_count
+ # Mixed: 0xFF (8) + 0x00 (0) + 0x0F (4) = 12
+ assert_equal 12, IO::Buffer.for("\xFF\x00\x0F").bit_count
+ # Subrange: offset=0, length=1 => 0xFF => 8
+ assert_equal 8, IO::Buffer.for("\xFF\x00\x0F").bit_count(0, 1)
+ # Subrange: offset=1, length=1 => 0x00 => 0
+ assert_equal 0, IO::Buffer.for("\xFF\x00\x0F").bit_count(1, 1)
+ # Subrange: offset=2, length=1 => 0x0F => 4
+ assert_equal 4, IO::Buffer.for("\xFF\x00\x0F").bit_count(2, 1)
+ # Subrange: offset=1, length=2 => 0x00 + 0x0F = 4
+ assert_equal 4, IO::Buffer.for("\xFF\x00\x0F").bit_count(1, 2)
+ # Empty buffer: 0
+ assert_equal 0, IO::Buffer.new(0).bit_count
+ # 8-byte aligned: 8 bytes of 0xFF => 64 bits
+ assert_equal 64, IO::Buffer.for("\xFF" * 8).bit_count
+ # Cross 8-byte boundary: 9 bytes of 0xFF => 72 bits
+ assert_equal 72, IO::Buffer.for("\xFF" * 9).bit_count
+ # offset=0 with no length => defaults to full buffer:
+ assert_equal 12, IO::Buffer.for("\xFF\x00\x0F").bit_count(0)
+ # offset=1 with no length => 0x00 + 0x0F = 4:
+ assert_equal 4, IO::Buffer.for("\xFF\x00\x0F").bit_count(1)
+ # Out-of-range raises
+ assert_raise(ArgumentError) { IO::Buffer.for("\xFF").bit_count(0, 2) }
+ assert_raise(ArgumentError) { IO::Buffer.for("\xFF").bit_count(1, 1) }
+ end
+
def test_shared
message = "Hello World"
buffer = IO::Buffer.new(64, IO::Buffer::MAPPED | IO::Buffer::SHARED)
@@ -620,8 +776,8 @@ class TestIOBuffer < Test::Unit::TestCase
buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE)
begin
- assert buffer.private?
- refute buffer.readonly?
+ assert_predicate buffer, :private?
+ refute_predicate buffer, :readonly?
buffer.set_string("J")
@@ -683,4 +839,230 @@ class TestIOBuffer < Test::Unit::TestCase
buf.set_string('a', 0, 0)
assert_predicate buf, :empty?
end
+
+ # https://bugs.ruby-lang.org/issues/21210
+ def test_bug_21210
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ str = +"hello"
+ buf = IO::Buffer.for(str)
+ assert_predicate buf, :valid?
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_predicate buf, :valid?
+ end
+
+ def test_128_bit_integers
+ buffer = IO::Buffer.new(32)
+
+ # Test unsigned 128-bit integers
+ test_values_u128 = [
+ 0,
+ 1,
+ 2**64 - 1,
+ 2**64,
+ 2**127 - 1,
+ 2**128 - 1,
+ ]
+
+ test_values_u128.each do |value|
+ buffer.set_value(:u128, 0, value)
+ assert_equal value, buffer.get_value(:u128, 0), "u128: #{value}"
+
+ buffer.set_value(:U128, 0, value)
+ assert_equal value, buffer.get_value(:U128, 0), "U128: #{value}"
+ end
+
+ # Test signed 128-bit integers
+ test_values_s128 = [
+ -2**127,
+ -2**63 - 1,
+ -1,
+ 0,
+ 1,
+ 2**63,
+ 2**127 - 1,
+ ]
+
+ test_values_s128.each do |value|
+ buffer.set_value(:s128, 0, value)
+ assert_equal value, buffer.get_value(:s128, 0), "s128: #{value}"
+
+ buffer.set_value(:S128, 0, value)
+ assert_equal value, buffer.get_value(:S128, 0), "S128: #{value}"
+ end
+
+ # Test size_of
+ assert_equal 16, IO::Buffer.size_of(:u128)
+ assert_equal 16, IO::Buffer.size_of(:U128)
+ assert_equal 16, IO::Buffer.size_of(:s128)
+ assert_equal 16, IO::Buffer.size_of(:S128)
+ assert_equal 32, IO::Buffer.size_of([:u128, :u128])
+ end
+
+ def test_integer_endianness_swapping
+ # Test that byte order is swapped correctly for all signed and unsigned integers > 1 byte
+ host_is_le = IO::Buffer::HOST_ENDIAN == IO::Buffer::LITTLE_ENDIAN
+ host_is_be = IO::Buffer::HOST_ENDIAN == IO::Buffer::BIG_ENDIAN
+
+ # Test values that will produce different byte patterns when swapped
+ # Format: [little_endian_type, big_endian_type, test_value, expected_swapped_value]
+ # expected_swapped_value is the result when writing as le_type and reading as be_type
+ # (or vice versa) on a little-endian host
+ test_cases = [
+ [:u16, :U16, 0x1234, 0x3412],
+ [:s16, :S16, 0x1234, 0x3412],
+ [:u32, :U32, 0x12345678, 0x78563412],
+ [:s32, :S32, 0x12345678, 0x78563412],
+ [:u64, :U64, 0x0123456789ABCDEF, 0xEFCDAB8967452301],
+ [:s64, :S64, 0x0123456789ABCDEF, -1167088121787636991],
+ [:u128, :U128, 0x0123456789ABCDEF0123456789ABCDEF, 0xEFCDAB8967452301EFCDAB8967452301],
+ [:u128, :U128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301],
+ [:u128, :U128, 0xFEDCBA98765432100123456789ABCDEF, 0xEFCDAB89674523011032547698BADCFE],
+ [:u128, :U128, 0x123456789ABCDEF0FEDCBA9876543210, 0x1032547698BADCFEF0DEBC9A78563412],
+ [:s128, :S128, 0x0123456789ABCDEF0123456789ABCDEF, -21528975894082904073953971026863512831],
+ [:s128, :S128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301],
+ ]
+
+ test_cases.each do |le_type, be_type, value, expected_swapped|
+ buffer_size = IO::Buffer.size_of(le_type)
+ buffer = IO::Buffer.new(buffer_size * 2)
+
+ # Test little-endian round-trip
+ buffer.set_value(le_type, 0, value)
+ result_le = buffer.get_value(le_type, 0)
+ assert_equal value, result_le, "#{le_type}: round-trip failed"
+
+ # Test big-endian round-trip
+ buffer.set_value(be_type, buffer_size, value)
+ result_be = buffer.get_value(be_type, buffer_size)
+ assert_equal value, result_be, "#{be_type}: round-trip failed"
+
+ # Verify byte patterns are different when endianness differs from host
+ if host_is_le
+ # On little-endian host: le_type should match host, be_type should be swapped
+ # So the byte patterns should be different (unless value is symmetric)
+ # Read back with opposite endianness to verify swapping
+ result_le_read_as_be = buffer.get_value(be_type, 0)
+ result_be_read_as_le = buffer.get_value(le_type, buffer_size)
+
+ # The swapped reads should NOT equal the original value (unless it's symmetric)
+ # For most values, this will be different
+ if value != 0 && value != -1 && value.abs != 1
+ refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on LE host"
+ refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on LE host"
+ end
+
+ # Verify that reading back with correct endianness works
+ assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on LE host"
+ assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on LE host (with swapping)"
+ elsif host_is_be
+ # On big-endian host: be_type should match host, le_type should be swapped
+ result_le_read_as_be = buffer.get_value(be_type, 0)
+ result_be_read_as_le = buffer.get_value(le_type, buffer_size)
+
+ # The swapped reads should NOT equal the original value (unless it's symmetric)
+ if value != 0 && value != -1 && value.abs != 1
+ refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on BE host"
+ refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on BE host"
+ end
+
+ # Verify that reading back with correct endianness works
+ assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on BE host"
+ assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on BE host (with swapping)"
+ end
+
+ # Verify that when we write with one endianness and read with the opposite,
+ # we get the expected swapped value
+ buffer.set_value(le_type, 0, value)
+ swapped_value_le_to_be = buffer.get_value(be_type, 0)
+ assert_equal expected_swapped, swapped_value_le_to_be, "#{le_type} written, read as #{be_type} should produce expected swapped value"
+
+ # Also verify the reverse direction
+ buffer.set_value(be_type, buffer_size, value)
+ swapped_value_be_to_le = buffer.get_value(le_type, buffer_size)
+ assert_equal expected_swapped, swapped_value_be_to_le, "#{be_type} written, read as #{le_type} should produce expected swapped value"
+
+ # Verify that writing the swapped value back and reading with original endianness
+ # gives us the original value (double-swap should restore original)
+ buffer.set_value(be_type, 0, swapped_value_le_to_be)
+ round_trip_value = buffer.get_value(le_type, 0)
+ assert_equal value, round_trip_value, "#{le_type}/#{be_type}: double-swap should restore original value"
+ end
+ end
+
+ class Bug21882 < RuntimeError; end
+ def test_locked_exception
+ buf = IO::Buffer.new(10)
+ assert_raise(Bug21882, '#locked should propagate exception') do
+ buf.locked { raise Bug21882 }
+ end
+
+ # should be unlocked now and can be locked again
+ refute_predicate buf, :locked?
+ buf.locked { }
+ end
+
+ def test_locked_break
+ buf = IO::Buffer.new(10)
+ assert_equal :ok, (buf.locked { break :ok })
+
+ # should be unlocked now and can be locked again
+ refute_predicate buf, :locked?
+ buf.locked { }
+ end
+
+ def test_locked_throw
+ buf = IO::Buffer.new(10)
+ assert_equal :ok, (catch(:bug21882) { buf.locked { throw :bug21882, :ok } })
+
+ # should be unlocked now and can be locked again
+ refute_predicate buf, :locked?
+ buf.locked { }
+ end
+
+ def test_hexdump_default_width
+ buffer = IO::Buffer.for("Hello World")
+ hexdump = buffer.hexdump
+ assert_include hexdump, "Hello World"
+ assert_include hexdump, "0x00000000"
+ end
+
+ def test_hexdump_custom_width
+ buffer = IO::Buffer.for("A" * 64)
+ hexdump = buffer.hexdump(0, 64, 32)
+ assert_include hexdump, "0x00000000"
+ assert_include hexdump, "0x00000020"
+ end
+
+ def test_hexdump_maximum_width
+ buffer = IO::Buffer.for("A" * 2048)
+ # Maximum width is 1024
+ hexdump = buffer.hexdump(0, 1024, 1024)
+ assert_include hexdump, "0x00000000"
+ end
+
+ def test_hexdump_width_too_large
+ buffer = IO::Buffer.for("A")
+ # Width exceeding maximum (1024) should raise ArgumentError
+ assert_raise(ArgumentError) do
+ buffer.hexdump(0, 1, 1025)
+ end
+ end
+
+ def test_hexdump_width_negative
+ buffer = IO::Buffer.for("A")
+ assert_raise(ArgumentError) do
+ buffer.hexdump(0, 1, -1)
+ end
+ end
+
+ def test_hexdump_width_zero
+ buffer = IO::Buffer.for("A")
+ # Width must be at least 1
+ assert_raise(ArgumentError) do
+ buffer.hexdump(0, 1, 0)
+ end
+ end
end
diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb
index b01d627d92..83d4fb0c7b 100644
--- a/test/ruby/test_io_m17n.rb
+++ b/test/ruby/test_io_m17n.rb
@@ -1395,30 +1395,6 @@ EOT
}
end
- def test_open_pipe_r_enc
- EnvUtil.suppress_warning do # https://bugs.ruby-lang.org/issues/19630
- open("|#{EnvUtil.rubybin} -e 'putc 255'", "r:ascii-8bit") {|f|
- assert_equal(Encoding::ASCII_8BIT, f.external_encoding)
- assert_equal(nil, f.internal_encoding)
- s = f.read
- assert_equal(Encoding::ASCII_8BIT, s.encoding)
- assert_equal("\xff".force_encoding("ascii-8bit"), s)
- }
- end
- end
-
- def test_open_pipe_r_enc2
- EnvUtil.suppress_warning do # https://bugs.ruby-lang.org/issues/19630
- open("|#{EnvUtil.rubybin} -e 'putc \"\\u3042\"'", "r:UTF-8") {|f|
- assert_equal(Encoding::UTF_8, f.external_encoding)
- assert_equal(nil, f.internal_encoding)
- s = f.read
- assert_equal(Encoding::UTF_8, s.encoding)
- assert_equal("\u3042", s)
- }
- end
- end
-
def test_s_foreach_enc
with_tmpdir {
generate_file("t", "\xff")
@@ -2748,8 +2724,8 @@ EOT
def test_pos_with_buffer_end_cr
bug6401 = '[ruby-core:44874]'
with_tmpdir {
- # Read buffer size is 8191. This generates '\r' at 8191.
- lines = ["X" * 8187, "X"]
+ # Read buffer size is 8192. This generates '\r' at 8192.
+ lines = ["X" * 8188, "X"]
generate_file("tmp", lines.join("\r\n") + "\r\n")
open("tmp", "r") do |f|
@@ -2830,4 +2806,17 @@ EOT
flunk failure.join("\n---\n")
end
end
+
+ def test_each_codepoint_encoding_with_ungetc
+ File.open(File::NULL, "rt:utf-8") do |f|
+ f.ungetc(%Q[\u{3042}\u{3044}\u{3046}])
+ assert_equal [0x3042, 0x3044, 0x3046], f.each_codepoint.to_a
+ end
+ File.open(File::NULL, "rt:us-ascii") do |f|
+ f.ungetc(%Q[\u{3042}\u{3044}\u{3046}])
+ assert_raise(ArgumentError) do
+ f.each_codepoint.to_a
+ end
+ end
+ end
end
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
index 9eb9c84602..b4760dc412 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -92,7 +92,7 @@ class TestISeq < Test::Unit::TestCase
42
end
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_forwardable
@@ -102,7 +102,7 @@ class TestISeq < Test::Unit::TestCase
def foo(...); bar(...); end
}
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval.new.foo(40, 2))
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval.new.foo(40, 2))
end
def test_super_with_block
@@ -112,7 +112,7 @@ class TestISeq < Test::Unit::TestCase
end
42
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_super_with_block_hash_0
@@ -123,7 +123,7 @@ class TestISeq < Test::Unit::TestCase
end
42
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_super_with_block_and_kwrest
@@ -133,17 +133,16 @@ class TestISeq < Test::Unit::TestCase
end
42
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_lambda_with_ractor_roundtrip
iseq = compile(<<~EOF, __LINE__+1)
x = 42
- y = nil.instance_eval{ lambda { x } }
- Ractor.make_shareable(y)
+ y = Ractor.shareable_lambda{x}
y.call
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_super_with_anonymous_block
@@ -153,27 +152,23 @@ class TestISeq < Test::Unit::TestCase
end
42
EOF
- assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
end
def test_ractor_unshareable_outer_variable
name = "\u{2603 26a1}"
- y = nil.instance_eval do
- eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
+ assert_raise_with_message(Ractor::IsolationError, /\(#{name}\)/) do
+ eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}")
end
- assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
- Ractor.make_shareable(y)
- end
- y = nil.instance_eval do
- eval("proc {#{name} = []; proc {|x| #{name}}}").call
- end
- assert_raise_with_message(Ractor::IsolationError, /'#{name}'/) do
- Ractor.make_shareable(y)
+
+ assert_raise_with_message(Ractor::IsolationError, /\'#{name}\'/) do
+ eval("#{name} = []; Ractor.shareable_proc{#{name}}")
end
+
obj = Object.new
- def obj.foo(*) nil.instance_eval{ ->{super} } end
- assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable '\*'/) do
- Ractor.make_shareable(obj.foo)
+ def obj.foo(*) Ractor.shareable_proc{super} end
+ assert_raise_with_message(Ractor::IsolationError, /cannot make a shareable Proc because it can refer unshareable object \[\]/) do
+ obj.foo(*[])
end
end
@@ -182,7 +177,7 @@ class TestISeq < Test::Unit::TestCase
# shareable_constant_value: literal
REGEX = /#{}/ # [Bug #20569]
RUBY
- assert_includes iseq.to_binary, "REGEX".b
+ assert_includes iseq_to_binary(iseq), "REGEX".b
end
def test_disasm_encoding
@@ -217,6 +212,26 @@ class TestISeq < Test::Unit::TestCase
end
end
+ def test_compile_file_options
+ Tempfile.create(%w"test_iseq .rb") do |f|
+ f.puts('_ = "test"')
+ f.close
+ iseq = RubyVM::InstructionSequence.compile_file(f.path, { frozen_string_literal: false })
+ refute_predicate iseq.eval, :frozen?
+
+ iseq = RubyVM::InstructionSequence.compile_file(f.path, { frozen_string_literal: true })
+ assert_predicate iseq.eval, :frozen?
+ end
+ end
+
+ def test_compile_options
+ iseq = RubyVM::InstructionSequence.compile("'test'", nil, nil, nil, { frozen_string_literal: false })
+ refute_predicate iseq.eval, :frozen?
+
+ iseq = RubyVM::InstructionSequence.compile("'test'", nil, nil, nil, { frozen_string_literal: true })
+ assert_predicate iseq.eval, :frozen?
+ end
+
LINE_BEFORE_METHOD = __LINE__
def method_test_line_trace
@@ -297,6 +312,56 @@ class TestISeq < Test::Unit::TestCase
assert_raise(TypeError, bug11159) {compile(1)}
end
+ def test_invalid_source_no_memory_leak
+ # [Bug #21394]
+ assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ code = proc do |t|
+ RubyVM::InstructionSequence.new(nil)
+ rescue TypeError
+ else
+ raise "TypeError was not raised during RubyVM::InstructionSequence.new"
+ end
+
+ 10.times(&code)
+ begin;
+ 1_000_000.times(&code)
+ end;
+
+ # [Bug #21394]
+ # RubyVM::InstructionSequence.new calls rb_io_path, which dups the string
+ # and can leak memory if the dup raises
+ assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
+ MyError = Class.new(StandardError)
+ String.prepend(Module.new do
+ def initialize_dup(_)
+ if $raise_on_dup
+ raise MyError
+ else
+ super
+ end
+ end
+ end)
+
+ code = proc do |t|
+ Tempfile.create do |f|
+ $raise_on_dup = true
+ t.times do
+ RubyVM::InstructionSequence.new(f)
+ rescue MyError
+ else
+ raise "MyError was not raised during RubyVM::InstructionSequence.new"
+ end
+ ensure
+ $raise_on_dup = false
+ end
+ end
+
+ code.call(100)
+ begin;
+ code.call(1_000_000)
+ end;
+ end
+
def test_frozen_string_literal_compile_option
$f = 'f'
line = __LINE__ + 2
@@ -310,6 +375,20 @@ class TestISeq < Test::Unit::TestCase
assert_not_predicate(s4, :frozen?)
end
+ def test_frozen_string_literal_compile_option_file
+ Tempfile.create(%w[fsl .rb]) do |f|
+ f.write("['foo', 'foo', \"\#{$f}foo\", \"\#{'foo'}\"]\n")
+ f.flush
+ $f = 'f'
+ s1, s2, s3, s4 = RubyVM::InstructionSequence
+ .compile_file(f.path, frozen_string_literal: true).eval
+ assert_predicate(s1, :frozen?)
+ assert_predicate(s2, :frozen?)
+ assert_not_predicate(s3, :frozen?)
+ assert_not_predicate(s4, :frozen?)
+ end
+ end
+
# Safe call chain is not optimized when Coverage is running.
# So we can test it only when Coverage is not running.
def test_safe_call_chain
@@ -566,16 +645,20 @@ class TestISeq < Test::Unit::TestCase
}
end
+ def iseq_to_binary(iseq)
+ iseq.to_binary
+ rescue RuntimeError => e
+ omit e.message if /compile with coverage/ =~ e.message
+ raise
+ end
+
def assert_iseq_to_binary(code, mesg = nil)
iseq = RubyVM::InstructionSequence.compile(code)
bin = assert_nothing_raised(mesg) do
- iseq.to_binary
- rescue RuntimeError => e
- omit e.message if /compile with coverage/ =~ e.message
- raise
+ iseq_to_binary(iseq)
end
10.times do
- bin2 = iseq.to_binary
+ bin2 = iseq_to_binary(iseq)
assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)})
end
iseq2 = RubyVM::InstructionSequence.load_from_binary(bin)
@@ -593,7 +676,7 @@ class TestISeq < Test::Unit::TestCase
def test_to_binary_with_hidden_local_variables
assert_iseq_to_binary("for _foo in bar; end")
- bin = RubyVM::InstructionSequence.compile(<<-RUBY).to_binary
+ bin = iseq_to_binary(RubyVM::InstructionSequence.compile(<<-RUBY))
Object.new.instance_eval do
a = []
def self.bar; [1] end
@@ -633,6 +716,17 @@ class TestISeq < Test::Unit::TestCase
assert_equal([[:nokey]], iseq.eval.singleton_method(:foo).parameters)
end
+ def test_to_binary_dumps_noblock
+ iseq = assert_iseq_to_binary(<<-RUBY)
+ o = Object.new
+ class << o
+ def foo(&nil); end
+ end
+ o
+ RUBY
+ assert_equal([[:noblock]], iseq.eval.singleton_method(:foo).parameters)
+ end
+
def test_to_binary_line_info
assert_iseq_to_binary("#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #14660]').eval
begin;
@@ -668,7 +762,7 @@ class TestISeq < Test::Unit::TestCase
end
RUBY
- iseq_bin = iseq.to_binary
+ iseq_bin = iseq_to_binary(iseq)
iseq = ISeq.load_from_binary(iseq_bin)
lines = []
TracePoint.new(tracepoint_type){|tp|
@@ -764,7 +858,7 @@ class TestISeq < Test::Unit::TestCase
def test_iseq_builtin_load
Tempfile.create(["builtin", ".iseq"]) do |f|
f.binmode
- f.write(RubyVM::InstructionSequence.of(1.method(:abs)).to_binary)
+ f.write(iseq_to_binary(RubyVM::InstructionSequence.of(1.method(:abs))))
f.close
assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
@@ -804,7 +898,7 @@ class TestISeq < Test::Unit::TestCase
GC.start
Float(30)
}
- assert_equal :new, r.take
+ assert_equal :new, r.value
RUBY
end
@@ -812,6 +906,10 @@ class TestISeq < Test::Unit::TestCase
assert_ruby_status([], "BEGIN {exit}; while true && true; end")
end
+ def test_short_circuited_loop_condition
+ assert_ruby_status([], "while true || true; exit; end; abort")
+ end
+
def test_unreachable_syntax_error
mesg = /Invalid break/
assert_syntax_error("false and break", mesg)
@@ -855,9 +953,28 @@ class TestISeq < Test::Unit::TestCase
end
end
+ def test_serialize_anonymous_outer_variables
+ iseq = RubyVM::InstructionSequence.compile(<<~'RUBY')
+ obj = Object.new
+ def obj.test
+ [1].each do
+ raise "Oops"
+ rescue
+ return it
+ end
+ end
+ obj
+ RUBY
+
+ binary = iseq.to_binary # [Bug # 21370]
+ roundtripped_iseq = RubyVM::InstructionSequence.load_from_binary(binary)
+ object = roundtripped_iseq.eval
+ assert_equal 1, object.test
+ end
+
def test_loading_kwargs_memory_leak
assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true)
- a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary
+ a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary
begin;
1_000_000.times do
RubyVM::InstructionSequence.load_from_binary(a)
@@ -868,7 +985,7 @@ class TestISeq < Test::Unit::TestCase
def test_ibf_bignum
iseq = RubyVM::InstructionSequence.compile("0x0"+"_0123_4567_89ab_cdef"*5)
expected = iseq.eval
- result = RubyVM::InstructionSequence.load_from_binary(iseq.to_binary).eval
+ result = RubyVM::InstructionSequence.load_from_binary(iseq_to_binary(iseq)).eval
assert_equal expected, result, proc {sprintf("expected: %x, result: %x", expected, result)}
end
@@ -919,4 +1036,10 @@ class TestISeq < Test::Unit::TestCase
assert_predicate(status, :success?)
end
end
+
+ def test_compile_empty_under_gc_stress
+ EnvUtil.under_gc_stress do
+ RubyVM::InstructionSequence.compile_file(File::NULL)
+ end
+ end
end
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 4563308fa2..c836abd0c6 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -2424,6 +2424,21 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_raise(ArgumentError) { m.call(42, a: 1, **h2) }
end
+ def test_ruby2_keywords_post_arg
+ def self.a(*c, **kw) [c, kw] end
+ def self.b(*a, b) a(*a, b) end
+ assert_warn(/Skipping set of ruby2_keywords flag for b \(method accepts keywords or post arguments or method does not accept argument splat\)/) do
+ assert_nil(singleton_class.send(:ruby2_keywords, :b))
+ end
+ assert_equal([[{foo: 1}, {bar: 1}], {}], b({foo: 1}, bar: 1))
+
+ b = ->(*a, b){a(*a, b)}
+ assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or post arguments or proc does not accept argument splat\)/) do
+ b.ruby2_keywords
+ end
+ assert_equal([[{foo: 1}, {bar: 1}], {}], b.({foo: 1}, bar: 1))
+ end
+
def test_proc_ruby2_keywords
h1 = {:a=>1}
foo = ->(*args, &block){block.call(*args)}
@@ -2436,8 +2451,8 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_raise(ArgumentError) { foo.call(:a=>1, &->(arg, **kw){[arg, kw]}) }
assert_equal(h1, foo.call(:a=>1, &->(arg){arg}))
- [->(){}, ->(arg){}, ->(*args, **kw){}, ->(*args, k: 1){}, ->(*args, k: ){}].each do |pr|
- assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or proc does not accept argument splat\)/) do
+ [->(){}, ->(arg){}, ->(*args, x){}, ->(*args, **kw){}, ->(*args, k: 1){}, ->(*args, k: ){}].each do |pr|
+ assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or post arguments or proc does not accept argument splat\)/) do
pr.ruby2_keywords
end
end
@@ -2790,10 +2805,21 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(:opt, o.clear_last_opt(a: 1))
assert_nothing_raised(ArgumentError) { o.clear_last_empty_method(a: 1) }
- assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or method does not accept argument splat\)/) do
+ assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or post arguments or method does not accept argument splat\)/) do
assert_nil(c.send(:ruby2_keywords, :bar))
end
+ c.class_eval do
+ def bar_post(*a, x) = nil
+ define_method(:bar_post_bmethod) { |*a, x| }
+ end
+ assert_warn(/Skipping set of ruby2_keywords flag for bar_post \(method accepts keywords or post arguments or method does not accept argument splat\)/) do
+ assert_nil(c.send(:ruby2_keywords, :bar_post))
+ end
+ assert_warn(/Skipping set of ruby2_keywords flag for bar_post_bmethod \(method accepts keywords or post arguments or method does not accept argument splat\)/) do
+ assert_nil(c.send(:ruby2_keywords, :bar_post_bmethod))
+ end
+
utf16_sym = "abcdef".encode("UTF-16LE").to_sym
c.send(:define_method, utf16_sym, c.instance_method(:itself))
assert_warn(/abcdef/) do
@@ -4033,7 +4059,7 @@ class TestKeywordArguments < Test::Unit::TestCase
tap { m }
GC.start
tap { m }
- }, bug8964
+ }, bug8964, timeout: 30
assert_normal_exit %q{
prc = Proc.new {|a: []|}
GC.stress = true
diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb
index 3cbb54306c..ce0f338760 100644
--- a/test/ruby/test_lambda.rb
+++ b/test/ruby/test_lambda.rb
@@ -163,7 +163,7 @@ class TestLambdaParameters < Test::Unit::TestCase
end
def test_proc_inside_lambda_toplevel
- assert_separately [], <<~RUBY
+ assert_ruby_status [], <<~RUBY
lambda{
$g = proc{ return :pr }
}.call
@@ -276,27 +276,27 @@ class TestLambdaParameters < Test::Unit::TestCase
end
def test_do_lambda_source_location
- exp = [__LINE__ + 1, 12, __LINE__ + 5, 7]
+ exp_lineno = __LINE__ + 3
lmd = ->(x,
y,
z) do
#
end
- file, *loc = lmd.source_location
+ file, lineno = lmd.source_location
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(exp, loc)
+ assert_equal(exp_lineno, lineno, "must be at the beginning of the block")
end
def test_brace_lambda_source_location
- exp = [__LINE__ + 1, 12, __LINE__ + 5, 5]
+ exp_lineno = __LINE__ + 3
lmd = ->(x,
y,
z) {
#
}
- file, *loc = lmd.source_location
+ file, lineno = lmd.source_location
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(exp, loc)
+ assert_equal(exp_lineno, lineno, "must be at the beginning of the block")
end
def test_not_orphan_return
diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb
index 4dddbab50c..3652096237 100644
--- a/test/ruby/test_lazy_enumerator.rb
+++ b/test/ruby/test_lazy_enumerator.rb
@@ -608,7 +608,7 @@ EOS
end
def test_require_block
- %i[select reject drop_while take_while map flat_map].each do |method|
+ %i[select reject drop_while take_while map flat_map tap_each].each do |method|
assert_raise(ArgumentError){ [].lazy.send(method) }
end
end
@@ -715,4 +715,23 @@ EOS
def test_with_index_size
assert_equal(3, Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size)
end
+
+ def test_tap_each
+ out = []
+
+ e = (1..Float::INFINITY).lazy
+ .tap_each { |x| out << x }
+ .select(&:even?)
+ .first(5)
+
+ assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], out)
+ assert_equal([2, 4, 6, 8, 10], e)
+ end
+
+ def test_tap_each_is_not_intrusive
+ s = Step.new(1..3)
+
+ assert_equal(2, s.lazy.tap_each { |x| x }.map { |x| x * 2 }.first)
+ assert_equal(1, s.current)
+ end
end
diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb
index dbff3c4734..cff888d4b3 100644
--- a/test/ruby/test_literal.rb
+++ b/test/ruby/test_literal.rb
@@ -682,6 +682,11 @@ class TestRubyLiteral < Test::Unit::TestCase
$VERBOSE = verbose_bak
end
+ def test_rational_float
+ assert_equal(12, 0.12r * 100)
+ assert_equal(12, 0.1_2r * 100)
+ end
+
def test_symbol_list
assert_equal([:foo, :bar], %i[foo bar])
assert_equal([:"\"foo"], %i["foo])
diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb
index b0e2e9f849..9f7a3c7f4b 100644
--- a/test/ruby/test_m17n.rb
+++ b/test/ruby/test_m17n.rb
@@ -186,33 +186,35 @@ class TestM17N < Test::Unit::TestCase
end
def test_string_inspect_encoding
- EnvUtil.suppress_warning do
- begin
- orig_int = Encoding.default_internal
- orig_ext = Encoding.default_external
- Encoding.default_internal = nil
- [Encoding::UTF_8, Encoding::EUC_JP, Encoding::Windows_31J, Encoding::GB18030].
- each do |e|
- Encoding.default_external = e
- str = "\x81\x30\x81\x30".force_encoding('GB18030')
- assert_equal(Encoding::GB18030 == e ? %{"#{str}"} : '"\x{81308130}"', str.inspect)
- str = e("\xa1\x8f\xa1\xa1")
- expected = "\"\\xA1\x8F\xA1\xA1\"".force_encoding("EUC-JP")
- assert_equal(Encoding::EUC_JP == e ? expected : "\"\\xA1\\x{8FA1A1}\"", str.inspect)
- str = s("\x81@")
- assert_equal(Encoding::Windows_31J == e ? %{"#{str}"} : '"\x{8140}"', str.inspect)
- str = "\u3042\u{10FFFD}"
- assert_equal(Encoding::UTF_8 == e ? %{"#{str}"} : '"\u3042\u{10FFFD}"', str.inspect)
- end
- Encoding.default_external = Encoding::UTF_8
- [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE,
- Encoding::UTF8_SOFTBANK].each do |e|
- str = "abc".encode(e)
- assert_equal('"abc"', str.inspect)
- end
- ensure
- Encoding.default_internal = orig_int
- Encoding.default_external = orig_ext
+ [
+ Encoding::UTF_8,
+ Encoding::EUC_JP,
+ Encoding::Windows_31J,
+ Encoding::GB18030,
+ ].each do |e|
+ EnvUtil.with_default_external(e) do
+ str = "\x81\x30\x81\x30".force_encoding('GB18030')
+ assert_equal(Encoding::GB18030 == e ? %{"#{str}"} : '"\x{81308130}"', str.inspect)
+ str = e("\xa1\x8f\xa1\xa1")
+ expected = "\"\\xA1\x8F\xA1\xA1\"".force_encoding("EUC-JP")
+ assert_equal(Encoding::EUC_JP == e ? expected : "\"\\xA1\\x{8FA1A1}\"", str.inspect)
+ str = s("\x81@")
+ assert_equal(Encoding::Windows_31J == e ? %{"#{str}"} : '"\x{8140}"', str.inspect)
+ str = "\u3042\u{10FFFD}"
+ assert_equal(Encoding::UTF_8 == e ? %{"#{str}"} : '"\u3042\u{10FFFD}"', str.inspect)
+ end
+ end
+
+ EnvUtil.with_default_external(Encoding::UTF_8) do
+ [
+ Encoding::UTF_16BE,
+ Encoding::UTF_16LE,
+ Encoding::UTF_32BE,
+ Encoding::UTF_32LE,
+ Encoding::UTF8_SOFTBANK
+ ].each do |e|
+ str = "abc".encode(e)
+ assert_equal('"abc"', str.inspect)
end
end
end
@@ -246,59 +248,43 @@ class TestM17N < Test::Unit::TestCase
end
def test_object_utf16_32_inspect
- EnvUtil.suppress_warning do
- begin
- orig_int = Encoding.default_internal
- orig_ext = Encoding.default_external
- Encoding.default_internal = nil
- Encoding.default_external = Encoding::UTF_8
- o = Object.new
- [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].each do |e|
- o.instance_eval "undef inspect;def inspect;'abc'.encode('#{e}');end"
- assert_equal '[abc]', [o].inspect
- end
- ensure
- Encoding.default_internal = orig_int
- Encoding.default_external = orig_ext
+ EnvUtil.with_default_external(Encoding::UTF_8) do
+ o = Object.new
+ [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].each do |e|
+ o.instance_eval "undef inspect;def inspect;'abc'.encode('#{e}');end"
+ assert_equal '[abc]', [o].inspect
end
end
end
def test_object_inspect_external
- orig_v, $VERBOSE = $VERBOSE, false
- orig_int, Encoding.default_internal = Encoding.default_internal, nil
- orig_ext = Encoding.default_external
-
omit "https://bugs.ruby-lang.org/issues/18338"
o = Object.new
- Encoding.default_external = Encoding::UTF_16BE
- def o.inspect
- "abc"
- end
- assert_nothing_raised(Encoding::CompatibilityError) { [o].inspect }
+ EnvUtil.with_default_external(Encoding::UTF_16BE) do
+ def o.inspect
+ "abc"
+ end
+ assert_nothing_raised(Encoding::CompatibilityError) { [o].inspect }
- def o.inspect
- "abc".encode(Encoding.default_external)
+ def o.inspect
+ "abc".encode(Encoding.default_external)
+ end
+ assert_equal '[abc]', [o].inspect
end
- assert_equal '[abc]', [o].inspect
-
- Encoding.default_external = Encoding::US_ASCII
- def o.inspect
- "\u3042"
- end
- assert_equal '[\u3042]', [o].inspect
+ EnvUtil.with_default_external(Encoding::US_ASCII) do
+ def o.inspect
+ "\u3042"
+ end
+ assert_equal '[\u3042]', [o].inspect
- def o.inspect
- "\x82\xa0".force_encoding(Encoding::Windows_31J)
+ def o.inspect
+ "\x82\xa0".force_encoding(Encoding::Windows_31J)
+ end
+ assert_equal '[\x{82A0}]', [o].inspect
end
- assert_equal '[\x{82A0}]', [o].inspect
- ensure
- Encoding.default_internal = orig_int
- Encoding.default_external = orig_ext
- $VERBOSE = orig_v
end
def test_str_dump
diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb
index bcd8892f23..48a67e1dc5 100644
--- a/test/ruby/test_marshal.rb
+++ b/test/ruby/test_marshal.rb
@@ -268,7 +268,11 @@ class TestMarshal < Test::Unit::TestCase
classISO8859_1.name
ClassISO8859_1 = classISO8859_1
- def test_class_nonascii
+ moduleUTF8 = const_set("C\u{30af 30e9 30b9}", Module.new)
+ moduleUTF8.name
+ ModuleUTF8 = moduleUTF8
+
+ def test_nonascii_class_instance
a = ClassUTF8.new
assert_instance_of(ClassUTF8, Marshal.load(Marshal.dump(a)), '[ruby-core:24790]')
@@ -301,10 +305,16 @@ class TestMarshal < Test::Unit::TestCase
end
end
+ def test_nonascii_class_module
+ assert_same(ClassUTF8, Marshal.load(Marshal.dump(ClassUTF8)))
+ assert_same(ClassISO8859_1, Marshal.load(Marshal.dump(ClassISO8859_1)))
+ assert_same(ModuleUTF8, Marshal.load(Marshal.dump(ModuleUTF8)))
+ end
+
def test_regexp2
assert_equal(/\\u/, Marshal.load("\004\b/\b\\\\u\000"))
assert_equal(/u/, Marshal.load("\004\b/\a\\u\000"))
- assert_equal(/u/, Marshal.load("\004\bI/\a\\u\000\006:\016@encoding\"\vEUC-JP"))
+ assert_raise(FrozenError) { Marshal.load("\x04\bI/\x06u\x00\a:\x06EF:\t@fooi/") }
bug2109 = '[ruby-core:25625]'
a = "\x82\xa0".force_encoding(Encoding::Windows_31J)
@@ -459,6 +469,30 @@ class TestMarshal < Test::Unit::TestCase
assert_equal(o1.foo, o2.foo)
end
+ class TooComplex
+ def initialize
+ @marshal_complex = 1
+ end
+ end
+
+ def test_complex_shape_object_id_not_dumped
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ TooComplex.new.instance_variable_set("@TestObjectIdTooComplex#{i}", 1)
+ end
+ obj = TooComplex.new
+ ivar = "@a#{rand(10_000).to_s.rjust(5, '0')}"
+ obj.instance_variable_set(ivar, 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(obj), :complex?)
+ end
+ obj.object_id
+ assert_equal "\x04\bo:\x1CTestMarshal::TooComplex\a:\x15@marshal_complexi\x06:\f#{ivar}i\x06".b, Marshal.dump(obj)
+ end
+
def test_marshal_complex
assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x05")}
assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x06i\x00")}
@@ -653,10 +687,10 @@ class TestMarshal < Test::Unit::TestCase
Marshal.load(d)
}
- # cleanup
+ ensure
self.class.class_eval do
remove_const name
- end
+ end if c
end
def test_unloadable_userdef
@@ -670,9 +704,17 @@ class TestMarshal < Test::Unit::TestCase
Marshal.load(d)
}
- # cleanup
+ ensure
self.class.class_eval do
remove_const name
+ end if c
+ end
+
+ def test_recursive_userdef
+ t = Time.utc(0)
+ t.instance_eval {@v = t}
+ assert_raise_with_message(RuntimeError, /recursive\b.*\b_dump/) do
+ Marshal.dump(t)
end
end
@@ -817,17 +859,15 @@ class TestMarshal < Test::Unit::TestCase
def test_marshal_dump_adding_instance_variable
obj = Bug15968.new
- assert_raise_with_message(RuntimeError, /instance variable added/) do
- Marshal.dump(obj)
- end
+ loaded = Marshal.load(Marshal.dump(obj))
+ assert_nil loaded.baz
end
def test_marshal_dump_removing_instance_variable
obj = Bug15968.new
obj.baz = :Bug15968
- assert_raise_with_message(RuntimeError, /instance variable removed/) do
- Marshal.dump(obj)
- end
+ loaded = Marshal.load(Marshal.dump(obj))
+ assert_equal :Bug15968, loaded.baz
end
ruby2_keywords def ruby2_keywords_hash(*a)
@@ -893,6 +933,41 @@ class TestMarshal < Test::Unit::TestCase
end
end
+ def test_load_overread
+ input = Struct.new(:bytes, :used) do
+ def initialize
+ super("\x04\x08[\x07".bytes, false)
+ end
+
+ def getbyte
+ bytes.shift
+ end
+
+ def read(_len, _outbuf = nil)
+ return nil if used
+ self.used = true
+ "0" * (1024 * 128)
+ end
+ end.new
+
+ assert_equal([nil, nil], Marshal.load(input))
+ end
+
+ def test_bignum_len_overflow
+ assert_raise(ArgumentError) do
+ Marshal.load("\x04\x08l+\x04\x00\x00\x00\x40")
+ end
+ assert_raise(ArgumentError) do
+ Marshal.load("\x04\x08l+\xfc\x00\x00\x00\x80")
+ end
+ end
+
+ def test_bignum_invalid_sign
+ assert_raise(ArgumentError) do
+ Marshal.load("\x04\bl?")
+ end
+ end
+
class TestMarshalFreezeProc < Test::Unit::TestCase
include MarshalTestLib
@@ -946,7 +1021,7 @@ class TestMarshal < Test::Unit::TestCase
end
def test_proc_returned_object_are_not_frozen
- source = ["foo", {}, /foo/, 1..2]
+ source = ["foo", {}, 1..2]
objects = Marshal.load(encode(source), ->(o) { o.dup }, freeze: true)
assert_equal source, objects
refute_predicate objects, :frozen?
@@ -960,5 +1035,19 @@ class TestMarshal < Test::Unit::TestCase
refute_predicate Object, :frozen?
refute_predicate Kernel, :frozen?
end
+
+ def test_linked_strings_are_frozen
+ str = "test"
+ str.instance_variable_set(:@self, str)
+ source = [str, str]
+
+ objects = Marshal.load(encode(source), freeze: true)
+ assert_predicate objects[0], :frozen?
+ assert_predicate objects[1], :frozen?
+ assert_same objects[0], objects[1]
+ assert_same objects[0], objects[0].instance_variable_get(:@self)
+ assert_same objects[1], objects[1].instance_variable_get(:@self)
+ assert_same objects[0].instance_variable_get(:@self), objects[1].instance_variable_get(:@self)
+ end
end
end
diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb
index 6e67099c6b..e134600cc4 100644
--- a/test/ruby/test_math.rb
+++ b/test/ruby/test_math.rb
@@ -147,6 +147,13 @@ class TestMath < Test::Unit::TestCase
check(Math::E ** 2, Math.exp(2))
end
+ def test_expm1
+ check(0, Math.expm1(0))
+ check(Math.sqrt(Math::E) - 1, Math.expm1(0.5))
+ check(Math::E - 1, Math.expm1(1))
+ check(Math::E ** 2 - 1, Math.expm1(2))
+ end
+
def test_log
check(0, Math.log(1))
check(1, Math.log(Math::E))
@@ -201,6 +208,19 @@ class TestMath < Test::Unit::TestCase
assert_nothing_raised { assert_infinity(-Math.log10(0)) }
end
+ def test_log1p
+ check(0, Math.log1p(0))
+ check(1, Math.log1p(Math::E - 1))
+ check(Math.log(2.0 ** 64 + 1), Math.log1p(1 << 64))
+ check(Math.log(2) * 1024.0, Math.log1p(2 ** 1024))
+ assert_nothing_raised { assert_infinity(Math.log1p(1.0/0)) }
+ assert_nothing_raised { assert_infinity(-Math.log1p(-1.0)) }
+ assert_raise_with_message(Math::DomainError, /\blog1p\b/) { Math.log1p(-1.1) }
+ assert_raise_with_message(Math::DomainError, /\blog1p\b/) { Math.log1p(-Float::EPSILON-1) }
+ assert_nothing_raised { assert_nan(Math.log1p(Float::NAN)) }
+ assert_nothing_raised { assert_infinity(-Math.log1p(-1)) }
+ end
+
def test_sqrt
check(0, Math.sqrt(0))
check(1, Math.sqrt(1))
@@ -301,11 +321,21 @@ class TestMath < Test::Unit::TestCase
assert_float_and_int([Math.log(6), 1], Math.lgamma(4))
assert_raise_with_message(Math::DomainError, /\blgamma\b/) { Math.lgamma(-Float::INFINITY) }
+
+ x, sign = Math.lgamma(+0.0)
+ mesg = "Math.lgamma(+0.0) should be [INF, +1]"
+ assert_infinity(x, mesg)
+ assert_equal(+1, sign, mesg)
+
x, sign = Math.lgamma(-0.0)
mesg = "Math.lgamma(-0.0) should be [INF, -1]"
assert_infinity(x, mesg)
assert_equal(-1, sign, mesg)
- x, sign = Math.lgamma(Float::NAN)
+
+ x, = Math.lgamma(-1)
+ assert_infinity(x, "Math.lgamma(-1) should be +INF")
+
+ x, = Math.lgamma(Float::NAN)
assert_nan(x)
end
diff --git a/test/ruby/test_memory_view.rb b/test/ruby/test_memory_view.rb
index 5a39084d18..d0122ddd59 100644
--- a/test/ruby/test_memory_view.rb
+++ b/test/ruby/test_memory_view.rb
@@ -335,7 +335,7 @@ class TestMemoryView < Test::Unit::TestCase
p mv[[0, 2]]
mv[[1, 3]]
end
- p r.take
+ p r.value
end;
end
end
diff --git a/test/ruby/test_metaclass.rb b/test/ruby/test_metaclass.rb
index 8c1990a78c..6570fa5945 100644
--- a/test/ruby/test_metaclass.rb
+++ b/test/ruby/test_metaclass.rb
@@ -163,6 +163,6 @@ class TestMetaclass < Test::Unit::TestCase
assert_nothing_raised{ metametaclass_of_bar.metaclass_method_c }
assert_nothing_raised{ metametaclass_of_bar.metametaclass_method_o }
assert_nothing_raised{ metametaclass_of_bar.metametaclass_method_f }
- assert_raise(NoMethodError){ metametaclass_of_bar.metaclass_method_b }
+ assert_raise(NoMethodError){ metametaclass_of_bar.metametaclass_method_b }
end
end
diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb
index 5d5d5aac02..00512bf2c6 100644
--- a/test/ruby/test_method.rb
+++ b/test/ruby/test_method.rb
@@ -32,6 +32,7 @@ class TestMethod < Test::Unit::TestCase
def mk7(a, b = nil, *c, d, **o) nil && o end
def mk8(a, b = nil, *c, d, e:, f: nil, **o) nil && o end
def mnk(**nil) end
+ def mnb(&nil) end
def mf(...) end
class Base
@@ -111,6 +112,20 @@ class TestMethod < Test::Unit::TestCase
end
end
+ def test_unbound_method_equality_with_extended_module
+ m = Module.new { def hello; "hello"; end }
+ base = Class.new { extend m }
+ sub = Class.new(base)
+
+ from_module = m.instance_method(:hello)
+ from_base = base.method(:hello).unbind
+ from_sub = sub.method(:hello).unbind
+
+ assert_equal(from_module, from_base)
+ assert_equal(from_module, from_sub)
+ assert_equal(from_base, from_sub)
+ end
+
def test_callee
assert_equal(:test_callee, __method__)
assert_equal(:m, Class.new {def m; __method__; end}.new.m)
@@ -284,8 +299,10 @@ class TestMethod < Test::Unit::TestCase
assert_raise(TypeError) { m.bind(Object.new) }
cx = EnvUtil.labeled_class("X\u{1f431}")
- assert_raise_with_message(TypeError, /X\u{1f431}/) do
- o.method(cx)
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{1f431}/) do
+ o.method(cx)
+ end
end
end
@@ -315,9 +332,12 @@ class TestMethod < Test::Unit::TestCase
assert_raise(TypeError) do
Class.new.class_eval { define_method(:bar, o.method(:bar)) }
end
+
cx = EnvUtil.labeled_class("X\u{1f431}")
- assert_raise_with_message(TypeError, /X\u{1F431}/) do
- Class.new {define_method(cx) {}}
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{1F431}/) do
+ Class.new {define_method(cx) {}}
+ end
end
end
@@ -483,6 +503,20 @@ class TestMethod < Test::Unit::TestCase
end
end
+ def test_clone_preserves_singleton_methods
+ m = method(:itself)
+ m.define_singleton_method(:foo) { :bar }
+ assert_equal(:bar, m.foo)
+ assert_equal(:bar, m.clone.foo)
+ end
+
+ def test_dup_does_not_preserve_singleton_methods
+ m = method(:itself)
+ m.define_singleton_method(:foo) { :bar }
+ assert_equal(:bar, m.foo)
+ assert_raise(NoMethodError) { m.dup.foo }
+ end
+
def test_inspect
o = Object.new
def o.foo; end; line_no = __LINE__
@@ -598,6 +632,7 @@ class TestMethod < Test::Unit::TestCase
define_method(:pmk7) {|a, b = nil, *c, d, **o|}
define_method(:pmk8) {|a, b = nil, *c, d, e:, f: nil, **o|}
define_method(:pmnk) {|**nil|}
+ define_method(:pmnb) {|&nil|}
def test_bound_parameters
assert_equal([], method(:m0).parameters)
@@ -621,6 +656,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:mk7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:mk8).parameters)
assert_equal([[:nokey]], method(:mnk).parameters)
+ assert_equal([[:noblock]], method(:mnb).parameters)
# pending
assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], method(:mf).parameters)
end
@@ -647,6 +683,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:mk7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:mk8).parameters)
assert_equal([[:nokey]], self.class.instance_method(:mnk).parameters)
+ assert_equal([[:noblock]], self.class.instance_method(:mnb).parameters)
# pending
assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], self.class.instance_method(:mf).parameters)
end
@@ -672,6 +709,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:pmk7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:pmk8).parameters)
assert_equal([[:nokey]], method(:pmnk).parameters)
+ assert_equal([[:noblock]], method(:pmnb).parameters)
end
def test_bmethod_unbound_parameters
@@ -696,6 +734,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:pmk7).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:pmk8).parameters)
assert_equal([[:nokey]], self.class.instance_method(:pmnk).parameters)
+ assert_equal([[:noblock]], self.class.instance_method(:pmnb).parameters)
end
def test_hidden_parameters
@@ -1439,6 +1478,46 @@ class TestMethod < Test::Unit::TestCase
def foo
a = b = c = a = b = c = 12345
end
+
+ def binding_noarg
+ a = a = 12345
+ binding
+ end
+
+ def binding_one_arg(x)
+ a = a = 12345
+ binding
+ end
+
+ def binding_optargs(x, y=42)
+ a = a = 12345
+ binding
+ end
+
+ def binding_anyargs(*x)
+ a = a = 12345
+ binding
+ end
+
+ def binding_keywords(x: 42)
+ a = a = 12345
+ binding
+ end
+
+ def binding_anykeywords(**x)
+ a = a = 12345
+ binding
+ end
+
+ def binding_forwarding(...)
+ a = a = 12345
+ binding
+ end
+
+ def binding_forwarding1(x, ...)
+ a = a = 12345
+ binding
+ end
end
def test_to_proc_binding
@@ -1457,6 +1536,66 @@ class TestMethod < Test::Unit::TestCase
assert_equal([:bar, :foo], b.local_variables.sort, bug11012)
end
+ def test_method_binding
+ c = C.new
+
+ b = c.binding_noarg
+ assert_equal(12345, b.local_variable_get(:a))
+
+ b = c.binding_one_arg(0)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(0, b.local_variable_get(:x))
+
+ b = c.binding_anyargs()
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal([], b.local_variable_get(:x))
+ b = c.binding_anyargs(0)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal([0], b.local_variable_get(:x))
+ b = c.binding_anyargs(0, 1)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal([0, 1], b.local_variable_get(:x))
+
+ b = c.binding_optargs(0)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(0, b.local_variable_get(:x))
+ assert_equal(42, b.local_variable_get(:y))
+ b = c.binding_optargs(0, 1)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(0, b.local_variable_get(:x))
+ assert_equal(1, b.local_variable_get(:y))
+
+ b = c.binding_keywords()
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(42, b.local_variable_get(:x))
+ b = c.binding_keywords(x: 102)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(102, b.local_variable_get(:x))
+
+ b = c.binding_anykeywords()
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal({}, b.local_variable_get(:x))
+ b = c.binding_anykeywords(foo: 999)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal({foo: 999}, b.local_variable_get(:x))
+
+ b = c.binding_forwarding()
+ assert_equal(12345, b.local_variable_get(:a))
+ b = c.binding_forwarding(0)
+ assert_equal(12345, b.local_variable_get(:a))
+ b = c.binding_forwarding(0, 1)
+ assert_equal(12345, b.local_variable_get(:a))
+ b = c.binding_forwarding(foo: 42)
+ assert_equal(12345, b.local_variable_get(:a))
+
+ b = c.binding_forwarding1(987)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(987, b.local_variable_get(:x))
+ b = c.binding_forwarding1(987, 654)
+ assert_equal(12345, b.local_variable_get(:a))
+ assert_equal(987, b.local_variable_get(:x))
+ end
+
MethodInMethodClass_Setup = -> do
remove_const :MethodInMethodClass if defined? MethodInMethodClass
@@ -1512,7 +1651,7 @@ class TestMethod < Test::Unit::TestCase
begin
foo(1)
rescue ArgumentError => e
- assert_equal "main.rb:#{$line_method}:in 'foo'", e.backtrace.first
+ assert_equal "main.rb:#{$line_method}:in 'Object#foo'", e.backtrace.first
end
EOS
END_OF_BODY
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb
index 4c171bb439..ad83d09823 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -9,18 +9,18 @@ class TestModule < Test::Unit::TestCase
yield
end
- def assert_method_defined?(klass, mid, message="")
+ def assert_method_defined?(klass, (mid, *args), message="")
message = build_message(message, "#{klass}\##{mid} expected to be defined.")
_wrap_assertion do
- klass.method_defined?(mid) or
+ klass.method_defined?(mid, *args) or
raise Test::Unit::AssertionFailedError, message, caller(3)
end
end
- def assert_method_not_defined?(klass, mid, message="")
+ def assert_method_not_defined?(klass, (mid, *args), message="")
message = build_message(message, "#{klass}\##{mid} expected to not be defined.")
_wrap_assertion do
- klass.method_defined?(mid) and
+ klass.method_defined?(mid, *args) and
raise Test::Unit::AssertionFailedError, message, caller(3)
end
end
@@ -412,19 +412,6 @@ class TestModule < Test::Unit::TestCase
assert_equal([:MIXIN, :USER], User.constants.sort)
end
- def test_initialize_copy
- mod = Module.new { define_method(:foo) {:first} }
- klass = Class.new { include mod }
- instance = klass.new
- assert_equal(:first, instance.foo)
- new_mod = Module.new { define_method(:foo) { :second } }
- assert_raise(TypeError) do
- mod.send(:initialize_copy, new_mod)
- end
- 4.times { GC.start }
- assert_equal(:first, instance.foo) # [BUG] unreachable
- end
-
def test_initialize_copy_empty
m = Module.new do
def x
@@ -435,11 +422,6 @@ class TestModule < Test::Unit::TestCase
assert_equal([:x], m.instance_methods)
assert_equal([:@x], m.instance_variables)
assert_equal([:X], m.constants)
- assert_raise(TypeError) do
- m.module_eval do
- initialize_copy(Module.new)
- end
- end
m = Class.new(Module) do
def initialize_copy(other)
@@ -601,7 +583,7 @@ class TestModule < Test::Unit::TestCase
end
def test_gc_prepend_chain
- assert_separately([], <<-EOS)
+ assert_ruby_status([], <<-EOS)
10000.times { |i|
m1 = Module.new do
def foo; end
@@ -831,40 +813,40 @@ class TestModule < Test::Unit::TestCase
def test_method_defined?
[User, Class.new{include User}, Class.new{prepend User}].each do |klass|
[[], [true]].each do |args|
- assert !klass.method_defined?(:wombat, *args)
- assert klass.method_defined?(:mixin, *args)
- assert klass.method_defined?(:user, *args)
- assert klass.method_defined?(:user2, *args)
- assert !klass.method_defined?(:user3, *args)
+ assert_method_not_defined?(klass, [:wombat, *args])
+ assert_method_defined?(klass, [:mixin, *args])
+ assert_method_defined?(klass, [:user, *args])
+ assert_method_defined?(klass, [:user2, *args])
+ assert_method_not_defined?(klass, [:user3, *args])
- assert !klass.method_defined?("wombat", *args)
- assert klass.method_defined?("mixin", *args)
- assert klass.method_defined?("user", *args)
- assert klass.method_defined?("user2", *args)
- assert !klass.method_defined?("user3", *args)
+ assert_method_not_defined?(klass, ["wombat", *args])
+ assert_method_defined?(klass, ["mixin", *args])
+ assert_method_defined?(klass, ["user", *args])
+ assert_method_defined?(klass, ["user2", *args])
+ assert_method_not_defined?(klass, ["user3", *args])
end
end
end
def test_method_defined_without_include_super
- assert User.method_defined?(:user, false)
- assert !User.method_defined?(:mixin, false)
- assert Mixin.method_defined?(:mixin, false)
+ assert_method_defined?(User, [:user, false])
+ assert_method_not_defined?(User, [:mixin, false])
+ assert_method_defined?(Mixin, [:mixin, false])
User.const_set(:FOO, c = Class.new)
c.prepend(User)
- assert !c.method_defined?(:user, false)
+ assert_method_not_defined?(c, [:user, false])
c.define_method(:user){}
- assert c.method_defined?(:user, false)
+ assert_method_defined?(c, [:user, false])
- assert !c.method_defined?(:mixin, false)
+ assert_method_not_defined?(c, [:mixin, false])
c.define_method(:mixin){}
- assert c.method_defined?(:mixin, false)
+ assert_method_defined?(c, [:mixin, false])
- assert !c.method_defined?(:userx, false)
+ assert_method_not_defined?(c, [:userx, false])
c.define_method(:userx){}
- assert c.method_defined?(:userx, false)
+ assert_method_defined?(c, [:userx, false])
# cleanup
User.class_eval do
@@ -1291,8 +1273,11 @@ class TestModule < Test::Unit::TestCase
assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-16le"), :foo) }
assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-32be"), :foo) }
assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-32le"), :foo) }
+
cx = EnvUtil.labeled_class("X\u{3042}")
- assert_raise_with_message(TypeError, /X\u{3042}/) { c1.const_set(cx, :foo) }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /X\u{3042}/) { c1.const_set(cx, :foo) }
+ end
end
def test_const_get_invalid_name
@@ -1449,6 +1434,7 @@ class TestModule < Test::Unit::TestCase
c.instance_eval { attr_reader :"." }
end
+ c = Class.new
assert_equal([:a], c.class_eval { attr :a })
assert_equal([:b, :c], c.class_eval { attr :b, :c })
assert_equal([:d], c.class_eval { attr_reader :d })
@@ -1457,6 +1443,16 @@ class TestModule < Test::Unit::TestCase
assert_equal([:h=, :i=], c.class_eval { attr_writer :h, :i })
assert_equal([:j, :j=], c.class_eval { attr_accessor :j })
assert_equal([:k, :k=, :l, :l=], c.class_eval { attr_accessor :k, :l })
+
+ c = Class.new
+ assert_equal([:a], c.class_eval { attr "a" })
+ assert_equal([:b, :c], c.class_eval { attr "b", "c" })
+ assert_equal([:d], c.class_eval { attr_reader "d" })
+ assert_equal([:e, :f], c.class_eval { attr_reader "e", "f" })
+ assert_equal([:g=], c.class_eval { attr_writer "g" })
+ assert_equal([:h=, :i=], c.class_eval { attr_writer "h", "i" })
+ assert_equal([:j, :j=], c.class_eval { attr_accessor "j" })
+ assert_equal([:k, :k=, :l, :l=], c.class_eval { attr_accessor "k", "l" })
end
def test_alias_method
@@ -2826,7 +2822,7 @@ class TestModule < Test::Unit::TestCase
b = a.dup
b.new.a = 'B'
- assert_equal 'A', a.new.a, '[ruby-core:17019]'
+ assert_equal 'B', a.new.a, '[ruby-core:17019] behaviour changed: cvar resolves through original CREF'
end
Bug6891 = '[ruby-core:47241]'
@@ -3020,17 +3016,17 @@ class TestModule < Test::Unit::TestCase
bug11532 = '[ruby-core:70828] [Bug #11532]'
c = Class.new {const_set(:A, 1)}.freeze
- assert_raise_with_message(FrozenError, /frozen class/, bug11532) {
+ assert_raise_with_message(FrozenError, /frozen Class/, bug11532) {
c.class_eval {private_constant :A}
}
c = Class.new {const_set(:A, 1); private_constant :A}.freeze
- assert_raise_with_message(FrozenError, /frozen class/, bug11532) {
+ assert_raise_with_message(FrozenError, /frozen Class/, bug11532) {
c.class_eval {public_constant :A}
}
c = Class.new {const_set(:A, 1)}.freeze
- assert_raise_with_message(FrozenError, /frozen class/, bug11532) {
+ assert_raise_with_message(FrozenError, /frozen Class/, bug11532) {
c.class_eval {deprecate_constant :A}
}
end
@@ -3077,7 +3073,7 @@ class TestModule < Test::Unit::TestCase
end
def test_prepend_gc
- assert_separately [], %{
+ assert_ruby_status [], %{
module Foo
end
class Object
@@ -3207,7 +3203,6 @@ class TestModule < Test::Unit::TestCase
end
def test_redefinition_mismatch
- omit "Investigating trunk-rjit failure on ci.rvm.jp" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
m = Module.new
m.module_eval "A = 1", __FILE__, line = __LINE__
e = assert_raise_with_message(TypeError, /is not a module/) {
@@ -3270,15 +3265,18 @@ class TestModule < Test::Unit::TestCase
end
module CloneTestM0
+ TEST = :M0
def foo; TEST; end
end
CloneTestM1 = CloneTestM0.clone
CloneTestM2 = CloneTestM0.clone
module CloneTestM1
+ remove_const :TEST
TEST = :M1
end
module CloneTestM2
+ remove_const :TEST
TEST = :M2
end
class CloneTestC1
@@ -3293,8 +3291,8 @@ class TestModule < Test::Unit::TestCase
assert_equal 1, m::C, '[ruby-core:47834]'
assert_equal 1, m.m, '[ruby-core:47834]'
- assert_equal :M1, CloneTestC1.new.foo, '[Bug #15877]'
- assert_equal :M2, CloneTestC2.new.foo, '[Bug #15877]'
+ assert_equal :M0, CloneTestC1.new.foo, 'originally [Bug #15877], but behaviour changed'
+ assert_equal :M0, CloneTestC2.new.foo, 'originally [Bug #15877], but behaviour changed'
end
def test_clone_freeze
@@ -3365,6 +3363,53 @@ class TestModule < Test::Unit::TestCase
CODE
end
+ def test_set_temporary_name
+ m = Module.new
+ assert_nil m.name
+
+ m.const_set(:N, Module.new)
+
+ assert_match(/\A#<Module:0x\h+>::N\z/, m::N.name)
+ assert_same m::N, m::N.set_temporary_name(name = "fake_name_under_M")
+ name.upcase!
+ assert_equal("fake_name_under_M", m::N.name)
+ assert_raise(FrozenError) {m::N.name.upcase!}
+ assert_same m::N, m::N.set_temporary_name(nil)
+ assert_nil(m::N.name)
+
+ m::N.const_set(:O, Module.new)
+ m.const_set(:Recursive, m)
+ m::N.const_set(:Recursive, m)
+ m.const_set(:A, 42)
+
+ assert_same m, m.set_temporary_name(name = "fake_name")
+ name.upcase!
+ assert_equal("fake_name", m.name)
+ assert_raise(FrozenError) {m.name.upcase!}
+ assert_equal("fake_name::N", m::N.name)
+ assert_equal("fake_name::N::O", m::N::O.name)
+
+ assert_same m, m.set_temporary_name(nil)
+ assert_nil m.name
+ assert_nil m::N.name
+ assert_nil m::N::O.name
+
+ assert_raise_with_message(ArgumentError, "empty class/module name") do
+ m.set_temporary_name("")
+ end
+ %w[A A::B ::A ::A::B].each do |name|
+ assert_raise_with_message(ArgumentError, /must not be a constant path/) do
+ m.set_temporary_name(name)
+ end
+ end
+
+ [Object, User, AClass].each do |mod|
+ assert_raise_with_message(RuntimeError, /permanent name/) do
+ mod.set_temporary_name("fake_name")
+ end
+ end
+ end
+
private
def assert_top_method_is_private(method)
diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb
index 6d413e6391..6abd20cc81 100644
--- a/test/ruby/test_nomethod_error.rb
+++ b/test/ruby/test_nomethod_error.rb
@@ -78,7 +78,7 @@ class TestNoMethodError < Test::Unit::TestCase
assert_equal :foo, error.name
assert_equal [1, 2], error.args
assert_equal receiver, error.receiver
- assert error.private_call?, "private_call? was false."
+ assert_predicate error, :private_call?
end
def test_message_encoding
@@ -106,4 +106,32 @@ class TestNoMethodError < Test::Unit::TestCase
assert_match(/undefined method.+this_method_does_not_exist.+for.+Module/, err.to_s)
end
+
+ def test_send_forward_raises
+ t = EnvUtil.labeled_class("Test") do
+ def foo(...)
+ forward(...)
+ end
+ end
+ obj = t.new
+ assert_raise(NoMethodError) do
+ obj.foo
+ end
+ end
+
+ # [Bug #21535]
+ def test_send_forward_raises_when_called_through_vcall
+ t = EnvUtil.labeled_class("Test") do
+ def foo(...)
+ forward(...)
+ end
+ def foo_indirect
+ foo # vcall
+ end
+ end
+ obj = t.new
+ assert_raise(NoMethodError) do
+ obj.foo_indirect
+ end
+ end
end
diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb
index ab492743f6..b272b89921 100644
--- a/test/ruby/test_numeric.rb
+++ b/test/ruby/test_numeric.rb
@@ -18,18 +18,24 @@ class TestNumeric < Test::Unit::TestCase
assert_raise_with_message(TypeError, /can't be coerced into /) {1|:foo}
assert_raise_with_message(TypeError, /can't be coerced into /) {1^:foo}
- assert_raise_with_message(TypeError, /:\u{3042}/) {1+:"\u{3042}"}
- assert_raise_with_message(TypeError, /:\u{3042}/) {1&:"\u{3042}"}
- assert_raise_with_message(TypeError, /:\u{3042}/) {1|:"\u{3042}"}
- assert_raise_with_message(TypeError, /:\u{3042}/) {1^:"\u{3042}"}
- assert_raise_with_message(TypeError, /:"\\u3042"/) {1+:"\u{3042}"}
- assert_raise_with_message(TypeError, /:"\\u3042"/) {1&:"\u{3042}"}
- assert_raise_with_message(TypeError, /:"\\u3042"/) {1|:"\u{3042}"}
- assert_raise_with_message(TypeError, /:"\\u3042"/) {1^:"\u{3042}"}
- assert_raise_with_message(TypeError, /:\u{3044}/) {1+"\u{3044}".to_sym}
- assert_raise_with_message(TypeError, /:\u{3044}/) {1&"\u{3044}".to_sym}
- assert_raise_with_message(TypeError, /:\u{3044}/) {1|"\u{3044}".to_sym}
- assert_raise_with_message(TypeError, /:\u{3044}/) {1^"\u{3044}".to_sym}
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(TypeError, /:\u{3042}/) {1+:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:\u{3042}/) {1&:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:\u{3042}/) {1|:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:\u{3042}/) {1^:"\u{3042}"}
+
+ assert_raise_with_message(TypeError, /:\u{3044}/) {1+"\u{3044}".to_sym}
+ assert_raise_with_message(TypeError, /:\u{3044}/) {1&"\u{3044}".to_sym}
+ assert_raise_with_message(TypeError, /:\u{3044}/) {1|"\u{3044}".to_sym}
+ assert_raise_with_message(TypeError, /:\u{3044}/) {1^"\u{3044}".to_sym}
+ end
+
+ EnvUtil.with_default_internal(Encoding::US_ASCII) do
+ assert_raise_with_message(TypeError, /:"\\u3042"/) {1+:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:"\\u3042"/) {1&:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:"\\u3042"/) {1|:"\u{3042}"}
+ assert_raise_with_message(TypeError, /:"\\u3042"/) {1^:"\u{3042}"}
+ end
bug10711 = '[ruby-core:67405] [Bug #10711]'
exp = "1.2 can't be coerced into Integer"
@@ -200,14 +206,6 @@ class TestNumeric < Test::Unit::TestCase
assert_nil(a <=> :foo)
end
- def test_float_round_ndigits
- bug14635 = "[ruby-core:86323]"
- f = 0.5
- 31.times do |i|
- assert_equal(0.5, f.round(i+1), bug14635 + " (argument: #{i+1})")
- end
- end
-
def test_floor_ceil_round_truncate
a = Class.new(Numeric) do
def to_f; 1.5; end
@@ -483,6 +481,10 @@ class TestNumeric < Test::Unit::TestCase
assert_equal(0, 0.pow(3, 1))
assert_equal(0, 2.pow(3, 1))
assert_equal(0, -2.pow(3, 1))
+
+ min, max = RbConfig::LIMITS.values_at("FIXNUM_MIN", "FIXNUM_MAX")
+ assert_equal(0, 0.pow(2, min))
+ assert_equal(0, Integer.sqrt(max+1).pow(2, min))
end
end
diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb
index 7d00422629..53ae4fb110 100644
--- a/test/ruby/test_object.rb
+++ b/test/ruby/test_object.rb
@@ -280,6 +280,12 @@ class TestObject < Test::Unit::TestCase
assert_equal([:foo], k.private_methods(false))
end
+ class ToStrCounter
+ def initialize(str = "@foo") @str = str; @count = 0; end
+ def to_str; @count += 1; @str; end
+ def count; @count; end
+ end
+
def test_instance_variable_get
o = Object.new
o.instance_eval { @foo = :foo }
@@ -291,9 +297,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_get("bar") }
assert_raise(TypeError) { o.instance_variable_get(1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
assert_equal(:foo, o.instance_variable_get(n))
assert_equal(1, n.count)
end
@@ -308,9 +312,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_set("bar", 1) }
assert_raise(TypeError) { o.instance_variable_set(1, 1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
o.instance_variable_set(n, :bar)
assert_equal(:bar, o.instance_eval { @foo })
assert_equal(1, n.count)
@@ -327,9 +329,7 @@ class TestObject < Test::Unit::TestCase
assert_raise(NameError) { o.instance_variable_defined?("bar") }
assert_raise(TypeError) { o.instance_variable_defined?(1) }
- n = Object.new
- def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end
- def n.count; @count; end
+ n = ToStrCounter.new
assert_equal(true, o.instance_variable_defined?(n))
assert_equal(1, n.count)
end
@@ -356,38 +356,43 @@ class TestObject < Test::Unit::TestCase
end
def test_remove_instance_variable_re_embed
- require "objspace"
-
- c = Class.new do
- def a = @a
-
- def b = @b
-
- def c = @c
- end
-
- o1 = c.new
- o2 = c.new
-
- o1.instance_variable_set(:@foo, 5)
- o1.instance_variable_set(:@a, 0)
- o1.instance_variable_set(:@b, 1)
- o1.instance_variable_set(:@c, 2)
- refute_includes ObjectSpace.dump(o1), '"embedded":true'
- o1.remove_instance_variable(:@foo)
- assert_includes ObjectSpace.dump(o1), '"embedded":true'
-
- o2.instance_variable_set(:@a, 0)
- o2.instance_variable_set(:@b, 1)
- o2.instance_variable_set(:@c, 2)
- assert_includes ObjectSpace.dump(o2), '"embedded":true'
-
- assert_equal(0, o1.a)
- assert_equal(1, o1.b)
- assert_equal(2, o1.c)
- assert_equal(0, o2.a)
- assert_equal(1, o2.b)
- assert_equal(2, o2.c)
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ # Determine the RVALUE pool's embed capacity from GC constants.
+ rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
+ rbasic_size = GC::INTERNAL_CONSTANTS[:RBASIC_SIZE]
+ embed_cap = (rvalue_size - rbasic_size) / RbConfig::SIZEOF["void*"]
+
+ # Build a class whose initialize sets embed_cap ivars so objects
+ # are allocated in the RVALUE pool with embedded storage.
+ init_body = embed_cap.times.map { |i| "@v#{i} = nil" }.join("; ")
+ c = Class.new { class_eval("def initialize; #{init_body}; end") }
+
+ o1 = c.new
+ o2 = c.new
+
+ # All embed_cap ivars fit - should be embedded
+ embed_cap.times { |i| o1.instance_variable_set(:"@v#{i}", i) }
+ assert_includes ObjectSpace.dump(o1), '"embedded":true'
+
+ # One more ivar overflows embed capacity
+ o1.instance_variable_set(:@overflow, 99)
+ refute_includes ObjectSpace.dump(o1), '"embedded":true'
+
+ # Remove the overflow ivar - should re-embed
+ o1.remove_instance_variable(:@overflow)
+ assert_includes ObjectSpace.dump(o1), '"embedded":true'
+
+ # An object that never overflowed is also embedded
+ embed_cap.times { |i| o2.instance_variable_set(:"@v#{i}", i) }
+ assert_includes ObjectSpace.dump(o2), '"embedded":true'
+
+ # Verify values survived re-embedding
+ embed_cap.times do |i|
+ assert_equal(i, o1.instance_variable_get(:"@v#{i}"))
+ assert_equal(i, o2.instance_variable_get(:"@v#{i}"))
+ end
+ end;
end
def test_convert_string
@@ -950,6 +955,82 @@ class TestObject < Test::Unit::TestCase
assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x.inspect)
x.instance_variable_set("@\u{3046}".encode(Encoding::EUC_JP), 6)
assert_match(/@\u{3046}=6\b/, x.inspect)
+
+ x = Object.new
+ x.singleton_class.class_eval do
+ private def instance_variables_to_inspect = [:@host, :@user]
+ end
+
+ x.instance_variable_set(:@host, "localhost")
+ x.instance_variable_set(:@user, "root")
+ x.instance_variable_set(:@password, "hunter2")
+ s = x.inspect
+ assert_include(s, "@host=\"localhost\"")
+ assert_include(s, "@user=\"root\"")
+ assert_not_include(s, "@password=")
+ end
+
+ def test_inspect_mutating_ivar
+ obj = Object.new
+ evil = Object.new
+ evil.define_singleton_method(:inspect) do
+ obj.instance_variables.each { |v| obj.remove_instance_variable(v) }
+ "evil"
+ end
+ obj.instance_variable_set(:@evil, evil)
+ 10.times { |i| obj.instance_variable_set(:"@v#{i}", 0) }
+ # Buffered iteration: inspect sees a snapshot of the original ivars
+ result = obj.inspect
+ assert_include result, "@evil=evil"
+ 10.times { |i| assert_include result, "@v#{i}=0" }
+ end
+
+ def test_inspect_mutating_ivar_complex
+ # Force complex by creating many shape variations on the same class
+ c = Class.new
+ 50.times do |i|
+ o = c.new
+ o.instance_variable_set(:"@unique_#{i}", 0)
+ end
+
+ obj = c.new
+ evil = Object.new
+ evil.define_singleton_method(:inspect) do
+ obj.instance_variables.each { |v| obj.remove_instance_variable(v) }
+ ""
+ end
+ obj.instance_variable_set(:@evil, evil)
+ 10.times { |i| obj.instance_variable_set(:"@v#{i}", 0) }
+ # complex objects use st_foreach which handles mutation gracefully
+ obj.inspect
+ end
+
+ def test_inspect_complex
+ kernel_inspect = Kernel.instance_method(:inspect)
+
+ klasses = [
+ Class.new,
+ Class.new(String),
+ Class.new(Array),
+ Class.new(Hash),
+ Struct.new(:x),
+ Class.new(Thread::Mutex),
+ # It's very difficult to get a complex T_CLASS, so that isn't tested here
+ ]
+
+ klasses.each_with_index do |klass, idx|
+ 8.times do |i|
+ klass.new.instance_variable_set(:"@sib_#{rand(999999)}", 1)
+ end
+
+ obj = klass.new
+ obj.instance_variable_set(:@a, 1)
+ obj.instance_variable_set(:@b, 2)
+
+ s = kernel_inspect.bind_call(obj)
+ assert_include(s, "@a=1")
+ assert_include(s, "@b=2")
+ end
end
def test_singleton_methods
@@ -1009,6 +1090,47 @@ class TestObject < Test::Unit::TestCase
assert_predicate(ys, :frozen?, '[Bug #19169]')
end
+ def test_singleton_class_of_singleton_class_freeze
+ x = Object.new
+ xs = x.singleton_class
+ xxs = xs.singleton_class
+ xxxs = xxs.singleton_class
+ x.freeze
+ assert_predicate(xs, :frozen?, '[Bug #20319]')
+ assert_predicate(xxs, :frozen?, '[Bug #20319]')
+ assert_predicate(xxxs, :frozen?, '[Bug #20319]')
+
+ y = Object.new
+ ys = y.singleton_class
+ ys.prepend(Module.new)
+ yys = ys.singleton_class
+ yys.prepend(Module.new)
+ yyys = yys.singleton_class
+ yyys.prepend(Module.new)
+ y.freeze
+ assert_predicate(ys, :frozen?, '[Bug #20319]')
+ assert_predicate(yys, :frozen?, '[Bug #20319]')
+ assert_predicate(yyys, :frozen?, '[Bug #20319]')
+
+ c = Class.new
+ cs = c.singleton_class
+ ccs = cs.singleton_class
+ cccs = ccs.singleton_class
+ d = Class.new(c)
+ ds = d.singleton_class
+ dds = ds.singleton_class
+ ddds = dds.singleton_class
+ d.freeze
+ assert_predicate(d, :frozen?, '[Bug #20319]')
+ assert_predicate(ds, :frozen?, '[Bug #20319]')
+ assert_predicate(dds, :frozen?, '[Bug #20319]')
+ assert_predicate(ddds, :frozen?, '[Bug #20319]')
+ assert_not_predicate(c, :frozen?, '[Bug #20319]')
+ assert_not_predicate(cs, :frozen?, '[Bug #20319]')
+ assert_not_predicate(ccs, :frozen?, '[Bug #20319]')
+ assert_not_predicate(cccs, :frozen?, '[Bug #20319]')
+ end
+
def test_redef_method_missing
bug5473 = '[ruby-core:40287]'
['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code|
diff --git a/test/ruby/test_object_id.rb b/test/ruby/test_object_id.rb
new file mode 100644
index 0000000000..034674e5be
--- /dev/null
+++ b/test/ruby/test_object_id.rb
@@ -0,0 +1,303 @@
+require 'test/unit'
+require "securerandom"
+
+class TestObjectId < Test::Unit::TestCase
+ def setup
+ @obj = Object.new
+ end
+
+ def test_dup_new_id
+ id = @obj.object_id
+ refute_equal id, @obj.dup.object_id
+ end
+
+ def test_dup_with_ivar_and_id
+ id = @obj.object_id
+ @obj.instance_variable_set(:@foo, 42)
+
+ copy = @obj.dup
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_dup_with_id_and_ivar
+ @obj.instance_variable_set(:@foo, 42)
+ id = @obj.object_id
+
+ copy = @obj.dup
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_dup_with_id_and_ivar_and_frozen
+ @obj.instance_variable_set(:@foo, 42)
+ @obj.freeze
+ id = @obj.object_id
+
+ copy = @obj.dup
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ refute_predicate copy, :frozen?
+ end
+
+ def test_clone_new_id
+ id = @obj.object_id
+ refute_equal id, @obj.clone.object_id
+ end
+
+ def test_clone_with_ivar_and_id
+ id = @obj.object_id
+ @obj.instance_variable_set(:@foo, 42)
+
+ copy = @obj.clone
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_clone_with_id_and_ivar
+ @obj.instance_variable_set(:@foo, 42)
+ id = @obj.object_id
+
+ copy = @obj.clone
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_clone_with_id_and_ivar_and_frozen
+ @obj.instance_variable_set(:@foo, 42)
+ @obj.freeze
+ id = @obj.object_id
+
+ copy = @obj.clone
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ assert_predicate copy, :frozen?
+ end
+
+ def test_marshal_new_id
+ return pass if @obj.is_a?(Module)
+
+ id = @obj.object_id
+ refute_equal id, Marshal.load(Marshal.dump(@obj)).object_id
+ end
+
+ def test_marshal_with_ivar_and_id
+ return pass if @obj.is_a?(Module)
+
+ id = @obj.object_id
+ @obj.instance_variable_set(:@foo, 42)
+
+ copy = Marshal.load(Marshal.dump(@obj))
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_marshal_with_id_and_ivar
+ return pass if @obj.is_a?(Module)
+
+ @obj.instance_variable_set(:@foo, 42)
+ id = @obj.object_id
+
+ copy = Marshal.load(Marshal.dump(@obj))
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ end
+
+ def test_marshal_with_id_and_ivar_and_frozen
+ return pass if @obj.is_a?(Module)
+
+ @obj.instance_variable_set(:@foo, 42)
+ @obj.freeze
+ id = @obj.object_id
+
+ copy = Marshal.load(Marshal.dump(@obj))
+ refute_equal id, copy.object_id
+ assert_equal 42, copy.instance_variable_get(:@foo)
+ refute_predicate copy, :frozen?
+ end
+
+ def test_object_id_need_resize
+ (3 - @obj.instance_variables.size).times do |i|
+ @obj.instance_variable_set("@a_#{i}", "[Bug #21445]")
+ end
+ @obj.object_id
+ GC.start
+ end
+end
+
+class TestObjectIdClass < TestObjectId
+ def setup
+ @obj = Class.new
+ end
+end
+
+class TestObjectIdGeneric < TestObjectId
+ def setup
+ @obj = Array.new
+ end
+end
+
+class TestObjectIdTooComplex < TestObjectId
+ class TooComplex
+ def initialize
+ @complex_obj_id_test = 1
+ end
+ end
+
+ def setup
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ TooComplex.new.instance_variable_set("@TestObjectIdTooComplex#{i}", 1)
+ end
+ @obj = TooComplex.new
+ @obj.instance_variable_set("@a#{rand(10_000)}", 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(@obj), :complex?)
+ end
+ end
+end
+
+class TestObjectIdTooComplexClass < TestObjectId
+ class TooComplex < Module
+ end
+
+ def setup
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+
+ @obj = TooComplex.new
+
+ @obj.instance_variable_set("@___#{SecureRandom.hex}", 1)
+
+ 8.times do |i|
+ @obj.instance_variable_set("@TestObjectIdTooComplexClass#{i}", 1)
+ @obj.remove_instance_variable("@TestObjectIdTooComplexClass#{i}")
+ end
+
+ @obj.instance_variable_set("@test", 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(@obj), :complex?)
+ end
+ end
+end
+
+class TestObjectIdTooComplexGeneric < TestObjectId
+ class TooComplex < Array
+ end
+
+ def setup
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ TooComplex.new.instance_variable_set("@TestObjectIdTooComplexGeneric#{i}", 1)
+ end
+ @obj = TooComplex.new
+ @obj.instance_variable_set("@a#{rand(10_000)}", 1)
+ @obj.instance_variable_set("@a#{rand(10_000)}", 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(@obj), :complex?)
+ end
+ end
+end
+
+class TestObjectIdRactor < Test::Unit::TestCase
+ def test_object_id_race_free
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:experimental] = false
+ class MyClass
+ attr_reader :a, :b, :c
+ def initialize
+ @a = @b = @c = nil
+ end
+ end
+ N = 10_000
+ objs = Ractor.make_shareable(N.times.map { MyClass.new })
+ results = 4.times.map{
+ Ractor.new(objs) { |objs|
+ vars = []
+ ids = []
+ objs.each do |obj|
+ vars << obj.a << obj.b << obj.c
+ ids << obj.object_id
+ end
+ [vars, ids]
+ }
+ }.map(&:value)
+ assert_equal 1, results.uniq.size
+ end;
+ end
+
+ def test_external_object_id_ractor_move
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:experimental] = false
+ class MyClass
+ attr_reader :a, :b, :c
+ def initialize
+ @a = @b = @c = nil
+ end
+ end
+ obj = Ractor.make_shareable(MyClass.new)
+ object_id = obj.object_id
+ obj = Ractor.new { Ractor.receive }.send(obj, move: true).value
+ assert_equal object_id, obj.object_id
+ end;
+ end
+end
+
+class TestObjectIdStruct < TestObjectId
+ EmbeddedStruct = Struct.new(:embedded_field)
+
+ def setup
+ @obj = EmbeddedStruct.new
+ end
+end
+
+class TestObjectIdStructGenIvar < TestObjectId
+ GenIvarStruct = Struct.new(:a, :b, :c)
+
+ def setup
+ @obj = GenIvarStruct.new
+ end
+end
+
+class TestObjectIdStructNotEmbed < TestObjectId
+ MANY_IVS = 80
+
+ StructNotEmbed = Struct.new(*MANY_IVS.times.map { |i| :"field_#{i}" })
+
+ def setup
+ @obj = StructNotEmbed.new
+ end
+end
+
+class TestObjectIdStructTooComplex < TestObjectId
+ StructTooComplex = Struct.new(:a) do
+ def initialize
+ @complex_obj_id_test = 1
+ end
+ end
+
+ def setup
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ StructTooComplex.new.instance_variable_set("@TestObjectIdStructTooComplex#{i}", 1)
+ end
+ @obj = StructTooComplex.new
+ @obj.instance_variable_set("@a#{rand(10_000)}", 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(@obj), :complex?)
+ end
+ end
+end
diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb
index 5c79983b7e..a479547599 100644
--- a/test/ruby/test_objectspace.rb
+++ b/test/ruby/test_objectspace.rb
@@ -8,7 +8,7 @@ class TestObjectSpace < Test::Unit::TestCase
line = $1.to_i
code = <<"End"
define_method("test_id2ref_#{line}") {\
- o = ObjectSpace._id2ref(obj.object_id);\
+ o = EnvUtil.suppress_warning { ObjectSpace._id2ref(obj.object_id) }
assert_same(obj, o, "didn't round trip: \#{obj.inspect}");\
}
End
@@ -57,20 +57,20 @@ End
def test_id2ref_invalid_argument
msg = /no implicit conversion/
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(nil)}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(false)}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(true)}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(:a)}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref("0")}
- assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(Object.new)}
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(nil) } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(false) } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(true) } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(:a) } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref("0") } }
+ assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(Object.new) } }
end
def test_id2ref_invalid_symbol_id
# RB_STATIC_SYM_P checks for static symbols by checking that the bottom
# 8 bits of the object is equal to RUBY_SYMBOL_FLAG, so we need to make
# sure that the bottom 8 bits remain unchanged.
- msg = /is not symbol id value/
- assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + 256) }
+ msg = /is not a symbol id value/
+ assert_raise_with_message(RangeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(:a.object_id + 256) } }
end
def test_count_objects
@@ -94,7 +94,7 @@ End
end
def test_finalizer
- assert_in_out_err(["-e", <<-END], "", %w(:ok :ok :ok :ok), [])
+ assert_in_out_err(["-e", <<-END], "", %w(:ok :ok :ok), [])
a = []
ObjectSpace.define_finalizer(a) { p :ok }
b = a.dup
@@ -137,6 +137,25 @@ End
}
end
+ def test_finalizer_copy
+ assert_in_out_err(["-e", <<~'RUBY'], "", %w(:ok), [])
+ def fin
+ ids = Set.new
+ ->(id) { puts "object_id (#{id}) reused" unless ids.add?(id) }
+ end
+
+ OBJ = Object.new
+ ObjectSpace.define_finalizer(OBJ, fin)
+ OBJ.freeze
+
+ 10.times do
+ OBJ.clone
+ end
+
+ p :ok
+ RUBY
+ end
+
def test_finalizer_with_super
assert_in_out_err(["-e", <<-END], "", %w(:ok), [])
class A
@@ -265,6 +284,21 @@ End
end;
end
+ def test_id2ref_table_build
+ assert_separately([], <<-End)
+ 10.times do
+ Object.new.object_id
+ end
+
+ GC.start(immediate_mark: false)
+
+ obj = Object.new
+ EnvUtil.suppress_warning do
+ assert_equal obj, ObjectSpace._id2ref(obj.object_id)
+ end
+ End
+ end
+
def test_each_object_singleton_class
assert_separately([], <<-End)
class C
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index 5aaf9647a8..1554b43f18 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -591,7 +591,6 @@ class TestRubyOptimization < Test::Unit::TestCase
end
def test_tailcall_not_to_grow_stack
- omit 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
bug16161 = '[ruby-core:94881]'
tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
@@ -607,11 +606,11 @@ class TestRubyOptimization < Test::Unit::TestCase
end
class Bug10557
- def [](_)
+ def [](_, &)
block_given?
end
- def []=(_, _)
+ def []=(_, _, &)
block_given?
end
end
@@ -729,7 +728,7 @@ class TestRubyOptimization < Test::Unit::TestCase
insn = iseq.disasm
assert_match %r{putobject\s+#{Regexp.quote('"1.8.0"..."1.8.8"')}}, insn
assert_match %r{putobject\s+#{Regexp.quote('"2.0.0".."2.3.2"')}}, insn
- assert_no_match(/putstring/, insn)
+ assert_no_match(/dupstring/, insn)
assert_no_match(/newrange/, insn)
end
end
@@ -947,14 +946,14 @@ class TestRubyOptimization < Test::Unit::TestCase
end
def test_peephole_optimization_without_trace
- assert_separately [], <<-END
+ assert_ruby_status [], <<-END
RubyVM::InstructionSequence.compile_option = {trace_instruction: false}
eval "def foo; 1.times{|(a), &b| nil && a}; end"
END
end
def test_clear_unreachable_keyword_args
- assert_separately [], <<-END, timeout: 60
+ assert_ruby_status [], <<-END, timeout: 60
script = <<-EOS
if true
else
@@ -1081,7 +1080,7 @@ class TestRubyOptimization < Test::Unit::TestCase
class Objtostring
end
- def test_objtostring
+ def test_objtostring_immediate
assert_raise(NoMethodError){"#{BasicObject.new}"}
assert_redefine_method('Symbol', 'to_s', <<-'end')
assert_match %r{\A#<Symbol:0x[0-9a-f]+>\z}, "#{:foo}"
@@ -1095,11 +1094,17 @@ class TestRubyOptimization < Test::Unit::TestCase
assert_redefine_method('FalseClass', 'to_s', <<-'end')
assert_match %r{\A#<FalseClass:0x[0-9a-f]+>\z}, "#{false}"
end
+ end
+
+ def test_objtostring_fixnum
assert_redefine_method('Integer', 'to_s', <<-'end')
(-1..10).each { |i|
assert_match %r{\A#<Integer:0x[0-9a-f]+>\z}, "#{i}"
}
end
+ end
+
+ def test_objtostring
assert_equal "TestRubyOptimization::Objtostring", "#{Objtostring}"
assert_match %r{\A#<Class:0x[0-9a-f]+>\z}, "#{Class.new}"
assert_match %r{\A#<Module:0x[0-9a-f]+>\z}, "#{Module.new}"
@@ -1216,4 +1221,58 @@ class TestRubyOptimization < Test::Unit::TestCase
end
RUBY
end
+
+ def test_opt_new_with_safe_navigation
+ payload = nil
+ assert_nil payload&.new
+ end
+
+ def test_opt_new
+ pos_initialize = "
+ def initialize a, b
+ @a = a
+ @b = b
+ end
+ "
+ kw_initialize = "
+ def initialize a:, b:
+ @a = a
+ @b = b
+ end
+ "
+ kw_hash_initialize = "
+ def initialize a, **kw
+ @a = a
+ @b = kw[:b]
+ end
+ "
+ pos_prelude = "class OptNewFoo; #{pos_initialize}; end;"
+ kw_prelude = "class OptNewFoo; #{kw_initialize}; end;"
+ kw_hash_prelude = "class OptNewFoo; #{kw_hash_initialize}; end;"
+ [
+ "#{pos_prelude} OptNewFoo.new 1, 2",
+ "#{pos_prelude} a = 1; b = 2; OptNewFoo.new a, b",
+ "#{pos_prelude} def optnew_foo(a, b) = OptNewFoo.new(a, b); optnew_foo 1, 2",
+ "#{pos_prelude} def optnew_foo(*a) = OptNewFoo.new(*a); optnew_foo 1, 2",
+ "#{pos_prelude} def optnew_foo(...) = OptNewFoo.new(...); optnew_foo 1, 2",
+ "#{kw_prelude} def optnew_foo(**a) = OptNewFoo.new(**a); optnew_foo a: 1, b: 2",
+ "#{kw_hash_prelude} def optnew_foo(*a, **b) = OptNewFoo.new(*a, **b); optnew_foo 1, b: 2",
+ ].each do |code|
+ iseq = RubyVM::InstructionSequence.compile(code)
+ insn = iseq.disasm
+ assert_match(/opt_new/, insn)
+ assert_match(/OptNewFoo:.+@a=1, @b=2/, iseq.eval.inspect)
+ # clean up to avoid warnings
+ Object.send :remove_const, :OptNewFoo
+ Object.remove_method :optnew_foo if defined?(optnew_foo)
+ end
+ [
+ 'def optnew_foo(&) = OptNewFoo.new(&)',
+ 'def optnew_foo(a, ...) = OptNewFoo.new(a, ...)',
+ ].each do |code|
+ iseq = RubyVM::InstructionSequence.compile(code)
+ insn = iseq.disasm
+ assert_no_match(/opt_new/, insn)
+ end
+ end
end
diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb
index ca089f09c3..6e5f0fe7ff 100644
--- a/test/ruby/test_pack.rb
+++ b/test/ruby/test_pack.rb
@@ -283,6 +283,15 @@ class TestPack < Test::Unit::TestCase
assert_equal(["foo "], "foo ".unpack("a4"))
assert_equal(["foo"], "foo".unpack("A4"))
assert_equal(["foo"], "foo".unpack("a4"))
+
+ assert_equal(["foo", 4], "foo\0 ".unpack("A4^"))
+ assert_equal(["foo\0", 4], "foo\0 ".unpack("a4^"))
+ assert_equal(["foo", 4], "foo ".unpack("A4^"))
+ assert_equal(["foo ", 4], "foo ".unpack("a4^"))
+ assert_equal(["foo", 3], "foo".unpack("A4^"))
+ assert_equal(["foo", 3], "foo".unpack("a4^"))
+ assert_equal(["foo", 6], "foo\0 ".unpack("A*^"))
+ assert_equal(["foo", 6], "foo ".unpack("A*^"))
end
def test_pack_unpack_Z
@@ -298,6 +307,11 @@ class TestPack < Test::Unit::TestCase
assert_equal(["foo"], "foo".unpack("Z*"))
assert_equal(["foo"], "foo\0".unpack("Z*"))
assert_equal(["foo"], "foo".unpack("Z5"))
+
+ assert_equal(["foo", 3], "foo".unpack("Z*^"))
+ assert_equal(["foo", 4], "foo\0".unpack("Z*^"))
+ assert_equal(["foo", 3], "foo".unpack("Z5^"))
+ assert_equal(["foo", 5], "foo\0\0\0".unpack("Z5^"))
end
def test_pack_unpack_bB
@@ -549,6 +563,8 @@ class TestPack < Test::Unit::TestCase
assert_equal([0, 2], "\x00\x00\x02".unpack("CxC"))
assert_raise(ArgumentError) { "".unpack("x") }
+
+ assert_equal([0, 1, 2, 2, 3], "\x00\x00\x02".unpack("C^x^C^"))
end
def test_pack_unpack_X
@@ -558,6 +574,7 @@ class TestPack < Test::Unit::TestCase
assert_equal([0, 2, 2], "\x00\x02".unpack("CCXC"))
assert_raise(ArgumentError) { "".unpack("X") }
+ assert_equal([0, 1, 2, 2, 1, 2, 2], "\x00\x02".unpack("C^C^X^C^"))
end
def test_pack_unpack_atmark
@@ -571,6 +588,17 @@ class TestPack < Test::Unit::TestCase
pos = RbConfig::LIMITS["UINTPTR_MAX"] - 99 # -100
assert_raise(RangeError) {"0123456789".unpack("@#{pos}C10")}
+
+ assert_equal([1, 3, 4], "\x01\x00\x00\x02".unpack("x^@3^x^"))
+ end
+
+ def test_unpack_carret
+ assert_equal([0], "abc".unpack("^"))
+ assert_equal([2], "abc".unpack("^", offset: 2))
+ assert_equal([97, nil, 1], "a".unpack("CC^"))
+
+ assert_raise(ArgumentError) { "".unpack("^!") }
+ assert_raise(ArgumentError) { "".unpack("^_") }
end
def test_pack_unpack_percent
@@ -853,6 +881,19 @@ EXPECTED
assert_equal "\xDE\xAD\xBE\xEF\xBA\xBE\xF0\x0D\0\0\xBA\xAD\xFA\xCE", buf
assert_equal addr, [buf].pack('p')
+
+ assert_packing_buffer_fail("b*")
+ assert_packing_buffer_fail("B*")
+ assert_packing_buffer_fail("h*")
+ assert_packing_buffer_fail("H*")
+ assert_packing_buffer_fail("u", 16384)
+ assert_packing_buffer_fail("m", 16384)
+ assert_packing_buffer_fail("M", 16384)
+ end
+
+ def assert_packing_buffer_fail(fmt, size = 8192)
+ s = "\x01".b * size
+ assert_raise(ArgumentError) {[s].pack(fmt, buffer: s)}
end
def test_unpack_with_block
@@ -872,27 +913,29 @@ EXPECTED
def test_unpack1_offset
assert_equal 65, "ZA".unpack1("C", offset: 1)
+ assert_equal 65, "ZA".unpack1("C", offset: -1)
assert_equal "01000001", "YZA".unpack1("B*", offset: 2)
assert_nil "abc".unpack1("C", offset: 3)
- assert_raise_with_message(ArgumentError, /offset can't be negative/) {
- "a".unpack1("C", offset: -1)
- }
assert_raise_with_message(ArgumentError, /offset outside of string/) {
"a".unpack1("C", offset: 2)
}
+ assert_raise_with_message(ArgumentError, /offset outside of string/) {
+ "a".unpack1("C", offset: -2)
+ }
assert_nil "a".unpack1("C", offset: 1)
end
def test_unpack_offset
assert_equal [65], "ZA".unpack("C", offset: 1)
+ assert_equal [65], "ZA".unpack("C", offset: -1)
assert_equal ["01000001"], "YZA".unpack("B*", offset: 2)
assert_equal [nil, nil, nil], "abc".unpack("CCC", offset: 3)
- assert_raise_with_message(ArgumentError, /offset can't be negative/) {
- "a".unpack("C", offset: -1)
- }
assert_raise_with_message(ArgumentError, /offset outside of string/) {
"a".unpack("C", offset: 2)
}
+ assert_raise_with_message(ArgumentError, /offset outside of string/) {
+ "a".unpack("C", offset: -2)
+ }
assert_equal [nil], "a".unpack("C", offset: 1)
end
@@ -936,4 +979,116 @@ EXPECTED
assert_equal "oh no", v
end;
end
+
+ def test_unpack_broken_R
+ assert_equal([nil], "\xFF".unpack("R"))
+ assert_nil("\xFF".unpack1("R"))
+ assert_equal([nil], "\xFF".unpack("r"))
+ assert_nil("\xFF".unpack1("r"))
+
+ bytes = [256].pack("r")
+ assert_equal([256, nil, nil, nil], (bytes + "\xFF").unpack("rrrr"))
+
+ bytes = [256].pack("R")
+ assert_equal([256, nil, nil, nil], (bytes + "\xFF").unpack("RRRR"))
+
+ assert_equal([], "\xFF".unpack("R*"))
+ assert_equal([], "\xFF".unpack("r*"))
+ end
+
+ def test_pack_unpack_R
+ # ULEB128 encoding (unsigned)
+ assert_equal("\x00", [0].pack("R"))
+ assert_equal("\x01", [1].pack("R"))
+ assert_equal("\x7f", [127].pack("R"))
+ assert_equal("\x80\x01", [128].pack("R"))
+ assert_equal("\xff\x7f", [0x3fff].pack("R"))
+ assert_equal("\x80\x80\x01", [0x4000].pack("R"))
+ assert_equal("\xff\xff\xff\xff\x0f", [0xffffffff].pack("R"))
+ assert_equal("\x80\x80\x80\x80\x10", [0x100000000].pack("R"))
+ assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01", [0xffff_ffff_ffff_ffff].pack("R"))
+ assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("R"))
+
+ # Multiple values
+ assert_equal("\x01\x02", [1, 2].pack("R*"))
+ assert_equal("\x7f\x80\x01", [127, 128].pack("R*"))
+
+ # Negative numbers should raise an error
+ assert_raise(ArgumentError) { [-1].pack("R") }
+ assert_raise(ArgumentError) { [-100].pack("R") }
+
+ # Unpack tests
+ assert_equal([0], "\x00".unpack("R"))
+ assert_equal([1], "\x01".unpack("R"))
+ assert_equal([127], "\x7f".unpack("R"))
+ assert_equal([128], "\x80\x01".unpack("R"))
+ assert_equal([0x3fff], "\xff\x7f".unpack("R"))
+ assert_equal([0x4000], "\x80\x80\x01".unpack("R"))
+ assert_equal([0xffffffff], "\xff\xff\xff\xff\x0f".unpack("R"))
+ assert_equal([0x100000000], "\x80\x80\x80\x80\x10".unpack("R"))
+ assert_equal([0xffff_ffff_ffff_ffff], "\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01".unpack("R"))
+ assert_equal([0xffff_ffff_ffff_ffff_ffff_ffff].pack("R"), "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f")
+
+ # Multiple values
+ assert_equal([1, 2], "\x01\x02".unpack("R*"))
+ assert_equal([127, 128], "\x7f\x80\x01".unpack("R*"))
+
+ # Round-trip test
+ values = [0, 1, 127, 128, 0x3fff, 0x4000, 0xffffffff, 0x100000000]
+ assert_equal(values, values.pack("R*").unpack("R*"))
+ end
+
+ def test_pack_unpack_r
+ # SLEB128 encoding (signed)
+ assert_equal("\x00", [0].pack("r"))
+ assert_equal("\x01", [1].pack("r"))
+ assert_equal("\x7f", [-1].pack("r"))
+ assert_equal("\x7e", [-2].pack("r"))
+ assert_equal("\xff\x00", [127].pack("r"))
+ assert_equal("\x80\x01", [128].pack("r"))
+ assert_equal("\x81\x7f", [-127].pack("r"))
+ assert_equal("\x80\x7f", [-128].pack("r"))
+
+ # Larger positive numbers
+ assert_equal("\xff\xff\x00", [0x3fff].pack("r"))
+ assert_equal("\x80\x80\x01", [0x4000].pack("r"))
+
+ # Larger negative numbers
+ assert_equal("\x81\x80\x7f", [-0x3fff].pack("r"))
+ assert_equal("\x80\x80\x7f", [-0x4000].pack("r"))
+
+ # Very large numbers
+ assert_equal("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x1F", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("r"))
+ assert_equal("\x81\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80`", [-0xffff_ffff_ffff_ffff_ffff_ffff].pack("r"))
+
+ # Multiple values
+ assert_equal("\x00\x01\x7f", [0, 1, -1].pack("r*"))
+
+ # Unpack tests
+ assert_equal([0], "\x00".unpack("r"))
+ assert_equal([1], "\x01".unpack("r"))
+ assert_equal([-1], "\x7f".unpack("r"))
+ assert_equal([-2], "\x7e".unpack("r"))
+ assert_equal([127], "\xff\x00".unpack("r"))
+ assert_equal([128], "\x80\x01".unpack("r"))
+ assert_equal([-127], "\x81\x7f".unpack("r"))
+ assert_equal([-128], "\x80\x7f".unpack("r"))
+
+ # Larger numbers
+ assert_equal([0x3fff], "\xff\xff\x00".unpack("r"))
+ assert_equal([0x4000], "\x80\x80\x01".unpack("r"))
+ assert_equal([-0x3fff], "\x81\x80\x7f".unpack("r"))
+ assert_equal([-0x4000], "\x80\x80\x7f".unpack("r"))
+
+ # Very large numbers
+ assert_equal("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x1f", [0xffff_ffff_ffff_ffff_ffff_ffff].pack("r"))
+ assert_equal("\x81\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80`", [-0xffff_ffff_ffff_ffff_ffff_ffff].pack("r"))
+
+ # Multiple values
+ assert_equal([0, 1, -1], "\x00\x01\x7f".unpack("r*"))
+
+ # Round-trip test
+ values = [0, 1, -1, 127, -127, 128, -128, 0x3fff, -0x3fff, 0x4000, -0x4000]
+ assert_equal(values, values.pack("r*").unpack("r*"))
+ end
end
diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb
index eaf9412ded..def41d6017 100644
--- a/test/ruby/test_parse.rb
+++ b/test/ruby/test_parse.rb
@@ -186,6 +186,15 @@ class TestParse < Test::Unit::TestCase
end;
end
+ c = Class.new
+ c.freeze
+ assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do
+ begin;
+ c::FOO &= p 1
+ ::FOO &= p 1
+ end;
+ end
+
assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do
begin;
$1 &= 1
@@ -343,6 +352,21 @@ class TestParse < Test::Unit::TestCase
assert_equal("foobar", b)
end
+ def test_call_command
+ a = b = nil
+ o = Object.new
+ def o.m(*arg); proc {|a| arg.join + a }; end
+
+ assert_nothing_raised do
+ o.instance_eval <<-END, __FILE__, __LINE__+1
+ a = o.m "foo", "bar" do end.("buz")
+ b = o.m "foo", "bar" do end::("buz")
+ END
+ end
+ assert_equal("foobarbuz", a)
+ assert_equal("foobarbuz", b)
+ end
+
def test_xstring
assert_raise(Errno::ENOENT) do
eval("``")
@@ -466,6 +490,12 @@ class TestParse < Test::Unit::TestCase
assert_parse_error(%q[def (:"#{42}").foo; end], msg)
assert_parse_error(%q[def ([]).foo; end], msg)
assert_parse_error(%q[def ([1]).foo; end], msg)
+ assert_parse_error(%q[def (__FILE__).foo; end], msg)
+ assert_parse_error(%q[def (__LINE__).foo; end], msg)
+ assert_parse_error(%q[def (__ENCODING__).foo; end], msg)
+ assert_parse_error(%q[def __FILE__.foo; end], msg)
+ assert_parse_error(%q[def __LINE__.foo; end], msg)
+ assert_parse_error(%q[def __ENCODING__.foo; end], msg)
end
def test_flip_flop
@@ -648,6 +678,8 @@ class TestParse < Test::Unit::TestCase
assert_equal("\u{1234}", eval('?\u{1234}'))
assert_equal("\u{1234}", eval('?\u1234'))
assert_syntax_error('?\u{41 42}', 'Multiple codepoints at single character literal')
+ assert_syntax_error("?and", /unexpected '\?'/)
+ assert_syntax_error("?\u1234and", /unexpected '\?'/)
e = assert_syntax_error('"#{?\u123}"', 'invalid Unicode escape')
assert_not_match(/end-of-input/, e.message)
@@ -1527,7 +1559,7 @@ x = __ENCODING__
end
def test_shareable_constant_value_simple
- obj = [['unsharable_value']]
+ obj = [['unshareable_value']]
a, b, c = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}")
begin;
# shareable_constant_value: experimental_everything
@@ -1556,7 +1588,7 @@ x = __ENCODING__
assert_ractor_shareable(a)
assert_not_ractor_shareable(obj)
assert_equal obj, a
- assert !obj.equal?(a)
+ assert_not_same obj, a
bug_20339 = '[ruby-core:117186] [Bug #20339]'
bug_20341 = '[ruby-core:117197] [Bug #20341]'
diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb
index 92a3244fc2..96aa2a7fd6 100644
--- a/test/ruby/test_pattern_matching.rb
+++ b/test/ruby/test_pattern_matching.rb
@@ -197,11 +197,49 @@ class TestPatternMatching < Test::Unit::TestCase
end
end
- assert_syntax_error(%q{
+ assert_valid_syntax(%{
+ case 0
+ in [ :a | :b, x]
+ true
+ end
+ })
+
+ assert_in_out_err(['-c'], %q{
case 0
in a | 0
end
- }, /illegal variable in alternative pattern/)
+ }, [], /alternative pattern/,
+ success: false)
+
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in 0 | a
+ end
+ }, [], /alternative pattern/,
+ success: false)
+ end
+
+ def test_alternative_pattern_nested
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in [a] | 1
+ end
+ }, [], /alternative pattern/,
+ success: false)
+
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in { a: b } | 1
+ end
+ }, [], /alternative pattern/,
+ success: false)
+
+ assert_in_out_err(['-c'], %q{
+ case 0
+ in [{ a: [{ b: [{ c: }] }] }] | 1
+ end
+ }, [], /alternative pattern/,
+ success: false)
end
def test_var_pattern
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index acacea9362..f74342322f 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -513,7 +513,7 @@ class TestProc < Test::Unit::TestCase
file, lineno = method(:source_location_test).to_proc.binding.source_location
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(@@line_of_source_location_test[0], lineno, 'Bug #2427')
+ assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427')
end
def test_binding_error_unless_ruby_frame
@@ -1499,19 +1499,15 @@ class TestProc < Test::Unit::TestCase
assert_include(EnvUtil.labeled_class(name, Proc).new {}.to_s, name)
end
- @@line_of_source_location_test = [__LINE__ + 1, 2, __LINE__ + 3, 5]
+ @@line_of_source_location_test = __LINE__ + 1
def source_location_test a=1,
b=2
end
def test_source_location
- file, *loc = method(:source_location_test).source_location
+ file, lineno = method(:source_location_test).source_location
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(@@line_of_source_location_test, loc, 'Bug #2427')
-
- file, *loc = self.class.instance_method(:source_location_test).source_location
- assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(@@line_of_source_location_test, loc, 'Bug #2427')
+ assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427')
end
@@line_of_attr_reader_source_location_test = __LINE__ + 3
@@ -1544,13 +1540,13 @@ class TestProc < Test::Unit::TestCase
end
def test_block_source_location
- exp_loc = [__LINE__ + 3, 49, __LINE__ + 4, 49]
- file, *loc = block_source_location_test(1,
+ exp_lineno = __LINE__ + 3
+ file, lineno = block_source_location_test(1,
2,
3) do
end
assert_match(/^#{ Regexp.quote(__FILE__) }$/, file)
- assert_equal(exp_loc, loc)
+ assert_equal(exp_lineno, lineno)
end
def test_splat_without_respond_to
@@ -1637,6 +1633,10 @@ class TestProc < Test::Unit::TestCase
assert_equal(3, b.local_variable_get(:when))
assert_equal(4, b.local_variable_get(:begin))
assert_equal(5, b.local_variable_get(:end))
+
+ assert_raise_with_message(NameError, /local variable \Wdefault\W/) {
+ binding.local_variable_get(:default)
+ }
end
def test_local_variable_set
@@ -1649,6 +1649,274 @@ class TestProc < Test::Unit::TestCase
assert_equal(20, b.eval("b"))
end
+ def test_numparam_is_not_local_variables
+ "foo".tap do
+ _9 and flunk
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:_9) }
+ assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
+ "bar".tap do
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:_9) }
+ assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
+ end
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:_9) }
+ assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
+ end
+
+ "foo".tap do
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:_9) }
+ assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
+ "bar".tap do
+ _9 and flunk
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:_9) }
+ assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
+ end
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:_9) }
+ assert_raise(NameError) { binding.local_variable_set(:_9, 1) }
+ assert_raise(NameError) { binding.local_variable_defined?(:_9) }
+ end
+ end
+
+ def test_implicit_parameters_for_numparams
+ x = x = 1
+ assert_raise(NameError) { binding.implicit_parameter_get(:x) }
+ assert_raise(NameError) { binding.implicit_parameter_defined?(:x) }
+
+ "foo".tap do
+ _5 and flunk
+ assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:_1))
+ assert_equal(nil, binding.implicit_parameter_get(:_5))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(true, binding.implicit_parameter_defined?(:_1))
+ assert_equal(true, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ "bar".tap do
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ end
+ assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:_1))
+ assert_equal(nil, binding.implicit_parameter_get(:_5))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(true, binding.implicit_parameter_defined?(:_1))
+ assert_equal(true, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ end
+
+ "foo".tap do
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ "bar".tap do
+ _5 and flunk
+ assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters)
+ assert_equal("bar", binding.implicit_parameter_get(:_1))
+ assert_equal(nil, binding.implicit_parameter_get(:_5))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(true, binding.implicit_parameter_defined?(:_1))
+ assert_equal(true, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ end
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ end
+ end
+
+ def test_it_is_not_local_variable
+ "foo".tap do
+ it
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ "bar".tap do
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ end
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ "bar".tap do
+ it
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ end
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ end
+
+ "foo".tap do
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ "bar".tap do
+ it
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ end
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ end
+ end
+
+ def test_implicit_parameters_for_it
+ "foo".tap do
+ it or flunk
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:it))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ "bar".tap do
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ end
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:it))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ end
+
+ "foo".tap do
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ "bar".tap do
+ it or flunk
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("bar", binding.implicit_parameter_get(:it))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ end
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ end
+ end
+
+ def test_implicit_parameters_for_it_complex
+ "foo".tap do
+ it = it = "bar"
+
+ assert_equal([], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+
+ assert_equal([:it], binding.local_variables)
+ assert_equal("bar", binding.local_variable_get(:it))
+ assert_equal(true, binding.local_variable_defined?(:it))
+ end
+
+ "foo".tap do
+ it or flunk
+
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:it))
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+
+ assert_equal([], binding.local_variables)
+ assert_raise(NameError) { binding.local_variable_get(:it) }
+ assert_equal(false, binding.local_variable_defined?(:it))
+ end
+
+ "foo".tap do
+ it or flunk
+ it = it = "bar"
+
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("foo", binding.implicit_parameter_get(:it))
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+
+ assert_equal([:it], binding.local_variables)
+ assert_equal("bar", binding.local_variable_get(:it))
+ assert_equal(true, binding.local_variable_defined?(:it))
+ end
+ end
+
+ def test_implicit_parameters_for_it_and_numparams
+ "foo".tap do
+ it or flunk
+ "bar".tap do
+ _5 and flunk
+ assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters)
+ assert_raise(NameError) { binding.implicit_parameter_get(:it) }
+ assert_equal("bar", binding.implicit_parameter_get(:_1))
+ assert_equal(nil, binding.implicit_parameter_get(:_5))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_equal(false, binding.implicit_parameter_defined?(:it))
+ assert_equal(true, binding.implicit_parameter_defined?(:_1))
+ assert_equal(true, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ end
+ end
+
+ "foo".tap do
+ _5 and flunk
+ "bar".tap do
+ it or flunk
+ assert_equal([:it], binding.implicit_parameters)
+ assert_equal("bar", binding.implicit_parameter_get(:it))
+ assert_raise(NameError) { binding.implicit_parameter_get(:_1) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_5) }
+ assert_raise(NameError) { binding.implicit_parameter_get(:_6) }
+ assert_equal(true, binding.implicit_parameter_defined?(:it))
+ assert_equal(false, binding.implicit_parameter_defined?(:_1))
+ assert_equal(false, binding.implicit_parameter_defined?(:_5))
+ assert_equal(false, binding.implicit_parameter_defined?(:_6))
+ end
+ end
+ end
+
+ def test_implicit_parameter_invalid_name
+ message_pattern = /is not an implicit parameter/
+ assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?(:foo) }
+ assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get(:foo) }
+ assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?("wrong_implicit_parameter_name_#{rand(10000)}") }
+ assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get("wrong_implicit_parameter_name_#{rand(10000)}") }
+ end
+
def test_local_variable_set_wb
assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 30)
b = binding
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index e0a86b75b1..d99e356e69 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -58,6 +58,8 @@ class TestProcess < Test::Unit::TestCase
def test_rlimit_nofile
return unless rlimit_exist?
+ omit "LSAN needs to open proc file" if Test::Sanitizers.lsan_enabled?
+
with_tmpchdir {
File.write 's', <<-"End"
# Too small RLIMIT_NOFILE, such as zero, causes problems.
@@ -114,14 +116,19 @@ class TestProcess < Test::Unit::TestCase
}
assert_raise(ArgumentError) { Process.getrlimit(:FOO) }
assert_raise(ArgumentError) { Process.getrlimit("FOO") }
- assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.getrlimit("\u{30eb 30d3 30fc}") }
+
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.getrlimit("\u{30eb 30d3 30fc}") }
+ end
end
def test_rlimit_value
return unless rlimit_exist?
assert_raise(ArgumentError) { Process.setrlimit(:FOO, 0) }
assert_raise(ArgumentError) { Process.setrlimit(:CORE, :FOO) }
- assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit("\u{30eb 30d3 30fc}", 0) }
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit("\u{30eb 30d3 30fc}", 0) }
+ end
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit(:CORE, "\u{30eb 30d3 30fc}") }
with_tmpchdir do
s = run_in_child(<<-'End')
@@ -275,21 +282,22 @@ class TestProcess < Test::Unit::TestCase
end;
end
- MANDATORY_ENVS = %w[RUBYLIB RJIT_SEARCH_BUILD_DIR]
- case RbConfig::CONFIG['target_os']
- when /linux/
- MANDATORY_ENVS << 'LD_PRELOAD'
- when /mswin|mingw/
- MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE])
- when /darwin/
- MANDATORY_ENVS.concat(ENV.keys.grep(/\A__CF_/))
- end
+ MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH RUBY_FREE_AT_EXIT]
if e = RbConfig::CONFIG['LIBPATHENV']
MANDATORY_ENVS << e
end
if e = RbConfig::CONFIG['PRELOADENV'] and !e.empty?
MANDATORY_ENVS << e
end
+ case RbConfig::CONFIG['target_os']
+ when /mswin|mingw/
+ MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE])
+ when /darwin/
+ MANDATORY_ENVS.concat(%w[TMPDIR], ENV.keys.grep(/\A__CF_/))
+ # IO.popen([ENV.keys.to_h {|e| [e, nil]},
+ # RUBY, "-e", %q[print ENV.keys.join(?\0)]],
+ # &:read).split(?\0)
+ end
PREENVARG = ['-e', "%w[#{MANDATORY_ENVS.join(' ')}].each{|e|ENV.delete(e)}"]
ENVARG = ['-e', 'ENV.each {|k,v| puts "#{k}=#{v}" }']
ENVCOMMAND = [RUBY].concat(PREENVARG).concat(ENVARG)
@@ -1560,7 +1568,7 @@ class TestProcess < Test::Unit::TestCase
def test_wait_exception
bug11340 = '[ruby-dev:49176] [Bug #11340]'
t0 = t1 = nil
- sec = 3
+ sec = EnvUtil.apply_timeout_scale(3)
code = "puts;STDOUT.flush;Thread.start{gets;exit};sleep(#{sec})"
IO.popen([RUBY, '-e', code], 'r+') do |f|
pid = f.pid
@@ -1682,9 +1690,10 @@ class TestProcess < Test::Unit::TestCase
if u = Etc.getpwuid(Process.uid)
assert_equal(Process.uid, Process::UID.from_name(u.name), u.name)
end
- assert_raise_with_message(ArgumentError, /\u{4e0d 5b58 5728}/) {
+ exc = assert_raise_kind_of(ArgumentError, SystemCallError) {
Process::UID.from_name("\u{4e0d 5b58 5728}")
}
+ assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError)
end
end
@@ -1693,12 +1702,7 @@ class TestProcess < Test::Unit::TestCase
if g = Etc.getgrgid(Process.gid)
assert_equal(Process.gid, Process::GID.from_name(g.name), g.name)
end
- expected_excs = [ArgumentError]
- expected_excs << Errno::ENOENT if defined?(Errno::ENOENT)
- expected_excs << Errno::ESRCH if defined?(Errno::ESRCH) # WSL 2 actually raises Errno::ESRCH
- expected_excs << Errno::EBADF if defined?(Errno::EBADF)
- expected_excs << Errno::EPERM if defined?(Errno::EPERM)
- exc = assert_raise(*expected_excs) do
+ exc = assert_raise_kind_of(ArgumentError, SystemCallError) do
Process::GID.from_name("\u{4e0d 5b58 5728}") # fu son zai ("absent" in Kanji)
end
assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError)
@@ -1753,11 +1757,7 @@ class TestProcess < Test::Unit::TestCase
end
assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable'
end
- if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # checking -DRJIT_FORCE_ENABLE. It may trigger extra SIGCHLD.
- assert_equal [true], signal_received.uniq, "[ruby-core:19744]"
- else
- assert_equal [true], signal_received, "[ruby-core:19744]"
- end
+ assert_equal [true], signal_received, "[ruby-core:19744]"
rescue NotImplementedError, ArgumentError
ensure
begin
@@ -1767,15 +1767,12 @@ class TestProcess < Test::Unit::TestCase
end
def test_no_curdir
- if /solaris/i =~ RUBY_PLATFORM
- omit "Temporary omit to avoid CI failures after commit to use realpath on required files"
- end
with_tmpchdir {|d|
Dir.mkdir("vd")
status = nil
Dir.chdir("vd") {
dir = "#{d}/vd"
- # OpenSolaris cannot remove the current directory.
+ # Windows cannot remove the current directory with permission issues.
system(RUBY, "--disable-gems", "-e", "Dir.chdir '..'; Dir.rmdir #{dir.dump}", err: File::NULL)
system({"RUBYLIB"=>nil}, RUBY, "--disable-gems", "-e", "exit true")
status = $?
@@ -1809,9 +1806,6 @@ class TestProcess < Test::Unit::TestCase
end
def test_aspawn_too_long_path
- if /solaris/i =~ RUBY_PLATFORM && !defined?(Process::RLIMIT_NPROC)
- omit "Too exhaustive test on platforms without Process::RLIMIT_NPROC such as Solaris 10"
- end
bug4315 = '[ruby-core:34833] #7904 [ruby-core:52628] #11613'
assert_fail_too_long_path(%w"echo |", bug4315)
end
@@ -2002,7 +1996,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_popen_reopen
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
io = File.open(IO::NULL)
io2 = io.dup
@@ -2393,7 +2387,7 @@ EOS
end
def test_deadlock_by_signal_at_forking
- assert_separately(%W(- #{RUBY}), <<-INPUT, timeout: 100)
+ assert_ruby_status(%W(- #{RUBY}), <<-INPUT, timeout: 100)
ruby = ARGV.shift
GC.start # reduce garbage
GC.disable # avoid triggering CoW after forks
@@ -2780,11 +2774,13 @@ EOS
# Disable GC so we can make sure GC only runs in Process.warmup
GC.disable
- total_slots_before = GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots)
+ total_slots_before = GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_bytes) / GC.stat_heap(0, :slot_size)
Process.warmup
- assert_equal(total_slots_before, GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots))
+ # TODO: flaky
+ # assert_equal(total_slots_before, GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_bytes) / GC.stat_heap(0, :slot_size))
+
assert_equal(0, GC.stat(:heap_empty_pages))
assert_operator(GC.stat(:total_freed_pages), :>, 0)
end;
diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb
new file mode 100644
index 0000000000..611b3b7715
--- /dev/null
+++ b/test/ruby/test_ractor.rb
@@ -0,0 +1,377 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestRactor < Test::Unit::TestCase
+ def test_shareability_of_iseq_proc
+ assert_raise Ractor::IsolationError do
+ foo = []
+ Ractor.shareable_proc{ foo }
+ end
+ end
+
+ def test_shareability_of_method_proc
+ # TODO: fix with Ractor.shareable_proc/lambda
+=begin
+ str = +""
+
+ x = str.instance_exec { proc { to_s } }
+ assert_unshareable(x, /Proc\'s self is not shareable/)
+
+ x = str.instance_exec { method(:to_s) }
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
+
+ x = str.instance_exec { method(:to_s).to_proc }
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
+
+ x = str.instance_exec { method(:itself).to_proc }
+ assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error)
+
+ str.freeze
+
+ x = str.instance_exec { proc { to_s } }
+ assert_make_shareable(x)
+
+ x = str.instance_exec { method(:to_s) }
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
+
+ x = str.instance_exec { method(:to_s).to_proc }
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
+
+ x = str.instance_exec { method(:itself).to_proc }
+ assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error)
+=end
+ end
+
+ def test_shareable_proc_define_method_super_method_missing
+ assert_ractor(<<~'RUBY', timeout: 30)
+ iterations = 1_000_000
+
+ class SuperFromShareableProcMethodMissingBase
+ def method_missing(mid, *) = mid
+ end
+
+ class SuperFromShareableProcMethodMissingChild < SuperFromShareableProcMethodMissingBase
+ BODY = Ractor.shareable_proc { super() }
+ define_method(:foo, &BODY)
+ define_method(:bar, &BODY)
+ end
+
+ [:foo, :bar].map do |mid|
+ Ractor.new(mid, iterations) do |mid, iterations|
+ obj = SuperFromShareableProcMethodMissingChild.new
+ iterations.times do
+ got = obj.__send__(mid)
+ raise "#{mid} returned #{got.inspect}" unless got == mid
+ end
+ end
+ end.each(&:value)
+ RUBY
+ end
+
+ def test_shareable_proc_define_method_super_method_entry
+ assert_ractor(<<~'RUBY', timeout: 30)
+ iterations = 1_000_000
+
+ class SuperFromShareableProcBase
+ def foo = :foo
+ def bar = :bar
+ end
+
+ class SuperFromShareableProcChild < SuperFromShareableProcBase
+ BODY = Ractor.shareable_proc { super() }
+ define_method(:foo, &BODY)
+ define_method(:bar, &BODY)
+ end
+
+ [:foo, :bar].map do |mid|
+ Ractor.new(mid, iterations) do |mid, iterations|
+ obj = SuperFromShareableProcChild.new
+ iterations.times do
+ got = obj.__send__(mid)
+ raise "#{mid} returned #{got.inspect}" unless got == mid
+ end
+ end
+ end.each(&:value)
+ RUBY
+ end
+
+ def test_shareability_error_uses_inspect
+ x = (+"").instance_exec { method(:to_s) }
+ def x.to_s
+ raise "this should not be called"
+ end
+ assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()> because it refers unshareable objects", exception: Ractor::Error)
+ end
+
+ def test_sending_exception_with_backtrace
+ assert_ractor(<<~'RUBY')
+ def build_error
+ raise "Test"
+ rescue => error
+ error
+ end
+
+ error = build_error
+ refute_empty error.backtrace
+ refute_empty error.backtrace_locations
+
+ backtrace, backtrace_locations = Ractor.new(error) do |error2|
+ [error2.backtrace, error2.backtrace_locations]
+ end.value
+
+ assert_equal error.backtrace, backtrace
+ refute_empty backtrace_locations
+ RUBY
+ end
+
+ def test_sending_exception_with_array_backtrace
+ assert_ractor(<<~'RUBY')
+ error = StandardError.new
+ error.set_backtrace(["foo", "bar"])
+ refute_empty error.backtrace
+ assert_nil error.backtrace_locations
+
+ backtrace, backtrace_locations = Ractor.new(error) do |error2|
+ [error2.backtrace, error2.backtrace_locations]
+ end.value
+
+ assert_equal error.backtrace, backtrace
+ assert_nil backtrace_locations
+ RUBY
+ end
+
+ def test_sending_object_with_broken_clone
+ assert_ractor(<<~'RUBY')
+ o = Object.new
+ def o.clone
+ self
+ end
+ ractor = Ractor.new { Ractor.receive }
+ error = assert_raise Ractor::Error do
+ ractor.send(o)
+ end
+ assert_match "#clone returned self", error.message
+ RUBY
+ end
+
+ def test_default_thread_group
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:experimental] = false
+
+ main_ractor_id = Thread.current.group.object_id
+ ractor_id = Ractor.new { Thread.current.group.object_id }.value
+ refute_equal main_ractor_id, ractor_id
+ end;
+ end
+
+ def test_class_instance_variables
+ assert_ractor(<<~'RUBY')
+ # Once we're in multi-ractor mode, the codepaths
+ # for class instance variables are a bit different.
+ Ractor.new {}.value
+
+ class TestClass
+ @a = 1
+ @b = 2
+ @c = 3
+ @d = 4
+ end
+
+ assert_equal 4, TestClass.remove_instance_variable(:@d)
+ assert_nil TestClass.instance_variable_get(:@d)
+ assert_equal 4, TestClass.instance_variable_set(:@d, 4)
+ assert_equal 4, TestClass.instance_variable_get(:@d)
+ RUBY
+ end
+
+
+ def test_class_variables
+ # [Bug #22072]
+ assert_ractor(<<~'RUBY')
+ module Foo
+ def self.foo = @@foo
+ end
+
+ Foo.class_variable_set(:@@foo, 1)
+
+ 10.times { |i| Foo.class_variable_set(:"@@bar#{i}", i) }
+
+ assert_equal(Foo.foo, 1)
+ RUBY
+ end
+
+ def test_struct_instance_variables
+ assert_ractor(<<~'RUBY')
+ StructIvar = Struct.new(:member) do
+ def initialize(*)
+ super
+ @ivar = "ivar"
+ end
+ attr_reader :ivar
+ end
+ obj = StructIvar.new("member")
+ obj_copy = Ractor.new { Ractor.receive }.send(obj).value
+ assert_equal obj.ivar, obj_copy.ivar
+ refute_same obj.ivar, obj_copy.ivar
+ assert_equal obj.member, obj_copy.member
+ refute_same obj.member, obj_copy.member
+ RUBY
+ end
+
+ def test_move_nested_hash_during_gc_with_yjit
+ assert_ractor(<<~'RUBY', timeout: 20, args: [{ "RUBY_YJIT_ENABLE" => "1" }])
+ GC.stress = true
+ hash = { foo: { bar: "hello" }, baz: { qux: "there" } }
+ result = Ractor.new { Ractor.receive }.send(hash, move: true).value
+ assert_equal "hello", result[:foo][:bar]
+ assert_equal "there", result[:baz][:qux]
+ RUBY
+ end
+
+ def test_fork_raise_isolation_error
+ assert_ractor(<<~'RUBY')
+ ractor = Ractor.new do
+ Process.fork
+ rescue Ractor::IsolationError => e
+ e
+ end
+ assert_equal Ractor::IsolationError, ractor.value.class
+ RUBY
+ end if Process.respond_to?(:fork)
+
+ def test_require_raises_and_no_ractor_belonging_issue
+ assert_ractor(<<~'RUBY')
+ require "tempfile"
+ f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
+ f.write("raise 'uh oh'")
+ f.flush
+ err_msg = Ractor.new(f.path) do |path|
+ begin
+ require path
+ rescue RuntimeError => e
+ e.message # had confirm belonging issue here
+ else
+ nil
+ end
+ end.value
+ assert_equal "uh oh", err_msg
+ RUBY
+ end
+
+ def test_require_non_string
+ assert_ractor(<<~'RUBY')
+ require "tempfile"
+ require "pathname"
+ f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
+ f.write("")
+ f.flush
+ result = Ractor.new(f.path) do |path|
+ require Pathname.new(path)
+ "success"
+ end.value
+ assert_equal "success", result
+ RUBY
+ end
+
+ # [Bug #21398]
+ def test_port_receive_dnt_with_port_send
+ omit 'unstable on windows and macos-14' if RUBY_PLATFORM =~ /mswin|mingw|darwin/
+ assert_ractor(<<~'RUBY', timeout: 90)
+ THREADS = 10
+ JOBS_PER_THREAD = 50
+ ARRAY_SIZE = 20_000
+ def ractor_job(job_count, array_size)
+ port = Ractor::Port.new
+ workers = (1..4).map do |i|
+ Ractor.new(port) do |job_port|
+ while job = Ractor.receive
+ result = job.map { |x| x * 2 }.sum
+ job_port.send result
+ end
+ end
+ end
+ jobs = Array.new(job_count) { Array.new(array_size) { rand(1000) } }
+ jobs.each_with_index do |job, i|
+ w_idx = i % 4
+ workers[w_idx].send(job)
+ end
+ results = []
+ jobs.size.times do
+ result = port.receive # dnt receive
+ results << result
+ end
+ results
+ end
+ threads = []
+ # creates 40 ractors (THREADSx4)
+ THREADS.times do
+ threads << Thread.new do
+ ractor_job(JOBS_PER_THREAD, ARRAY_SIZE)
+ end
+ end
+ threads.each(&:join)
+ RUBY
+ end
+
+ # [Bug #20146]
+ def test_max_cpu_1
+ assert_ractor(<<~'RUBY', args: [{ "RUBY_MAX_CPU" => "1" }])
+ assert_equal :ok, Ractor.new { :ok }.value
+ RUBY
+ end
+
+ def test_symbol_proc_is_shareable
+ pr = :symbol.to_proc
+ assert_make_shareable(pr)
+ end
+
+ # [Bug #21775]
+ def test_ifunc_proc_not_shareable
+ h = Hash.new { self }
+ pr = h.to_proc
+ assert_unshareable(pr, /not supported yet/, exception: RuntimeError)
+ end
+
+ def test_copy_unshareable_object_error_message
+ assert_ractor(<<~'RUBY')
+ pr = proc {}
+ err = assert_raise(Ractor::Error) do
+ Ractor.new(pr) {}.join
+ end
+ assert_match(/can not copy Proc object/, err.message)
+ RUBY
+ end
+
+ def test_ractor_new_raises_isolation_error_if_outer_variables_are_accessed
+ assert_raise(Ractor::IsolationError) do
+ channel = Ractor::Port.new
+ Ractor.new(channel) do
+ inbound_work = Ractor::Port.new
+ channel << inbound_work
+ end
+ end
+ end
+
+ def test_ractor_new_raises_isolation_error_if_proc_uses_yield
+ assert_raise(Ractor::IsolationError) do
+ Ractor.new do
+ yield
+ end
+ end
+ end
+
+ def assert_make_shareable(obj)
+ refute Ractor.shareable?(obj), "object was already shareable"
+ Ractor.make_shareable(obj)
+ assert Ractor.shareable?(obj), "object didn't become shareable"
+ end
+
+ def assert_unshareable(obj, msg=nil, exception: Ractor::IsolationError)
+ refute Ractor.shareable?(obj), "object is already shareable"
+ assert_raise_with_message(exception, msg) do
+ Ractor.make_shareable(obj)
+ end
+ refute Ractor.shareable?(obj), "despite raising, object became shareable"
+ end
+end
diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb
index 10d69ea2df..ff17dca69e 100644
--- a/test/ruby/test_range.rb
+++ b/test/ruby/test_range.rb
@@ -36,6 +36,7 @@ class TestRange < Test::Unit::TestCase
assert_equal(["a"], ("a" ... "b").to_a)
assert_equal(["a", "b"], ("a" .. "b").to_a)
assert_equal([*"a".."z", "aa"], ("a"..).take(27))
+ assert_equal([*"a".."z"], eval("('a' || 'b')..'z'").to_a)
end
def test_range_numeric_string
@@ -121,13 +122,15 @@ class TestRange < Test::Unit::TestCase
assert_equal([10,9,8], (0..10).max(3))
assert_equal([9,8,7], (0...10).max(3))
+ assert_equal([10,9,8], (..10).max(3))
+ assert_equal([9,8,7], (...10).max(3))
assert_raise(RangeError) { (1..).max(3) }
assert_raise(RangeError) { (1...).max(3) }
assert_raise(RangeError) { (..0).min {|a, b| a <=> b } }
assert_equal(2, (..2).max)
- assert_raise(TypeError) { (...2).max }
+ assert_equal(1, (...2).max)
assert_raise(TypeError) { (...2.0).max }
assert_equal(Float::INFINITY, (1..Float::INFINITY).max)
@@ -870,16 +873,20 @@ class TestRange < Test::Unit::TestCase
def test_first_last
assert_equal([0, 1, 2], (0..10).first(3))
assert_equal([8, 9, 10], (0..10).last(3))
+ assert_equal([8, 9, 10], (nil..10).last(3))
assert_equal(0, (0..10).first)
assert_equal(10, (0..10).last)
+ assert_equal(10, (nil..10).last)
assert_equal("a", ("a".."c").first)
assert_equal("c", ("a".."c").last)
assert_equal(0, (2..0).last)
assert_equal([0, 1, 2], (0...10).first(3))
assert_equal([7, 8, 9], (0...10).last(3))
+ assert_equal([7, 8, 9], (nil...10).last(3))
assert_equal(0, (0...10).first)
assert_equal(10, (0...10).last)
+ assert_equal(10, (nil...10).last)
assert_equal("a", ("a"..."c").first)
assert_equal("c", ("a"..."c").last)
assert_equal(0, (2...0).last)
@@ -1451,6 +1458,12 @@ class TestRange < Test::Unit::TestCase
assert_raise(RangeError) { (1..).to_a }
end
+ def test_to_set
+ assert_equal(Set[1,2,3,4,5], (1..5).to_set)
+ assert_equal(Set[1,2,3,4], (1...5).to_set)
+ assert_raise(RangeError) { (1..).to_set }
+ end
+
def test_beginless_range_iteration
assert_raise(TypeError) { (..1).each { } }
end
@@ -1507,6 +1520,7 @@ class TestRange < Test::Unit::TestCase
assert_operator((nil..nil), :overlap?, (3..))
assert_operator((nil...nil), :overlap?, (nil..))
assert_operator((nil..nil), :overlap?, (..3))
+ assert_operator((..3), :overlap?, (nil..nil))
assert_raise(TypeError) { (1..3).overlap?(1) }
diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb
index 89bb7b20a8..a02e11acc5 100644
--- a/test/ruby/test_rational.rb
+++ b/test/ruby/test_rational.rb
@@ -65,7 +65,7 @@ class Rational_Test < Test::Unit::TestCase
assert_instance_of(String, c.to_s)
end
- def test_conv
+ def test_conv_integer
c = Rational(0,1)
assert_equal(Rational(0,1), c)
@@ -94,6 +94,11 @@ class Rational_Test < Test::Unit::TestCase
c = Rational(Rational(1,2),Rational(1,2))
assert_equal(Rational(1), c)
+ assert_equal(Rational(3),Rational(3))
+ assert_equal(Rational(1),Rational(3,3))
+ end
+
+ def test_conv_complex
c = Rational(Complex(1,2),2)
assert_equal(Complex(Rational(1,2),1), c)
@@ -102,11 +107,21 @@ class Rational_Test < Test::Unit::TestCase
c = Rational(Complex(1,2),Complex(1,2))
assert_equal(Rational(1), c)
+ end
- assert_equal(Rational(3),Rational(3))
- assert_equal(Rational(1),Rational(3,3))
+ def test_conv_float
assert_equal(3.3.to_r,Rational(3.3))
assert_equal(1,Rational(3.3,3.3))
+
+ if (0.0/0).nan?
+ assert_raise(FloatDomainError){Rational(0.0/0)}
+ end
+ if (1.0/0).infinite?
+ assert_raise(FloatDomainError){Rational(1.0/0)}
+ end
+ end
+
+ def test_conv_string
assert_equal(Rational(3),Rational('3'))
assert_equal(Rational(1),Rational('3.0','3.0'))
assert_equal(Rational(1),Rational('3/3','3/3'))
@@ -115,11 +130,19 @@ class Rational_Test < Test::Unit::TestCase
assert_equal(Rational(111, 10), Rational('1.11e1'))
assert_equal(Rational(111, 100), Rational('1.11e0'))
assert_equal(Rational(111, 1000), Rational('1.11e-1'))
+ assert_equal(Rational(5, 4), Rational('3.0r','2.4R'))
+ end
+
+ def test_conv_error
assert_raise(TypeError){Rational(nil)}
assert_raise(ArgumentError){Rational('')}
- assert_raise_with_message(ArgumentError, /\u{221a 2668}/) {
- Rational("\u{221a 2668}")
- }
+
+ EnvUtil.with_default_internal(Encoding::UTF_8) do
+ assert_raise_with_message(ArgumentError, /\u{221a 2668}/) {
+ Rational("\u{221a 2668}")
+ }
+ end
+
assert_warning('') {
assert_predicate(Rational('1e-99999999999999999999'), :zero?)
}
@@ -127,7 +150,9 @@ class Rational_Test < Test::Unit::TestCase
assert_raise(TypeError){Rational(Object.new)}
assert_raise(TypeError){Rational(Object.new, Object.new)}
assert_raise(TypeError){Rational(1, Object.new)}
+ end
+ def test_conv_coerce
bug12485 = '[ruby-core:75995] [Bug #12485]'
o = Object.new
def o.to_int; 1; end
@@ -159,13 +184,6 @@ class Rational_Test < Test::Unit::TestCase
assert_raise(ArgumentError){Rational()}
assert_raise(ArgumentError){Rational(1,2,3)}
- if (0.0/0).nan?
- assert_raise(FloatDomainError){Rational(0.0/0)}
- end
- if (1.0/0).infinite?
- assert_raise(FloatDomainError){Rational(1.0/0)}
- end
-
bug16518 = "[ruby-core:96942] [Bug #16518]"
cls = Class.new(Numeric) do
def /(y); 42; end
@@ -825,6 +843,10 @@ class Rational_Test < Test::Unit::TestCase
ng[5, 3, '5/3x']
ng[5, 1, '5/-3']
+
+ ok[30, 24, '3.0r/2.4R']
+ ng[30, 24, '3.0r/2.4re1']
+ ng[30, 240, '3.0r/2.4e1r']
end
def test_parse_zero_denominator
diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb
index 6ce434790b..dce09c2dd8 100644
--- a/test/ruby/test_refinement.rb
+++ b/test/ruby/test_refinement.rb
@@ -1035,6 +1035,43 @@ class TestRefinement < Test::Unit::TestCase
RUBY
end
+ def test_prohibit_super_in_refined_module_method
+ assert_separately([], <<-"end;")
+ bug22071 = '[ruby-core:125511] [Bug #22071]'
+ class BasicObject
+ def a; "B" end
+ end
+
+ module G
+ def a; "G" + super end
+ end
+
+ module F
+ include G
+ def a; "F" + super end
+ end
+
+ class A
+ def a; "A" + super end
+ end
+
+ class B < A
+ include F
+ end
+
+ module R
+ refine F do
+ def a; "R"+super end
+ end
+ end
+ using R
+
+ msg = "super in a method in a module that has been refined and that is called via super" +
+ " from a refinement method is not supported."
+ assert_raise(NoMethodError, msg, bug22071) { B.new.a }
+ end;
+ end
+
def test_refine_after_using
assert_separately([], <<-"end;")
bug8880 = '[ruby-core:57079] [Bug #8880]'
@@ -1058,6 +1095,613 @@ class TestRefinement < Test::Unit::TestCase
end;
end
+ {
+ zsuper: "public :a",
+ super: "def a = super"
+ }.each do |desc, method_def|
+ define_method :"test_modify_#{desc}_refinement_method_in_superclass" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ alias a a
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, B.new.a)
+
+ class A
+ def a = :b
+ end
+ assert_equal(:b, B.new.a)
+ end;
+ end
+
+ define_method :"test_modify_#{desc}_refinement_method_in_module_prepended_to_superclass" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :a
+ alias a a
+ end
+
+ class A
+ prepend M
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, B.new.a)
+
+ module M
+ def a = :b
+ end
+ assert_equal(:b, B.new.a)
+ end;
+ end
+
+ define_method :"test_modify_#{desc}_refinement_method_in_module_included_in_superclass" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :a
+ alias a a
+ end
+
+ class A
+ include M
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, B.new.a)
+
+ module M
+ def a = :b
+ end
+ assert_equal(:b, B.new.a)
+ end;
+ end
+
+ define_method :"test_remove_#{desc}_refinement_method_from_superclass" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ private def a = :b
+ end
+
+ class C < B
+ end
+
+ module R
+ refine C do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, C.new.a)
+
+ class B
+ remove_method(:a)
+ end
+ assert_equal(:a, C.new.a)
+ end;
+ end
+
+ define_method :"test_remove_#{desc}_refinement_method_from_module_prepended_to_superclass" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :b
+ end
+
+ class A
+ prepend M
+ private def a = :a
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, B.new.a)
+
+ module M
+ remove_method(:a)
+ end
+ assert_equal(:a, B.new.a)
+ end;
+ end
+
+ define_method :"test_remove_#{desc}_refinement_method_from_module_prepended_to_class" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :b
+ end
+
+ class A
+ prepend M
+ private def a = :a
+ end
+
+ module R
+ refine A do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, A.new.a)
+
+ module M
+ remove_method(:a)
+ end
+ assert_equal(:a, A.new.a)
+ end;
+ end
+
+ define_method :"test_remove_#{desc}_refinement_method_from_module_included_in_superclass" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :b
+ end
+
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ include M
+ end
+
+ class C < B
+ end
+
+ module R
+ refine C do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, C.new.a)
+
+ module M
+ remove_method(:a)
+ end
+ assert_equal(:a, C.new.a)
+ end;
+ end
+
+ define_method :"test_remove_#{desc}_refinement_method_from_module_included_in_class" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :b
+ end
+
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ include M
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, B.new.a)
+
+ module M
+ remove_method(:a)
+ end
+ assert_equal(:a, B.new.a)
+ end;
+ end
+
+ define_method :"test_undef_#{desc}_refinement_method_in_superclass" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ private def a = :b
+ end
+
+ class C < B
+ end
+
+ module R
+ refine C do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, C.new.a)
+
+ class B
+ undef_method(:a)
+ end
+ assert_raise(NoMethodError) { C.new.a }
+ end;
+ end
+
+ define_method :"test_undef_#{desc}_refinement_method_in_module_prepended_to_superclass" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :b
+ end
+
+ class A
+ prepend M
+ private def a = :a
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, B.new.a)
+
+ module M
+ undef_method(:a)
+ end
+ assert_raise(NoMethodError) { B.new.a }
+ end;
+ end
+
+ define_method :"test_undef_#{desc}_refinement_method_in_module_prepended_to_class" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :b
+ end
+
+ class A
+ prepend M
+ private def a = :a
+ end
+
+ module R
+ refine A do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, A.new.a)
+
+ module M
+ undef_method(:a)
+ end
+ assert_raise(NoMethodError) { A.new.a }
+ end;
+ end
+
+ define_method :"test_undef_#{desc}_refinement_method_in_module_included_in_superclass" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :b
+ end
+
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ include M
+ end
+
+ class C < B
+ end
+
+ module R
+ refine C do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, C.new.a)
+
+ module M
+ undef_method(:a)
+ end
+ assert_raise(NoMethodError) { C.new.a }
+ end;
+ end
+
+ define_method :"test_undef_#{desc}_refinement_method_in_module_included_in_class" do
+ assert_separately([], <<-"end;")
+ module M
+ private def a = :b
+ end
+
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ include M
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:b, B.new.a)
+
+ module M
+ undef_method(:a)
+ end
+ assert_raise(NoMethodError) { B.new.a }
+ end;
+ end
+
+ define_method :"test_override_#{desc}_refinement_method_by_prepending_to_class" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ module R
+ refine A do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, A.new.a)
+
+ module M
+ def a = :b
+ end
+ A.prepend M
+ assert_equal(:b, A.new.a)
+ end;
+ end
+
+ define_method :"test_override_#{desc}_refinement_method_by_prepending_to_superclass" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, B.new.a)
+
+ module M
+ def a = :b
+ end
+ A.prepend M
+ assert_equal(:b, B.new.a)
+ end;
+ end
+
+ define_method :"test_override_#{desc}_refinement_method_by_including_in_class" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, B.new.a)
+
+ module M
+ def a = :b
+ end
+ B.include M
+ assert_equal(:b, B.new.a)
+ end;
+ end
+
+ define_method :"test_override_#{desc}_refinement_method_by_including_in_superclass" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ end
+
+ class C < B
+ end
+
+ module R
+ refine C do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, C.new.a)
+
+ module M
+ def a = :b
+ end
+ B.include M
+ assert_equal(:b, C.new.a)
+ end;
+ end
+
+ define_method :"test_override_#{desc}_refinement_method_by_prepending_undef_to_class" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ module R
+ refine A do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, A.new.a)
+
+ module M
+ def a = :b
+ undef_method :a
+ end
+ A.prepend M
+ assert_raise(NoMethodError) { A.new.a }
+ end;
+ end
+
+ define_method :"test_override_#{desc}_refinement_method_by_prepending_undef_to_superclass" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, B.new.a)
+
+ module M
+ def a = :b
+ undef_method :a
+ end
+ A.prepend M
+ assert_raise(NoMethodError) { B.new.a }
+ end;
+ end
+
+ define_method :"test_override_#{desc}_refinement_method_by_including_undef_in_class" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ end
+
+ module R
+ refine B do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, B.new.a)
+
+ module M
+ def a = :b
+ undef_method :a
+ end
+ B.include M
+ assert_raise(NoMethodError) { B.new.a }
+ end;
+ end
+
+ define_method :"test_override_#{desc}_refinement_method_by_including_undef_in_superclass" do
+ assert_separately([], <<-"end;")
+ class A
+ private def a = :a
+ end
+
+ class B < A
+ end
+
+ class C < B
+ end
+
+ module R
+ refine C do
+ #{method_def}
+ end
+ end
+ using R
+ assert_equal(:a, C.new.a)
+
+ module M
+ def a = :b
+ undef_method :a
+ end
+ B.include M
+ assert_raise(NoMethodError) { C.new.a }
+ end;
+ end
+ end
+
+ def test_zsuper_refinement_method_arity_and_parameters
+ assert_separately([], <<-"end;")
+ class A
+ private def a(b) = b
+ end
+
+ class B < A
+ public :a
+ end
+
+ module R
+ refine A do
+ public :a
+ end
+ end
+ using R
+
+ m = B.instance_method(:a)
+ assert_equal(1, m.arity)
+ assert_equal([[:req, :b]], m.parameters)
+
+ m = A.instance_method(:a)
+ assert_equal(1, m.arity)
+ assert_equal([[:req, :b]], m.parameters)
+ end;
+ end
+
def test_instance_methods
bug8881 = '[ruby-core:57080] [Bug #8881]'
assert_not_include(Foo.instance_methods(false), :z, bug8881)
@@ -1933,6 +2577,29 @@ class TestRefinement < Test::Unit::TestCase
end;
end
+ def test_public_in_refine_for_method_in_superclass
+ assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ bug21446 = '[ruby-core:122558] [Bug #21446]'
+
+ class CowSuper
+ private
+ def moo() "Moo"; end
+ end
+ class Cow < CowSuper
+ end
+
+ module PublicCows
+ refine(Cow) {
+ public :moo
+ }
+ end
+
+ using PublicCows
+ assert_equal("Moo", Cow.new.moo, bug21446)
+ end;
+ end
+
module SuperToModule
class Parent
end
@@ -2232,7 +2899,7 @@ class TestRefinement < Test::Unit::TestCase
def test_refining_module_repeatedly
bug14070 = '[ruby-core:83617] [Bug #14070]'
- assert_in_out_err([], <<-INPUT, ["ok"], [], bug14070)
+ assert_in_out_err([], <<-INPUT, ["ok"], [], bug14070, timeout: 30)
1000.times do
Class.new do
include Enumerable
@@ -2712,6 +3379,287 @@ class TestRefinement < Test::Unit::TestCase
INPUT
end
+ def test_refined_module_method
+ m = Module.new {
+ x = Module.new {def qux;end}
+ refine(x) {def qux;end}
+ break x
+ }
+ extend m
+ meth = method(:qux)
+ assert_equal m, meth.owner
+ assert_equal :qux, meth.name
+ end
+
+ def test_symbol_proc_from_using_scope
+ # assert_separately to contain the side effects of refining Kernel
+ assert_separately([], <<~RUBY)
+ class RefinedScope
+ using(Module.new { refine(Kernel) { def itself = 0 } })
+ ITSELF = :itself.to_proc
+ end
+
+ assert_equal(1, RefinedScope::ITSELF[1], "[Bug #21265]")
+ RUBY
+ end
+
+ def test_method_super_method_single_refinements
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ module M
+ R = refine(A) { def b; "M" + super; end }
+ end
+ using M
+ a = A.new
+ m = a.method(:b)
+ assert_equal("MA", a.b)
+ assert_equal("MA", m.call)
+ assert_equal(M::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ RUBY
+ end
+
+ def test_method_super_method_multiple_refinements_with_activated_refinements_during_super
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ module M
+ R = refine(A) { def b; "M" + super; end }
+ end
+ module N
+ using M
+ R = refine(A) { def b; "N" + super; end }
+ end
+ using M
+ using N
+ a = A.new
+ m = a.method(:b)
+ assert_equal("NMA", a.b)
+ assert_equal("NMA", m.call)
+ assert_equal(N::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(M::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ RUBY
+ end
+
+ def test_method_super_method_multiple_refinements_without_activated_refinements_during_super
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ module M
+ R = refine(A) { def b; "M" + super; end }
+ end
+ module N
+ R = refine(A) { def b; "N" + super; end }
+ end
+ using M
+ using N
+ a = A.new
+ m = a.method(:b)
+ assert_equal("NA", a.b)
+ assert_equal("NA", m.call)
+ assert_equal(N::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ RUBY
+ end
+
+ def test_unbound_method_super_method_single_refinements
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ module M
+ R = refine(A) { def b; "M" + super; end }
+ end
+ using M
+ m = A.instance_method(:b)
+ assert_equal(M::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ RUBY
+ end
+
+ def test_method_super_method_nonrefined_finds_refined_super
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ module M
+ R = refine(A) { def b; "M" + super; end }
+ end
+ using M
+ class B < A
+ def b = "B" + super
+ end
+ b = B.new
+ m = b.method(:b)
+ assert_equal("BMA", b.b)
+ assert_equal("BMA", m.call)
+ assert_equal(B, m.owner)
+ m = m.super_method
+ assert_equal(M::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ RUBY
+ end
+
+ def test_method_super_method_refined_finds_refined_method_in_superclass
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ class B < A
+ end
+ module M
+ R = refine(A) { def b; "M" + super; end }
+ end
+ using M
+ module N
+ R = refine(B) { def b; "N" + super; end }
+ end
+ using N
+
+ b = B.new
+ m = b.method(:b)
+ assert_equal("NMA", b.b)
+ assert_equal("NMA", m.call)
+ assert_equal(N::R, m.owner)
+ assert_equal(B, m.owner.target)
+
+ m = m.super_method
+ assert_equal(M::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ RUBY
+ end
+
+ def test_method_super_method_uses_cref_of_method_not_cref_of_caller
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ class B < A
+ end
+ module M
+ R = refine(A) { def b; "M" + super; end }
+ end
+ module N
+ R = refine(B) do
+ using M
+ def b; "N" + super; end
+ end
+ end
+ using N
+
+ b = B.new
+ m = b.method(:b)
+ assert_equal("NMA", b.b)
+ assert_equal("NMA", m.call)
+ assert_equal(N::R, m.owner)
+ assert_equal(B, m.owner.target)
+
+ m = m.super_method
+ assert_equal(M::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ RUBY
+ end
+
+ def test_method_super_method_only_considers_activated_refinements
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ class B < A
+ def b = "B" + super
+ end
+ module M
+ R = refine(A){def b = "M" + super}
+ end
+ module N
+ R = refine(B){def b = "N" + super}
+ end
+
+ module O
+ using M
+ using N
+
+ b = B.new
+ m = b.method(:b)
+ assert_equal("NBA", b.b)
+ assert_equal("NBA", m.call)
+ assert_equal(N::R, m.owner)
+ assert_equal(B, m.owner.target)
+
+ m = m.super_method
+ assert_equal(B, m.owner)
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ end
+ RUBY
+ end
+
+ def test_method_super_method_bmethod_finds_refinements
+ assert_separately([], <<~RUBY)
+ class A
+ def b = "A"
+ end
+ module M
+ R = refine(A) { def b; "M" + super; end }
+ end
+ using M
+ class B < A
+ define_method(:b) { "B" + super() }
+ end
+
+ b = B.new
+ m = b.method(:b)
+ assert_equal("BMA", b.b)
+ assert_equal("BMA", m.call)
+ assert_equal(B, m.owner)
+
+ m = m.super_method
+ assert_equal(M::R, m.owner)
+ assert_equal(A, m.owner.target)
+
+ m = m.super_method
+ assert_equal(A, m.owner)
+ assert_nil(m.super_method)
+ RUBY
+ end
+
private
def eval_using(mod, s)
diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb
index a4e9d7ec8e..805c57b472 100644
--- a/test/ruby/test_regexp.rb
+++ b/test/ruby/test_regexp.rb
@@ -975,7 +975,7 @@ class TestRegexp < Test::Unit::TestCase
def test_dup
assert_equal(//, //.dup)
- assert_raise(TypeError) { //.dup.instance_eval { initialize_copy(nil) } }
+ assert_raise(FrozenError) { //.dup.instance_eval { initialize_copy(/a/) } }
end
def test_regsub
@@ -999,6 +999,30 @@ class TestRegexp < Test::Unit::TestCase
assert_equal('foobazquux/foobazquux', result, bug8856)
end
+ def test_regsub_no_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", rss: true)
+ code = proc do
+ "aaaaaaaaaaa".gsub(/a/, "")
+ end
+
+ 1_000.times(&code)
+ begin;
+ 100_000.times(&code)
+ end;
+ end
+
+ def test_regsub_no_memory_leak_many_captures
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", rss: true)
+ code = proc do
+ "aaaaaaaaaaa".gsub(/(a)(b)?(c)?(d)?(e)?(f)?(g)?(h)?/, "")
+ end
+
+ 1_000.times(&code)
+ begin;
+ 100_000.times(&code)
+ end;
+ end
+
def test_ignorecase
v = assert_deprecated_warning(/variable \$= is no longer effective/) { $= }
assert_equal(false, v)
@@ -1024,10 +1048,12 @@ class TestRegexp < Test::Unit::TestCase
[Encoding::UTF_8, Encoding::Shift_JIS, Encoding::EUC_JP].each do |enc|
idx = key.encode(enc)
pat = /#{idx}/
- test.call {|m| assert_raise_with_message(IndexError, pat, bug10877) {m[idx]} }
- test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.offset(idx)} }
- test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.begin(idx)} }
- test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.end(idx)} }
+ EnvUtil.with_default_internal(enc) do
+ test.call {|m| assert_raise_with_message(IndexError, pat, bug10877) {m[idx]} }
+ test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.offset(idx)} }
+ test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.begin(idx)} }
+ test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.end(idx)} }
+ end
end
test.call {|m| assert_equal(/a/, m.regexp) }
test.call {|m| assert_equal("abc", m.string) }
@@ -1296,6 +1322,9 @@ class TestRegexp < Test::Unit::TestCase
assert_match(/\A[[:space:]]+\z/, "\r\n\v\f\r\s\u0085")
assert_match(/\A[[:ascii:]]+\z/, "\x00\x7F")
assert_no_match(/[[:ascii:]]/, "\x80\xFF")
+
+ assert_match(/[[:word:]]/, "\u{200C}")
+ assert_match(/[[:word:]]/, "\u{200D}")
end
def test_cclass_R
@@ -1499,6 +1528,120 @@ class TestRegexp < Test::Unit::TestCase
"CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF")
end
+ def test_unicode_age_15_1
+ @matches = %w"15.1"
+ @unmatches = %w"15.0"
+
+ # https://www.unicode.org/Public/15.1.0/ucd/DerivedAge.txt
+ assert_unicode_age("\u{2FFC}".."\u{2FFF}",
+ "IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION")
+ assert_unicode_age("\u{31EF}",
+ "IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION")
+ assert_unicode_age("\u{2EBF0}".."\u{2EE5D}",
+ "CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D")
+ end
+
+ def test_unicode_age_16_0
+ @matches = %w"16.0"
+ @unmatches = %w"15.1"
+
+ # https://www.unicode.org/Public/16.0.0/ucd/DerivedAge.txt
+ assert_unicode_age("\u{0897}",
+ "ARABIC PEPET")
+ assert_unicode_age("\u{1B4E}".."\u{1B4F}",
+ "BALINESE INVERTED CARIK SIKI..BALINESE INVERTED CARIK PAREREN")
+ assert_unicode_age("\u{1B7F}",
+ "BALINESE PANTI BAWAK")
+ assert_unicode_age("\u{1C89}".."\u{1C8A}",
+ "CYRILLIC CAPITAL LETTER TJE..CYRILLIC SMALL LETTER TJE")
+ assert_unicode_age("\u{2427}".."\u{2429}",
+ "SYMBOL FOR DELETE SQUARE CHECKER BOARD FORM..SYMBOL FOR DELETE MEDIUM SHADE FORM")
+ assert_unicode_age("\u{31E4}".."\u{31E5}",
+ "CJK STROKE HXG..CJK STROKE SZP")
+ assert_unicode_age("\u{A7CB}".."\u{A7CD}",
+ "LATIN CAPITAL LETTER RAMS HORN..LATIN SMALL LETTER S WITH DIAGONAL STROKE")
+ assert_unicode_age("\u{A7DA}".."\u{A7DC}",
+ "LATIN CAPITAL LETTER LAMBDA..LATIN CAPITAL LETTER LAMBDA WITH STROKE")
+ assert_unicode_age("\u{105C0}".."\u{105F3}",
+ "TODHRI LETTER A..TODHRI LETTER OO")
+ assert_unicode_age("\u{10D40}".."\u{10D65}",
+ "GARAY DIGIT ZERO..GARAY CAPITAL LETTER OLD NA")
+ assert_unicode_age("\u{10D69}".."\u{10D85}",
+ "GARAY VOWEL SIGN E..GARAY SMALL LETTER OLD NA")
+ assert_unicode_age("\u{10D8E}".."\u{10D8F}",
+ "GARAY PLUS SIGN..GARAY MINUS SIGN")
+ assert_unicode_age("\u{10EC2}".."\u{10EC4}",
+ "ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW")
+ assert_unicode_age("\u{10EFC}",
+ "ARABIC COMBINING ALEF OVERLAY")
+ assert_unicode_age("\u{11380}".."\u{11389}",
+ "TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL")
+ assert_unicode_age("\u{1138B}",
+ "TULU-TIGALARI LETTER EE")
+ assert_unicode_age("\u{1138E}",
+ "TULU-TIGALARI LETTER AI")
+ assert_unicode_age("\u{11390}".."\u{113B5}",
+ "TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA")
+ assert_unicode_age("\u{113B7}".."\u{113C0}",
+ "TULU-TIGALARI SIGN AVAGRAHA..TULU-TIGALARI VOWEL SIGN VOCALIC LL")
+ assert_unicode_age("\u{113C2}",
+ "TULU-TIGALARI VOWEL SIGN EE")
+ assert_unicode_age("\u{113C5}",
+ "TULU-TIGALARI VOWEL SIGN AI")
+ assert_unicode_age("\u{113C7}".."\u{113CA}",
+ "TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI SIGN CANDRA ANUNASIKA")
+ assert_unicode_age("\u{113CC}".."\u{113D5}",
+ "TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI DOUBLE DANDA")
+ assert_unicode_age("\u{113D7}".."\u{113D8}",
+ "TULU-TIGALARI SIGN OM PUSHPIKA..TULU-TIGALARI SIGN SHRII PUSHPIKA")
+ assert_unicode_age("\u{113E1}".."\u{113E2}",
+ "TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA")
+ assert_unicode_age("\u{116D0}".."\u{116E3}",
+ "MYANMAR PAO DIGIT ZERO..MYANMAR EASTERN PWO KAREN DIGIT NINE")
+ assert_unicode_age("\u{11BC0}".."\u{11BE1}",
+ "SUNUWAR LETTER DEVI..SUNUWAR SIGN PVO")
+ assert_unicode_age("\u{11BF0}".."\u{11BF9}",
+ "SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE")
+ assert_unicode_age("\u{11F5A}",
+ "KAWI SIGN NUKTA")
+ assert_unicode_age("\u{13460}".."\u{143FA}",
+ "EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA")
+ assert_unicode_age("\u{16100}".."\u{16139}",
+ "GURUNG KHEMA LETTER A..GURUNG KHEMA DIGIT NINE")
+ assert_unicode_age("\u{16D40}".."\u{16D79}",
+ "KIRAT RAI SIGN ANUSVARA..KIRAT RAI DIGIT NINE")
+ assert_unicode_age("\u{18CFF}",
+ "KHITAN SMALL SCRIPT CHARACTER-18CFF")
+ assert_unicode_age("\u{1CC00}".."\u{1CCF9}",
+ "UP-POINTING GO-KART..OUTLINED DIGIT NINE")
+ assert_unicode_age("\u{1CD00}".."\u{1CEB3}",
+ "BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET")
+ assert_unicode_age("\u{1E5D0}".."\u{1E5FA}",
+ "OL ONAL LETTER O..OL ONAL DIGIT NINE")
+ assert_unicode_age("\u{1E5FF}",
+ "OL ONAL ABBREVIATION SIGN")
+ assert_unicode_age("\u{1F8B2}".."\u{1F8BB}",
+ "RIGHTWARDS ARROW WITH LOWER HOOK..SOUTH WEST ARROW FROM BAR")
+ assert_unicode_age("\u{1F8C0}".."\u{1F8C1}",
+ "LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW")
+ assert_unicode_age("\u{1FA89}",
+ "HARP")
+ assert_unicode_age("\u{1FA8F}",
+ "SHOVEL")
+ assert_unicode_age("\u{1FABE}",
+ "LEAFLESS TREE")
+ assert_unicode_age("\u{1FAC6}",
+ "FINGERPRINT")
+ assert_unicode_age("\u{1FADC}",
+ "ROOT VEGETABLE")
+ assert_unicode_age("\u{1FADF}",
+ "SPLATTER")
+ assert_unicode_age("\u{1FAE9}",
+ "FACE WITH BAGS UNDER EYES")
+ assert_unicode_age("\u{1FBCB}".."\u{1FBEF}",
+ "WHITE CROSS MARK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE")
+ end
+
UnicodeAgeRegexps = Hash.new do |h, age|
h[age] = [/\A\p{age=#{age}}+\z/u, /\A\P{age=#{age}}+\z/u].freeze
end
@@ -1538,6 +1681,65 @@ class TestRegexp < Test::Unit::TestCase
assert_equal("hoge fuga", h["body"])
end
+ def test_matchdata_large_capture_groups_stack
+ env = {"RUBY_THREAD_MACHINE_STACK_SIZE" => (256 * 1024).to_s}
+ assert_separately([env], <<~'RUBY')
+ n = 20000
+ require "rbconfig/sizeof"
+ stack = RubyVM::DEFAULT_PARAMS[:thread_machine_stack_size]
+ size = RbConfig::SIZEOF["long"]
+ required = (n + 1) * 4 * size
+ if !stack || stack == 0 || stack >= required
+ omit "thread machine stack size not reduced (#{stack}:#{required})"
+ end
+
+ inspect = Thread.new do
+ str = "\u{3042}" * n
+ m = Regexp.new("(.)" * n).match(str)
+ assert_not_nil(m)
+ assert_equal([n - 1, n], m.offset(n))
+ m.inspect
+ end.value
+
+ assert_include(inspect, "MatchData")
+ RUBY
+ end
+
+ def test_match_integer_at
+ m = /(\d{4})(\d{2})(\d{2})/.match("20260308")
+ assert_equal(20260308, m.integer_at(0))
+ assert_equal(2026, m.integer_at(1))
+ assert_equal(3, m.integer_at(2))
+ assert_equal(8, m.integer_at(3))
+ assert_equal(nil, m.integer_at(4))
+ assert_equal(8, m.integer_at(-1))
+ assert_equal(3, m.integer_at(-2))
+ assert_equal(2026, m.integer_at(-3))
+ assert_equal(nil, m.integer_at(-4))
+
+ re = /[a-z]+|(\d+)/
+ assert_equal(123, re.match("123").integer_at(1))
+ assert_equal(nil, re.match("abc").integer_at(1))
+ end
+
+ def test_match_integer_at_name
+ m = /(?<y>\d{4})(?<m>\d{2})(?<d>\d{2})/.match("20260308")
+ assert_equal(2026, m.integer_at("y"))
+ assert_equal(3, m.integer_at("m"))
+ assert_equal(8, m.integer_at("d"))
+ end
+
+ def test_match_integer_at_base
+ assert_equal(91, /\w+/.match("111").integer_at(0, 9))
+ assert_equal(10_0000, /\w+/.match("10_0000").integer_at(0))
+ assert_equal(0d1_0000, /\w+/.match("01_0000").integer_at(0))
+ assert_equal(0o1_0000, /\w+/.match("01_0000").integer_at(0, 0))
+ assert_equal(0b1_0000, /\w+/.match("0b1_0000").integer_at(0, 0))
+ assert_equal(0o1_0000, /\w+/.match("0o1_0000").integer_at(0, 0))
+ assert_equal(0d1_0000, /\w+/.match("0d1_0000").integer_at(0, 0))
+ assert_equal(0x1_0000, /\w+/.match("0x1_0000").integer_at(0, 0))
+ end
+
def test_regexp_popped
EnvUtil.suppress_warning do
assert_nothing_raised { eval("a = 1; /\#{ a }/; a") }
@@ -1612,6 +1814,33 @@ class TestRegexp < Test::Unit::TestCase
assert_raise(RegexpError, bug12418){ Regexp.new('(0?0|(?(5)||)|(?(5)||))?') }
end
+ def test_quick_search
+ assert_match_at('(?i) *TOOKY', 'Mozilla/5.0 (Linux; Android 4.0.3; TOOKY', [[34, 40]]) # Issue #120
+ end
+
+ def test_ss_in_look_behind
+ assert_match_at("(?i:ss)", "ss", [[0, 2]])
+ assert_match_at("(?i:ss)", "Ss", [[0, 2]])
+ assert_match_at("(?i:ss)", "SS", [[0, 2]])
+ assert_match_at("(?i:ss)", "\u017fS", [[0, 2]]) # LATIN SMALL LETTER LONG S
+ assert_match_at("(?i:ss)", "s\u017f", [[0, 2]])
+ assert_match_at("(?i:ss)", "\u00df", [[0, 1]]) # LATIN SMALL LETTER SHARP S
+ assert_match_at("(?i:ss)", "\u1e9e", [[0, 1]]) # LATIN CAPITAL LETTER SHARP S
+ assert_match_at("(?i:xssy)", "xssy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "xSsy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "xSSy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "x\u017fSy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "xs\u017fy", [[0, 4]])
+ assert_match_at("(?i:xssy)", "x\u00dfy", [[0, 3]])
+ assert_match_at("(?i:xssy)", "x\u1e9ey", [[0, 3]])
+ assert_match_at("(?i:\u00df)", "ss", [[0, 2]])
+ assert_match_at("(?i:\u00df)", "SS", [[0, 2]])
+ assert_match_at("(?i:[\u00df])", "ss", [[0, 2]])
+ assert_match_at("(?i:[\u00df])", "SS", [[0, 2]])
+ assert_match_at("(?i)(?<!ss)\u2728", "qq\u2728", [[2, 3]]) # Issue #92
+ assert_match_at("(?i)(?<!xss)\u2728", "qq\u2728", [[2, 3]])
+ end
+
def test_options_in_look_behind
assert_nothing_raised {
assert_match_at("(?<=(?i)ab)cd", "ABcd", [[2,4]])
@@ -1749,6 +1978,12 @@ class TestRegexp < Test::Unit::TestCase
end;
end
+ def test_too_big_number_for_repeat_range
+ assert_raise_with_message(SyntaxError, /too big number for repeat range/) do
+ eval(%[/|{1000000}/])
+ end
+ end
+
# This assertion is for porting x2() tests in testpy.py of Onigmo.
def assert_match_at(re, str, positions, msg = nil)
re = Regexp.new(re) unless re.is_a?(Regexp)
@@ -1812,6 +2047,7 @@ class TestRegexp < Test::Unit::TestCase
Regexp.timeout = 1e300
assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
+ assert_raise(ArgumentError) { Regexp.timeout = Float::NAN }
assert_raise(ArgumentError) { Regexp.timeout = 0 }
assert_raise(ArgumentError) { Regexp.timeout = -1 }
@@ -1853,7 +2089,7 @@ class TestRegexp < Test::Unit::TestCase
end
def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout)
- assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 60)
global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect }
per_instance_timeout = #{ (per_instance_timeout ? EnvUtil.apply_timeout_scale(per_instance_timeout) : nil).inspect }
expected_timeout = #{ EnvUtil.apply_timeout_scale(expected_timeout).inspect }
@@ -1904,6 +2140,7 @@ class TestRegexp < Test::Unit::TestCase
assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout)
+ assert_raise(ArgumentError) { Regexp.new("foo", timeout: Float::NAN) }
assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) }
assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) }
end;
@@ -1971,7 +2208,7 @@ class TestRegexp < Test::Unit::TestCase
end
def test_match_cache_positive_look_ahead_complex
- assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 30)
timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
begin;
Regexp.timeout = timeout
@@ -2114,4 +2351,29 @@ class TestRegexp < Test::Unit::TestCase
re =~ s
end
end
+
+ def test_bug_16145_and_bug_21176_caseinsensitive_small # [Bug#16145] [Bug#21176]
+ encodings = [Encoding::UTF_8, Encoding::ISO_8859_1]
+ encodings.each do |enc|
+ o_acute_lower = "\u00F3".encode(enc)
+ o_acute_upper = "\u00D3".encode(enc)
+ assert_match(/[x#{o_acute_lower}]/i, "abc#{o_acute_upper}", "should match o acute case insensitive")
+
+ e_acute_lower = "\u00E9".encode(enc)
+ e_acute_upper = "\u00C9".encode(enc)
+ assert_match(/[x#{e_acute_lower}]/i, "CAF#{e_acute_upper}", "should match e acute case insensitive")
+ end
+ end
+
+ def test_too_many_range_repeat
+ source = '(?:foobar){0,100}' * 100000
+ assert_raise(RegexpError) { Regexp.new(source) }
+ assert_raise(SyntaxError) { eval("/#{source}/") }
+ end
+
+ def test_too_many_null_check
+ source = '(?:(?:foo)?|(?:bar)?)*' * 100000
+ assert_raise(RegexpError) { Regexp.new(source) }
+ assert_raise(SyntaxError) { eval("/#{source}/") }
+ end
end
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 13e7076391..eed8e97da8 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -54,7 +54,7 @@ class TestRequire < Test::Unit::TestCase
end;
begin
- assert_in_out_err(["-S", "-w", "foo/" * 1024 + "foo"], "") do |r, e|
+ assert_in_out_err(["-S", "-w", (["foo"] * 1025).join("_")], "") do |r, e|
assert_equal([], r)
assert_operator(2, :<=, e.size)
assert_match(/warning: openpath: pathname too long \(ignored\)/, e.first)
@@ -840,6 +840,36 @@ class TestRequire < Test::Unit::TestCase
p :ok
end;
}
+
+ # [Bug #21567]
+ assert_ruby_status(%w[-rtempfile], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
+ class MyString
+ def initialize(path)
+ @path = path
+ end
+
+ def to_str
+ $LOADED_FEATURES.clear
+ @path
+ end
+
+ def to_path = @path
+ end
+
+ FILES = []
+
+ def create_ruby_file
+ file = Tempfile.open(["test", ".rb"])
+ FILES << file
+ file.path
+ end
+
+ require MyString.new(create_ruby_file)
+ $LOADED_FEATURES.unshift(create_ruby_file)
+ $LOADED_FEATURES << MyString.new(create_ruby_file)
+ require create_ruby_file
+ end;
end
def test_loading_fifo_threading_raise
@@ -999,7 +1029,7 @@ class TestRequire < Test::Unit::TestCase
def test_require_with_public_method_missing
# [Bug #19793]
- assert_separately(["-W0", "-rtempfile"], __FILE__, __LINE__, <<~RUBY, timeout: 60)
+ assert_ruby_status(["-W0", "-rtempfile"], <<~RUBY, timeout: 60)
GC.stress = true
class Object
@@ -1011,4 +1041,18 @@ class TestRequire < Test::Unit::TestCase
end
RUBY
end
+
+ def test_bug_21568
+ load_path = $LOAD_PATH.dup
+ loaded_featrures = $LOADED_FEATURES.dup
+
+ $LOAD_PATH.clear
+ $LOADED_FEATURES.replace(["foo.so", "a/foo.rb", "b/foo.rb"])
+
+ assert_nothing_raised(LoadError) { require "foo" }
+
+ ensure
+ $LOAD_PATH.replace(load_path) if load_path
+ $LOADED_FEATURES.replace loaded_featrures
+ end
end
diff --git a/test/ruby/test_require_lib.rb b/test/ruby/test_require_lib.rb
index a88279727e..44dfbcf9ec 100644
--- a/test/ruby/test_require_lib.rb
+++ b/test/ruby/test_require_lib.rb
@@ -13,11 +13,11 @@ class TestRequireLib < Test::Unit::TestCase
scripts.concat(Dir.glob(dirs.map {|d| d + '/*.rb'}, base: libdir).map {|f| f.chomp('.rb')})
# skip some problems
- scripts -= %w[bundler bundled_gems rubygems mkmf]
+ scripts -= %w[bundler bundled_gems rubygems mkmf set/sorted_set]
scripts.each do |lib|
define_method "test_thread_size:#{lib}" do
- assert_separately(['-W0'], "#{<<~"begin;"}\n#{<<~"end;"}")
+ assert_separately(['-W0'], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 60)
begin;
n = Thread.list.size
require #{lib.dump}
diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb
index dbaf074db9..4a31f91b4a 100644
--- a/test/ruby/test_rubyoptions.rb
+++ b/test/ruby/test_rubyoptions.rb
@@ -8,9 +8,6 @@ require_relative '../lib/jit_support'
require_relative '../lib/parser_support'
class TestRubyOptions < Test::Unit::TestCase
- def self.rjit_enabled? = defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
- def self.yjit_enabled? = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
-
# Here we're defining our own RUBY_DESCRIPTION without "+PRISM". We do this
# here so that the various tests that reference RUBY_DESCRIPTION don't have to
# worry about it. The flag itself is tested in its own test.
@@ -22,10 +19,11 @@ class TestRubyOptions < Test::Unit::TestCase
end
NO_JIT_DESCRIPTION =
- if rjit_enabled?
- RUBY_DESCRIPTION.sub(/\+RJIT /, '')
- elsif yjit_enabled?
- RUBY_DESCRIPTION.sub(/\+YJIT( (dev|dev_nodebug|stats))? /, '')
+ case
+ when JITSupport.yjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+YJIT( \w+)? /, '')
+ when JITSupport.zjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+ZJIT( \w+)? /, '')
else
RUBY_DESCRIPTION
end
@@ -49,10 +47,15 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err([], "", [], [])
end
+ # This constant enforces the traditional 80x25 terminal size standard
+ TRADITIONAL_TERM_COLS = 80 # DO NOT MODIFY!
+ TRADITIONAL_TERM_ROWS = 25 # DO NOT MODIFY!
+
def test_usage
+ # This test checks if the output of `ruby -h` fits in 80x25
assert_in_out_err(%w(-h)) do |r, e|
- assert_operator(r.size, :<=, 25)
- longer = r[1..-1].select {|x| x.size >= 80}
+ assert_operator(r.size, :<=, TRADITIONAL_TERM_ROWS)
+ longer = r[1..-1].select {|x| x.size >= TRADITIONAL_TERM_COLS}
assert_equal([], longer)
assert_equal([], e)
end
@@ -173,21 +176,10 @@ class TestRubyOptions < Test::Unit::TestCase
end
private_constant :VERSION_PATTERN
- VERSION_PATTERN_WITH_RJIT =
- case RUBY_ENGINE
- when 'ruby'
- /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? \+RJIT (\+MN )?(\+PRISM )?(\+GC)?(\[\w+\]\s|\s)?\[#{q[RUBY_PLATFORM]}\]$/
- else
- VERSION_PATTERN
- end
- private_constant :VERSION_PATTERN_WITH_RJIT
-
def test_verbose
assert_in_out_err([{'RUBY_YJIT_ENABLE' => nil}, "-vve", ""]) do |r, e|
assert_match(VERSION_PATTERN, r[0])
- if self.class.rjit_enabled? && !JITSupport.rjit_force_enabled?
- assert_equal(NO_JIT_DESCRIPTION, r[0])
- elsif self.class.yjit_enabled? && !JITSupport.yjit_force_enabled?
+ if (JITSupport.yjit_enabled? && !JITSupport.yjit_force_enabled?) || JITSupport.zjit_enabled?
assert_equal(NO_JIT_DESCRIPTION, r[0])
else
assert_equal(RUBY_DESCRIPTION, r[0])
@@ -212,15 +204,12 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(--enable all -e) + [""], "", [], [])
assert_in_out_err(%w(--enable-all -e) + [""], "", [], [])
assert_in_out_err(%w(--enable=all -e) + [""], "", [], [])
- elsif JITSupport.rjit_supported?
- # Avoid failing tests by RJIT warnings
- assert_in_out_err(%w(--enable all --disable rjit -e) + [""], "", [], [])
- assert_in_out_err(%w(--enable-all --disable-rjit -e) + [""], "", [], [])
- assert_in_out_err(%w(--enable=all --disable=rjit -e) + [""], "", [], [])
end
assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [],
/unknown argument for --enable: 'foobarbazqux'/)
assert_in_out_err(%w(--enable), "", [], /missing argument for --enable/)
+ assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: true)
+ assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: nil)
end
def test_disable
@@ -230,7 +219,7 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(--disable foobarbazqux -e) + [""], "", [],
/unknown argument for --disable: 'foobarbazqux'/)
assert_in_out_err(%w(--disable), "", [], /missing argument for --disable/)
- assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], [])
+ assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], [], gems: false)
assert_in_out_err(%w(--disable-did_you_mean -e) + ['p defined? DidYouMean'], "", ["nil"], [])
assert_in_out_err(%w(-e) + ['p defined? DidYouMean'], "", ["nil"], [])
end
@@ -258,7 +247,7 @@ class TestRubyOptions < Test::Unit::TestCase
assert_match(VERSION_PATTERN, r[0])
if ENV['RUBY_YJIT_ENABLE'] == '1'
assert_equal(NO_JIT_DESCRIPTION, r[0])
- elsif self.class.rjit_enabled? || self.class.yjit_enabled? # checking -D(M|Y)JIT_FORCE_ENABLE
+ elsif JITSupport.yjit_enabled? || JITSupport.zjit_enabled? # checking -DYJIT_FORCE_ENABLE
assert_equal(EnvUtil.invoke_ruby(['-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
else
assert_equal(RUBY_DESCRIPTION, r[0])
@@ -267,46 +256,6 @@ class TestRubyOptions < Test::Unit::TestCase
end
end
- def test_rjit_disabled_version
- return unless JITSupport.rjit_supported?
- return if JITSupport.yjit_force_enabled?
-
- env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children
- [
- %w(--version --rjit --disable=rjit),
- %w(--version --enable=rjit --disable=rjit),
- %w(--version --enable-rjit --disable-rjit),
- ].each do |args|
- assert_in_out_err([env] + args) do |r, e|
- assert_match(VERSION_PATTERN, r[0])
- assert_match(NO_JIT_DESCRIPTION, r[0])
- assert_equal([], e)
- end
- end
- end
-
- def test_rjit_version
- return unless JITSupport.rjit_supported?
- return if JITSupport.yjit_force_enabled?
-
- env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children
- [
- %w(--version --rjit),
- %w(--version --enable=rjit),
- %w(--version --enable-rjit),
- ].each do |args|
- assert_in_out_err([env] + args) do |r, e|
- assert_match(VERSION_PATTERN_WITH_RJIT, r[0])
- if JITSupport.rjit_force_enabled?
- assert_equal(RUBY_DESCRIPTION, r[0])
- else
- assert_equal(EnvUtil.invoke_ruby([env, '--rjit', '-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
- end
- assert_equal([], e)
- end
- end
- end
-
def test_enabled_gc
omit unless /linux|darwin/ =~ RUBY_PLATFORM
@@ -318,6 +267,8 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_parser_flag
+ omit if ENV["RUBYOPT"]&.include?("--parser=")
+
assert_in_out_err(%w(--parser=prism -e) + ["puts :hi"], "", %w(hi), [])
assert_in_out_err(%w(--parser=prism --dump=parsetree -e _=:hi), "", /"hi"/, [])
@@ -495,37 +446,28 @@ class TestRubyOptions < Test::Unit::TestCase
def test_search
rubypath_orig = ENV['RUBYPATH']
path_orig = ENV['PATH']
+ libpath = (path_orig if path_orig && RbConfig::CONFIG['LIBPATHENV'] == 'PATH')
- Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t|
- t.puts "p 1"
- t.close
+ Dir.mktmpdir("test_ruby_test_rubyoption") do |path|
+ name = "test_rubyoption.rb"
+ parent, dir = File.split(path)
+ File.write("#{path}/#{name}", "p 1")
+ load_error = %r[#{Regexp.quote dir}/#{Regexp.quote name} \(LoadError\)]
- @verbose = $VERBOSE
- $VERBOSE = nil
-
- path, name = File.split(t.path)
-
- ENV['PATH'] = (path_orig && RbConfig::CONFIG['LIBPATHENV'] == 'PATH') ?
- [path, path_orig].join(File::PATH_SEPARATOR) : path
+ ENV['PATH'] = [path, *libpath].join(File::PATH_SEPARATOR)
assert_in_out_err(%w(-S) + [name], "", %w(1), [])
+ ENV['PATH'] = [parent, *libpath].join(File::PATH_SEPARATOR)
+ assert_in_out_err(%W(-S) + ["#{dir}/#{name}"], "", [], load_error)
ENV['PATH'] = path_orig
ENV['RUBYPATH'] = path
assert_in_out_err(%w(-S) + [name], "", %w(1), [])
- }
-
- ensure
- if rubypath_orig
+ ENV['RUBYPATH'] = parent
+ assert_in_out_err(%w(-S) + ["#{dir}/#{name}"], "", [], load_error)
+ ensure
ENV['RUBYPATH'] = rubypath_orig
- else
- ENV.delete('RUBYPATH')
- end
- if path_orig
ENV['PATH'] = path_orig
- else
- ENV.delete('PATH')
end
- $VERBOSE = @verbose
end
def test_shebang
@@ -577,6 +519,8 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%w(- -#=foo), "#!ruby -s\n", [],
/invalid name for global variable - -# \(NameError\)/)
+
+ assert_in_out_err(['-s', '-e', 'GC.start; p $DEBUG', '--', '-DEBUG=x'], "", ['"x"'])
end
def test_option_missing_argument
@@ -843,14 +787,22 @@ class TestRubyOptions < Test::Unit::TestCase
unless /mswin|mingw/ =~ RUBY_PLATFORM
opts[:rlimit_core] = 0
end
+ opts[:failed] = proc do |status, message = "", out = ""|
+ if (sig = status.termsig) && Signal.list["SEGV"] == sig
+ out = ""
+ end
+ Test::Unit::CoreAssertions::FailDesc[status, message]
+ end
ExecOptions = opts.freeze
+ # The regexp list that should match the entire stderr output.
+ # see assert_pattern_list
ExpectedStderrList = [
%r(
- -e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n
+ (?:-e:(?:1:)?\s)?\[BUG\]\sSegmentation\sfault.*\n
)x,
%r(
- #{ Regexp.quote((TestRubyOptions.rjit_enabled? && !JITSupport.rjit_force_enabled?) ? NO_JIT_DESCRIPTION : RUBY_DESCRIPTION) }\n\n
+ #{ Regexp.quote(RUBY_DESCRIPTION) }\n\n
)x,
%r(
(?:--\s(?:.+\n)*\n)?
@@ -890,20 +842,24 @@ class TestRubyOptions < Test::Unit::TestCase
end
def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &block)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
# We want YJIT to be enabled in the subprocess if it's enabled for us
# so that the Ruby description matches.
env = Hash === args.first ? args.shift : {}
- args.unshift("--yjit") if self.class.yjit_enabled?
+ args.unshift("--yjit") if JITSupport.yjit_enabled?
+ args.unshift("--zjit") if JITSupport.zjit_enabled?
env.update({'RUBY_ON_BUG' => nil})
+ env['RUBY_CRASH_REPORT'] ||= nil # default to not passing down parent setting
# ASAN registers a segv handler which prints out "AddressSanitizer: DEADLYSIGNAL" when
# catching sigsegv; we don't expect that output, so suppress it.
- env.update({'ASAN_OPTIONS' => 'handle_segv=0'})
+ env.update({'ASAN_OPTIONS' => 'handle_segv=0', 'LSAN_OPTIONS' => 'handle_segv=0'})
args.unshift(env)
test_stdin = ""
- tests = [//, list] unless block
+ if !block
+ tests = [//, list, message]
+ elsif message
+ tests = [[], [], message]
+ end
assert_in_out_err(args, test_stdin, *tests, encoding: "ASCII-8BIT",
**SEGVTest::ExecOptions, **opt, &block)
@@ -916,13 +872,12 @@ class TestRubyOptions < Test::Unit::TestCase
def test_segv_loaded_features
bug7402 = '[ruby-core:49573]'
- status = assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}",
- '-e', 'class Bogus; def to_str; exit true; end; end',
- '-e', '$".clear',
- '-e', '$".unshift Bogus.new',
- '-e', '(p $"; abort) unless $".size == 1',
- ])
- assert_not_predicate(status, :success?, "segv but success #{bug7402}")
+ assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}",
+ '-e', 'class Bogus; def to_str; exit true; end; end',
+ '-e', '$".clear',
+ '-e', '$".unshift Bogus.new',
+ '-e', '(p $"; abort) unless $".size == 1',
+ ], bug7402, success: false)
end
def test_segv_setproctitle
@@ -935,8 +890,6 @@ class TestRubyOptions < Test::Unit::TestCase
end
def assert_crash_report(path, cmd = nil, &block)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
Dir.mktmpdir("ruby_crash_report") do |dir|
list = SEGVTest::ExpectedStderrList
if cmd
@@ -990,6 +943,27 @@ class TestRubyOptions < Test::Unit::TestCase
end
end
+ def test_crash_report_pipe_script
+ omit "only runs on Linux" unless RUBY_PLATFORM.include?("linux")
+
+ Tempfile.create(["script", ".sh"]) do |script|
+ Tempfile.create("crash_report") do |crash_report|
+ script.write(<<~BASH)
+ #!/usr/bin/env bash
+
+ cat > #{crash_report.path}
+ BASH
+ script.close
+
+ FileUtils.chmod("+x", script)
+
+ assert_crash_report("| #{script.path}") do
+ assert_include(File.read(crash_report.path), "[BUG] Segmentation fault at")
+ end
+ end
+ end
+ end
+
def test_DATA
Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t|
t.puts "puts DATA.read.inspect"
@@ -1036,7 +1010,7 @@ class TestRubyOptions < Test::Unit::TestCase
pid = spawn(EnvUtil.rubybin, :in => s, :out => w)
w.close
assert_nothing_raised('[ruby-dev:37798]') do
- result = EnvUtil.timeout(3) {r.read}
+ result = EnvUtil.timeout(10) {r.read}
end
Process.wait pid
}
@@ -1334,4 +1308,10 @@ class TestRubyOptions < Test::Unit::TestCase
def test_toplevel_ruby
assert_instance_of Module, ::Ruby
end
+
+ def test_ruby_patchlevel
+ # We stopped bumping RUBY_PATCHLEVEL at Ruby 4.0.0.
+ # Released versions have RUBY_PATCHLEVEL 0, and un-released versions have -1.
+ assert_include [-1, 0], RUBY_PATCHLEVEL
+ end
end
diff --git a/test/set/test_set.rb b/test/ruby/test_set.rb
index 565946096e..427dd4b6b0 100644
--- a/test/set/test_set.rb
+++ b/test/ruby/test_set.rb
@@ -3,7 +3,36 @@ require 'test/unit'
require 'set'
class TC_Set < Test::Unit::TestCase
- class Set2 < Set
+ class SetSubclass < Set
+ end
+ class CoreSetSubclass < Set::CoreSet
+ end
+ ALL_SET_CLASSES = [Set, SetSubclass, CoreSetSubclass].freeze
+
+ def test_marshal
+ set = Set[1, 2, 3]
+ mset = Marshal.load(Marshal.dump(set))
+ assert_equal(set, mset)
+ assert_equal(set.compare_by_identity?, mset.compare_by_identity?)
+
+ set.compare_by_identity
+ mset = Marshal.load(Marshal.dump(set))
+ assert_equal(set, mset)
+ assert_equal(set.compare_by_identity?, mset.compare_by_identity?)
+
+ set.instance_variable_set(:@a, 1)
+ mset = Marshal.load(Marshal.dump(set))
+ assert_equal(set, mset)
+ assert_equal(set.compare_by_identity?, mset.compare_by_identity?)
+ assert_equal(1, mset.instance_variable_get(:@a))
+
+ old_stdlib_set_data = "\x04\bo:\bSet\x06:\n@hash}\bi\x06Ti\aTi\bTF".b
+ set = Marshal.load(old_stdlib_set_data)
+ assert_equal(Set[1, 2, 3], set)
+
+ old_stdlib_set_cbi_data = "\x04\bo:\bSet\x06:\n@hashC:\tHash}\ai\x06Ti\aTF".b
+ set = Marshal.load(old_stdlib_set_cbi_data)
+ assert_equal(Set[1, 2].compare_by_identity, set)
end
def test_aref
@@ -104,6 +133,12 @@ class TC_Set < Test::Unit::TestCase
assert_equal(Set['a','b','c'], set)
set = Set[1,2]
+ ret = set.replace(Set.new('a'..'c'))
+
+ assert_same(set, ret)
+ assert_equal(Set['a','b','c'], set)
+
+ set = Set[1,2]
assert_raise(ArgumentError) {
set.replace(3)
}
@@ -232,7 +267,7 @@ class TC_Set < Test::Unit::TestCase
set.superset?([2])
}
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(true, set.superset?(klass[]), klass.name)
assert_equal(true, set.superset?(klass[1,2]), klass.name)
assert_equal(true, set.superset?(klass[1,2,3]), klass.name)
@@ -261,7 +296,7 @@ class TC_Set < Test::Unit::TestCase
set.proper_superset?([2])
}
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(true, set.proper_superset?(klass[]), klass.name)
assert_equal(true, set.proper_superset?(klass[1,2]), klass.name)
assert_equal(false, set.proper_superset?(klass[1,2,3]), klass.name)
@@ -290,7 +325,7 @@ class TC_Set < Test::Unit::TestCase
set.subset?([2])
}
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(true, set.subset?(klass[1,2,3,4]), klass.name)
assert_equal(true, set.subset?(klass[1,2,3]), klass.name)
assert_equal(false, set.subset?(klass[1,2]), klass.name)
@@ -319,7 +354,7 @@ class TC_Set < Test::Unit::TestCase
set.proper_subset?([2])
}
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(true, set.proper_subset?(klass[1,2,3,4]), klass.name)
assert_equal(false, set.proper_subset?(klass[1,2,3]), klass.name)
assert_equal(false, set.proper_subset?(klass[1,2]), klass.name)
@@ -339,7 +374,7 @@ class TC_Set < Test::Unit::TestCase
assert_nil(set <=> set.to_a)
- [Set, Set2].each { |klass|
+ ALL_SET_CLASSES.each { |klass|
assert_equal(-1, set <=> klass[1,2,3,4], klass.name)
assert_equal( 0, set <=> klass[3,2,1] , klass.name)
assert_equal(nil, set <=> klass[1,2,4] , klass.name)
@@ -606,6 +641,22 @@ class TC_Set < Test::Unit::TestCase
}
end
+ def test_merge_mutating_hash_bug_21305
+ a = (1..100).to_a
+ o = Object.new
+ o.define_singleton_method(:hash) do
+ a.clear
+ 0
+ end
+ a.unshift o
+ assert_equal([o], Set.new.merge(a).to_a)
+ end
+
+ def test_initialize_mutating_array_bug_21306
+ a = (1..100).to_a
+ assert_equal(Set[0], Set.new(a){a.clear; 0})
+ end
+
def test_subtract
set = Set[1,2,3]
@@ -639,15 +690,28 @@ class TC_Set < Test::Unit::TestCase
end
def test_xor
- set = Set[1,2,3,4]
- ret = set ^ [2,4,5,5]
- assert_not_same(set, ret)
- assert_equal(Set[1,3,5], ret)
+ ALL_SET_CLASSES.each { |klass|
+ set = klass[1,2,3,4]
+ ret = set ^ [2,4,5,5]
+ assert_not_same(set, ret)
+ assert_equal(klass[1,3,5], ret)
+
+ set2 = klass[1,2,3,4]
+ ret2 = set2 ^ [2,4,5,5]
+ assert_instance_of(klass, ret2)
+ assert_equal(klass[1,3,5], ret2)
+ }
+ end
+
+ def test_xor_does_not_mutate_other_set
+ a = Set[1]
+ b = Set[1, 2]
+ original_b = b.dup
- set2 = Set2[1,2,3,4]
- ret2 = set2 ^ [2,4,5,5]
- assert_instance_of(Set2, ret2)
- assert_equal(Set2[1,3,5], ret2)
+ result = a ^ b
+
+ assert_equal(original_b, b)
+ assert_equal(Set[2], result)
end
def test_eq
@@ -733,6 +797,10 @@ class TC_Set < Test::Unit::TestCase
ret.each { |s| n += s.size }
assert_equal(set.size, n)
assert_equal(set, ret.flatten)
+
+ set = Set[2,12,9,11,13,4,10,15,3,8,5,0,1,7,14]
+ ret = set.divide { |a,b| (a - b).abs == 1 }
+ assert_equal(2, ret.size)
end
def test_freeze
@@ -787,24 +855,32 @@ class TC_Set < Test::Unit::TestCase
def test_inspect
set1 = Set[1, 2]
- assert_equal('#<Set: {1, 2}>', set1.inspect)
+ assert_equal('Set[1, 2]', set1.inspect)
set2 = Set[Set[0], 1, 2, set1]
- assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2}>}>', set2.inspect)
+ assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.inspect)
set1.add(set2)
- assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2, #<Set: {...}>}>}>', set2.inspect)
+ assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.inspect)
+
+ c = Class.new(Set::CoreSet)
+ c.set_temporary_name("_MySet")
+ assert_equal('_MySet[1, 2]', c[1, 2].inspect)
+
+ c = Class.new(Set)
+ c.set_temporary_name("_MySet")
+ assert_equal('#<_MySet: {1, 2}>', c[1, 2].inspect)
end
def test_to_s
set1 = Set[1, 2]
- assert_equal('#<Set: {1, 2}>', set1.to_s)
+ assert_equal('Set[1, 2]', set1.to_s)
set2 = Set[Set[0], 1, 2, set1]
- assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2}>}>', set2.to_s)
+ assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.to_s)
set1.add(set2)
- assert_equal('#<Set: {#<Set: {0}>, 1, 2, #<Set: {1, 2, #<Set: {...}>}>}>', set2.to_s)
+ assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.to_s)
end
def test_compare_by_identity
@@ -826,6 +902,25 @@ class TC_Set < Test::Unit::TestCase
assert_equal(array.uniq.sort, set.sort)
end
+ def test_compare_by_identity_compact
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ # [Bug #22064]
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ set = Set.new.compare_by_identity
+
+ o = Object.new
+ set.add(o)
+
+ assert_include(set, o)
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_include(set, o)
+ end;
+ end
+
def test_reset
[Set, Class.new(Set)].each { |klass|
a = [1, 2]
@@ -838,6 +933,55 @@ class TC_Set < Test::Unit::TestCase
assert_equal(klass.new([a]), set, klass.name)
}
end
+
+ def test_set_gc_compact_does_not_allocate
+ assert_in_out_err([], <<-"end;", [], [])
+ def x
+ s = Set.new
+ s << Object.new
+ s
+ end
+
+ x
+ begin
+ GC.compact
+ rescue NotImplementedError
+ end
+ end;
+ end
+
+ def test_larger_sets
+ set = Set.new
+ 10_000.times do |i|
+ set << i
+ end
+ set = set.dup
+
+ 10_000.times do |i|
+ assert_includes set, i
+ end
+ end
+
+ def test_subclass_new_calls_add
+ c = Class.new(Set) do
+ def add(o)
+ super
+ super(o+1)
+ end
+ end
+ assert_equal([1, 2], c.new([1]).to_a)
+ end
+
+ def test_subclass_aref_calls_initialize
+ c = Class.new(Set) do
+ def initialize(enum)
+ super
+ add(1)
+ end
+ end
+ assert_equal([2, 1], c[2].to_a)
+ end
+
end
class TC_Enumerable < Test::Unit::TestCase
@@ -853,7 +997,38 @@ class TC_Enumerable < Test::Unit::TestCase
assert_equal([-10,-8,-6,-4,-2], set.sort)
assert_same set, set.to_set
- assert_not_same set, set.to_set { |o| o }
+ transformed = set.to_set { |o| o + 1 }
+ assert_equal([-9,-7,-5,-3,-1], transformed.sort)
+ end
+
+ class MyEnum
+ include Enumerable
+
+ def initialize(array)
+ @array = array
+ end
+
+ def each(&block)
+ @array.each(&block)
+ end
+
+ def size
+ raise "should not be called"
+ end
+ end
+
+ def test_to_set_not_calling_size
+ enum = MyEnum.new([1,2,3])
+
+ set = assert_nothing_raised { enum.to_set }
+ assert(set.is_a?(Set))
+ assert_equal(Set[1,2,3], set)
+
+ enumerator = enum.to_enum
+
+ set = assert_nothing_raised { enumerator.to_set }
+ assert(set.is_a?(Set))
+ assert_equal(Set[1,2,3], set)
end
end
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb
index 37358757a6..8b0e08fc97 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -845,6 +845,9 @@ CODE
args = nil
trace = TracePoint.trace(:call){|tp|
next if !target_thread?
+ # In parallel testing, unexpected events like IO operations may be traced,
+ # so we filter out events here.
+ next unless [TracePoint, TestSetTraceFunc].include?(tp.defined_class)
ary << tp.method_id
}
foo
@@ -1996,7 +1999,7 @@ CODE
TracePoint.new(:c_call, &capture_events).enable{
c.new
}
- assert_equal [:c_call, :itself, :initialize], events[1]
+ assert_equal [:c_call, :itself, :initialize], events[0]
events.clear
o = Class.new{
@@ -2223,7 +2226,7 @@ CODE
def test_thread_add_trace_func
events = []
base_line = __LINE__
- q = Thread::Queue.new
+ q = []
t = Thread.new{
Thread.current.add_trace_func proc{|ev, file, line, *args|
events << [ev, line] if file == __FILE__
@@ -2262,9 +2265,6 @@ CODE
}
# it is dirty hack. usually we shouldn't use such technique
Thread.pass until t.status == 'sleep'
- # When RJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop.
- # This sleep forces it to reach m2t_q.pop for --jit-wait.
- sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
t.add_trace_func proc{|ev, file, line, *args|
if file == __FILE__
@@ -2583,6 +2583,7 @@ CODE
def test_enable_target_thread
events = []
TracePoint.new(:line) do |tp|
+ next unless tp.path == __FILE__
events << Thread.current
end.enable(target_thread: Thread.current) do
_a = 1
@@ -2596,6 +2597,7 @@ CODE
events = []
tp = TracePoint.new(:line) do |tp|
+ next unless tp.path == __FILE__
events << Thread.current
end
@@ -2724,7 +2726,7 @@ CODE
end
def test_disable_local_tracepoint_in_trace
- assert_normal_exit <<-EOS
+ assert_normal_exit(<<-EOS, timeout: 60)
def foo
trace = TracePoint.new(:b_return){|tp|
tp.disable
@@ -2957,4 +2959,210 @@ CODE
assert_kind_of(Thread, target_thread)
end
+
+ def test_tracepoint_garbage_collected_when_disable
+ before_count_stat = 0
+ before_count_objspace = 0
+ TracePoint.stat.each do
+ before_count_stat += 1
+ end
+ ObjectSpace.each_object(TracePoint) do
+ before_count_objspace += 1
+ end
+ tp = TracePoint.new(:c_call, :c_return) do
+ end
+ tp.enable
+ Class.inspect # c_call, c_return invoked
+ tp.disable
+ tp_id = tp.object_id
+ tp = nil
+
+ gc_times = 0
+ gc_max_retries = 10
+ EnvUtil.suppress_warning do
+ until (ObjectSpace._id2ref(tp_id) rescue nil).nil?
+ GC.start
+ gc_times += 1
+ if gc_times == gc_max_retries
+ break
+ end
+ end
+ end
+ return if gc_times == gc_max_retries
+
+ after_count_stat = 0
+ TracePoint.stat.each do |v|
+ after_count_stat += 1
+ end
+ assert after_count_stat <= before_count_stat
+ after_count_objspace = 0
+ ObjectSpace.each_object(TracePoint) do
+ after_count_objspace += 1
+ end
+ assert after_count_objspace <= before_count_objspace
+ end
+
+ def test_tp_ractor_local_untargeted
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ r = Ractor.new do
+ results = []
+ tp = TracePoint.new(:line) { |tp| results << tp.path }
+ tp.enable
+ Ractor.main << :continue
+ Ractor.receive
+ tp.disable
+ results
+ end
+ outer_results = []
+ outer_tp = TracePoint.new(:line) { |tp| outer_results << tp.path }
+ outer_tp.enable
+ Ractor.receive
+ GC.start # so I can check <internal:gc> path
+ r << :continue
+ inner_results = r.value
+ outer_tp.disable
+ assert_equal 1, outer_results.select { |path| path.match?(/internal:gc/) }.size
+ assert_equal 0, inner_results.select { |path| path.match?(/internal:gc/) }.size
+ end;
+ end
+
+ def test_tp_targeted_ractor_local_bmethod
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ mname = :foo
+ prok = Ractor.shareable_proc do
+ end
+ klass = EnvUtil.labeled_class(:Klass) do
+ define_method(mname, &prok)
+ end
+ outer_results = 0
+ _outer_tp = TracePoint.new(:call) do
+ outer_results += 1
+ end # not enabled
+ rs = 10.times.map do
+ Ractor.new(mname, klass) do |mname, klass0|
+ inner_results = 0
+ tp = TracePoint.new(:call) { |tp| inner_results += 1 }
+ target = klass0.instance_method(mname)
+ tp.enable(target: target)
+ obj = klass0.new
+ 10.times { obj.send(mname) }
+ tp.disable
+ inner_results
+ end
+ end
+ inner_results = rs.map(&:value).sum
+ obj = klass.new
+ 10.times { obj.send(mname) }
+ assert_equal 100, inner_results
+ assert_equal 0, outer_results
+ end;
+ end
+
+ def test_tp_targeted_ractor_local_method
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ def foo
+ end
+ outer_results = 0
+ _outer_tp = TracePoint.new(:call) do
+ outer_results += 1
+ end # not enabled
+
+ rs = 10.times.map do
+ Ractor.new do
+ inner_results = 0
+ tp = TracePoint.new(:call) do
+ inner_results += 1
+ end
+ tp.enable(target: method(:foo))
+ 10.times { foo }
+ tp.disable
+ inner_results
+ end
+ end
+
+ inner_results = rs.map(&:value).sum
+ 10.times { foo }
+ assert_equal 100, inner_results
+ assert_equal 0, outer_results
+ end;
+ end
+
+ def test_tracepoints_not_disabled_by_ractor_gc
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $-w = nil # uses ObjectSpace._id2ref
+ def hi = "hi"
+ greetings = 0
+ tp_target = TracePoint.new(:call) do |tp|
+ greetings += 1
+ end
+ tp_target.enable(target: method(:hi))
+
+ raises = 0
+ tp_global = TracePoint.new(:raise) do |tp|
+ raises += 1
+ end
+ tp_global.enable
+
+ r = Ractor.new { 10 }
+ r.join
+ ractor_id = r.object_id
+ r = nil # allow gc for ractor
+ gc_max_retries = 15
+ gc_times = 0
+ # force GC of ractor (or try, because we have a conservative GC)
+ until (ObjectSpace._id2ref(ractor_id) rescue nil).nil?
+ GC.start
+ gc_times += 1
+ if gc_times == gc_max_retries
+ break
+ end
+ end
+
+ # tracepoints should still be enabled after GC of `r`
+ 5.times {
+ hi
+ }
+ 6.times {
+ raise "uh oh" rescue nil
+ }
+ tp_target.disable
+ tp_global.disable
+ assert_equal 5, greetings
+ if gc_times == gc_max_retries # _id2ref never raised
+ assert_equal 6, raises
+ else
+ assert_equal 7, raises
+ end
+ end;
+ end
+
+ def test_lots_of_enabled_tracepoints_ractor_gc
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ def foo; end
+ sum = 8.times.map do
+ Ractor.new do
+ called = 0
+ TracePoint.new(:call) do |tp|
+ next if tp.callee_id != :foo
+ called += 1
+ end.enable
+ 200.times do
+ TracePoint.new(:line) {
+ # all these allocations shouldn't GC these tracepoints while the ractor is alive.
+ Object.new
+ }.enable
+ end
+ 100.times { foo }
+ called
+ end
+ end.map(&:value).sum
+ assert_equal 800, sum
+ 4.times { GC.start } # Now the tracepoints can be GC'd because the ractors can be GC'd
+ end;
+ end
end
diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb
index 9b02504384..bace69658a 100644
--- a/test/ruby/test_shapes.rb
+++ b/test/ruby/test_shapes.rb
@@ -2,10 +2,11 @@
require 'test/unit'
require 'objspace'
require 'json'
+require 'securerandom'
# These test the functionality of object shapes
class TestShapes < Test::Unit::TestCase
- MANY_IVS = 80
+ MANY_IVS = RubyVM::Shape::SHAPE_MAX_FIELDS + 1
class IVOrder
def expected_ivs
@@ -92,15 +93,18 @@ class TestShapes < Test::Unit::TestCase
# RubyVM::Shape.of returns new instances of shape objects for
# each call. This helper method allows us to define equality for
# shapes
- def assert_shape_equal(shape1, shape2)
- assert_equal(shape1.id, shape2.id)
- assert_equal(shape1.parent_id, shape2.parent_id)
- assert_equal(shape1.depth, shape2.depth)
- assert_equal(shape1.type, shape2.type)
+ def assert_shape_equal(e, a)
+ assert_equal(
+ {id: e.offset, parent_offset: e.parent_offset, depth: e.depth, type: e.type, name: e.edge_name},
+ {id: a.offset, parent_offset: a.parent_offset, depth: a.depth, type: a.type, name: e.edge_name},
+ )
end
- def refute_shape_equal(shape1, shape2)
- refute_equal(shape1.id, shape2.id)
+ def refute_shape_equal(e, a)
+ refute_equal(
+ {id: e.offset, parent_offset: e.parent_offset, depth: e.depth, type: e.type, name: e.edge_name},
+ {id: a.offset, parent_offset: a.parent_offset, depth: a.depth, type: a.type, name: e.edge_name},
+ )
end
def test_iv_order_correct_on_complex_objects
@@ -113,12 +117,12 @@ class TestShapes < Test::Unit::TestCase
assert_equal obj.expected_ivs, iv_list.map(&:to_s)
end
- def test_too_complex
+ def test_complex
ensure_complex
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
end
def test_ordered_alloc_is_not_complex
@@ -127,6 +131,48 @@ class TestShapes < Test::Unit::TestCase
assert_operator obj["variation_count"], :<, RubyVM::Shape::SHAPE_MAX_VARIATIONS
end
+ def test_max_iv_count
+ klass = Class.new
+ object = klass.new
+
+ assert_equal 0, RubyVM::Shape.class_max_iv_count(klass)
+ 8.times do |i|
+ object.instance_variable_set("@ivar_#{i}", i)
+ end
+ assert_equal 8, RubyVM::Shape.class_max_iv_count(klass)
+
+ subklass = Class.new(klass)
+ assert_equal 8, RubyVM::Shape.class_max_iv_count(subklass)
+ end
+
+ def test_max_iv_count_on_Object
+ object = Object.new
+
+ assert_equal 0, RubyVM::Shape.class_max_iv_count(Object)
+ 8.times do |i|
+ object.instance_variable_set("@ivar_#{i}", i)
+ end
+ assert_equal 0, RubyVM::Shape.class_max_iv_count(Object)
+ end
+
+ def test_max_iv_count_on_BasicObject
+ object = BasicObject.new
+
+ assert_equal 0, RubyVM::Shape.class_max_iv_count(BasicObject)
+ 8.times do |i|
+ Object.instance_method(:instance_variable_set).bind_call(object, "@ivar_#{i}", i)
+ end
+ assert_equal 0, RubyVM::Shape.class_max_iv_count(BasicObject)
+
+ subklass = Class.new(BasicObject)
+ object = subklass.new
+ assert_equal 0, RubyVM::Shape.class_max_iv_count(subklass)
+ 8.times do |i|
+ Object.instance_method(:instance_variable_set).bind_call(object, "@ivar_#{i}", i)
+ end
+ assert_equal 8, RubyVM::Shape.class_max_iv_count(subklass)
+ end
+
def test_too_many_ivs_on_obj
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
@@ -139,18 +185,21 @@ class TestShapes < Test::Unit::TestCase
obj.instance_variable_set(:@c, 1)
obj.instance_variable_set(:@d, 1)
- assert_predicate RubyVM::Shape.of(obj), :too_complex?
+ assert_predicate RubyVM::Shape.of(obj), :complex?
end;
end
def test_too_many_ivs_on_class
obj = Class.new
- (MANY_IVS + 1).times do
+ obj.instance_variable_set(:@test_too_many_ivs_on_class, 1)
+ refute_predicate RubyVM::Shape.of(obj), :complex?
+
+ MANY_IVS.times do
obj.instance_variable_set(:"@a#{_1}", 1)
end
- assert_false RubyVM::Shape.of(obj).too_complex?
+ assert_predicate RubyVM::Shape.of(obj), :complex?
end
def test_removing_when_too_many_ivs_on_class
@@ -179,7 +228,7 @@ class TestShapes < Test::Unit::TestCase
assert_empty obj.instance_variables
end
- def test_too_complex_geniv
+ def test_complex_geniv
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
class TooComplex < Hash
@@ -221,7 +270,7 @@ class TestShapes < Test::Unit::TestCase
end
def test_run_out_of_shape_for_object
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
class A
def initialize
@@ -332,7 +381,7 @@ class TestShapes < Test::Unit::TestCase
end
def test_gc_stress_during_evacuate_generic_ivar
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
[].instance_variable_set(:@a, 1)
@@ -500,7 +549,7 @@ class TestShapes < Test::Unit::TestCase
end
def test_run_out_of_shape_rb_obj_copy_ivar
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
class A
def initialize
@@ -579,7 +628,7 @@ class TestShapes < Test::Unit::TestCase
end;
end
- def test_too_complex_ractor
+ def test_complex_ractor
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
$VERBOSE = nil
@@ -594,14 +643,14 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.instance_variable_set(:"@very_unique", 3)
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
assert_equal 3, tc.very_unique
- assert_equal 3, Ractor.new(tc) { |x| Ractor.yield(x.very_unique) }.take
- assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| Ractor.yield(x.instance_variables) }.take.sort
+ assert_equal 3, Ractor.new(tc) { |x| x.very_unique }.value
+ assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| x.instance_variables }.value.sort
end;
end
- def test_too_complex_ractor_shareable
+ def test_complex_ractor_shareable
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
$VERBOSE = nil
@@ -616,13 +665,104 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.instance_variable_set(:"@very_unique", 3)
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
assert_equal 3, tc.very_unique
assert_equal 3, Ractor.make_shareable(tc).very_unique
end;
end
- def test_too_complex_obj_ivar_ractor_share
+ def test_complex_and_frozen
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class TooComplex
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:"@very_unique", 3)
+
+ shape = RubyVM::Shape.of(tc)
+ assert_predicate shape, :complex?
+ refute_predicate shape, :shape_frozen?
+ tc.freeze
+ frozen_shape = RubyVM::Shape.of(tc)
+ refute_equal shape.id, frozen_shape.id
+ assert_predicate frozen_shape, :complex?
+ assert_predicate frozen_shape, :shape_frozen?
+
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.make_shareable(tc).very_unique
+ end;
+ end
+
+ def test_object_id_transition_complex
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ RubyVM::Shape.exhaust_shapes
+ assert_equal obj.object_id, obj.object_id
+ end;
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class Hi; end
+ obj = Hi.new
+ obj.instance_variable_set(:@a, 1)
+ obj.instance_variable_set(:@b, 2)
+ old_id = obj.object_id
+
+ RubyVM::Shape.exhaust_shapes
+ obj.remove_instance_variable(:@a)
+
+ assert_equal old_id, obj.object_id
+ end;
+ end
+
+ def test_complex_and_frozen_and_object_id
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class TooComplex
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:"@very_unique", 3)
+
+ shape = RubyVM::Shape.of(tc)
+ assert_predicate shape, :complex?
+ refute_predicate shape, :shape_frozen?
+ tc.freeze
+ frozen_shape = RubyVM::Shape.of(tc)
+ refute_equal shape.id, frozen_shape.id
+ assert_predicate frozen_shape, :complex?
+ assert_predicate frozen_shape, :shape_frozen?
+ refute_predicate frozen_shape, :has_object_id?
+
+ assert_equal tc.object_id, tc.object_id
+
+ id_shape = RubyVM::Shape.of(tc)
+ refute_equal frozen_shape.id, id_shape.id
+ assert_predicate id_shape, :complex?
+ assert_predicate id_shape, :has_object_id?
+ assert_predicate id_shape, :shape_frozen?
+
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.make_shareable(tc).very_unique
+ end;
+ end
+
+ def test_complex_obj_ivar_ractor_share
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
$VERBOSE = nil
@@ -632,15 +772,15 @@ class TestShapes < Test::Unit::TestCase
r = Ractor.new do
o = Object.new
o.instance_variable_set(:@a, "hello")
- Ractor.yield(o)
+ o
end
- o = r.take
+ o = r.value
assert_equal "hello", o.instance_variable_get(:@a)
end;
end
- def test_too_complex_generic_ivar_ractor_share
+ def test_complex_generic_ivar_ractor_share
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
$VERBOSE = nil
@@ -650,10 +790,10 @@ class TestShapes < Test::Unit::TestCase
r = Ractor.new do
o = []
o.instance_variable_set(:@a, "hello")
- Ractor.yield(o)
+ o
end
- o = r.take
+ o = r.value
assert_equal "hello", o.instance_variable_get(:@a)
end;
end
@@ -663,7 +803,7 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
assert_equal 3, tc.a3_m
end
@@ -672,7 +812,7 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
assert_equal 3, tc.a3_m
assert_equal 3, tc.a3
end
@@ -682,7 +822,7 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
tc.write_iv_method
tc.write_iv_method
assert_equal 12345, tc.a3_m
@@ -694,7 +834,7 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
tc.write_iv
tc.write_iv
assert_equal 12345, tc.a3_m
@@ -706,7 +846,7 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
assert_equal 3, tc.a3_m
assert_equal 3, tc.instance_variable_get(:@a3)
end
@@ -716,20 +856,53 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
+
+ assert_equal 3, tc.a3_m # make sure IV is initialized
+ assert tc.instance_variable_defined?(:@a3)
+ tc.remove_instance_variable(:@a3)
+ refute tc.instance_variable_defined?(:@a3)
+ assert_nil tc.a3
+ end
+
+ def test_delete_iv_after_complex_and_object_id
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :complex?
assert_equal 3, tc.a3_m # make sure IV is initialized
assert tc.instance_variable_defined?(:@a3)
+ tc.object_id
tc.remove_instance_variable(:@a3)
+ refute tc.instance_variable_defined?(:@a3)
assert_nil tc.a3
end
+ def test_delete_iv_after_complex_and_freeze
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :complex?
+
+ assert_equal 3, tc.a3_m # make sure IV is initialized
+ assert tc.instance_variable_defined?(:@a3)
+ tc.freeze
+ assert_raise FrozenError do
+ tc.remove_instance_variable(:@a3)
+ end
+ assert tc.instance_variable_defined?(:@a3)
+ assert_equal 3, tc.a3
+ end
+
def test_delete_undefined_after_complex
ensure_complex
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
refute tc.instance_variable_defined?(:@a3)
assert_raise(NameError) do
@@ -786,13 +959,15 @@ class TestShapes < Test::Unit::TestCase
def test_remove_instance_variable_capacity_transition
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
- t_object_shape = RubyVM::Shape.find_by_id(RubyVM::Shape::FIRST_T_OBJECT_SHAPE_ID)
- assert_equal(RubyVM::Shape::SHAPE_T_OBJECT, t_object_shape.type)
-
- initial_capacity = t_object_shape.capacity
# a does not transition in capacity
a = Class.new.new
+ root_shape = RubyVM::Shape.of(a)
+
+ assert_equal(RubyVM::Shape::SHAPE_ROOT, root_shape.type)
+ initial_capacity = root_shape.capacity
+ refute_equal(0, initial_capacity)
+
initial_capacity.times do |i|
a.instance_variable_set(:"@ivar#{i + 1}", i)
end
@@ -821,11 +996,11 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
tc.freeze
assert_raise(FrozenError) { tc.a3_m }
# doesn't transition to frozen shape in this case
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
end
def test_read_undefined_iv_after_complex
@@ -833,9 +1008,9 @@ class TestShapes < Test::Unit::TestCase
tc = TooComplex.new
tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
assert_equal nil, tc.iv_not_defined
- assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_predicate RubyVM::Shape.of(tc), :complex?
end
def test_shape_order
@@ -852,13 +1027,13 @@ class TestShapes < Test::Unit::TestCase
def test_iv_index
example = RemoveAndAdd.new
initial_shape = RubyVM::Shape.of(example)
- assert_equal 0, initial_shape.next_iv_index
+ assert_equal 0, initial_shape.next_field_index
example.add_foo # makes a transition
add_foo_shape = RubyVM::Shape.of(example)
assert_equal([:@foo], example.instance_variables)
- assert_equal(initial_shape.id, add_foo_shape.parent.id)
- assert_equal(1, add_foo_shape.next_iv_index)
+ assert_equal(initial_shape.offset, add_foo_shape.parent.offset)
+ assert_equal(1, add_foo_shape.next_field_index)
example.remove_foo # makes a transition
remove_foo_shape = RubyVM::Shape.of(example)
@@ -868,8 +1043,8 @@ class TestShapes < Test::Unit::TestCase
example.add_bar # makes a transition
bar_shape = RubyVM::Shape.of(example)
assert_equal([:@bar], example.instance_variables)
- assert_equal(initial_shape.id, bar_shape.parent_id)
- assert_equal(1, bar_shape.next_iv_index)
+ assert_equal(initial_shape.offset, bar_shape.parent_offset)
+ assert_equal(1, bar_shape.next_field_index)
end
def test_remove_then_add_again
@@ -888,10 +1063,41 @@ class TestShapes < Test::Unit::TestCase
def test_new_obj_has_t_object_shape
obj = TestObject.new
shape = RubyVM::Shape.of(obj)
- assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type
+ assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type
assert_nil shape.parent
end
+ def test_shape_layout
+ assert_equal :robject, RubyVM::Shape.of(TestObject.new).layout
+
+ if ENV["RUBY_BOX"]
+ assert_equal :other, RubyVM::Shape.of(Kernel).layout
+ assert_equal :other, RubyVM::Shape.of(String).layout
+ else
+ assert_equal :rclass, RubyVM::Shape.of(Kernel).layout
+ assert_equal :rclass, RubyVM::Shape.of(String).layout
+ end
+
+ assert_equal :rclass, RubyVM::Shape.of(Class.new).layout
+ assert_equal :rclass, RubyVM::Shape.of(Module.new).layout
+
+ klass = Class.new
+ assert_equal :rclass, RubyVM::Shape.of(klass).layout
+ klass.instance_variable_set(:@a, 123)
+ assert_equal :rclass, RubyVM::Shape.of(klass).layout
+
+ assert_equal :rdata, RubyVM::Shape.of(Thread.current).layout
+ assert_equal :rdata, RubyVM::Shape.of(lambda {}).layout
+
+ assert_equal :other, RubyVM::Shape.of(Struct.new(:x).new(1)).layout
+ assert_equal :other, RubyVM::Shape.of([]).layout
+ assert_equal :other, RubyVM::Shape.of("hello").layout
+ assert_equal :other, RubyVM::Shape.of(/foo/).layout
+ assert_equal :other, RubyVM::Shape.of(2..3).layout
+ assert_equal :other, RubyVM::Shape.of(2**67).layout
+ assert_equal :other, RubyVM::Shape.of(:"aaroniscool#{123}").layout
+ end
+
def test_str_has_root_shape
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(""))
end
@@ -900,20 +1106,32 @@ class TestShapes < Test::Unit::TestCase
assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([]))
end
- def test_true_has_special_const_shape_id
- assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(true).id)
- end
-
- def test_nil_has_special_const_shape_id
- assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(nil).id)
+ def test_raise_on_special_consts
+ assert_raise ArgumentError do
+ RubyVM::Shape.of(true)
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.of(false)
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.of(nil)
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.of(0)
+ end
+ # 32-bit platforms don't have flonums or static symbols as special
+ # constants
+ # TODO(max): Add ArgumentError tests for symbol and flonum, skipping if
+ # RUBY_PLATFORM =~ /i686/
end
- def test_root_shape_transition_to_special_const_on_frozen
- assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of([].freeze).id)
+ def test_root_shape_frozen
+ frozen_root_shape = RubyVM::Shape.of([].freeze)
+ assert_predicate(frozen_root_shape, :frozen?)
+ assert_equal(RubyVM::Shape.root_shape.id, frozen_root_shape.offset)
end
def test_basic_shape_transition
- omit "Failing with RJIT for some reason" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
obj = Example.new
shape = RubyVM::Shape.of(obj)
refute_equal(RubyVM::Shape.root_shape, shape)
@@ -921,7 +1139,7 @@ class TestShapes < Test::Unit::TestCase
assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type
shape = shape.parent
- assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type
+ assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type
assert_nil shape.parent
assert_equal(1, obj.instance_variable_get(:@a))
@@ -941,7 +1159,7 @@ class TestShapes < Test::Unit::TestCase
assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
end
- def test_duplicating_too_complex_objects_memory_leak
+ def test_duplicating_complex_objects_memory_leak
assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", "[Bug #20162]", rss: true)
RubyVM::Shape.exhaust_shapes
@@ -956,18 +1174,19 @@ class TestShapes < Test::Unit::TestCase
def test_freezing_and_duplicating_object
obj = Object.new.freeze
+ assert_predicate(RubyVM::Shape.of(obj), :shape_frozen?)
+
+ # dup'd objects shouldn't be frozen
obj2 = obj.dup
refute_predicate(obj2, :frozen?)
- # dup'd objects shouldn't be frozen, and the shape should be the
- # parent shape of the copied object
- assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id)
+ refute_predicate(RubyVM::Shape.of(obj2), :shape_frozen?)
end
def test_freezing_and_duplicating_object_with_ivars
obj = Example.new.freeze
obj2 = obj.dup
refute_predicate(obj2, :frozen?)
- refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ refute_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
assert_equal(obj2.instance_variable_get(:@a), 1)
end
@@ -977,6 +1196,7 @@ class TestShapes < Test::Unit::TestCase
str.freeze
str2 = str.dup
refute_predicate(str2, :frozen?)
+
refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id)
assert_equal(str2.instance_variable_get(:@a), 1)
end
@@ -992,9 +1212,8 @@ class TestShapes < Test::Unit::TestCase
obj = Object.new
obj2 = obj.clone(freeze: true)
assert_predicate(obj2, :frozen?)
- refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
- assert_equal(RubyVM::Shape::SHAPE_FROZEN, RubyVM::Shape.of(obj2).type)
- assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2).parent)
+ refute_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_predicate(RubyVM::Shape.of(obj2), :shape_frozen?)
end
def test_freezing_and_cloning_object_with_ivars
@@ -1037,4 +1256,68 @@ class TestShapes < Test::Unit::TestCase
tc.send("a#{_1}_m")
end
end
+
+ def assert_complex_during_delete(obj)
+ obj.instance_variable_set("@___#{SecureRandom.hex}", 1)
+
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i|
+ obj.instance_variable_set("@ivar#{i}", i)
+ end
+
+ refute_predicate RubyVM::Shape.of(obj), :complex?
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i|
+ obj.remove_instance_variable("@ivar#{i}")
+ end
+ assert_predicate RubyVM::Shape.of(obj), :complex?
+ end
+
+ def test_object_complex_during_delete
+ assert_complex_during_delete(Class.new.new)
+ end
+
+ def test_class_complex_during_delete
+ assert_complex_during_delete(Module.new)
+ end
+
+ def test_generic_complex_during_delete
+ assert_complex_during_delete(Class.new(Array).new)
+ end
+
+ def assert_complex_max_fields(obj)
+ extra_fields = RubyVM::Shape::SHAPE_MAX_FIELDS - obj.instance_variables.size
+ extra_fields.times do |i|
+ obj.instance_variable_set("@camel_ivar#{i}", i)
+ end
+ refute_predicate RubyVM::Shape.of(obj), :complex?
+ obj.instance_variable_set("@camel_straw", true)
+ assert_predicate RubyVM::Shape.of(obj), :complex?
+ end
+
+ def test_max_fields_complex
+ assert_complex_max_fields(Class.new(Object).new)
+ end
+
+ def test_generic_max_fields_complex
+ assert_complex_max_fields(Class.new(Array).new)
+ end
+
+ def test_class_max_fields_complex
+ assert_complex_max_fields(Class.new(Module).new)
+ end
+
+ def test_max_initial_fields
+ klass = Class.new
+ init_ivars = (RubyVM::Shape::SHAPE_MAX_FIELDS + 1).times.map { |i| "@ivar_#{i} = #{i}" }
+ klass.class_eval(<<~RUBY)
+ def initialize(init = false)
+ if init
+ #{init_ivars.join(";")}
+ end
+ end
+ RUBY
+ assert_predicate RubyVM::Shape.of(klass.new), :complex?
+ assert_predicate RubyVM::Shape.of(klass.new.dup), :complex?
+ assert_predicate RubyVM::Shape.of(klass.new(true)), :complex?
+ assert_predicate RubyVM::Shape.of(klass.new(true).dup), :complex?
+ end
end if defined?(RubyVM::Shape)
diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb
index a2bdf02b88..1ee3720ded 100644
--- a/test/ruby/test_signal.rb
+++ b/test/ruby/test_signal.rb
@@ -320,20 +320,20 @@ class TestSignal < Test::Unit::TestCase
# The parent should be notified about the stop
_, status = Process.waitpid2(child_pid, Process::WUNTRACED)
- assert status.stopped?
+ assert_predicate status, :stopped?
# It can be continued
Process.kill(:CONT, child_pid)
# And the child then runs to completion
_, status = Process.waitpid2(child_pid)
- assert status.exited?
- assert status.success?
+ assert_predicate status, :exited?
+ assert_predicate status, :success?
end
def test_sigwait_fd_unused
t = EnvUtil.apply_timeout_scale(0.1)
- assert_separately([], <<-End)
+ assert_ruby_status([], <<-End)
tgt = $$
trap(:TERM) { exit(0) }
e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})"
@@ -350,4 +350,18 @@ class TestSignal < Test::Unit::TestCase
loop { sleep }
End
end if Process.respond_to?(:kill) && Process.respond_to?(:daemon)
+
+ def test_signal_during_kwarg_call
+ status = assert_in_out_err([], <<~'RUBY', [], [], success: false)
+ Thread.new do
+ sleep 0.1
+ Process.kill("TERM", $$)
+ end
+
+ loop do
+ File.open(IO::NULL, kwarg: true) {}
+ end
+ RUBY
+ assert_predicate(status, :signaled?) if Signal.list.include?("QUIT")
+ end if Process.respond_to?(:kill)
end
diff --git a/test/ruby/test_sleep.rb b/test/ruby/test_sleep.rb
index 991b73ebd5..7ef962db4a 100644
--- a/test/ruby/test_sleep.rb
+++ b/test/ruby/test_sleep.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: false
require 'test/unit'
require 'etc'
+require 'timeout'
class TestSleep < Test::Unit::TestCase
def test_sleep_5sec
@@ -13,4 +14,21 @@ class TestSleep < Test::Unit::TestCase
assert_operator(slept, :<=, 6.0, "[ruby-core:18015]: longer than expected")
end
end
+
+ def test_sleep_forever_not_woken_by_sigchld
+ begin
+ t = Thread.new do
+ sleep 0.5
+ `echo hello`
+ end
+
+ assert_raise Timeout::Error do
+ Timeout.timeout 2 do
+ sleep # Should block forever
+ end
+ end
+ ensure
+ t.join
+ end
+ end
end
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb
index d2099607fd..aedfc93e5d 100644
--- a/test/ruby/test_string.rb
+++ b/test/ruby/test_string.rb
@@ -675,7 +675,7 @@ CODE
omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
require 'objspace'
- base_slot_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
+ base_slot_size = GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]
small_obj_size = (base_slot_size / 2)
large_obj_size = base_slot_size * 2
@@ -851,7 +851,6 @@ CODE
assert_equal(S("\u{AB}"), S('"\\u00AB"').undump)
assert_equal(S("\u{ABC}"), S('"\\u0ABC"').undump)
assert_equal(S("\uABCD"), S('"\\uABCD"').undump)
- assert_equal(S("\uABCD"), S('"\\uABCD"').undump)
assert_equal(S("\u{ABCDE}"), S('"\\u{ABCDE}"').undump)
assert_equal(S("\u{10ABCD}"), S('"\\u{10ABCD}"').undump)
assert_equal(S("\u{ABCDE 10ABCD}"), S('"\\u{ABCDE 10ABCD}"').undump)
@@ -872,6 +871,10 @@ CODE
assert_equal('\#', S('"\\\\#"').undump)
assert_equal('\#{', S('"\\\\\#{"').undump)
+ assert_undump("\0\u{ABCD}")
+ assert_undump(S('"\x00\u3042"'.force_encoding("SJIS")))
+ assert_undump(S('"\u3042\x7E"'.force_encoding("SJIS")))
+
assert_raise(RuntimeError) { S('\u3042').undump }
assert_raise(RuntimeError) { S('"\x82\xA0\u3042"'.force_encoding("SJIS")).undump }
assert_raise(RuntimeError) { S('"\u3042\x82\xA0"'.force_encoding("SJIS")).undump }
@@ -994,6 +997,32 @@ CODE
assert_equal [65, 66, 67], res
end
+ def test_getbyte
+ s = S('foo')
+ assert_equal(102, s.getbyte(0))
+ assert_equal(111, s.getbyte(2))
+ assert_equal(102, s.getbyte(-3))
+ assert_nil(s.getbyte(3))
+ assert_nil(s.getbyte(-4))
+ assert_nil(S('').getbyte(0))
+ assert_nil(S('').getbyte(-1))
+ end
+
+ def test_setbyte
+ s = S('xyzzy')
+ assert_equal(129, s.setbyte(2, 129))
+ assert_equal(S("xy\x81zy").force_encoding(s.encoding), s)
+
+ s = S('foo')
+ s.setbyte(-3, 98)
+ assert_equal(S('boo').force_encoding(s.encoding), s)
+
+ assert_raise(IndexError) { S('foo').setbyte(3, 0) }
+ assert_raise(IndexError) { S('foo').setbyte(-4, 0) }
+
+ assert_raise(FrozenError) { S('foo').freeze.setbyte(0, 0x61) }
+ end
+
def test_each_codepoint
# Single byte optimization
assert_equal 65, S("ABC").each_codepoint.next
@@ -1869,6 +1898,13 @@ CODE
result = []; S("aaa,bbb,ccc,ddd").split(/,/) {|s| result << s.gsub(/./, "A")}
assert_equal(["AAA"]*4, result)
+
+ s = S("abc ") * 20
+ assert_raise(RuntimeError) {
+ 10.times do
+ s.split {s.prepend("xxx" * 100)}
+ end
+ }
ensure
EnvUtil.suppress_warning {$; = fs}
end
@@ -1876,9 +1912,24 @@ CODE
def test_fs
return unless @cls == String
- assert_raise_with_message(TypeError, /\$;/) {
- $; = []
- }
+ begin
+ fs = $;
+ assert_deprecated_warning(/non-nil '\$;'/) {$; = "x"}
+ assert_raise_with_message(TypeError, /\$;/) {$; = []}
+ ensure
+ EnvUtil.suppress_warning {$; = fs}
+ end
+ name = "\u{5206 5217}"
+ assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}")
+ do;
+ alias $#{name} $;
+ assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" }
+ assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 }
+ end;
+ end
+
+ def test_fs_gc
+ return unless @cls == String
assert_separately(%W[-W0], "#{<<~"begin;"}\n#{<<~'end;'}")
bug = '[ruby-core:79582] $; must not be GCed'
@@ -2027,6 +2078,117 @@ CODE
assert_equal(S("x") ,a)
end
+ def test_strip_with_selectors
+ assert_equal(S("abc"), S("---abc+++").strip("-+"))
+ assert_equal(S("abc"), S("+++abc---").strip("-+"))
+ assert_equal(S("abc"), S("+-+abc-+-").strip("-+"))
+ assert_equal(S(""), S("---+++").strip("-+"))
+ assert_equal(S("abc "), S("---abc ").strip("-"))
+ assert_equal(S(" abc"), S(" abc+++").strip("+"))
+
+ # Test with multibyte characters
+ assert_equal(S("abc"), S("あああabcいいい").strip("あい"))
+ assert_equal(S("abc"), S("いいいabcあああ").strip("あい"))
+
+ # Test with NUL characters
+ assert_equal(S("abc\0"), S("---abc\0--").strip("-"))
+ assert_equal(S("\0abc"), S("--\0abc---").strip("-"))
+
+ # Test without modification
+ assert_equal(S("abc"), S("abc").strip("-+"))
+ assert_equal(S("abc"), S("abc").strip(""))
+
+ # Test with range
+ assert_equal(S("abc"), S("012abc345").strip("0-9"))
+ assert_equal(S("abc"), S("012abc345").strip("^a-z"))
+
+ # Test with multiple selectors
+ assert_equal(S("4abc56"), S("01234abc56789").strip("0-9", "^4-6"))
+ end
+
+ def test_strip_bang_with_chars
+ a = S("---abc+++")
+ assert_equal(S("abc"), a.strip!("-+"))
+ assert_equal(S("abc"), a)
+
+ a = S("+++abc---")
+ assert_equal(S("abc"), a.strip!("-+"))
+ assert_equal(S("abc"), a)
+
+ a = S("abc")
+ assert_nil(a.strip!("-+"))
+ assert_equal(S("abc"), a)
+
+ # Test with multibyte characters
+ a = S("あああabcいいい")
+ assert_equal(S("abc"), a.strip!("あい"))
+ assert_equal(S("abc"), a)
+ end
+
+ def test_lstrip_with_selectors
+ assert_equal(S("abc+++"), S("---abc+++").lstrip("-"))
+ assert_equal(S("abc---"), S("+++abc---").lstrip("+"))
+ assert_equal(S("abc"), S("---abc").lstrip("-"))
+ assert_equal(S(""), S("---").lstrip("-"))
+
+ # Test with multibyte characters
+ assert_equal(S("abcいいい"), S("あああabcいいい").lstrip("あ"))
+
+ # Test with NUL characters
+ assert_equal(S("\0abc+++"), S("--\0abc+++").lstrip("-"))
+
+ # Test without modification
+ assert_equal(S("abc"), S("abc").lstrip("-"))
+
+ # Test with range
+ assert_equal(S("abc345"), S("012abc345").lstrip("0-9"))
+
+ # Test with multiple selectors
+ assert_equal(S("4abc56789"), S("01234abc56789").lstrip("0-9", "^4-6"))
+ end
+
+ def test_lstrip_bang_with_chars
+ a = S("---abc+++")
+ assert_equal(S("abc+++"), a.lstrip!("-"))
+ assert_equal(S("abc+++"), a)
+
+ a = S("abc")
+ assert_nil(a.lstrip!("-"))
+ assert_equal(S("abc"), a)
+ end
+
+ def test_rstrip_with_selectors
+ assert_equal(S("---abc"), S("---abc+++").rstrip("+"))
+ assert_equal(S("+++abc"), S("+++abc---").rstrip("-"))
+ assert_equal(S("abc"), S("abc+++").rstrip("+"))
+ assert_equal(S(""), S("+++").rstrip("+"))
+
+ # Test with multibyte characters
+ assert_equal(S("あああabc"), S("あああabcいいい").rstrip("い"))
+
+ # Test with NUL characters
+ assert_equal(S("---abc\0"), S("---abc\0++").rstrip("+"))
+
+ # Test without modification
+ assert_equal(S("abc"), S("abc").rstrip("-"))
+
+ # Test with range
+ assert_equal(S("012abc"), S("012abc345").rstrip("0-9"))
+
+ # Test with multiple selectors
+ assert_equal(S("01234abc56"), S("01234abc56789").rstrip("0-9", "^4-6"))
+ end
+
+ def test_rstrip_bang_with_chars
+ a = S("---abc+++")
+ assert_equal(S("---abc"), a.rstrip!("+"))
+ assert_equal(S("---abc"), a)
+
+ a = S("abc")
+ assert_nil(a.rstrip!("+"))
+ assert_equal(S("abc"), a)
+ end
+
def test_sub
assert_equal(S("h*llo"), S("hello").sub(/[aeiou]/, S('*')))
assert_equal(S("h<e>llo"), S("hello").sub(/([aeiou])/, S('<\1>')))
@@ -2462,37 +2624,11 @@ CODE
assert_equal([0xa9, 0x42, 0x2260], S("\xc2\xa9B\xe2\x89\xa0").unpack(S("U*")))
-=begin
- skipping "Not tested:
- D,d & double-precision float, native format\\
- E & double-precision float, little-endian byte order\\
- e & single-precision float, little-endian byte order\\
- F,f & single-precision float, native format\\
- G & double-precision float, network (big-endian) byte order\\
- g & single-precision float, network (big-endian) byte order\\
- I & unsigned integer\\
- i & integer\\
- L & unsigned long\\
- l & long\\
-
- m & string encoded in base64 (uuencoded)\\
- N & long, network (big-endian) byte order\\
- n & short, network (big-endian) byte-order\\
- P & pointer to a structure (fixed-length string)\\
- p & pointer to a null-terminated string\\
- S & unsigned short\\
- s & short\\
- V & long, little-endian byte order\\
- v & short, little-endian byte order\\
- X & back up a byte\\
- x & null byte\\
- Z & ASCII string (null padded, count is width)\\
-"
-=end
+ # more comprehensive tests are in test_pack.rb
end
def test_upcase
- assert_equal(S("HELLO"), S("hello").upcase)
+ assert_equal(S("HELLO"), S("helLO").upcase)
assert_equal(S("HELLO"), S("hello").upcase)
assert_equal(S("HELLO"), S("HELLO").upcase)
assert_equal(S("ABC HELLO 123"), S("abc HELLO 123").upcase)
@@ -2646,8 +2782,9 @@ CODE
def test_match_method
assert_equal("bar", S("foobarbaz").match(/bar/).to_s)
- o = Regexp.new('foo')
- def o.match(x, y, z); x + y + z; end
+ o = Class.new(Regexp) {
+ def match(x, y, z) = x + y + z
+ }.new('foo')
assert_equal("foobarbaz", S("foo").match(o, "bar", "baz"))
x = nil
S("foo").match(o, "bar", "baz") {|y| x = y }
@@ -2780,14 +2917,21 @@ CODE
assert_equal([S("abcdb"), S("c"), S("e")], S("abcdbce").rpartition(/b\Kc/))
end
- def test_fs_setter
+ def test_rs
return unless @cls == String
- assert_raise(TypeError) { $/ = 1 }
+ begin
+ rs = $/
+ assert_deprecated_warning(/non-nil '\$\/'/) { $/ = "" }
+ assert_raise(TypeError) { $/ = 1 }
+ ensure
+ EnvUtil.suppress_warning { $/ = rs }
+ end
name = "\u{5206 884c}"
assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}")
do;
alias $#{name} $/
+ assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" }
assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 }
end;
end
@@ -2838,27 +2982,45 @@ CODE
assert_equal("\u3042", ("\u3042" * 100)[-1])
end
-=begin
def test_compare_different_encoding_string
s1 = S("\xff".force_encoding("UTF-8"))
s2 = S("\xff".force_encoding("ISO-2022-JP"))
assert_equal([-1, 1], [s1 <=> s2, s2 <=> s1].sort)
+
+ s3 = S("あ".force_encoding("UTF-16LE"))
+ s4 = S("a".force_encoding("IBM437"))
+ assert_equal([-1, 1], [s3 <=> s4, s4 <=> s3].sort)
end
-=end
def test_casecmp
assert_equal(0, S("FoO").casecmp("fOO"))
assert_equal(1, S("FoO").casecmp("BaR"))
+ assert_equal(-1, S("foo").casecmp("FOOBAR"))
assert_equal(-1, S("baR").casecmp("FoO"))
assert_equal(1, S("\u3042B").casecmp("\u3042a"))
assert_equal(-1, S("foo").casecmp("foo\0"))
+ assert_equal(1, S("FOOBAR").casecmp("foo"))
+ assert_equal(0, S("foo\0bar").casecmp("FOO\0BAR"))
assert_nil(S("foo").casecmp(:foo))
assert_nil(S("foo").casecmp(Object.new))
+ assert_nil(S("foo").casecmp(0))
+ assert_nil(S("foo").casecmp(5.00))
+
o = Object.new
def o.to_str; "fOO"; end
assert_equal(0, S("FoO").casecmp(o))
+
+ assert_equal(0, S("#" * 128 + "A" * 256 + "b").casecmp("#" * 128 + "a" * 256 + "B"))
+ assert_equal(0, S("a" * 256 + "B").casecmp("A" * 256 + "b"))
+
+ assert_equal(-1, S("@").casecmp("`"))
+ assert_equal(0, S("hello\u00E9X").casecmp("HELLO\u00E9x"))
+
+ s1 = S("\xff".force_encoding("UTF-8"))
+ s2 = S("\xff".force_encoding("ISO-2022-JP"))
+ assert_nil(s1.casecmp(s2))
end
def test_casecmp?
@@ -2871,9 +3033,16 @@ CODE
assert_nil(S("foo").casecmp?(:foo))
assert_nil(S("foo").casecmp?(Object.new))
+ assert_nil(S("foo").casecmp(0))
+ assert_nil(S("foo").casecmp(5.00))
+
o = Object.new
def o.to_str; "fOO"; end
assert_equal(true, S("FoO").casecmp?(o))
+
+ s1 = S("\xff".force_encoding("UTF-8"))
+ s2 = S("\xff".force_encoding("ISO-2022-JP"))
+ assert_nil(s1.casecmp?(s2))
end
def test_upcase2
@@ -2946,7 +3115,6 @@ CODE
s5 = S("\u0000\u3042")
assert_equal("\u3042", s5.lstrip!)
assert_equal("\u3042", s5)
-
end
def test_delete_prefix_type_error
@@ -3246,18 +3414,12 @@ CODE
assert_equal('"\\u3042\\u3044\\u3046"', S("\u3042\u3044\u3046".encode(e)).inspect)
assert_equal('"ab\\"c"', S("ab\"c".encode(e)).inspect, bug4081)
end
- begin
- verbose, $VERBOSE = $VERBOSE, nil
- ext = Encoding.default_external
- Encoding.default_external = "us-ascii"
- $VERBOSE = verbose
+
+ EnvUtil.with_default_external(Encoding::US_ASCII) do
i = S("abc\"\\".force_encoding("utf-8")).inspect
- ensure
- $VERBOSE = nil
- Encoding.default_external = ext
- $VERBOSE = verbose
+
+ assert_equal('"abc\\"\\\\"', i, bug4081)
end
- assert_equal('"abc\\"\\\\"', i, bug4081)
end
def test_dummy_inspect
@@ -3314,11 +3476,37 @@ CODE
assert_equal(u("\x82")+("\u3042"*9), S("\u3042"*10).byteslice(2, 28))
+ assert_equal("\xE3", S("こんにちは").byteslice(0))
+ assert_equal("こんにちは", S("こんにちは").byteslice(0, 15))
+ assert_equal("こ", S("こんにちは").byteslice(0, 3))
+ assert_equal("は", S("こんにちは").byteslice(12, 15))
+
bug7954 = '[ruby-dev:47108]'
assert_equal(false, S("\u3042").byteslice(0, 2).valid_encoding?, bug7954)
assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?, bug7954)
end
+ def test_shared_middle_string_terminator
+ ten = "0123456789"
+ hundred = ten * 10
+ str = "#{hundred}\0#{hundred}".freeze
+
+ require 'objspace'
+
+ substr = str.byteslice(0, hundred.bytesize)
+ assert_equal hundred, substr
+ assert_includes ObjectSpace.dump(substr), ' "shared":true,'
+
+ # Larger terminator
+ substr.force_encoding(Encoding::UTF_16BE)
+ assert_equal hundred.dup.force_encoding(Encoding::UTF_16BE), substr
+ refute_includes ObjectSpace.dump(substr), ' "shared":true,'
+
+ substr = str.byteslice(0, hundred.bytesize + 1)
+ assert_equal hundred + "\0", substr
+ refute_includes ObjectSpace.dump(substr), ' "shared":true,'
+ end
+
def test_unknown_string_option
str = nil
assert_nothing_raised(SyntaxError) do
@@ -3397,6 +3585,12 @@ CODE
assert_same(str, bar, "uminus deduplicates [Feature #13077] str: #{ObjectSpace.dump(str)} bar: #{ObjectSpace.dump(bar)}")
end
+ def test_uminus_dedup_in_place
+ dynamic = "this string is unique and frozen #{rand}".freeze
+ assert_same dynamic, -dynamic
+ assert_same dynamic, -dynamic.dup
+ end
+
def test_uminus_frozen
return unless @cls == String
@@ -3431,6 +3625,17 @@ CODE
assert_equal(false, str.frozen?)
end
+ def test_uminus_no_embed_gc
+ pad = "a"*2048
+ File.open(IO::NULL, "w") do |dev_null|
+ ("aa".."zz").each do |c|
+ fstr = -(c + pad).freeze
+ dev_null.write(fstr)
+ end
+ end
+ GC.start
+ end
+
def test_ord
assert_equal(97, S("a").ord)
assert_equal(97, S("abc").ord)
@@ -3737,6 +3942,96 @@ CODE
Warning[:deprecated] = deprecated
end
+ def test_encode_fallback_raise_memory_leak
+ {
+ "hash" => <<~RUBY,
+ fallback = Hash.new { raise MyError }
+ RUBY
+ "proc" => <<~RUBY,
+ fallback = proc { raise MyError }
+ RUBY
+ "method" => <<~RUBY,
+ def my_method(_str) = raise MyError
+ fallback = method(:my_method)
+ RUBY
+ "aref" => <<~RUBY,
+ fallback = Object.new
+ def fallback.[](_str) = raise MyError
+ RUBY
+ }.each do |type, code|
+ assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true)
+ class MyError < StandardError; end
+
+ #{code}
+
+ 100_000.times do |i|
+ "\\ufffd".encode(Encoding::US_ASCII, fallback:)
+ rescue MyError
+ end
+ RUBY
+ end
+ end
+
+ def test_encode_fallback_too_big_memory_leak
+ {
+ "hash" => <<~RUBY,
+ fallback = Hash.new { "\\uffee" }
+ RUBY
+ "proc" => <<~RUBY,
+ fallback = proc { "\\uffee" }
+ RUBY
+ "method" => <<~RUBY,
+ def my_method(_str) = "\\uffee"
+ fallback = method(:my_method)
+ RUBY
+ "aref" => <<~RUBY,
+ fallback = Object.new
+ def fallback.[](_str) = "\\uffee"
+ RUBY
+ }.each do |type, code|
+ assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true)
+ class MyError < StandardError; end
+
+ #{code}
+
+ 100_000.times do |i|
+ "\\ufffd".encode(Encoding::US_ASCII, fallback:)
+ rescue ArgumentError
+ end
+ RUBY
+ end
+ end
+
+ def test_encode_fallback_not_string_memory_leak
+ {
+ "hash" => <<~RUBY,
+ fallback = Hash.new { Object.new }
+ RUBY
+ "proc" => <<~RUBY,
+ fallback = proc { Object.new }
+ RUBY
+ "method" => <<~RUBY,
+ def my_method(_str) = Object.new
+ fallback = method(:my_method)
+ RUBY
+ "aref" => <<~RUBY,
+ fallback = Object.new
+ def fallback.[](_str) = Object.new
+ RUBY
+ }.each do |type, code|
+ assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true)
+ class MyError < StandardError; end
+
+ #{code}
+
+ 100_000.times do |i|
+ "\\ufffd".encode(Encoding::US_ASCII, fallback:)
+ rescue TypeError
+ end
+ RUBY
+ end
+ end
+
private
def assert_bytesplice_result(expected, s, *args)
@@ -3783,6 +4078,10 @@ CODE
def assert_byterindex(expected, string, match, *rest)
assert_index_like(:byterindex, expected, string, match, *rest)
end
+
+ def assert_undump(str, *rest)
+ assert_equal(str, str.dump.undump, *rest)
+ end
end
class TestString2 < TestString
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index 3d727adf04..b37f4dba97 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -41,8 +41,14 @@ module TestStruct
end
end
+ MAX_EMBEDDED_MEMBERS = (
+ GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] -
+ GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] -
+ GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]
+ ) / RbConfig::SIZEOF["void*"]
+
def test_larger_than_largest_pool
- count = (GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] / RbConfig::SIZEOF["void*"]) + 1
+ count = MAX_EMBEDDED_MEMBERS + 1
list = Array(0..count)
klass = @Struct.new(*list.map { |i| :"a_#{i}"})
struct = klass.new(*list)
@@ -535,19 +541,27 @@ module TestStruct
end
def test_named_structs_are_not_rooted
+ omit 'skip on riscv64-linux CI machine. See https://github.com/ruby/ruby/pull/13422' if ENV['RUBY_DEBUG'] == 'ci' && /riscv64-linux/ =~ RUBY_DESCRIPTION
+
# [Bug #20311]
- assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true, limit: 2.2)
code = proc do
Struct.new("A")
Struct.send(:remove_const, :A)
end
- 1_000.times(&code)
+ 10_000.times(&code)
PREP
50_000.times(&code)
CODE
end
+ def test_frozen_subclass
+ test = Class.new(@Struct.new(:a)).freeze.new(a: 0)
+ assert_kind_of(@Struct, test)
+ assert_equal([:a], test.members)
+ end
+
class TopStruct < Test::Unit::TestCase
include TestStruct
diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb
index 8e973b0f7f..39594d74be 100644
--- a/test/ruby/test_super.rb
+++ b/test/ruby/test_super.rb
@@ -759,4 +759,33 @@ class TestSuper < Test::Unit::TestCase
inherited = inherited_class.new
assert_equal 2, inherited.test # it may read index=1 while it should be index=2
end
+
+ def test_define_initialize_in_basic_object
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class ::BasicObject
+ alias_method :initialize, :initialize
+ def initialize
+ @bug = "[Bug #21992]"
+ end
+ end
+
+ assert_not_nil Object.new
+ end;
+ end
+
+ def test_super_in_basic_object
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class ::BasicObject
+ def no_super
+ super()
+ rescue ::NameError
+ :ok
+ end
+ end
+
+ assert_equal :ok, "[Bug #21694]".no_super
+ end;
+ end
end
diff --git a/test/ruby/test_symbol.rb b/test/ruby/test_symbol.rb
index c50febf5d1..fa65dca225 100644
--- a/test/ruby/test_symbol.rb
+++ b/test/ruby/test_symbol.rb
@@ -417,8 +417,9 @@ class TestSymbol < Test::Unit::TestCase
def test_match_method
assert_equal("bar", :"foobarbaz".match(/bar/).to_s)
- o = Regexp.new('foo')
- def o.match(x, y, z); x + y + z; end
+ o = Class.new(Regexp) {
+ def match(x, y, z) = x + y + z
+ }.new('foo')
assert_equal("foobarbaz", :"foo".match(o, "bar", "baz"))
x = nil
:"foo".match(o, "bar", "baz") {|y| x = y }
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index 62f1d99bdc..ae4cdf5fe7 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -202,6 +202,59 @@ class TestSyntax < Test::Unit::TestCase
assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/)
end
+ def test_no_block_argument_in_method
+ assert_valid_syntax("def f(&nil) end")
+ assert_valid_syntax("def f(a, &nil) end")
+ assert_valid_syntax("def f(*rest, &nil) end")
+ assert_valid_syntax("def f(*rest, p, &nil) end")
+ assert_valid_syntax("def f(a, *rest, &nil) end")
+ assert_valid_syntax("def f(a, *rest, p, &nil) end")
+ assert_valid_syntax("def f(a, k: nil, &nil) end")
+ assert_valid_syntax("def f(a, k: nil, **kw, &nil) end")
+ assert_valid_syntax("def f(a, *rest, k: nil, &nil) end")
+ assert_valid_syntax("def f(a, *rest, k: nil, **kw, &nil) end")
+ assert_valid_syntax("def f(a, *rest, p, k: nil, &nil) end")
+ assert_valid_syntax("def f(a, *rest, p, k: nil, **kw, &nil) end")
+
+ obj = Object.new
+ obj.instance_eval "def f(&nil) end"
+ assert_raise_with_message(ArgumentError, /block accepted/) {obj.f {}}
+ assert_raise_with_message(ArgumentError, /block accepted/) {obj.f(&proc {})}
+ end
+
+ def test_trailing_comma_in_method_parameters
+ assert_valid_syntax("def f(a,b,c,); end")
+ assert_valid_syntax("def f(a,b,*c,); end")
+ assert_valid_syntax("def f(a,b,*,); end")
+ assert_valid_syntax("def f(a,b,**c,); end")
+ assert_valid_syntax("def f(a,b,**,); end")
+ assert_syntax_error("def f(a,b,&block,); end", /unexpected/)
+ assert_syntax_error("def f(a,b,...,); end", /unexpected/)
+ end
+
+ def test_no_block_argument_in_block
+ assert_valid_syntax("proc do |&nil| end")
+ assert_valid_syntax("proc do |a, &nil| end")
+ assert_valid_syntax("proc do |*rest, &nil| end")
+ assert_valid_syntax("proc do |*rest, p, &nil| end")
+ assert_valid_syntax("proc do |a, *rest, &nil| end")
+ assert_valid_syntax("proc do |a, *rest, p, &nil| end")
+ assert_valid_syntax("proc do |a, k: nil, &nil| end")
+ assert_valid_syntax("proc do |a, k: nil, **kw, &nil| end")
+ assert_valid_syntax("proc do |a, *rest, k: nil, &nil| end")
+ assert_valid_syntax("proc do |a, *rest, k: nil, **kw, &nil| end")
+ assert_valid_syntax("proc do |a, *rest, p, k: nil, &nil| end")
+ assert_valid_syntax("proc do |a, *rest, p, k: nil, **kw, &nil| end")
+
+ pr = eval "proc {|&nil|}"
+ assert_nil(pr.call)
+ assert_raise_with_message(ArgumentError, /block accepted/) {pr.call {}}
+ pr = eval "proc {|a, &nil| a}"
+ assert_nil(pr.call)
+ assert_equal(1, pr.call(1))
+ assert_raise_with_message(ArgumentError, /block accepted/) {pr.call {}}
+ end
+
def test_newline_in_block_parameters
bug = '[ruby-dev:45292]'
["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params|
@@ -1259,6 +1312,52 @@ eom
assert_valid_syntax("a #\n#\n&.foo\n")
end
+ def test_fluent_and
+ assert_valid_syntax("a\n" "&& foo")
+ assert_valid_syntax("a\n" "and foo")
+
+ assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}"))
+ begin;
+ a = true
+ if a
+ && (a = :ok; true)
+ a
+ end
+ end;
+
+ assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}"))
+ begin;
+ a = true
+ if a
+ and (a = :ok; true)
+ a
+ end
+ end;
+ end
+
+ def test_fluent_or
+ assert_valid_syntax("a\n" "|| foo")
+ assert_valid_syntax("a\n" "or foo")
+
+ assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}"))
+ begin;
+ a = false
+ if a
+ || (a = :ok; true)
+ a
+ end
+ end;
+
+ assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}"))
+ begin;
+ a = false
+ if a
+ or (a = :ok; true)
+ a
+ end
+ end;
+ end
+
def test_safe_call_in_massign_lhs
assert_syntax_error("*a&.x=0", /multiple assignment destination/)
assert_syntax_error("a&.x,=0", /multiple assignment destination/)
@@ -1493,10 +1592,10 @@ eom
begin raise; rescue; return; end
return false; raise
return 1; raise
- "#{return}"
- raise((return; "should not raise"))
+ "#{return if true}"
+ raise((return if true; "should not raise"))
begin raise; ensure return; end; self
- nil&defined?0--begin e=no_method_error(); return; 0;end
+ nil&defined?0--begin e=no_method_error(); return if true; 0;end
return puts('ignored') #=> ignored
BEGIN {return}
END {return if false}
@@ -1794,15 +1893,12 @@ eom
assert_equal("class ok", k.rescued("ok"))
assert_equal("instance ok", k.new.rescued("ok"))
- # Current technical limitation: cannot prepend "private" or something for command endless def
- error = /(syntax error,|\^~*) unexpected string literal/
- error2 = /(syntax error,|\^~*) unexpected local variable or method/
- assert_syntax_error('private def foo = puts "Hello"', error)
- assert_syntax_error('private def foo() = puts "Hello"', error)
- assert_syntax_error('private def foo(x) = puts x', error2)
- assert_syntax_error('private def obj.foo = puts "Hello"', error)
- assert_syntax_error('private def obj.foo() = puts "Hello"', error)
- assert_syntax_error('private def obj.foo(x) = puts x', error2)
+ assert_valid_syntax('private def foo = puts "Hello"')
+ assert_valid_syntax('private def foo() = puts "Hello"')
+ assert_valid_syntax('private def foo(x) = puts x')
+ assert_valid_syntax('private def obj.foo = puts "Hello"')
+ assert_valid_syntax('private def obj.foo() = puts "Hello"')
+ assert_valid_syntax('private def obj.foo(x) = puts x')
end
def test_methoddef_in_cond
@@ -1815,6 +1911,24 @@ eom
assert_valid_syntax('while class Foo a = tap do end; end; break; end')
end
+ def test_while_until_conditional_bug_22002
+ @foo = 123 until defined?(@foo)
+ assert_equal(123, @foo)
+
+ @bar = 456 while @bar==nil..true
+ assert_equal(456, @bar)
+
+ while false and @baz
+ @baz = 789
+ end
+ assert_equal(nil, @baz)
+
+ until true || @baz
+ @baz = 789
+ end
+ assert_equal(nil, @baz)
+ end
+
def test_command_with_cmd_brace_block
assert_valid_syntax('obj.foo (1) {}')
assert_valid_syntax('obj::foo (1) {}')
@@ -1946,6 +2060,40 @@ eom
end
assert_valid_syntax('proc {def foo(_);end;it}')
assert_syntax_error('p { [it **2] }', /unexpected \*\*/)
+ assert_equal(1, eval('1.then { raise rescue it }'))
+ assert_equal(2, eval('1.then { 2.then { raise rescue it } }'))
+ assert_equal(3, eval('3.then { begin; raise; rescue; it; end }'))
+ assert_equal(4, eval('4.tap { begin; raise ; rescue; raise rescue it; end; }'))
+ assert_equal(5, eval('a = 0; 5.then { begin; nil; ensure; a = it; end }; a'))
+ assert_equal(6, eval('a = 0; 6.then { begin; nil; rescue; ensure; a = it; end }; a'))
+ assert_equal(7, eval('a = 0; 7.then { begin; raise; ensure; a = it; end } rescue a'))
+ assert_equal(8, eval('a = 0; 8.then { begin; raise; rescue; ensure; a = it; end }; a'))
+ assert_equal(/9/, eval('9.then { /#{it}/o }'))
+ end
+
+ def test_it_with_splat_super_method
+ bug21256 = '[ruby-core:121592] [Bug #21256]'
+
+ a = Class.new do
+ define_method(:foo) { it }
+ end
+ b = Class.new(a) do
+ def foo(*args) = super
+ end
+
+ assert_equal(1, b.new.foo(1), bug21256)
+ end
+
+ BUG_21669 = '[Bug #21669]'
+
+ def test_value_expr_in_block
+ assert_syntax_error("#{<<~"{#"}\n#{<<~'};'}", /void value expression/, nil, "#{BUG_21669} 2.1")
+ {#
+ x = begin
+ return
+ "NG"
+ end
+ };
end
def test_value_expr_in_condition
@@ -1954,6 +2102,51 @@ eom
assert_valid_syntax("tap {a = (true ? true : break)}")
assert_valid_syntax("tap {a = (break if false)}")
assert_valid_syntax("tap {a = (break unless true)}")
+
+ assert_syntax_error("#{<<~"{#"}\n#{<<~'};'}", /void value expression/, nil, "#{BUG_21669} 1.4")
+ {#
+ x = if rand < 0.5
+ return
+ else
+ return
+ end
+ };
+
+ assert_syntax_error("#{<<~"{#"}\n#{<<~'};'}", /void value expression/, nil, "#{BUG_21669} 2.2")
+ {#
+ x = if rand < 0.5
+ return
+ "NG"
+ else
+ return
+ end
+ };
+
+ assert_valid_syntax("#{<<~"{#"}\n#{<<~'};'}", "#{BUG_21669} 2.3")
+ {#
+ x = begin
+ return if true
+ "OK"
+ end
+ };
+
+ assert_valid_syntax("#{<<~"{#"}\n#{<<~'};'}")
+ {#
+ x = if true
+ return "NG"
+ else
+ "OK"
+ end
+ };
+
+ assert_valid_syntax("#{<<~"{#"}\n#{<<~'};'}")
+ {#
+ x = if false
+ "OK"
+ else
+ return "NG"
+ end
+ };
end
def test_value_expr_in_singleton
@@ -1961,6 +2154,67 @@ eom
assert_syntax_error("class << (return); end", mesg)
end
+ def test_value_expr_in_rescue
+ assert_valid_syntax("#{<<~"{#"}\n#{<<~'};'}", "#{BUG_21669} 1.1")
+ {#
+ x = begin
+ raise
+ return
+ rescue
+ "OK"
+ else
+ return
+ end
+ };
+
+ assert_syntax_error("#{<<~"{#"}\n#{<<~'};'}", /void value expression/, nil, "#{BUG_21669} 1.2")
+ {#
+ x = begin
+ foo
+ rescue
+ return
+ else
+ return
+ end
+ };
+ end
+
+ def test_value_expr_in_case
+ assert_syntax_error("#{<<~"{#"}\n#{<<~'};'}", /void value expression/, nil, "#{BUG_21669} 1.3")
+ {#
+ x =
+ case a
+ when 1; return
+ when 2; return
+ else return
+ end
+ };
+ end
+
+ def test_value_expr_in_case2
+ assert_syntax_error("#{<<~"{#"}\n#{<<~'};'}", /void value expression/, nil, "#{BUG_21669} 1.3")
+ {#
+ x =
+ case
+ when 1; return
+ when 2; return
+ else return
+ end
+ };
+ end
+
+ def test_value_expr_in_case3
+ assert_syntax_error("#{<<~"{#"}\n#{<<~'};'}", /void value expression/, nil, "#{BUG_21669} 1.3")
+ {#
+ x =
+ case a
+ in 1; return
+ in 2; return
+ else return
+ end
+ };
+ end
+
def test_tautological_condition
assert_valid_syntax("def f() return if false and invalid; nil end")
assert_valid_syntax("def f() return unless true or invalid; nil end")
@@ -2018,10 +2272,11 @@ eom
end
obj4 = obj1.clone
obj5 = obj1.clone
+ obj6 = obj1.clone
obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__)
- obj1.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__)
obj4.instance_eval("def foo ...\n bar(...)\n""end", __FILE__, __LINE__)
obj5.instance_eval("def foo ...; bar(...); end", __FILE__, __LINE__)
+ obj6.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__)
klass = Class.new {
def foo(*args, **kws, &block)
@@ -2050,7 +2305,7 @@ eom
end
obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__)
- [obj1, obj2, obj3, obj4, obj5].each do |obj|
+ [obj1, obj2, obj3, obj4, obj5, obj6].each do |obj|
assert_warning('') {
assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x})
}
@@ -2230,13 +2485,13 @@ eom
end
def test_class_module_Object_ancestors
- assert_separately([], <<-RUBY)
+ assert_ruby_status([], <<-RUBY)
m = Module.new
m::Bug18832 = 1
include m
class Bug18832; end
RUBY
- assert_separately([], <<-RUBY)
+ assert_ruby_status([], <<-RUBY)
m = Module.new
m::Bug18832 = 1
include m
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index 6620ccbf33..c3d9dcf56d 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -243,6 +243,10 @@ class TestThread < Test::Unit::TestCase
def test_join_argument_conversion
t = Thread.new {}
+
+ # Make sure that the thread terminates
+ Thread.pass while t.status
+
assert_raise(TypeError) {t.join(:foo)}
limit = Struct.new(:to_f, :count).new(0.05)
@@ -323,7 +327,6 @@ class TestThread < Test::Unit::TestCase
s += 1
end
Thread.pass until t.stop?
- sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # t.stop? behaves unexpectedly with --jit-wait
assert_equal(1, s)
t.wakeup
Thread.pass while t.alive?
@@ -795,7 +798,7 @@ class TestThread < Test::Unit::TestCase
def for_test_handle_interrupt_with_return
Thread.handle_interrupt(Object => :never){
- Thread.current.raise RuntimeError.new("have to be rescured")
+ Thread.current.raise RuntimeError.new("have to be rescued")
return
}
rescue
@@ -812,7 +815,7 @@ class TestThread < Test::Unit::TestCase
assert_nothing_raised do
begin
Thread.handle_interrupt(Object => :never){
- Thread.current.raise RuntimeError.new("have to be rescured")
+ Thread.current.raise RuntimeError.new("have to be rescued")
break
}
rescue
@@ -1477,10 +1480,9 @@ q.pop
end
def test_thread_interrupt_for_killed_thread
- opts = { timeout: 5, timeout_error: nil }
+ pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM
- # prevent SIGABRT from slow shutdown with RJIT
- opts[:reprieve] = 3 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
+ opts = { timeout: 5, timeout_error: nil }
assert_normal_exit(<<-_end, '[Bug #8996]', **opts)
Thread.report_on_exception = false
@@ -1557,4 +1559,139 @@ q.pop
assert_equal(true, t.pending_interrupt?(Exception))
assert_equal(false, t.pending_interrupt?(ArgumentError))
end
+
+ def test_deadlock_backtrace
+ bug21127 = '[ruby-core:120930] [Bug #21127]'
+
+ expected_stderr = [
+ /-:12:in 'Thread#join': No live threads left. Deadlock\? \(fatal\)\n/,
+ /2 threads, 2 sleeps current:\w+ main thread:\w+\n/,
+ /\* #<Thread:\w+ sleep_forever>\n/,
+ :*,
+ /^\s*-:6:in 'Object#frame_for_deadlock_test_2'/,
+ :*,
+ /\* #<Thread:\w+ -:10 sleep_forever>\n/,
+ :*,
+ /^\s*-:2:in 'Object#frame_for_deadlock_test_1'/,
+ :*,
+ ]
+
+ assert_in_out_err([], <<-INPUT, [], expected_stderr, bug21127)
+ def frame_for_deadlock_test_1
+ yield
+ end
+
+ def frame_for_deadlock_test_2
+ yield
+ end
+
+ q = Thread::Queue.new
+ t = Thread.new { frame_for_deadlock_test_1 { q.pop } }
+
+ frame_for_deadlock_test_2 { t.join }
+ INPUT
+ end
+
+ def test_unlock_locked_mutex_with_collected_fiber
+ bug21342 = '[ruby-core:122121] [Bug #21342]'
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}", bug21342)
+ begin;
+ 5.times do
+ m = Mutex.new
+ Thread.new do
+ m.synchronize do
+ end
+ end.join
+ Fiber.new do
+ GC.start
+ m.lock
+ end.resume
+ end
+ end;
+ end
+
+ def test_unlock_locked_mutex_with_collected_fiber2
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ MUTEXES = []
+ 5.times do
+ m = Mutex.new
+ Fiber.new do
+ GC.start
+ m.lock
+ end.resume
+ MUTEXES << m
+ end
+ 10.times do
+ MUTEXES.clear
+ GC.start
+ end
+ end;
+ end
+
+ def test_mutexes_locked_in_fiber_dont_have_aba_issue_with_new_fibers
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ mutexes = 1000.times.map do
+ Mutex.new
+ end
+
+ mutexes.map do |m|
+ Fiber.new do
+ m.lock
+ end.resume
+ end
+
+ GC.start
+
+ 1000.times.map do
+ Fiber.new do
+ raise "FAILED!" if mutexes.any?(&:owned?)
+ end.resume
+ end
+ end;
+ end
+
+ # [Bug #21836]
+ def test_mn_threads_sub_millisecond_sleep
+ assert_separately([{'RUBY_MN_THREADS' => '1'}], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30)
+ begin;
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ 1000.times { sleep 0.0001 }
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ elapsed = t1 - t0
+ assert_operator elapsed, :>=, 0.1, "sub-millisecond sleeps should not return immediately"
+ end;
+ end
+
+ # [Bug #21926]
+ def test_thread_join_during_finalizers
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 60)
+ begin;
+ require 'open3'
+
+ class ProcessWrapper
+ def initialize
+ @stdin, @stdout, @stderr, @wait_thread = Open3.popen3("cat") # hangs until we close our stdin side
+ ObjectSpace.define_finalizer(self, self.class.make_finalizer(@stdin, @stdout, @stderr, @wait_thread))
+ end
+
+ def self.make_finalizer(stdin, stdout, stderr, wait_thread)
+ proc do
+ stdin.close rescue nil
+ stdout.close rescue nil
+ stderr.close rescue nil
+ # On some GC implementations (e.g. mmtk), finalizers run as postponed
+ # jobs which can execute on any thread, including the wait_thread itself.
+ # Guard against joining the current thread.
+ wait_thread.value unless Thread.current == wait_thread
+ end
+ end
+ end
+
+ 20.times { ProcessWrapper.new }
+ GC.stress = true
+ 1000.times { Object.new }
+ end;
+ end
end
diff --git a/test/ruby/test_thread_cv.rb b/test/ruby/test_thread_cv.rb
index eb88b9606c..e5fd513c5c 100644
--- a/test/ruby/test_thread_cv.rb
+++ b/test/ruby/test_thread_cv.rb
@@ -70,13 +70,13 @@ class TestThreadConditionVariable < Test::Unit::TestCase
end
end
end
- sleep 0.1
+ Thread.pass until threads.all?(&:stop?)
mutex.synchronize do
result << "P1"
condvar.broadcast
result << "P2"
end
- Timeout.timeout(5) do
+ Timeout.timeout(60) do
nr_threads.times do |i|
threads[i].join
end
diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb
index 545bf98888..4046185fd2 100644
--- a/test/ruby/test_thread_queue.rb
+++ b/test/ruby/test_thread_queue.rb
@@ -217,7 +217,7 @@ class TestThreadQueue < Test::Unit::TestCase
bug5343 = '[ruby-core:39634]'
Dir.mktmpdir {|d|
- timeout = 60
+ timeout = 120
total_count = 250
begin
assert_normal_exit(<<-"_eom", bug5343, timeout: timeout, chdir: d)
@@ -235,8 +235,14 @@ class TestThreadQueue < Test::Unit::TestCase
end
_eom
rescue Timeout::Error
+ # record load average:
+ uptime = `uptime` rescue nil
+ if uptime && /(load average: [\d.]+),/ =~ uptime
+ la = " (#{$1})"
+ end
+
count = File.read("#{d}/test_thr_kill_count").to_i
- flunk "only #{count}/#{total_count} done in #{timeout} seconds."
+ flunk "only #{count}/#{total_count} done in #{timeout} seconds.#{la}"
end
}
end
@@ -373,7 +379,7 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal false, q.closed?
q << :something
assert_equal q, q.close
- assert q.closed?
+ assert_predicate q, :closed?
assert_raise_with_message(ClosedQueueError, /closed/){q << :nothing}
assert_equal q.pop, :something
assert_nil q.pop
@@ -427,7 +433,7 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal 1, q.size
assert_equal :one, q.pop
- assert q.empty?, "queue not empty"
+ assert_empty q
end
# make sure that shutdown state is handled properly by empty? for the non-blocking case
@@ -561,7 +567,7 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal 0, q.size
assert_equal 3, ary.size
- ary.each{|e| assert [0,1,2,3,4,5].include?(e)}
+ ary.each{|e| assert_include [0,1,2,3,4,5], e}
assert_nil q.pop
prod_threads.each{|t|
diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb
index 333edb8021..b2cbd06a9f 100644
--- a/test/ruby/test_time.rb
+++ b/test/ruby/test_time.rb
@@ -1421,7 +1421,7 @@ class TestTime < Test::Unit::TestCase
# Time objects are common in some code, try to keep them small
omit "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM
omit "GC is in debug" if GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] > 0
- omit "memsize is not accurate due to using malloc_usable_size" if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+ omit "memsize is not accurate due to using malloc_usable_size" if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1
omit "Only run this test on 64-bit" if RbConfig::SIZEOF["void*"] != 8
require 'objspace'
@@ -1433,7 +1433,10 @@ class TestTime < Test::Unit::TestCase
RbConfig::SIZEOF["void*"] # Same size as VALUE
end
sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8
- expect = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + sizeof_timew + sizeof_vtm
+ data_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] + sizeof_timew + sizeof_vtm
+ # Round up to the smallest slot size that fits
+ slot_sizes = GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times.map { |i| GC.stat_heap(i, :slot_size) }
+ expect = slot_sizes.find { |s| s >= data_size } || slot_sizes.last
assert_operator ObjectSpace.memsize_of(t), :<=, expect
rescue LoadError => e
omit "failed to load objspace: #{e.message}"
diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb
index f66cd9bec2..473c3cabcb 100644
--- a/test/ruby/test_time_tz.rb
+++ b/test/ruby/test_time_tz.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: false
require 'test/unit'
-require '-test-/time'
class TestTimeTZ < Test::Unit::TestCase
has_right_tz = true
diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb
index 63d37f4ba4..2c4462eb71 100644
--- a/test/ruby/test_transcode.rb
+++ b/test/ruby/test_transcode.rb
@@ -2320,6 +2320,93 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("A\nB\nC", s.encode(usascii, newline: :lf))
end
+ def test_ractor_lazy_load_encoding
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}", timeout: 60)
+ begin;
+ rs = []
+ autoload_encodings = Encoding.list.select { |e| e.inspect.include?("(autoload)") }.freeze
+ 7.times do
+ rs << Ractor.new(autoload_encodings) do |encodings|
+ str = "\u0300"
+ encodings.each do |enc|
+ str.encode(enc) rescue Encoding::UndefinedConversionError
+ end
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
+ def test_ractor_lazy_load_encoding_random
+ omit 'unstable on s390x and windows' if RUBY_PLATFORM =~ /s390x|mswin/
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30)
+ begin;
+ rs = []
+ 100.times do
+ rs << Ractor.new do
+ "\u0300".encode(Encoding.list.sample) rescue Encoding::UndefinedConversionError
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
+ def test_ractor_asciicompat_encoding_exists
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ rs = []
+ 7.times do
+ rs << Ractor.new do
+ string = "ISO-2022-JP"
+ encoding = Encoding.find(string)
+ 20_000.times do
+ Encoding::Converter.asciicompat_encoding(string)
+ Encoding::Converter.asciicompat_encoding(encoding)
+ end
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
+ def test_ractor_asciicompat_encoding_doesnt_exist
+ assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}", timeout: 60)
+ begin;
+ rs = []
+ NO_EXIST = "I".freeze
+ 7.times do
+ rs << Ractor.new do
+ 50.times do
+ if (val = Encoding::Converter.asciicompat_encoding(NO_EXIST))
+ raise "Got #{val}, expected nil"
+ end
+ end
+ end
+ end
+
+ while rs.any?
+ r, _obj = Ractor.select(*rs)
+ rs.delete(r)
+ end
+ assert_empty rs
+ end;
+ end
+
private
def assert_conversion_both_ways_utf8(utf8, raw, encoding)
diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb
index 49fec2d40e..a305ad6b2a 100644
--- a/test/ruby/test_variable.rb
+++ b/test/ruby/test_variable.rb
@@ -50,6 +50,11 @@ class TestVariable < Test::Unit::TestCase
end
Zeus = Gods.clone
+ class Zeus
+ def ruler5
+ @@rule
+ end
+ end
def test_cloned_allows_setting_cvar
Zeus.class_variable_set(:@@rule, "Athena")
@@ -58,8 +63,12 @@ class TestVariable < Test::Unit::TestCase
zeus = Zeus.new.ruler0
assert_equal "Cronus", god
- assert_equal "Athena", zeus
- assert_not_equal god.object_id, zeus.object_id
+ assert_equal "Cronus", zeus
+
+ assert_equal "Athena", Zeus.new.ruler5
+
+ assert_equal "Cronus", Gods.class_variable_get(:@@rule)
+ assert_equal "Athena", Zeus.class_variable_get(:@@rule)
end
def test_singleton_class_included_class_variable
@@ -120,6 +129,34 @@ class TestVariable < Test::Unit::TestCase
TestVariable.send(:remove_const, :Parent) rescue nil
end
+ def test_cvar_cache_invalidated_by_parent_class_variable_set
+ m = Module.new { class_variable_set(:@@x, 1) }
+ a = Class.new
+ b = Class.new(a) do
+ include m
+ class_eval "def self.x; @@x; end"
+ end
+ assert_equal 1, b.x # warm cache
+ a.class_variable_set(:@@x, 2)
+ error = assert_raise(RuntimeError) { b.x }
+ assert_match(/class variable @@x of .+ is overtaken by .+/, error.message)
+ end
+
+ def test_cvar_cache_invalidated_by_module_class_variable_set
+ m = Module.new
+ n = Module.new
+ b = Class.new do
+ include m
+ include n
+ class_eval "def self.x; @@x; end"
+ end
+ m.class_variable_set(:@@x, 1)
+ assert_equal 1, b.x # warm cache
+ n.class_variable_set(:@@x, 2)
+ error = assert_raise(RuntimeError) { b.x }
+ assert_match(/class variable @@x of .+ is overtaken by .+/, error.message)
+ end
+
def test_cvar_overtaken_by_module
error = eval <<~EORB
class ParentForModule
@@ -388,6 +425,61 @@ class TestVariable < Test::Unit::TestCase
end
end
+ class RemoveIvar
+ class << self
+ attr_reader :ivar
+
+ def add_ivar
+ @ivar = 1
+ end
+ end
+
+ attr_reader :ivar
+
+ def add_ivar
+ @ivar = 1
+ end
+ end
+
+ def add_and_remove_ivar(obj)
+ assert_nil obj.ivar
+ assert_equal 1, obj.add_ivar
+ assert_equal 1, obj.instance_variable_get(:@ivar)
+ assert_equal 1, obj.ivar
+
+ obj.remove_instance_variable(:@ivar)
+ assert_nil obj.ivar
+
+ assert_raise NameError do
+ obj.remove_instance_variable(:@ivar)
+ end
+ end
+
+ def test_remove_instance_variables_object
+ obj = RemoveIvar.new
+ add_and_remove_ivar(obj)
+ add_and_remove_ivar(obj)
+ end
+
+ def test_remove_instance_variables_class
+ add_and_remove_ivar(RemoveIvar)
+ add_and_remove_ivar(RemoveIvar)
+ end
+
+ class RemoveIvarGeneric < Array
+ attr_reader :ivar
+
+ def add_ivar
+ @ivar = 1
+ end
+ end
+
+ def test_remove_instance_variables_generic
+ obj = RemoveIvarGeneric.new
+ add_and_remove_ivar(obj)
+ add_and_remove_ivar(obj)
+ end
+
class ExIvar < Hash
def initialize
@a = 1
@@ -407,6 +499,21 @@ class TestVariable < Test::Unit::TestCase
}
end
+ def test_exivar_resize_with_compaction_stress
+ omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
+ objs = 10_000.times.map do
+ ExIvar.new
+ end
+ EnvUtil.under_gc_compact_stress do
+ 10.times do
+ x = ExIvar.new
+ x.instance_variable_set(:@resize, 1)
+ x
+ end
+ end
+ objs or flunk
+ end
+
def test_local_variables_with_kwarg
bug11674 = '[ruby-core:71437] [Bug #11674]'
v = with_kwargs_11(v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8,v9:9,v10:10,v11:11)
@@ -426,12 +533,55 @@ class TestVariable < Test::Unit::TestCase
end
def test_local_variables_encoding
- α = 1
+ α = 1 or flunk
b = binding
b.eval("".encode("us-ascii"))
assert_equal(%i[α b], b.local_variables)
end
+ def test_genivar_cache
+ bug21547 = '[Bug #21547]'
+ klass = Class.new(Array)
+ instance = klass.new
+ instance.instance_variable_set(:@a1, 1)
+ instance.instance_variable_set(:@a2, 2)
+ Fiber.new do
+ instance.instance_variable_set(:@a3, 3)
+ instance.instance_variable_set(:@a4, 4)
+ end.resume
+ assert_equal 4, instance.instance_variable_get(:@a4), bug21547
+ end
+
+ def test_genivar_cache_free
+ str = +"hello"
+ str.instance_variable_set(:@x, :old_value)
+
+ str.instance_variable_get(:@x) # populate cache
+
+ Fiber.new {
+ str.remove_instance_variable(:@x)
+ str.instance_variable_set(:@x, :new_value)
+ }.resume
+
+ assert_equal :new_value, str.instance_variable_get(:@x)
+ end
+
+ def test_genivar_cache_invalidated_by_gc
+ str = +"hello"
+ str.instance_variable_set(:@x, :old_value)
+
+ str.instance_variable_get(:@x) # populate cache
+
+ Fiber.new {
+ str.remove_instance_variable(:@x)
+ str.instance_variable_set(:@x, :new_value)
+ }.resume
+
+ GC.start
+
+ assert_equal :new_value, str.instance_variable_get(:@x)
+ end
+
private
def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:)
local_variables
diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb
index 709fd5eadf..d183e03391 100644
--- a/test/ruby/test_vm_dump.rb
+++ b/test/ruby/test_vm_dump.rb
@@ -5,8 +5,7 @@ return unless /darwin/ =~ RUBY_PLATFORM
class TestVMDump < Test::Unit::TestCase
def assert_darwin_vm_dump_works(args, timeout=nil)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
+ args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil})
assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300)
end
@@ -15,7 +14,7 @@ class TestVMDump < Test::Unit::TestCase
end
def test_darwin_segv_in_syscall
- assert_darwin_vm_dump_works('-e1.times{Process.kill :SEGV,$$}')
+ assert_darwin_vm_dump_works(['-e1.times{Process.kill :SEGV,$$}'])
end
def test_darwin_invalid_access
diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb
index a2904776bc..2f5c747339 100644
--- a/test/ruby/test_weakmap.rb
+++ b/test/ruby/test_weakmap.rb
@@ -39,6 +39,13 @@ class TestWeakMap < Test::Unit::TestCase
assert_same(:foo, @wm[x])
end
+ def test_aset_returns_value
+ key = Object.new
+ value = Object.new
+
+ assert_same(value, @wm.send(:[]=, key, value))
+ end
+
def assert_weak_include(m, k, n = 100)
if n > 0
return assert_weak_include(m, k, n-1)
@@ -203,7 +210,7 @@ class TestWeakMap < Test::Unit::TestCase
@wm[i] = obj
end
- assert_separately([], <<-'end;')
+ assert_ruby_status([], <<-'end;')
wm = ObjectSpace::WeakMap.new
obj = Object.new
100.times do
@@ -224,7 +231,7 @@ class TestWeakMap < Test::Unit::TestCase
assert_equal(val, wm[key])
end;
- assert_separately(["-W0"], <<-'end;')
+ assert_ruby_status(["-W0"], <<-'end;')
wm = ObjectSpace::WeakMap.new
ary = 10_000.times.map do
@@ -265,4 +272,27 @@ class TestWeakMap < Test::Unit::TestCase
10_000.times { weakmap[Object.new] = Object.new }
RUBY
end
+
+ def test_generational_gc
+ EnvUtil.without_gc do
+ wmap = ObjectSpace::WeakMap.new
+
+ (GC::INTERNAL_CONSTANTS[:RVALUE_OLD_AGE] - 1).times { GC.start }
+
+ retain = []
+ 50.times do
+ k = Object.new
+ wmap[k] = true
+ retain << k
+ end
+
+ GC.start # WeakMap promoted, other objects still young
+
+ retain.clear
+
+ GC.start(full_mark: false)
+
+ wmap.keys.each(&:itself) # call method on keys to cause crash
+ end
+ end
end
diff --git a/test/ruby/test_yield.rb b/test/ruby/test_yield.rb
index 9b2b2f37e0..e7e65fce9e 100644
--- a/test/ruby/test_yield.rb
+++ b/test/ruby/test_yield.rb
@@ -401,7 +401,7 @@ class TestRubyYieldGen < Test::Unit::TestCase
def test_block_cached_argc
# [Bug #11451]
- assert_separately([], <<-"end;")
+ assert_ruby_status([], <<-"end;")
class Yielder
def each
yield :x, :y, :z
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index 0e476588f4..0d7fe66e1c 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -133,7 +133,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_yjit_enable_with_monkey_patch
- assert_separately(%w[--yjit-disable], <<~RUBY)
+ assert_ruby_status(%w[--yjit-disable], <<~RUBY)
# This lets rb_method_entry_at(rb_mKernel, ...) return NULL
Kernel.prepend(Module.new)
@@ -142,6 +142,36 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_yjit_enable_with_valid_runtime_call_threshold_option
+ assert_in_out_err(['--yjit-disable', '-e',
+ 'RubyVM::YJIT.enable(call_threshold: 1); puts RubyVM::YJIT.enabled?']) do |stdout, stderr, _status|
+ assert_empty stderr
+ assert_include stdout.join, "true"
+ end
+ end
+
+ def test_yjit_enable_with_invalid_runtime_call_threshold_option
+ assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(mem_size: 0)']) do |stdout, stderr, status|
+ assert_not_empty stderr
+ assert_match(/ArgumentError/, stderr.join)
+ assert_equal 1, status.exitstatus
+ end
+ end
+
+ def test_yjit_enable_with_invalid_runtime_mem_size_option
+ assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(mem_size: 0)']) do |stdout, stderr, status|
+ assert_not_empty stderr
+ assert_match(/ArgumentError/, stderr.join)
+ assert_equal 1, status.exitstatus
+ end
+ end
+
+ if JITSupport.zjit_supported?
+ def test_yjit_enable_with_zjit_enabled
+ assert_in_out_err(['--zjit'], 'puts RubyVM::YJIT.enable', ['false'], ['Only one JIT can be enabled at the same time.'])
+ end
+ end
+
def test_yjit_stats_and_v_no_error
_stdout, stderr, _status = invoke_ruby(%w(-v --yjit-stats), '', true, true)
refute_includes(stderr, "NoMethodError")
@@ -517,7 +547,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_opt_getconstant_path_slowpath
- assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2)
+ assert_compiles(<<~RUBY, result: [42, 42, 1, 1], call_threshold: 2)
class A
FOO = 42
class << self
@@ -614,6 +644,40 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ STRUCT_MAX_EMBEDDED_MEMBERS = (
+ GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] -
+ GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] -
+ GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]
+ ) / RbConfig::SIZEOF["void*"]
+
+ def test_spilled_struct_aref
+ omit("FIXME: https://github.com/Shopify/ruby/issues/977")
+ assert_compiles(<<~RUBY)
+ LargeStruct = Struct.new(:foo, :bar, *(#{STRUCT_MAX_EMBEDDED_MEMBERS} - 2).times.map { :"m_\#{it}" })
+
+ def foo(obj)
+ foo = obj.foo
+ raise "Expected 1, got: \#{foo}" unless foo == 1
+ bar = obj.bar
+ raise "Expected 2, got: \#{bar}" unless bar == 2
+ end
+
+ embedded_struct = LargeStruct.new(1, 2)
+ # Bump RCLASS_MAX_IV_COUNT for LargeStruct
+ embedded_struct.instance_variable_set(:@test, 1)
+
+ # Next allocation reserves space for the imemo/fields reference.
+ heap_struct = LargeStruct.new(1, 2)
+
+ RubyVM::YJIT.reset_stats!
+
+ foo(embedded_struct)
+ foo(embedded_struct)
+ foo(heap_struct)
+ foo(heap_struct)
+ RUBY
+ end
+
def test_struct_aset
assert_compiles(<<~RUBY)
def foo(obj)
@@ -627,6 +691,26 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_struct_aset_guards_recv_is_not_frozen
+ assert_compiles(<<~RUBY, result: :ok, exits: { opt_send_without_block: 1 })
+ def foo(obj)
+ obj.foo = 123
+ end
+
+ Foo = Struct.new(:foo)
+ obj = Foo.new(123)
+ 100.times do
+ foo(obj)
+ end
+ obj.freeze
+ begin
+ foo(obj)
+ rescue FrozenError
+ :ok
+ end
+ RUBY
+ end
+
def test_getblockparam
assert_compiles(<<~'RUBY', insns: [:getblockparam])
def foo &blk
@@ -923,6 +1007,40 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_super_bmethod
+ # Bmethod defined at class scope
+ assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: true, exits: {})
+ class SuperItself
+ define_method(:itself) { super() }
+ end
+
+ obj = SuperItself.new
+ obj.itself
+ obj.itself == obj
+ RUBY
+
+ # Bmethod defined inside a method (the block's local_iseq is ISEQ_TYPE_METHOD
+ # but the CME is at the bmethod frame, not the enclosing method's frame)
+ assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: "Base#foo via bmethod", exits: {})
+ class Base
+ def foo = "Base#foo"
+ end
+
+ class SetupHelper
+ def add_bmethod_to(klass)
+ klass.define_method(:foo) { super() + " via bmethod" }
+ end
+ end
+
+ class Target < Base; end
+
+ SetupHelper.new.add_bmethod_to(Target)
+ obj = Target.new
+ obj.foo
+ obj.foo
+ RUBY
+ end
+
# Tests calling a variadic cfunc with many args
def test_build_large_struct
assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], call_threshold: 2)
@@ -1316,7 +1434,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_tracing_str_uplus
- assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok, exits: { putspecialobject: 1, definemethod: 1 })
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok, exits: { putspecialobject: 1 })
def str_uplus
_ = 1
_ = 2
@@ -1504,14 +1622,6 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
- def test_opt_aref_with
- assert_compiles(<<~RUBY, insns: %i[opt_aref_with], result: "bar", frozen_string_literal: false)
- h = {"foo" => "bar"}
-
- h["foo"]
- RUBY
- end
-
def test_proc_block_arg
assert_compiles(<<~RUBY, result: [:proc, :no_block])
def yield_if_given = block_given? ? yield : :no_block
@@ -1637,7 +1747,7 @@ class TestYJIT < Test::Unit::TestCase
[
stats[:object_shape_count].is_a?(Integer),
- stats[:ratio_in_yjit].is_a?(Float),
+ stats[:ratio_in_yjit].nil? || stats[:ratio_in_yjit].is_a?(Float),
].all?
RUBY
end
@@ -1648,7 +1758,7 @@ class TestYJIT < Test::Unit::TestCase
3.times { test }
# Collect single stat.
- stat = RubyVM::YJIT.runtime_stats(:ratio_in_yjit)
+ stat = RubyVM::YJIT.runtime_stats(:yjit_alloc_size)
# Ensure this invocation had stats.
return true unless RubyVM::YJIT.runtime_stats[:all_stats]
@@ -1750,6 +1860,62 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_proc_block_with_kwrest
+ # When the bug was present this required --yjit-stats to trigger.
+ assert_compiles(<<~RUBY, result: {extra: 5})
+ def foo = bar(w: 1, x: 2, y: 3, z: 4, extra: 5, &proc { _1 })
+ def bar(w:, x:, y:, z:, **kwrest) = yield kwrest
+
+ GC.stress = true
+ foo
+ foo
+ RUBY
+ end
+
+ def test_yjit_dump_insns
+ # Testing that this undocumented debugging feature doesn't crash
+ args = [
+ '--yjit-call-threshold=1',
+ '--yjit-dump-insns',
+ '-e def foo(case:) = {case:}[:case]',
+ '-e foo(case:0)',
+ ]
+ _out, _err, status = invoke_ruby(args, '', true, true)
+ assert_not_predicate(status, :signaled?)
+ end
+
+ def test_yjit_prelude_kernel_prepend
+ # Simulate what bundler/setup can do: prepend a module to Kernel during
+ # the prelude via the BUNDLER_SETUP mechanism in rubygems.rb:
+ # require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler)
+ Tempfile.create(["kernel_prepend", ".rb"]) do |f|
+ f.write("Kernel.prepend(Module.new)\n")
+ f.flush
+ assert_separately([{ "BUNDLER_SETUP" => f.path }, "--enable=gems", "--yjit"], "", ignore_stderr: true)
+ end
+ end
+
+ def test_exceptional_entry_into_env_escaped_before_yjit_enablement
+ threshold = 2
+ assert_separately(["--disable-all", "--yjit-disable", "--yjit-call-threshold=#{threshold}"], <<~RUBY)
+ def run
+ @captured_env = ->{}
+ RubyVM::YJIT.enable
+
+ i = 0
+ while i < #{threshold}
+ next_i = i + 1
+ from_break = tap { break i + 1 } # break from the block generates an exceptional entry
+ assert_equal(from_break, next_i, '[Bug #21941]')
+ i = next_i
+ end
+ end
+
+ run
+ assert_equal(#{threshold}, @captured_env.binding.local_variable_get(:i))
+ RUBY
+ end
+
private
def code_gc_helpers
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
new file mode 100644
index 0000000000..a56fea6d51
--- /dev/null
+++ b/test/ruby/test_zjit.rb
@@ -0,0 +1,556 @@
+# frozen_string_literal: true
+#
+# This set of tests can be run with:
+# make test-all TESTS=test/ruby/test_zjit.rb
+
+require 'test/unit'
+require 'envutil'
+require_relative '../lib/jit_support'
+return unless JITSupport.zjit_supported?
+
+class TestZJIT < Test::Unit::TestCase
+ def test_enabled
+ assert_runs 'false', <<~RUBY, zjit: false
+ RubyVM::ZJIT.enabled?
+ RUBY
+ assert_runs 'true', <<~RUBY, zjit: true
+ RubyVM::ZJIT.enabled?
+ RUBY
+ end
+
+ def test_stats_enabled
+ assert_runs 'false', <<~RUBY, stats: false
+ RubyVM::ZJIT.stats_enabled?
+ RUBY
+ assert_runs 'true', <<~RUBY, stats: true
+ RubyVM::ZJIT.stats_enabled?
+ RUBY
+ end
+
+ def test_stats_string_no_zjit
+ assert_runs 'nil', <<~RUBY, zjit: false
+ RubyVM::ZJIT.stats_string
+ RUBY
+ assert_runs 'true', <<~RUBY, stats: false
+ RubyVM::ZJIT.stats_string.is_a?(String)
+ RUBY
+ assert_runs 'true', <<~RUBY, stats: true
+ RubyVM::ZJIT.stats_string.is_a?(String)
+ RUBY
+ end
+
+ def test_stats_quiet
+ # Test that --zjit-stats-quiet collects stats but doesn't print them
+ script = <<~RUBY
+ def test = 42
+ test
+ test
+ puts RubyVM::ZJIT.stats_enabled?
+ RUBY
+
+ stats_header = "***ZJIT: Printing ZJIT statistics on exit***"
+
+ # With --zjit-stats, stats should be printed to stderr
+ out, err, status = eval_with_jit(script, stats: true)
+ assert_success(out, err, status)
+ assert_includes(err, stats_header)
+ assert_equal("true\n", out)
+
+ # With --zjit-stats-quiet, stats should NOT be printed but still enabled
+ out, err, status = eval_with_jit(script, stats: :quiet)
+ assert_success(out, err, status)
+ refute_includes(err, stats_header)
+ assert_equal("true\n", out)
+
+ # With --zjit-stats=<path>, stats should be printed to the path
+ Tempfile.create("zjit-stats-") {|tmp|
+ stats_file = tmp.path
+ tmp.puts("Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...")
+ tmp.close
+
+ out, err, status = eval_with_jit(script, stats: stats_file)
+ assert_success(out, err, status)
+ refute_includes(err, stats_header)
+ assert_equal("true\n", out)
+ assert_equal stats_header, File.open(stats_file) {|f| f.gets(chomp: true)}, "should be overwritten"
+ }
+ end
+
+ def test_enable_through_env
+ child_env = {'RUBY_YJIT_ENABLE' => nil, 'RUBY_ZJIT_ENABLE' => '1'}
+ assert_in_out_err([child_env, '-v'], '') do |stdout, stderr|
+ assert_includes(stdout.first, '+ZJIT')
+ assert_equal([], stderr)
+ end
+ end
+
+ def test_zjit_enable
+ # --disable-all is important in case the build/environment has YJIT enabled by
+ # default through e.g. -DYJIT_FORCE_ENABLE. Can't enable ZJIT when YJIT is on.
+ assert_separately(["--disable-all"], <<~'RUBY')
+ refute_predicate RubyVM::ZJIT, :enabled?
+ refute_predicate RubyVM::ZJIT, :stats_enabled?
+ refute_includes RUBY_DESCRIPTION, "+ZJIT"
+
+ RubyVM::ZJIT.enable
+
+ assert_predicate RubyVM::ZJIT, :enabled?
+ refute_predicate RubyVM::ZJIT, :stats_enabled?
+ assert_includes RUBY_DESCRIPTION, "+ZJIT"
+ RUBY
+ end
+
+ def test_zjit_disable
+ assert_separately(["--zjit", "--zjit-disable"], <<~'RUBY')
+ refute_predicate RubyVM::ZJIT, :enabled?
+ refute_includes RUBY_DESCRIPTION, "+ZJIT"
+
+ RubyVM::ZJIT.enable
+
+ assert_predicate RubyVM::ZJIT, :enabled?
+ assert_includes RUBY_DESCRIPTION, "+ZJIT"
+ RUBY
+ end
+
+ def test_zjit_prelude_kernel_prepend
+ # Simulate what bundler/setup can do: prepend a module to Kernel during
+ # the prelude via the BUNDLER_SETUP mechanism in rubygems.rb:
+ # require ENV["BUNDLER_SETUP"] if ENV["BUNDLER_SETUP"] && !defined?(Bundler)
+ Tempfile.create(["kernel_prepend", ".rb"]) do |f|
+ f.write("Kernel.prepend(Module.new)\n")
+ f.flush
+ assert_separately([{ "BUNDLER_SETUP" => f.path }, "--enable=gems", "--zjit"], "", ignore_stderr: true)
+ end
+ end
+
+ def test_zjit_enable_respects_existing_options
+ assert_separately(['--zjit-disable', '--zjit-stats-quiet'], <<~RUBY)
+ refute_predicate RubyVM::ZJIT, :enabled?
+ assert_predicate RubyVM::ZJIT, :stats_enabled?
+
+ RubyVM::ZJIT.enable
+
+ assert_predicate RubyVM::ZJIT, :enabled?
+ assert_predicate RubyVM::ZJIT, :stats_enabled?
+ RUBY
+ end
+
+ def test_toplevel_binding
+ # Not using assert_compiles, which doesn't use the toplevel frame for `test_script`.
+ out, err, status = eval_with_jit(%q{
+ a = 1
+ b = 2
+ TOPLEVEL_BINDING.local_variable_set(:b, 3)
+ c = 4
+ print [a, b, c]
+ })
+ assert_success(out, err, status)
+ assert_equal "[1, 3, 4]", out
+ end
+
+ def test_send_exit_with_uninitialized_locals
+ assert_runs 'nil', %q{
+ def entry(init)
+ function_stub_exit(init)
+ end
+
+ def function_stub_exit(init)
+ uninitialized_local = 1 if init
+ uninitialized_local
+ end
+
+ entry(true) # profile and set 1 to the local slot
+ entry(false)
+ }, call_threshold: 2, allowed_iseqs: 'entry@-e:2'
+ end
+
+ def test_opt_new_with_custom_allocator
+ assert_compiles '"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"', %q{
+ require "digest"
+ def test = Digest::SHA256.new.hexdigest
+ test; test
+ }, insns: [:opt_new], call_threshold: 2
+ end
+
+ def test_opt_new_with_custom_allocator_raises
+ assert_compiles '[42, 42]', %q{
+ require "digest"
+ class C < Digest::Base; end
+ def test
+ begin
+ Digest::Base.new
+ rescue NotImplementedError
+ 42
+ end
+ end
+ [test, test]
+ }, insns: [:opt_new], call_threshold: 2
+ end
+
+ def test_uncached_getconstant_path
+ assert_compiles RUBY_COPYRIGHT.dump, %q{
+ def test = RUBY_COPYRIGHT
+ test
+ }, call_threshold: 1, insns: [:opt_getconstant_path]
+ end
+
+ def test_getconstant_path_autoload
+ # A constant-referencing expression can run arbitrary code through Kernel#autoload.
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, 'test_getconstant_path_autoload.rb')
+ File.write(autoload_path, 'X = RUBY_COPYRIGHT')
+
+ assert_compiles RUBY_COPYRIGHT.dump, %Q{
+ Object.autoload(:X, #{File.realpath(autoload_path).inspect})
+ def test = X
+ test
+ }, call_threshold: 1, insns: [:opt_getconstant_path]
+ end
+ end
+
+ def test_send_backtrace
+ backtrace = [
+ "-e:2:in 'Object#jit_frame1'",
+ "-e:3:in 'Object#entry'",
+ "-e:5:in 'block in <main>'",
+ "-e:6:in '<main>'",
+ ]
+ assert_compiles backtrace.inspect, %q{
+ def jit_frame2 = caller # 1
+ def jit_frame1 = jit_frame2 # 2
+ def entry = jit_frame1 # 3
+ entry # profile send # 4
+ entry # 5
+ }, call_threshold: 2
+ end
+
+ # tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and
+ # b) being reliably ordered after all the other instructions.
+ def test_instruction_order
+ insn_names = RubyVM::INSTRUCTION_NAMES
+ zjit, others = insn_names.map.with_index.partition { |name, _| name.start_with?('zjit_') }
+ zjit_indexes = zjit.map(&:last)
+ other_indexes = others.map(&:last)
+ zjit_indexes.product(other_indexes).each do |zjit_index, other_index|
+ assert zjit_index > other_index, "'#{insn_names[zjit_index]}' at #{zjit_index} "\
+ "must be defined after '#{insn_names[other_index]}' at #{other_index}"
+ end
+ end
+
+ def test_require_rubygems
+ assert_runs 'true', %q{
+ require 'rubygems'
+ }, call_threshold: 2
+ end
+
+ def test_require_rubygems_with_auto_compact
+ omit("GC.auto_compact= support is required for this test") unless GC.respond_to?(:auto_compact=)
+ assert_runs 'true', %q{
+ GC.auto_compact = true
+ require 'rubygems'
+ }, call_threshold: 2
+ end
+
+ def test_stats_availability
+ assert_runs '[true, true]', %q{
+ def test = 1
+ test
+ [
+ RubyVM::ZJIT.stats[:zjit_insn_count] > 0,
+ RubyVM::ZJIT.stats(:zjit_insn_count) > 0,
+ ]
+ }, stats: true
+ end
+
+ def test_stats_consistency
+ assert_runs '[]', %q{
+ def test = 1
+ test # increment some counters
+
+ RubyVM::ZJIT.stats.to_a.filter_map do |key, value|
+ # The value may be incremented, but the class should stay the same
+ other_value = RubyVM::ZJIT.stats(key)
+ if value.class != other_value.class
+ [key, value, other_value]
+ end
+ end
+ }, stats: true
+ end
+
+ def test_reset_stats
+ assert_runs 'true', %q{
+ def test = 1
+ 100.times { test }
+
+ # Get initial stats and verify they're non-zero
+ initial_stats = RubyVM::ZJIT.stats
+
+ # Reset the stats
+ RubyVM::ZJIT.reset_stats!
+
+ # Get stats after reset
+ reset_stats = RubyVM::ZJIT.stats
+
+ [
+ # After reset, counters should be zero or at least much smaller
+ # (some instructions might execute between reset and reading stats)
+ :zjit_insn_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] },
+ :compiled_iseq_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] }
+ ].all?
+ }, stats: true
+ end
+
+ def test_zjit_option_uses_array_each_in_ruby
+ omit 'ZJIT wrongly compiles Array#each, so it is disabled for now'
+ assert_runs '"<internal:array>"', %q{
+ Array.instance_method(:each).source_location&.first
+ }
+ end
+
+ def test_line_tracepoint_on_c_method
+ assert_compiles '"[[:line, true]]"', %q{
+ events = []
+ events.instance_variable_set(
+ :@tp,
+ TracePoint.new(:line) { |tp| events << [tp.event, tp.lineno] if tp.path == __FILE__ }
+ )
+ def events.to_str
+ @tp.enable; ''
+ end
+
+ # Stay in generated code while enabling tracing
+ def events.compiled(obj)
+ String(obj)
+ @tp.disable; __LINE__
+ end
+
+ line = events.compiled(events)
+ events[0][-1] = (events[0][-1] == line)
+
+ events.to_s # can't dump events as it's a singleton object AND it has a TracePoint instance variable, which also can't be dumped
+ }
+ end
+
+ def test_targeted_line_tracepoint_in_c_method_call
+ assert_compiles '"[true]"', %q{
+ events = []
+ events.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno })
+ def events.to_str
+ @tp.enable(target: method(:compiled))
+ ''
+ end
+
+ # Stay in generated code while enabling tracing
+ def events.compiled(obj)
+ String(obj)
+ __LINE__
+ end
+
+ line = events.compiled(events)
+ events[0] = (events[0] == line)
+
+ events.to_s # can't dump events as it's a singleton object AND it has a TracePoint instance variable, which also can't be dumped
+ }
+ end
+
+ def test_regression_cfp_sp_set_correctly_before_leaf_gc_call
+ assert_compiles ':ok', %q{
+ def check(l, r)
+ return 1 unless l
+ 1 + check(*l) + check(*r)
+ end
+
+ def tree(depth)
+ # This duparray is our leaf-gc target.
+ return [nil, nil] unless depth > 0
+
+ # Modify the local and pass it to the following calls.
+ depth -= 1
+ [tree(depth), tree(depth)]
+ end
+
+ def test
+ GC.stress = true
+ 2.times do
+ t = tree(11)
+ check(*t)
+ end
+ :ok
+ end
+
+ test
+ }, call_threshold: 14, num_profiles: 5
+ end
+
+ def test_exit_tracing
+ # Smoke test: --zjit-trace-exits writes a Fuchsia trace (.fxt) file to /tmp
+ assert_compiles('true', <<~RUBY, extra_args: ['--zjit-trace-exits'])
+ def test(object) = object.itself
+
+ # induce an exit just for good measure
+ array = []
+ test(array)
+ test(array)
+ def array.itself = :not_itself
+ test(array)
+
+ fxt_files = Dir.glob("/tmp/perfetto-\#{Process.pid}.fxt")
+ result = fxt_files.length == 1 && !File.empty?(fxt_files.first)
+ File.unlink(*fxt_files)
+ result
+ RUBY
+ end
+
+ def test_send_no_profiles_with_disabled_specialized_instruction
+ # Regression test: when specialized_instruction is disabled (as power_assert does),
+ # eval'd code uses `send` instead of `opt_send_without_block`, producing SendNoProfiles.
+ # The `times` call with a literal block is the SendNoProfiles send whose exit profiling
+ # triggers recompilation of `run`. After recompilation, `make`'s eval("proc { }") crashes
+ # in vm_make_env_each because the caller frame's EP[-1] (specval) has a stale value.
+ assert_runs ':ok', <<~RUBY
+ RubyVM::InstructionSequence.compile_option = { specialized_instruction: false }
+ eval <<~'INNERRUBY'
+ def make = eval("proc { }")
+ def run(n) = n.times { make }
+ INNERRUBY
+ run(6)
+ :ok
+ RUBY
+ end
+
+ def test_float_arithmetic
+ assert_compiles '4.0', 'def test = 1.5 + 2.5; test'
+ assert_compiles '6.0', 'def test = 2.0 * 3.0; test'
+ assert_compiles '1.5', 'def test = 3.5 - 2.0; test'
+ assert_compiles '2.5', 'def test = 5.0 / 2.0; test'
+ assert_compiles '4.5', 'def test = 1.5 * 3; test' # Float * Fixnum
+ assert_compiles 'true', 'def test = (Float::NAN + 1.0).nan?; test'
+ assert_compiles 'Infinity', 'def test = Float::INFINITY * 2.0; test'
+ assert_compiles '3', 'def test = 3.7.to_i; test'
+ assert_compiles '-2', 'def test = (-2.9).to_i; test'
+ end
+
+ private
+
+ # Assert that every method call in `test_script` can be compiled by ZJIT
+ # at a given call_threshold
+ def assert_compiles(expected, test_script, insns: [], **opts)
+ assert_runs(expected, test_script, insns:, assert_compiles: true, **opts)
+ end
+
+ # Assert that `test_script` runs successfully with ZJIT enabled.
+ # Unlike `assert_compiles`, `assert_runs(assert_compiles: false)`
+ # allows ZJIT to skip compiling methods.
+ def assert_runs(expected, test_script, insns: [], assert_compiles: false, **opts)
+ pipe_fd = 3
+ disasm_method = :test
+
+ script = <<~RUBY
+ ret_val = (_test_proc = -> { #{('RubyVM::ZJIT.assert_compiles; ' if assert_compiles)}#{test_script.lstrip} }).call
+ result = {
+ ret_val:,
+ #{ unless insns.empty?
+ "insns: RubyVM::InstructionSequence.of(method(#{disasm_method.inspect})).to_a"
+ end}
+ }
+ IO.open(#{pipe_fd}).write(Marshal.dump(result))
+ RUBY
+
+ out, err, status, result = eval_with_jit(script, pipe_fd:, **opts)
+ assert_success(out, err, status)
+
+ result = Marshal.load(result)
+ assert_equal(expected, result.fetch(:ret_val).inspect)
+
+ unless insns.empty?
+ iseq = result.fetch(:insns)
+ assert_equal(
+ "YARVInstructionSequence/SimpleDataFormat",
+ iseq.first,
+ "Failed to get ISEQ disassembly. " \
+ "Make sure to put code directly under the '#{disasm_method}' method."
+ )
+ iseq_insns = iseq.last
+
+ expected_insns = Set.new(insns)
+ iseq_insns.each do
+ next unless it.is_a?(Array)
+ expected_insns.delete(it.first)
+ end
+ assert(expected_insns.empty?, -> { "Not present in ISeq: #{expected_insns.to_a}" })
+ end
+ end
+
+ # Run a Ruby process with ZJIT options and a pipe for writing test results
+ def eval_with_jit(
+ script,
+ call_threshold: 1,
+ num_profiles: 1,
+ zjit: true,
+ stats: false,
+ debug: true,
+ allowed_iseqs: nil,
+ extra_args: nil,
+ timeout: 1000,
+ pipe_fd: nil
+ )
+ args = ["--disable-gems", *extra_args]
+ if zjit
+ args << "--zjit-call-threshold=#{call_threshold}"
+ args << "--zjit-num-profiles=#{num_profiles}"
+ case stats
+ when true
+ args << "--zjit-stats"
+ when :quiet
+ args << "--zjit-stats-quiet"
+ else
+ args << "--zjit-stats=#{stats}" if stats
+ end
+ args << "--zjit-debug" if debug
+ if allowed_iseqs
+ jitlist = Tempfile.new("jitlist")
+ jitlist.write(allowed_iseqs)
+ jitlist.close
+ args << "--zjit-allowed-iseqs=#{jitlist.path}"
+ end
+ end
+ args << "-e" << script_shell_encode(script)
+ ios = {}
+ if pipe_fd
+ pipe_r, pipe_w = IO.pipe
+ # Separate thread so we don't deadlock when
+ # the child ruby blocks writing the output to pipe_fd
+ pipe_out = nil
+ pipe_reader = Thread.new do
+ pipe_out = pipe_r.read
+ pipe_r.close
+ end
+ ios[pipe_fd] = pipe_w
+ end
+ result = EnvUtil.invoke_ruby(args, '', true, true, rubybin: RbConfig.ruby, timeout: timeout, ios:)
+ if pipe_fd
+ pipe_w.close
+ pipe_reader.join(timeout)
+ result << pipe_out
+ end
+ result
+ ensure
+ pipe_reader&.kill
+ pipe_reader&.join(timeout)
+ pipe_r&.close
+ pipe_w&.close
+ jitlist&.unlink
+ end
+
+ def assert_success(out, err, status)
+ message = "exited with status #{status.to_i}"
+ message << "\nstdout:\n```\n#{out}```\n" unless out.empty?
+ message << "\nstderr:\n```\n#{err}```\n" unless err.empty?
+ assert status.success?, message
+ end
+
+ def script_shell_encode(s)
+ # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
+ s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
+ end
+end
diff --git a/test/rubygems/coverage_setup.rb b/test/rubygems/coverage_setup.rb
new file mode 100644
index 0000000000..7e978e59e0
--- /dev/null
+++ b/test/rubygems/coverage_setup.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+# This file is loaded via -r flag BEFORE rubygems to enable coverage tracking
+# of rubygems boot files. It must be used with --disable-gems and -Ilib
+# so that Coverage.start runs before rubygems is loaded.
+
+require "coverage"
+Coverage.start(lines: true)
+require "rubygems"
diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb
index 3514954103..2411dbc649 100644
--- a/test/rubygems/helper.rb
+++ b/test/rubygems/helper.rb
@@ -3,17 +3,42 @@
require "rubygems"
begin
+ raise LoadError if ENV["GEM_COMMAND"]
+
+ gem "simplecov_json_formatter"
+ require "simplecov"
+
+ unless ENV["SIMPLECOV_SUBPROCESS"]
+ SimpleCov.start do
+ command_name "rubygems"
+ root File.expand_path("../..", __dir__)
+ coverage_dir File.expand_path("../../coverage", __dir__)
+
+ add_filter "/test/"
+ add_filter "/bundler/"
+ add_filter "/tool/"
+ add_filter "/lib/rubygems/vendor/"
+ add_filter ".gemspec"
+ end
+
+ # Prevent SimpleCov from running in subprocesses spawned by assert_separately
+ ENV["SIMPLECOV_SUBPROCESS"] = "1"
+ end
+rescue LoadError
+ # SimpleCov is not installed
+end
+
+begin
gem "test-unit", "~> 3.0"
rescue Gem::LoadError
end
require "test/unit"
-ENV["JARS_SKIP"] = "true" if Gem.java_platform? # avoid unnecessary and noisy `jar-dependencies` post install hook
-
require "fileutils"
require "pathname"
require "pp"
+require "rubygems/installer"
require "rubygems/package"
require "shellwords"
require "tmpdir"
@@ -21,6 +46,24 @@ require "rubygems/vendor/uri/lib/uri"
require "zlib"
require_relative "mock_gem_ui"
+# JRuby on Windows raises TypeError inside File.symlink (the wincode helper
+# trips on a nil path), so any test that exercises Gem::Installer's symlink
+# branch fails to even install the gem. Real users hit the wrapper branch via
+# `gem install` (DependencyInstaller passes wrappers: true), so mirror that
+# default for direct Gem::Installer.at callers in the test suite.
+if Gem.win_platform? && Gem.java_platform?
+ module Gem::InstallerDefaultWrappersOnJRubyWindows
+ def at(path, options = {})
+ super(path, { wrappers: true }.merge(options))
+ end
+
+ def for_spec(spec, options = {})
+ super(spec, { wrappers: true }.merge(options))
+ end
+ end
+ Gem::Installer.singleton_class.prepend(Gem::InstallerDefaultWrappersOnJRubyWindows)
+end
+
module Gem
##
# Allows setting the gem path searcher.
@@ -62,6 +105,44 @@ class Gem::Command
end
end
+class Gem::Installer
+ # Copy from Gem::Installer#install with install_as_default option from old version
+ def install_default_gem
+ pre_install_checks
+
+ run_pre_install_hooks
+
+ spec.loaded_from = default_spec_file
+
+ FileUtils.rm_rf gem_dir
+ FileUtils.rm_rf spec.extension_dir
+
+ dir_mode = options[:dir_mode]
+ FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755
+
+ extract_bin
+ write_default_spec
+
+ generate_bin
+ generate_plugins
+
+ File.chmod(dir_mode, gem_dir) if dir_mode
+
+ say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil?
+
+ Gem::Specification.add_spec(spec)
+
+ load_plugin
+
+ run_post_install_hooks
+
+ spec
+ rescue Errno::EACCES => e
+ # Permission denied - /path/to/foo
+ raise Gem::FilePermissionError, e.message.split(" - ").last
+ end
+end
+
##
# RubyGemTestCase provides a variety of methods for testing rubygems and
# gem-related behavior in a sandbox. Through RubyGemTestCase you can install
@@ -297,8 +378,12 @@ class Gem::TestCase < Test::Unit::TestCase
ENV["XDG_CONFIG_HOME"] = nil
ENV["XDG_DATA_HOME"] = nil
ENV["XDG_STATE_HOME"] = nil
+ ENV["MAKEFLAGS"] = nil
ENV["SOURCE_DATE_EPOCH"] = nil
ENV["BUNDLER_VERSION"] = nil
+ ENV["BUNDLE_CONFIG"] = nil
+ ENV["BUNDLE_USER_CONFIG"] = nil
+ ENV["BUNDLE_USER_HOME"] = nil
ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = "true"
@current_dir = Dir.pwd
@@ -402,8 +487,9 @@ class Gem::TestCase < Test::Unit::TestCase
Gem::RemoteFetcher.fetcher = Gem::FakeFetcher.new
@gem_repo = "http://gems.example.com/"
+ Gem.instance_variable_set :@default_sources, [@gem_repo]
+ Gem.instance_variable_set :@sources, nil
@uri = Gem::URI.parse @gem_repo
- Gem.sources.replace [@gem_repo]
Gem.searcher = nil
Gem::SpecFetcher.fetcher = nil
@@ -420,6 +506,9 @@ class Gem::TestCase < Test::Unit::TestCase
@orig_hooks[name] = Gem.send(name).dup
end
+ Gem::Platform.const_get(:GENERIC_CACHE).clear
+ Gem::Platform.const_get(:GENERICS).each {|g| Gem::Platform.const_get(:GENERIC_CACHE)[g] = g }
+
@marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}"
@orig_loaded_features = $LOADED_FEATURES.dup
end
@@ -682,15 +771,19 @@ class Gem::TestCase < Test::Unit::TestCase
path
end
+ def write_dummy_extconf(gem_name)
+ write_file File.join(@tempdir, "extconf.rb") do |io|
+ io.puts "require 'mkmf'"
+ yield io if block_given?
+ io.puts "create_makefile '#{gem_name}'"
+ end
+ end
+
##
- # Load a YAML string, the psych 3 way
+ # Load a YAML string using the safe loader with gem-spec permitted classes.
def load_yaml(yaml)
- if Psych.respond_to?(:unsafe_load)
- Psych.unsafe_load(yaml)
- else
- Psych.load(yaml)
- end
+ Gem::SafeYAML.safe_load(yaml)
end
##
@@ -715,7 +808,7 @@ class Gem::TestCase < Test::Unit::TestCase
#
# Use this with #write_file to build an installed gem.
- def quick_gem(name, version="2")
+ def quick_gem(name, version = "2")
require "rubygems/specification"
spec = Gem::Specification.new do |s|
@@ -801,8 +894,8 @@ class Gem::TestCase < Test::Unit::TestCase
def install_default_gems(*specs)
specs.each do |spec|
- installer = Gem::Installer.for_spec(spec, install_as_default: true)
- installer.install
+ installer = Gem::Installer.for_spec(spec)
+ installer.install_default_gem
Gem.register_default_spec(spec)
end
end
@@ -1024,7 +1117,7 @@ Also, a list:
# Add +spec+ to +@fetcher+ serving the data in the file +path+.
# +repo+ indicates which repo to make +spec+ appear to be in.
- def add_to_fetcher(spec, path=nil, repo=@gem_repo)
+ def add_to_fetcher(spec, path = nil, repo = @gem_repo)
path ||= spec.cache_file
@fetcher.data["#{@gem_repo}gems/#{spec.file_name}"] = read_binary(path)
end
@@ -1186,6 +1279,26 @@ Also, a list:
system("nmake /? 1>NUL 2>&1")
end
+ @@symlink_supported = nil
+
+ # This is needed for Windows environment without symlink support enabled (the default
+ # for non admin) to be able to skip test for features using symlinks.
+ def symlink_supported?
+ if @@symlink_supported.nil?
+ begin
+ File.symlink(File.join(@tempdir, "a"), File.join(@tempdir, "b"))
+ File.readlink(File.join(@tempdir, "b"))
+ rescue NotImplementedError, SystemCallError
+ @@symlink_supported = false
+ else
+ @@symlink_supported = true
+ ensure
+ File.unlink(File.join(@tempdir, "b")) if File.symlink?(File.join(@tempdir, "b"))
+ end
+ end
+ @@symlink_supported
+ end
+
# In case we're building docs in a background process, this method waits for
# that process to exit (or if it's already been reaped, or never happened,
# swallows the Errno::ECHILD error).
@@ -1197,7 +1310,7 @@ Also, a list:
##
# Allows the proper version of +rake+ to be used for the test.
- def build_rake_in(good=true)
+ def build_rake_in(good = true)
gem_ruby = Gem.ruby
Gem.ruby = self.class.rubybin
env_rake = ENV["rake"]
@@ -1569,3 +1682,9 @@ class Object
end
require_relative "utilities"
+
+# mise installed rubygems_plugin.rb to system wide `site_ruby` directory.
+# This empty module avoid to call `mise` command.
+module ReshimInstaller
+ def self.reshim; end
+end
diff --git a/test/rubygems/installer_test_case.rb b/test/rubygems/installer_test_case.rb
index 8a34d28db8..9e0cbf9c69 100644
--- a/test/rubygems/installer_test_case.rb
+++ b/test/rubygems/installer_test_case.rb
@@ -215,26 +215,26 @@ class Gem::InstallerTestCase < Gem::TestCase
##
# Creates an installer for +spec+ that will install into +gem_home+.
- def util_installer(spec, gem_home, force=true)
+ def util_installer(spec, gem_home, force = true)
Gem::Installer.at(spec.cache_file,
install_dir: gem_home,
force: force)
end
- @@symlink_supported = nil
-
- # This is needed for Windows environment without symlink support enabled (the default
- # for non admin) to be able to skip test for features using symlinks.
- def symlink_supported?
- if @@symlink_supported.nil?
- begin
- File.symlink("", "")
- rescue Errno::ENOENT, Errno::EEXIST
- @@symlink_supported = true
- rescue NotImplementedError, SystemCallError
- @@symlink_supported = false
- end
+ def test_ensure_writable_dir_creates_missing_parent_directories
+ installer = setup_base_installer(false)
+
+ non_existent_parent = File.join(@tempdir, "non_existent_parent")
+ target_dir = File.join(non_existent_parent, "target_dir")
+
+ refute_directory_exists non_existent_parent, "Parent directory should not exist yet"
+ refute_directory_exists target_dir, "Target directory should not exist yet"
+
+ assert_nothing_raised do
+ installer.send(:ensure_writable_dir, target_dir)
end
- @@symlink_supported
+
+ assert_directory_exists non_existent_parent, "Parent directory should exist now"
+ assert_directory_exists target_dir, "Target directory should exist now"
end
end
diff --git a/test/rubygems/mock_gem_ui.rb b/test/rubygems/mock_gem_ui.rb
index 218d4b6965..fb804c5555 100644
--- a/test/rubygems/mock_gem_ui.rb
+++ b/test/rubygems/mock_gem_ui.rb
@@ -77,7 +77,7 @@ class Gem::MockGemUi < Gem::StreamUI
@terminated
end
- def terminate_interaction(status=0)
+ def terminate_interaction(status = 0)
@terminated = true
raise TermError, status if status != 0
diff --git a/test/rubygems/package/tar_test_case.rb b/test/rubygems/package/tar_test_case.rb
index e3d812bf3f..26135cf296 100644
--- a/test/rubygems/package/tar_test_case.rb
+++ b/test/rubygems/package/tar_test_case.rb
@@ -6,23 +6,7 @@ require "rubygems/package"
##
# A test case for Gem::Package::Tar* classes
-class Gem::Package::TarTestCase < Gem::TestCase
- def ASCIIZ(str, length)
- str + "\0" * (length - str.length)
- end
-
- def SP(s)
- s + " "
- end
-
- def SP_Z(s)
- s + " \0"
- end
-
- def Z(s)
- s + "\0"
- end
-
+module Gem::Package::TarTestMethods
def assert_headers_equal(expected, actual)
expected = expected.to_s unless String === expected
actual = actual.to_s unless String === actual
@@ -66,6 +50,26 @@ class Gem::Package::TarTestCase < Gem::TestCase
assert_equal expected[chksum_off, 8], actual[chksum_off, 8]
end
+end
+
+class Gem::Package::TarTestCase < Gem::TestCase
+ include Gem::Package::TarTestMethods
+
+ def ASCIIZ(str, length)
+ str + "\0" * (length - str.length)
+ end
+
+ def SP(s)
+ s + " "
+ end
+
+ def SP_Z(s)
+ s + " \0"
+ end
+
+ def Z(s)
+ s + "\0"
+ end
def calc_checksum(header)
sum = header.sum(0)
diff --git a/test/rubygems/test_bundled_ca.rb b/test/rubygems/test_bundled_ca.rb
index a737185681..cc8fa884ca 100644
--- a/test/rubygems/test_bundled_ca.rb
+++ b/test/rubygems/test_bundled_ca.rb
@@ -12,7 +12,7 @@ require "rubygems/request"
# = Testing Bundled CA
#
-# The tested hosts are explained in detail here: https://github.com/rubygems/rubygems/commit/5e16a5428f973667cabfa07e94ff939e7a83ebd9
+# The tested hosts are explained in detail here: https://github.com/ruby/rubygems/commit/5e16a5428f973667cabfa07e94ff939e7a83ebd9
#
class TestGemBundledCA < Gem::TestCase
diff --git a/test/rubygems/test_config.rb b/test/rubygems/test_config.rb
index 657624d526..822b57b0dc 100644
--- a/test/rubygems/test_config.rb
+++ b/test/rubygems/test_config.rb
@@ -5,13 +5,6 @@ require "rubygems"
require "shellwords"
class TestGemConfig < Gem::TestCase
- def test_datadir
- util_make_gems
- spec = Gem::Specification.find_by_name("a")
- spec.activate
- assert_equal "#{spec.full_gem_path}/data/a", spec.datadir
- end
-
def test_good_rake_path_is_escaped
path = Gem::TestCase.class_variable_get(:@@good_rake)
ruby, rake = path.shellsplit
diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb
index cdc3479e37..c81b0b0547 100644
--- a/test/rubygems/test_gem.rb
+++ b/test/rubygems/test_gem.rb
@@ -150,6 +150,8 @@ class TestGem < Gem::TestCase
end
def assert_self_install_permissions(format_executable: false, data_mode: 0o640)
+ omit "FileUtils.install signature differs on JRuby/Windows" if Gem.win_platform? && Gem.java_platform?
+
mask = Gem.win_platform? ? 0o700 : 0o777
options = {
dir_mode: 0o500,
@@ -199,7 +201,8 @@ class TestGem < Gem::TestCase
end
assert_equal(expected, result)
ensure
- File.chmod(0o755, *Dir.glob(@gemhome + "/gems/**/"))
+ files = Dir.glob(@gemhome + "/gems/**/")
+ File.chmod(0o755, *files) unless files.empty?
end
def test_require_missing
@@ -310,7 +313,7 @@ class TestGem < Gem::TestCase
assert_equal %w[a-1 b-2 c-2], loaded_spec_names
end
- def test_activate_bin_path_raises_a_meaningful_error_if_a_gem_thats_finally_activated_has_orphaned_dependencies
+ def test_activate_bin_path_backtracks_when_highest_version_has_orphaned_dependencies
a1 = util_spec "a", "1" do |s|
s.executables = ["exec"]
s.add_dependency "b"
@@ -328,13 +331,11 @@ class TestGem < Gem::TestCase
install_specs c1, b1, b2, a1
- # c2 is missing, and b2 which has it as a dependency will be activated, so we should get an error about the orphaned dependency
-
- e = assert_raise Gem::UnsatisfiableDependencyError do
- load Gem.activate_bin_path("a", "exec", ">= 0")
- end
+ # c2 is missing, but the resolver backtracks from b2 to b1 which
+ # works with c1, finding a valid solution despite partial installation
+ load Gem.activate_bin_path("a", "exec", ">= 0")
- assert_equal "Unable to resolve dependency: 'b (>= 0)' requires 'c (= 2)'", e.message
+ assert_equal %w[a-1 b-1 c-1], loaded_spec_names
end
def test_activate_bin_path_in_debug_mode
@@ -527,35 +528,6 @@ class TestGem < Gem::TestCase
assert_equal expected, Gem.configuration
end
- def test_self_datadir
- foo = nil
-
- Dir.chdir @tempdir do
- FileUtils.mkdir_p "data"
- File.open File.join("data", "foo.txt"), "w" do |fp|
- fp.puts "blah"
- end
-
- foo = util_spec "foo" do |s|
- s.files = %w[data/foo.txt]
- end
-
- install_gem foo
- end
-
- gem "foo"
-
- expected = File.join @gemhome, "gems", foo.full_name, "data", "foo"
-
- assert_equal expected, Gem::Specification.find_by_name("foo").datadir
- end
-
- def test_self_datadir_nonexistent_package
- assert_raise(Gem::MissingSpecError) do
- Gem::Specification.find_by_name("xyzzy").datadir
- end
- end
-
def test_self_default_exec_format
ruby_install_name "ruby" do
assert_equal "%s", Gem.default_exec_format
@@ -615,6 +587,7 @@ class TestGem < Gem::TestCase
end
def test_self_default_sources
+ Gem.remove_instance_variable :@default_sources
assert_equal %w[https://rubygems.org/], Gem.default_sources
end
@@ -1227,6 +1200,8 @@ class TestGem < Gem::TestCase
Gem.sources = nil
Gem.configuration.sources = %w[http://test.example.com/]
assert_equal %w[http://test.example.com/], Gem.sources
+ ensure
+ Gem.configuration.sources = nil
end
def test_try_activate_returns_true_for_activated_specs
@@ -1239,6 +1214,28 @@ class TestGem < Gem::TestCase
assert Gem.try_activate("b"), "try_activate should still return true"
end
+ def test_try_activate_does_not_raise_no_method_error_on_activation_conflict
+ a1 = util_spec "a", "1.0" do |s|
+ s.files << "lib/a/old.rb"
+ end
+
+ a2 = util_spec "a", "2.0" do |s|
+ s.files << "lib/a/old.rb"
+ s.files << "lib/a/new_file.rb"
+ end
+
+ install_specs a1, a2
+
+ # Activate the older version
+ gem "a", "= 1.0"
+
+ # try_activate a file only in the newer version should not raise
+ # NoMethodError on nil (https://bugs.ruby-lang.org/issues/21954)
+ assert_nothing_raised do
+ Gem.try_activate("a/new_file")
+ end
+ end
+
def test_spec_order_is_consistent
b1 = util_spec "b", "1.0"
b2 = util_spec "b", "2.0"
@@ -1308,10 +1305,14 @@ class TestGem < Gem::TestCase
refute Gem.try_activate "nonexistent"
end
- expected = "Ignoring ext-1 because its extensions are not built. " \
- "Try: gem pristine ext --version 1\n"
+ if RUBY_ENGINE == "jruby"
+ assert_equal "", err
+ else
+ expected = "Ignoring ext-1 because its extensions are not built. " \
+ "Try: gem pristine ext --version 1\n"
- assert_equal expected, err
+ assert_equal expected, err
+ end
end
def test_self_use_paths_with_nils
@@ -1659,6 +1660,27 @@ class TestGem < Gem::TestCase
assert_nil Gem.find_unresolved_default_spec("README")
end
+ def test_register_default_spec_new_style_with_native_extension
+ Gem.clear_default_specs
+
+ dlext = RbConfig::CONFIG["DLEXT"]
+
+ new_style = Gem::Specification.new do |spec|
+ spec.name = "my_ext"
+ spec.version = "1.0"
+ spec.files = ["lib/my_ext.rb", "my_ext_core.#{dlext}", "ext/my_ext/my_ext_core.c", "README.md"]
+ spec.require_paths = ["lib"]
+ end
+
+ Gem.register_default_spec new_style
+
+ assert_equal new_style, Gem.find_unresolved_default_spec("my_ext.rb")
+ assert_equal new_style, Gem.find_unresolved_default_spec("my_ext_core")
+ assert_equal new_style, Gem.find_unresolved_default_spec("my_ext_core.#{dlext}")
+ assert_nil Gem.find_unresolved_default_spec("ext/my_ext/my_ext_core.c")
+ assert_nil Gem.find_unresolved_default_spec("README.md")
+ end
+
def test_register_default_spec_old_style_with_folder_starting_with_lib
Gem.clear_default_specs
diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb
index b72670b802..b5ef6293ab 100644
--- a/test/rubygems/test_gem_bundler_version_finder.rb
+++ b/test/rubygems/test_gem_bundler_version_finder.rb
@@ -2,6 +2,7 @@
require_relative "helper"
require "rubygems/bundler_version_finder"
+require "tempfile"
class TestGemBundlerVersionFinder < Gem::TestCase
def setup
@@ -32,6 +33,11 @@ class TestGemBundlerVersionFinder < Gem::TestCase
assert_equal v("1.1.1.1"), bvf.bundler_version
end
+ def test_bundler_version_with_empty_env_var
+ ENV["BUNDLER_VERSION"] = ""
+ assert_nil bvf.bundler_version
+ end
+
def test_bundler_version_with_bundle_update_bundler
ARGV.replace %w[update --bundler]
assert_nil bvf.bundler_version
@@ -51,6 +57,157 @@ class TestGemBundlerVersionFinder < Gem::TestCase
assert_nil bvf.bundler_version
end
+ def test_bundler_version_with_bundle_config
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "system"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_nil bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_single_quoted
+ config_with_single_quoted_version = <<~CONFIG
+ BUNDLE_VERSION: 'system'
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_with_single_quoted_version)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_nil bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_version
+ ENV["BUNDLER_VERSION"] = "1.1.1.1"
+
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "1.2.3"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_equal v("1.1.1.1"), bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_version_env_system
+ ENV["BUNDLE_VERSION"] = "system"
+
+ bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do
+ assert_nil bvf.bundler_version
+ end
+ end
+
+ def test_bundler_version_with_bundle_version_env_overrides_config
+ ENV["BUNDLE_VERSION"] = "2.3.4"
+
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "1.2.3"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_equal v("2.3.4"), bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_empty_bundle_version_env
+ ENV["BUNDLE_VERSION"] = ""
+
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "1.2.3"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_equal v("1.2.3"), bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_version_env_lockfile
+ ENV["BUNDLE_VERSION"] = "lockfile"
+
+ bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do
+ assert_equal v("1.1.1.1"), bvf.bundler_version
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_version_lockfile
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "lockfile"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do
+ assert_equal v("1.1.1.1"), bvf.bundler_version
+ end
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_non_existent_file
+ bvf.stub(:bundler_global_config_file, "/non/existent/path") do
+ assert_nil bvf.bundler_version
+ end
+ end
+
+ def test_bundler_version_set_on_local_config
+ config_content = <<~CONFIG
+ BUNDLE_VERSION: "1.2.3"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_content)
+ f.flush
+
+ bvf.stub(:bundler_local_config_file, f.path) do
+ assert_equal v("1.2.3"), bvf.bundler_version
+ end
+ end
+ end
+
+ def test_bundler_version_with_bundle_config_without_version
+ config_without_version = <<~CONFIG
+ BUNDLE_JOBS: "8"
+ BUNDLE_GEM__TEST: "minitest"
+ CONFIG
+
+ Tempfile.create("bundle_config") do |f|
+ f.write(config_without_version)
+ f.flush
+
+ bvf.stub(:bundler_global_config_file, f.path) do
+ assert_nil bvf.bundler_version
+ end
+ end
+ end
+
def test_bundler_version_with_lockfile
bvf.stub(:lockfile_contents, "") do
assert_nil bvf.bundler_version
@@ -82,7 +239,7 @@ class TestGemBundlerVersionFinder < Gem::TestCase
def test_deleted_directory
pend "Cannot perform this test on windows" if Gem.win_platform?
- pend "Cannot perform this test on Solaris" if RUBY_PLATFORM.include?("solaris")
+
require "tmpdir"
orig_dir = Dir.pwd
diff --git a/test/rubygems/test_gem_command_manager.rb b/test/rubygems/test_gem_command_manager.rb
index f3848e498d..889d5ce9e6 100644
--- a/test/rubygems/test_gem_command_manager.rb
+++ b/test/rubygems/test_gem_command_manager.rb
@@ -43,7 +43,7 @@ class TestGemCommandManager < Gem::TestCase
assert_kind_of Gem::Commands::SigninCommand, command
end
- def test_find_logout_alias_comamnd
+ def test_find_logout_alias_command
command = @command_manager.find_command "logout"
assert_kind_of Gem::Commands::SignoutCommand, command
@@ -78,7 +78,7 @@ class TestGemCommandManager < Gem::TestCase
message = "Unknown command pish".dup
- if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable)
+ if e.respond_to?(:corrections)
message << "\nDid you mean? \"push\""
end
@@ -287,47 +287,6 @@ class TestGemCommandManager < Gem::TestCase
assert_equal "foobar.rb", check_options[:args].first
end
- # HACK: move to query command test
- def test_process_args_query
- # capture all query options
- check_options = nil
- @command_manager["query"].when_invoked do |options|
- check_options = options
- true
- end
-
- # check defaults
- Gem::Deprecate.skip_during do
- @command_manager.process_args %w[query]
- end
- assert_nil(check_options[:name])
- assert_equal :local, check_options[:domain]
- assert_equal false, check_options[:details]
-
- # check settings
- check_options = nil
- Gem::Deprecate.skip_during do
- @command_manager.process_args %w[query --name foobar --local --details]
- end
- assert_equal(/foobar/i, check_options[:name])
- assert_equal :local, check_options[:domain]
- assert_equal true, check_options[:details]
-
- # remote domain
- check_options = nil
- Gem::Deprecate.skip_during do
- @command_manager.process_args %w[query --remote]
- end
- assert_equal :remote, check_options[:domain]
-
- # both (local/remote) domains
- check_options = nil
- Gem::Deprecate.skip_during do
- @command_manager.process_args %w[query --both]
- end
- assert_equal :both, check_options[:domain]
- end
-
# HACK: move to update command test
def test_process_args_update
# capture all update options
diff --git a/test/rubygems/test_gem_commands_build_command.rb b/test/rubygems/test_gem_commands_build_command.rb
index d44126d204..9339f41f7c 100644
--- a/test/rubygems/test_gem_commands_build_command.rb
+++ b/test/rubygems/test_gem_commands_build_command.rb
@@ -43,16 +43,6 @@ class TestGemCommandsBuildCommand < Gem::TestCase
assert_includes Gem.platforms, Gem::Platform.local
end
- def test_handle_deprecated_options
- use_ui @ui do
- @cmd.handle_options %w[-C ./test/dir]
- end
-
- assert_equal "WARNING: The \"-C\" option has been deprecated and will be removed in Rubygems 4.0. " \
- "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead\n",
- @ui.error
- end
-
def test_options_filename
gemspec_file = File.join(@tempdir, @gem.spec_name)
diff --git a/test/rubygems/test_gem_commands_cert_command.rb b/test/rubygems/test_gem_commands_cert_command.rb
index c173467935..ed1a1c8627 100644
--- a/test/rubygems/test_gem_commands_cert_command.rb
+++ b/test/rubygems/test_gem_commands_cert_command.rb
@@ -31,14 +31,6 @@ class TestGemCommandsCertCommand < Gem::TestCase
@cmd = Gem::Commands::CertCommand.new
@trust_dir = Gem::Security.trust_dir
-
- @cleanup = []
- end
-
- def teardown
- FileUtils.rm_f(@cleanup)
-
- super
end
def test_certificates_matching
@@ -498,7 +490,7 @@ Removed '/CN=alternate/DC=example'
assert_equal "/CN=nobody/DC=example", cert.issuer.to_s
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(path).mode unless Gem.win_platform?
end
@@ -527,7 +519,7 @@ Removed '/CN=alternate/DC=example'
assert_equal "/CN=nobody/DC=example", cert.issuer.to_s
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(path).mode unless Gem.win_platform?
end
@@ -559,7 +551,7 @@ Removed '/CN=alternate/DC=example'
assert_equal "/CN=nobody/DC=example", cert.issuer.to_s
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(path).mode unless Gem.win_platform?
end
@@ -591,7 +583,7 @@ Removed '/CN=alternate/DC=example'
assert_equal "/CN=nobody/DC=example", cert.issuer.to_s
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(path).mode unless Gem.win_platform?
end
@@ -661,8 +653,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis
assert_equal "/CN=nobody/DC=example", EXPIRED_PUBLIC_CERT.issuer.to_s
- tmp_expired_cert_file = File.join(Dir.tmpdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
- @cleanup << tmp_expired_cert_file
+ tmp_expired_cert_file = File.join(@tempdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
File.write(tmp_expired_cert_file, File.read(EXPIRED_PUBLIC_CERT_FILE))
@cmd.handle_options %W[
@@ -694,8 +685,7 @@ ERROR: --private-key not specified and ~/.gem/gem-private_key.pem does not exis
assert_equal "/CN=nobody/DC=example", EXPIRED_PUBLIC_CERT.issuer.to_s
- tmp_expired_cert_file = File.join(Dir.tmpdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
- @cleanup << tmp_expired_cert_file
+ tmp_expired_cert_file = File.join(@tempdir, File.basename(EXPIRED_PUBLIC_CERT_FILE))
File.write(tmp_expired_cert_file, File.read(EXPIRED_PUBLIC_CERT_FILE))
@cmd.handle_options %W[
diff --git a/test/rubygems/test_gem_commands_environment_command.rb b/test/rubygems/test_gem_commands_environment_command.rb
index 48252d84d4..e27de544c6 100644
--- a/test/rubygems/test_gem_commands_environment_command.rb
+++ b/test/rubygems/test_gem_commands_environment_command.rb
@@ -164,4 +164,8 @@ class TestGemCommandsEnvironmentCommand < Gem::TestCase
assert_equal "#{Gem.platforms.join File::PATH_SEPARATOR}\n", @ui.output
assert_equal "", @ui.error
end
+
+ def test_description_mentions_concurrent_downloads
+ assert_match(/:concurrent_downloads:/, @cmd.description)
+ end
end
diff --git a/test/rubygems/test_gem_commands_exec_command.rb b/test/rubygems/test_gem_commands_exec_command.rb
index b9d5888068..b949cd34a6 100644
--- a/test/rubygems/test_gem_commands_exec_command.rb
+++ b/test/rubygems/test_gem_commands_exec_command.rb
@@ -370,8 +370,11 @@ class TestGemCommandsExecCommand < Gem::TestCase
util_clear_gems
use_ui @ui do
- @cmd.invoke "a:2"
- assert_equal "a-2 foo\n", @ui.output
+ e = assert_raise Gem::MockGemUi::TermError do
+ @cmd.invoke "a:2"
+ end
+ assert_equal 1, e.exit_code
+ assert_equal "ERROR: Ambiguous which executable from gem `a` should be run: the options are [\"bar\", \"foo\"], specify one via COMMAND, and use `-g` and `-v` to specify gem and version\n", @ui.error
end
end
@@ -853,4 +856,33 @@ class TestGemCommandsExecCommand < Gem::TestCase
assert_equal %w[a-1.1.a], @installed_specs.map(&:full_name)
end
end
+
+ def test_install_dependency_resolution_error
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2 do |s|
+ s.executables = %w[a]
+ s.add_dependency "b", "~> 1.0"
+ s.add_dependency "c", "~> 1.0"
+ end
+ fetcher.gem "b", 1 do |s|
+ s.add_dependency "d", "= 1.0"
+ end
+ fetcher.gem "c", 1 do |s|
+ s.add_dependency "d", "= 2.0"
+ end
+ fetcher.gem "d", 1
+ fetcher.gem "d", 2
+ end
+
+ util_clear_gems
+
+ use_ui @ui do
+ e = assert_raise Gem::MockGemUi::TermError do
+ @cmd.invoke "a:2"
+ end
+ assert_equal 2, e.exit_code
+ end
+
+ assert_match(/ERROR:.*Error installing a:/, @ui.error)
+ end
end
diff --git a/test/rubygems/test_gem_commands_fetch_command.rb b/test/rubygems/test_gem_commands_fetch_command.rb
index 84fad08fd6..e673e391fe 100644
--- a/test/rubygems/test_gem_commands_fetch_command.rb
+++ b/test/rubygems/test_gem_commands_fetch_command.rb
@@ -157,7 +157,7 @@ class TestGemCommandsFetchCommand < Gem::TestCase
execute_with_term_error
msg = "ERROR: Can't use --version with multiple gems. You can specify multiple gems with" \
- " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
+ " version requirements using `gem fetch 'my_gem:1.0.0' 'my_other_gem:>=2'`"
assert_empty @ui.output
assert_equal msg, @ui.error.chomp
diff --git a/test/rubygems/test_gem_commands_help_command.rb b/test/rubygems/test_gem_commands_help_command.rb
index 01ab4aab2f..4ce7285d1f 100644
--- a/test/rubygems/test_gem_commands_help_command.rb
+++ b/test/rubygems/test_gem_commands_help_command.rb
@@ -36,7 +36,7 @@ class TestGemCommandsHelpCommand < Gem::TestCase
def test_gem_help_build
util_gem "build" do |out, err|
- assert_match(/-C PATH *Run as if gem build was started in <PATH>/, out)
+ assert_match(/--platform PLATFORM\s+Specify the platform of gem to build/, out)
assert_equal "", err
end
end
diff --git a/test/rubygems/test_gem_commands_info_command.rb b/test/rubygems/test_gem_commands_info_command.rb
index f020d380d2..dab7cfb836 100644
--- a/test/rubygems/test_gem_commands_info_command.rb
+++ b/test/rubygems/test_gem_commands_info_command.rb
@@ -13,7 +13,7 @@ class TestGemCommandsInfoCommand < Gem::TestCase
def gem(name, version = "1.0")
spec = quick_gem name do |gem|
gem.summary = "test gem"
- gem.homepage = "https://github.com/rubygems/rubygems"
+ gem.homepage = "https://github.com/ruby/rubygems"
gem.files = %W[lib/#{name}.rb Rakefile]
gem.authors = ["Colby", "Jack"]
gem.license = "MIT"
diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb
index 4e49f52b4c..d75ba349f9 100644
--- a/test/rubygems/test_gem_commands_install_command.rb
+++ b/test/rubygems/test_gem_commands_install_command.rb
@@ -119,11 +119,7 @@ class TestGemCommandsInstallCommand < Gem::TestCase
end
end
- expected = <<-EXPECTED
-ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in any repository
- EXPECTED
-
- assert_equal expected, @ui.error
+ assert_match(/ERROR:.*foo.*bar/m, @ui.error)
end
def test_execute_local_dependency_nonexistent_ignore_dependencies
@@ -303,11 +299,7 @@ ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in a
assert_equal 2, e.exit_code
end
- expected = <<-EXPECTED
-ERROR: Could not find a valid gem 'bar' (= 0.5) (required by 'foo' (>= 0)) in any repository
- EXPECTED
-
- assert_equal expected, @ui.error
+ assert_match(/ERROR:.*foo.*bar/m, @ui.error)
end
def test_execute_http_proxy
@@ -647,17 +639,10 @@ ERROR: Possible alternatives: non_existent_with_hint
@cmd.options[:args] = %w[a]
use_ui @ui do
- # Don't use Dir.chdir with a block, it warnings a lot because
- # of a downstream Dir.chdir with a block
- old = Dir.getwd
-
- begin
- Dir.chdir @tempdir
+ Dir.chdir @tempdir do
assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
@cmd.execute
end
- ensure
- Dir.chdir old
end
end
@@ -684,17 +669,10 @@ ERROR: Possible alternatives: non_existent_with_hint
@cmd.options[:args] = %w[a]
use_ui @ui do
- # Don't use Dir.chdir with a block, it warnings a lot because
- # of a downstream Dir.chdir with a block
- old = Dir.getwd
-
- begin
- Dir.chdir @tempdir
+ Dir.chdir @tempdir do
assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
@cmd.execute
end
- ensure
- Dir.chdir old
end
end
@@ -720,17 +698,10 @@ ERROR: Possible alternatives: non_existent_with_hint
@cmd.options[:args] = %w[a]
use_ui @ui do
- # Don't use Dir.chdir with a block, it warnings a lot because
- # of a downstream Dir.chdir with a block
- old = Dir.getwd
-
- begin
- Dir.chdir @tempdir
+ Dir.chdir @tempdir do
assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
@cmd.execute
end
- ensure
- Dir.chdir old
end
end
@@ -901,7 +872,7 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_empty @cmd.installed_specs
msg = "ERROR: Can't use --version with multiple gems. You can specify multiple gems with" \
- " version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
+ " version requirements using `gem install 'my_gem:1.0.0' 'my_other_gem:>=2'`"
assert_empty @ui.output
assert_equal msg, @ui.error.chomp
@@ -1005,6 +976,38 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_equal %W[a-3-#{local}], @cmd.installed_specs.map(&:full_name)
end
+ def test_install_gem_platform_specificity_match
+ util_set_arch "arm64-darwin-20"
+
+ spec_fetcher do |fetcher|
+ %w[ruby universal-darwin universal-darwin-20 x64-darwin-20 arm64-darwin-20].each do |platform|
+ fetcher.download "a", 3 do |s|
+ s.platform = platform
+ end
+ end
+ end
+
+ @cmd.install_gem "a", ">= 0"
+
+ assert_equal %w[a-3-arm64-darwin-20], @cmd.installed_specs.map(&:full_name)
+ end
+
+ def test_install_gem_platform_specificity_match_reverse_order
+ util_set_arch "arm64-darwin-20"
+
+ spec_fetcher do |fetcher|
+ %w[ruby universal-darwin universal-darwin-20 x64-darwin-20 arm64-darwin-20].reverse_each do |platform|
+ fetcher.download "a", 3 do |s|
+ s.platform = platform
+ end
+ end
+ end
+
+ @cmd.install_gem "a", ">= 0"
+
+ assert_equal %w[a-3-arm64-darwin-20], @cmd.installed_specs.map(&:full_name)
+ end
+
def test_install_gem_ignore_dependencies_specific_file
spec = util_spec "a", 2
@@ -1214,6 +1217,30 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_match "Installing a (2)", @ui.output
end
+ def test_execute_installs_from_a_gemdeps_with_prerelease
+ spec_fetcher do |fetcher|
+ fetcher.download "a", 1
+ fetcher.download "a", "2.a"
+ end
+
+ File.open @gemdeps, "w" do |f|
+ f << "gem 'a'"
+ end
+
+ @cmd.handle_options %w[--prerelease]
+ @cmd.options[:gemdeps] = @gemdeps
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_equal %w[a-2.a], @cmd.installed_specs.map(&:full_name)
+
+ assert_match "Installing a (2.a)", @ui.output
+ end
+
def test_execute_installs_deps_a_gemdeps
spec_fetcher do |fetcher|
fetcher.download "q", "1.0"
@@ -1548,4 +1575,63 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_includes @ui.output, "A new release of RubyGems is available: 1.2.3 → 2.0.0!"
end
end
+
+ def test_pass_down_the_job_option_to_make
+ gemspec = nil
+
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2 do |spec|
+ gemspec = spec
+
+ extconf_path = "#{spec.gem_dir}/extconf.rb"
+
+ write_file(extconf_path) do |io|
+ io.puts "require 'mkmf'"
+ io.puts "create_makefile '#{spec.name}'"
+ end
+
+ spec.extensions = "extconf.rb"
+ end
+ end
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.invoke "a", "-j4"
+ end
+ end
+
+ gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out"))
+ if vc_windows? && nmake_found?
+ refute_includes(gem_make_out, " -j4")
+ else
+ assert_includes(gem_make_out, "make -j4")
+ end
+ end
+
+ def test_execute_bindir_with_nonexistent_parent_dirs
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2 do |s|
+ s.executables = %w[a_bin]
+ s.files = %w[bin/a_bin]
+ end
+ end
+
+ @cmd.options[:args] = %w[a]
+
+ nested_bin_dir = File.join(@tempdir, "not", "exists")
+ refute_directory_exists nested_bin_dir, "Nested bin directory should not exist yet"
+
+ @cmd.options[:bin_dir] = nested_bin_dir
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_directory_exists nested_bin_dir, "Nested bin directory should exist now"
+ assert_path_exist File.join(nested_bin_dir, "a_bin")
+
+ assert_equal %w[a-2], @cmd.installed_specs.map(&:full_name)
+ end
end
diff --git a/test/rubygems/test_gem_commands_open_command.rb b/test/rubygems/test_gem_commands_open_command.rb
index d9e518048c..addc7427e2 100644
--- a/test/rubygems/test_gem_commands_open_command.rb
+++ b/test/rubygems/test_gem_commands_open_command.rb
@@ -21,6 +21,8 @@ class TestGemCommandsOpenCommand < Gem::TestCase
end
def test_execute
+ omit "JRuby on Windows spawns the editor with a different cwd" if Gem.win_platform? && Gem.java_platform?
+
@cmd.options[:args] = %w[foo]
@cmd.options[:editor] = (ruby_with_rubygems_in_load_path + ["-e", "puts(ARGV,Dir.pwd)", "--"]).join(" ")
diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb
index bc4f13ff2a..f6d4d03f84 100644
--- a/test/rubygems/test_gem_commands_owner_command.rb
+++ b/test/rubygems/test_gem_commands_owner_command.rb
@@ -32,9 +32,12 @@ class TestGemCommandsOwnerCommand < Gem::TestCase
- email: user1@example.com
id: 1
handle: user1
+ role: owner
- email: user2@example.com
+ role: maintainer
- id: 3
handle: user3
+ role: owner
- id: 4
EOF
@@ -48,14 +51,14 @@ EOF
assert_equal Gem.configuration.rubygems_api_key, @stub_fetcher.last_request["Authorization"]
assert_match(/Owners for gem: freewill/, @stub_ui.output)
- assert_match(/- user1@example.com/, @stub_ui.output)
- assert_match(/- user2@example.com/, @stub_ui.output)
- assert_match(/- user3/, @stub_ui.output)
+ assert_match(/- user1@example.com \(owner\)/, @stub_ui.output)
+ assert_match(/- user2@example.com \(maintainer\)/, @stub_ui.output)
+ assert_match(/- user3 \(owner\)/, @stub_ui.output)
assert_match(/- 4/, @stub_ui.output)
end
def test_show_owners_dont_load_objects
- pend "testing a psych-only API" unless defined?(::Psych::DisallowedClass)
+ Gem.load_yaml
response = <<EOF
---
@@ -386,9 +389,10 @@ EOF
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @stub_ui.output
+ assert_match @stub_fetcher.webauthn_url_with_port(server.port), @stub_ui.output
assert_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
assert_equal "Uvh6T57tkWuUnWYo", @stub_fetcher.last_request["OTP"]
assert_match response_success, @stub_ui.output
@@ -412,10 +416,12 @@ EOF
end
end
- assert_match @stub_fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
- assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(server.port)} " \
+ webauthn_verification_request = @stub_fetcher.requests.find {|req| req.path == "/api/v1/webauthn_verification" }
+ assert_match webauthn_verification_request["Authorization"], Gem.configuration.rubygems_api_key
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @stub_ui.output
+ assert_match @stub_fetcher.webauthn_url_with_port(server.port), @stub_ui.output
assert_match "ERROR: Security device verification failed: Something went wrong", @stub_ui.error
refute_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
refute_match response_success, @stub_ui.output
@@ -435,9 +441,10 @@ EOF
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \
"command with the `--otp [your_code]` option.", @stub_ui.output
+ assert_match @stub_fetcher.webauthn_url_with_port(server.port), @stub_ui.output
assert_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
assert_equal "Uvh6T57tkWuUnWYo", @stub_fetcher.last_request["OTP"]
assert_match response_success, @stub_ui.output
@@ -463,16 +470,17 @@ EOF
end
assert_match @stub_fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
- assert_match "You have enabled multi-factor authentication. Please visit #{@stub_fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \
"command with the `--otp [your_code]` option.", @stub_ui.output
+ assert_match @stub_fetcher.webauthn_url_with_port(server.port), @stub_ui.output
assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \
"or been used already.", @stub_ui.error
refute_match "You are verified with a security device. You may close the browser window.", @stub_ui.output
refute_match response_success, @stub_ui.output
end
- def test_remove_owners_unathorized_api_key
+ def test_remove_owners_unauthorized_api_key
response_forbidden = "The API key doesn't have access"
response_success = "Owner removed successfully."
@@ -537,7 +545,7 @@ EOF
assert_empty reused_otp_codes
end
- def test_add_owners_unathorized_api_key
+ def test_add_owners_unauthorized_api_key
response_forbidden = "The API key doesn't have access"
response_success = "Owner added successfully."
diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb
index 46c06db014..0ea140897c 100644
--- a/test/rubygems/test_gem_commands_pristine_command.rb
+++ b/test/rubygems/test_gem_commands_pristine_command.rb
@@ -125,8 +125,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- assert File.exist?(gem_bin)
- assert File.exist?(gem_stub)
+ assert_path_exist gem_bin
+ assert_path_exist gem_stub
out = @ui.output.split "\n"
@@ -248,7 +248,13 @@ class TestGemCommandsPristineCommand < Gem::TestCase
end
refute_includes @ui.output, "Restored #{a.full_name}"
- assert_includes @ui.output, "Restored #{b.full_name}"
+
+ if Gem.java_platform?
+ refute_includes @ui.output, "Restored #{b.full_name}"
+ assert_includes @ui.output, "No gems with missing extensions to restore"
+ else
+ assert_includes @ui.output, "Restored #{b.full_name}"
+ end
end
def test_execute_no_extension
@@ -537,8 +543,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- assert File.exist? gem_exec
- refute File.exist? gem_lib
+ assert_path_exist gem_exec
+ assert_path_not_exist gem_lib
end
def test_execute_only_plugins
@@ -572,9 +578,9 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- refute File.exist? gem_exec
- assert File.exist? gem_plugin
- refute File.exist? gem_lib
+ assert_path_not_exist gem_exec
+ assert_path_exist gem_plugin
+ assert_path_not_exist gem_lib
end
def test_execute_bindir
@@ -606,8 +612,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- refute File.exist? gem_exec
- assert File.exist? gem_bindir
+ assert_path_not_exist gem_exec
+ assert_path_exist gem_bindir
end
def test_execute_unknown_gem_at_remote_source
@@ -659,6 +665,42 @@ class TestGemCommandsPristineCommand < Gem::TestCase
refute_includes "ruby_executable_hooks", File.read(exe)
end
+ def test_execute_default_gem_and_regular_gem
+ a_default = new_default_spec("a", "1.2.0")
+
+ a = util_spec "a" do |s|
+ s.extensions << "ext/a/extconf.rb"
+ end
+
+ ext_path = File.join @tempdir, "ext", "a", "extconf.rb"
+ write_file ext_path do |io|
+ io.write <<-'RUBY'
+ File.open "Makefile", "w" do |f|
+ f.puts "clean:\n\techo cleaned\n"
+ f.puts "all:\n\techo built\n"
+ f.puts "install:\n\techo installed\n"
+ end
+ RUBY
+ end
+
+ install_default_gems a_default
+ install_gem a
+
+ # Remove the extension files for a
+ FileUtils.rm_rf a.gem_build_complete_path
+
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_includes @ui.output, "Restored #{a.full_name}"
+
+ # Check extension files for a were restored
+ assert_path_exist a.gem_build_complete_path
+ end
+
def test_execute_multi_platform
a = util_spec "a" do |s|
s.extensions << "ext/a/extconf.rb"
diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb
index 2d0190b49f..ada95e89b4 100644
--- a/test/rubygems/test_gem_commands_push_command.rb
+++ b/test/rubygems/test_gem_commands_push_command.rb
@@ -115,40 +115,116 @@ class TestGemCommandsPushCommand < Gem::TestCase
assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
content_length = @fetcher.last_request["Content-Length"].to_i
assert_equal content_length, @fetcher.last_request.body.length
- assert_equal "multipart", @fetcher.last_request.main_type, @fetcher.last_request.content_type
- assert_equal "form-data", @fetcher.last_request.sub_type
- assert_include @fetcher.last_request.type_params, "boundary"
- boundary = @fetcher.last_request.type_params["boundary"]
+ assert_attestation_multipart Gem.read_binary("#{@path}.sigstore.json")
+ end
- parts = @fetcher.last_request.body.split(/(?:\r\n|\A)--#{Regexp.quote(boundary)}(?:\r\n|--)/m)
- refute_empty parts
- assert_empty parts[0]
- parts.shift # remove the first empty part
+ def test_execute_attestation_auto
+ omit if RUBY_ENGINE == "jruby"
- p1 = parts.shift
- p2 = parts.shift
- assert_equal "\r\n", parts.shift
- assert_empty parts
+ ENV["GITHUB_ACTIONS"] = "true"
+ begin
+ @response = "Successfully registered gem: freewill (1.0.0)"
+ @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
- assert_equal [
- "Content-Disposition: form-data; name=\"gem\"; filename=\"#{@path}\"",
- "Content-Type: application/octet-stream",
- nil,
- Gem.read_binary(@path),
- ].join("\r\n").b, p1
- assert_equal [
- "Content-Disposition: form-data; name=\"attestations\"",
- nil,
- "[#{Gem.read_binary("#{@path}.sigstore.json")}]",
- ].join("\r\n").b, p2
+ attestation_path = "#{@path}.sigstore.json"
+ attestation_content = "auto-attestation"
+ File.write(attestation_path, attestation_content)
+ @cmd.options[:args] = [@path]
+
+ @cmd.stub(:attest!, attestation_path) do
+ @cmd.execute
+ end
+
+ assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
+ content_length = @fetcher.last_request["Content-Length"].to_i
+ assert_equal content_length, @fetcher.last_request.body.length
+ assert_attestation_multipart attestation_content
+ ensure
+ ENV.delete("GITHUB_ACTIONS")
+ end
end
- def test_execute_allowed_push_host
+ def test_execute_attestation_fallback
+ omit if RUBY_ENGINE == "jruby"
+
+ ENV["GITHUB_ACTIONS"] = "true"
+ begin
+ @response = "Successfully registered gem: freewill (1.0.0)"
+ @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
+
+ @cmd.options[:args] = [@path]
+
+ @cmd.stub(:attest!, proc { raise Gem::Exception, "boom" }) do
+ use_ui @ui do
+ @cmd.execute
+ end
+ end
+
+ assert_match "Failed to push with attestation, retrying without attestation.", @ui.error
+ assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
+ assert_equal Gem.read_binary(@path), @fetcher.last_request.body
+ assert_equal "application/octet-stream",
+ @fetcher.last_request["Content-Type"]
+ ensure
+ ENV.delete("GITHUB_ACTIONS")
+ end
+ end
+
+ def test_execute_attestation_skipped_on_non_rubygems_host
@spec, @path = util_gem "freebird", "1.0.1" do |spec|
spec.metadata["allowed_push_host"] = "https://privategemserver.example"
end
+ @response = "Successfully registered gem: freebird (1.0.1)"
+ @fetcher.data["#{@spec.metadata["allowed_push_host"]}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
+
+ @cmd.options[:args] = [@path]
+
+ attest_called = false
+ @cmd.stub(:attest!, proc { attest_called = true }) do
+ @cmd.execute
+ end
+
+ refute attest_called, "attest! should not be called for non-rubygems.org hosts"
+ assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
+ assert_equal Gem.read_binary(@path), @fetcher.last_request.body
+ assert_equal "application/octet-stream",
+ @fetcher.last_request["Content-Type"]
+ end
+
+ def test_execute_attestation_skipped_on_jruby
@response = "Successfully registered gem: freewill (1.0.0)"
+ @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
+
+ @cmd.options[:args] = [@path]
+
+ attest_called = false
+ engine = RUBY_ENGINE
+ Object.send :remove_const, :RUBY_ENGINE
+ Object.const_set :RUBY_ENGINE, "jruby"
+
+ begin
+ @cmd.stub(:attest!, proc { attest_called = true }) do
+ @cmd.execute
+ end
+
+ refute attest_called, "attest! should not be called on JRuby"
+ assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class
+ assert_equal Gem.read_binary(@path), @fetcher.last_request.body
+ assert_equal "application/octet-stream",
+ @fetcher.last_request["Content-Type"]
+ ensure
+ Object.send :remove_const, :RUBY_ENGINE
+ Object.const_set :RUBY_ENGINE, engine
+ end
+ end
+
+ def test_execute_allowed_push_host
+ @spec, @path = util_gem "freebird", "1.0.1" do |spec|
+ spec.metadata["allowed_push_host"] = "https://privategemserver.example"
+ end
+
+ @response = "Successfully registered gem: freebird (1.0.1)"
@fetcher.data["#{@spec.metadata["allowed_push_host"]}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK")
@fetcher.data["#{Gem.host}/api/v1/gems"] =
["fail", 500, "Internal Server Error"]
@@ -477,15 +553,17 @@ class TestGemCommandsPushCommand < Gem::TestCase
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "You are verified with a security device. You may close the browser window.", @ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
assert_match response_success, @ui.output
end
def test_with_webauthn_enabled_failure
+ pend "Flaky on TruffleRuby" if RUBY_ENGINE == "truffleruby"
response_success = "Successfully registered gem: freewill (1.0.0)"
server = Gem::MockTCPServer.new
error = Gem::WebauthnVerificationError.new("Something went wrong")
@@ -505,9 +583,10 @@ class TestGemCommandsPushCommand < Gem::TestCase
assert_equal 1, error.exit_code
assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "ERROR: Security device verification failed: Something went wrong", @ui.error
refute_match "You are verified with a security device. You may close the browser window.", @ui.output
refute_match response_success, @ui.output
@@ -527,9 +606,10 @@ class TestGemCommandsPushCommand < Gem::TestCase
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "You are verified with a security device. You may close the browser window.", @ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
assert_match response_success, @ui.output
@@ -553,16 +633,17 @@ class TestGemCommandsPushCommand < Gem::TestCase
assert_equal 1, error.exit_code
assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
- "to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin " \
- "command with the `--otp [your_code]` option.", @ui.output
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
+ "to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
+ "you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \
"or been used already.", @ui.error
refute_match "You are verified with a security device. You may close the browser window.", @ui.output
refute_match response_success, @ui.output
end
- def test_sending_gem_unathorized_api_key_with_mfa_enabled
+ def test_sending_gem_unauthorized_api_key_with_mfa_enabled
response_mfa_enabled = "You have enabled multifactor authentication but your request doesn't have the correct OTP code. Please check it and retry."
response_forbidden = "The API key doesn't have access"
response_success = "Successfully registered gem: freewill (1.0.0)"
@@ -638,6 +719,35 @@ class TestGemCommandsPushCommand < Gem::TestCase
private
+ def assert_attestation_multipart(attestation_payload)
+ assert_equal "multipart", @fetcher.last_request.main_type, @fetcher.last_request.content_type
+ assert_equal "form-data", @fetcher.last_request.sub_type
+ assert_include @fetcher.last_request.type_params, "boundary"
+ boundary = @fetcher.last_request.type_params["boundary"]
+
+ parts = @fetcher.last_request.body.split(/(?:\r\n|\A)--#{Regexp.quote(boundary)}(?:\r\n|--)/m)
+ refute_empty parts
+ assert_empty parts[0]
+ parts.shift # remove the first empty part
+
+ p1 = parts.shift
+ p2 = parts.shift
+ assert_equal "\r\n", parts.shift
+ assert_empty parts
+
+ assert_equal [
+ "Content-Disposition: form-data; name=\"gem\"; filename=\"#{@path}\"",
+ "Content-Type: application/octet-stream",
+ nil,
+ Gem.read_binary(@path),
+ ].join("\r\n").b, p1
+ assert_equal [
+ "Content-Disposition: form-data; name=\"attestations\"",
+ nil,
+ "[#{attestation_payload}]",
+ ].join("\r\n").b, p2
+ end
+
def singleton_gem_class
class << Gem; self; end
end
diff --git a/test/rubygems/test_gem_commands_query_command.rb b/test/rubygems/test_gem_commands_query_command.rb
deleted file mode 100644
index 8e590df124..0000000000
--- a/test/rubygems/test_gem_commands_query_command.rb
+++ /dev/null
@@ -1,830 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-require "rubygems/commands/query_command"
-
-module TestGemCommandsQueryCommandSetup
- def setup
- super
-
- @cmd = Gem::Commands::QueryCommand.new
-
- @specs = add_gems_to_fetcher
- @stub_ui = Gem::MockGemUi.new
- @stub_fetcher = Gem::FakeFetcher.new
-
- @stub_fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = proc do
- raise Gem::RemoteFetcher::FetchError
- end
- end
-end
-
-class TestGemCommandsQueryCommandWithInstalledGems < Gem::TestCase
- include TestGemCommandsQueryCommandSetup
-
- def test_execute
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_all
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r --all]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_all_prerelease
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r --all --prerelease]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_details
- spec_fetcher do |fetcher|
- fetcher.spec "a", 2 do |s|
- s.summary = "This is a lot of text. " * 4
- s.authors = ["Abraham Lincoln", "Hirohito"]
- s.homepage = "http://a.example.com/"
- end
-
- fetcher.legacy_platform
- end
-
- @cmd.handle_options %w[-r -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
- Authors: Abraham Lincoln, Hirohito
- Homepage: http://a.example.com/
-
- This is a lot of text. This is a lot of text. This is a lot of text.
- This is a lot of text.
-
-pl (1)
- Platform: i386-linux
- Author: A User
- Homepage: http://example.com
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_details_cleans_text
- spec_fetcher do |fetcher|
- fetcher.spec "a", 2 do |s|
- s.summary = "This is a lot of text. " * 4
- s.authors = ["Abraham Lincoln \x01", "\x02 Hirohito"]
- s.homepage = "http://a.example.com/\x03"
- end
-
- fetcher.legacy_platform
- end
-
- @cmd.handle_options %w[-r -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
- Authors: Abraham Lincoln ., . Hirohito
- Homepage: http://a.example.com/.
-
- This is a lot of text. This is a lot of text. This is a lot of text.
- This is a lot of text.
-
-pl (1)
- Platform: i386-linux
- Author: A User
- Homepage: http://example.com
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_details_truncates_summary
- spec_fetcher do |fetcher|
- fetcher.spec "a", 2 do |s|
- s.summary = "This is a lot of text. " * 10_000
- s.authors = ["Abraham Lincoln \x01", "\x02 Hirohito"]
- s.homepage = "http://a.example.com/\x03"
- end
-
- fetcher.legacy_platform
- end
-
- @cmd.handle_options %w[-r -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
- Authors: Abraham Lincoln ., . Hirohito
- Homepage: http://a.example.com/.
-
- Truncating the summary for a-2 to 100,000 characters:
-#{" This is a lot of text. This is a lot of text. This is a lot of text.\n" * 1449} This is a lot of te
-
-pl (1)
- Platform: i386-linux
- Author: A User
- Homepage: http://example.com
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_installed
- @cmd.handle_options %w[-n a --installed]
-
- assert_raise Gem::MockGemUi::SystemExitException do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "true\n", @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_installed_inverse
- @cmd.handle_options %w[-n a --no-installed]
-
- e = assert_raise Gem::MockGemUi::TermError do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "false\n", @stub_ui.output
- assert_equal "", @stub_ui.error
-
- assert_equal 1, e.exit_code
- end
-
- def test_execute_installed_inverse_not_installed
- @cmd.handle_options %w[-n not_installed --no-installed]
-
- assert_raise Gem::MockGemUi::SystemExitException do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "true\n", @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_installed_no_name
- @cmd.handle_options %w[--installed]
-
- e = assert_raise Gem::MockGemUi::TermError do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "", @stub_ui.output
- assert_equal "ERROR: You must specify a gem name\n", @stub_ui.error
-
- assert_equal 4, e.exit_code
- end
-
- def test_execute_installed_not_installed
- @cmd.handle_options %w[-n not_installed --installed]
-
- e = assert_raise Gem::MockGemUi::TermError do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "false\n", @stub_ui.output
- assert_equal "", @stub_ui.error
-
- assert_equal 1, e.exit_code
- end
-
- def test_execute_installed_version
- @cmd.handle_options %w[-n a --installed --version 2]
-
- assert_raise Gem::MockGemUi::SystemExitException do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "true\n", @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_installed_version_not_installed
- @cmd.handle_options %w[-n c --installed --version 2]
-
- e = assert_raise Gem::MockGemUi::TermError do
- use_ui @stub_ui do
- @cmd.execute
- end
- end
-
- assert_equal "false\n", @stub_ui.output
- assert_equal "", @stub_ui.error
-
- assert_equal 1, e.exit_code
- end
-
- def test_execute_local
- spec_fetcher(&:legacy_platform)
-
- @cmd.options[:domain] = :local
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_local_notty
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[]
-
- @stub_ui.outs.tty = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_local_quiet
- spec_fetcher(&:legacy_platform)
-
- @cmd.options[:domain] = :local
- Gem.configuration.verbose = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_no_versions
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r --no-versions]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a
-pl
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_notty
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-r]
-
- @stub_ui.outs.tty = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (2)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_prerelease
- @cmd.handle_options %w[-r --prerelease]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (3.a)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_prerelease_local
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-l --prerelease]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_no_prerelease_local
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[-l --no-prerelease]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_remote
- spec_fetcher(&:legacy_platform)
-
- @cmd.options[:domain] = :remote
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_remote_notty
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[]
-
- @stub_ui.outs.tty = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (3.a, 2, 1)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_remote_quiet
- spec_fetcher(&:legacy_platform)
-
- @cmd.options[:domain] = :remote
- Gem.configuration.verbose = false
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-a (2)
-pl (1 i386-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_make_entry
- a_2_name = @specs["a-2"].original_name
-
- @stub_fetcher.data.delete \
- "#{@gem_repo}quick/Marshal.#{Gem.marshal_version}/#{a_2_name}.gemspec.rz"
-
- a2 = @specs["a-2"]
- entry_tuples = [
- [Gem::NameTuple.new(a2.name, a2.version, a2.platform),
- Gem.sources.first],
- ]
-
- platforms = { a2.version => [a2.platform] }
-
- entry = @cmd.send :make_entry, entry_tuples, platforms
-
- assert_equal "a (2)", entry
- end
-
- # Test for multiple args handling!
- def test_execute_multiple_args
- spec_fetcher(&:legacy_platform)
-
- @cmd.handle_options %w[a pl]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- assert_match(/^a /, @stub_ui.output)
- assert_match(/^pl /, @stub_ui.output)
- assert_equal "", @stub_ui.error
- end
-
- def test_show_gems
- @cmd.options[:name] = //
- @cmd.options[:domain] = :remote
-
- use_ui @stub_ui do
- @cmd.send :show_gems, /a/i
- end
-
- assert_match(/^a /, @stub_ui.output)
- refute_match(/^pl /, @stub_ui.output)
- assert_empty @stub_ui.error
- end
-
- private
-
- def add_gems_to_fetcher
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
- fetcher.spec "a", 2
- fetcher.spec "a", "3.a"
- end
- end
-end
-
-class TestGemCommandsQueryCommandWithoutInstalledGems < Gem::TestCase
- include TestGemCommandsQueryCommandSetup
-
- def test_execute_platform
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
- fetcher.spec "a", 1 do |s|
- s.platform = "x86-linux"
- end
-
- fetcher.spec "a", 2 do |s|
- s.platform = "universal-darwin"
- end
- end
-
- @cmd.handle_options %w[-r -a]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-a (2 universal-darwin, 1 ruby x86-linux)
- EOF
-
- assert_equal expected, @stub_ui.output
- assert_equal "", @stub_ui.error
- end
-
- def test_execute_show_default_gems
- spec_fetcher {|fetcher| fetcher.spec "a", 2 }
-
- a1 = new_default_spec "a", 1
- install_default_gems a1
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (2, default: 1)
-EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_show_default_gems_with_platform
- a1 = new_default_spec "a", 1
- a1.platform = "java"
- install_default_gems a1
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (default: 1 java)
-EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_default_details
- spec_fetcher do |fetcher|
- fetcher.spec "a", 2
- end
-
- a1 = new_default_spec "a", 1
- install_default_gems a1
-
- @cmd.handle_options %w[-l -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (2, 1)
- Author: A User
- Homepage: http://example.com
- Installed at (2): #{@gemhome}
- (1, default): #{a1.base_dir}
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_local_details
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1 do |s|
- s.platform = "x86-linux"
- end
-
- fetcher.spec "a", 2 do |s|
- s.summary = "This is a lot of text. " * 4
- s.authors = ["Abraham Lincoln", "Hirohito"]
- s.homepage = "http://a.example.com/"
- s.platform = "universal-darwin"
- end
-
- fetcher.legacy_platform
- end
-
- @cmd.handle_options %w[-l -d]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- str = @stub_ui.output
-
- str.gsub!(/\(\d\): [^\n]*/, "-")
- str.gsub!(/at: [^\n]*/, "at: -")
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-a (2, 1)
- Platforms:
- 1: x86-linux
- 2: universal-darwin
- Authors: Abraham Lincoln, Hirohito
- Homepage: http://a.example.com/
- Installed at -
- -
-
- This is a lot of text. This is a lot of text. This is a lot of text.
- This is a lot of text.
-
-pl (1)
- Platform: i386-linux
- Author: A User
- Homepage: http://example.com
- Installed at: -
-
- this is a summary
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_exact_remote
- spec_fetcher do |fetcher|
- fetcher.spec "coolgem-omg", 3
- fetcher.spec "coolgem", "4.2.1"
- fetcher.spec "wow_coolgem", 1
- end
-
- @cmd.handle_options %w[--remote --exact coolgem]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** REMOTE GEMS ***
-
-coolgem (4.2.1)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_exact_local
- spec_fetcher do |fetcher|
- fetcher.spec "coolgem-omg", 3
- fetcher.spec "coolgem", "4.2.1"
- fetcher.spec "wow_coolgem", 1
- end
-
- @cmd.handle_options %w[--exact coolgem]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-coolgem (4.2.1)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_execute_exact_multiple
- spec_fetcher do |fetcher|
- fetcher.spec "coolgem-omg", 3
- fetcher.spec "coolgem", "4.2.1"
- fetcher.spec "wow_coolgem", 1
-
- fetcher.spec "othergem-omg", 3
- fetcher.spec "othergem", "1.2.3"
- fetcher.spec "wow_othergem", 1
- end
-
- @cmd.handle_options %w[--exact coolgem othergem]
-
- use_ui @stub_ui do
- @cmd.execute
- end
-
- expected = <<-EOF
-
-*** LOCAL GEMS ***
-
-coolgem (4.2.1)
-
-*** LOCAL GEMS ***
-
-othergem (1.2.3)
- EOF
-
- assert_equal expected, @stub_ui.output
- end
-
- def test_depprecated
- assert @cmd.deprecated?
- end
-
- private
-
- def add_gems_to_fetcher
- spec_fetcher do |fetcher|
- fetcher.download "a", 1
- fetcher.download "a", 2
- fetcher.download "a", "3.a"
- end
- end
-end
diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb
index c3622c02cd..b33e05ab28 100644
--- a/test/rubygems/test_gem_commands_setup_command.rb
+++ b/test/rubygems/test_gem_commands_setup_command.rb
@@ -4,13 +4,6 @@ require_relative "helper"
require "rubygems/commands/setup_command"
class TestGemCommandsSetupCommand < Gem::TestCase
- bundler_gemspec = File.expand_path("../../bundler/lib/bundler/version.rb", __dir__)
- if File.exist?(bundler_gemspec)
- BUNDLER_VERS = File.read(bundler_gemspec).match(/VERSION = "(#{Gem::Version::VERSION_PATTERN})"/)[1]
- else
- BUNDLER_VERS = "2.0.1"
- end
-
def setup
super
@@ -35,9 +28,10 @@ class TestGemCommandsSetupCommand < Gem::TestCase
create_dummy_files(filelist)
- gemspec = util_spec "bundler", BUNDLER_VERS do |s|
+ gemspec = util_spec "bundler", "9.9.9" do |s|
s.bindir = "exe"
s.executables = ["bundle", "bundler"]
+ s.files = ["lib/bundler.rb"]
end
File.open "bundler/bundler.gemspec", "w" do |io|
@@ -229,6 +223,9 @@ class TestGemCommandsSetupCommand < Gem::TestCase
assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}"
assert_path_exist "#{Gem.dir}/gems/bundler-audit-1.0.0"
+
+ assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/exe/bundle"
+ assert_path_not_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/lib/bundler.rb"
end
def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_dir
@@ -380,20 +377,22 @@ class TestGemCommandsSetupCommand < Gem::TestCase
File.open "CHANGELOG.md", "w" do |io|
io.puts <<-HISTORY_TXT
-# #{Gem::VERSION} / 2013-03-26
+# Changelog
+
+## #{Gem::VERSION} / 2013-03-26
-## Bug fixes:
+### Bug fixes:
* Fixed release note display for LANG=C when installing rubygems
* π is tasty
-# 2.0.2 / 2013-03-06
+## 2.0.2 / 2013-03-06
-## Bug fixes:
+### Bug fixes:
* Other bugs fixed
-# 2.0.1 / 2013-03-05
+## 2.0.1 / 2013-03-05
-## Bug fixes:
+### Bug fixes:
* Yet more bugs fixed
HISTORY_TXT
end
@@ -403,9 +402,9 @@ class TestGemCommandsSetupCommand < Gem::TestCase
end
expected = <<-EXPECTED
-# #{Gem::VERSION} / 2013-03-26
+## #{Gem::VERSION} / 2013-03-26
-## Bug fixes:
+### Bug fixes:
* Fixed release note display for LANG=C when installing rubygems
* π is tasty
diff --git a/test/rubygems/test_gem_commands_signin_command.rb b/test/rubygems/test_gem_commands_signin_command.rb
index 29e5edceb7..e612288faf 100644
--- a/test/rubygems/test_gem_commands_signin_command.rb
+++ b/test/rubygems/test_gem_commands_signin_command.rb
@@ -121,7 +121,7 @@ class TestGemCommandsSigninCommand < Gem::TestCase
assert_match "The default access scope is:", key_name_ui.output
assert_match "index_rubygems: y", key_name_ui.output
assert_match "Do you want to customise scopes? [yN]", key_name_ui.output
- assert_equal "name=test-key&index_rubygems=true", fetcher.last_request.body
+ assert_equal "name=test-key&index_rubygems=true&push_rubygem=true", fetcher.last_request.body
credentials = load_yaml_file Gem.configuration.credentials_path
assert_equal api_key, credentials[:rubygems_api_key]
diff --git a/test/rubygems/test_gem_commands_sources_command.rb b/test/rubygems/test_gem_commands_sources_command.rb
index 5e675e5c84..71c6d5ce16 100644
--- a/test/rubygems/test_gem_commands_sources_command.rb
+++ b/test/rubygems/test_gem_commands_sources_command.rb
@@ -32,7 +32,7 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
end
expected = <<-EOF
-*** CURRENT SOURCES ***
+*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***
#{@gem_repo}
EOF
@@ -42,23 +42,104 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
end
def test_execute_add
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
+ setup_fake_source(@new_repo)
+
+ @cmd.handle_options %W[--add #{@new_repo}]
+
+ use_ui @ui do
+ @cmd.execute
end
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
+ assert_equal [@gem_repo, @new_repo], Gem.sources
+
+ expected = <<-EOF
+#{@new_repo} added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_add_without_trailing_slash
+ setup_fake_source("https://rubygems.pkg.github.com/my-org")
+
+ @cmd.handle_options %W[--add https://rubygems.pkg.github.com/my-org]
+
+ use_ui @ui do
+ @cmd.execute
end
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap specs_dump_gz do |io|
- Marshal.dump specs, io
+ assert_equal [@gem_repo, "https://rubygems.pkg.github.com/my-org/"], Gem.sources
+
+ expected = <<-EOF
+https://rubygems.pkg.github.com/my-org/ added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_add_multiple_trailing_slash
+ setup_fake_source("https://rubygems.pkg.github.com/my-org/")
+
+ @cmd.handle_options %W[--add https://rubygems.pkg.github.com/my-org///]
+
+ use_ui @ui do
+ @cmd.execute
end
- @fetcher.data["#{@new_repo}/specs.#{@marshal_version}.gz"] =
- specs_dump_gz.string
+ assert_equal [@gem_repo, "https://rubygems.pkg.github.com/my-org/"], Gem.sources
- @cmd.handle_options %W[--add #{@new_repo}]
+ expected = <<-EOF
+https://rubygems.pkg.github.com/my-org/ added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_append_without_trailing_slash
+ setup_fake_source("https://rubygems.pkg.github.com/my-org")
+
+ @cmd.handle_options %W[--append https://rubygems.pkg.github.com/my-org]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal [@gem_repo, "https://rubygems.pkg.github.com/my-org/"], Gem.sources
+
+ expected = <<-EOF
+https://rubygems.pkg.github.com/my-org/ added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_prepend_without_trailing_slash
+ setup_fake_source("https://rubygems.pkg.github.com/my-org")
+
+ @cmd.handle_options %W[--prepend https://rubygems.pkg.github.com/my-org]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal ["https://rubygems.pkg.github.com/my-org/", @gem_repo], Gem.sources
+
+ expected = <<-EOF
+https://rubygems.pkg.github.com/my-org/ added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_append
+ setup_fake_source(@new_repo)
+
+ @cmd.handle_options %W[--append #{@new_repo}]
use_ui @ui do
@cmd.execute
@@ -77,21 +158,31 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
def test_execute_add_allow_typo_squatting_source
rubygems_org = "https://rubyems.org"
- spec_fetcher do |fetcher|
- fetcher.spec("a", 1)
- end
+ setup_fake_source(rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--add #{rubygems_org}]
+ ui = Gem::MockGemUi.new("y")
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
- Marshal.dump(specs, io)
+ use_ui ui do
+ @cmd.execute
end
- @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
- @cmd.handle_options %W[--add #{rubygems_org}]
+ expected = "https://rubyems.org is too similar to https://rubygems.org\n\nDo you want to add this source? [yn] https://rubyems.org added to sources\n"
+
+ assert_equal expected, ui.output
+
+ source = Gem::Source.new(rubygems_org)
+ assert Gem.sources.include?(source)
+
+ assert_empty ui.error
+ end
+
+ def test_execute_append_allow_typo_squatting_source
+ rubygems_org = "https://rubyems.org"
+
+ setup_fake_source(rubygems_org)
+
+ @cmd.handle_options %W[--append #{rubygems_org}]
ui = Gem::MockGemUi.new("y")
use_ui ui do
@@ -111,21 +202,27 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
def test_execute_add_allow_typo_squatting_source_forced
rubygems_org = "https://rubyems.org"
- spec_fetcher do |fetcher|
- fetcher.spec("a", 1)
- end
+ setup_fake_source(rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--force --add #{rubygems_org}]
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
- Marshal.dump(specs, io)
- end
+ @cmd.execute
- @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
- @cmd.handle_options %W[--force --add #{rubygems_org}]
+ expected = "https://rubyems.org added to sources\n"
+ assert_equal expected, ui.output
+
+ source = Gem::Source.new(rubygems_org)
+ assert Gem.sources.include?(source)
+
+ assert_empty ui.error
+ end
+
+ def test_execute_append_allow_typo_squatting_source_forced
+ rubygems_org = "https://rubyems.org"
+
+ setup_fake_source(rubygems_org)
+
+ @cmd.handle_options %W[--force --append #{rubygems_org}]
@cmd.execute
@@ -141,23 +238,34 @@ class TestGemCommandsSourcesCommand < Gem::TestCase
def test_execute_add_deny_typo_squatting_source
rubygems_org = "https://rubyems.org"
- spec_fetcher do |fetcher|
- fetcher.spec("a", 1)
- end
+ setup_fake_source(rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--add #{rubygems_org}]
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
- Marshal.dump(specs, io)
+ ui = Gem::MockGemUi.new("n")
+
+ use_ui ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
end
- @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] =
- specs_dump_gz.string
+ expected = "https://rubyems.org is too similar to https://rubygems.org\n\nDo you want to add this source? [yn] "
- @cmd.handle_options %W[--add #{rubygems_org}]
+ assert_equal expected, ui.output
+
+ source = Gem::Source.new(rubygems_org)
+ refute Gem.sources.include?(source)
+
+ assert_empty ui.error
+ end
+
+ def test_execute_append_deny_typo_squatting_source
+ rubygems_org = "https://rubyems.org"
+
+ setup_fake_source(rubygems_org)
+
+ @cmd.handle_options %W[--append #{rubygems_org}]
ui = Gem::MockGemUi.new("n")
@@ -202,6 +310,31 @@ Error fetching http://beta-gems.example.com:
assert_equal "", @ui.error
end
+ def test_execute_append_nonexistent_source
+ spec_fetcher
+
+ uri = "http://beta-gems.example.com/specs.#{@marshal_version}.gz"
+ @fetcher.data[uri] = proc do
+ raise Gem::RemoteFetcher::FetchError.new("it died", uri)
+ end
+
+ @cmd.handle_options %w[--append http://beta-gems.example.com]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
+ end
+
+ expected = <<-EOF
+Error fetching http://beta-gems.example.com:
+\tit died (#{uri})
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
def test_execute_add_existent_source_invalid_uri
spec_fetcher
@@ -227,6 +360,31 @@ Error fetching https://u:REDACTED@example.com:
assert_equal "", @ui.error
end
+ def test_execute_append_existent_source_invalid_uri
+ spec_fetcher
+
+ uri = "https://u:p@example.com/specs.#{@marshal_version}.gz"
+
+ @cmd.handle_options %w[--append https://u:p@example.com]
+ @fetcher.data[uri] = proc do
+ raise Gem::RemoteFetcher::FetchError.new("it died", uri)
+ end
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
+ end
+
+ expected = <<-EOF
+Error fetching https://u:REDACTED@example.com:
+\tit died (https://u:REDACTED@example.com/specs.#{@marshal_version}.gz)
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
def test_execute_add_existent_source_invalid_uri_with_error_by_chance_including_the_uri_password
spec_fetcher
@@ -252,6 +410,31 @@ Error fetching https://u:REDACTED@example.com:
assert_equal "", @ui.error
end
+ def test_execute_append_existent_source_invalid_uri_with_error_by_chance_including_the_uri_password
+ spec_fetcher
+
+ uri = "https://u:secret@example.com/specs.#{@marshal_version}.gz"
+
+ @cmd.handle_options %w[--append https://u:secret@example.com]
+ @fetcher.data[uri] = proc do
+ raise Gem::RemoteFetcher::FetchError.new("it secretly died", uri)
+ end
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
+ end
+
+ expected = <<-EOF
+Error fetching https://u:REDACTED@example.com:
+\tit secretly died (https://u:REDACTED@example.com/specs.#{@marshal_version}.gz)
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
def test_execute_add_redundant_source
spec_fetcher
@@ -271,27 +454,34 @@ source #{@gem_repo} already present in the cache
assert_equal "", @ui.error
end
- def test_execute_add_redundant_source_trailing_slash
+ def test_execute_append_redundant_source
spec_fetcher
- # Remove pre-existing gem source (w/ slash)
- repo_with_slash = "http://gems.example.com/"
- @cmd.handle_options %W[--remove #{repo_with_slash}]
+ @cmd.handle_options %W[--append #{@gem_repo}]
+
use_ui @ui do
@cmd.execute
end
- source = Gem::Source.new repo_with_slash
- assert_equal false, Gem.sources.include?(source)
+
+ assert_equal [@gem_repo], Gem.sources
expected = <<-EOF
-#{repo_with_slash} removed from sources
+#{@gem_repo} moved to end of sources
EOF
assert_equal expected, @ui.output
assert_equal "", @ui.error
+ end
+
+ def test_execute_add_redundant_source_trailing_slash
+ repo_with_slash = "http://sample.repo/"
+
+ Gem.configuration.sources = [repo_with_slash]
+
+ setup_fake_source(repo_with_slash)
# Re-add pre-existing gem source (w/o slash)
- repo_without_slash = "http://gems.example.com"
+ repo_without_slash = repo_with_slash.delete_suffix("/")
@cmd.handle_options %W[--add #{repo_without_slash}]
use_ui @ui do
@cmd.execute
@@ -300,8 +490,7 @@ source #{@gem_repo} already present in the cache
assert_equal true, Gem.sources.include?(source)
expected = <<-EOF
-http://gems.example.com/ removed from sources
-http://gems.example.com added to sources
+source #{repo_without_slash} already present in the cache
EOF
assert_equal expected, @ui.output
@@ -316,35 +505,46 @@ http://gems.example.com added to sources
assert_equal true, Gem.sources.include?(source)
expected = <<-EOF
-http://gems.example.com/ removed from sources
-http://gems.example.com added to sources
-source http://gems.example.com/ already present in the cache
+source #{repo_without_slash} already present in the cache
+source #{repo_with_slash} already present in the cache
EOF
assert_equal expected, @ui.output
assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
end
def test_execute_add_http_rubygems_org
http_rubygems_org = "http://rubygems.org/"
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
- end
+ setup_fake_source(http_rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--add #{http_rubygems_org}]
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap specs_dump_gz do |io|
- Marshal.dump specs, io
+ ui = Gem::MockGemUi.new "n"
+
+ use_ui ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
end
- @fetcher.data["#{http_rubygems_org}/specs.#{@marshal_version}.gz"] =
- specs_dump_gz.string
+ assert_equal [@gem_repo], Gem.sources
- @cmd.handle_options %W[--add #{http_rubygems_org}]
+ expected = <<-EXPECTED
+ EXPECTED
+
+ assert_equal expected, @ui.output
+ assert_empty @ui.error
+ end
+
+ def test_execute_append_http_rubygems_org
+ http_rubygems_org = "http://rubygems.org/"
+
+ setup_fake_source(http_rubygems_org)
+
+ @cmd.handle_options %W[--append #{http_rubygems_org}]
ui = Gem::MockGemUi.new "n"
@@ -366,21 +566,27 @@ source http://gems.example.com/ already present in the cache
def test_execute_add_http_rubygems_org_forced
rubygems_org = "http://rubygems.org"
- spec_fetcher do |fetcher|
- fetcher.spec("a", 1)
- end
+ setup_fake_source(rubygems_org)
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
- end
+ @cmd.handle_options %W[--force --add #{rubygems_org}]
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap(specs_dump_gz) do |io|
- Marshal.dump(specs, io)
- end
+ @cmd.execute
- @fetcher.data["#{rubygems_org}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
- @cmd.handle_options %W[--force --add #{rubygems_org}]
+ expected = "http://rubygems.org added to sources\n"
+ assert_equal expected, ui.output
+
+ source = Gem::Source.new(rubygems_org)
+ assert Gem.sources.include?(source)
+
+ assert_empty ui.error
+ end
+
+ def test_execute_append_http_rubygems_org_forced
+ rubygems_org = "http://rubygems.org"
+
+ setup_fake_source(rubygems_org)
+
+ @cmd.handle_options %W[--force --append #{rubygems_org}]
@cmd.execute
@@ -396,27 +602,68 @@ source http://gems.example.com/ already present in the cache
def test_execute_add_https_rubygems_org
https_rubygems_org = "https://rubygems.org/"
- spec_fetcher do |fetcher|
- fetcher.spec "a", 1
+ setup_fake_source(https_rubygems_org)
+
+ @cmd.handle_options %W[--add #{https_rubygems_org}]
+
+ use_ui @ui do
+ @cmd.execute
end
- specs = Gem::Specification.map do |spec|
- [spec.name, spec.version, spec.original_platform]
+ assert_equal [@gem_repo, https_rubygems_org], Gem.sources
+
+ expected = <<-EXPECTED
+#{https_rubygems_org} added to sources
+ EXPECTED
+
+ assert_equal expected, @ui.output
+ assert_empty @ui.error
+ end
+
+ def test_execute_append_https_rubygems_org
+ https_rubygems_org = "https://rubygems.org/"
+
+ setup_fake_source(https_rubygems_org)
+
+ @cmd.handle_options %W[--append #{https_rubygems_org}]
+
+ use_ui @ui do
+ @cmd.execute
end
- specs_dump_gz = StringIO.new
- Zlib::GzipWriter.wrap specs_dump_gz do |io|
- Marshal.dump specs, io
+ assert_equal [@gem_repo, https_rubygems_org], Gem.sources
+
+ expected = <<-EXPECTED
+#{https_rubygems_org} added to sources
+ EXPECTED
+
+ assert_equal expected, @ui.output
+ assert_empty @ui.error
+ end
+
+ def test_execute_add_bad_uri
+ @cmd.handle_options %w[--add beta-gems.example.com]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
end
- @fetcher.data["#{https_rubygems_org}/specs.#{@marshal_version}.gz"] =
- specs_dump_gz.string
+ assert_equal [@gem_repo], Gem.sources
- @cmd.handle_options %W[--add #{https_rubygems_org}]
+ expected = <<-EOF
+beta-gems.example.com/ is not a URI
+ EOF
- ui = Gem::MockGemUi.new "n"
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
- use_ui ui do
+ def test_execute_append_bad_uri
+ @cmd.handle_options %w[--append beta-gems.example.com]
+
+ use_ui @ui do
assert_raise Gem::MockGemUi::TermError do
@cmd.execute
end
@@ -424,15 +671,16 @@ source http://gems.example.com/ already present in the cache
assert_equal [@gem_repo], Gem.sources
- expected = <<-EXPECTED
- EXPECTED
+ expected = <<-EOF
+beta-gems.example.com/ is not a URI
+ EOF
assert_equal expected, @ui.output
- assert_empty @ui.error
+ assert_equal "", @ui.error
end
- def test_execute_add_bad_uri
- @cmd.handle_options %w[--add beta-gems.example.com]
+ def test_execute_prepend_bad_uri
+ @cmd.handle_options %w[--prepend beta-gems.example.com]
use_ui @ui do
assert_raise Gem::MockGemUi::TermError do
@@ -443,7 +691,7 @@ source http://gems.example.com/ already present in the cache
assert_equal [@gem_repo], Gem.sources
expected = <<-EOF
-beta-gems.example.com is not a URI
+beta-gems.example.com/ is not a URI
EOF
assert_equal expected, @ui.output
@@ -476,7 +724,7 @@ beta-gems.example.com is not a URI
end
expected = <<-EOF
-*** CURRENT SOURCES ***
+*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***
#{@gem_repo}
EOF
@@ -486,24 +734,32 @@ beta-gems.example.com is not a URI
end
def test_execute_remove
- @cmd.handle_options %W[--remove #{@gem_repo}]
+ Gem.configuration.sources = [@new_repo]
+
+ setup_fake_source(@new_repo)
+
+ @cmd.handle_options %W[--remove #{@new_repo}]
use_ui @ui do
@cmd.execute
end
- expected = "#{@gem_repo} removed from sources\n"
+ expected = "#{@new_repo} removed from sources\n"
assert_equal expected, @ui.output
assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
end
def test_execute_remove_no_network
+ Gem.configuration.sources = [@new_repo]
+
spec_fetcher
- @cmd.handle_options %W[--remove #{@gem_repo}]
+ @cmd.handle_options %W[--remove #{@new_repo}]
- @fetcher.data["#{@gem_repo}Marshal.#{Gem.marshal_version}"] = proc do
+ @fetcher.data["#{@new_repo}Marshal.#{Gem.marshal_version}"] = proc do
raise Gem::RemoteFetcher::FetchError
end
@@ -511,10 +767,129 @@ beta-gems.example.com is not a URI
@cmd.execute
end
+ expected = "#{@new_repo} removed from sources\n"
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_execute_remove_not_present
+ Gem.configuration.sources = ["https://other.repo"]
+
+ @cmd.handle_options %W[--remove #{@new_repo}]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ expected = "source #{@new_repo} cannot be removed because it's not present in #{Gem.configuration.config_file_name}\n"
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_execute_remove_nothing_configured
+ spec_fetcher
+
+ @cmd.handle_options %W[--remove https://does.not.exist]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ expected = "source https://does.not.exist cannot be removed because there are no configured sources in #{Gem.configuration.config_file_name}\n"
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_remove_default_also_present_in_configuration
+ Gem.configuration.sources = [@gem_repo]
+
+ @cmd.handle_options %W[--remove #{@gem_repo}]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ expected = "WARNING: Removing a default source when it is the only source has no effect. Add a different source to #{Gem.configuration.config_file_name} if you want to stop using it as a source.\n"
+
+ assert_equal "", @ui.output
+ assert_equal expected, @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_remove_default_also_present_in_configuration_when_there_are_more_configured_sources
+ Gem.configuration.sources = [@gem_repo, "https://other.repo"]
+
+ @cmd.handle_options %W[--remove #{@gem_repo}]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
expected = "#{@gem_repo} removed from sources\n"
assert_equal expected, @ui.output
assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_execute_remove_redundant_source_trailing_slash
+ repo_with_slash = "http://sample.repo/"
+
+ Gem.configuration.sources = [repo_with_slash]
+
+ setup_fake_source(repo_with_slash)
+
+ repo_without_slash = repo_with_slash.delete_suffix("/")
+
+ @cmd.handle_options %W[--remove #{repo_without_slash}]
+ use_ui @ui do
+ @cmd.execute
+ end
+ source = Gem::Source.new repo_without_slash
+ assert_equal false, Gem.sources.include?(source)
+
+ expected = <<-EOF
+#{repo_without_slash} removed from sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
+ end
+
+ def test_execute_remove_without_trailing_slash
+ source_uri = "https://rubygems.pkg.github.com/my-org/"
+
+ Gem.configuration.sources = [source_uri]
+
+ setup_fake_source(source_uri)
+
+ @cmd.handle_options %W[--remove https://rubygems.pkg.github.com/my-org]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal [], Gem.sources
+
+ expected = <<-EOF
+#{source_uri} removed from sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ ensure
+ Gem.configuration.sources = nil
end
def test_execute_update
@@ -531,4 +906,102 @@ beta-gems.example.com is not a URI
assert_equal "source cache successfully updated\n", @ui.output
assert_equal "", @ui.error
end
+
+ def test_execute_prepend_new_source
+ setup_fake_source(@new_repo)
+
+ @cmd.handle_options %W[--prepend #{@new_repo}]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal [@new_repo, @gem_repo], Gem.sources
+
+ expected = <<-EOF
+#{@new_repo} added to sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_prepend_existing_source
+ setup_fake_source(@new_repo)
+
+ # Append the source normally first
+ @cmd.handle_options %W[--append #{@new_repo}]
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ # Initial state: [@gem_repo, @new_repo]
+ assert_equal [@gem_repo, @new_repo], Gem.sources
+
+ # Now prepend the existing source
+ @cmd.handle_options %W[--prepend #{@new_repo}]
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ # Should be moved to front: [@new_repo, @gem_repo]
+ assert_equal [@new_repo, @gem_repo], Gem.sources
+
+ expected = <<-EOF
+#{@new_repo} added to sources
+#{@new_repo} moved to top of sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_append_existing_source
+ setup_fake_source(@new_repo)
+
+ # Prepend the source first so it's at the beginning
+ @cmd.handle_options %W[--prepend #{@new_repo}]
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ # Initial state: [@new_repo, @gem_repo] (new_repo is first)
+ assert_equal [@new_repo, @gem_repo], Gem.sources
+
+ # Now append the existing source
+ @cmd.handle_options %W[--append #{@new_repo}]
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ # Should be moved to end: [@gem_repo, @new_repo]
+ assert_equal [@gem_repo, @new_repo], Gem.sources
+
+ expected = <<-EOF
+#{@new_repo} added to sources
+#{@new_repo} moved to end of sources
+ EOF
+
+ assert_equal expected, @ui.output
+ assert_equal "", @ui.error
+ end
+
+ private
+
+ def setup_fake_source(uri)
+ spec_fetcher do |fetcher|
+ fetcher.spec "a", 1
+ end
+
+ specs = Gem::Specification.map do |spec|
+ [spec.name, spec.version, spec.original_platform]
+ end
+
+ specs_dump_gz = StringIO.new
+ Zlib::GzipWriter.wrap specs_dump_gz do |io|
+ Marshal.dump specs, io
+ end
+
+ @fetcher.data["#{uri.chomp("/")}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string
+ end
end
diff --git a/test/rubygems/test_gem_commands_uninstall_command.rb b/test/rubygems/test_gem_commands_uninstall_command.rb
index 32553d1730..71ceb22ce5 100644
--- a/test/rubygems/test_gem_commands_uninstall_command.rb
+++ b/test/rubygems/test_gem_commands_uninstall_command.rb
@@ -513,7 +513,7 @@ WARNING: Use your OS package manager to uninstall vendor gems
end
msg = "ERROR: Can't use --version with multiple gems. You can specify multiple gems with" \
- " version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:~>2.0.0'`"
+ " version requirements using `gem uninstall 'my_gem:1.0.0' 'my_other_gem:>=2'`"
assert_empty @ui.output
assert_equal msg, @ui.error.lines.last.chomp
diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb
index 3b106e4581..5ed12ad481 100644
--- a/test/rubygems/test_gem_commands_update_command.rb
+++ b/test/rubygems/test_gem_commands_update_command.rb
@@ -696,6 +696,38 @@ class TestGemCommandsUpdateCommand < Gem::TestCase
assert_equal expected, @cmd.fetch_remote_gems(specs["a-1"])
end
+ def test_pass_down_the_job_option_to_make
+ gemspec = nil
+
+ spec_fetcher do |fetcher|
+ fetcher.download "a", 3 do |spec|
+ gemspec = spec
+
+ extconf_path = "#{spec.gem_dir}/extconf.rb"
+
+ write_file(extconf_path) do |io|
+ io.puts "require 'mkmf'"
+ io.puts "create_makefile '#{spec.name}'"
+ end
+
+ spec.extensions = "extconf.rb"
+ end
+
+ fetcher.gem "a", 2
+ end
+
+ use_ui @ui do
+ @cmd.invoke("a", "-j2")
+ end
+
+ gem_make_out = File.read(File.join(gemspec.extension_dir, "gem_make.out"))
+ if vc_windows? && nmake_found?
+ refute_includes(gem_make_out, " -j2")
+ else
+ assert_includes(gem_make_out, "make -j2")
+ end
+ end
+
def test_handle_options_system
@cmd.handle_options %w[--system]
diff --git a/test/rubygems/test_gem_commands_which_command.rb b/test/rubygems/test_gem_commands_which_command.rb
index cbd5b5ef14..e114d6e689 100644
--- a/test/rubygems/test_gem_commands_which_command.rb
+++ b/test/rubygems/test_gem_commands_which_command.rb
@@ -38,8 +38,6 @@ class TestGemCommandsWhichCommand < Gem::TestCase
end
def test_execute_one_missing
- # TODO: this test fails in isolation
-
util_foo_bar
@cmd.handle_options %w[foo_bar missinglib]
diff --git a/test/rubygems/test_gem_commands_yank_command.rb b/test/rubygems/test_gem_commands_yank_command.rb
index eb78e3a542..457a0e65c8 100644
--- a/test/rubygems/test_gem_commands_yank_command.rb
+++ b/test/rubygems/test_gem_commands_yank_command.rb
@@ -131,15 +131,17 @@ class TestGemCommandsYankCommand < Gem::TestCase
end
assert_match %r{Yanking gem from http://example}, @ui.output
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "You are verified with a security device. You may close the browser window.", @ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
assert_match "Successfully yanked", @ui.output
end
def test_with_webauthn_enabled_failure
+ pend "Flaky on TruffleRuby" if RUBY_ENGINE == "truffleruby"
server = Gem::MockTCPServer.new
error = Gem::WebauthnVerificationError.new("Something went wrong")
@@ -163,9 +165,10 @@ class TestGemCommandsYankCommand < Gem::TestCase
assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
assert_match %r{Yanking gem from http://example}, @ui.output
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "ERROR: Security device verification failed: Something went wrong", @ui.error
refute_match "You are verified with a security device. You may close the browser window.", @ui.output
refute_match "Successfully yanked", @ui.output
@@ -189,9 +192,10 @@ class TestGemCommandsYankCommand < Gem::TestCase
end
assert_match %r{Yanking gem from http://example}, @ui.output
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "You are verified with a security device. You may close the browser window.", @ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
assert_match "Successfully yanked", @ui.output
@@ -219,9 +223,10 @@ class TestGemCommandsYankCommand < Gem::TestCase
assert_match @fetcher.last_request["Authorization"], Gem.configuration.rubygems_api_key
assert_match %r{Yanking gem from http://example}, @ui.output
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @ui.output
assert_match "ERROR: Security device verification failed: The token in the link you used has either expired " \
"or been used already.", @ui.error
refute_match "You are verified with a security device. You may close the browser window.", @ui.output
@@ -267,7 +272,7 @@ class TestGemCommandsYankCommand < Gem::TestCase
assert_equal [yank_uri], @fetcher.paths
end
- def test_yank_gem_unathorized_api_key
+ def test_yank_gem_unauthorized_api_key
response_forbidden = "The API key doesn't have access"
response_success = "Successfully yanked"
host = "http://example"
diff --git a/test/rubygems/test_gem_config_file.rb b/test/rubygems/test_gem_config_file.rb
index 4230eda4d3..3c79cb0762 100644
--- a/test/rubygems/test_gem_config_file.rb
+++ b/test/rubygems/test_gem_config_file.rb
@@ -43,6 +43,7 @@ class TestGemConfigFile < Gem::TestCase
assert_equal [@gem_repo], Gem.sources
assert_equal 365, @cfg.cert_expiration_length_days
assert_equal false, @cfg.ipv4_fallback_enabled
+ assert_equal true, @cfg.install_extension_in_lib
File.open @temp_conf, "w" do |fp|
fp.puts ":backtrace: true"
@@ -52,14 +53,16 @@ class TestGemConfigFile < Gem::TestCase
fp.puts ":sources:"
fp.puts " - http://more-gems.example.com"
fp.puts "install: --wrappers"
+ fp.puts ":gemhome: /tmp/gems"
fp.puts ":gempath:"
fp.puts "- /usr/ruby/1.8/lib/ruby/gems/1.8"
fp.puts "- /var/ruby/1.8/gem_home"
fp.puts ":ssl_verify_mode: 0"
fp.puts ":ssl_ca_cert: /etc/ssl/certs"
fp.puts ":cert_expiration_length_days: 28"
- fp.puts ":install_extension_in_lib: true"
+ fp.puts ":install_extension_in_lib: false"
fp.puts ":ipv4_fallback_enabled: true"
+ fp.puts ":use_psych: true"
end
util_config_file
@@ -69,13 +72,15 @@ class TestGemConfigFile < Gem::TestCase
assert_equal false, @cfg.update_sources
assert_equal %w[http://more-gems.example.com], @cfg.sources
assert_equal "--wrappers", @cfg[:install]
+ assert_equal "/tmp/gems", @cfg.home
assert_equal(["/usr/ruby/1.8/lib/ruby/gems/1.8", "/var/ruby/1.8/gem_home"],
@cfg.path)
assert_equal 0, @cfg.ssl_verify_mode
assert_equal "/etc/ssl/certs", @cfg.ssl_ca_cert
assert_equal 28, @cfg.cert_expiration_length_days
- assert_equal true, @cfg.install_extension_in_lib
+ assert_equal false, @cfg.install_extension_in_lib
assert_equal true, @cfg.ipv4_fallback_enabled
+ assert_equal true, @cfg.use_psych
end
def test_initialize_ipv4_fallback_enabled_env
@@ -83,6 +88,53 @@ class TestGemConfigFile < Gem::TestCase
util_config_file %W[--config-file #{@temp_conf}]
assert_equal true, @cfg.ipv4_fallback_enabled
+ ensure
+ ENV.delete("IPV4_FALLBACK_ENABLED")
+ end
+
+ def test_initialize_global_gem_cache_default
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal false, @cfg.global_gem_cache
+ end
+
+ def test_initialize_global_gem_cache_env
+ ENV["RUBYGEMS_GLOBAL_GEM_CACHE"] = "true"
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal true, @cfg.global_gem_cache
+ ensure
+ ENV.delete("RUBYGEMS_GLOBAL_GEM_CACHE")
+ end
+
+ def test_initialize_global_gem_cache_gemrc
+ File.open @temp_conf, "w" do |fp|
+ fp.puts ":global_gem_cache: true"
+ end
+
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal true, @cfg.global_gem_cache
+ end
+
+ def test_initialize_use_psych_env
+ orig_use_psych = ENV["RUBYGEMS_USE_PSYCH"]
+ ENV["RUBYGEMS_USE_PSYCH"] = "true"
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal true, @cfg.use_psych
+ ensure
+ ENV["RUBYGEMS_USE_PSYCH"] = orig_use_psych
+ end
+
+ def test_initialize_concurrent_downloads
+ File.open @temp_conf, "w" do |fp|
+ fp.puts ":concurrent_downloads: 2"
+ end
+
+ util_config_file %W[--config-file #{@temp_conf}]
+
+ assert_equal 2, @cfg.concurrent_downloads
end
def test_initialize_handle_arguments_config_file
diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb
index 56b84160c4..c2fb6f264b 100644
--- a/test/rubygems/test_gem_dependency_installer.rb
+++ b/test/rubygems/test_gem_dependency_installer.rb
@@ -382,13 +382,9 @@ class TestGemDependencyInstaller < Gem::TestCase
FileUtils.mv f1_gem, @tempdir
inst = nil
- pwd = Dir.getwd
- Dir.chdir @tempdir
- begin
+ Dir.chdir @tempdir do
inst = Gem::DependencyInstaller.new
inst.install "f"
- ensure
- Dir.chdir pwd
end
assert_equal %w[f-1], inst.installed_gems.map(&:full_name)
@@ -523,6 +519,58 @@ class TestGemDependencyInstaller < Gem::TestCase
assert_equal %w[a-1], inst.installed_gems.map(&:full_name)
end
+ def test_install_local_with_extensions_already_installed
+ pend "needs investigation" if Gem.java_platform?
+ pend "ruby.h is not provided by ruby repo" if ruby_repo?
+
+ @spec = quick_gem "a" do |s|
+ s.extensions << "extconf.rb"
+ s.files += %w[extconf.rb a.c]
+ end
+
+ write_dummy_extconf "a"
+
+ c_source_path = File.join(@tempdir, "a.c")
+
+ write_file c_source_path do |io|
+ io.write <<-C
+ #include <ruby.h>
+ void Init_a() { }
+ C
+ end
+
+ package_path = Gem::Package.build @spec
+ installer = Gem::Installer.at(package_path)
+
+ # Make sure the gem is installed and backup the correct package
+
+ installer.install
+
+ package_bkp_path = "#{package_path}.bkp"
+ FileUtils.cp package_path, package_bkp_path
+
+ # Break the extension, rebuild it, and try to install it
+
+ write_file c_source_path do |io|
+ io.write "typo"
+ end
+
+ Gem::Package.build @spec
+
+ assert_raise Gem::Ext::BuildError do
+ installer.install
+ end
+
+ # Make sure installing the good package again still works
+
+ FileUtils.cp "#{package_path}.bkp", package_path
+
+ Dir.chdir @tempdir do
+ inst = Gem::DependencyInstaller.new domain: :local
+ inst.install package_path
+ end
+ end
+
def test_install_minimal_deps
util_setup_gems
@@ -629,8 +677,7 @@ class TestGemDependencyInstaller < Gem::TestCase
util_setup_gems
FileUtils.mv @b1_gem, @tempdir
- si = util_setup_spec_fetcher @b1
- @fetcher.data["http://gems.example.com/gems/yaml"] = si.to_yaml
+ util_setup_spec_fetcher @b1
inst = nil
Dir.chdir @tempdir do
@@ -641,6 +688,25 @@ class TestGemDependencyInstaller < Gem::TestCase
assert_equal %w[b-1], inst.installed_gems.map(&:full_name)
end
+ def test_install_force_with_unsatisfiable_dep
+ # foo depends on bar >= 2.0, but only bar-1.0 exists.
+ # With --force, the unsatisfiable dep should be skipped.
+ _, foo_gem = util_gem "foo", "1" do |s|
+ s.add_dependency "bar", ">= 2.0"
+ end
+
+ util_setup_spec_fetcher(util_spec("bar", "1.0"))
+ FileUtils.mv foo_gem, @tempdir
+ inst = nil
+
+ Dir.chdir @tempdir do
+ inst = Gem::DependencyInstaller.new force: true
+ inst.install "foo"
+ end
+
+ assert_equal %w[foo-1], inst.installed_gems.map(&:full_name)
+ end
+
def test_install_build_args
util_setup_gems
@@ -746,13 +812,12 @@ class TestGemDependencyInstaller < Gem::TestCase
inst = nil
Dir.chdir @tempdir do
- e = assert_raise Gem::UnsatisfiableDependencyError do
+ e = assert_raise Gem::DependencyResolutionError do
inst = Gem::DependencyInstaller.new domain: :local
inst.install "b"
end
- expected = "Unable to resolve dependency: 'b (>= 0)' requires 'a (>= 0)'"
- assert_equal expected, e.message
+ assert_match(/depends on a >= 0 which could not be found in any repository/, e.message)
end
assert_equal [], inst.installed_gems.map(&:full_name)
@@ -907,9 +972,7 @@ class TestGemDependencyInstaller < Gem::TestCase
s.platform = Gem::Platform.new %w[cpu other_platform 1]
end
- si = util_setup_spec_fetcher @a1, a2_o
-
- @fetcher.data["http://gems.example.com/gems/yaml"] = si.to_yaml
+ util_setup_spec_fetcher @a1, a2_o
a1_data = nil
a2_o_data = nil
@@ -1066,117 +1129,6 @@ class TestGemDependencyInstaller < Gem::TestCase
assert_equal %w[activesupport-1.0.0], Gem::Specification.map(&:full_name)
end
- def test_find_gems_gems_with_sources
- util_setup_gems
-
- inst = Gem::DependencyInstaller.new
- dep = Gem::Dependency.new "b", ">= 0"
-
- Gem::Specification.reset
-
- set = Gem::Deprecate.skip_during do
- inst.find_gems_with_sources(dep)
- end
-
- assert_kind_of Gem::AvailableSet, set
-
- s = set.set.first
-
- assert_equal @b1, s.spec
- assert_equal Gem::Source.new(@gem_repo), s.source
- end
-
- def test_find_gems_with_sources_local
- util_setup_gems
-
- FileUtils.mv @a1_gem, @tempdir
- inst = Gem::DependencyInstaller.new
- dep = Gem::Dependency.new "a", ">= 0"
- set = nil
-
- Dir.chdir @tempdir do
- set = Gem::Deprecate.skip_during do
- inst.find_gems_with_sources dep
- end
- end
-
- gems = set.sorted
-
- assert_equal 2, gems.length
-
- remote, local = gems
-
- assert_equal "a-1", local.spec.full_name, "local spec"
- assert_equal File.join(@tempdir, @a1.file_name),
- local.source.download(local.spec), "local path"
-
- assert_equal "a-1", remote.spec.full_name, "remote spec"
- assert_equal Gem::Source.new(@gem_repo), remote.source, "remote path"
- end
-
- def test_find_gems_with_sources_prerelease
- util_setup_gems
-
- installer = Gem::DependencyInstaller.new
-
- dependency = Gem::Dependency.new("a", Gem::Requirement.default)
-
- set = Gem::Deprecate.skip_during do
- installer.find_gems_with_sources(dependency)
- end
-
- releases = set.all_specs
-
- assert releases.any? {|s| s.name == "a" && s.version.to_s == "1" }
- refute releases.any? {|s| s.name == "a" && s.version.to_s == "1.a" }
-
- dependency.prerelease = true
-
- set = Gem::Deprecate.skip_during do
- installer.find_gems_with_sources(dependency)
- end
-
- prereleases = set.all_specs
-
- assert_equal [@a1_pre, @a1], prereleases
- end
-
- def test_find_gems_with_sources_with_best_only_and_platform
- util_setup_gems
- a1_x86_mingw32, = util_gem "a", "1" do |s|
- s.platform = "x86-mingw32"
- end
- util_setup_spec_fetcher @a1, a1_x86_mingw32
- Gem.platforms << Gem::Platform.new("x86-mingw32")
-
- installer = Gem::DependencyInstaller.new
-
- dependency = Gem::Dependency.new("a", Gem::Requirement.default)
-
- set = Gem::Deprecate.skip_during do
- installer.find_gems_with_sources(dependency, true)
- end
-
- releases = set.all_specs
-
- assert_equal [a1_x86_mingw32], releases
- end
-
- def test_find_gems_with_sources_with_bad_source
- Gem.sources.replace ["http://not-there.nothing"]
-
- installer = Gem::DependencyInstaller.new
-
- dep = Gem::Dependency.new("a")
-
- out = Gem::Deprecate.skip_during do
- installer.find_gems_with_sources(dep)
- end
-
- assert out.empty?
- assert_kind_of Gem::SourceFetchProblem, installer.errors.first
- end
-
def test_resolve_dependencies
util_setup_gems
diff --git a/test/rubygems/test_gem_dependency_resolution_error.rb b/test/rubygems/test_gem_dependency_resolution_error.rb
index 98a6b6b8fd..d8fa96a260 100644
--- a/test/rubygems/test_gem_dependency_resolution_error.rb
+++ b/test/rubygems/test_gem_dependency_resolution_error.rb
@@ -6,20 +6,23 @@ class TestGemDependencyResolutionError < Gem::TestCase
def setup
super
- @spec = util_spec "a", 2
-
- @a1_req = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
- @a2_req = Gem::Resolver::DependencyRequest.new dep("a", "= 2"), nil
+ failure = Struct.new(:explanation).new("a depends on b (= 1.0) but no versions match")
+ @error = Gem::DependencyResolutionError.new failure
+ end
- @activated = Gem::Resolver::ActivationRequest.new @spec, @a2_req
+ def test_message
+ assert_equal "a depends on b (= 1.0) but no versions match", @error.message
+ end
- @conflict = Gem::Resolver::Conflict.new @a1_req, @activated
+ def test_explanation
+ assert_equal "a depends on b (= 1.0) but no versions match", @error.explanation
+ end
- @error = Gem::DependencyResolutionError.new @conflict
+ def test_conflict
+ assert_nil @error.conflict
end
- def test_message
- assert_match(/^conflicting dependencies a \(= 1\) and a \(= 2\)$/,
- @error.message)
+ def test_conflicting_dependencies
+ assert_equal [], @error.conflicting_dependencies
end
end
diff --git a/test/rubygems/test_gem_ext_builder.rb b/test/rubygems/test_gem_ext_builder.rb
index 34f85e6b75..37204f3c47 100644
--- a/test/rubygems/test_gem_ext_builder.rb
+++ b/test/rubygems/test_gem_ext_builder.rb
@@ -18,7 +18,7 @@ class TestGemExtBuilder < Gem::TestCase
@spec = util_spec "a"
- @builder = Gem::Ext::Builder.new @spec, ""
+ @builder = Gem::Ext::Builder.new @spec
end
def teardown
@@ -106,6 +106,22 @@ install:
assert_match(/install: OK/, results)
end
+ def test_class_run_closes_stdin
+ results = []
+ check_stdin_script = <<~'RUBY'
+ if IO.select([STDIN], nil, nil, 1)
+ puts "STDIN: #{STDIN.read.inspect}"
+ else
+ puts "NOT_READY"
+ end
+ RUBY
+
+ Gem::Ext::Builder.run([Gem.ruby, "-e", check_stdin_script], results)
+
+ command_output = results.last
+ assert_equal "STDIN: \"\"\n", command_output
+ end
+
def test_build_extensions
pend "terminates on mswin" if vc_windows? && ruby_repo?
@@ -201,6 +217,57 @@ install:
Gem.configuration.install_extension_in_lib = @orig_install_extension_in_lib
end
+ def test_build_multiple_extensions
+ pend if RUBY_ENGINE == "truffleruby"
+ pend "terminates on ruby/ruby" if ruby_repo?
+
+ extension_in_lib do
+ @spec.extensions << "ext/Rakefile"
+ @spec.extensions << "ext/extconf.rb"
+
+ ext_dir = File.join @spec.gem_dir, "ext"
+
+ FileUtils.mkdir_p ext_dir
+
+ extconf_rb = File.join ext_dir, "extconf.rb"
+ rakefile = File.join ext_dir, "Rakefile"
+
+ File.open extconf_rb, "w" do |f|
+ f.write <<-'RUBY'
+ require 'mkmf'
+
+ create_makefile 'a'
+ RUBY
+ end
+
+ File.open rakefile, "w" do |f|
+ f.write <<-RUBY
+ task :default do
+ FileUtils.touch File.join "#{ext_dir}", 'foo'
+ end
+ RUBY
+ end
+
+ ext_lib_dir = File.join ext_dir, "lib"
+ FileUtils.mkdir ext_lib_dir
+ FileUtils.touch File.join ext_lib_dir, "a.rb"
+ FileUtils.mkdir File.join ext_lib_dir, "a"
+ FileUtils.touch File.join ext_lib_dir, "a", "b.rb"
+
+ use_ui @ui do
+ @builder.build_extensions
+ end
+
+ assert_path_exist @spec.extension_dir
+ assert_path_exist @spec.gem_build_complete_path
+ assert_path_exist File.join @spec.gem_dir, "ext", "foo"
+ assert_path_exist File.join @spec.extension_dir, "gem_make.out"
+ assert_path_exist File.join @spec.extension_dir, "a.rb"
+ assert_path_exist File.join @spec.gem_dir, "lib", "a.rb"
+ assert_path_exist File.join @spec.gem_dir, "lib", "a", "b.rb"
+ end
+ end
+
def test_build_extensions_none
use_ui @ui do
@builder.build_extensions
diff --git a/test/rubygems/test_gem_ext_cargo_builder.rb b/test/rubygems/test_gem_ext_cargo_builder.rb
index 5faf3e2480..b970e442c2 100644
--- a/test/rubygems/test_gem_ext_cargo_builder.rb
+++ b/test/rubygems/test_gem_ext_cargo_builder.rb
@@ -3,6 +3,10 @@
require_relative "helper"
require "rubygems/ext"
require "open3"
+begin
+ require "fiddle"
+rescue LoadError
+end
class TestGemExtCargoBuilder < Gem::TestCase
def setup
@@ -137,6 +141,58 @@ class TestGemExtCargoBuilder < Gem::TestCase
end
end
+ def test_linker_args
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "clang"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert_nil args[2]
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_options
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "gcc -Wl,--no-undefined"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert args[3], "link-args=-Wl,--no-undefined"
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_cachetools
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "sccache clang"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert_nil args[2]
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_cachetools_and_options
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "ccache gcc -Wl,--no-undefined"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert args[3], "link-args=-Wl,--no-undefined"
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
private
def skip_unsupported_platforms!
@@ -149,7 +205,8 @@ class TestGemExtCargoBuilder < Gem::TestCase
end
def assert_ffi_handle(bundle, name)
- require "fiddle"
+ return unless defined?(Fiddle)
+
dylib_handle = Fiddle.dlopen bundle
assert_nothing_raised { dylib_handle[name] }
ensure
@@ -157,7 +214,8 @@ class TestGemExtCargoBuilder < Gem::TestCase
end
def refute_ffi_handle(bundle, name)
- require "fiddle"
+ return unless defined?(Fiddle)
+
dylib_handle = Fiddle.dlopen bundle
assert_raise { dylib_handle[name] }
ensure
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock
index aaa512fd34..d6c49c3de1 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "aho-corasick"
@@ -13,16 +13,14 @@ dependencies = [
[[package]]
name = "bindgen"
-version = "0.69.1"
+version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
+checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
- "lazy_static",
- "lazycell",
- "peeking_take_while",
+ "itertools",
"proc-macro2",
"quote",
"regex",
@@ -71,22 +69,31 @@ dependencies = [
]
[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
-name = "lazy_static"
-version = "1.4.0"
+name = "itertools"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
[[package]]
-name = "lazycell"
-version = "1.3.0"
+name = "lazy_static"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
@@ -127,16 +134,10 @@ dependencies = [
]
[[package]]
-name = "peeking_take_while"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-
-[[package]]
name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
@@ -152,18 +153,18 @@ dependencies = [
[[package]]
name = "rb-sys"
-version = "0.9.107"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56aaf81d9efc195606456e91896297ee5ab2002381539f8ed1ba6b4f2e467f3b"
+checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
-version = "0.9.107"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "035b513baded6df2b90a8559efb1973c47ba42e16c21c5f0863dd2aa4dbd6abe"
+checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
dependencies = [
"bindgen",
"lazy_static",
@@ -193,9 +194,9 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustc-hash"
-version = "1.1.0"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
[[package]]
name = "shell-words"
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml
index 07c57c144d..056567c708 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml
@@ -7,4 +7,4 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-rb-sys = "0.9.107"
+rb-sys = "0.9.128"
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock
index d6836e68f6..806d51d3a1 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "aho-corasick"
@@ -13,16 +13,14 @@ dependencies = [
[[package]]
name = "bindgen"
-version = "0.69.1"
+version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2"
+checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
- "lazy_static",
- "lazycell",
- "peeking_take_while",
+ "itertools",
"proc-macro2",
"quote",
"regex",
@@ -64,22 +62,31 @@ dependencies = [
]
[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
-name = "lazy_static"
-version = "1.4.0"
+name = "itertools"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
[[package]]
-name = "lazycell"
-version = "1.3.0"
+name = "lazy_static"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
@@ -120,16 +127,10 @@ dependencies = [
]
[[package]]
-name = "peeking_take_while"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-
-[[package]]
name = "proc-macro2"
-version = "1.0.66"
+version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
@@ -145,18 +146,18 @@ dependencies = [
[[package]]
name = "rb-sys"
-version = "0.9.107"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56aaf81d9efc195606456e91896297ee5ab2002381539f8ed1ba6b4f2e467f3b"
+checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
-version = "0.9.107"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "035b513baded6df2b90a8559efb1973c47ba42e16c21c5f0863dd2aa4dbd6abe"
+checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
dependencies = [
"bindgen",
"lazy_static",
@@ -193,9 +194,9 @@ dependencies = [
[[package]]
name = "rustc-hash"
-version = "1.1.0"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
[[package]]
name = "shell-words"
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml
index 45ab82869b..f0ddeeb91c 100644
--- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml
@@ -7,4 +7,4 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-rb-sys = "0.9.107"
+rb-sys = "0.9.128"
diff --git a/test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb b/test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb
index a3fef50d54..3693f63df6 100644
--- a/test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb
+++ b/test/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb
@@ -25,7 +25,7 @@ class TestGemExtCargoBuilderLinkFlagConverter < Gem::TestCase
}.freeze
CASES.each do |test_name, (arg, expected)|
- raise "duplicate test name" if instance_methods.include?(test_name)
+ raise "duplicate test name" if method_defined?(test_name)
define_method(test_name) do
assert_equal(expected, Gem::Ext::CargoBuilder::LinkFlagConverter.convert(arg))
diff --git a/test/rubygems/test_gem_ext_cmake_builder.rb b/test/rubygems/test_gem_ext_cmake_builder.rb
index 5f886af05f..b9b57084d4 100644
--- a/test/rubygems/test_gem_ext_cmake_builder.rb
+++ b/test/rubygems/test_gem_ext_cmake_builder.rb
@@ -7,7 +7,7 @@ class TestGemExtCmakeBuilder < Gem::TestCase
def setup
super
- # Details: https://github.com/rubygems/rubygems/issues/1270#issuecomment-177368340
+ # Details: https://github.com/ruby/rubygems/issues/1270#issuecomment-177368340
pend "CmakeBuilder doesn't work on Windows." if Gem.win_platform?
require "open3"
@@ -29,7 +29,7 @@ class TestGemExtCmakeBuilder < Gem::TestCase
def test_self_build
File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists|
cmakelists.write <<-EO_CMAKE
-cmake_minimum_required(VERSION 2.6)
+cmake_minimum_required(VERSION 3.26)
project(self_build NONE)
install (FILES test.txt DESTINATION bin)
EO_CMAKE
@@ -39,46 +39,107 @@ install (FILES test.txt DESTINATION bin)
output = []
- Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext
+ builder = Gem::Ext::CmakeBuilder.new
+ builder.build nil, @dest_path, output, [], @dest_path, @ext
output = output.join "\n"
- assert_match(/^cmake \. -DCMAKE_INSTALL_PREFIX\\=#{Regexp.escape @dest_path}/, output)
+ assert_match(/^current directory: #{Regexp.escape @ext}/, output)
+ assert_match(/cmake.*-DCMAKE_RUNTIME_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output)
+ assert_match(/cmake.*-DCMAKE_LIBRARY_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output)
+ assert_match(/#{Regexp.escape @ext}/, output)
+ end
+
+ def test_self_build_presets
+ File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists|
+ cmakelists.write <<-EO_CMAKE
+cmake_minimum_required(VERSION 3.26)
+project(self_build NONE)
+install (FILES test.txt DESTINATION bin)
+ EO_CMAKE
+ end
+
+ File.open File.join(@ext, "CMakePresets.json"), "w" do |presets|
+ presets.write <<-EO_CMAKE
+{
+ "version": 6,
+ "configurePresets": [
+ {
+ "name": "debug",
+ "displayName": "Debug",
+ "generator": "Ninja",
+ "binaryDir": "build/debug",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Debug"
+ }
+ },
+ {
+ "name": "release",
+ "displayName": "Release",
+ "generator": "Ninja",
+ "binaryDir": "build/release",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Release"
+ }
+ }
+ ]
+}
+ EO_CMAKE
+ end
+
+ FileUtils.touch File.join(@ext, "test.txt")
+
+ output = []
+
+ builder = Gem::Ext::CmakeBuilder.new
+ builder.build nil, @dest_path, output, [], @dest_path, @ext
+
+ output = output.join "\n"
+
+ assert_match(/The gem author provided a list of presets that can be used to build the gem./, output)
+ assert_match(/Available configure presets/, output)
+ assert_match(/\"debug\" - Debug/, output)
+ assert_match(/\"release\" - Release/, output)
+ assert_match(/^current directory: #{Regexp.escape @ext}/, output)
+ assert_match(/cmake.*-DCMAKE_RUNTIME_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output)
+ assert_match(/cmake.*-DCMAKE_LIBRARY_OUTPUT_DIRECTORY\\=#{Regexp.escape @dest_path}/, output)
assert_match(/#{Regexp.escape @ext}/, output)
- assert_contains_make_command "", output
- assert_contains_make_command "install", output
- assert_match(/test\.txt/, output)
end
def test_self_build_fail
output = []
+ builder = Gem::Ext::CmakeBuilder.new
error = assert_raise Gem::InstallError do
- Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext
+ builder.build nil, @dest_path, output, [], @dest_path, @ext
end
- output = output.join "\n"
+ assert_match "cmake_configure failed", error.message
shell_error_msg = /(CMake Error: .*)/
-
- assert_match "cmake failed", error.message
-
- assert_match(/^cmake . -DCMAKE_INSTALL_PREFIX\\=#{Regexp.escape @dest_path}/, output)
+ output = output.join "\n"
assert_match(/#{shell_error_msg}/, output)
+ assert_match(/CMake Error: The source directory .* does not appear to contain CMakeLists.txt./, output)
end
def test_self_build_has_makefile
- File.open File.join(@ext, "Makefile"), "w" do |makefile|
- makefile.puts "all:\n\t@echo ok\ninstall:\n\t@echo ok"
+ File.open File.join(@ext, "CMakeLists.txt"), "w" do |cmakelists|
+ cmakelists.write <<-EO_CMAKE
+cmake_minimum_required(VERSION 3.26)
+project(self_build NONE)
+install (FILES test.txt DESTINATION bin)
+ EO_CMAKE
end
output = []
- Gem::Ext::CmakeBuilder.build nil, @dest_path, output, [], nil, @ext
+ builder = Gem::Ext::CmakeBuilder.new
+ builder.build nil, @dest_path, output, [], @dest_path, @ext
output = output.join "\n"
- assert_contains_make_command "", output
- assert_contains_make_command "install", output
+ # The default generator will create a Makefile in the build directory
+ makefile = File.join(@ext, "build", "Makefile")
+ assert(File.exist?(makefile))
end
end
diff --git a/test/rubygems/test_gem_ext_ext_conf_builder.rb b/test/rubygems/test_gem_ext_ext_conf_builder.rb
index 218c6f3d5e..bc383e5540 100644
--- a/test/rubygems/test_gem_ext_ext_conf_builder.rb
+++ b/test/rubygems/test_gem_ext_ext_conf_builder.rb
@@ -15,15 +15,12 @@ class TestGemExtExtConfBuilder < Gem::TestCase
end
def test_class_build
- if Gem.java_platform?
- pend("failing on jruby")
- end
-
if vc_windows? && !nmake_found?
pend("test_class_build skipped - nmake not found")
end
File.open File.join(@ext, "extconf.rb"), "w" do |extconf|
+ extconf.puts "return if Gem.java_platform?"
extconf.puts "require 'mkmf'\ncreate_makefile 'foo'"
end
@@ -35,20 +32,22 @@ class TestGemExtExtConfBuilder < Gem::TestCase
assert_match(/^current directory:/, output[0])
assert_match(/^#{Regexp.quote(Gem.ruby)}.* extconf.rb/, output[1])
- assert_equal "creating Makefile\n", output[2]
- assert_match(/^current directory:/, output[3])
- assert_contains_make_command "clean", output[4]
- assert_contains_make_command "", output[7]
- assert_contains_make_command "install", output[10]
+
+ if Gem.java_platform?
+ assert_includes(output, "Skipping make for extconf.rb as no Makefile was found.")
+ else
+ assert_equal "creating Makefile\n", output[2]
+ assert_match(/^current directory:/, output[3])
+ assert_contains_make_command "clean", output[4]
+ assert_contains_make_command "", output[7]
+ assert_contains_make_command "install", output[10]
+ end
+
assert_empty Dir.glob(File.join(@ext, "siteconf*.rb"))
assert_empty Dir.glob(File.join(@ext, ".gem.*"))
end
def test_class_build_rbconfig_make_prog
- if Gem.java_platform?
- pend("failing on jruby")
- end
-
configure_args do
File.open File.join(@ext, "extconf.rb"), "w" do |extconf|
extconf.puts "require 'mkmf'\ncreate_makefile 'foo'"
@@ -72,10 +71,6 @@ class TestGemExtExtConfBuilder < Gem::TestCase
env_large_make = ENV.delete "MAKE"
ENV["MAKE"] = "anothermake"
- if Gem.java_platform?
- pend("failing on jruby")
- end
-
configure_args "" do
File.open File.join(@ext, "extconf.rb"), "w" do |extconf|
extconf.puts "require 'mkmf'\ncreate_makefile 'foo'"
@@ -206,11 +201,11 @@ end
end
def test_class_make_no_Makefile
- error = assert_raise Gem::InstallError do
+ error = assert_raise Gem::Ext::Builder::NoMakefileError do
Gem::Ext::ExtConfBuilder.make @ext, ["output"], @ext
end
- assert_equal "Makefile not found", error.message
+ assert_match(/No Makefile found/, error.message)
end
def configure_args(args = nil)
diff --git a/test/rubygems/test_gem_ext_rake_builder.rb b/test/rubygems/test_gem_ext_rake_builder.rb
index bd72c1aa08..68ad15b044 100644
--- a/test/rubygems/test_gem_ext_rake_builder.rb
+++ b/test/rubygems/test_gem_ext_rake_builder.rb
@@ -29,7 +29,7 @@ class TestGemExtRakeBuilder < Gem::TestCase
end
end
- # https://github.com/rubygems/rubygems/pull/1819
+ # https://github.com/ruby/rubygems/pull/1819
#
# It should not fail with a non-empty args list either
def test_class_build_with_args
diff --git a/test/rubygems/test_gem_gem_runner.rb b/test/rubygems/test_gem_gem_runner.rb
index 4fb205040c..9cc2fac619 100644
--- a/test/rubygems/test_gem_gem_runner.rb
+++ b/test/rubygems/test_gem_gem_runner.rb
@@ -82,17 +82,6 @@ class TestGemGemRunner < Gem::TestCase
assert_equal %w[--foo], args
end
- def test_query_is_deprecated
- args = %w[query]
-
- use_ui @ui do
- @runner.run(args)
- end
-
- assert_match(/WARNING: query command is deprecated. It will be removed in Rubygems [0-9]+/, @ui.error)
- assert_match(/WARNING: It is recommended that you use `gem search` or `gem list` instead/, @ui.error)
- end
-
def test_info_succeeds
args = %w[info]
diff --git a/test/rubygems/test_gem_gemcutter_utilities.rb b/test/rubygems/test_gem_gemcutter_utilities.rb
index a3236e6276..ca34c8d03d 100644
--- a/test/rubygems/test_gem_gemcutter_utilities.rb
+++ b/test/rubygems/test_gem_gemcutter_utilities.rb
@@ -150,7 +150,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
util_sign_in
- assert_equal "", @sign_in_ui.output
+ assert_match(/You are already signed in/, @sign_in_ui.output)
end
def test_sign_in_skips_with_key_override
@@ -158,7 +158,7 @@ class TestGemGemcutterUtilities < Gem::TestCase
@cmd.options[:key] = :KEY
util_sign_in
- assert_equal "", @sign_in_ui.output
+ assert_match(/You are already signed in/, @sign_in_ui.output)
end
def test_sign_in_with_other_credentials_doesnt_overwrite_other_keys
@@ -233,9 +233,10 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @sign_in_ui.output
assert_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
end
@@ -255,9 +256,10 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
assert_equal 1, error.exit_code
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @sign_in_ui.output
assert_match "ERROR: Security device verification failed: Something went wrong", @sign_in_ui.error
refute_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
refute_match "Signed in with API key:", @sign_in_ui.output
@@ -273,9 +275,10 @@ class TestGemGemcutterUtilities < Gem::TestCase
util_sign_in
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @sign_in_ui.output
assert_match "You are verified with a security device. You may close the browser window.", @sign_in_ui.output
assert_equal "Uvh6T57tkWuUnWYo", @fetcher.last_request["OTP"]
end
@@ -292,9 +295,10 @@ class TestGemGemcutterUtilities < Gem::TestCase
end
end
- assert_match "You have enabled multi-factor authentication. Please visit #{@fetcher.webauthn_url_with_port(server.port)} " \
+ assert_match "You have enabled multi-factor authentication. Please visit the following URL " \
"to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, " \
"you can re-run the gem signin command with the `--otp [your_code]` option.", @sign_in_ui.output
+ assert_match @fetcher.webauthn_url_with_port(server.port), @sign_in_ui.output
assert_match "ERROR: Security device verification failed: " \
"The token in the link you used has either expired or been used already.", @sign_in_ui.error
end
diff --git a/test/rubygems/test_gem_impossible_dependencies_error.rb b/test/rubygems/test_gem_impossible_dependencies_error.rb
deleted file mode 100644
index 94c0290ea1..0000000000
--- a/test/rubygems/test_gem_impossible_dependencies_error.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-
-class TestGemImpossibleDependenciesError < Gem::TestCase
- def test_message_conflict
- request = dependency_request dep("net-ssh", ">= 2.0.13"), "rye", "0.9.8"
-
- conflicts = []
-
- # These conflicts are lies as their dependencies does not have the correct
- # requested-by entries, but they are suitable for testing the message.
- # See #485 to construct a correct conflict.
- net_ssh_2_2_2 =
- dependency_request dep("net-ssh", ">= 2.6.5"), "net-ssh", "2.2.2", request
- net_ssh_2_6_5 =
- dependency_request dep("net-ssh", "~> 2.2.2"), "net-ssh", "2.6.5", request
-
- conflict1 = Gem::Resolver::Conflict.new \
- net_ssh_2_6_5, net_ssh_2_6_5.requester
-
- conflict2 = Gem::Resolver::Conflict.new \
- net_ssh_2_2_2, net_ssh_2_2_2.requester
-
- conflicts << [net_ssh_2_6_5.requester.spec, conflict1]
- conflicts << [net_ssh_2_2_2.requester.spec, conflict2]
-
- error = Gem::ImpossibleDependenciesError.new request, conflicts
-
- expected = <<-EXPECTED
-rye-0.9.8 requires net-ssh (>= 2.0.13) but it conflicted:
- Activated net-ssh-2.6.5
- which does not match conflicting dependency (~> 2.2.2)
-
- Conflicting dependency chains:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.6.5 activated
-
- versus:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.6.5 activated, depends on
- net-ssh (~> 2.2.2)
-
- Activated net-ssh-2.2.2
- which does not match conflicting dependency (>= 2.6.5)
-
- Conflicting dependency chains:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.2.2 activated
-
- versus:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.2.2 activated, depends on
- net-ssh (>= 2.6.5)
-
- EXPECTED
-
- assert_equal expected, error.message
- end
-end
diff --git a/test/rubygems/test_gem_install_update_options.rb b/test/rubygems/test_gem_install_update_options.rb
index 8fd5d9c543..1e451dcb05 100644
--- a/test/rubygems/test_gem_install_update_options.rb
+++ b/test/rubygems/test_gem_install_update_options.rb
@@ -202,4 +202,16 @@ class TestGemInstallUpdateOptions < Gem::InstallerTestCase
assert_equal true, @cmd.options[:minimal_deps]
end
+
+ def test_build_jobs_short_version
+ @cmd.handle_options %w[-j 4]
+
+ assert_equal 4, @cmd.options[:build_jobs]
+ end
+
+ def test_build_jobs_long_version
+ @cmd.handle_options %w[--build-jobs 4]
+
+ assert_equal 4, @cmd.options[:build_jobs]
+ end
end
diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb
index 83e43c135f..bf7a4a8dfc 100644
--- a/test/rubygems/test_gem_installer.rb
+++ b/test/rubygems/test_gem_installer.rb
@@ -24,36 +24,35 @@ class TestGemInstaller < Gem::InstallerTestCase
util_make_exec @spec, ""
- expected = <<-EOF
-#!#{Gem.ruby}
-#
-# This file was generated by RubyGems.
-#
-# The application 'a' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-require 'rubygems'
-
-Gem.use_gemdeps
-
-version = \">= 0.a\"
-
-str = ARGV.first
-if str
- str = str.b[/\\A_(.*)_\\z/, 1]
- if str and Gem::Version.correct?(str)
- version = str
- ARGV.shift
- end
-end
+ expected = <<~EOF
+ #!#{Gem.ruby}
+ #
+ # This file was generated by RubyGems.
+ #
+ # The application 'a' is installed as part of a gem, and
+ # this file is here to facilitate running it.
+ #
+
+ require 'rubygems'
+
+ Gem.use_gemdeps
+
+ version = \">= 0.a\"
+
+ str = ARGV.first
+ if str
+ str = str.b[/\\A_(.*)_\\z/, 1]
+ if str and Gem::Version.correct?(str)
+ version = str
+ ARGV.shift
+ end
+ end
-if Gem.respond_to?(:activate_bin_path)
-load Gem.activate_bin_path('a', 'executable', version)
-else
-gem "a", version
-load Gem.bin_path("a", "executable", version)
-end
+ if Gem.respond_to?(:activate_and_load_bin_path)
+ Gem.activate_and_load_bin_path('a', 'executable', version)
+ else
+ load Gem.activate_bin_path('a', 'executable', version)
+ end
EOF
wrapper = installer.app_script_text "executable"
@@ -121,12 +120,12 @@ end
end
File.open File.join(util_inst_bindir, "executable"), "w" do |io|
- io.write <<-EXEC
-#!/usr/local/bin/ruby
-#
-# This file was generated by RubyGems
+ io.write <<~EXEC
+ #!/usr/local/bin/ruby
+ #
+ # This file was generated by RubyGems
-gem 'other', version
+ gem 'other', version
EXEC
end
@@ -690,8 +689,11 @@ gem 'other', version
def test_generate_bin_symlink_win32
old_win_platform = Gem.win_platform?
- Gem.win_platform = true
old_alt_separator = File::ALT_SEPARATOR
+
+ omit "JRuby on Windows still creates the symlink so the wrapper branch is not exercised" if Gem.win_platform? && Gem.java_platform?
+
+ Gem.win_platform = true
File.__send__(:remove_const, :ALT_SEPARATOR)
File.const_set(:ALT_SEPARATOR, "\\")
@@ -744,6 +746,8 @@ gem 'other', version
end
def test_generate_bin_with_dangling_symlink
+ omit "JRuby on Windows still creates the symlink so the wrapper branch is not exercised" if Gem.win_platform? && Gem.java_platform?
+
gem_with_dangling_symlink = File.expand_path("packages/ascii_binder-0.1.10.1.gem", __dir__)
installer = Gem::Installer.at(
@@ -760,8 +764,12 @@ gem 'other', version
errors = @ui.error.split("\n")
assert_equal "WARNING: ascii_binder-0.1.10.1 ships with a dangling symlink named bin/ascii_binder pointing to missing bin/asciibinder file. Ignoring", errors.shift
- assert_empty errors
-
+ if symlink_supported?
+ assert_empty errors
+ else
+ assert_match(/Unable to use symlinks, installing wrapper/i,
+ errors.to_s)
+ end
assert_empty @ui.output
end
@@ -869,11 +877,11 @@ gem 'other', version
spec_version = spec.version
plugin_path = File.join("lib", "rubygems_plugin.rb")
write_file File.join(@tempdir, plugin_path) do |io|
- io.write <<-PLUGIN
-#{self.class}.plugin_loaded = true
-Gem.post_install do
- #{self.class}.post_install_is_called = true
-end
+ io.write <<~PLUGIN
+ #{self.class}.plugin_loaded = true
+ Gem.post_install do
+ #{self.class}.post_install_is_called = true
+ end
PLUGIN
end
spec.files += [plugin_path]
@@ -1247,7 +1255,7 @@ end
end
assert_raise(Gem::Ext::BuildError) do
- installer.install
+ build_rake_in { installer.install }
end
assert_path_not_exist(File.join(installer.bin_dir, "executable.lock"))
@@ -1478,12 +1486,7 @@ end
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
- create_makefile("#{@spec.name}")
- RUBY
- end
+ write_dummy_extconf @spec.name
@spec.files += %w[extconf.rb]
@@ -1503,12 +1506,7 @@ end
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
- create_makefile("#{@spec.name}")
- RUBY
- end
+ write_dummy_extconf @spec.name
@spec.files += %w[extconf.rb]
@@ -1539,12 +1537,7 @@ end
def test_install_user_extension_dir
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
- create_makefile("#{@spec.name}")
- RUBY
- end
+ write_dummy_extconf @spec.name
@spec.files += %w[extconf.rb]
@@ -1571,22 +1564,20 @@ end
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
+ write_dummy_extconf @spec.name do |io|
+ io.write <<~RUBY
CONFIG['CC'] = '$(TOUCH) $@ ||'
CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
$ruby = '#{Gem.ruby}'
- create_makefile("#{@spec.name}")
RUBY
end
write_file File.join(@tempdir, "depend")
write_file File.join(@tempdir, "a.c") do |io|
- io.write <<-C
+ io.write <<~C
#include <ruby.h>
void Init_a() { }
C
@@ -1618,17 +1609,12 @@ end
@spec = setup_base_spec
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
- create_makefile("#{@spec.name}")
- RUBY
- end
+ write_dummy_extconf @spec.name
rb = File.join("lib", "#{@spec.name}.rb")
@spec.files += [rb]
write_file File.join(@tempdir, rb) do |io|
- io.write <<-RUBY
+ io.write <<~RUBY
# #{@spec.name}.rb
RUBY
end
@@ -1637,7 +1623,7 @@ end
rb2 = File.join("lib", @spec.name, "#{@spec.name}.rb")
@spec.files << rb2
write_file File.join(@tempdir, rb2) do |io|
- io.write <<-RUBY
+ io.write <<~RUBY
# #{@spec.name}/#{@spec.name}.rb
RUBY
end
@@ -1663,15 +1649,13 @@ end
@spec.extensions << "extconf.rb"
- write_file File.join(@tempdir, "extconf.rb") do |io|
- io.write <<-RUBY
- require "mkmf"
+ write_dummy_extconf @spec.name do |io|
+ io.write <<~RUBY
CONFIG['CC'] = '$(TOUCH) $@ ||'
CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
$ruby = '#{Gem.ruby}'
- create_makefile("#{@spec.name}")
RUBY
end
@@ -1698,13 +1682,13 @@ end
@spec.require_paths = ["."]
@spec.extensions << "extconf.rb"
- File.write File.join(@tempdir, "extconf.rb"), <<-RUBY
- require "mkmf"
- CONFIG['CC'] = '$(TOUCH) $@ ||'
- CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
- $ruby = '#{Gem.ruby}'
- create_makefile("#{@spec.name}")
- RUBY
+ write_dummy_extconf @spec.name do |io|
+ io.write <<~RUBY
+ CONFIG['CC'] = '$(TOUCH) $@ ||'
+ CONFIG['LDSHARED'] = '$(TOUCH) $@ ||'
+ $ruby = '#{Gem.ruby}'
+ RUBY
+ end
# empty depend file for no auto dependencies
@spec.files += %W[depend #{@spec.name}.c].each do |file|
@@ -1938,10 +1922,10 @@ end
end
def test_pre_install_checks_malicious_platform_before_eval
- gem_with_ill_formated_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__)
+ gem_with_ill_formatted_platform = File.expand_path("packages/ill-formatted-platform-1.0.0.10.gem", __dir__)
installer = Gem::Installer.at(
- gem_with_ill_formated_platform,
+ gem_with_ill_formatted_platform,
install_dir: @gemhome,
user_install: false,
force: true
@@ -2304,19 +2288,6 @@ end
assert_equal "#!1 #{bin_env} 2 #{Gem.ruby} -ws 3 executable", shebang
end
- def test_unpack
- installer = util_setup_installer
-
- dest = File.join @gemhome, "gems", @spec.full_name
-
- Gem::Deprecate.skip_during do
- installer.unpack dest
- end
-
- assert_path_exist File.join dest, "lib", "code.rb"
- assert_path_exist File.join dest, "bin", "executable"
- end
-
def test_write_build_info_file
installer = setup_base_installer
@@ -2423,25 +2394,31 @@ end
installer = Gem::Installer.for_spec @spec
installer.gem_home = @gemhome
- File.singleton_class.class_eval do
- alias_method :original_binwrite, :binwrite
-
- def binwrite(path, data)
+ assert_raise(Errno::ENOSPC) do
+ Gem::AtomicFileWriter.open(@spec.spec_file) do
raise Errno::ENOSPC
end
end
- assert_raise Errno::ENOSPC do
- installer.write_spec
- end
-
assert_path_not_exist @spec.spec_file
- ensure
- File.singleton_class.class_eval do
- remove_method :binwrite
- alias_method :binwrite, :original_binwrite
- remove_method :original_binwrite
- end
+ end
+
+ def test_write_default_spec
+ @spec = setup_base_spec
+ @spec.files = %w[a.rb b.rb c.rb]
+
+ installer = Gem::Installer.for_spec @spec
+ installer.gem_home = @gemhome
+
+ installer.write_default_spec
+
+ assert_path_exist installer.default_spec_file
+
+ loaded = Gem::Specification.load installer.default_spec_file
+
+ assert_equal @spec.files, loaded.files
+ assert_equal @spec.name, loaded.name
+ assert_equal @spec.version, loaded.version
end
def test_dir
@@ -2450,137 +2427,154 @@ end
assert_match %r{/gemhome/gems/a-2$}, installer.dir
end
- def test_default_gem_loaded_from
- spec = util_spec "a"
- installer = Gem::Installer.for_spec spec, install_as_default: true
- installer.install
- assert_predicate spec, :default_gem?
+ def test_package_attribute
+ gem = quick_gem "c" do |spec|
+ util_make_exec spec, "#!/usr/bin/ruby", "exe"
+ end
+
+ installer = util_installer(gem, @gemhome)
+ assert_respond_to(installer, :package)
+ assert_kind_of(Gem::Package, installer.package)
end
- def test_default_gem_without_wrappers
- installer = setup_base_installer
+ def test_gem_attribute
+ gem = quick_gem "c" do |spec|
+ util_make_exec spec, "#!/usr/bin/ruby", "exe"
+ end
- FileUtils.rm_rf File.join(Gem.default_dir, "specifications")
+ installer = util_installer(gem, @gemhome)
+ assert_respond_to(installer, :gem)
+ assert_kind_of(String, installer.gem)
+ end
- installer.wrappers = false
- installer.options[:install_as_default] = true
- installer.gem_dir = @spec.gem_dir
+ def test_install_no_build_extension
+ installer = util_setup_installer
+
+ gemdir = File.join @gemhome, "gems", @spec.full_name
+
+ installer.options[:build_extension] = false
use_ui @ui do
installer.install
end
- assert_directory_exists File.join(@spec.gem_dir, "bin")
- installed_exec = File.join @spec.gem_dir, "bin", "executable"
- assert_path_exist installed_exec
-
- assert_directory_exists File.join(Gem.default_dir, "specifications")
- assert_directory_exists File.join(Gem.default_dir, "specifications", "default")
-
- default_spec = eval File.read File.join(Gem.default_dir, "specifications", "default", "a-2.gemspec")
- assert_equal Gem::Version.new("2"), default_spec.version
- assert_equal ["bin/executable"], default_spec.files
+ assert_path_exist gemdir
+ assert_path_not_exist File.join(@spec.extension_dir, "gem.build_complete")
+ assert_match "contains native extensions that were not built", @ui.error
+ assert_match "gem pristine #{@spec.name} --extensions", @ui.error
+ end
- assert_directory_exists util_inst_bindir
+ def test_install_no_build_extension_without_extensions
+ spec = quick_gem "b", 2
- installed_exec = File.join util_inst_bindir, "executable"
- assert_path_exist installed_exec
+ util_build_gem spec
- wrapper = File.read installed_exec
+ installer = util_installer spec, @gemhome
+ installer.options[:build_extension] = false
- if symlink_supported?
- refute_match(/generated by RubyGems/, wrapper)
- else # when symlink not supported, it warns and fallbacks back to installing wrapper
- assert_match(/Unable to use symlinks, installing wrapper/, @ui.error)
- assert_match(/generated by RubyGems/, wrapper)
+ use_ui @ui do
+ installer.install
end
- end
- def test_default_gem_with_wrappers
- installer = setup_base_installer
+ refute_match "contains native extensions", @ui.error
+ end
- installer.wrappers = true
- installer.options[:install_as_default] = true
- installer.gem_dir = @spec.gem_dir
+ def test_install_no_install_plugin
+ installer = util_setup_installer do |spec|
+ write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
+ io.write "# do nothing"
+ end
- use_ui @ui do
- installer.install
+ spec.files += %w[lib/rubygems_plugin.rb]
end
- assert_directory_exists util_inst_bindir
+ installer.options[:install_plugin] = false
- installed_exec = File.join util_inst_bindir, "executable"
- assert_path_exist installed_exec
+ build_rake_in do
+ use_ui @ui do
+ installer.install
+ end
+ end
- wrapper = File.read installed_exec
- assert_match(/generated by RubyGems/, wrapper)
+ plugin_path = File.join Gem.plugindir, "a_plugin.rb"
+ refute File.exist?(plugin_path), "plugin must not be written when --no-install-plugin"
+ assert_match "contains plugins that were not installed", @ui.error
+ assert_match "gem pristine #{@spec.name} --only-plugins", @ui.error
end
- def test_default_gem_with_exe_as_bindir
- @spec = quick_gem "c" do |spec|
- util_make_exec spec, "#!/usr/bin/ruby", "exe"
+ def test_install_no_install_plugin_skips_load_plugin
+ installer = util_setup_installer do |spec|
+ write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
+ io.write "$no_install_plugin_test_loaded = true"
+ end
+
+ spec.files += %w[lib/rubygems_plugin.rb]
end
- util_build_gem @spec
+ # Simulate a pre-existing plugin wrapper from a previous install
+ FileUtils.mkdir_p Gem.plugindir
+ plugin_path = File.join Gem.plugindir, "a_plugin.rb"
+ File.write(plugin_path, "require_relative '../../gems/#{@spec.full_name}/lib/rubygems_plugin'")
- @spec.cache_file
+ installer.options[:install_plugin] = false
- installer = util_installer @spec, @gemhome
+ build_rake_in do
+ use_ui @ui do
+ installer.install
+ end
+ end
- installer.options[:install_as_default] = true
- installer.gem_dir = @spec.gem_dir
+ refute defined?($no_install_plugin_test_loaded) && $no_install_plugin_test_loaded,
+ "plugin must not be loaded when --no-install-plugin"
+ ensure
+ $no_install_plugin_test_loaded = nil
+ end
- use_ui @ui do
- installer.install
- end
+ def test_install_no_install_plugin_without_plugins
+ installer = util_setup_installer
- assert_directory_exists File.join(@spec.gem_dir, "exe")
- installed_exec = File.join @spec.gem_dir, "exe", "executable"
- assert_path_exist installed_exec
+ installer.options[:install_plugin] = false
- assert_directory_exists File.join(Gem.default_dir, "specifications")
- assert_directory_exists File.join(Gem.default_dir, "specifications", "default")
+ build_rake_in do
+ use_ui @ui do
+ installer.install
+ end
+ end
- default_spec = eval File.read File.join(Gem.default_dir, "specifications", "default", "c-2.gemspec")
- assert_equal Gem::Version.new("2"), default_spec.version
- assert_equal ["exe/executable"], default_spec.files
+ refute_match "contains plugins", @ui.error
end
- def test_default_gem_to_specific_install_dir
- @gem = setup_base_gem
- installer = util_installer @spec, "#{@gemhome}2"
- installer.options[:install_as_default] = true
+ def test_install_no_install_plugin_removes_stale_wrappers
+ # First install a version with a plugin
+ installer = util_setup_installer do |spec|
+ write_file File.join(@tempdir, "lib", "rubygems_plugin.rb") do |io|
+ io.write "# plugin code"
+ end
- use_ui @ui do
- installer.install
+ spec.files += %w[lib/rubygems_plugin.rb]
end
- assert_directory_exists File.join("#{@gemhome}2", "specifications")
- assert_directory_exists File.join("#{@gemhome}2", "specifications", "default")
+ build_rake_in do
+ use_ui @ui do
+ installer.install
+ end
+ end
- default_spec = eval File.read File.join("#{@gemhome}2", "specifications", "default", "a-2.gemspec")
- assert_equal Gem::Version.new("2"), default_spec.version
- assert_equal ["bin/executable"], default_spec.files
- end
+ plugin_path = File.join Gem.plugindir, "a_plugin.rb"
+ assert File.exist?(plugin_path), "plugin wrapper should exist after first install"
- def test_package_attribute
- gem = quick_gem "c" do |spec|
- util_make_exec spec, "#!/usr/bin/ruby", "exe"
- end
+ # Now install a new version without plugins, using --no-install-plugin
+ spec2 = quick_gem "a", 3
+ util_build_gem spec2
- installer = util_installer(gem, @gemhome)
- assert_respond_to(installer, :package)
- assert_kind_of(Gem::Package, installer.package)
- end
+ installer2 = util_installer spec2, @gemhome
+ installer2.options[:install_plugin] = false
- def test_gem_attribute
- gem = quick_gem "c" do |spec|
- util_make_exec spec, "#!/usr/bin/ruby", "exe"
+ use_ui @ui do
+ installer2.install
end
- installer = util_installer(gem, @gemhome)
- assert_respond_to(installer, :gem)
- assert_kind_of(String, installer.gem)
+ refute File.exist?(plugin_path), "stale plugin wrapper must be removed"
end
private
diff --git a/test/rubygems/test_gem_name_tuple.rb b/test/rubygems/test_gem_name_tuple.rb
index bdb8181ce8..4876737c83 100644
--- a/test/rubygems/test_gem_name_tuple.rb
+++ b/test/rubygems/test_gem_name_tuple.rb
@@ -57,4 +57,41 @@ class TestGemNameTuple < Gem::TestCase
assert_equal 1, a_p.<=>(a)
end
+
+ def test_deconstruct
+ name_tuple = Gem::NameTuple.new "rails", Gem::Version.new("7.0.0"), "ruby"
+ assert_equal ["rails", Gem::Version.new("7.0.0"), "ruby"], name_tuple.deconstruct
+ end
+
+ def test_deconstruct_keys
+ name_tuple = Gem::NameTuple.new "rails", Gem::Version.new("7.0.0"), "x86_64-linux"
+ keys = name_tuple.deconstruct_keys(nil)
+ assert_equal "rails", keys[:name]
+ assert_equal Gem::Version.new("7.0.0"), keys[:version]
+ assert_equal "x86_64-linux", keys[:platform]
+ end
+
+ def test_pattern_matching_array
+ name_tuple = Gem::NameTuple.new "rails", Gem::Version.new("7.0.0"), "ruby"
+ result =
+ case name_tuple
+ in [name, version, "ruby"]
+ "#{name}-#{version}"
+ else
+ "no match"
+ end
+ assert_equal "rails-7.0.0", result
+ end
+
+ def test_pattern_matching_hash
+ name_tuple = Gem::NameTuple.new "rails", Gem::Version.new("7.0.0"), "ruby"
+ result =
+ case name_tuple
+ in name: "rails", version:, platform: "ruby"
+ version.to_s
+ else
+ "no match"
+ end
+ assert_equal "7.0.0", result
+ end
end
diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb
index 8a9cc85580..0014c20737 100644
--- a/test/rubygems/test_gem_package.rb
+++ b/test/rubygems/test_gem_package.rb
@@ -175,6 +175,9 @@ class TestGemPackage < Gem::Package::TarTestCase
end
def test_add_files_symlink
+ unless symlink_supported?
+ omit("symlink - developer mode must be enabled on Windows")
+ end
spec = Gem::Specification.new
spec.files = %w[lib/code.rb lib/code_sym.rb lib/code_sym2.rb]
@@ -185,16 +188,8 @@ class TestGemPackage < Gem::Package::TarTestCase
end
# NOTE: 'code.rb' is correct, because it's relative to lib/code_sym.rb
- begin
- File.symlink("code.rb", "lib/code_sym.rb")
- File.symlink("../lib/code.rb", "lib/code_sym2.rb")
- rescue Errno::EACCES => e
- if Gem.win_platform?
- pend "symlink - must be admin with no UAC on Windows"
- else
- raise e
- end
- end
+ File.symlink("code.rb", "lib/code_sym.rb")
+ File.symlink("../lib/code.rb", "lib/code_sym2.rb")
package = Gem::Package.new "bogus.gem"
package.spec = spec
@@ -506,7 +501,7 @@ class TestGemPackage < Gem::Package::TarTestCase
extracted = File.join @destination, "lib/code.rb"
assert_path_exist extracted
- mask = 0o100666 & (~File.umask)
+ mask = 0o100666 & ~File.umask
assert_equal mask.to_s(8), File.stat(extracted).mode.to_s(8) unless
Gem.win_platform?
@@ -583,25 +578,71 @@ class TestGemPackage < Gem::Package::TarTestCase
tar.add_symlink "lib/foo.rb", "../relative.rb", 0o644
end
- begin
- package.extract_tar_gz tgz_io, @destination
- rescue Errno::EACCES => e
- if Gem.win_platform?
- pend "symlink - must be admin with no UAC on Windows"
- else
- raise e
- end
- end
+ package.extract_tar_gz tgz_io, @destination
extracted = File.join @destination, "lib/foo.rb"
assert_path_exist extracted
- assert_equal "../relative.rb",
- File.readlink(extracted)
+ if symlink_supported?
+ assert_equal "../relative.rb",
+ File.readlink(extracted)
+ end
assert_equal "hi",
+ File.read(extracted),
+ "should read file content either by following symlink or on Windows by reading copy"
+ end
+
+ def test_extract_tar_gz_symlink_directory
+ package = Gem::Package.new @gem
+ package.verify
+
+ tgz_io = util_tar_gz do |tar|
+ tar.add_symlink "link", "lib/orig", 0o644
+ tar.mkdir "lib", 0o755
+ tar.mkdir "lib/orig", 0o755
+ tar.add_file "lib/orig/file.rb", 0o644 do |io|
+ io.write "ok"
+ end
+ end
+
+ package.extract_tar_gz tgz_io, @destination
+ extracted = File.join @destination, "link/file.rb"
+ assert_path_exist extracted
+ if symlink_supported?
+ assert_equal "lib/orig",
+ File.readlink(File.dirname(extracted))
+ end
+ assert_equal "ok",
File.read(extracted)
end
+ def test_extract_tar_gz_rejects_preexisting_symlink_escape
+ omit "Symlinks not supported or not enabled" unless symlink_supported?
+
+ package = Gem::Package.new @gem
+
+ tgz_io = util_tar_gz do |tar|
+ tar.add_file "lib/owned.txt", 0o644 do |io|
+ io.write "poc-content"
+ end
+ end
+
+ escape_dir = File.join(@tempdir, "escape")
+ FileUtils.mkdir_p escape_dir
+
+ FileUtils.rm_rf File.join(@destination, "lib")
+ File.symlink escape_dir, File.join(@destination, "lib")
+
+ escaped = File.join(escape_dir, "owned.txt")
+
+ assert_raise Gem::Package::PathError do
+ package.extract_tar_gz tgz_io, @destination
+ end
+
+ refute File.exist?(escaped), "must not write outside extraction root via symlink"
+ end
+
def test_extract_symlink_into_symlink_dir
+ omit "Symlinks not supported or not enabled" unless symlink_supported?
package = Gem::Package.new @gem
tgz_io = util_tar_gz do |tar|
tar.mkdir "lib", 0o755
@@ -665,14 +706,10 @@ class TestGemPackage < Gem::Package::TarTestCase
destination_subdir = File.join @destination, "subdir"
FileUtils.mkdir_p destination_subdir
- expected_exceptions = Gem.win_platform? ? [Gem::Package::SymlinkError, Errno::EACCES] : [Gem::Package::SymlinkError]
-
- e = assert_raise(*expected_exceptions) do
+ e = assert_raise(Gem::Package::SymlinkError) do
package.extract_tar_gz tgz_io, destination_subdir
end
- pend "symlink - must be admin with no UAC on Windows" if Errno::EACCES === e
-
assert_equal("installing symlink 'lib/link' pointing to parent path #{@destination} of " \
"#{destination_subdir} is not allowed", e.message)
@@ -700,14 +737,10 @@ class TestGemPackage < Gem::Package::TarTestCase
tar.add_symlink "link/dir", ".", 16_877
end
- expected_exceptions = Gem.win_platform? ? [Gem::Package::SymlinkError, Errno::EACCES] : [Gem::Package::SymlinkError]
-
- e = assert_raise(*expected_exceptions) do
+ e = assert_raise(Gem::Package::SymlinkError) do
package.extract_tar_gz tgz_io, destination_subdir
end
- pend "symlink - must be admin with no UAC on Windows" if Errno::EACCES === e
-
assert_equal("installing symlink 'link' pointing to parent path #{destination_user_dir} of " \
"#{destination_subdir} is not allowed", e.message)
@@ -858,7 +891,7 @@ class TestGemPackage < Gem::Package::TarTestCase
"#{@destination} is not allowed", e.message)
end
- def test_load_spec
+ def test_load_spec_from_metadata
entry = StringIO.new Gem::Util.gzip @spec.to_yaml
def entry.full_name
"metadata.gz"
@@ -866,7 +899,7 @@ class TestGemPackage < Gem::Package::TarTestCase
package = Gem::Package.new "nonexistent.gem"
- spec = package.load_spec entry
+ spec = package.load_spec_from_metadata entry
assert_equal @spec, spec
end
@@ -909,7 +942,11 @@ class TestGemPackage < Gem::Package::TarTestCase
}
tar.add_file "checksums.yaml.gz", 0o444 do |io|
Zlib::GzipWriter.wrap io do |gz_io|
- gz_io.write Psych.dump bogus_checksums
+ if Gem.use_psych?
+ gz_io.write Psych.dump(bogus_checksums)
+ else
+ gz_io.write Gem::YAMLSerializer.dump(bogus_checksums)
+ end
end
end
end
@@ -955,7 +992,11 @@ class TestGemPackage < Gem::Package::TarTestCase
tar.add_file "checksums.yaml.gz", 0o444 do |io|
Zlib::GzipWriter.wrap io do |gz_io|
- gz_io.write Psych.dump checksums
+ if Gem.use_psych?
+ gz_io.write Psych.dump(checksums)
+ else
+ gz_io.write Gem::YAMLSerializer.dump(checksums)
+ end
end
end
@@ -1247,71 +1288,25 @@ class TestGemPackage < Gem::Package::TarTestCase
# end #verify tests
- def test_verify_entry
- entry = Object.new
- def entry.full_name
- raise ArgumentError, "whatever"
- end
-
- package = Gem::Package.new @gem
-
- _, err = use_ui @ui do
- e = nil
-
- out_err = capture_output do
- e = assert_raise ArgumentError do
- package.verify_entry entry
+ def test_missing_metadata
+ invalid_metadata = ["metadataxgz", "foobar\nmetadata", "metadata\nfoobar"]
+ invalid_metadata.each do |fname|
+ tar = StringIO.new
+
+ Gem::Package::TarWriter.new(tar) do |gem_tar|
+ gem_tar.add_file fname, 0o444 do |io|
+ gz_io = Zlib::GzipWriter.new io, Zlib::BEST_COMPRESSION
+ gz_io.write "bad metadata"
+ gz_io.close
end
end
- assert_equal "whatever", e.message
- assert_equal "full_name", e.backtrace_locations.first.label
-
- out_err
- end
-
- assert_equal "Exception while verifying #{@gem}\n", err
-
- valid_metadata = ["metadata", "metadata.gz"]
- valid_metadata.each do |vm|
- $spec_loaded = false
- $good_name = vm
-
- entry = Object.new
- def entry.full_name
- $good_name
- end
-
- package = Gem::Package.new(@gem)
- package.instance_variable_set(:@files, [])
- def package.load_spec(entry)
- $spec_loaded = true
- end
-
- package.verify_entry(entry)
+ tar.rewind
- assert $spec_loaded
- end
-
- invalid_metadata = ["metadataxgz", "foobar\nmetadata", "metadata\nfoobar"]
- invalid_metadata.each do |vm|
- $spec_loaded = false
- $bad_name = vm
-
- entry = Object.new
- def entry.full_name
- $bad_name
- end
-
- package = Gem::Package.new(@gem)
- package.instance_variable_set(:@files, [])
- def package.load_spec(entry)
- $spec_loaded = true
+ package = Gem::Package.new(Gem::Package::IOSource.new(tar))
+ assert_raise Gem::Package::FormatError do
+ package.verify
end
-
- package.verify_entry(entry)
-
- refute $spec_loaded
end
end
diff --git a/test/rubygems/test_gem_package_old.rb b/test/rubygems/test_gem_package_old.rb
index 7582dbedd4..e532fa25e1 100644
--- a/test/rubygems/test_gem_package_old.rb
+++ b/test/rubygems/test_gem_package_old.rb
@@ -39,7 +39,7 @@ unless Gem.java_platform? # jruby can't require the simple_gem file
extracted = File.join @destination, "lib/foo.rb"
assert_path_exist extracted
- mask = 0o100644 & (~File.umask)
+ mask = 0o100644 & ~File.umask
assert_equal mask, File.stat(extracted).mode unless Gem.win_platform?
end
diff --git a/test/rubygems/test_gem_package_tar_header_ractor.rb b/test/rubygems/test_gem_package_tar_header_ractor.rb
new file mode 100644
index 0000000000..5714064805
--- /dev/null
+++ b/test/rubygems/test_gem_package_tar_header_ractor.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require_relative "package/tar_test_case"
+
+unless Gem::Package::TarTestCase.method_defined?(:assert_ractor)
+ require "core_assertions"
+ Gem::Package::TarTestCase.include Test::Unit::CoreAssertions
+end
+
+class TestGemPackageTarHeaderRactor < Gem::Package::TarTestCase
+ SETUP = <<~RUBY
+ header = {
+ name: "x",
+ mode: 0o644,
+ uid: 1000,
+ gid: 10_000,
+ size: 100,
+ mtime: 12_345,
+ typeflag: "0",
+ linkname: "link",
+ uname: "user",
+ gname: "group",
+ devmajor: 1,
+ devminor: 2,
+ prefix: "y",
+ }
+
+ tar_header = Gem::Package::TarHeader.new header
+ # Move this require to arguments of assert_ractor after Ruby 4.0 or updating core_assertions.rb at Ruby 3.4.
+ require "stringio"
+ # Remove this after Ruby 4.0 or updating core_assertions.rb at Ruby 3.4.
+ class Ractor; alias value take unless method_defined?(:value); end
+ RUBY
+
+ def test_decode_in_ractor
+ assert_ractor(SETUP + <<~RUBY, require: "rubygems/package", require_relative: "package/tar_test_case")
+ include Gem::Package::TarTestMethods
+
+ new_header = Ractor.new(tar_header.to_s) do |str|
+ Gem::Package::TarHeader.from StringIO.new str
+ end.value
+
+ assert_headers_equal tar_header, new_header
+ RUBY
+ end
+
+ def test_encode_in_ractor
+ assert_ractor(SETUP + <<~RUBY, require: "rubygems/package", require_relative: "package/tar_test_case")
+ include Gem::Package::TarTestMethods
+
+ header_bytes = tar_header.to_s
+
+ new_header_bytes = Ractor.new(header_bytes) do |str|
+ new_header = Gem::Package::TarHeader.from StringIO.new str
+ new_header.to_s
+ end.value
+
+ assert_headers_equal header_bytes, new_header_bytes
+ RUBY
+ end
+end unless RUBY_PLATFORM.match?(/mingw|mswin/)
diff --git a/test/rubygems/test_gem_package_tar_writer.rb b/test/rubygems/test_gem_package_tar_writer.rb
index 751ceaca81..cb9e0d26fa 100644
--- a/test/rubygems/test_gem_package_tar_writer.rb
+++ b/test/rubygems/test_gem_package_tar_writer.rb
@@ -33,7 +33,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
f.write "a" * 10
end
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -50,11 +50,24 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
end
end
+ def test_add_file_with_mtime
+ Time.stub :now, Time.at(1_458_518_157) do
+ mtime = Time.now
+
+ @tar_writer.add_file "x", 0o644, mtime do |f|
+ f.write "a" * 10
+ end
+
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, mtime),
+ @io.string[0, 512])
+ end
+ end
+
def test_add_symlink
Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.add_symlink "x", "y", 0o644
- assert_headers_equal(tar_symlink_header("x", "", 0o644, Time.now, "y"),
+ assert_headers_equal(tar_symlink_header("x", "", 0o644, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc, "y"),
@io.string[0, 512])
end
assert_equal 512, @io.pos
@@ -86,7 +99,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
"e1cf14b0",
digests["SHA512"].hexdigest
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -109,7 +122,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
"e1cf14b0",
digests["SHA512"].hexdigest
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -126,7 +139,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10
end
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -137,7 +150,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
signature = signer.sign digest.digest
assert_headers_equal(tar_file_header("x.sig", "", 0o444, signature.length,
- Time.now),
+ Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[1024, 512])
assert_equal "#{signature}#{"\0" * (512 - signature.length)}",
@io.string[1536, 512]
@@ -154,7 +167,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10
end
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
end
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -168,7 +181,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
io.write "a" * 10
end
- assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.now),
+ assert_headers_equal(tar_file_header("x", "", 0o644, 10, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512])
assert_equal "aaaaaaaaaa#{"\0" * 502}", @io.string[512, 512]
@@ -192,7 +205,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.add_file_simple "x", 0, 100
- assert_headers_equal tar_file_header("x", "", 0, 100, Time.now),
+ assert_headers_equal tar_file_header("x", "", 0, 100, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]
end
@@ -250,7 +263,7 @@ class TestGemPackageTarWriter < Gem::Package::TarTestCase
Time.stub :now, Time.at(1_458_518_157) do
@tar_writer.mkdir "foo", 0o644
- assert_headers_equal tar_dir_header("foo", "", 0o644, Time.now),
+ assert_headers_equal tar_dir_header("foo", "", 0o644, Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc),
@io.string[0, 512]
assert_equal 512, @io.pos
diff --git a/test/rubygems/test_gem_path_support.rb b/test/rubygems/test_gem_path_support.rb
index 8720bcf858..c5181496c0 100644
--- a/test/rubygems/test_gem_path_support.rb
+++ b/test/rubygems/test_gem_path_support.rb
@@ -121,14 +121,12 @@ class TestGemPathSupport < Gem::TestCase
end
def test_gem_paths_do_not_contain_symlinks
+ pend "symlinks not supported" unless symlink_supported?
+
dir = "#{@tempdir}/realgemdir"
symlink = "#{@tempdir}/symdir"
Dir.mkdir dir
- begin
- File.symlink(dir, symlink)
- rescue NotImplementedError, SystemCallError
- pend "symlinks not supported"
- end
+ File.symlink(dir, symlink)
not_existing = "#{@tempdir}/does_not_exist"
path = "#{symlink}#{File::PATH_SEPARATOR}#{not_existing}"
diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb
index 070c8007bc..c1ff36772b 100644
--- a/test/rubygems/test_gem_platform.rb
+++ b/test/rubygems/test_gem_platform.rb
@@ -11,15 +11,6 @@ class TestGemPlatform < Gem::TestCase
assert_equal Gem::Platform.new(%w[x86 darwin 8]), Gem::Platform.local
end
- def test_self_match
- Gem::Deprecate.skip_during do
- assert Gem::Platform.match(nil), "nil == ruby"
- assert Gem::Platform.match(Gem::Platform.local), "exact match"
- assert Gem::Platform.match(Gem::Platform.local.to_s), "=~ match"
- assert Gem::Platform.match(Gem::Platform::RUBY), "ruby"
- end
- end
-
def test_self_match_gem?
assert Gem::Platform.match_gem?(nil, "json"), "nil == ruby"
assert Gem::Platform.match_gem?(Gem::Platform.local, "json"), "exact match"
@@ -148,12 +139,29 @@ class TestGemPlatform < Gem::TestCase
"wasm32-wasi" => ["wasm32", "wasi", nil],
"wasm32-wasip1" => ["wasm32", "wasi", nil],
"wasm32-wasip2" => ["wasm32", "wasi", nil],
+
+ "darwin-java-java" => ["darwin", "java", nil],
+ "linux-linux-linux" => ["linux", "linux", "linux"],
+ "linux-linux-linux1.0" => ["linux", "linux", "linux1"],
+ "x86x86-1x86x86x86x861linuxx86x86" => ["x86x86", "linux", "x86x86"],
+ "freebsd0" => [nil, "freebsd", "0"],
+ "darwin0" => [nil, "darwin", "0"],
+ "darwin0---" => [nil, "darwin", "0"],
+ "x86-linux-x8611.0l" => ["x86", "linux", "x8611"],
+ "0-x86linuxx86---" => ["0", "linux", "x86"],
+ "x86_64-macruby-x86" => ["x86_64", "macruby", nil],
+ "x86_64-dotnetx86" => ["x86_64", "dotnet", nil],
+ "x86_64-dalvik0" => ["x86_64", "dalvik", "0"],
+ "x86_64-dotnet1." => ["x86_64", "dotnet", "1"],
+
+ "--" => [nil, "unknown", nil],
}
test_cases.each do |arch, expected|
platform = Gem::Platform.new arch
assert_equal expected, platform.to_a, arch.inspect
- assert_equal expected, Gem::Platform.new(platform.to_s).to_a, arch.inspect
+ platform2 = Gem::Platform.new platform.to_s
+ assert_equal expected, platform2.to_a, "#{arch.inspect} => #{platform2.inspect}"
end
end
@@ -246,19 +254,19 @@ class TestGemPlatform < Gem::TestCase
x86_darwin8 = Gem::Platform.new "i686-darwin8.0"
util_set_arch "powerpc-darwin8"
- assert((ppc_darwin8 === Gem::Platform.local), "powerpc =~ universal")
- assert((uni_darwin8 === Gem::Platform.local), "powerpc =~ universal")
- refute((x86_darwin8 === Gem::Platform.local), "powerpc =~ universal")
+ assert(ppc_darwin8 === Gem::Platform.local, "powerpc =~ universal")
+ assert(uni_darwin8 === Gem::Platform.local, "powerpc =~ universal")
+ refute(x86_darwin8 === Gem::Platform.local, "powerpc =~ universal")
util_set_arch "i686-darwin8"
- refute((ppc_darwin8 === Gem::Platform.local), "powerpc =~ universal")
- assert((uni_darwin8 === Gem::Platform.local), "x86 =~ universal")
- assert((x86_darwin8 === Gem::Platform.local), "powerpc =~ universal")
+ refute(ppc_darwin8 === Gem::Platform.local, "powerpc =~ universal")
+ assert(uni_darwin8 === Gem::Platform.local, "x86 =~ universal")
+ assert(x86_darwin8 === Gem::Platform.local, "powerpc =~ universal")
util_set_arch "universal-darwin8"
- assert((ppc_darwin8 === Gem::Platform.local), "universal =~ ppc")
- assert((uni_darwin8 === Gem::Platform.local), "universal =~ universal")
- assert((x86_darwin8 === Gem::Platform.local), "universal =~ x86")
+ assert(ppc_darwin8 === Gem::Platform.local, "universal =~ ppc")
+ assert(uni_darwin8 === Gem::Platform.local, "universal =~ universal")
+ assert(x86_darwin8 === Gem::Platform.local, "universal =~ x86")
end
def test_nil_cpu_arch_is_treated_as_universal
@@ -266,18 +274,18 @@ class TestGemPlatform < Gem::TestCase
with_uni_arch = Gem::Platform.new ["universal", "mingw32"]
with_x86_arch = Gem::Platform.new ["x86", "mingw32"]
- assert((with_nil_arch === with_uni_arch), "nil =~ universal")
- assert((with_uni_arch === with_nil_arch), "universal =~ nil")
- assert((with_nil_arch === with_x86_arch), "nil =~ x86")
- assert((with_x86_arch === with_nil_arch), "x86 =~ nil")
+ assert(with_nil_arch === with_uni_arch, "nil =~ universal")
+ assert(with_uni_arch === with_nil_arch, "universal =~ nil")
+ assert(with_nil_arch === with_x86_arch, "nil =~ x86")
+ assert(with_x86_arch === with_nil_arch, "x86 =~ nil")
end
def test_nil_version_is_treated_as_any_version
x86_darwin_8 = Gem::Platform.new "i686-darwin8.0"
x86_darwin_nil = Gem::Platform.new "i686-darwin"
- assert((x86_darwin_8 === x86_darwin_nil), "8.0 =~ nil")
- assert((x86_darwin_nil === x86_darwin_8), "nil =~ 8.0")
+ assert(x86_darwin_8 === x86_darwin_nil, "8.0 =~ nil")
+ assert(x86_darwin_nil === x86_darwin_8, "nil =~ 8.0")
end
def test_nil_version_is_stricter_for_linux_os
@@ -371,40 +379,33 @@ class TestGemPlatform < Gem::TestCase
arm64 = Gem::Platform.new "arm64-linux"
util_set_arch "armv5-linux"
- assert((arm === Gem::Platform.local), "arm === armv5")
- assert((armv5 === Gem::Platform.local), "armv5 === armv5")
- refute((armv7 === Gem::Platform.local), "armv7 === armv5")
- refute((arm64 === Gem::Platform.local), "arm64 === armv5")
- refute((Gem::Platform.local === arm), "armv5 === arm")
+ assert(arm === Gem::Platform.local, "arm === armv5")
+ assert(armv5 === Gem::Platform.local, "armv5 === armv5")
+ refute(armv7 === Gem::Platform.local, "armv7 === armv5")
+ refute(arm64 === Gem::Platform.local, "arm64 === armv5")
+ refute(Gem::Platform.local === arm, "armv5 === arm")
util_set_arch "armv7-linux"
- assert((arm === Gem::Platform.local), "arm === armv7")
- refute((armv5 === Gem::Platform.local), "armv5 === armv7")
- assert((armv7 === Gem::Platform.local), "armv7 === armv7")
- refute((arm64 === Gem::Platform.local), "arm64 === armv7")
- refute((Gem::Platform.local === arm), "armv7 === arm")
+ assert(arm === Gem::Platform.local, "arm === armv7")
+ refute(armv5 === Gem::Platform.local, "armv5 === armv7")
+ assert(armv7 === Gem::Platform.local, "armv7 === armv7")
+ refute(arm64 === Gem::Platform.local, "arm64 === armv7")
+ refute(Gem::Platform.local === arm, "armv7 === arm")
util_set_arch "arm64-linux"
- refute((arm === Gem::Platform.local), "arm === arm64")
- refute((armv5 === Gem::Platform.local), "armv5 === arm64")
- refute((armv7 === Gem::Platform.local), "armv7 === arm64")
- assert((arm64 === Gem::Platform.local), "arm64 === arm64")
+ refute(arm === Gem::Platform.local, "arm === arm64")
+ refute(armv5 === Gem::Platform.local, "armv5 === arm64")
+ refute(armv7 === Gem::Platform.local, "armv7 === arm64")
+ assert(arm64 === Gem::Platform.local, "arm64 === arm64")
end
def test_equals3_universal_mingw
uni_mingw = Gem::Platform.new "universal-mingw"
- mingw32 = Gem::Platform.new "x64-mingw32"
mingw_ucrt = Gem::Platform.new "x64-mingw-ucrt"
- util_set_arch "x64-mingw32"
- assert((uni_mingw === Gem::Platform.local), "uni_mingw === mingw32")
- assert((mingw32 === Gem::Platform.local), "mingw32 === mingw32")
- refute((mingw_ucrt === Gem::Platform.local), "mingw32 === mingw_ucrt")
-
util_set_arch "x64-mingw-ucrt"
- assert((uni_mingw === Gem::Platform.local), "uni_mingw === mingw32")
- assert((mingw_ucrt === Gem::Platform.local), "mingw_ucrt === mingw_ucrt")
- refute((mingw32 === Gem::Platform.local), "mingw32 === mingw_ucrt")
+ assert(uni_mingw === Gem::Platform.local, "uni_mingw === mingw_ucrt")
+ assert(mingw_ucrt === Gem::Platform.local, "mingw_ucrt === mingw_ucrt")
end
def test_equals3_version
@@ -415,11 +416,11 @@ class TestGemPlatform < Gem::TestCase
x86_darwin8 = Gem::Platform.new ["x86", "darwin", "8"]
x86_darwin9 = Gem::Platform.new ["x86", "darwin", "9"]
- assert((x86_darwin === Gem::Platform.local), "x86_darwin === x86_darwin8")
- assert((x86_darwin8 === Gem::Platform.local), "x86_darwin8 === x86_darwin8")
+ assert(x86_darwin === Gem::Platform.local, "x86_darwin === x86_darwin8")
+ assert(x86_darwin8 === Gem::Platform.local, "x86_darwin8 === x86_darwin8")
- refute((x86_darwin7 === Gem::Platform.local), "x86_darwin7 === x86_darwin8")
- refute((x86_darwin9 === Gem::Platform.local), "x86_darwin9 === x86_darwin8")
+ refute(x86_darwin7 === Gem::Platform.local, "x86_darwin7 === x86_darwin8")
+ refute(x86_darwin9 === Gem::Platform.local, "x86_darwin9 === x86_darwin8")
end
def test_equals_tilde
@@ -492,15 +493,171 @@ class TestGemPlatform < Gem::TestCase
assert_equal 1, result.scan(/@version=/).size
end
- def test_gem_platform_match_with_string_argument
- util_set_arch "x86_64-linux-musl"
+ def test_constants
+ assert_equal [nil, "java", nil], Gem::Platform::JAVA.to_a
+ assert_equal ["x86", "mswin32", nil], Gem::Platform::MSWIN.to_a
+ assert_equal [nil, "mswin64", nil], Gem::Platform::MSWIN64.to_a
+ assert_equal ["x86", "mingw32", nil], Gem::Platform::MINGW.to_a
+ assert_equal ["x64", "mingw", "ucrt"], Gem::Platform::X64_MINGW.to_a
+ assert_equal ["universal", "mingw", nil], Gem::Platform::UNIVERSAL_MINGW.to_a
+ assert_equal [["x86", "mswin32", nil], [nil, "mswin64", nil], ["universal", "mingw", nil]], Gem::Platform::WINDOWS.map(&:to_a)
+ assert_equal ["x86_64", "linux", nil], Gem::Platform::X64_LINUX.to_a
+ assert_equal ["x86_64", "linux", "musl"], Gem::Platform::X64_LINUX_MUSL.to_a
+ end
+
+ def test_generic
+ # converts non-windows platforms into ruby
+ assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform.new("x86-darwin-10"))
+ assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform::RUBY)
+
+ # converts java platform variants into java
+ assert_equal Gem::Platform::JAVA, Gem::Platform.generic(Gem::Platform.new("java"))
+ assert_equal Gem::Platform::JAVA, Gem::Platform.generic(Gem::Platform.new("universal-java-17"))
+
+ # converts mswin platform variants into x86-mswin32
+ assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("mswin32"))
+ assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("i386-mswin32"))
+ assert_equal Gem::Platform::MSWIN, Gem::Platform.generic(Gem::Platform.new("x86-mswin32"))
+
+ # converts 32-bit mingw platform variants into universal-mingw
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("i386-mingw32"))
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x86-mingw32"))
+
+ # converts 64-bit mingw platform variants into universal-mingw
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x64-mingw32"))
- Gem::Deprecate.skip_during do
- assert(Gem::Platform.match(Gem::Platform.new("x86_64-linux")), "should match Gem::Platform")
- assert(Gem::Platform.match("x86_64-linux"), "should match String platform")
+ # converts x64 mingw UCRT platform variants into universal-mingw
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("x64-mingw-ucrt"))
+
+ # converts aarch64 mingw UCRT platform variants into universal-mingw
+ assert_equal Gem::Platform::UNIVERSAL_MINGW, Gem::Platform.generic(Gem::Platform.new("aarch64-mingw-ucrt"))
+
+ assert_equal Gem::Platform::RUBY, Gem::Platform.generic(Gem::Platform.new("unknown"))
+ assert_equal Gem::Platform::RUBY, Gem::Platform.generic(nil)
+ assert_equal Gem::Platform::MSWIN64, Gem::Platform.generic(Gem::Platform.new("mswin64"))
+ end
+
+ def test_platform_specificity_match
+ [
+ ["ruby", "ruby", -1, -1],
+ ["x86_64-linux-musl", "x86_64-linux-musl", -1, -1],
+ ["x86_64-linux", "x86_64-linux-musl", 100, 200],
+ ["universal-darwin", "x86-darwin", 10, 20],
+ ["universal-darwin-19", "x86-darwin", 210, 120],
+ ["universal-darwin-19", "universal-darwin-20", 200, 200],
+ ["arm-darwin-19", "arm64-darwin-19", 0, 20],
+ ].each do |spec_platform, user_platform, s1, s2|
+ spec_platform = Gem::Platform.new(spec_platform)
+ user_platform = Gem::Platform.new(user_platform)
+ assert_equal s1, Gem::Platform.platform_specificity_match(spec_platform, user_platform),
+ "Gem::Platform.platform_specificity_match(#{spec_platform.to_s.inspect}, #{user_platform.to_s.inspect})"
+ assert_equal s2, Gem::Platform.platform_specificity_match(user_platform, spec_platform),
+ "Gem::Platform.platform_specificity_match(#{user_platform.to_s.inspect}, #{spec_platform.to_s.inspect})"
end
end
+ def test_sort_and_filter_best_platform_match
+ a_1 = util_spec "a", "1"
+ a_1_java = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform::JAVA
+ end
+ a_1_universal_darwin = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin")
+ end
+ a_1_universal_darwin_19 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin-19")
+ end
+ a_1_universal_darwin_20 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin-20")
+ end
+ a_1_arm_darwin_19 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("arm64-darwin-19")
+ end
+ a_1_x86_darwin = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("x86-darwin")
+ end
+ specs = [a_1, a_1_java, a_1_universal_darwin, a_1_universal_darwin_19, a_1_universal_darwin_20, a_1_arm_darwin_19, a_1_x86_darwin]
+ assert_equal [a_1], Gem::Platform.sort_and_filter_best_platform_match(specs, "ruby")
+ assert_equal [a_1_java], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform::JAVA)
+ assert_equal [a_1_arm_darwin_19], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("arm64-darwin-19"))
+ assert_equal [a_1_universal_darwin_20], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("arm64-darwin-20"))
+ assert_equal [a_1_universal_darwin_19], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-19"))
+ assert_equal [a_1_universal_darwin_20], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-20"))
+ assert_equal [a_1_x86_darwin], Gem::Platform.sort_and_filter_best_platform_match(specs, Gem::Platform.new("x86-darwin-21"))
+ end
+
+ def test_sort_best_platform_match
+ a_1 = util_spec "a", "1"
+ a_1_java = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform::JAVA
+ end
+ a_1_universal_darwin = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin")
+ end
+ a_1_universal_darwin_19 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin-19")
+ end
+ a_1_universal_darwin_20 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("universal-darwin-20")
+ end
+ a_1_arm_darwin_19 = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("arm64-darwin-19")
+ end
+ a_1_x86_darwin = util_spec "a", "1" do |s|
+ s.platform = Gem::Platform.new("x86-darwin")
+ end
+ specs = [a_1, a_1_java, a_1_universal_darwin, a_1_universal_darwin_19, a_1_universal_darwin_20, a_1_arm_darwin_19, a_1_x86_darwin]
+ assert_equal ["ruby",
+ "java",
+ "universal-darwin",
+ "universal-darwin-19",
+ "universal-darwin-20",
+ "arm64-darwin-19",
+ "x86-darwin"], Gem::Platform.sort_best_platform_match(specs, "ruby").map {|s| s.platform.to_s }
+ assert_equal ["java",
+ "universal-darwin",
+ "x86-darwin",
+ "universal-darwin-19",
+ "universal-darwin-20",
+ "arm64-darwin-19",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform::JAVA).map {|s| s.platform.to_s }
+ assert_equal ["arm64-darwin-19",
+ "universal-darwin-19",
+ "universal-darwin",
+ "java",
+ "x86-darwin",
+ "universal-darwin-20",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("arm64-darwin-19")).map {|s| s.platform.to_s }
+ assert_equal ["universal-darwin-20",
+ "universal-darwin",
+ "java",
+ "x86-darwin",
+ "arm64-darwin-19",
+ "universal-darwin-19",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("arm64-darwin-20")).map {|s| s.platform.to_s }
+ assert_equal ["universal-darwin-19",
+ "arm64-darwin-19",
+ "x86-darwin",
+ "universal-darwin",
+ "java",
+ "universal-darwin-20",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-19")).map {|s| s.platform.to_s }
+ assert_equal ["universal-darwin-20",
+ "x86-darwin",
+ "universal-darwin",
+ "java",
+ "universal-darwin-19",
+ "arm64-darwin-19",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-20")).map {|s| s.platform.to_s }
+ assert_equal ["x86-darwin",
+ "universal-darwin",
+ "java",
+ "universal-darwin-19",
+ "universal-darwin-20",
+ "arm64-darwin-19",
+ "ruby"], Gem::Platform.sort_best_platform_match(specs, Gem::Platform.new("x86-darwin-21")).map {|s| s.platform.to_s }
+ end
+
def assert_local_match(name)
assert_match Gem::Platform.local, name
end
@@ -508,4 +665,38 @@ class TestGemPlatform < Gem::TestCase
def refute_local_match(name)
refute_match Gem::Platform.local, name
end
+
+ def test_deconstruct
+ platform = Gem::Platform.new("x86_64-linux")
+ assert_equal ["x86_64", "linux", nil], platform.deconstruct
+ end
+
+ def test_deconstruct_keys
+ platform = Gem::Platform.new("x86_64-darwin-20")
+ assert_equal({ cpu: "x86_64", os: "darwin", version: "20" }, platform.deconstruct_keys(nil))
+ end
+
+ def test_pattern_matching_array
+ platform = Gem::Platform.new("arm64-darwin-21")
+ result =
+ case platform
+ in ["arm64", "darwin", version]
+ version
+ else
+ "no match"
+ end
+ assert_equal "21", result
+ end
+
+ def test_pattern_matching_hash
+ platform = Gem::Platform.new("x86_64-linux")
+ result =
+ case platform
+ in cpu: "x86_64", os: "linux"
+ "matched"
+ else
+ "no match"
+ end
+ assert_equal "matched", result
+ end
end
diff --git a/test/rubygems/test_gem_rdoc.rb b/test/rubygems/test_gem_rdoc.rb
index c4282b309c..9ecbb7d8c3 100644
--- a/test/rubygems/test_gem_rdoc.rb
+++ b/test/rubygems/test_gem_rdoc.rb
@@ -18,19 +18,7 @@ class TestGemRDoc < Gem::TestCase
install_gem @a
- hook_class = if defined?(RDoc::RubyGemsHook)
- RDoc::RubyGemsHook
- else
- Gem::RDoc
- end
-
- @hook = hook_class.new @a
-
- begin
- hook_class.load_rdoc
- rescue Gem::DocumentError => e
- pend e.message
- end
+ @hook = Gem::RDoc.new @a
Gem.configuration[:rdoc] = nil
end
diff --git a/test/rubygems/test_gem_remote_fetcher.rb b/test/rubygems/test_gem_remote_fetcher.rb
index ca858cfda5..c35da2fc5a 100644
--- a/test/rubygems/test_gem_remote_fetcher.rb
+++ b/test/rubygems/test_gem_remote_fetcher.rb
@@ -60,7 +60,7 @@ class TestGemRemoteFetcher < Gem::TestCase
uri = Gem::URI "http://example/file"
path = File.join @tempdir, "file"
- fetcher = util_fuck_with_fetcher "hello"
+ fetcher = fake_fetcher(uri.to_s, "hello")
data = fetcher.cache_update_path uri, path
@@ -75,7 +75,7 @@ class TestGemRemoteFetcher < Gem::TestCase
path = File.join @tempdir, "file"
data = String.new("\xC8").force_encoding(Encoding::BINARY)
- fetcher = util_fuck_with_fetcher data
+ fetcher = fake_fetcher(uri.to_s, data)
written_data = fetcher.cache_update_path uri, path
@@ -88,7 +88,7 @@ class TestGemRemoteFetcher < Gem::TestCase
uri = Gem::URI "http://example/file"
path = File.join @tempdir, "file"
- fetcher = util_fuck_with_fetcher "hello"
+ fetcher = fake_fetcher(uri.to_s, "hello")
data = fetcher.cache_update_path uri, path, false
@@ -97,103 +97,79 @@ class TestGemRemoteFetcher < Gem::TestCase
assert_path_not_exist path
end
- def util_fuck_with_fetcher(data, blow = false)
- fetcher = Gem::RemoteFetcher.fetcher
- fetcher.instance_variable_set :@test_data, data
-
- if blow
- def fetcher.fetch_path(arg, *rest)
- # OMG I'm such an ass
- class << self; remove_method :fetch_path; end
- def self.fetch_path(arg, *rest)
- @test_arg = arg
- @test_data
- end
+ def test_cache_update_path_overwrites_existing_file
+ uri = Gem::URI "http://example/file"
+ path = File.join @tempdir, "file"
- raise Gem::RemoteFetcher::FetchError.new("haha!", "")
- end
- else
- def fetcher.fetch_path(arg, *rest)
- @test_arg = arg
- @test_data
- end
- end
+ # Create existing file with old content
+ File.write(path, "old content")
+ assert_equal "old content", File.read(path)
+
+ fetcher = fake_fetcher(uri.to_s, "new content")
+
+ data = fetcher.cache_update_path uri, path
- fetcher
+ assert_equal "new content", data
+ assert_equal "new content", File.read(path)
end
def test_download
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://gems.example.com")
- assert_equal("http://gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
def test_download_with_auth
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://user:password@gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:password@gems.example.com")
- assert_equal("http://user:password@gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
def test_download_with_token
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://token@gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://token@gems.example.com")
- assert_equal("http://token@gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
def test_download_with_x_oauth_basic
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://token:x-oauth-basic@gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://token:x-oauth-basic@gems.example.com")
- assert_equal("http://token:x-oauth-basic@gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
def test_download_with_encoded_auth
- a1_data = nil
- File.open @a1_gem, "rb" do |fp|
- a1_data = fp.read
- end
+ a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://user:%25pas%25sword@gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
a1_cache_gem = @a1.cache_file
assert_equal a1_cache_gem, fetcher.download(@a1, "http://user:%25pas%25sword@gems.example.com")
- assert_equal("http://user:%25pas%25sword@gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
@@ -235,8 +211,9 @@ class TestGemRemoteFetcher < Gem::TestCase
def test_download_install_dir
a1_data = File.open @a1_gem, "rb", &:read
+ a1_url = "http://gems.example.com/gems/a-1.gem"
- fetcher = util_fuck_with_fetcher a1_data
+ fetcher = fake_fetcher(a1_url, a1_data)
install_dir = File.join @tempdir, "more_gems"
@@ -245,8 +222,7 @@ class TestGemRemoteFetcher < Gem::TestCase
actual = fetcher.download(@a1, "http://gems.example.com", install_dir)
assert_equal a1_cache_gem, actual
- assert_equal("http://gems.example.com/gems/a-1.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
+ assert_equal a1_url, fetcher.paths.last
assert File.exist?(a1_cache_gem)
end
@@ -282,7 +258,12 @@ class TestGemRemoteFetcher < Gem::TestCase
FileUtils.chmod 0o555, @a1.cache_dir
FileUtils.chmod 0o555, @gemhome
- fetcher = util_fuck_with_fetcher File.read(@a1_gem)
+ fetcher = Gem::RemoteFetcher.fetcher
+ def fetcher.fetch_path(uri, *rest)
+ File.read File.join(@test_gem_dir, "a-1.gem")
+ end
+ fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem))
+
fetcher.download(@a1, "http://gems.example.com")
a1_cache_gem = File.join Gem.user_dir, "cache", @a1.file_name
assert File.exist? a1_cache_gem
@@ -301,19 +282,21 @@ class TestGemRemoteFetcher < Gem::TestCase
end
e1.loaded_from = File.join(@gemhome, "specifications", e1.full_name)
- e1_data = nil
- File.open e1_gem, "rb" do |fp|
- e1_data = fp.read
- end
+ e1_data = File.open e1_gem, "rb", &:read
- fetcher = util_fuck_with_fetcher e1_data, :blow_chunks
+ fetcher = Gem::RemoteFetcher.fetcher
+ def fetcher.fetch_path(uri, *rest)
+ @call_count ||= 0
+ @call_count += 1
+ raise Gem::RemoteFetcher::FetchError.new("error", uri) if @call_count == 1
+ @test_data
+ end
+ fetcher.instance_variable_set(:@test_data, e1_data)
e1_cache_gem = e1.cache_file
assert_equal e1_cache_gem, fetcher.download(e1, "http://gems.example.com")
- assert_equal("http://gems.example.com/gems/#{e1.original_name}.gem",
- fetcher.instance_variable_get(:@test_arg).to_s)
assert File.exist?(e1_cache_gem)
end
@@ -592,7 +575,112 @@ class TestGemRemoteFetcher < Gem::TestCase
end
end
- def assert_error(exception_class=Exception)
+ def test_download_with_global_gem_cache
+ # Use a temp directory to safely test global cache behavior
+ test_cache_dir = File.join(@tempdir, "global_gem_cache_test")
+
+ Gem.stub :global_gem_cache_path, test_cache_dir do
+ Gem.configuration.global_gem_cache = true
+
+ # Use the real RemoteFetcher with stubbed fetch_path
+ fetcher = Gem::RemoteFetcher.fetcher
+ def fetcher.fetch_path(uri, *rest)
+ File.binread File.join(@test_gem_dir, "a-1.gem")
+ end
+ fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem))
+
+ # With global cache enabled, gem goes directly to global cache
+ global_cache_gem = File.join(test_cache_dir, @a1.file_name)
+ assert_equal global_cache_gem, fetcher.download(@a1, "http://gems.example.com")
+ assert File.exist?(global_cache_gem), "Gem should be in global cache"
+ end
+ ensure
+ Gem.configuration.global_gem_cache = false
+ end
+
+ def test_download_uses_global_gem_cache
+ # Use a temp directory to safely test global cache behavior
+ test_cache_dir = File.join(@tempdir, "global_gem_cache_test")
+
+ Gem.stub :global_gem_cache_path, test_cache_dir do
+ Gem.configuration.global_gem_cache = true
+
+ # Pre-populate global cache
+ FileUtils.mkdir_p test_cache_dir
+ global_cache_gem = File.join(test_cache_dir, @a1.file_name)
+ FileUtils.cp @a1_gem, global_cache_gem
+
+ fetcher = Gem::RemoteFetcher.fetcher
+
+ # Should return global cache path without downloading
+ result = fetcher.download(@a1, "http://gems.example.com")
+ assert_equal global_cache_gem, result
+ end
+ ensure
+ Gem.configuration.global_gem_cache = false
+ end
+
+ def test_download_without_global_gem_cache
+ # Use a temp directory to safely test global cache behavior
+ test_cache_dir = File.join(@tempdir, "global_gem_cache_test")
+
+ Gem.stub :global_gem_cache_path, test_cache_dir do
+ Gem.configuration.global_gem_cache = false
+
+ # Use the real RemoteFetcher with stubbed fetch_path
+ fetcher = Gem::RemoteFetcher.fetcher
+ def fetcher.fetch_path(uri, *rest)
+ File.binread File.join(@test_gem_dir, "a-1.gem")
+ end
+ fetcher.instance_variable_set(:@test_gem_dir, File.dirname(@a1_gem))
+
+ a1_cache_gem = @a1.cache_file
+ assert_equal a1_cache_gem, fetcher.download(@a1, "http://gems.example.com")
+
+ # Verify gem was NOT copied to global cache
+ global_cache_gem = File.join(test_cache_dir, @a1.file_name)
+ refute File.exist?(global_cache_gem), "Gem should not be copied to global cache when disabled"
+ end
+ end
+
+ def test_fetch_http_with_custom_error_header
+ fetcher = Gem::RemoteFetcher.new nil
+ @fetcher = fetcher
+ url = "http://gems.example.com/error"
+
+ def fetcher.request(uri, request_class, last_modified = nil)
+ res = Gem::Net::HTTPBadRequest.new nil, 403, "Forbidden"
+ res.add_field "X-Error-Message", "Component blocked by policy"
+ res
+ end
+
+ e = assert_raise Gem::RemoteFetcher::FetchError do
+ fetcher.fetch_http Gem::URI.parse(url)
+ end
+
+ assert_equal "Bad response Component blocked by policy 403 (#{url})", e.message
+ end
+
+ def test_fetch_http_without_custom_error_header
+ fetcher = Gem::RemoteFetcher.new nil
+ @fetcher = fetcher
+ url = "http://gems.example.com/error"
+
+ def fetcher.request(uri, request_class, last_modified = nil)
+ res = Gem::Net::HTTPBadRequest.new nil, 403, "Forbidden"
+ res
+ end
+
+ e = assert_raise Gem::RemoteFetcher::FetchError do
+ fetcher.fetch_http Gem::URI.parse(url)
+ end
+
+ assert_equal "Bad response Forbidden 403 (#{url})", e.message
+ end
+
+ private
+
+ def assert_error(exception_class = Exception)
got_exception = false
begin
@@ -603,4 +691,13 @@ class TestGemRemoteFetcher < Gem::TestCase
assert got_exception, "Expected exception conforming to #{exception_class}"
end
+
+ def fake_fetcher(url, data)
+ original_fetcher = Gem::RemoteFetcher.fetcher
+ fetcher = Gem::FakeFetcher.new
+ fetcher.data[url] = data
+ Gem::RemoteFetcher.fetcher = fetcher
+ ensure
+ Gem::RemoteFetcher.fetcher = original_fetcher
+ end
end
diff --git a/test/rubygems/test_gem_remote_fetcher_s3.rb b/test/rubygems/test_gem_remote_fetcher_s3.rb
index fe7eb7ec01..4a5acc5a86 100644
--- a/test/rubygems/test_gem_remote_fetcher_s3.rb
+++ b/test/rubygems/test_gem_remote_fetcher_s3.rb
@@ -8,6 +8,100 @@ require "rubygems/package"
class TestGemRemoteFetcherS3 < Gem::TestCase
include Gem::DefaultUserInteraction
+ class FakeGemRequest < Gem::Request
+ attr_reader :last_request, :uri
+
+ # Override perform_request to stub things
+ def perform_request(request)
+ @last_request = request
+ @response
+ end
+
+ def set_response(response)
+ @response = response
+ end
+ end
+
+ class FakeS3URISigner < Gem::S3URISigner
+ class << self
+ attr_accessor :return_token, :instance_profile
+ end
+
+ # Convenience method to output the recent aws iam queries made in tests
+ # this outputs the verb, path, and any non-generic headers
+ def recent_aws_query_logs
+ sreqs = @aws_iam_calls.map do |c|
+ r = c.last_request
+ s = +"#{r.method} #{c.uri}\n"
+ r.each_header do |key, v|
+ # Only include headers that start with x-
+ next unless key.start_with?("x-")
+ s << " #{key}=#{v}\n"
+ end
+ s
+ end
+
+ sreqs.join("")
+ end
+
+ def initialize(uri, method)
+ @aws_iam_calls = []
+ super
+ end
+
+ def ec2_iam_request(uri, verb)
+ fake_s3_request = FakeGemRequest.new(uri, verb, nil, nil)
+ @aws_iam_calls << fake_s3_request
+
+ case uri.to_s
+ when "http://169.254.169.254/latest/api/token"
+ if FakeS3URISigner.return_token.nil?
+ res = Gem::Net::HTTPUnauthorized.new nil, 401, nil
+ def res.body = "you got a 401! panic!"
+ else
+ res = Gem::Net::HTTPOK.new nil, 200, nil
+ def res.body = FakeS3URISigner.return_token
+ end
+ when "http://169.254.169.254/latest/meta-data/iam/info"
+ res = Gem::Net::HTTPOK.new nil, 200, nil
+ def res.body
+ <<~JSON
+ {
+ "Code": "Success",
+ "LastUpdated": "2023-05-27:05:05",
+ "InstanceProfileArn": "arn:aws:iam::somesecretid:instance-profile/TestRole",
+ "InstanceProfileId": "SOMEPROFILEID"
+ }
+ JSON
+ end
+
+ when "http://169.254.169.254/latest/meta-data/iam/security-credentials/TestRole"
+ res = Gem::Net::HTTPOK.new nil, 200, nil
+ def res.body = FakeS3URISigner.instance_profile
+ else
+ raise "Unexpected request to #{uri}"
+ end
+
+ fake_s3_request.set_response(res)
+ fake_s3_request
+ end
+ end
+
+ class FakeGemFetcher < Gem::RemoteFetcher
+ attr_reader :fetched_uri, :last_s3_uri_signer
+
+ def request(uri, request_class, last_modified = nil)
+ @fetched_uri = uri
+ res = Gem::Net::HTTPOK.new nil, 200, nil
+ def res.body = "success"
+ res
+ end
+
+ def s3_uri_signer(uri, method)
+ @last_s3_uri_signer = FakeS3URISigner.new(uri, method)
+ end
+ end
+
def setup
super
@@ -18,39 +112,61 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
@a1.loaded_from = File.join(@gemhome, "specifications", @a1.full_name)
end
- def assert_fetch_s3(url, signature, token=nil, region="us-east-1", instance_profile_json=nil)
- fetcher = Gem::RemoteFetcher.new nil
- @fetcher = fetcher
- $fetched_uri = nil
- $instance_profile = instance_profile_json
+ def assert_fetched_s3_with_imds_v2(expected_token)
+ # Three API requests:
+ # 1. Get the token
+ # 2. Lookup profile details
+ # 3. Query the credentials
+ expected = <<~TEXT
+ PUT http://169.254.169.254/latest/api/token
+ x-aws-ec2-metadata-token-ttl-seconds=60
+ GET http://169.254.169.254/latest/meta-data/iam/info
+ x-aws-ec2-metadata-token=#{expected_token}
+ GET http://169.254.169.254/latest/meta-data/iam/security-credentials/TestRole
+ x-aws-ec2-metadata-token=#{expected_token}
+ TEXT
+ recent_aws_query_logs = @fetcher.last_s3_uri_signer.recent_aws_query_logs
+ assert_equal(expected.strip, recent_aws_query_logs.strip)
+ end
- def fetcher.request(uri, request_class, last_modified = nil)
- $fetched_uri = uri
- res = Gem::Net::HTTPOK.new nil, 200, nil
- def res.body
- "success"
- end
- res
- end
+ def assert_fetched_s3_with_imds_v1
+ # Three API requests:
+ # 1. Get the token (which fails)
+ # 2. Lookup profile details without token
+ # 3. Query the credentials without token
+ expected = <<~TEXT
+ PUT http://169.254.169.254/latest/api/token
+ x-aws-ec2-metadata-token-ttl-seconds=60
+ GET http://169.254.169.254/latest/meta-data/iam/info
+ GET http://169.254.169.254/latest/meta-data/iam/security-credentials/TestRole
+ TEXT
+ recent_aws_query_logs = @fetcher.last_s3_uri_signer.recent_aws_query_logs
+ assert_equal(expected.strip, recent_aws_query_logs.strip)
+ end
- def fetcher.s3_uri_signer(uri)
- require "json"
- s3_uri_signer = Gem::S3URISigner.new(uri)
- def s3_uri_signer.ec2_metadata_credentials_json
- JSON.parse($instance_profile)
- end
- # Running sign operation to make sure uri.query is not mutated
- s3_uri_signer.sign
- raise "URI query is not empty: #{uri.query}" unless uri.query.nil?
- s3_uri_signer
- end
+ def with_imds_v2_failure
+ FakeS3URISigner.should_fail = true
+ yield(fetcher)
+ ensure
+ FakeS3URISigner.should_fail = false
+ end
- data = fetcher.fetch_s3 Gem::URI.parse(url)
+ def assert_fetch_s3(url:, signature:, token: nil, region: "us-east-1", instance_profile_json: nil, fetcher: nil, method: "GET")
+ FakeS3URISigner.instance_profile = instance_profile_json
+ FakeS3URISigner.return_token = token
- assert_equal "https://my-bucket.s3.#{region}.amazonaws.com/gems/specs.4.8.gz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=testuser%2F20190624%2F#{region}%2Fs3%2Faws4_request&X-Amz-Date=20190624T050641Z&X-Amz-Expires=86400#{token ? "&X-Amz-Security-Token=" + token : ""}&X-Amz-SignedHeaders=host&X-Amz-Signature=#{signature}", $fetched_uri.to_s
- assert_equal "success", data
+ @fetcher = fetcher || FakeGemFetcher.new(nil)
+ res = @fetcher.fetch_s3 Gem::URI.parse(url), nil, (method == "HEAD")
+
+ assert_equal "https://my-bucket.s3.#{region}.amazonaws.com/gems/specs.4.8.gz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=testuser%2F20190624%2F#{region}%2Fs3%2Faws4_request&X-Amz-Date=20190624T051941Z&X-Amz-Expires=86400#{token ? "&X-Amz-Security-Token=" + token : ""}&X-Amz-SignedHeaders=host&X-Amz-Signature=#{signature}", @fetcher.fetched_uri.to_s
+ if method == "HEAD"
+ assert_equal 200, res.code
+ else
+ assert_equal "success", res
+ end
ensure
- $fetched_uri = nil
+ FakeS3URISigner.instance_profile = nil
+ FakeS3URISigner.return_token = nil
end
def test_fetch_s3_config_creds
@@ -59,7 +175,34 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b"
+ assert_fetch_s3(
+ url: url,
+ signature: "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c",
+ )
+ end
+ ensure
+ Gem.configuration[:s3_source] = nil
+ end
+
+ def test_fetch_s3_head_request
+ Gem.configuration[:s3_source] = {
+ "my-bucket" => { id: "testuser", secret: "testpass" },
+ }
+ url = "s3://my-bucket/gems/specs.4.8.gz"
+ Time.stub :now, Time.at(1_561_353_581) do
+ token = nil
+ region = "us-east-1"
+ instance_profile_json = nil
+ method = "HEAD"
+
+ assert_fetch_s3(
+ url: url,
+ signature: "a3c6cf9a2db62e85f4e57f8fc8ac8b5ff5c1fdd4aeef55935d05e05174d9c885",
+ token: token,
+ region: region,
+ instance_profile_json: instance_profile_json,
+ method: method
+ )
end
ensure
Gem.configuration[:s3_source] = nil
@@ -71,7 +214,11 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2"
+ assert_fetch_s3(
+ url: url,
+ signature: "ef07487bfd8e3ca594f8fc29775b70c0a0636f51318f95d4f12b2e6e1fd8c716",
+ region: "us-west-2"
+ )
end
ensure
Gem.configuration[:s3_source] = nil
@@ -83,7 +230,11 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken"
+ assert_fetch_s3(
+ url: url,
+ signature: "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625",
+ token: "testtoken"
+ )
end
ensure
Gem.configuration[:s3_source] = nil
@@ -98,7 +249,10 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b"
+ assert_fetch_s3(
+ url: url,
+ signature: "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c"
+ )
end
ensure
ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") }
@@ -114,7 +268,12 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2"
+ assert_fetch_s3(
+ url: url,
+ signature: "ef07487bfd8e3ca594f8fc29775b70c0a0636f51318f95d4f12b2e6e1fd8c716",
+ token: nil,
+ region: "us-west-2"
+ )
end
ensure
ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") }
@@ -130,7 +289,11 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken"
+ assert_fetch_s3(
+ url: url,
+ signature: "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625",
+ token: "testtoken"
+ )
end
ensure
ENV.each_key {|key| ENV.delete(key) if key.start_with?("AWS") }
@@ -140,7 +303,10 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
def test_fetch_s3_url_creds
url = "s3://testuser:testpass@my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b"
+ assert_fetch_s3(
+ url: url,
+ signature: "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c"
+ )
end
end
@@ -151,8 +317,14 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "20f974027db2f3cd6193565327a7c73457a138efb1a63ea248d185ce6827d41b", nil, "us-east-1",
- '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass"}'
+ assert_fetch_s3(
+ url: url,
+ signature: "da82e098bdaed0d3087047670efc98eaadc20559a473b5eac8d70190d2a9e8fd",
+ region: "us-east-1",
+ token: "mysecrettoken",
+ instance_profile_json: '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "mysecrettoken"}'
+ )
+ assert_fetched_s3_with_imds_v2("mysecrettoken")
end
ensure
Gem.configuration[:s3_source] = nil
@@ -165,8 +337,14 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "4afc3010757f1fd143e769f1d1dabd406476a4fc7c120e9884fd02acbb8f26c9", nil, "us-west-2",
- '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass"}'
+ assert_fetch_s3(
+ url: url,
+ signature: "532960594dbfe31d1bbfc0e8e7a666c3cbdd8b00a143774da51b7f920704afd2",
+ region: "us-west-2",
+ token: "mysecrettoken",
+ instance_profile_json: '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "mysecrettoken"}'
+ )
+ assert_fetched_s3_with_imds_v2("mysecrettoken")
end
ensure
Gem.configuration[:s3_source] = nil
@@ -179,14 +357,40 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
url = "s3://my-bucket/gems/specs.4.8.gz"
Time.stub :now, Time.at(1_561_353_581) do
- assert_fetch_s3 url, "935160a427ef97e7630f799232b8f208c4a4e49aad07d0540572a2ad5fe9f93c", "testtoken", "us-east-1",
- '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "testtoken"}'
+ assert_fetch_s3(
+ url: url,
+ signature: "e709338735f9077edf8f6b94b247171c266a9605975e08e4a519a123c3322625",
+ token: "testtoken",
+ region: "us-east-1",
+ instance_profile_json: '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass", "Token": "testtoken"}'
+ )
+ assert_fetched_s3_with_imds_v2("testtoken")
+ end
+ ensure
+ Gem.configuration[:s3_source] = nil
+ end
+
+ def test_fetch_s3_instance_profile_creds_with_fallback
+ Gem.configuration[:s3_source] = {
+ "my-bucket" => { provider: "instance_profile" },
+ }
+
+ url = "s3://my-bucket/gems/specs.4.8.gz"
+ Time.stub :now, Time.at(1_561_353_581) do
+ assert_fetch_s3(
+ url: url,
+ signature: "b5cb80c1301f7b1c50c4af54f1f6c034f80b56d32f000a855f0a903dc5a8413c",
+ token: nil,
+ region: "us-east-1",
+ instance_profile_json: '{"AccessKeyId": "testuser", "SecretAccessKey": "testpass"}'
+ )
+ assert_fetched_s3_with_imds_v1
end
ensure
Gem.configuration[:s3_source] = nil
end
- def refute_fetch_s3(url, expected_message)
+ def refute_fetch_s3(url:, expected_message:)
fetcher = Gem::RemoteFetcher.new nil
@fetcher = fetcher
@@ -199,7 +403,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
def test_fetch_s3_no_source_key
url = "s3://my-bucket/gems/specs.4.8.gz"
- refute_fetch_s3 url, "no s3_source key exists in .gemrc"
+ refute_fetch_s3(url: url, expected_message: "no s3_source key exists in .gemrc")
end
def test_fetch_s3_no_host
@@ -208,7 +412,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
}
url = "s3://other-bucket/gems/specs.4.8.gz"
- refute_fetch_s3 url, "no key for host other-bucket in s3_source in .gemrc"
+ refute_fetch_s3(url: url, expected_message: "no key for host other-bucket in s3_source in .gemrc")
ensure
Gem.configuration[:s3_source] = nil
end
@@ -217,7 +421,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
Gem.configuration[:s3_source] = { "my-bucket" => { secret: "testpass" } }
url = "s3://my-bucket/gems/specs.4.8.gz"
- refute_fetch_s3 url, "s3_source for my-bucket missing id or secret"
+ refute_fetch_s3(url: url, expected_message: "s3_source for my-bucket missing id or secret")
ensure
Gem.configuration[:s3_source] = nil
end
@@ -226,7 +430,7 @@ class TestGemRemoteFetcherS3 < Gem::TestCase
Gem.configuration[:s3_source] = { "my-bucket" => { id: "testuser" } }
url = "s3://my-bucket/gems/specs.4.8.gz"
- refute_fetch_s3 url, "s3_source for my-bucket missing id or secret"
+ refute_fetch_s3(url: url, expected_message: "s3_source for my-bucket missing id or secret")
ensure
Gem.configuration[:s3_source] = nil
end
diff --git a/test/rubygems/test_gem_request.rb b/test/rubygems/test_gem_request.rb
index eb15eed749..cd0a416e79 100644
--- a/test/rubygems/test_gem_request.rb
+++ b/test/rubygems/test_gem_request.rb
@@ -248,7 +248,7 @@ class TestGemRequest < Gem::TestCase
auth_header = conn.payload["Authorization"]
assert_equal "Basic #{base64_encode64("{DEScede}pass:x-oauth-basic")}".strip, auth_header
- assert_includes @ui.output, "GET https://REDACTED:x-oauth-basic@example.rubygems/specs.#{Gem.marshal_version}"
+ assert_includes @ui.output, "GET https://REDACTED@example.rubygems/specs.#{Gem.marshal_version}"
end
def test_fetch_head
@@ -363,19 +363,19 @@ class TestGemRequest < Gem::TestCase
def test_verify_certificate_extra_message
pend if Gem.java_platform?
- error_number = OpenSSL::X509::V_ERR_INVALID_CA
+ error_number = OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
store = OpenSSL::X509::Store.new
- context = OpenSSL::X509::StoreContext.new store
- context.error = error_number
+ context = OpenSSL::X509::StoreContext.new store, CHILD_CERT
+ context.verify
use_ui @ui do
Gem::Request.verify_certificate context
end
expected = <<-ERROR
-ERROR: SSL verification error at depth 0: invalid CA certificate (#{error_number})
-ERROR: Certificate is an invalid CA certificate
+ERROR: SSL verification error at depth 0: unable to get local issuer certificate (#{error_number})
+ERROR: You must add #{CHILD_CERT.issuer} to your local trusted store
ERROR
assert_equal expected, @ui.error
diff --git a/test/rubygems/test_gem_request_connection_pools.rb b/test/rubygems/test_gem_request_connection_pools.rb
index 966447bff6..2860deabf7 100644
--- a/test/rubygems/test_gem_request_connection_pools.rb
+++ b/test/rubygems/test_gem_request_connection_pools.rb
@@ -148,4 +148,16 @@ class TestGemRequestConnectionPool < Gem::TestCase
end
end.join
end
+
+ def test_checkouts_multiple_connections_from_the_pool
+ uri = Gem::URI.parse("http://example/some_endpoint")
+ pools = Gem::Request::ConnectionPools.new nil, [], 2
+ pool = pools.pool_for uri
+
+ pool.checkout
+
+ Thread.new do
+ assert_not_nil(pool.checkout)
+ end.join
+ end
end
diff --git a/test/rubygems/test_gem_request_set.rb b/test/rubygems/test_gem_request_set.rb
index 9aa244892c..33054aa8e5 100644
--- a/test/rubygems/test_gem_request_set.rb
+++ b/test/rubygems/test_gem_request_set.rb
@@ -93,6 +93,34 @@ Gems to install:
end
end
+ def test_install_from_gemdeps_explain_verbose
+ spec_fetcher do |fetcher|
+ fetcher.gem "a", 2
+ end
+
+ rs = Gem::RequestSet.new
+
+ verbose = Gem.configuration.verbose
+ Gem.configuration.verbose = :really
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts 'gem "a"'
+ io.flush
+
+ expected = <<-EXPECTED
+Gems to install:
+ a-2
+ EXPECTED
+
+ actual, _ = capture_output do
+ rs.install_from_gemdeps gemdeps: io.path, explain: true
+ end
+ assert_equal(expected, actual)
+ end
+ ensure
+ Gem.configuration.verbose = verbose
+ end
+
def test_install_from_gemdeps_install_dir
spec_fetcher do |fetcher|
fetcher.gem "a", 2
@@ -311,6 +339,110 @@ ruby "0"
assert_empty rs.dependencies
end
+ def test_load_gemdeps_with_lockfile_gem_section
+ rs = Gem::RequestSet.new
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts 'gem "b"'
+ end
+
+ File.open "gem.deps.rb.lock", "w" do |io|
+ io.puts <<~LOCKFILE
+ GEM
+ remote: #{@gem_repo}
+ specs:
+ a (1)
+ b (1)
+ a (~> 1.0)
+
+ PLATFORMS
+ #{Gem::Platform::RUBY}
+
+ DEPENDENCIES
+ b
+ LOCKFILE
+ end
+
+ rs.load_gemdeps "gem.deps.rb"
+
+ lock_set = rs.sets.find {|set| Gem::Resolver::LockSet === set }
+ refute_nil lock_set, "LockSet should be created from GEM section"
+ assert_equal %w[a-1 b-1], lock_set.specs.map(&:full_name).sort
+ end
+
+ def test_load_gemdeps_with_lockfile_git_section
+ rs = Gem::RequestSet.new
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts 'gem "a", :git => "git://example/a.git"'
+ end
+
+ File.open "gem.deps.rb.lock", "w" do |io|
+ io.puts <<~LOCKFILE
+ GIT
+ remote: git://example/a.git
+ revision: deadbeef
+ specs:
+ a (1)
+
+ PLATFORMS
+ #{Gem::Platform::RUBY}
+
+ DEPENDENCIES
+ a!
+ LOCKFILE
+ end
+
+ rs.load_gemdeps "gem.deps.rb"
+
+ git_set = rs.sets.find {|set| Gem::Resolver::GitSet === set }
+ refute_nil git_set, "GitSet should be created from GIT section"
+ assert_includes git_set.specs.keys, "a"
+ end
+
+ def test_load_gemdeps_with_lockfile_path_section
+ _, _, directory = vendor_gem
+
+ rs = Gem::RequestSet.new
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts "gem \"a\", :path => #{directory.inspect}"
+ end
+
+ File.open "gem.deps.rb.lock", "w" do |io|
+ io.puts <<~LOCKFILE
+ PATH
+ remote: #{directory}
+ specs:
+ a (1)
+
+ PLATFORMS
+ #{Gem::Platform::RUBY}
+
+ DEPENDENCIES
+ a!
+ LOCKFILE
+ end
+
+ rs.load_gemdeps "gem.deps.rb"
+
+ vendor_set = rs.sets.find {|set| Gem::Resolver::VendorSet === set }
+ refute_nil vendor_set, "VendorSet should be created from PATH section"
+ assert_equal %w[a-1], vendor_set.specs.values.map(&:full_name)
+ end
+
+ def test_load_gemdeps_with_missing_lockfile
+ rs = Gem::RequestSet.new
+
+ File.open "gem.deps.rb", "w" do |io|
+ io.puts 'gem "a"'
+ end
+
+ rs.load_gemdeps "gem.deps.rb"
+
+ assert_equal [dep("a")], rs.dependencies
+ end
+
def test_resolve
a = util_spec "a", "2", "b" => ">= 2"
b = util_spec "b", "2"
diff --git a/test/rubygems/test_gem_request_set_lockfile_parser.rb b/test/rubygems/test_gem_request_set_lockfile_parser.rb
deleted file mode 100644
index 253a59b243..0000000000
--- a/test/rubygems/test_gem_request_set_lockfile_parser.rb
+++ /dev/null
@@ -1,544 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-require "rubygems/request_set"
-require "rubygems/request_set/lockfile"
-require "rubygems/request_set/lockfile/tokenizer"
-require "rubygems/request_set/lockfile/parser"
-
-class TestGemRequestSetLockfileParser < Gem::TestCase
- def setup
- super
- @gem_deps_file = "gem.deps.rb"
- @lock_file = File.expand_path "#{@gem_deps_file}.lock"
- @set = Gem::RequestSet.new
- end
-
- def test_get
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
- parser = tokenizer.make_parser nil, nil
-
- assert_equal :newline, parser.get.first
- end
-
- def test_get_type_mismatch
- filename = File.expand_path("#{@gem_deps_file}.lock")
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "foo", filename, 1, 0
- parser = tokenizer.make_parser nil, nil
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- parser.get :section
- end
-
- expected =
- 'unexpected token [:text, "foo"], expected :section (at line 1 column 0)'
-
- assert_equal expected, e.message
-
- assert_equal 1, e.line
- assert_equal 0, e.column
- assert_equal filename, e.path
- end
-
- def test_get_type_multiple
- filename = File.expand_path("#{@gem_deps_file}.lock")
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "x", filename, 1
- parser = tokenizer.make_parser nil, nil
-
- assert parser.get [:text, :section]
- end
-
- def test_get_type_value_mismatch
- filename = File.expand_path("#{@gem_deps_file}.lock")
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "x", filename, 1
- parser = tokenizer.make_parser nil, nil
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- parser.get :text, "y"
- end
-
- expected =
- 'unexpected token [:text, "x"], expected [:text, "y"] (at line 1 column 0)'
-
- assert_equal expected, e.message
-
- assert_equal 1, e.line
- assert_equal 0, e.column
- assert_equal File.expand_path("#{@gem_deps_file}.lock"), e.path
- end
-
- def test_parse
- write_lockfile <<-LOCKFILE.strip
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- a
- LOCKFILE
-
- platforms = []
- parse_lockfile @set, platforms
-
- assert_equal [dep("a")], @set.dependencies
-
- assert_equal [Gem::Platform::RUBY], platforms
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "could not find a LockSet"
-
- assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
- end
-
- def test_parse_dependencies
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- a (>= 1, <= 2)
- LOCKFILE
-
- platforms = []
- parse_lockfile @set, platforms
-
- assert_equal [dep("a", ">= 1", "<= 2")], @set.dependencies
-
- assert_equal [Gem::Platform::RUBY], platforms
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "could not find a LockSet"
-
- assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
- end
-
- def test_parse_DEPENDENCIES_git
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://git.example/josevalim/rails-footnotes.git
- revision: 3a6ac1971e91d822f057650cc5916ebfcbd6ee37
- specs:
- rails-footnotes (3.7.9)
- rails (>= 3.0.0)
-
-GIT
- remote: git://git.example/svenfuchs/i18n-active_record.git
- revision: 55507cf59f8f2173d38e07e18df0e90d25b1f0f6
- specs:
- i18n-active_record (0.0.2)
- i18n (>= 0.5.0)
-
-GEM
- remote: http://gems.example/
- specs:
- i18n (0.6.9)
- rails (4.0.0)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- i18n-active_record!
- rails-footnotes!
- LOCKFILE
-
- parse_lockfile @set, []
-
- expected = [
- dep("i18n-active_record", "= 0.0.2"),
- dep("rails-footnotes", "= 3.7.9"),
- ]
-
- assert_equal expected, @set.dependencies
- end
-
- def test_parse_DEPENDENCIES_git_version
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://github.com/progrium/ruby-jwt.git
- revision: 8d74770c6cd92ea234b428b5d0c1f18306a4f41c
- specs:
- jwt (1.1)
-
-GEM
- remote: http://gems.example/
- specs:
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- jwt (= 1.1)!
- LOCKFILE
-
- parse_lockfile @set, []
-
- expected = [
- dep("jwt", "= 1.1"),
- ]
-
- assert_equal expected, @set.dependencies
- end
-
- def test_parse_GEM
- write_lockfile <<-LOCKFILE
-GEM
- specs:
- a (2)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- a
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", ">= 0")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "found a LockSet"
-
- assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
- end
-
- def test_parse_GEM_remote_multiple
- write_lockfile <<-LOCKFILE
-GEM
- remote: https://gems.example/
- remote: https://other.example/
- specs:
- a (2)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- a
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", ">= 0")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "found a LockSet"
-
- assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
-
- assert_equal %w[https://gems.example/ https://other.example/],
- lockfile_set.specs.flat_map {|s| s.sources.map {|src| src.uri.to_s } }
- end
-
- def test_parse_GIT
- @set.instance_variable_set :@install_dir, "install_dir"
-
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://example/a.git
- revision: abranch
- specs:
- a (2)
- b (>= 3)
- c
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 2")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- git_set = @set.sets.find do |set|
- Gem::Resolver::GitSet === set
- end
-
- assert git_set, "could not find a GitSet"
-
- assert_equal %w[a-2], git_set.specs.values.map(&:full_name)
-
- assert_equal [dep("b", ">= 3"), dep("c")],
- git_set.specs.values.first.dependencies
-
- expected = {
- "a" => %w[git://example/a.git abranch],
- }
-
- assert_equal expected, git_set.repositories
- assert_equal "install_dir", git_set.root_dir
- end
-
- def test_parse_GIT_branch
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://example/a.git
- revision: 1234abc
- branch: 0-9-12-stable
- specs:
- a (2)
- b (>= 3)
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 2")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- git_set = @set.sets.find do |set|
- Gem::Resolver::GitSet === set
- end
-
- assert git_set, "could not find a GitSet"
-
- expected = {
- "a" => %w[git://example/a.git 1234abc],
- }
-
- assert_equal expected, git_set.repositories
- end
-
- def test_parse_GIT_ref
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://example/a.git
- revision: 1234abc
- ref: 1234abc
- specs:
- a (2)
- b (>= 3)
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 2")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- git_set = @set.sets.find do |set|
- Gem::Resolver::GitSet === set
- end
-
- assert git_set, "could not find a GitSet"
-
- expected = {
- "a" => %w[git://example/a.git 1234abc],
- }
-
- assert_equal expected, git_set.repositories
- end
-
- def test_parse_GIT_tag
- write_lockfile <<-LOCKFILE
-GIT
- remote: git://example/a.git
- revision: 1234abc
- tag: v0.9.12
- specs:
- a (2)
- b (>= 3)
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 2")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- git_set = @set.sets.find do |set|
- Gem::Resolver::GitSet === set
- end
-
- assert git_set, "could not find a GitSet"
-
- expected = {
- "a" => %w[git://example/a.git 1234abc],
- }
-
- assert_equal expected, git_set.repositories
- end
-
- def test_parse_PATH
- _, _, directory = vendor_gem
-
- write_lockfile <<-LOCKFILE
-PATH
- remote: #{directory}
- specs:
- a (1)
- b (2)
-
-DEPENDENCIES
- a!
- LOCKFILE
-
- parse_lockfile @set, []
-
- assert_equal [dep("a", "= 1")], @set.dependencies
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set, "found a LockSet"
-
- vendor_set = @set.sets.find do |set|
- Gem::Resolver::VendorSet === set
- end
-
- assert vendor_set, "could not find a VendorSet"
-
- assert_equal %w[a-1], vendor_set.specs.values.map(&:full_name)
-
- spec = vendor_set.load_spec "a", nil, nil, nil
-
- assert_equal [dep("b", "= 2")], spec.dependencies
- end
-
- def test_parse_dependency
- write_lockfile " 1)"
-
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
- parser = tokenizer.make_parser nil, nil
-
- parsed = parser.parse_dependency "a", "="
-
- assert_equal dep("a", "= 1"), parsed
-
- write_lockfile ")"
-
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
- parser = tokenizer.make_parser nil, nil
-
- parsed = parser.parse_dependency "a", "2"
-
- assert_equal dep("a", "= 2"), parsed
- end
-
- def test_parse_gem_specs_dependency
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
- b (= 3)
- c (~> 4)
- d
- e (~> 5.0, >= 5.0.1)
- b (3-x86_64-linux)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- a
- LOCKFILE
-
- platforms = []
- parse_lockfile @set, platforms
-
- assert_equal [dep("a")], @set.dependencies
-
- assert_equal [Gem::Platform::RUBY], platforms
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- assert lockfile_set, "could not find a LockSet"
-
- assert_equal %w[a-2 b-3], lockfile_set.specs.map(&:full_name)
-
- expected = [
- Gem::Platform::RUBY,
- Gem::Platform.new("x86_64-linux"),
- ]
-
- assert_equal expected, lockfile_set.specs.map(&:platform)
-
- spec = lockfile_set.specs.first
-
- expected = [
- dep("b", "= 3"),
- dep("c", "~> 4"),
- dep("d"),
- dep("e", "~> 5.0", ">= 5.0.1"),
- ]
-
- assert_equal expected, spec.dependencies
- end
-
- def test_parse_missing
- assert_raise(Errno::ENOENT) do
- parse_lockfile @set, []
- end
-
- lockfile_set = @set.sets.find do |set|
- Gem::Resolver::LockSet === set
- end
-
- refute lockfile_set
- end
-
- def write_lockfile(lockfile)
- File.open @lock_file, "w" do |io|
- io.write lockfile
- end
- end
-
- def parse_lockfile(set, platforms)
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
- parser = tokenizer.make_parser set, platforms
- parser.parse
- end
-end
diff --git a/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb b/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb
deleted file mode 100644
index dce8c9ada5..0000000000
--- a/test/rubygems/test_gem_request_set_lockfile_tokenizer.rb
+++ /dev/null
@@ -1,307 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-require "rubygems/request_set"
-require "rubygems/request_set/lockfile"
-require "rubygems/request_set/lockfile/tokenizer"
-require "rubygems/request_set/lockfile/parser"
-
-class TestGemRequestSetLockfileTokenizer < Gem::TestCase
- def setup
- super
-
- @gem_deps_file = "gem.deps.rb"
- @lock_file = File.expand_path "#{@gem_deps_file}.lock"
- end
-
- def test_peek
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
-
- assert_equal :newline, tokenizer.peek.first
-
- assert_equal :newline, tokenizer.next_token.first
-
- assert_equal :EOF, tokenizer.peek.first
- end
-
- def test_skip
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
-
- refute_predicate tokenizer, :empty?
-
- tokenizer.skip :newline
-
- assert_empty tokenizer
- end
-
- def test_token_pos
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new ""
- assert_equal [5, 0], tokenizer.token_pos(5)
-
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "", nil, 1, 2
- assert_equal [3, 1], tokenizer.token_pos(5)
- end
-
- def test_tokenize
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
- b (= 2)
- c (!= 3)
- d (> 4)
- e (< 5)
- f (>= 6)
- g (<= 7)
- h (~> 8)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- a
- LOCKFILE
-
- expected = [
- [:section, "GEM", 0, 0],
- [:newline, nil, 3, 0],
-
- [:entry, "remote", 2, 1],
- [:text, @gem_repo, 10, 1],
- [:newline, nil, 34, 1],
-
- [:entry, "specs", 2, 2],
- [:newline, nil, 8, 2],
-
- [:text, "a", 4, 3],
- [:l_paren, nil, 6, 3],
- [:text, "2", 7, 3],
- [:r_paren, nil, 8, 3],
- [:newline, nil, 9, 3],
-
- [:text, "b", 6, 4],
- [:l_paren, nil, 8, 4],
- [:requirement, "=", 9, 4],
- [:text, "2", 11, 4],
- [:r_paren, nil, 12, 4],
- [:newline, nil, 13, 4],
-
- [:text, "c", 6, 5],
- [:l_paren, nil, 8, 5],
- [:requirement, "!=", 9, 5],
- [:text, "3", 12, 5],
- [:r_paren, nil, 13, 5],
- [:newline, nil, 14, 5],
-
- [:text, "d", 6, 6],
- [:l_paren, nil, 8, 6],
- [:requirement, ">", 9, 6],
- [:text, "4", 11, 6],
- [:r_paren, nil, 12, 6],
- [:newline, nil, 13, 6],
-
- [:text, "e", 6, 7],
- [:l_paren, nil, 8, 7],
- [:requirement, "<", 9, 7],
- [:text, "5", 11, 7],
- [:r_paren, nil, 12, 7],
- [:newline, nil, 13, 7],
-
- [:text, "f", 6, 8],
- [:l_paren, nil, 8, 8],
- [:requirement, ">=", 9, 8],
- [:text, "6", 12, 8],
- [:r_paren, nil, 13, 8],
- [:newline, nil, 14, 8],
-
- [:text, "g", 6, 9],
- [:l_paren, nil, 8, 9],
- [:requirement, "<=", 9, 9],
- [:text, "7", 12, 9],
- [:r_paren, nil, 13, 9],
- [:newline, nil, 14, 9],
-
- [:text, "h", 6, 10],
- [:l_paren, nil, 8, 10],
- [:requirement, "~>", 9, 10],
- [:text, "8", 12, 10],
- [:r_paren, nil, 13, 10],
- [:newline, nil, 14, 10],
-
- [:newline, nil, 0, 11],
-
- [:section, "PLATFORMS", 0, 12],
- [:newline, nil, 9, 12],
-
- [:text, Gem::Platform::RUBY, 2, 13],
- [:newline, nil, 6, 13],
-
- [:newline, nil, 0, 14],
-
- [:section, "DEPENDENCIES", 0, 15],
- [:newline, nil, 12, 15],
-
- [:text, "a", 2, 16],
- [:newline, nil, 3, 16],
- ]
-
- assert_equal expected, tokenize_lockfile
- end
-
- def test_tokenize_capitals
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- Ab (2)
-
-PLATFORMS
- #{Gem::Platform::RUBY}
-
-DEPENDENCIES
- Ab
- LOCKFILE
-
- expected = [
- [:section, "GEM", 0, 0],
- [:newline, nil, 3, 0],
- [:entry, "remote", 2, 1],
- [:text, @gem_repo, 10, 1],
- [:newline, nil, 34, 1],
- [:entry, "specs", 2, 2],
- [:newline, nil, 8, 2],
- [:text, "Ab", 4, 3],
- [:l_paren, nil, 7, 3],
- [:text, "2", 8, 3],
- [:r_paren, nil, 9, 3],
- [:newline, nil, 10, 3],
- [:newline, nil, 0, 4],
- [:section, "PLATFORMS", 0, 5],
- [:newline, nil, 9, 5],
- [:text, Gem::Platform::RUBY, 2, 6],
- [:newline, nil, 6, 6],
- [:newline, nil, 0, 7],
- [:section, "DEPENDENCIES", 0, 8],
- [:newline, nil, 12, 8],
- [:text, "Ab", 2, 9],
- [:newline, nil, 4, 9],
- ]
-
- assert_equal expected, tokenize_lockfile
- end
-
- def test_tokenize_conflict_markers
- write_lockfile "<<<<<<<"
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- tokenize_lockfile
- end
-
- assert_equal "your #{@lock_file} contains merge conflict markers (at line 0 column 0)",
- e.message
-
- write_lockfile "|||||||"
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- tokenize_lockfile
- end
-
- assert_equal "your #{@lock_file} contains merge conflict markers (at line 0 column 0)",
- e.message
-
- write_lockfile "======="
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- tokenize_lockfile
- end
-
- assert_equal "your #{@lock_file} contains merge conflict markers (at line 0 column 0)",
- e.message
-
- write_lockfile ">>>>>>>"
-
- e = assert_raise Gem::RequestSet::Lockfile::ParseError do
- tokenize_lockfile
- end
-
- assert_equal "your #{@lock_file} contains merge conflict markers (at line 0 column 0)",
- e.message
- end
-
- def test_tokenize_git
- write_lockfile <<-LOCKFILE
-DEPENDENCIES
- a!
- LOCKFILE
-
- expected = [
- [:section, "DEPENDENCIES", 0, 0],
- [:newline, nil, 12, 0],
-
- [:text, "a", 2, 1],
- [:bang, nil, 3, 1],
- [:newline, nil, 4, 1],
- ]
-
- assert_equal expected, tokenize_lockfile
- end
-
- def test_tokenize_multiple
- write_lockfile <<-LOCKFILE
-GEM
- remote: #{@gem_repo}
- specs:
- a (2)
- b (~> 3.0, >= 3.0.1)
- LOCKFILE
-
- expected = [
- [:section, "GEM", 0, 0],
- [:newline, nil, 3, 0],
-
- [:entry, "remote", 2, 1],
- [:text, @gem_repo, 10, 1],
- [:newline, nil, 34, 1],
-
- [:entry, "specs", 2, 2],
- [:newline, nil, 8, 2],
-
- [:text, "a", 4, 3],
- [:l_paren, nil, 6, 3],
- [:text, "2", 7, 3],
- [:r_paren, nil, 8, 3],
- [:newline, nil, 9, 3],
-
- [:text, "b", 6, 4],
- [:l_paren, nil, 8, 4],
- [:requirement, "~>", 9, 4],
- [:text, "3.0", 12, 4],
- [:comma, nil, 15, 4],
- [:requirement, ">=", 17, 4],
- [:text, "3.0.1", 20, 4],
- [:r_paren, nil, 25, 4],
- [:newline, nil, 26, 4],
- ]
-
- assert_equal expected, tokenize_lockfile
- end
-
- def test_unget
- tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
- tokenizer.unshift :token
- parser = tokenizer.make_parser nil, nil
-
- assert_equal :token, parser.get
- end
-
- def write_lockfile(lockfile)
- File.open @lock_file, "w" do |io|
- io.write lockfile
- end
- end
-
- def tokenize_lockfile
- Gem::RequestSet::Lockfile::Tokenizer.from_file(@lock_file).to_a
- end
-end
diff --git a/test/rubygems/test_gem_requirement.rb b/test/rubygems/test_gem_requirement.rb
index de0d11ec00..00634dc7f4 100644
--- a/test/rubygems/test_gem_requirement.rb
+++ b/test/rubygems/test_gem_requirement.rb
@@ -137,11 +137,7 @@ class TestGemRequirement < Gem::TestCase
refute_satisfied_by "1.2", r
assert_satisfied_by "1.3", r
- assert_raise ArgumentError do
- Gem::Deprecate.skip_during do
- assert_satisfied_by nil, r
- end
- end
+ assert_satisfied_by nil, r
end
def test_satisfied_by_eh_blank
@@ -151,11 +147,7 @@ class TestGemRequirement < Gem::TestCase
assert_satisfied_by "1.2", r
refute_satisfied_by "1.3", r
- assert_raise ArgumentError do
- Gem::Deprecate.skip_during do
- assert_satisfied_by nil, r
- end
- end
+ refute_satisfied_by nil, r
end
def test_satisfied_by_eh_equal
@@ -165,11 +157,7 @@ class TestGemRequirement < Gem::TestCase
assert_satisfied_by "1.2", r
refute_satisfied_by "1.3", r
- assert_raise ArgumentError do
- Gem::Deprecate.skip_during do
- assert_satisfied_by nil, r
- end
- end
+ refute_satisfied_by nil, r
end
def test_satisfied_by_eh_gt
@@ -179,9 +167,7 @@ class TestGemRequirement < Gem::TestCase
refute_satisfied_by "1.2", r
assert_satisfied_by "1.3", r
- assert_raise ArgumentError do
- r.satisfied_by? nil
- end
+ refute_satisfied_by nil, r
end
def test_satisfied_by_eh_gte
diff --git a/test/rubygems/test_gem_resolver.rb b/test/rubygems/test_gem_resolver.rb
index 4990d5d2dd..84ede36b6c 100644
--- a/test/rubygems/test_gem_resolver.rb
+++ b/test/rubygems/test_gem_resolver.rb
@@ -86,63 +86,6 @@ class TestGemResolver < Gem::TestCase
assert_same index_set, composed
end
- def test_requests
- a1 = util_spec "a", 1, "b" => 2
-
- r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
-
- act = Gem::Resolver::ActivationRequest.new a1, r1
-
- res = Gem::Resolver.new [a1]
-
- reqs = []
-
- res.requests a1, act, reqs
-
- assert_equal ["b (= 2)"], reqs.map(&:to_s)
- end
-
- def test_requests_development
- a1 = util_spec "a", 1, "b" => 2
-
- spec = Gem::Resolver::SpecSpecification.new nil, a1
- def spec.fetch_development_dependencies
- @called = true
- end
-
- r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
-
- act = Gem::Resolver::ActivationRequest.new spec, r1
-
- res = Gem::Resolver.new [act]
- res.development = true
-
- reqs = []
-
- res.requests spec, act, reqs
-
- assert_equal ["b (= 2)"], reqs.map(&:to_s)
-
- assert spec.instance_variable_defined? :@called
- end
-
- def test_requests_ignore_dependencies
- a1 = util_spec "a", 1, "b" => 2
-
- r1 = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
-
- act = Gem::Resolver::ActivationRequest.new a1, r1
-
- res = Gem::Resolver.new [a1]
- res.ignore_dependencies = true
-
- reqs = []
-
- res.requests a1, act, reqs
-
- assert_empty reqs
- end
-
def test_resolve_conservative
a1_spec = util_spec "a", 1
@@ -197,6 +140,34 @@ class TestGemResolver < Gem::TestCase
assert_resolves_to [a2_spec, b2_spec, c1_spec, d2_spec, e1_spec], res
end
+ def test_conservative_upgrades_when_installed_blocked
+ # Conservative mode floats the installed (skip) version to the front but
+ # keeps newer versions selectable. When the installed version cannot be
+ # used because its own dependency is unsatisfiable, the solver backtracks
+ # to a newer version instead of failing. This intentionally diverges from
+ # Molinillo (which hard-restricted to skip versions and raised) and reaches
+ # Bundler's upgrade-over-raise outcome. See the comment in
+ # Gem::Resolver#all_versions_for.
+ a1_spec = util_spec "a", 1 do |s|
+ s.add_dependency "b", ">= 2"
+ end
+ a2_spec = util_spec "a", 2 do |s|
+ s.add_dependency "b", ">= 1"
+ end
+ b1_spec = util_spec "b", 1
+
+ # b-2 is intentionally absent, so a-1's `b >= 2` cannot be satisfied.
+ deps = [make_dep("a", ">= 1")]
+ s = set a1_spec, a2_spec, b1_spec
+
+ res = Gem::Resolver.new deps, s
+ # a-1 is already installed and satisfies `a >= 1`, so conservative mode
+ # prefers it - but it is blocked by the missing b-2, forcing an upgrade.
+ res.skip_gems = { "a" => [a1_spec] }
+
+ assert_resolves_to [a2_spec, b1_spec], res
+ end
+
def test_resolve_development
a_spec = util_spec "a", 1 do |s|
s.add_development_dependency "b"
@@ -511,19 +482,10 @@ class TestGemResolver < Gem::TestCase
r.resolve
end
- deps = [make_dep("c", "= 2"), make_dep("c", "= 1")]
- assert_equal deps, e.conflicting_dependencies
-
- con = e.conflict
-
- act = con.activated
- assert_equal "c-1", act.spec.full_name
-
- parent = act.parent
- assert_equal "a-1", parent.spec.full_name
-
- act = con.requester
- assert_equal "b-1", act.spec.full_name
+ assert_nil e.conflict
+ assert_match(/your request/, e.message)
+ assert_match(/a depends on c/, e.message)
+ assert_match(/b depends on c/, e.message)
end
def test_raises_when_a_gem_is_missing
@@ -578,12 +540,11 @@ class TestGemResolver < Gem::TestCase
r = Gem::Resolver.new([ad], set(a1))
- e = assert_raise Gem::UnsatisfiableDependencyError do
+ e = assert_raise Gem::DependencyResolutionError do
r.resolve
end
- assert_equal "Unable to resolve dependency: 'a (= 1)' requires 'b (= 2)'",
- e.message
+ assert_match(/depends on b = 2 which could not be found in any repository/, e.message)
end
def test_raises_when_possibles_are_exhausted
@@ -605,18 +566,9 @@ class TestGemResolver < Gem::TestCase
r.resolve
end
- dependency = e.conflict.dependency
-
- assert_includes %w[a b], dependency.name
- assert_equal req(">= 0"), dependency.requirement
-
- activated = e.conflict.activated
- assert_equal "c-1", activated.full_name
-
- assert_equal dep("c", "= 1"), activated.request.dependency
-
- assert_equal [dep("c", ">= 2"), dep("c", "= 1")],
- e.conflict.conflicting_dependencies
+ assert_nil e.conflict
+ assert_match(/a depends on c/, e.message)
+ assert_match(/b depends on c/, e.message)
end
def test_keeps_resolving_after_seeing_satisfied_dep
@@ -772,7 +724,7 @@ class TestGemResolver < Gem::TestCase
assert_resolves_to [b1, c1, d2], r
end
- def test_sorts_by_source_then_version
+ def test_picks_highest_version_across_sources
source_a = Gem::Source.new "http://example.com/a"
source_b = Gem::Source.new "http://example.com/b"
source_c = Gem::Source.new "http://example.com/c"
@@ -795,7 +747,43 @@ class TestGemResolver < Gem::TestCase
resolver = Gem::Resolver.new [dependency], set
- assert_resolves_to [spec_b_2], resolver
+ assert_resolves_to [spec_a_2], resolver
+ end
+
+ def test_same_version_prefers_earlier_source
+ source_a = Gem::Source.new "http://example.com/a"
+ source_b = Gem::Source.new "http://example.com/b"
+
+ spec_a = util_spec "some-dep", "1.0.0"
+ spec_b = util_spec "some-dep", "1.0.0"
+
+ set = StaticSet.new [
+ Gem::Resolver::SpecSpecification.new(nil, spec_a, source_a),
+ Gem::Resolver::SpecSpecification.new(nil, spec_b, source_b),
+ ]
+
+ resolver = Gem::Resolver.new [make_dep("some-dep", "> 0")], set
+ result = resolver.resolve
+
+ assert_equal source_a, result.first.spec.source
+ end
+
+ def test_same_version_prefers_earlier_source_when_order_flipped
+ source_a = Gem::Source.new "http://example.com/a"
+ source_b = Gem::Source.new "http://example.com/b"
+
+ spec_a = util_spec "some-dep", "1.0.0"
+ spec_b = util_spec "some-dep", "1.0.0"
+
+ set = StaticSet.new [
+ Gem::Resolver::SpecSpecification.new(nil, spec_b, source_b),
+ Gem::Resolver::SpecSpecification.new(nil, spec_a, source_a),
+ ]
+
+ resolver = Gem::Resolver.new [make_dep("some-dep", "> 0")], set
+ result = resolver.resolve
+
+ assert_equal source_b, result.first.spec.source
end
def test_select_local_platforms
@@ -850,4 +838,338 @@ class TestGemResolver < Gem::TestCase
assert_match "No match for 'a (= 1)' on this platform. Found: c-p-1",
e.message
end
+
+ def test_resolve_prerelease_not_considered_when_stable_exists
+ # a-1.0 depends on b ~> 2.0 - only b-2.0.pre satisfies that, but
+ # b also has a stable version (1.0), so prereleases are filtered out.
+ # The resolver must fail, not silently use b-2.0.pre during propagation.
+ a_stable = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", "~> 2.0"
+ end
+
+ b_stable = util_spec "b", "1.0"
+ b_pre = util_spec "b", "2.0.pre"
+
+ s = set(a_stable, b_stable, b_pre)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+ end
+
+ def test_resolve_prerelease_considered_when_enabled
+ a_stable = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+
+ b_pre = util_spec "b", "2.0.pre"
+
+ s = set(a_stable, b_pre)
+ s.prerelease = true
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a_stable, b_pre], r
+ end
+
+ def test_resolve_prerelease_used_when_no_stable_versions_exist
+ a_stable = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+
+ b_pre = util_spec "b", "2.0.pre"
+ b_other_pre = util_spec "b", "1.0.pre"
+
+ s = set(a_stable, b_pre, b_other_pre)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a_stable, b_pre], r
+ end
+
+ def test_resolve_prerelease_required_by_exact_requirement
+ # A root dep with an exact prerelease version must resolve to that
+ # version even when stable versions of the same gem are in the set.
+ # Gem.finish_resolve hits this: it imports loaded_specs as exact-version
+ # deps, so the currently-activated prerelease bundler becomes a root dep.
+ a_stable = util_spec "a", "1.0"
+ a_pre = util_spec "a", "2.0.pre"
+
+ s = set(a_stable, a_pre)
+
+ ad = make_dep "a", "= 2.0.pre"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a_pre], r
+ end
+
+ def test_resolve_transitive_prerelease_required_by_exact_requirement
+ # A transitive dep with an exact prerelease version must resolve to that
+ # version even when stable versions of the same gem are in the set.
+ # The gate on prereleases lives in versions_for and is per-constraint:
+ # `= 2.0.pre` carries a prerelease bound, so prereleases are admitted for
+ # this range even though the global prerelease flag is off.
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", "= 2.0.pre"
+ end
+
+ b_stable = util_spec "b", "1.0"
+ b_pre = util_spec "b", "2.0.pre"
+
+ s = set(a, b_stable, b_pre)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a, b_pre], r
+ end
+
+ def test_error_includes_platform_hint_when_specs_exist_for_other_platforms
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+
+ b_foreign = util_spec "b", "1.0" do |s|
+ s.platform = "java"
+ end
+
+ s = set(a, b_foreign)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/could not be found in any repository/, e.message)
+ assert_match(/b-1.0-java/, e.message)
+ end
+
+ def test_error_includes_ruby_version_hint_when_filtered
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+
+ b = util_spec "b", "1.0" do |s|
+ s.required_ruby_version = ">= 999.0"
+ end
+
+ s = set(a, b)
+
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/requires Ruby/, e.message)
+ assert_match(/you have/, e.message)
+ end
+
+ def test_root_gem_incompatible_ruby_version_names_ruby_requirement
+ # A requested (root) gem available only for an incompatible Ruby version
+ # flows through the solver to a DependencyResolutionError whose message
+ # names the Ruby requirement. This matches Bundler (which models Ruby as a
+ # synthetic dependency and reports a solve failure) and is clearer than the
+ # platform-oriented UnsatisfiableDependencyError. Contrast the foreign-
+ # *platform* case (test_raises_and_explains_when_platform_prevents_install),
+ # which is genuinely "not found" and does raise UnsatisfiableDependencyError.
+ a = util_spec "a", "1.0" do |s|
+ s.required_ruby_version = ">= 999.0"
+ end
+
+ ad = make_dep "a", "= 1.0"
+ r = Gem::Resolver.new([ad], set(a))
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/requires Ruby >= 999.0/, e.message)
+ end
+
+ def test_self_dependency_does_not_crash
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "a"
+ end
+
+ s = set(a)
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ assert_resolves_to [a], r
+ end
+
+ def test_contradictory_root_requirements_give_clear_error
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2"
+
+ s = set(a1, a2)
+ r = Gem::Resolver.new([make_dep("a", "= 1"), make_dep("a", "= 2")], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/contradictory/, e.message)
+ refute_match(/unknown package/, e.message)
+ end
+
+ def test_empty_range_transitive_dep_does_not_say_unknown
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", "> 2", "< 1"
+ end
+
+ b = util_spec "b", "1.5"
+
+ s = set(a, b)
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/contradictory/, e.message)
+ refute_match(/unknown package/, e.message)
+ end
+
+ def test_error_hints_about_prerelease_when_filtered
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", "~> 2.0"
+ end
+
+ b_stable = util_spec "b", "1.0"
+ b_pre = util_spec "b", "2.0.pre"
+
+ s = set(a, b_stable, b_pre)
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/pre-release/, e.message)
+ assert_match(/--prerelease/, e.message)
+ end
+
+ def test_soft_missing_skips_dep_with_wrong_version
+ a = util_spec "a", "1.0" do |s|
+ s.add_dependency "b", ">= 2.0"
+ end
+
+ b = util_spec "b", "1.0"
+
+ s = set(a, b)
+ ad = make_dep "a"
+ r = Gem::Resolver.new([ad], s)
+ r.soft_missing = true
+
+ # b exists but only 1.0, which doesn't satisfy >= 2.0.
+ # With soft_missing (--force), the dep should be skipped.
+ assert_resolves_to [a], r
+ end
+
+ def test_backtracks_to_clean_sibling_when_higher_version_has_missing_dep
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2))
+
+ # 'zzz' has zero specs anywhere, so a-2 is unusable, but a-1 is clean
+ # and resolution must backtrack to it rather than declaring every
+ # version of 'a' invalid.
+ assert_resolves_to [a1], r
+ end
+
+ def test_backtracks_over_band_of_bad_high_versions_to_clean_lower
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+ a3 = util_spec "a", "3" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2, a3))
+
+ # Only the a-2..a-3 band shares the missing 'zzz' dep and should be
+ # eliminated; band scoping is load-bearing here, not just sibling
+ # presence.
+ assert_resolves_to [a1], r
+ end
+
+ def test_backtracks_when_one_of_several_deps_is_missing
+ good = util_spec "good", "1"
+ a1 = util_spec "a", "1" do |s|
+ s.add_dependency "good", ">= 1"
+ end
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "good", ">= 1"
+ s.add_dependency "zzz", ">= 1"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2, good))
+
+ # Only a-2, which carries the missing 'zzz' dep, is eliminated; the
+ # per-dep check inside a multi-dep version must not poison a-1.
+ assert_resolves_to [a1, good], r
+ end
+
+ def test_fails_when_every_version_depends_on_missing_package
+ a1 = util_spec "a", "1" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2))
+
+ e = assert_raise Gem::DependencyResolutionError do
+ r.resolve
+ end
+
+ assert_match(/every version of a depends on zzz >= 1 which could not be found in any repository/, e.message)
+ end
+
+ def test_resolves_when_only_lowest_version_has_missing_dep
+ a1 = util_spec "a", "1" do |s|
+ s.add_dependency "zzz", ">= 1"
+ end
+ a2 = util_spec "a", "2"
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2))
+
+ # a-2 is preferred/tried first, so this is already green; it guards
+ # against the bug being re-introduced in an order-sensitive way.
+ assert_resolves_to [a2], r
+ end
+
+ def test_filtered_platform_dep_lets_clean_sibling_backtrack
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2" do |s|
+ s.add_dependency "b", ">= 1.0"
+ end
+ b_java = util_spec "b", "1.0" do |s|
+ s.platform = "java"
+ end
+
+ r = Gem::Resolver.new([make_dep("a")], set(a1, a2, b_java))
+
+ # 'b' EXISTS in the unfiltered specs but is platform-filtered, so a-2
+ # is unusable via NoVersions (not InvalidDependency). Resolution must
+ # backtrack to the clean a-1 rather than eliminating it.
+ assert_resolves_to [a1], r
+ end
end
diff --git a/test/rubygems/test_gem_resolver_best_set.rb b/test/rubygems/test_gem_resolver_best_set.rb
index 02f542efc0..ac186884d1 100644
--- a/test/rubygems/test_gem_resolver_best_set.rb
+++ b/test/rubygems/test_gem_resolver_best_set.rb
@@ -31,6 +31,20 @@ class TestGemResolverBestSet < Gem::TestCase
assert_equal %w[a-1], found.map(&:full_name)
end
+ def test_pick_sets_prerelease
+ set = Gem::Resolver::BestSet.new
+ set.prerelease = true
+
+ set.pick_sets
+
+ sets = set.sets
+
+ assert_equal 1, sets.count
+
+ source_set = sets.first
+ assert_equal true, source_set.prerelease
+ end
+
def test_find_all_local
spec_fetcher do |fetcher|
fetcher.spec "a", 1
diff --git a/test/rubygems/test_gem_resolver_conflict.rb b/test/rubygems/test_gem_resolver_conflict.rb
deleted file mode 100644
index 5696ff266d..0000000000
--- a/test/rubygems/test_gem_resolver_conflict.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "helper"
-
-class TestGemResolverConflict < Gem::TestCase
- def test_explanation
- root =
- dependency_request dep("net-ssh", ">= 2.0.13"), "rye", "0.9.8"
- child =
- dependency_request dep("net-ssh", ">= 2.6.5"), "net-ssh", "2.2.2", root
-
- dep = Gem::Resolver::DependencyRequest.new dep("net-ssh", ">= 2.0.13"), nil
-
- spec = util_spec "net-ssh", "2.2.2"
- active =
- Gem::Resolver::ActivationRequest.new spec, dep
-
- conflict =
- Gem::Resolver::Conflict.new child, active
-
- expected = <<-EXPECTED
- Activated net-ssh-2.2.2
- which does not match conflicting dependency (>= 2.6.5)
-
- Conflicting dependency chains:
- net-ssh (>= 2.0.13), 2.2.2 activated
-
- versus:
- rye (= 0.9.8), 0.9.8 activated, depends on
- net-ssh (>= 2.0.13), 2.2.2 activated, depends on
- net-ssh (>= 2.6.5)
-
- EXPECTED
-
- assert_equal expected, conflict.explanation
- end
-
- def test_explanation_user_request
- spec = util_spec "a", 2
-
- a1_req = Gem::Resolver::DependencyRequest.new dep("a", "= 1"), nil
- a2_req = Gem::Resolver::DependencyRequest.new dep("a", "= 2"), nil
-
- activated = Gem::Resolver::ActivationRequest.new spec, a2_req
-
- conflict = Gem::Resolver::Conflict.new a1_req, activated
-
- expected = <<-EXPECTED
- Activated a-2
- which does not match conflicting dependency (= 1)
-
- Conflicting dependency chains:
- a (= 2), 2 activated
-
- versus:
- a (= 1)
-
- EXPECTED
-
- assert_equal expected, conflict.explanation
- end
-
- def test_request_path
- root =
- dependency_request dep("net-ssh", ">= 2.0.13"), "rye", "0.9.8"
-
- child =
- dependency_request dep("other", ">= 1.0"), "net-ssh", "2.2.2", root
-
- conflict =
- Gem::Resolver::Conflict.new nil, nil
-
- expected = [
- "net-ssh (>= 2.0.13), 2.2.2 activated",
- "rye (= 0.9.8), 0.9.8 activated",
- ]
-
- assert_equal expected, conflict.request_path(child.requester)
- end
-end
diff --git a/test/rubygems/test_gem_resolver_git_specification.rb b/test/rubygems/test_gem_resolver_git_specification.rb
index 621333d3bf..e03c61e27d 100644
--- a/test/rubygems/test_gem_resolver_git_specification.rb
+++ b/test/rubygems/test_gem_resolver_git_specification.rb
@@ -97,6 +97,44 @@ class TestGemResolverGitSpecification < Gem::TestCase
assert_path_exist File.join git_spec.spec.extension_dir, "b.rb"
end
+ def test_install_no_build_extension
+ pend if Gem.java_platform?
+ pend "terminates on mswin" if vc_windows? && ruby_repo?
+ name, _, repository, = git_gem "a", 1 do |s|
+ s.extensions << "ext/extconf.rb"
+ end
+
+ Dir.chdir "git/a" do
+ FileUtils.mkdir_p "ext/lib"
+
+ File.open "ext/extconf.rb", "w" do |io|
+ io.puts 'require "mkmf"'
+ io.puts 'create_makefile "a"'
+ end
+
+ FileUtils.touch "ext/lib/b.rb"
+
+ system @git, "add", "ext/extconf.rb"
+ system @git, "add", "ext/lib/b.rb"
+
+ system @git, "commit", "--quiet", "-m", "Add extension files"
+ end
+
+ source = Gem::Source::Git.new name, repository, nil, true
+
+ spec = source.specs.first
+
+ git_spec = Gem::Resolver::GitSpecification.new @set, spec, source
+
+ use_ui @ui do
+ git_spec.install(build_extension: false)
+ end
+
+ assert_path_not_exist File.join(git_spec.spec.extension_dir, "b.rb")
+ assert_match "contains native extensions that were not built", @ui.error
+ assert_match "gem pristine #{git_spec.spec.name} --extensions", @ui.error
+ end
+
def test_install_installed
git_gem "a", 1
diff --git a/test/rubygems/test_gem_resolver_strategy.rb b/test/rubygems/test_gem_resolver_strategy.rb
new file mode 100644
index 0000000000..57c9aadde8
--- /dev/null
+++ b/test/rubygems/test_gem_resolver_strategy.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+
+class TestGemResolverStrategy < Gem::TestCase
+ # Minimal source that implements the two methods Strategy calls:
+ # all_versions_for(package) - returns versions in preference order
+ # versions_for(package, range) - returns versions matching a range
+ #
+ # Tracks call counts so we can assert on caching behavior.
+ class StubSource
+ attr_reader :versions_for_calls
+
+ def initialize(versions_by_package)
+ @versions_by_package = versions_by_package
+ @versions_for_calls = 0
+ end
+
+ def all_versions_for(package)
+ @versions_by_package.fetch(package.to_s, [])
+ end
+
+ def versions_for(package, range)
+ @versions_for_calls += 1
+ all = @versions_by_package.fetch(package.to_s, [])
+ all.select {|v| range.include?(v) }
+ end
+ end
+
+ def v(version_string)
+ Gem::Version.new(version_string)
+ end
+
+ def make_package(name)
+ Gem::PubGrub::Package.new(name)
+ end
+
+ def make_range_any
+ Gem::PubGrub::VersionRange.any
+ end
+
+ # A range >= min (unbounded above)
+ def make_range_gte(version)
+ Gem::PubGrub::VersionRange.new(min: version, include_min: true)
+ end
+
+ # A range >= min AND < max
+ def make_range_between(min, max)
+ Gem::PubGrub::VersionRange.new(
+ min: min, max: max,
+ include_min: true, include_max: false
+ )
+ end
+
+ def test_most_preferred_version_respects_all_versions_for_ordering
+ # all_versions_for returns [2.0, 1.0, 3.0] - so 2.0 is most preferred
+ # even though 3.0 is numerically highest.
+ pkg = make_package("a")
+ source = StubSource.new("a" => [v("2.0"), v("1.0"), v("3.0")])
+
+ strategy = Gem::Resolver::Strategy.new(source)
+ unsatisfied = { pkg => make_range_any }
+
+ _package, version = strategy.next_package_and_version(unsatisfied)
+
+ assert_equal v("2.0"), version
+ end
+
+ def test_picks_most_constrained_package
+ # "a" has 3 matching versions, "b" has 1 matching version.
+ # Strategy should pick "b" because it's more constrained.
+ pkg_a = make_package("a")
+ pkg_b = make_package("b")
+
+ source = StubSource.new(
+ "a" => [v("3.0"), v("2.0"), v("1.0")],
+ "b" => [v("1.0")]
+ )
+
+ strategy = Gem::Resolver::Strategy.new(source)
+
+ unsatisfied = {
+ pkg_a => make_range_any,
+ pkg_b => make_range_any,
+ }
+
+ package, _version = strategy.next_package_and_version(unsatisfied)
+
+ assert_equal pkg_b, package
+ end
+
+ def test_picks_package_with_fewer_higher_versions_as_tiebreaker
+ # Both "a" and "b" have 2 matching versions (so both get priority [1, ...]).
+ # "a" has matching [2.0, 1.0] with higher (above range) = [] (0 higher)
+ # "b" has matching [2.0, 1.0] with higher [3.0] (1 higher)
+ # Tiebreaker: fewer higher versions wins, so "a" is picked.
+ pkg_a = make_package("a")
+ pkg_b = make_package("b")
+
+ range = make_range_between(v("0.5"), v("2.5"))
+
+ source = StubSource.new(
+ "a" => [v("2.0"), v("1.0")],
+ "b" => [v("3.0"), v("2.0"), v("1.0")]
+ )
+
+ strategy = Gem::Resolver::Strategy.new(source)
+
+ unsatisfied = {
+ pkg_a => range,
+ pkg_b => range,
+ }
+
+ package, _version = strategy.next_package_and_version(unsatisfied)
+
+ assert_equal pkg_a, package
+ end
+
+ def test_cache_prevents_redundant_versions_for_calls
+ pkg = make_package("a")
+ source = StubSource.new("a" => [v("2.0"), v("1.0")])
+
+ strategy = Gem::Resolver::Strategy.new(source)
+
+ range = make_range_any
+ unsatisfied = { pkg => range }
+
+ # First call: should call versions_for for matching + upper_invert + most_preferred
+ strategy.next_package_and_version(unsatisfied)
+ calls_after_first = source.versions_for_calls
+
+ # Second call with same package+range: next_term_to_try_from should
+ # hit the cache, so only most_preferred_version_of adds a call.
+ strategy.next_package_and_version(unsatisfied)
+ calls_after_second = source.versions_for_calls
+
+ # The cached path saves the 2 calls in next_term_to_try_from,
+ # so only the 1 call from most_preferred_version_of is added.
+ assert_equal 1, calls_after_second - calls_after_first
+ end
+
+ def test_cache_is_keyed_by_package_and_range
+ pkg = make_package("a")
+ source = StubSource.new("a" => [v("3.0"), v("2.0"), v("1.0")])
+
+ strategy = Gem::Resolver::Strategy.new(source)
+
+ range_any = make_range_any
+ range_gte = make_range_gte(v("2.0"))
+
+ # First call with range_any
+ strategy.next_package_and_version({ pkg => range_any })
+ calls_after_first = source.versions_for_calls
+
+ # Second call with different range - cache miss, so versions_for is called again
+ strategy.next_package_and_version({ pkg => range_gte })
+ calls_after_second = source.versions_for_calls
+
+ # A cache miss means 2 new versions_for calls (matching + upper_invert)
+ # plus 1 from most_preferred_version_of = 3 total new calls
+ assert_equal 3, calls_after_second - calls_after_first
+ end
+end
diff --git a/test/rubygems/test_gem_safe_marshal.rb b/test/rubygems/test_gem_safe_marshal.rb
index deeb8205bc..7e3a046c4e 100644
--- a/test/rubygems/test_gem_safe_marshal.rb
+++ b/test/rubygems/test_gem_safe_marshal.rb
@@ -234,8 +234,6 @@ class TestGemSafeMarshal < Gem::TestCase
end
def test_link_after_float
- pend "Marshal.load of links and floats is broken on truffleruby, see https://github.com/oracle/truffleruby/issues/3747" if RUBY_ENGINE == "truffleruby"
-
a = []
a << a
assert_safe_load_as [0.0, a, 1.0, a]
@@ -254,6 +252,8 @@ class TestGemSafeMarshal < Gem::TestCase
end
def test_hash_with_compare_by_identity
+ pend "Marshal.dump of a compare_by_identity Hash emits an unexpected ivar on jruby" if RUBY_ENGINE == "jruby"
+
with_const(Gem::SafeMarshal, :PERMITTED_CLASSES, %w[Hash]) do
assert_safe_load_as Hash.new.compare_by_identity.tap {|h|
h[+"a"] = 1
diff --git a/test/rubygems/test_gem_safe_yaml.rb b/test/rubygems/test_gem_safe_yaml.rb
index 02df9f97da..8d0ac63c41 100644
--- a/test/rubygems/test_gem_safe_yaml.rb
+++ b/test/rubygems/test_gem_safe_yaml.rb
@@ -5,6 +5,28 @@ require_relative "helper"
Gem.load_yaml
class TestGemSafeYAML < Gem::TestCase
+ def yaml_load(input, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES,
+ permitted_symbols: Gem::SafeYAML::PERMITTED_SYMBOLS,
+ aliases: true)
+ if Gem.use_psych?
+ Psych.safe_load(input, permitted_classes: permitted_classes,
+ permitted_symbols: permitted_symbols,
+ aliases: aliases)
+ else
+ Gem::YAMLSerializer.load(input, permitted_classes: permitted_classes,
+ permitted_symbols: permitted_symbols,
+ aliases: aliases)
+ end
+ end
+
+ def yaml_dump(obj)
+ if Gem.use_psych?
+ obj.to_yaml
+ else
+ Gem::YAMLSerializer.dump(obj)
+ end
+ end
+
def test_aliases_enabled_by_default
assert_predicate Gem::SafeYAML, :aliases_enabled?
assert_equal({ "a" => "a", "b" => "a" }, Gem::SafeYAML.safe_load("a: &a a\nb: *a\n"))
@@ -21,4 +43,1284 @@ class TestGemSafeYAML < Gem::TestCase
ensure
Gem::SafeYAML.aliases_enabled = aliases_enabled
end
+
+ def test_specification_version_is_integer
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ specification_version: 4
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Integer, spec.specification_version
+ assert_equal 4, spec.specification_version
+ end
+
+ def test_disallowed_class_rejected
+ yaml = <<~YAML
+ --- !ruby/object:SomeDisallowedClass
+ foo: bar
+ YAML
+
+ exception = assert_raise(Psych::DisallowedClass) do
+ Gem::SafeYAML.safe_load(yaml)
+ end
+ assert_match(/unspecified class/, exception.message)
+ end
+
+ def test_plain_tag_key_does_not_construct_specification
+ yaml = <<~YAML
+ tag: "!ruby/object:Gem::Specification"
+ name: pwned
+ arbitrary_ivar: hello
+ YAML
+
+ result = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Hash, result
+ assert_equal "!ruby/object:Gem::Specification", result["tag"]
+ assert_equal "pwned", result["name"]
+ end
+
+ def test_disallowed_symbol_rejected
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Dependency
+ name: test
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0
+ type: :invalid_type
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0
+ YAML
+
+ exception = assert_raise(Psych::DisallowedClass) do
+ Gem::SafeYAML.safe_load(yaml)
+ end
+ assert_match(/unspecified class/, exception.message)
+ end
+
+ def test_disallowed_symbol_not_interned
+ unique = "rejected_symbol_#{rand(1 << 30)}"
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Dependency
+ name: test
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0
+ type: :#{unique}
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 0
+ YAML
+
+ assert_raise(Psych::DisallowedClass) do
+ Gem::YAMLSerializer.load(yaml,
+ permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES,
+ permitted_symbols: Gem::SafeYAML::PERMITTED_SYMBOLS)
+ end
+ refute_includes Symbol.all_symbols.map(&:to_s), unique
+ end
+
+ def test_inline_array_nesting_capped
+ depth = Gem::YAMLSerializer::Parser::MAX_NESTING_DEPTH + 1
+ yaml = "x: " + ("[" * depth) + "a" + ("]" * depth) + "\n"
+
+ expected = [Psych::SyntaxError]
+ # JRuby's JVM stack overflows before the Ruby-level nesting cap fires.
+ expected << ::Java::JavaLang::StackOverflowError if RUBY_ENGINE == "jruby"
+
+ assert_raise(*expected) do
+ Gem::YAMLSerializer.load(yaml, permitted_classes: [])
+ end
+ end
+
+ def test_unknown_alias_raises
+ yaml = <<~YAML
+ foo: 1
+ bar: *missing
+ YAML
+
+ expected_error = defined?(Psych::AnchorNotDefined) ? Psych::AnchorNotDefined : Psych::BadAlias
+ assert_raise(expected_error) { Gem::SafeYAML.safe_load(yaml) }
+ end
+
+ def test_unused_anchor_with_aliases_disabled_is_allowed
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = false
+
+ result = Gem::SafeYAML.safe_load("foo: &unused 1\nbar: 2\n")
+ assert_equal({ "foo" => 1, "bar" => 2 }, result)
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_yaml_serializer_aliases_disabled
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = false
+ refute_predicate Gem::SafeYAML, :aliases_enabled?
+
+ yaml = "a: &anchor value\nb: *anchor\n"
+
+ assert_raise(Psych::AliasesNotEnabled) do
+ Gem::SafeYAML.safe_load(yaml)
+ end
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_real_gemspec_fileutils
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: fileutils
+ version: !ruby/object:Gem::Version
+ version: 1.8.0
+ platform: ruby
+ authors:
+ - Minero Aoki
+ bindir: bin
+ cert_chain: []
+ date: 1980-01-02 00:00:00.000000000 Z
+ dependencies: []
+ description: Several file utility methods for copying, moving, removing, etc.
+ email:
+ -
+ executables: []
+ extensions: []
+ extra_rdoc_files: []
+ files:
+ - BSDL
+ - COPYING
+ - README.md
+ - Rakefile
+ - fileutils.gemspec
+ - lib/fileutils.rb
+ homepage: https://github.com/ruby/fileutils
+ licenses:
+ - Ruby
+ - BSD-2-Clause
+ metadata:
+ source_code_uri: https://github.com/ruby/fileutils
+ rdoc_options: []
+ require_paths:
+ - lib
+ required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 2.5.0
+ required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ requirements: []
+ rubygems_version: 3.6.9
+ specification_version: 4
+ summary: Several file utility methods for copying, moving, removing, etc.
+ test_files: []
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "fileutils", spec.name
+ assert_equal Gem::Version.new("1.8.0"), spec.version
+ assert_kind_of Integer, spec.specification_version
+ assert_equal 4, spec.specification_version
+ end
+
+ def test_yaml_anchor_and_alias_enabled
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ yaml = <<~YAML
+ dependencies:
+ - &req !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ - *req
+ YAML
+
+ result = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Hash, result
+ assert_kind_of Array, result["dependencies"]
+ assert_equal 2, result["dependencies"].size
+ assert_kind_of Gem::Requirement, result["dependencies"][0]
+ assert_kind_of Gem::Requirement, result["dependencies"][1]
+ assert_equal result["dependencies"][0].requirements, result["dependencies"][1].requirements
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_real_gemspec_rubygems_bundler
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: rubygems-bundler
+ version: !ruby/object:Gem::Version
+ version: 1.4.5
+ platform: ruby
+ authors:
+ - Josh Hull
+ - Michal Papis
+ autorequire:
+ bindir: bin
+ cert_chain: []
+ date: 2018-06-24 00:00:00.000000000 Z
+ dependencies:
+ - !ruby/object:Gem::Dependency
+ name: bundler-unload
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 1.0.2
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 1.0.2
+ description: Stop using bundle exec.
+ email:
+ - joshbuddy@gmail.com
+ - mpapis@gmail.com
+ executables: []
+ extensions: []
+ extra_rdoc_files: []
+ files:
+ - ".gem.config"
+ homepage: http://mpapis.github.com/rubygems-bundler
+ licenses:
+ - Apache-2.0
+ metadata: {}
+ post_install_message:
+ rdoc_options: []
+ require_paths:
+ - lib
+ required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ rubyforge_project:
+ rubygems_version: 2.7.6
+ signing_key:
+ specification_version: 4
+ summary: Stop using bundle exec
+ test_files: []
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "rubygems-bundler", spec.name
+ assert_equal Gem::Version.new("1.4.5"), spec.version
+ assert_equal 1, spec.dependencies.size
+
+ dep = spec.dependencies.first
+ assert_equal "bundler-unload", dep.name
+ assert_kind_of Gem::Requirement, dep.requirement
+ assert_kind_of Gem::Requirement, dep.instance_variable_get(:@version_requirements)
+ assert_equal dep.requirement.requirements, [[">=", Gem::Version.new("1.0.2")]]
+
+ # Empty fields should be nil
+ assert_nil spec.autorequire
+ assert_nil spec.post_install_message
+
+ # Metadata should be empty hash
+ assert_equal({}, spec.metadata)
+
+ # specification_version should be Integer
+ assert_kind_of Integer, spec.specification_version
+ assert_equal 4, spec.specification_version
+ end
+
+ def test_empty_requirements_array
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ dependencies:
+ - !ruby/object:Gem::Dependency
+ name: foo
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ type: :runtime
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "test", spec.name
+ assert_equal 1, spec.dependencies.size
+
+ dep = spec.dependencies.first
+ assert_equal "foo", dep.name
+ assert_kind_of Gem::Requirement, dep.requirement
+
+ reqs = dep.requirement.instance_variable_get(:@requirements)
+ assert_nil reqs
+ end
+
+ def test_requirements_hash_converted_to_array
+ # Malformed YAML where requirements is a Hash instead of Array
+ yaml = <<~YAML
+ !ruby/object:Gem::Requirement
+ requirements:
+ foo: bar
+ YAML
+
+ req = yaml_load(yaml, permitted_classes: ["Gem::Requirement"])
+ assert_kind_of Gem::Requirement, req
+
+ reqs = req.instance_variable_get(:@requirements)
+ assert_kind_of Hash, reqs
+ end
+
+ def test_requirement_quote
+ yaml = <<~YAML
+ requirements:
+ - "system: arrow-glib>=25.0.0: amazon_linux: arrow-glib-devel"
+ - 'system: arrow-glib>=25.0.0: fedora: libarrow-glib-devel'
+ YAML
+
+ expected = [
+ "system: arrow-glib>=25.0.0: amazon_linux: arrow-glib-devel",
+ "system: arrow-glib>=25.0.0: fedora: libarrow-glib-devel",
+ ]
+ assert_equal expected, yaml_load(yaml)["requirements"]
+ end
+
+ def test_rdoc_options_hash_converted_to_array
+ # Some gemspecs incorrectly have rdoc_options: {} instead of rdoc_options: []
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test-gem
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ rdoc_options: {}
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "test-gem", spec.name
+
+ assert_equal [], spec.rdoc_options
+ end
+
+ def test_load_returns_nil_for_comment_only_yaml
+ # Bundler config files may contain only comments after deleting all keys
+ result = yaml_load("---\n# BUNDLE_FOO: \"bar\"\n")
+ assert_nil result
+ end
+
+ def test_load_returns_nil_for_empty_document
+ assert_nil yaml_load("---\n")
+ assert_nil yaml_load("")
+ assert_raise(TypeError) { yaml_load(nil) }
+ end
+
+ def test_load_returns_hash_for_flow_empty_hash
+ # yaml_dump({}) produces "--- {}\n"
+ result = yaml_load("--- {}\n")
+ assert_kind_of Hash, result
+ assert_empty result
+ end
+
+ def test_load_parses_flow_empty_hash_as_value
+ result = yaml_load("metadata: {}\n")
+ assert_kind_of Hash, result
+ assert_kind_of Hash, result["metadata"]
+ assert_empty result["metadata"]
+ end
+
+ def test_yaml_non_specific_tag_stripped
+ # Legacy RubyGems (1.x) generated YAML with ! non-specific tags like:
+ # - ! '>='
+ # The ! prefix should be ignored.
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: legacy-gem
+ version: !ruby/object:Gem::Version
+ version: 0.1.0
+ required_ruby_version: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+ required_rubygems_version: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: 1.3.5
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "legacy-gem", spec.name
+ assert_equal Gem::Requirement.new(">= 0"), spec.required_ruby_version
+ assert_equal Gem::Requirement.new(">= 1.3.5"), spec.required_rubygems_version
+ end
+
+ def test_legacy_gemspec_with_anchors_and_non_specific_tags
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ # Real-world pattern from gems like vegas-0.1.11 that combine
+ # YAML anchors/aliases with ! non-specific tags
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: legacy-gem
+ version: !ruby/object:Gem::Version
+ version: 0.1.11
+ dependencies:
+ - !ruby/object:Gem::Dependency
+ name: rack
+ requirement: &id001 !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: 1.0.0
+ type: :runtime
+ prerelease: false
+ version_requirements: *id001
+ - !ruby/object:Gem::Dependency
+ name: mocha
+ requirement: &id002 !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 0.9.8
+ type: :development
+ prerelease: false
+ version_requirements: *id002
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "legacy-gem", spec.name
+
+ assert_equal 2, spec.dependencies.size
+
+ rack_dep = spec.dependencies.find {|d| d.name == "rack" }
+ assert_kind_of Gem::Dependency, rack_dep
+ assert_equal :runtime, rack_dep.type
+ assert_equal Gem::Requirement.new(">= 1.0.0"), rack_dep.requirement
+
+ mocha_dep = spec.dependencies.find {|d| d.name == "mocha" }
+ assert_kind_of Gem::Dependency, mocha_dep
+ assert_equal :development, mocha_dep.type
+ assert_equal Gem::Requirement.new("~> 0.9.8"), mocha_dep.requirement
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_non_specific_tag_on_plain_value
+ # ! tag on a bracketed value like rubyforge_project: ! '[none]'
+ result = yaml_load("key: ! '[none]'\n")
+ assert_equal({ "key" => "[none]" }, result)
+ end
+
+ def test_dump_quotes_dollar_sign_values
+ # Values starting with $ should be quoted to preserve them as strings
+ yaml = yaml_dump({ "BUNDLE_FOO" => "$BUILD_DIR", "BUNDLE_BAR" => "baz" })
+ assert_include yaml, 'BUNDLE_FOO: "$BUILD_DIR"'
+ assert_include yaml, "BUNDLE_BAR: baz"
+
+ # Round-trip: ensure the quoted value is parsed back correctly
+ result = yaml_load(yaml)
+ assert_equal "$BUILD_DIR", result["BUNDLE_FOO"]
+ assert_equal "baz", result["BUNDLE_BAR"]
+ end
+
+ def test_dump_quotes_special_characters
+ # Various special characters that should trigger quoting
+ special_values = {
+ "dollar" => "$HOME",
+ "exclamation" => "!important",
+ "ampersand" => "&anchor",
+ "asterisk" => "*ref",
+ "colon_prefix" => ":symbol",
+ "at_sign" => "@mention",
+ "percent" => "%encoded",
+ }
+
+ yaml = yaml_dump(special_values)
+ special_values.each do |key, value|
+ assert_include yaml, "#{key}: #{value.inspect}", "Value #{value.inspect} for key #{key} should be quoted"
+ end
+
+ # Round-trip
+ result = yaml_load(yaml)
+ special_values.each do |key, value|
+ assert_equal value, result[key], "Round-trip failed for key #{key}"
+ end
+ end
+
+ def test_load_ambiguous_value_with_colon
+ # "invalid: yaml: hah" is ambiguous YAML - our parser treats it as
+ # {"invalid" => "yaml: hah"}, but the value looks like a nested mapping.
+ # config_file.rb's load_file should detect this and reject it.
+ if Gem.use_psych?
+ # Psych raises a syntax error for this ambiguous YAML
+ assert_raise(Psych::SyntaxError) do
+ yaml_load("invalid: yaml: hah")
+ end
+ else
+ result = yaml_load("invalid: yaml: hah")
+ assert_kind_of Hash, result
+ assert_equal "yaml: hah", result["invalid"]
+ end
+ end
+
+ def test_nested_anchor_in_array_item
+ # Ensure aliases are enabled for this test
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test-gem
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ dependencies:
+ - !ruby/object:Gem::Dependency
+ name: foo
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - &id002
+ - ">="
+ - !ruby/object:Gem::Version
+ version: "0"
+ type: :runtime
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ assert_equal "test-gem", spec.name
+
+ dep = spec.dependencies.first
+ assert_kind_of Gem::Dependency, dep
+
+ # Requirements should be parsed as nested arrays, not strings
+ assert_kind_of Array, dep.requirement.requirements
+ assert_equal 1, dep.requirement.requirements.size
+
+ req_item = dep.requirement.requirements.first
+ assert_kind_of Array, req_item
+ assert_equal ">=", req_item[0]
+ assert_kind_of Gem::Version, req_item[1]
+ assert_equal "0", req_item[1].version
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_roundtrip_specification
+ spec = Gem::Specification.new do |s|
+ s.name = "round-trip-test"
+ s.version = "2.3.4"
+ s.platform = "ruby"
+ s.authors = ["Test Author"]
+ s.summary = "A test gem for round-trip"
+ s.description = "Longer description of the test gem"
+ s.files = ["lib/foo.rb", "README.md"]
+ s.require_paths = ["lib"]
+ s.homepage = "https://example.com"
+ s.licenses = ["MIT"]
+ s.metadata = { "source_code_uri" => "https://example.com/src" }
+ s.add_dependency "rake", ">= 1.0"
+ end
+
+ yaml = yaml_dump(spec)
+ loaded = Gem::SafeYAML.safe_load(yaml)
+
+ assert_kind_of Gem::Specification, loaded
+ assert_equal "round-trip-test", loaded.name
+ assert_equal Gem::Version.new("2.3.4"), loaded.version
+ assert_equal ["Test Author"], loaded.authors
+ assert_equal "A test gem for round-trip", loaded.summary
+ assert_equal ["README.md", "lib/foo.rb"], loaded.files
+ assert_equal ["lib"], loaded.require_paths
+ assert_equal "https://example.com", loaded.homepage
+ assert_equal ["MIT"], loaded.licenses
+ assert_equal({ "source_code_uri" => "https://example.com/src" }, loaded.metadata)
+ assert_equal 1, loaded.dependencies.size
+
+ dep = loaded.dependencies.first
+ assert_equal "rake", dep.name
+ assert_equal :runtime, dep.type
+ end
+
+ def test_roundtrip_specification_with_extensions
+ spec = Gem::Specification.new do |s|
+ s.name = "native-ext-test"
+ s.version = "1.0.0"
+ s.authors = ["Test"]
+ s.summary = "A gem with native extensions"
+ s.files = ["lib/native.rb", "ext/native/extconf.rb", "ext/native/native.c"]
+ s.extensions = ["ext/native/extconf.rb"]
+ s.require_paths = ["lib"]
+ end
+
+ yaml = yaml_dump(spec)
+ loaded = Gem::SafeYAML.safe_load(yaml)
+
+ assert_kind_of Gem::Specification, loaded
+ assert_equal ["ext/native/extconf.rb"], loaded.extensions
+ assert_equal ["ext/native/extconf.rb", "ext/native/native.c", "lib/native.rb"], loaded.files
+ end
+
+ def test_roundtrip_specification_with_windows_paths
+ spec = Gem::Specification.new do |s|
+ s.name = "win-path-test"
+ s.version = "1.0.0"
+ s.authors = ["Test"]
+ s.summary = "A gem with Windows-style paths"
+ s.files = ["lib/foo.rb", "lib/foo/bar.rb"]
+ s.require_paths = ["lib"]
+ s.description = 'Installed in D:\ruby\lib\ruby\gems'
+ s.post_install_message = "Installed to C:\\Program Files\\Ruby\\lib\\rdoc"
+ end
+
+ yaml = yaml_dump(spec)
+ loaded = Gem::SafeYAML.safe_load(yaml)
+
+ assert_kind_of Gem::Specification, loaded
+ assert_equal 'Installed in D:\ruby\lib\ruby\gems', loaded.description
+ assert_equal "Installed to C:\\Program Files\\Ruby\\lib\\rdoc", loaded.post_install_message
+ end
+
+ def test_roundtrip_specification_with_metadata
+ spec = Gem::Specification.new do |s|
+ s.name = "metadata-test"
+ s.version = "1.0.0"
+ s.authors = ["Test"]
+ s.summary = "A gem with metadata"
+ s.files = ["lib/foo.rb"]
+ s.require_paths = ["lib"]
+ s.metadata = {
+ "changelog_uri" => "https://example.com/CHANGELOG.md",
+ "source_code_uri" => "https://github.com/example/metadata-test",
+ "bug_tracker_uri" => "https://github.com/example/metadata-test/issues",
+ "allowed_push_host" => "https://rubygems.org",
+ }
+ end
+
+ yaml = yaml_dump(spec)
+ loaded = Gem::SafeYAML.safe_load(yaml)
+
+ assert_kind_of Gem::Specification, loaded
+ assert_kind_of Hash, loaded.metadata
+ assert_equal 4, loaded.metadata.size
+ assert_equal "https://example.com/CHANGELOG.md", loaded.metadata["changelog_uri"]
+ assert_equal "https://github.com/example/metadata-test", loaded.metadata["source_code_uri"]
+ assert_equal "https://github.com/example/metadata-test/issues", loaded.metadata["bug_tracker_uri"]
+ assert_equal "https://rubygems.org", loaded.metadata["allowed_push_host"]
+ end
+
+ def test_roundtrip_version
+ ver = Gem::Version.new("1.2.3")
+ yaml = yaml_dump(ver)
+ loaded = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+
+ assert_kind_of Gem::Version, loaded
+ assert_equal ver, loaded
+ end
+
+ def test_roundtrip_platform
+ plat = Gem::Platform.new("x86_64-linux")
+ yaml = yaml_dump(plat)
+ loaded = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+
+ assert_kind_of Gem::Platform, loaded
+ assert_equal plat.cpu, loaded.cpu
+ assert_equal plat.os, loaded.os
+ assert_equal plat.version, loaded.version
+ end
+
+ def test_roundtrip_requirement
+ req = Gem::Requirement.new(">= 1.0", "< 2.0")
+ yaml = yaml_dump(req)
+ loaded = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+
+ assert_kind_of Gem::Requirement, loaded
+ assert_equal req.requirements.sort_by(&:to_s), loaded.requirements.sort_by(&:to_s)
+ end
+
+ def test_roundtrip_dependency
+ dep = Gem::Dependency.new("foo", ">= 1.0", :development)
+ yaml = yaml_dump(dep)
+ loaded = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+
+ assert_kind_of Gem::Dependency, loaded
+ assert_equal "foo", loaded.name
+ assert_equal :development, loaded.type
+ assert_equal dep.requirement.requirements, loaded.requirement.requirements
+ end
+
+ def test_roundtrip_nested_hash
+ obj = { "a" => { "b" => "c", "d" => [1, 2, 3] } }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_equal obj, loaded
+ end
+
+ def test_roundtrip_block_scalar
+ obj = { "text" => "line1\nline2\n" }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_equal "line1\nline2\n", loaded["text"]
+ end
+
+ def test_roundtrip_special_characters
+ obj = {
+ "dollar" => "$HOME",
+ "exclamation" => "!important",
+ "ampersand" => "&anchor",
+ "asterisk" => "*ref",
+ "colon_prefix" => ":symbol",
+ "hash_char" => "value#comment",
+ "brackets" => "[item]",
+ "braces" => "{key}",
+ "comma" => "a,b,c",
+ }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ obj.each do |key, value|
+ assert_equal value, loaded[key], "Round-trip failed for key #{key}"
+ end
+ end
+
+ def test_roundtrip_boolean_nil_integer
+ obj = { "flag" => true, "count" => 42, "empty" => nil, "off" => false }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_equal true, loaded["flag"]
+ assert_equal 42, loaded["count"]
+ assert_nil loaded["empty"]
+ assert_equal false, loaded["off"]
+ end
+
+ def test_roundtrip_time
+ time = Time.utc(2024, 6, 15, 12, 30, 45)
+ obj = { "created" => time }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_kind_of Time, loaded["created"]
+ assert_equal time.year, loaded["created"].year
+ assert_equal time.month, loaded["created"].month
+ assert_equal time.day, loaded["created"].day
+ end
+
+ def test_roundtrip_empty_collections
+ obj = { "arr" => [], "hash" => {} }
+ yaml = yaml_dump(obj)
+ loaded = yaml_load(yaml)
+
+ assert_equal [], loaded["arr"]
+ assert_equal({}, loaded["hash"])
+ end
+
+ def test_load_double_quoted_escape_sequences
+ result = yaml_load("newline: \"hello\\nworld\"")
+ assert_equal "hello\nworld", result["newline"]
+
+ result = yaml_load("tab: \"col1\\tcol2\"")
+ assert_equal "col1\tcol2", result["tab"]
+
+ result = yaml_load("cr: \"line\\rend\"")
+ assert_equal "line\rend", result["cr"]
+
+ result = yaml_load("quote: \"say\\\"hi\\\"\"")
+ assert_equal "say\"hi\"", result["quote"]
+ end
+
+ def test_load_double_quoted_backslash_before_escape_chars
+ # \\r in YAML should become literal backslash + r, not carriage return
+ result = yaml_load('path: "D:\\\\ruby-mswin\\\\lib"')
+ assert_equal "D:\\ruby-mswin\\lib", result["path"]
+
+ # \\n should become literal backslash + n, not newline
+ result = yaml_load('path: "C:\\\\new_folder"')
+ assert_equal "C:\\new_folder", result["path"]
+
+ # \\t should become literal backslash + t, not tab
+ result = yaml_load('path: "C:\\\\tmp\\\\test"')
+ assert_equal "C:\\tmp\\test", result["path"]
+
+ # \\\\ should become two literal backslashes
+ result = yaml_load('val: "a\\\\\\\\b"')
+ assert_equal "a\\\\b", result["val"]
+ end
+
+ def test_load_single_quoted_escape
+ result = yaml_load("key: 'it''s'")
+ assert_equal "it's", result["key"]
+
+ result = yaml_load("key: 'no escape \\n here'")
+ assert_equal "no escape \\n here", result["key"]
+ end
+
+ def test_load_quoted_numeric_stays_string
+ result = yaml_load("key: \"42\"")
+ assert_equal "42", result["key"]
+ assert_kind_of String, result["key"]
+
+ result = yaml_load("key: '99'")
+ assert_equal "99", result["key"]
+ assert_kind_of String, result["key"]
+ end
+
+ def test_load_empty_string_value
+ result = yaml_load("key: \"\"")
+ assert_equal "", result["key"]
+ end
+
+ def test_load_unquoted_integer
+ result = yaml_load("key: 42")
+ assert_equal 42, result["key"]
+ assert_kind_of Integer, result["key"]
+
+ result = yaml_load("key: -7")
+ assert_equal(-7, result["key"])
+ end
+
+ def test_load_boolean_values
+ result = yaml_load("a: true\nb: false")
+ assert_equal true, result["a"]
+ assert_equal false, result["b"]
+ end
+
+ def test_load_nil_value
+ # YAML 1.2: "nil" is not a null value, only ~ and null are
+ result = yaml_load("key: nil")
+ assert_equal "nil", result["key"]
+
+ result = yaml_load("key: ~")
+ assert_nil result["key"]
+
+ result = yaml_load("key: null")
+ assert_nil result["key"]
+ end
+
+ def test_load_time_value
+ result = yaml_load("date: 2024-06-15 12:30:45.000000000 Z")
+ assert_kind_of Time, result["date"]
+ assert_equal 2024, result["date"].year
+ assert_equal 6, result["date"].month
+ assert_equal 15, result["date"].day
+ end
+
+ def test_load_block_scalar_keep_trailing_newline
+ yaml = "text: |\n line1\n line2\n"
+ result = yaml_load(yaml)
+ assert_equal "line1\nline2\n", result["text"]
+ end
+
+ def test_load_block_scalar_strip_trailing_newline
+ yaml = "text: |-\n no trailing newline\n"
+ result = yaml_load(yaml)
+ assert_equal "no trailing newline", result["text"]
+ refute result["text"].end_with?("\n")
+ end
+
+ def test_load_flow_array
+ result = yaml_load("items: [a, b, c]")
+ assert_equal ["a", "b", "c"], result["items"]
+ end
+
+ def test_load_flow_empty_array
+ result = yaml_load("items: []")
+ assert_equal [], result["items"]
+ end
+
+ def test_load_mapping_key_with_no_value
+ result = yaml_load("key:")
+ assert_kind_of Hash, result
+ assert_nil result["key"]
+ end
+
+ def test_load_sequence_item_as_mapping
+ yaml = "items:\n- name: foo\n ver: 1\n- name: bar\n ver: 2"
+ result = yaml_load(yaml)
+ assert_equal [{ "name" => "foo", "ver" => 1 }, { "name" => "bar", "ver" => 2 }], result["items"]
+ end
+
+ def test_load_nested_sequence
+ yaml = "matrix:\n- - a\n - b\n- - c\n - d"
+ result = yaml_load(yaml)
+ assert_equal [["a", "b"], ["c", "d"]], result["matrix"]
+ end
+
+ def test_load_comment_stripped_from_value
+ result = yaml_load("key: value # this is a comment")
+ assert_equal "value", result["key"]
+ end
+
+ def test_load_comment_in_quoted_string_preserved
+ result = yaml_load("key: \"value # not a comment\"")
+ assert_equal "value # not a comment", result["key"]
+
+ result = yaml_load("key: 'value # not a comment'")
+ assert_equal "value # not a comment", result["key"]
+ end
+
+ def test_load_crlf_line_endings
+ result = yaml_load("key: value\r\nother: data\r\n")
+ assert_equal "value", result["key"]
+ assert_equal "data", result["other"]
+ end
+
+ def test_load_version_requirement_old_tag
+ yaml = <<~YAML
+ !ruby/object:Gem::Version::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: "1.0"
+ YAML
+
+ req = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_kind_of Gem::Requirement, req
+ assert_equal [[">=", Gem::Version.new("1.0")]], req.requirements
+ end
+
+ def test_load_dependency_version_version_requirement_old_tag
+ yaml = <<~YAML
+ - !ruby/object:Gem::Dependency
+ name: test-unit
+ type: :development
+ version_requirement:
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: 2.0.2
+ version:
+ YAML
+
+ deps = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_not_nil(deps.first)
+
+ assert_equal [[">=", Gem::Version.new("2.0.2")]], deps.first.requirement.requirements
+ end
+
+ def test_load_platform_from_value_field
+ yaml = "!ruby/object:Gem::Platform\nvalue: x86-linux\n"
+ plat = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_kind_of Gem::Platform, plat
+ assert_nil plat.cpu
+ end
+
+ def test_load_platform_from_cpu_os_version_fields
+ yaml = "!ruby/object:Gem::Platform\ncpu: x86_64\nos: darwin\nversion: nil\n"
+ plat = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_kind_of Gem::Platform, plat
+ assert_equal "x86_64", plat.cpu
+ assert_equal "darwin", plat.os
+ end
+
+ def test_load_platform_malicious_sequence
+ yaml = "!ruby/object:Gem::Platform\n- \"x86-mswin32\\n system('id')#\"\n"
+ result = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ refute_kind_of Gem::Platform, result
+ assert_kind_of Array, result
+ end
+
+ def test_load_dependency_missing_requirement_uses_default
+ yaml = <<~YAML
+ !ruby/object:Gem::Dependency
+ name: foo
+ type: :runtime
+ YAML
+
+ dep = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_kind_of Gem::Dependency, dep
+ assert_equal "foo", dep.name
+ assert_equal :runtime, dep.type
+ assert_nil dep.instance_variable_get(:@requirement)
+ end
+
+ def test_load_dependency_missing_type_defaults_to_runtime
+ yaml = <<~YAML
+ !ruby/object:Gem::Dependency
+ name: bar
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ YAML
+
+ dep = yaml_load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES)
+ assert_equal :runtime, dep.type
+ end
+
+ def test_specification_version_non_numeric_string_not_converted
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ specification_version: abc
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Specification, spec
+ # Non-numeric string should not be converted to Integer
+ assert_equal "abc", spec.specification_version
+ end
+
+ def test_unknown_permitted_tag_raises_argument_error
+ yaml = "!ruby/object:MyCustomClass\nfoo: bar\n"
+ assert_raise(ArgumentError) do
+ yaml_load(yaml, permitted_classes: ["MyCustomClass"])
+ end
+ end
+
+ def test_dump_block_scalar_with_trailing_newline
+ yaml = yaml_dump({ "text" => "line1\nline2\n" })
+ assert_include yaml, " |\n"
+ refute_includes yaml, " |-\n"
+ end
+
+ def test_dump_block_scalar_without_trailing_newline
+ yaml = yaml_dump({ "text" => "line1\nline2" })
+ assert_include yaml, " |-\n"
+ end
+
+ def test_dump_nil_value
+ yaml = yaml_dump({ "key" => nil })
+
+ loaded = yaml_load(yaml)
+ assert_nil loaded["key"]
+ end
+
+ def test_dump_symbol_keys_quoted
+ yaml = yaml_dump({ foo: "bar" })
+ # Symbol keys should use inspect format
+ assert_include yaml, ":foo:"
+
+ # Symbol values in hash with symbol keys should be quoted
+ yaml = yaml_dump({ type: ":runtime" })
+ assert_include yaml, "\":runtime\""
+ end
+
+ def test_regression_flow_empty_hash_as_root
+ # Previously returned Mapping struct instead of Hash
+ result = yaml_load("--- {}")
+ assert_kind_of Hash, result
+ assert_empty result
+ end
+
+ def test_regression_alias_check_in_builder_not_parser
+ # Previously aliases were resolved in Parser, bypassing Builder's policy check.
+ # The Builder must enforce aliases: false.
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = false
+
+ # Alias in mapping value
+ assert_raise(Psych::AliasesNotEnabled) do
+ yaml_load("a: &x val\nb: *x", aliases: false)
+ end
+
+ # Alias in sequence item
+ assert_raise(Psych::AliasesNotEnabled) do
+ yaml_load("items:\n- &x val\n- *x", aliases: false)
+ end
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_regression_anchored_mapping_stored_for_alias_resolution
+ # Previously build_mapping didn't call store_anchor, so anchored
+ # Gem types (Requirement, etc.) couldn't be resolved via aliases.
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ yaml = <<~YAML
+ a: &req !ruby/object:Gem::Requirement
+ requirements:
+ - - ">="
+ - !ruby/object:Gem::Version
+ version: '0'
+ b: *req
+ YAML
+
+ result = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Gem::Requirement, result["a"]
+ assert_kind_of Gem::Requirement, result["b"]
+ assert_equal result["a"].requirements, result["b"].requirements
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_regression_register_anchor_sets_node_anchor
+ # Previously register_anchor only stored node in @anchors hash but
+ # didn't set node.anchor, so Builder couldn't track anchored values.
+ aliases_enabled = Gem::SafeYAML.aliases_enabled?
+ Gem::SafeYAML.aliases_enabled = true
+
+ yaml = <<~YAML
+ items:
+ - &item !ruby/object:Gem::Version
+ version: '1.0'
+ - *item
+ YAML
+
+ result = Gem::SafeYAML.safe_load(yaml)
+ assert_kind_of Array, result["items"]
+ assert_equal 2, result["items"].size
+ assert_kind_of Gem::Version, result["items"][0]
+ assert_kind_of Gem::Version, result["items"][1]
+ assert_equal result["items"][0], result["items"][1]
+ ensure
+ Gem::SafeYAML.aliases_enabled = aliases_enabled
+ end
+
+ def test_regression_coerce_empty_hash_not_wrapped_in_scalar
+ # Previously coerce("{}") returned Mapping but parse_plain_scalar
+ # wrapped it in Scalar.new(value: Mapping), causing type mismatch.
+ result = yaml_load("--- {}")
+ assert_kind_of Hash, result
+
+ result = yaml_load("key: {}")
+ assert_kind_of Hash, result["key"]
+ end
+
+ def test_regression_rdoc_options_normalized_to_array
+ # rdoc_options as Hash (malformed gemspec)
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ rdoc_options:
+ --title: MyGem
+ --main: README
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_equal ["--title", "MyGem", "--main", "README"], spec.rdoc_options
+ end
+
+ def test_regression_requirements_field_normalized_to_array
+ # The "requirements" field in a Specification (not Requirement)
+ # should be normalized from Hash to Array if malformed
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: test
+ version: !ruby/object:Gem::Version
+ version: 1.0.0
+ requirements:
+ foo: bar
+ YAML
+
+ spec = Gem::SafeYAML.safe_load(yaml)
+ assert_equal [["foo", "bar"]], spec.requirements
+ end
+
+ def test_binary_tag_decoded_in_mapping_key
+ yaml = <<~YAML
+ ---
+ !binary "U0hBMQ==":
+ metadata.gz: abc123
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal "SHA1", result.keys.first
+ assert_equal "abc123", result["SHA1"]["metadata.gz"]
+ end
+
+ def test_binary_tag_decoded_in_block_scalar_value
+ yaml = <<~YAML
+ ---
+ SHA256:
+ metadata.gz: !binary |-
+ OWY4YTM5Y2MxOTc3Mzc5MWYzNzk1NjRmZjVlYzljYjY1MDQwYWIwMg==
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal "9f8a39cc19773791f379564ff5ec9cb65040ab02", result["SHA256"]["metadata.gz"]
+ end
+
+ def test_binary_tag_decoded_in_inline_value
+ yaml = <<~YAML
+ ---
+ key: !binary "U0hBMQ=="
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal "SHA1", result["key"]
+ end
+
+ def test_binary_tag_checksums_yaml_roundtrip
+ # Simulates the checksums.yaml.gz format from older gems
+ yaml = <<~YAML
+ ---
+ !binary "U0hBMQ==":
+ metadata.gz: !binary |-
+ OWY4YTM5Y2MxOTc3Mzc5MWYzNzk1NjRmZjVlYzljYjY1MDQwYWIwMg==
+ data.tar.gz: !binary |-
+ ZTRmZGRhNjc1MWM5NmIwYzRhODFkYjI0OTlkMjY3ZjQ2MWNkMGM1ZA==
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal ["SHA1"], result.keys
+ assert_equal "9f8a39cc19773791f379564ff5ec9cb65040ab02", result["SHA1"]["metadata.gz"]
+ assert_equal "e4fdda6751c96b0c4a81db2499d267f461cd0c5d", result["SHA1"]["data.tar.gz"]
+ end
+
+ def test_binary_tag_decoded_in_sequence_item_inline
+ yaml = <<~YAML
+ ---
+ - !binary "U0hBMQ=="
+ YAML
+
+ result = yaml_load(yaml)
+ assert_equal ["SHA1"], result
+ end
+
+ def test_version_requirement_tag_always_permitted
+ yaml = <<~YAML
+ --- !ruby/object:Gem::Specification
+ name: escape
+ version: !ruby/object:Gem::Version
+ version: 0.0.4
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
+ requirements:
+ - - ">"
+ - !ruby/object:Gem::Version
+ version: 0.0.0
+ version:
+ YAML
+
+ result = yaml_load(yaml)
+ assert_kind_of Gem::Specification, result
+ assert_equal "escape", result.name
+ assert_kind_of Gem::Requirement, result.required_ruby_version
+ end
end
diff --git a/test/rubygems/test_gem_security_trust_dir.rb b/test/rubygems/test_gem_security_trust_dir.rb
index cfde8e9d48..bd3dfb86c2 100644
--- a/test/rubygems/test_gem_security_trust_dir.rb
+++ b/test/rubygems/test_gem_security_trust_dir.rb
@@ -56,7 +56,7 @@ class TestGemSecurityTrustDir < Gem::TestCase
assert_path_exist trusted
- mask = 0o100600 & (~File.umask)
+ mask = 0o100600 & ~File.umask
assert_equal mask, File.stat(trusted).mode unless Gem.win_platform?
@@ -70,7 +70,7 @@ class TestGemSecurityTrustDir < Gem::TestCase
assert_path_exist @dest_dir
- mask = 0o040700 & (~File.umask)
+ mask = 0o040700 & ~File.umask
mask |= 0o200000 if RUBY_PLATFORM.include?("aix")
assert_equal mask, File.stat(@dest_dir).mode unless Gem.win_platform?
@@ -91,7 +91,7 @@ class TestGemSecurityTrustDir < Gem::TestCase
@trust_dir.verify
- mask = 0o40700 & (~File.umask)
+ mask = 0o40700 & ~File.umask
mask |= 0o200000 if RUBY_PLATFORM.include?("aix")
assert_equal mask, File.stat(@dest_dir).mode unless Gem.win_platform?
diff --git a/test/rubygems/test_gem_source_git.rb b/test/rubygems/test_gem_source_git.rb
index fef79a0743..b7b2c52f9a 100644
--- a/test/rubygems/test_gem_source_git.rb
+++ b/test/rubygems/test_gem_source_git.rb
@@ -65,6 +65,8 @@ class TestGemSourceGit < Gem::TestCase
end
def test_checkout_submodules
+ omit "JRuby on Windows hits git submodule path differences" if Gem.win_platform? && Gem.java_platform?
+
# We need to allow to checkout submodules with file:// protocol
# CVE-2022-39253
# https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
diff --git a/test/rubygems/test_gem_source_list.rb b/test/rubygems/test_gem_source_list.rb
index 64353f8f90..5327b14db8 100644
--- a/test/rubygems/test_gem_source_list.rb
+++ b/test/rubygems/test_gem_source_list.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
-require "rubygems"
-require "rubygems/source_list"
require_relative "helper"
+require "rubygems/source_list"
class TestGemSourceList < Gem::TestCase
def setup
@@ -116,4 +115,128 @@ class TestGemSourceList < Gem::TestCase
@sl.delete Gem::Source.new(@uri)
assert_equal @sl.sources, []
end
+
+ def test_prepend_new_source
+ uri2 = "http://example2"
+ source2 = Gem::Source.new(uri2)
+
+ result = @sl.prepend(uri2)
+
+ assert_kind_of Gem::Source, result
+ assert_kind_of Gem::URI, result.uri
+ assert_equal uri2, result.uri.to_s
+ assert_equal [source2, @source], @sl.sources
+ end
+
+ def test_prepend_existing_source
+ uri2 = "http://example2"
+ source2 = Gem::Source.new(uri2)
+ @sl << uri2
+
+ assert_equal [@source, source2], @sl.sources
+
+ result = @sl.prepend(uri2)
+
+ assert_kind_of Gem::Source, result
+ assert_kind_of Gem::URI, result.uri
+ assert_equal uri2, result.uri.to_s
+ assert_equal [source2, @source], @sl.sources
+ end
+
+ def test_prepend_alias_behaves_like_unshift
+ sl = Gem::SourceList.new
+
+ uri1 = "http://one"
+ uri2 = "http://two"
+
+ source1 = sl << uri1
+ source2 = sl << uri2
+
+ # move existing to front
+ result = sl.prepend(uri2)
+
+ assert_kind_of Gem::Source, result
+ assert_equal [source2, source1], sl.sources
+
+ # and again with the other
+ result = sl.prepend(uri1)
+ assert_equal [source1, source2], sl.sources
+ end
+
+ def test_append_method_new_source
+ sl = Gem::SourceList.new
+
+ uri1 = "http://example1"
+
+ result = sl.append(uri1)
+
+ assert_kind_of Gem::Source, result
+ assert_kind_of Gem::URI, result.uri
+ assert_equal uri1, result.uri.to_s
+ assert_equal [result], sl.sources
+ end
+
+ def test_append_method_existing_moves_to_end
+ sl = Gem::SourceList.new
+
+ uri1 = "http://example1"
+ uri2 = "http://example2"
+
+ s1 = sl << uri1
+ s2 = sl << uri2
+
+ # list is [s1, s2]; appending s1 should move it to end => [s2, s1]
+ result = sl.append(uri1)
+
+ assert_equal s1, result
+ assert_equal [s2, s1], sl.sources
+ end
+
+ def test_prepend_with_gem_source_object
+ sl = Gem::SourceList.new
+
+ uri1 = "http://example1"
+ uri2 = "http://example2"
+ source1 = Gem::Source.new(uri1)
+ source2 = Gem::Source.new(uri2)
+
+ # Add first source
+ sl << source1
+
+ # Prepend with Gem::Source object
+ result = sl.prepend(source2)
+
+ assert_equal source2, result
+ assert_equal [source2, source1], sl.sources
+
+ # Prepend existing source - should move to front
+ result = sl.prepend(source1)
+
+ assert_equal source1, result
+ assert_equal [source1, source2], sl.sources
+ end
+
+ def test_append_with_gem_source_object
+ sl = Gem::SourceList.new
+
+ uri1 = "http://example1"
+ uri2 = "http://example2"
+ source1 = Gem::Source.new(uri1)
+ source2 = Gem::Source.new(uri2)
+
+ # Add first source
+ sl << source1
+
+ # Append with Gem::Source object
+ result = sl.append(source2)
+
+ assert_equal source2, result
+ assert_equal [source1, source2], sl.sources
+
+ # Append existing source - should move to end
+ result = sl.append(source1)
+
+ assert_equal source1, result
+ assert_equal [source2, source1], sl.sources
+ end
end
diff --git a/test/rubygems/test_gem_source_local.rb b/test/rubygems/test_gem_source_local.rb
index e9d7f45482..6062173629 100644
--- a/test/rubygems/test_gem_source_local.rb
+++ b/test/rubygems/test_gem_source_local.rb
@@ -63,6 +63,30 @@ class TestGemSourceLocal < Gem::TestCase
assert_equal "a-2.a", @sl.find_gem("a", req, true).full_name
end
+ def test_find_all_gems
+ _, a2_gem = util_gem "a", "2"
+ FileUtils.mv a2_gem, @tempdir
+
+ results = @sl.find_all_gems("a")
+ assert_equal ["a-1", "a-2"], results.map(&:full_name).sort
+ end
+
+ def test_find_all_gems_excludes_prerelease_by_default
+ results = @sl.find_all_gems("a")
+ assert_equal ["a-1"], results.map(&:full_name)
+ end
+
+ def test_find_all_gems_includes_prerelease_when_requested
+ results = @sl.find_all_gems("a", Gem::Requirement.create(">= 0"), true)
+ assert_equal ["a-1", "a-2.a"], results.map(&:full_name).sort
+ end
+
+ def test_find_all_gems_includes_prerelease_when_requirement_is_prerelease
+ req = Gem::Requirement.create("= 2.a")
+ results = @sl.find_all_gems("a", req)
+ assert_equal ["a-2.a"], results.map(&:full_name)
+ end
+
def test_fetch_spec
s = @sl.fetch_spec @a.name_tuple
assert_equal s, @a
diff --git a/test/rubygems/test_gem_specification.rb b/test/rubygems/test_gem_specification.rb
index 43b649b9ea..79be0c996d 100644
--- a/test/rubygems/test_gem_specification.rb
+++ b/test/rubygems/test_gem_specification.rb
@@ -16,7 +16,7 @@ rubygems_version: "1.0"
name: keyedlist
version: !ruby/object:Gem::Version
version: 0.4.0
-date: 2004-03-28 15:37:49.828000 +02:00
+date: 1980-01-02 00:00:00 UTC
platform:
summary: A Hash which automatically computes keys.
require_paths:
@@ -33,7 +33,6 @@ has_rdoc: true
Gem::Specification.new do |s|
s.name = %q{keyedlist}
s.version = %q{0.4.0}
- s.has_rdoc = true
s.summary = %q{A Hash which automatically computes keys.}
s.files = [%q{lib/keyedlist.rb}]
s.require_paths = [%q{lib}]
@@ -75,7 +74,7 @@ end
def assert_date(date)
assert_kind_of Time, date
assert_equal [0, 0, 0], [date.hour, date.min, date.sec]
- assert_operator (Gem::Specification::TODAY..Time.now), :cover?, date
+ assert_equal Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc, date
end
def setup
@@ -564,7 +563,6 @@ end
# [B] ~> 1.0
#
# and should resolve using b-1.0
- # TODO: move these to specification
def test_self_activate_over
a = util_spec "a", "1.0", "b" => ">= 1.0", "c" => "= 1.0"
@@ -653,6 +651,17 @@ end
end
end
+ def test_self_activate_missing_deps_does_not_raise_nested_exceptions
+ a = util_spec "a", "1.0", "b" => ">= 1.0"
+ install_specs a
+
+ e = assert_raise Gem::MissingSpecError do
+ a.activate
+ end
+
+ refute e.cause
+ end
+
def test_self_all_equals
a = util_spec "foo", "1", nil, "lib/foo.rb"
@@ -808,7 +817,7 @@ dependencies: []
write_file full_path do |io|
io.write @a2.to_ruby_for_cache
end
- rescue Errno::EINVAL
+ rescue Errno::EINVAL, Errno::EACCES
pend "cannot create '#{full_path}' on this platform"
end
@@ -827,7 +836,7 @@ dependencies: []
write_file full_path do |io|
io.write @a2.to_ruby_for_cache
end
- rescue Errno::EINVAL
+ rescue Errno::EINVAL, Errno::EACCES
pend "cannot create '#{full_path}' on this platform"
end
@@ -846,7 +855,7 @@ dependencies: []
write_file full_path do |io|
io.write @a2.to_ruby_for_cache
end
- rescue Errno::EINVAL
+ rescue Errno::EINVAL, Errno::EACCES
pend "cannot create '#{full_path}' on this platform"
end
@@ -1019,7 +1028,7 @@ dependencies: []
gem = "mingw"
v = "1.1.1"
- platforms = ["x86-mingw32", "x64-mingw32"]
+ platforms = ["x86-mingw32", "x64-mingw-ucrt"]
# create specs
platforms.each do |plat|
@@ -1238,12 +1247,37 @@ dependencies: []
end
def test_initialize_nil_version
- expected = "nil versions are discouraged and will be deprecated in Rubygems 4\n"
- actual_stdout, actual_stderr = capture_output do
- Gem::Specification.new.version = nil
+ spec = Gem::Specification.new
+ spec.name = "test-name"
+
+ assert_nil spec.version
+ spec.version = nil
+ assert_nil spec.version
+
+ spec.summary = "test gem"
+ spec.authors = ["test author"]
+ e = assert_raise Gem::InvalidSpecificationException do
+ spec.validate
end
- assert_empty actual_stdout
- assert_equal(expected, actual_stderr)
+ assert_match("missing value for attribute version", e.message)
+ end
+
+ def test_set_version_to_nil_after_setting_version
+ spec = Gem::Specification.new
+ spec.name = "test-name"
+
+ assert_nil spec.version
+ spec.version = "1.0.0"
+ assert_equal "1.0.0", spec.version.to_s
+ spec.version = nil
+ assert_nil spec.version
+
+ spec.summary = "test gem"
+ spec.authors = ["test author"]
+ e = assert_raise Gem::InvalidSpecificationException do
+ spec.validate
+ end
+ assert_match("missing value for attribute version", e.message)
end
def test__dump
@@ -1544,13 +1578,21 @@ dependencies: []
ext_spec
_, err = capture_output do
- refute @ext.contains_requirable_file? "nonexistent"
+ if RUBY_ENGINE == "jruby"
+ refute @ext.ignored?
+ else
+ refute @ext.contains_requirable_file? "nonexistent"
+ end
end
- expected = "Ignoring ext-1 because its extensions are not built. " \
- "Try: gem pristine ext --version 1\n"
+ if RUBY_ENGINE == "jruby"
+ assert_equal "", err
+ else
+ expected = "Ignoring ext-1 because its extensions are not built. " \
+ "Try: gem pristine ext --version 1\n"
- assert_equal expected, err
+ assert_equal expected, err
+ end
end
def test_contains_requirable_file_eh_extension_java_platform
@@ -2206,9 +2248,9 @@ dependencies: []
s1 = util_spec "a", "1"
s2 = util_spec "b", "1"
- assert_equal(-1, (s1 <=> s2))
- assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
- assert_equal(1, (s2 <=> s1))
+ assert_equal(-1, s1 <=> s2)
+ assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ assert_equal(1, s2 <=> s1)
end
def test_spaceship_platform
@@ -2217,18 +2259,18 @@ dependencies: []
s.platform = Gem::Platform.new "x86-my_platform1"
end
- assert_equal(-1, (s1 <=> s2))
- assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
- assert_equal(1, (s2 <=> s1))
+ assert_equal(-1, s1 <=> s2)
+ assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ assert_equal(1, s2 <=> s1)
end
def test_spaceship_version
s1 = util_spec "a", "1"
s2 = util_spec "a", "2"
- assert_equal(-1, (s1 <=> s2))
- assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
- assert_equal(1, (s2 <=> s1))
+ assert_equal(-1, s1 <=> s2)
+ assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
+ assert_equal(1, s2 <=> s1)
end
def test_spec_file
@@ -2661,27 +2703,7 @@ end
@a1.validate
end
- expected = <<-EXPECTED
-#{w}: prerelease dependency on b (>= 1.0.rc1) is not recommended
-#{w}: prerelease dependency on c (>= 2.0.rc2, development) is not recommended
-#{w}: open-ended dependency on i (>= 1.2) is not recommended
- if i is semantically versioned, use:
- add_runtime_dependency "i", "~> 1.2"
-#{w}: open-ended dependency on j (>= 1.2.3) is not recommended
- if j is semantically versioned, use:
- add_runtime_dependency "j", "~> 1.2", ">= 1.2.3"
-#{w}: open-ended dependency on k (> 1.2) is not recommended
- if k is semantically versioned, use:
- add_runtime_dependency "k", "~> 1.2", "> 1.2"
-#{w}: open-ended dependency on l (> 1.2.3) is not recommended
- if l is semantically versioned, use:
- add_runtime_dependency "l", "~> 1.2", "> 1.2.3"
-#{w}: open-ended dependency on o (>= 0) is not recommended
- use a bounded requirement, such as "~> x.y"
-#{w}: See https://guides.rubygems.org/specification-reference/ for help
- EXPECTED
-
- assert_equal expected, @ui.error, "warning"
+ assert_equal "", @ui.error, "warning"
end
end
@@ -2798,14 +2820,13 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use:
Dir.chdir @tempdir do
@a1.add_dependency @a1.name, "1"
- use_ui @ui do
+ e = assert_raise Gem::InvalidSpecificationException do
@a1.validate
end
- assert_equal <<-EXPECTED, @ui.error
-#{w}: Self referencing dependency is unnecessary and strongly discouraged.
-#{w}: See https://guides.rubygems.org/specification-reference/ for help
- EXPECTED
+ expected = "Dependencies of this gem include a self-reference."
+
+ assert_equal expected, e.message
end
end
@@ -2873,6 +2894,61 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use:
end
end
+ def test_validate_extension_require_relative_warning
+ util_setup_validate
+
+ Dir.chdir @tempdir do
+ @a1.extensions = ["ext/a/extconf.rb"]
+ @a1.files = %w[lib/code.rb lib/a.rb ext/a/extconf.rb]
+
+ File.write File.join("lib", "a.rb"), 'require_relative "a/a"'
+
+ use_ui @ui do
+ @a1.validate
+ end
+
+ assert_match(%r{require_relative "a/a"}, @ui.error)
+ assert_match(/will break in RubyGems 4\.2/, @ui.error)
+ assert_match(/Use `require` instead of `require_relative`/, @ui.error)
+ end
+ end
+
+ def test_validate_extension_require_relative_no_warning_when_rb_exists
+ util_setup_validate
+
+ Dir.chdir @tempdir do
+ @a1.extensions = ["ext/a/extconf.rb"]
+ @a1.files = %w[lib/code.rb lib/a.rb lib/a/a.rb ext/a/extconf.rb]
+
+ FileUtils.mkdir_p File.join("lib", "a")
+ File.write File.join("lib", "a.rb"), 'require_relative "a/a"'
+ File.write File.join("lib", "a", "a.rb"), ""
+
+ use_ui @ui do
+ @a1.validate
+ end
+
+ refute_match(/require_relative/, @ui.error)
+ end
+ end
+
+ def test_validate_extension_require_relative_no_warning_without_extensions
+ util_setup_validate
+
+ Dir.chdir @tempdir do
+ @a1.extensions = []
+ @a1.files = %w[lib/code.rb lib/a.rb]
+
+ File.write File.join("lib", "a.rb"), 'require_relative "a/a"'
+
+ use_ui @ui do
+ @a1.validate
+ end
+
+ refute_match(/require_relative/, @ui.error)
+ end
+ end
+
def test_validate_description
util_setup_validate
@@ -2999,6 +3075,65 @@ duplicate dependency on c (>= 1.2.3, development), (~> 1.2) use:
assert_match "#{w}: bin/exec is missing #! line\n", @ui.error, "error"
end
+ def test_validate_executables_with_space
+ util_setup_validate
+
+ FileUtils.mkdir_p File.join(@tempdir, "bin")
+ File.write File.join(@tempdir, "bin", "echo hax"), "#!/usr/bin/env ruby\n"
+
+ @a1.executables = ["echo hax"]
+
+ e = assert_raise Gem::InvalidSpecificationException do
+ use_ui @ui do
+ Dir.chdir @tempdir do
+ @a1.validate
+ end
+ end
+ end
+
+ assert_match "executable \"echo hax\" contains invalid characters", e.message
+ end
+
+ def test_validate_executables_with_path_separator
+ util_setup_validate
+
+ FileUtils.mkdir_p File.join(@tempdir, "bin")
+ File.write File.join(@tempdir, "exe"), "#!/usr/bin/env ruby\n"
+
+ @a1.executables = Gem.win_platform? ? ["..\\exe"] : ["../exe"]
+
+ e = assert_raise Gem::InvalidSpecificationException do
+ use_ui @ui do
+ Dir.chdir @tempdir do
+ @a1.validate
+ end
+ end
+ end
+
+ assert_match "executable \"#{Gem.win_platform? ? "..\\exe" : "../exe"}\" contains invalid characters", e.message
+ end
+
+ def test_validate_executables_with_path_list_separator
+ sep = Gem.win_platform? ? ";" : ":"
+
+ util_setup_validate
+
+ FileUtils.mkdir_p File.join(@tempdir, "bin")
+ File.write File.join(@tempdir, "bin", "foo#{sep}bar"), "#!/usr/bin/env ruby\n"
+
+ @a1.executables = ["foo#{sep}bar"]
+
+ e = assert_raise Gem::InvalidSpecificationException do
+ use_ui @ui do
+ Dir.chdir @tempdir do
+ @a1.validate
+ end
+ end
+ end
+
+ assert_match "executable \"foo#{sep}bar\" contains invalid characters", e.message
+ end
+
def test_validate_empty_require_paths
util_setup_validate
@@ -3654,8 +3789,6 @@ Did you mean 'Ruby'?
end
def test__load_fixes_Date_objects
- pend "Marshal.load of links and floats is broken on truffleruby, see https://github.com/oracle/truffleruby/issues/3747" if RUBY_ENGINE == "truffleruby"
-
spec = util_spec "a", 1
spec.instance_variable_set :@date, Date.today
@@ -3882,7 +4015,11 @@ end
def test_missing_extensions_eh
ext_spec
- assert @ext.missing_extensions?
+ if RUBY_ENGINE == "jruby"
+ refute @ext.missing_extensions?
+ else
+ assert @ext.missing_extensions?
+ end
extconf_rb = File.join @ext.gem_dir, @ext.extensions.first
FileUtils.mkdir_p File.dirname extconf_rb
diff --git a/test/rubygems/test_gem_stub_specification.rb b/test/rubygems/test_gem_stub_specification.rb
index 4b2d4c570a..6c07480c7f 100644
--- a/test/rubygems/test_gem_stub_specification.rb
+++ b/test/rubygems/test_gem_stub_specification.rb
@@ -68,13 +68,21 @@ class TestStubSpecification < Gem::TestCase
def test_contains_requirable_file_eh_extension
stub_with_extension do |stub|
_, err = capture_output do
- refute stub.contains_requirable_file? "nonexistent"
+ if RUBY_ENGINE == "jruby"
+ refute stub.ignored?
+ else
+ refute stub.contains_requirable_file? "nonexistent"
+ end
end
- expected = "Ignoring stub_e-2 because its extensions are not built. " \
- "Try: gem pristine stub_e --version 2\n"
+ if RUBY_ENGINE == "jruby"
+ assert_equal "", err
+ else
+ expected = "Ignoring stub_e-2 because its extensions are not built. " \
+ "Try: gem pristine stub_e --version 2\n"
- assert_equal expected, err
+ assert_equal expected, err
+ end
end
end
@@ -137,7 +145,11 @@ class TestStubSpecification < Gem::TestCase
end
end
- assert stub.missing_extensions?
+ if RUBY_ENGINE == "jruby"
+ refute stub.missing_extensions?
+ else
+ assert stub.missing_extensions?
+ end
stub.build_extensions
@@ -209,7 +221,7 @@ class TestStubSpecification < Gem::TestCase
end
def stub_with_version
- spec = File.join @gemhome, "specifications", "stub_e-2.gemspec"
+ spec = File.join @gemhome, "specifications", "stub_v-with-version.gemspec"
File.open spec, "w" do |io|
io.write <<~STUB
# -*- encoding: utf-8 -*-
@@ -232,7 +244,7 @@ class TestStubSpecification < Gem::TestCase
end
def stub_without_version
- spec = File.join @gemhome, "specifications", "stub-2.gemspec"
+ spec = File.join @gemhome, "specifications", "stub_v-without-version.gemspec"
File.open spec, "w" do |io|
io.write <<~STUB
# -*- encoding: utf-8 -*-
diff --git a/test/rubygems/test_gem_uri.rb b/test/rubygems/test_gem_uri.rb
index 1253ebc6de..ce633c99b6 100644
--- a/test/rubygems/test_gem_uri.rb
+++ b/test/rubygems/test_gem_uri.rb
@@ -21,7 +21,7 @@ class TestUri < Gem::TestCase
end
def test_redacted_with_user_x_oauth_basic
- assert_equal "https://REDACTED:x-oauth-basic@example.com", Gem::Uri.new("https://token:x-oauth-basic@example.com").redacted.to_s
+ assert_equal "https://REDACTED@example.com", Gem::Uri.new("https://token:x-oauth-basic@example.com").redacted.to_s
end
def test_redacted_without_credential
diff --git a/test/rubygems/test_gem_util.rb b/test/rubygems/test_gem_util.rb
index 608210a903..9688d066db 100644
--- a/test/rubygems/test_gem_util.rb
+++ b/test/rubygems/test_gem_util.rb
@@ -13,17 +13,6 @@ class TestGemUtil < Gem::TestCase
end
end
- def test_silent_system
- pend if Gem.java_platform?
- Gem::Deprecate.skip_during do
- out, err = capture_output do
- Gem::Util.silent_system(*ruby_with_rubygems_in_load_path, "-e", 'puts "hello"; warn "hello"')
- end
- assert_empty out
- assert_empty err
- end
- end
-
def test_traverse_parents
FileUtils.mkdir_p "a/b/c"
diff --git a/test/rubygems/test_gem_util_atomic_file_writer.rb b/test/rubygems/test_gem_util_atomic_file_writer.rb
new file mode 100644
index 0000000000..e011a38ad4
--- /dev/null
+++ b/test/rubygems/test_gem_util_atomic_file_writer.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+require "rubygems/util/atomic_file_writer"
+
+class TestGemUtilAtomicFileWriter < Gem::TestCase
+ def test_external_encoding
+ Gem::AtomicFileWriter.open(File.join(@tempdir, "test.txt")) do |file|
+ assert_equal(Encoding::ASCII_8BIT, file.external_encoding)
+ end
+ end
+end
diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb
index cf771bc5a1..f58359e54c 100644
--- a/test/rubygems/test_gem_version.rb
+++ b/test/rubygems/test_gem_version.rb
@@ -7,6 +7,11 @@ class TestGemVersion < Gem::TestCase
class V < ::Gem::Version
end
+ def test_nil_is_zero
+ zero = Gem::Version.create nil
+ assert_equal Gem::Version.create(0), zero
+ end
+
def test_bump
assert_bumped_version_equal "5.3", "5.2.4"
end
@@ -35,13 +40,6 @@ class TestGemVersion < Gem::TestCase
assert_same real, Gem::Version.create(real)
- expected = "nil versions are discouraged and will be deprecated in Rubygems 4\n"
- actual_stdout, actual_stderr = capture_output do
- assert_nil Gem::Version.create(nil)
- end
- assert_empty actual_stdout
- assert_equal(expected, actual_stderr)
-
assert_equal v("5.1"), Gem::Version.create("5.1")
ver = "1.1"
@@ -51,13 +49,7 @@ class TestGemVersion < Gem::TestCase
def test_class_correct
assert_equal true, Gem::Version.correct?("5.1")
assert_equal false, Gem::Version.correct?("an incorrect version")
-
- expected = "nil versions are discouraged and will be deprecated in Rubygems 4\n"
- actual_stdout, actual_stderr = capture_output do
- Gem::Version.correct?(nil)
- end
- assert_empty actual_stdout
- assert_equal(expected, actual_stderr)
+ assert_equal true, Gem::Version.correct?(nil)
end
def test_class_new_subclass
@@ -162,33 +154,36 @@ class TestGemVersion < Gem::TestCase
assert_equal(-1, v("5.a") <=> v("5.0.0.rc2"))
assert_equal(1, v("5.x") <=> v("5.0.0.rc2"))
- assert_equal(0, v("1.9.3") <=> "1.9.3")
- assert_equal(1, v("1.9.3") <=> "1.9.2.99")
- assert_equal(-1, v("1.9.3") <=> "1.9.3.1")
-
- assert_nil v("1.0") <=> "whatever"
+ [
+ [0, "1.9.3"],
+ [1, "1.9.2.99"],
+ [-1, "1.9.3.1"],
+ [nil, "whatever"],
+ ].each do |cmp, string_ver|
+ assert_equal(cmp, v("1.9.3") <=> string_ver)
+ end
end
def test_approximate_recommendation
- assert_approximate_equal "~> 1.0", "1"
+ assert_approximate_equal ">= 1.0", "1"
assert_approximate_satisfies_itself "1"
- assert_approximate_equal "~> 1.0", "1.0"
+ assert_approximate_equal ">= 1.0", "1.0"
assert_approximate_satisfies_itself "1.0"
- assert_approximate_equal "~> 1.2", "1.2"
+ assert_approximate_equal ">= 1.2", "1.2"
assert_approximate_satisfies_itself "1.2"
- assert_approximate_equal "~> 1.2", "1.2.0"
+ assert_approximate_equal ">= 1.2", "1.2.0"
assert_approximate_satisfies_itself "1.2.0"
- assert_approximate_equal "~> 1.2", "1.2.3"
+ assert_approximate_equal ">= 1.2", "1.2.3"
assert_approximate_satisfies_itself "1.2.3"
- assert_approximate_equal "~> 1.2.a", "1.2.3.a.4"
+ assert_approximate_equal ">= 1.2.a", "1.2.3.a.4"
assert_approximate_satisfies_itself "1.2.3.a.4"
- assert_approximate_equal "~> 1.9.a", "1.9.0.dev"
+ assert_approximate_equal ">= 1.9.a", "1.9.0.dev"
assert_approximate_satisfies_itself "1.9.0.dev"
end
@@ -205,6 +200,51 @@ class TestGemVersion < Gem::TestCase
assert_less_than "1.0.0-1", "1"
end
+ def test_sort_key_is_computed_on_regular_release
+ refute_nil v("9.8.7").send(:sort_key)
+ end
+
+ def test_sort_key_is_computed_on_security_release
+ refute_nil v("9.8.7.1").send(:sort_key)
+ end
+
+ def test_sort_key_is_not_computed_on_prerelease
+ assert_nil v("9.8.7.pre1").send(:sort_key)
+ end
+
+ def test_sort_key_is_not_computed_on_version_with_more_segments
+ assert_nil v("1.1.1.1.1.1.1").send(:sort_key)
+ end
+
+ def test_sort_key_is_not_computed_on_huge_numbers
+ assert_nil v("2.30.1.250000").send(:sort_key)
+ end
+
+ def test_sort_key_on_timestamped_version
+ a = v("1.0.0")
+ b = v("0.0.1.20220404083012")
+
+ assert_operator a, :>, b
+ end
+
+ def test_sort_key_when_segment_is_higher_than_radix
+ a = v("0.7.0")
+ b = v("0.6.63000")
+
+ assert_operator(a, :>, b)
+ end
+
+ def test_sort_key_is_used_for_comparison
+ a = v("18.0.1")
+ b = v("18.0.2")
+
+ # Ensure the slow path isn't getting hit
+ a.instance_variable_set(:@version, nil)
+ a.instance_variable_set(:@canonical_segments, nil)
+
+ assert_operator(a, :<, b)
+ end
+
# modifying the segments of a version should not affect the segments of the cached version object
def test_segments
v("9.8.7").segments[2] += 1
diff --git a/test/rubygems/test_project_sanity.rb b/test/rubygems/test_project_sanity.rb
index 8f23b2d8c0..3b08d1ec7b 100644
--- a/test/rubygems/test_project_sanity.rb
+++ b/test/rubygems/test_project_sanity.rb
@@ -12,6 +12,7 @@ class TestGemProjectSanity < Gem::TestCase
def test_manifest_is_up_to_date
pend unless File.exist?("#{root}/Rakefile")
+ omit "JRuby on Windows cannot exec the bin/rake shebang" if Gem.win_platform? && Gem.java_platform?
rake = "#{root}/bin/rake"
_, status = Open3.capture2e(rake, "check_manifest")
@@ -37,6 +38,8 @@ class TestGemProjectSanity < Gem::TestCase
end
def test_require_rubygems_package
+ omit "JRuby on Windows fails to spawn ruby --disable-gems here" if Gem.win_platform? && Gem.java_platform?
+
err, status = Open3.capture2e(*ruby_with_rubygems_in_load_path, "--disable-gems", "-e", "require \"rubygems/package\"")
assert status.success?, err
diff --git a/test/rubygems/test_require.rb b/test/rubygems/test_require.rb
index f63c23c315..db86a30905 100644
--- a/test/rubygems/test_require.rb
+++ b/test/rubygems/test_require.rb
@@ -431,6 +431,22 @@ class TestGemRequire < Gem::TestCase
assert_equal %w[default-2.0.0.0], loaded_spec_names
end
+ def test_multiple_gems_with_the_same_path_the_non_activated_spec_is_chosen
+ a1 = util_spec "a", "1", nil, "lib/ib.rb"
+ a2 = util_spec "a", "2", nil, "lib/foo.rb"
+ b1 = util_spec "b", "1", nil, "lib/ib.rb"
+
+ install_specs a1, a2, b1
+
+ a2.activate
+
+ assert_equal %w[a-2], loaded_spec_names
+ assert_empty unresolved_names
+
+ assert_require "ib"
+ assert_equal %w[a-2 b-1], loaded_spec_names
+ end
+
def test_default_gem_require_activates_just_once
default_gem_spec = new_default_spec("default", "2.0.0.0",
nil, "default/gem.rb")
@@ -460,6 +476,7 @@ class TestGemRequire < Gem::TestCase
def test_realworld_default_gem
omit "this test can't work under ruby-core setup" if ruby_repo?
+ omit "JRuby on Windows does not register json as a default gem the same way" if Gem.win_platform? && Gem.java_platform?
cmd = <<-RUBY
$stderr = $stdout
@@ -770,6 +787,8 @@ class TestGemRequire < Gem::TestCase
end
def test_require_does_not_crash_when_utilizing_bundler_version_finder
+ omit "JRuby on Windows hits a different require path" if Gem.win_platform? && Gem.java_platform?
+
a1 = util_spec "a", "1.1", { "bundler" => ">= 0" }
a2 = util_spec "a", "1.2", { "bundler" => ">= 0" }
b1 = util_spec "bundler", "2.3.7"
diff --git a/test/rubygems/test_rubygems.rb b/test/rubygems/test_rubygems.rb
index ec195b65cd..6566b5981e 100644
--- a/test/rubygems/test_rubygems.rb
+++ b/test/rubygems/test_rubygems.rb
@@ -10,6 +10,7 @@ class GemTest < Gem::TestCase
def test_operating_system_other_exceptions
pend "does not apply to truffleruby" if RUBY_ENGINE == "truffleruby"
+ omit "JRuby on Windows loads a different operating_system defaults file" if Gem.win_platform? && Gem.java_platform?
path = util_install_operating_system_rb <<-RUBY
intentionally_not_implemented_method
diff --git a/test/rubygems/test_webauthn_listener.rb b/test/rubygems/test_webauthn_listener.rb
index 08edabceb2..ded4128928 100644
--- a/test/rubygems/test_webauthn_listener.rb
+++ b/test/rubygems/test_webauthn_listener.rb
@@ -17,7 +17,7 @@ class WebauthnListenerTest < Gem::TestCase
super
end
- def test_listener_thread_retreives_otp_code
+ def test_listener_thread_retrieves_otp_code
thread = Gem::GemcutterUtilities::WebauthnListener.listener_thread(Gem.host, @server)
Gem::MockBrowser.get Gem::URI("http://localhost:#{@port}?code=xyz")
diff --git a/test/set/fixtures/fake_sorted_set_gem/sorted_set.rb b/test/set/fixtures/fake_sorted_set_gem/sorted_set.rb
deleted file mode 100644
index f45a766303..0000000000
--- a/test/set/fixtures/fake_sorted_set_gem/sorted_set.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-Object.instance_exec do
- # Remove the constant to cancel autoload that would be fired by
- # `class SortedSet` and cause circular require.
- remove_const :SortedSet if const_defined?(:SortedSet)
-end
-
-class SortedSet < Set
- # ...
-end
diff --git a/test/set/test_sorted_set.rb b/test/set/test_sorted_set.rb
deleted file mode 100644
index f7ad7af299..0000000000
--- a/test/set/test_sorted_set.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: false
-require 'test/unit'
-require 'set'
-
-class TC_SortedSet < Test::Unit::TestCase
- def base_dir
- "#{__dir__}/../lib"
- end
-
- def assert_runs(ruby, options: nil)
- options = ['-I', base_dir, *options]
- r = system(RbConfig.ruby, *options, '-e', ruby)
- assert(r)
- end
-
- def test_error
- assert_runs <<~RUBY
- require "set"
-
- r = begin
- puts SortedSet.new
- rescue Exception => e
- e.message
- end
- raise r unless r.match?(/has been extracted/)
- RUBY
- end
-
- def test_ok_with_gem
- assert_runs <<~RUBY, options: ['-I', "#{__dir__}/fixtures/fake_sorted_set_gem"]
- require "set"
-
- var = SortedSet.new.to_s
- RUBY
- end
-
- def test_ok_require
- assert_runs <<~RUBY, options: ['-I', "#{__dir__}/fixtures/fake_sorted_set_gem"]
- require "set"
- require "sorted_set"
-
- var = SortedSet.new.to_s
- RUBY
- end
-end
diff --git a/test/socket/test_addrinfo.rb b/test/socket/test_addrinfo.rb
index c61764d76d..0c9529090e 100644
--- a/test/socket/test_addrinfo.rb
+++ b/test/socket/test_addrinfo.rb
@@ -360,6 +360,12 @@ class TestSocketAddrinfo < Test::Unit::TestCase
assert_raise(Socket::ResolutionError) { Addrinfo.tcp("0.0.0.0", 4649).family_addrinfo("::1", 80) }
end
+ def test_ractor_shareable
+ assert_ractor(<<~'RUBY', require: 'socket', timeout: 60)
+ Ractor.make_shareable Addrinfo.new "\x10\x02\x14\xE9\xE0\x00\x00\xFB\x00\x00\x00\x00\x00\x00\x00\x00".b
+ RUBY
+ end
+
def random_port
# IANA suggests dynamic port for 49152 to 65535
# http://www.iana.org/assignments/port-numbers
diff --git a/test/socket/test_nonblock.rb b/test/socket/test_nonblock.rb
index 5a4688bac3..68fefc44b3 100644
--- a/test/socket/test_nonblock.rb
+++ b/test/socket/test_nonblock.rb
@@ -104,7 +104,7 @@ class TestSocketNonblock < Test::Unit::TestCase
assert_raise(IO::WaitReadable) { u1.recvfrom_nonblock(100) }
u2.send("", 0, u1.getsockname)
assert_nothing_raised("cygwin 1.5.19 has a problem to send an empty UDP packet. [ruby-dev:28915]") {
- Timeout.timeout(1) { IO.select [u1] }
+ Timeout.timeout(30) { IO.select [u1] }
}
mesg, inet_addr = u1.recvfrom_nonblock(100)
assert_equal("", mesg)
@@ -126,7 +126,7 @@ class TestSocketNonblock < Test::Unit::TestCase
assert_raise(IO::WaitReadable) { u1.recv_nonblock(100) }
u2.send("", 0, u1.getsockname)
assert_nothing_raised("cygwin 1.5.19 has a problem to send an empty UDP packet. [ruby-dev:28915]") {
- Timeout.timeout(1) { IO.select [u1] }
+ Timeout.timeout(30) { IO.select [u1] }
}
mesg = u1.recv_nonblock(100)
assert_equal("", mesg)
diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb
index 4d75caab50..3b5f5b9d74 100644
--- a/test/socket/test_socket.rb
+++ b/test/socket/test_socket.rb
@@ -173,8 +173,11 @@ class TestSocket < Test::Unit::TestCase
def errors_addrinuse
errs = [Errno::EADDRINUSE]
- # MinGW fails with "Errno::EACCES: Permission denied - bind(2) for 0.0.0.0:49721"
- errs << Errno::EACCES if /mingw/ =~ RUBY_PLATFORM
+ # Windows can fail with "Errno::EACCES: Permission denied - bind(2) for 0.0.0.0:49721"
+ # or "Test::Unit::ProxyError: Permission denied - bind(2) for 0.0.0.0:55333"
+ if /mswin|mingw/ =~ RUBY_PLATFORM
+ errs += [Errno::EACCES, Test::Unit::ProxyError]
+ end
errs
end
@@ -415,12 +418,16 @@ class TestSocket < Test::Unit::TestCase
ping_p = false
th = Thread.new {
- Socket.udp_server_loop_on(sockets) {|msg, msg_src|
- break if msg == "exit"
- rmsg = Marshal.dump([msg, msg_src.remote_address, msg_src.local_address])
- ping_p = true
- msg_src.reply rmsg
- }
+ begin
+ Socket.udp_server_loop_on(sockets) {|msg, msg_src|
+ break if msg == "exit"
+ rmsg = Marshal.dump([msg, msg_src.remote_address, msg_src.local_address])
+ ping_p = true
+ msg_src.reply rmsg
+ }
+ rescue Errno::ENOBUFS
+ # transient OS error on macOS CI, let client timeout and omit
+ end
}
ifaddrs.each {|ifa|
@@ -478,9 +485,11 @@ class TestSocket < Test::Unit::TestCase
}
end
- def timestamp_retry_rw(s1, s2, t1, type)
+ def timestamp_retry_rw(s1, type)
IO.pipe do |r,w|
+ t1 = Time.now
# UDP may not be reliable, keep sending until recvmsg returns:
+ s2 = Socket.new(:INET, :DGRAM, 0)
th = Thread.new do
n = 0
begin
@@ -489,84 +498,58 @@ class TestSocket < Test::Unit::TestCase
end while IO.select([r], nil, nil, 0.1).nil?
n
end
- timeout = (defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? ? 120 : 30) # for --jit-wait
+ timeout = 30
assert_equal([[s1],[],[]], IO.select([s1], nil, nil, timeout))
msg, _, _, stamp = s1.recvmsg
assert_equal("a", msg)
- assert(stamp.cmsg_is?(:SOCKET, type))
+ assert_send([stamp, :cmsg_is?, :SOCKET, type])
w.close # stop th
n = th.value
th = nil
n > 1 and
warn "UDP packet loss for #{type} over loopback, #{n} tries needed"
- t2 = Time.now.strftime("%Y-%m-%d")
- pat = Regexp.union([t1, t2].uniq)
- assert_match(pat, stamp.inspect)
- t = stamp.timestamp
- assert_match(pat, t.strftime("%Y-%m-%d"))
+ t2 = Time.now
+ assert_include(t1..t2, stamp.timestamp)
stamp
ensure
if th and !th.join(10)
th.kill.join(10)
end
+ s2.close
end
end
def test_timestamp
- return if /linux|freebsd|netbsd|openbsd|solaris|darwin/ !~ RUBY_PLATFORM
+ return if /linux|freebsd|netbsd|openbsd|darwin/ !~ RUBY_PLATFORM
return if !defined?(Socket::AncillaryData) || !defined?(Socket::SO_TIMESTAMP)
- t1 = Time.now.strftime("%Y-%m-%d")
- stamp = nil
Addrinfo.udp("127.0.0.1", 0).bind {|s1|
- Addrinfo.udp("127.0.0.1", 0).bind {|s2|
- s1.setsockopt(:SOCKET, :TIMESTAMP, true)
- stamp = timestamp_retry_rw(s1, s2, t1, :TIMESTAMP)
- }
+ s1.setsockopt(:SOCKET, :TIMESTAMP, true)
+ timestamp_retry_rw(s1, :TIMESTAMP)
}
- t = stamp.timestamp
- pat = /\.#{"%06d" % t.usec}/
- assert_match(pat, stamp.inspect)
end
def test_timestampns
return if /linux/ !~ RUBY_PLATFORM || !defined?(Socket::SO_TIMESTAMPNS)
- t1 = Time.now.strftime("%Y-%m-%d")
- stamp = nil
Addrinfo.udp("127.0.0.1", 0).bind {|s1|
- Addrinfo.udp("127.0.0.1", 0).bind {|s2|
- begin
- s1.setsockopt(:SOCKET, :TIMESTAMPNS, true)
- rescue Errno::ENOPROTOOPT
- # SO_TIMESTAMPNS is available since Linux 2.6.22
- return
- end
- stamp = timestamp_retry_rw(s1, s2, t1, :TIMESTAMPNS)
- }
+ begin
+ s1.setsockopt(:SOCKET, :TIMESTAMPNS, true)
+ rescue Errno::ENOPROTOOPT
+ # SO_TIMESTAMPNS is available since Linux 2.6.22
+ return
+ end
+ timestamp_retry_rw(s1, :TIMESTAMPNS)
}
- t = stamp.timestamp
- pat = /\.#{"%09d" % t.nsec}/
- assert_match(pat, stamp.inspect)
end
def test_bintime
return if /freebsd/ !~ RUBY_PLATFORM
- t1 = Time.now.strftime("%Y-%m-%d")
- stamp = nil
- Addrinfo.udp("127.0.0.1", 0).bind {|s1|
- Addrinfo.udp("127.0.0.1", 0).bind {|s2|
- s1.setsockopt(:SOCKET, :BINTIME, true)
- s2.send "a", 0, s1.local_address
- msg, _, _, stamp = s1.recvmsg
- assert_equal("a", msg)
- assert(stamp.cmsg_is?(:SOCKET, :BINTIME))
- }
+ stamp = Addrinfo.udp("127.0.0.1", 0).bind {|s1|
+ s1.setsockopt(:SOCKET, :BINTIME, true)
+ timestamp_retry_rw(s1, :BINTIME)
}
- t2 = Time.now.strftime("%Y-%m-%d")
- pat = Regexp.union([t1, t2].uniq)
- assert_match(pat, stamp.inspect)
t = stamp.timestamp
- assert_match(pat, t.strftime("%Y-%m-%d"))
- assert_equal(stamp.data[-8,8].unpack("Q")[0], t.subsec * 2**64)
+ data = stamp.data
+ assert_equal(data.unpack1("Q", offset: data.bytesize-8), t.subsec * 2**64)
end
def test_closed_read
@@ -906,7 +889,7 @@ class TestSocket < Test::Unit::TestCase
Addrinfo.define_singleton_method(:getaddrinfo) { |*_| sleep }
- assert_raise(Errno::ETIMEDOUT) do
+ assert_raise(IO::TimeoutError) do
Socket.tcp("localhost", port, resolv_timeout: 0.01)
end
ensure
@@ -931,12 +914,38 @@ class TestSocket < Test::Unit::TestCase
server.close
- assert_raise(Errno::ETIMEDOUT) do
+ assert_raise(IO::TimeoutError) do
Socket.tcp("localhost", port, resolv_timeout: 0.01)
end
RUBY
end
+ def test_tcp_socket_open_timeout
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ if family == Socket::AF_INET6
+ sleep
+ else
+ [Addrinfo.tcp("127.0.0.1", 12345)]
+ end
+ end
+
+ assert_raise(IO::TimeoutError) do
+ Socket.tcp("localhost", 12345, open_timeout: 0.01)
+ end
+ RUBY
+ end
+
+ def test_tcp_socket_open_timeout_with_other_timeouts
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ assert_raise(ArgumentError) do
+ Socket.tcp("localhost", 12345, open_timeout: 0.01, resolv_timout: 0.01)
+ end
+ RUBY
+ end
+
def test_tcp_socket_one_hostname_resolution_succeeded_at_least
opts = %w[-rsocket -W1]
assert_separately opts, <<~RUBY
@@ -982,7 +991,7 @@ class TestSocket < Test::Unit::TestCase
Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
case family
when Socket::AF_INET6 then raise SocketError
- when Socket::AF_INET then sleep(0.001); raise SocketError, "Last hostname resolution error"
+ when Socket::AF_INET then sleep(0.01); raise SocketError, "Last hostname resolution error"
end
end
@@ -995,6 +1004,28 @@ class TestSocket < Test::Unit::TestCase
RUBY
end
+ def test_tcp_socket_hostname_resolution_failed_after_connection_failure
+ opts = %w[-rsocket -W1]
+ assert_separately opts, <<~RUBY
+ server = TCPServer.new("127.0.0.1", 0)
+ port = server.connect_address.ip_port
+
+ Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_|
+ case family
+ when Socket::AF_INET6 then sleep(0.1); raise Socket::ResolutionError
+ when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", port)]
+ end
+ end
+
+ server.close
+
+ # SystemCallError is a workaround for Windows environment
+ assert_raise(Errno::ECONNREFUSED, SystemCallError) do
+ Socket.tcp("localhost", port)
+ end
+ RUBY
+ end
+
def test_tcp_socket_v6_address_passed
opts = %w[-rsocket -W1]
assert_separately opts, <<~RUBY
diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb
index 4984a7e7bc..d689ab2376 100644
--- a/test/socket/test_tcp.rb
+++ b/test/socket/test_tcp.rb
@@ -18,6 +18,12 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
end
def test_initialize_failure
+ assert_raise(Socket::ResolutionError) do
+ t = TCPSocket.open(nil, nil)
+ ensure
+ t&.close
+ end
+
# These addresses are chosen from TEST-NET-1, TEST-NET-2, and TEST-NET-3.
# [RFC 5737]
# They are chosen because probably they are not used as a host address.
@@ -43,16 +49,14 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
server_addr = '127.0.0.1'
server_port = 80
- begin
+ e = assert_raise_kind_of(SystemCallError) do
# Since client_addr is not an IP address of this host,
# bind() in TCPSocket.new should fail as EADDRNOTAVAIL.
t = TCPSocket.new(server_addr, server_port, client_addr, client_port)
- flunk "expected SystemCallError"
- rescue SystemCallError => e
- assert_match "for \"#{client_addr}\" port #{client_port}", e.message
+ ensure
+ t&.close
end
- ensure
- t.close if t && !t.closed?
+ assert_include e.message, "for \"#{client_addr}\" port #{client_port}"
end
def test_initialize_resolv_timeout
@@ -69,6 +73,30 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
end
end
+ def test_tcp_initialize_open_timeout
+ return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
+
+ server = TCPServer.new("127.0.0.1", 0)
+ port = server.connect_address.ip_port
+ server.close
+
+ assert_raise(IO::TimeoutError) do
+ TCPSocket.new(
+ "localhost",
+ port,
+ open_timeout: 0.01,
+ fast_fallback: true,
+ test_mode_settings: { delay: { ipv4: 1000 } }
+ )
+ end
+ end
+
+ def test_initialize_open_timeout_with_other_timeouts
+ assert_raise(ArgumentError) do
+ TCPSocket.new("localhost", 12345, open_timeout: 0.01, resolv_timeout: 0.01)
+ end
+ end
+
def test_initialize_connect_timeout
assert_raise(IO::TimeoutError, Errno::ENETUNREACH, Errno::EACCES) do
TCPSocket.new("192.0.2.1", 80, connect_timeout: 0)
@@ -293,7 +321,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
port = server.connect_address.ip_port
server.close
- assert_raise(Errno::ETIMEDOUT) do
+ assert_raise(IO::TimeoutError) do
TCPSocket.new(
"localhost",
port,
@@ -316,7 +344,7 @@ class TestSocket_TCPSocket < Test::Unit::TestCase
port = server.connect_address.ip_port
server.close
- assert_raise(Socket::ResolutionError) do
+ assert_raise(Errno::ECONNREFUSED) do
TCPSocket.new(
"localhost",
port,
diff --git a/test/socket/test_unix.rb b/test/socket/test_unix.rb
index 3e7d85befc..e239e3935b 100644
--- a/test/socket/test_unix.rb
+++ b/test/socket/test_unix.rb
@@ -146,6 +146,7 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
end
def test_fd_passing_race_condition
+ omit 'randomly crashes on macOS' if RUBY_PLATFORM =~ /darwin/
r1, w = IO.pipe
s1, s2 = UNIXSocket.pair
s1.nonblock = s2.nonblock = true
@@ -292,14 +293,18 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
File.unlink path if path && File.socket?(path)
end
- def test_open_nul_byte
- tmpfile = Tempfile.new("s")
- path = tmpfile.path
- tmpfile.close(true)
- assert_raise(ArgumentError) {UNIXServer.open(path+"\0")}
- assert_raise(ArgumentError) {UNIXSocket.open(path+"\0")}
- ensure
- File.unlink path if path && File.socket?(path)
+ def test_open_argument
+ assert_raise(TypeError) {UNIXServer.new(nil)}
+ assert_raise(TypeError) {UNIXServer.new(1)}
+ Tempfile.create("s") do |s|
+ path = s.path
+ s.close
+ File.unlink(path)
+ assert_raise(ArgumentError) {UNIXServer.open(path+"\0")}
+ assert_raise(ArgumentError) {UNIXSocket.open(path+"\0")}
+ arg = Struct.new(:to_path).new(path)
+ assert_equal(path, UNIXServer.open(arg) { |server| server.path })
+ end
end
def test_addr
diff --git a/test/stringio/test_ractor.rb b/test/stringio/test_ractor.rb
index 4a2033bc1f..6acf53fb0a 100644
--- a/test/stringio/test_ractor.rb
+++ b/test/stringio/test_ractor.rb
@@ -8,6 +8,10 @@ class TestStringIOInRactor < Test::Unit::TestCase
def test_ractor
assert_in_out_err([], <<-"end;", ["true"], [])
+ class Ractor
+ alias value take unless method_defined? :value # compat with Ruby 3.4 and olders
+ end
+
require "stringio"
$VERBOSE = nil
r = Ractor.new do
@@ -17,7 +21,7 @@ class TestStringIOInRactor < Test::Unit::TestCase
io.puts "def"
"\0\0\0\0def\n" == io.string
end
- puts r.take
+ puts r.value
end;
end
end
diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb
index 64bc5f67c3..0f61245a8a 100644
--- a/test/stringio/test_stringio.rb
+++ b/test/stringio/test_stringio.rb
@@ -14,6 +14,24 @@ class TestStringIO < Test::Unit::TestCase
include TestEOF::Seek
+ def test_do_not_mutate_shared_buffers
+ # Ensure we have two strings that are not embedded but have the same shared
+ # string reference.
+ #
+ # In this case, we must use eval because we need two strings literals that
+ # are long enough they cannot be embedded, but also contain the same bytes.
+
+ a = eval("+"+("x" * 1024).dump)
+ b = eval("+"+("x" * 1024).dump)
+
+ s = StringIO.new(b)
+ s.getc
+ s.ungetc '#'
+
+ # We mutated b, so a should not be mutated
+ assert_equal("x", a[0])
+ end
+
def test_version
assert_kind_of(String, StringIO::VERSION)
end
@@ -46,6 +64,35 @@ class TestStringIO < Test::Unit::TestCase
assert_nil io.gets
io.puts "abc"
assert_nil io.string
+
+ # Null device StringIO just drop ungot string
+ io.ungetc '#'
+ assert_nil io.getc
+ end
+
+ def test_eof_null
+ io = StringIO.new(nil)
+ assert_predicate io, :eof?
+ end
+
+ def test_pread_null
+ io = StringIO.new(nil)
+ assert_raise(EOFError) { io.pread(1, 0) }
+ end
+
+ def test_read_null
+ io = StringIO.new(nil)
+ assert_equal "", io.read(0)
+ end
+
+ def test_seek_null
+ io = StringIO.new(nil)
+ assert_equal(0, io.seek(0, IO::SEEK_SET))
+ assert_equal(0, io.pos)
+ assert_equal(0, io.seek(0, IO::SEEK_CUR))
+ assert_equal(0, io.pos)
+ assert_equal(0, io.seek(0, IO::SEEK_END)) # This should not segfault
+ assert_equal(0, io.pos)
end
def test_truncate
@@ -312,6 +359,9 @@ class TestStringIO < Test::Unit::TestCase
def test_isatty
assert_equal(false, StringIO.new("").isatty)
+ assert_equal(false, StringIO.new("").tty?)
+ assert_nothing_raised { StringIO.new("").freeze.isatty}
+ assert_nothing_raised { StringIO.new("").freeze.tty?}
end
def test_fsync
@@ -321,6 +371,7 @@ class TestStringIO < Test::Unit::TestCase
def test_sync
assert_equal(true, StringIO.new("").sync)
assert_equal(false, StringIO.new("").sync = false)
+ assert_nothing_raised { StringIO.new("").freeze.sync}
end
def test_set_fcntl
@@ -373,8 +424,8 @@ class TestStringIO < Test::Unit::TestCase
assert_equal(false, f.closed?)
f.close
assert_equal(true, f.closed?)
- ensure
- f.close unless f.closed?
+ f.freeze
+ assert_nothing_raised { f.closed? }
end
def test_closed_read
@@ -384,8 +435,8 @@ class TestStringIO < Test::Unit::TestCase
assert_equal(false, f.closed_read?)
f.close_read
assert_equal(true, f.closed_read?)
- ensure
- f.close unless f.closed?
+ f.freeze
+ assert_nothing_raised { f.closed_read? }
end
def test_closed_write
@@ -395,8 +446,8 @@ class TestStringIO < Test::Unit::TestCase
assert_equal(false, f.closed_write?)
f.close_write
assert_equal(true, f.closed_write?)
- ensure
- f.close unless f.closed?
+ f.freeze
+ assert_nothing_raised { f.closed_write? }
end
def test_dup
@@ -422,8 +473,8 @@ class TestStringIO < Test::Unit::TestCase
f.lineno = 1000
assert_equal([1000, "baz\n"], [f.lineno, f.gets])
assert_equal([1001, nil], [f.lineno, f.gets])
- ensure
- f.close unless f.closed?
+ f.freeze
+ assert_nothing_raised { f.lineno }
end
def test_pos
@@ -436,8 +487,8 @@ class TestStringIO < Test::Unit::TestCase
assert_equal([4, "bar\n"], [f.pos, f.gets])
assert_equal([8, "baz\n"], [f.pos, f.gets])
assert_equal([12, nil], [f.pos, f.gets])
- ensure
- f.close unless f.closed?
+ f.freeze
+ assert_nothing_raised { f.pos }
end
def test_reopen
@@ -461,6 +512,8 @@ class TestStringIO < Test::Unit::TestCase
assert_raise(Errno::EINVAL) { f.seek(1, 3) }
f.close
assert_raise(IOError) { f.seek(0) }
+ f = StringIO.new(-"1234")
+ assert_equal(0, f.seek(1))
ensure
f.close unless f.closed?
end
@@ -712,6 +765,8 @@ class TestStringIO < Test::Unit::TestCase
s = ""
f.read(nil, s)
assert_equal(Encoding::ASCII_8BIT, s.encoding, bug20418)
+
+ assert_raise(ArgumentError) {f.read(1, f.string)}
end
def test_readpartial
@@ -777,19 +832,28 @@ class TestStringIO < Test::Unit::TestCase
assert_raise(EOFError) { f.pread(1, 5) }
assert_raise(ArgumentError) { f.pread(-1, 0) }
+ assert_raise(ArgumentError) { f.pread(0, 0, f.string) }
assert_raise(Errno::EINVAL) { f.pread(3, -1) }
+ assert_raise(Errno::EINVAL) { f.pread(0, -1) }
+ assert_raise(IOError) { StringIO.new(nil, "w").pread(3, 0) }
+ assert_raise(TypeError) { f.pread(3, 0, []) }
assert_equal "".b, StringIO.new("").pread(0, 0)
- assert_equal "".b, StringIO.new("").pread(0, -10)
buf = "stale".b
assert_equal "stale".b, StringIO.new("").pread(0, 0, buf)
assert_equal "stale".b, buf
+
+ assert_nothing_raised { StringIO.new("pread").freeze.pread(3, 0)}
end
def test_size
f = StringIO.new("1234")
assert_equal(4, f.size)
+ assert_equal(4, f.length)
+ f.freeze
+ assert_nothing_raised { f.size }
+ assert_nothing_raised { f.length }
end
# This test is should in ruby/test_method.rb
@@ -944,7 +1008,7 @@ class TestStringIO < Test::Unit::TestCase
intptr_max = RbConfig::LIMITS["INTPTR_MAX"]
return if intptr_max > StringIO::MAX_LENGTH
limit = intptr_max - 0x10
- assert_separately(%w[-rstringio], "#{<<-"begin;"}\n#{<<-"end;"}")
+ assert_separately(%w[-W0 -rstringio], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
limit = #{limit}
ary = []
@@ -1012,6 +1076,20 @@ class TestStringIO < Test::Unit::TestCase
assert_predicate(s.string, :ascii_only?)
end
+ def test_coderange_after_read_into_buffer
+ s = StringIO.new("01234567890".b)
+
+ buf = "¿Cómo estás? Ça va bien?"
+ assert_not_predicate(buf, :ascii_only?)
+
+ assert_predicate(s.string, :ascii_only?)
+
+ s.read(10, buf)
+
+ assert_predicate(buf, :ascii_only?)
+ assert_equal '0123456789', buf
+ end
+
require "objspace"
if ObjectSpace.respond_to?(:dump) && ObjectSpace.dump(eval(%{"test"})).include?('"chilled":true') # Ruby 3.4+ chilled strings
def test_chilled_string
@@ -1030,6 +1108,72 @@ class TestStringIO < Test::Unit::TestCase
assert_equal("test", io.string)
assert_same(chilled_string, io.string)
end
+
+ def test_chilled_string_set_enocoding
+ chilled_string = eval(%{""})
+ io = StringIO.new(chilled_string)
+ assert_warning("") { io.set_encoding(Encoding::BINARY) }
+ assert_same(chilled_string, io.string)
+ end
+ end
+
+ def test_eof
+ f = StringIO.new
+ assert_equal(true, f.eof)
+ assert_equal(true, f.eof?)
+ f.ungetc("1234")
+ assert_equal(false, f.eof)
+ assert_equal(false, f.eof?)
+ f.freeze
+ assert_nothing_raised { f.eof }
+ assert_nothing_raised { f.eof? }
+ end
+
+ def test_pid
+ f = StringIO.new
+ assert_equal(nil, f.pid)
+ f.freeze
+ assert_nothing_raised { f.pid }
+ end
+
+ def test_fileno
+ f = StringIO.new
+ assert_equal(nil, f.fileno)
+ f.freeze
+ assert_nothing_raised { f.fileno }
+ end
+
+ def test_external_encoding
+ f = StringIO.new
+ assert_equal(Encoding.find("external"), f.external_encoding)
+ f = StringIO.new("1234".encode("UTF-16BE"))
+ assert_equal(Encoding::UTF_16BE, f.external_encoding)
+ f.freeze
+ assert_nothing_raised { f.external_encoding }
+ end
+
+ def test_string
+ f = StringIO.new
+ assert_equal("", f.string)
+ f = StringIO.new("1234")
+ assert_equal("1234", f.string)
+ f.freeze
+ assert_nothing_raised { f.string }
+ end
+
+ def test_initialize_copy
+ f = StringIO.new("1234")
+ f.read(1)
+ f2 = f.dup
+ assert_equal(1, f2.pos)
+ f.read(1)
+ assert_equal(2, f2.pos)
+ f.ungetc("56")
+ assert_equal(0, f2.pos)
+ assert_equal("5634", f2.string)
+ f.freeze
+ f2 = f.dup
+ assert_not_predicate(f2, :frozen?)
end
private
diff --git a/test/strscan/test_ractor.rb b/test/strscan/test_ractor.rb
index 9a279d2929..a13fd8fd13 100644
--- a/test/strscan/test_ractor.rb
+++ b/test/strscan/test_ractor.rb
@@ -8,6 +8,10 @@ class TestStringScannerRactor < Test::Unit::TestCase
def test_ractor
assert_in_out_err([], <<-"end;", ["stra", " ", "strb", " ", "strc"], [])
+ class Ractor
+ alias value take unless method_defined? :value # compat with Ruby 3.4 and olders
+ end
+
require "strscan"
$VERBOSE = nil
r = Ractor.new do
@@ -22,7 +26,7 @@ class TestStringScannerRactor < Test::Unit::TestCase
s.scan(/\\w+/)
]
end
- puts r.take.compact
+ puts r.value.compact
end;
end
end
diff --git a/test/strscan/test_stringscanner.rb b/test/strscan/test_stringscanner.rb
index 11f9b507c7..96a1badb1f 100644
--- a/test/strscan/test_stringscanner.rb
+++ b/test/strscan/test_stringscanner.rb
@@ -9,7 +9,6 @@ require 'test/unit'
module StringScannerTests
def test_peek_byte
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner('ab')
assert_equal(97, s.peek_byte)
assert_equal(97, s.scan_byte)
@@ -20,9 +19,10 @@ module StringScannerTests
end
def test_scan_byte
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner('ab')
+ assert_equal(2, s.match?(/(?<a>ab)/)) # set named_captures
assert_equal(97, s.scan_byte)
+ assert_equal({}, s.named_captures)
assert_equal(98, s.scan_byte)
assert_nil(s.scan_byte)
@@ -45,19 +45,6 @@ module StringScannerTests
assert_same(str, s.string)
end
- UNINIT_ERROR = ArgumentError
-
- def test_s_allocate
- s = StringScanner.allocate
- assert_equal('#<StringScanner (uninitialized)>', s.inspect.sub(/StringScanner_C/, 'StringScanner'))
- assert_raise(UNINIT_ERROR) { s.eos? }
- assert_raise(UNINIT_ERROR) { s.scan(/a/) }
- s.string = 'test'
- assert_equal('#<StringScanner 0/4 @ "test">', s.inspect.sub(/StringScanner_C/, 'StringScanner'))
- assert_nothing_raised(UNINIT_ERROR) { s.eos? }
- assert_equal(false, s.eos?)
- end
-
def test_s_mustc
assert_nothing_raised(NotImplementedError) {
StringScanner.must_C_version
@@ -107,11 +94,6 @@ module StringScannerTests
assert_equal(true, StringScanner::Version.frozen?)
end
- def test_const_Id
- assert_instance_of(String, StringScanner::Id)
- assert_equal(true, StringScanner::Id.frozen?)
- end
-
def test_inspect
str = 'test string'.dup
s = create_string_scanner(str, false)
@@ -178,9 +160,10 @@ module StringScannerTests
def test_string
s = create_string_scanner('test string')
assert_equal('test string', s.string)
- s.scan(/test/)
+ s.scan(/(?<t>test)/) # set named_captures
assert_equal('test string', s.string)
s.string = 'a'
+ assert_equal({}, s.named_captures)
assert_equal('a', s.string)
s.scan(/a/)
s.string = 'b'
@@ -367,7 +350,9 @@ module StringScannerTests
def test_getch
s = create_string_scanner('abcde')
+ assert_equal(3, s.match?(/(?<a>abc)/)) # set named_captures
assert_equal('a', s.getch)
+ assert_equal({}, s.named_captures)
assert_equal('b', s.getch)
assert_equal('c', s.getch)
assert_equal('d', s.getch)
@@ -386,7 +371,9 @@ module StringScannerTests
def test_get_byte
s = create_string_scanner('abcde')
+ assert_equal(3, s.match?(/(?<a>abc)/)) # set named_captures
assert_equal('a', s.get_byte)
+ assert_equal({}, s.named_captures)
assert_equal('b', s.get_byte)
assert_equal('c', s.get_byte)
assert_equal('d', s.get_byte)
@@ -409,12 +396,8 @@ module StringScannerTests
s = create_string_scanner('stra strb strc')
s.scan(/\w+/)
assert_equal('stra', s.matched)
- s.scan(/\s+/)
- assert_equal(' ', s.matched)
- s.scan('st')
- assert_equal('st', s.matched)
- s.scan(/\w+/)
- assert_equal('rb', s.matched)
+ s.scan_until(/\w+/)
+ assert_equal('strb', s.matched)
s.scan(/\s+/)
assert_equal(' ', s.matched)
s.scan(/\w+/)
@@ -432,10 +415,50 @@ module StringScannerTests
assert_equal('t', s.matched)
end
+ def test_matched_string
+ s = create_string_scanner('stra strb strc')
+ s.scan('stra')
+ assert_equal('stra', s.matched)
+ s.scan_until('strb')
+ assert_equal('strb', s.matched)
+ s.scan(' ')
+ assert_equal(' ', s.matched)
+ s.scan('strc')
+ assert_equal('strc', s.matched)
+ s.scan('c')
+ assert_nil(s.matched)
+ s.getch
+ assert_nil(s.matched)
+ end
+
def test_AREF
s = create_string_scanner('stra strb strc')
- s.scan(/\w+/)
+ s.scan(/\s+/)
+ assert_nil( s[-2])
+ assert_nil( s[-1])
+ assert_nil( s[0])
+ assert_nil( s[1])
+ assert_nil( s[:c])
+ assert_nil( s['c'])
+
+ s.scan("not match")
+ assert_nil( s[-2])
+ assert_nil( s[-1])
+ assert_nil( s[0])
+ assert_nil( s[1])
+ assert_nil( s[:c])
+ assert_nil( s['c'])
+
+ s.check(/\w+/)
+ assert_nil( s[-2])
+ assert_equal('stra', s[-1])
+ assert_equal('stra', s[0])
+ assert_nil( s[1])
+ assert_raise(IndexError) { s[:c] }
+ assert_raise(IndexError) { s['c'] }
+
+ s.scan("stra")
assert_nil( s[-2])
assert_equal('stra', s[-1])
assert_equal('stra', s[0])
@@ -502,6 +525,59 @@ module StringScannerTests
end
end
+ def assert_integer_at(s, specifier, *to_i_args)
+ assert_equal(s[specifier]&.to_i(*to_i_args),
+ s.integer_at(specifier, *to_i_args))
+ end
+
+ def test_integer_at
+ s = create_string_scanner("before 20260514 after")
+ s.skip_until(" ")
+ assert_equal("20260514", s.scan(/(\d{4})(\d{2})(\d{2})/))
+ assert_integer_at(s, 0) # 20260514
+ assert_integer_at(s, 1) # 2026
+ assert_integer_at(s, 2) # 5
+ assert_integer_at(s, 3) # 14
+ assert_integer_at(s, 4) # nil
+ assert_integer_at(s, -1) # 14
+ assert_integer_at(s, -2) # 5
+ assert_integer_at(s, -3) # 2026
+ assert_integer_at(s, -4) # 20260514
+ assert_integer_at(s, -5) # nil
+ end
+
+ def test_integer_at_name_string
+ s = create_string_scanner("before 20260514 after")
+ s.skip_until(" ")
+ assert_equal("20260514", s.scan(/(?<y>\d{4})(?<m>\d{2})(?<d>\d{2})/))
+ assert_integer_at(s, "y")
+ assert_integer_at(s, "m")
+ assert_integer_at(s, "d")
+ end
+
+ def test_integer_at_name_symbol
+ s = create_string_scanner("before 20260514 after")
+ s.skip_until(" ")
+ assert_equal("20260514", s.scan(/(?<y>\d{4})(?<m>\d{2})(?<d>\d{2})/))
+ assert_integer_at(s, :y)
+ assert_integer_at(s, :m)
+ assert_integer_at(s, :d)
+ end
+
+ def test_integer_at_base
+ s = create_string_scanner("before 111 after")
+ s.skip_until(" ")
+ assert_equal("111", s.scan(/\d+/))
+ assert_integer_at(s, 0, 2)
+ end
+
+ def test_integer_at_base_auto
+ s = create_string_scanner("before 0xa_f after")
+ s.skip_until(" ")
+ assert_equal("0xa_f", s.scan(/0x[\h_]+/))
+ assert_integer_at(s, 0, 0) # 0xaf
+ end
+
def test_pre_match
s = create_string_scanner('a b c d e')
s.scan(/\w/)
@@ -522,6 +598,26 @@ module StringScannerTests
assert_nil(s.pre_match)
end
+ def test_pre_match_string
+ s = create_string_scanner('a b c d e')
+ s.scan('a')
+ assert_equal('', s.pre_match)
+ s.skip(' ')
+ assert_equal('a', s.pre_match)
+ s.scan('b')
+ assert_equal('a ', s.pre_match)
+ s.scan_until('c')
+ assert_equal('a b ', s.pre_match)
+ s.getch
+ assert_equal('a b c', s.pre_match)
+ s.get_byte
+ assert_equal('a b c ', s.pre_match)
+ s.get_byte
+ assert_equal('a b c d', s.pre_match)
+ s.scan('never match')
+ assert_nil(s.pre_match)
+ end
+
def test_post_match
s = create_string_scanner('a b c d e')
s.scan(/\w/)
@@ -546,19 +642,41 @@ module StringScannerTests
assert_nil(s.post_match)
end
- def test_terminate
- s = create_string_scanner('ssss')
+ def test_post_match_string
+ s = create_string_scanner('a b c d e')
+ s.scan('a')
+ assert_equal(' b c d e', s.post_match)
+ s.skip(' ')
+ assert_equal('b c d e', s.post_match)
+ s.scan('b')
+ assert_equal(' c d e', s.post_match)
+ s.scan_until('c')
+ assert_equal(' d e', s.post_match)
s.getch
+ assert_equal('d e', s.post_match)
+ s.get_byte
+ assert_equal(' e', s.post_match)
+ s.get_byte
+ assert_equal('e', s.post_match)
+ s.scan('never match')
+ assert_nil(s.post_match)
+ end
+
+ def test_terminate
+ s = create_string_scanner('abcd')
+ s.scan(/(?<a>ab)/) # set named_captures
s.terminate
+ assert_equal({}, s.named_captures)
assert_equal(true, s.eos?)
s.terminate
assert_equal(true, s.eos?)
end
def test_reset
- s = create_string_scanner('ssss')
- s.getch
+ s = create_string_scanner('abcd')
+ s.scan(/(?<a>ab)/) # set named_captures
s.reset
+ assert_equal({}, s.named_captures)
assert_equal(0, s.pos)
s.scan(/\w+/)
s.reset
@@ -611,8 +729,6 @@ module StringScannerTests
end
def test_invalid_encoding_string
- omit("no encoding check on TruffleRuby for scan(String)") if RUBY_ENGINE == "truffleruby"
-
str = "\xA1\xA2".dup.force_encoding("euc-jp")
ss = create_string_scanner(str)
assert_raise(Encoding::CompatibilityError) do
@@ -682,7 +798,6 @@ module StringScannerTests
end
def test_exist_p_string
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner("test string")
assert_equal(3, s.exist?("s"))
assert_equal(0, s.pos)
@@ -704,7 +819,6 @@ module StringScannerTests
end
def test_scan_until_string
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner("Foo Bar\0Baz")
assert_equal("Foo", s.scan_until("Foo"))
assert_equal(3, s.pos)
@@ -728,7 +842,6 @@ module StringScannerTests
end
def test_skip_until_string
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner("Foo Bar Baz")
assert_equal(3, s.skip_until("Foo"))
assert_equal(3, s.pos)
@@ -747,7 +860,6 @@ module StringScannerTests
end
def test_check_until_string
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner("Foo Bar Baz")
assert_equal("Foo", s.check_until("Foo"))
assert_equal(0, s.pos)
@@ -769,7 +881,6 @@ module StringScannerTests
end
def test_search_full_string
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner("Foo Bar Baz")
assert_equal(8, s.search_full("Bar ", false, false))
assert_equal(0, s.pos)
@@ -794,11 +905,12 @@ module StringScannerTests
def test_unscan
s = create_string_scanner('test string')
- assert_equal("test", s.scan(/\w+/))
+ assert_equal(4, s.skip(/(?<t>test)/)) # set named_captures
s.unscan
+ assert_equal({}, s.named_captures)
assert_equal("te", s.scan(/../))
assert_equal(nil, s.scan(/\d/))
- assert_raise(ScanError) { s.unscan }
+ assert_raise(StringScanner::Error) { s.unscan }
end
def test_rest
@@ -828,15 +940,13 @@ module StringScannerTests
end
def test_aref_without_regex
- omit "#[:missing] always raises on TruffleRuby if matched" if RUBY_ENGINE == "truffleruby"
-
s = create_string_scanner('abc')
s.get_byte
- assert_nil(s[:c])
- assert_nil(s["c"])
+ assert_raise(IndexError) { s[:c] }
+ assert_raise(IndexError) { s['c'] }
s.getch
- assert_nil(s[:c])
- assert_nil(s["c"])
+ assert_raise(IndexError) { s[:c] }
+ assert_raise(IndexError) { s['c'] }
end
def test_size
@@ -884,18 +994,25 @@ module StringScannerTests
end
def test_named_captures
- omit("not implemented on TruffleRuby") if ["truffleruby"].include?(RUBY_ENGINE)
scan = StringScanner.new("foobarbaz")
assert_equal({}, scan.named_captures)
assert_equal(9, scan.match?(/(?<f>foo)(?<r>bar)(?<z>baz)/))
assert_equal({"f" => "foo", "r" => "bar", "z" => "baz"}, scan.named_captures)
+ assert_equal(9, scan.match?("foobarbaz"))
+ assert_equal({}, scan.named_captures)
end
- def test_scan_integer
- omit("scan_integer isn't implemented on TruffleRuby yet") if RUBY_ENGINE == "truffleruby"
+ def test_named_captures_same_name_union
+ scan = StringScanner.new("123")
+ assert_equal(1, scan.match?(/(?<number>0)|(?<number>1)|(?<number>2)/))
+ assert_equal({"number" => "1"}, scan.named_captures)
+ end
+ def test_scan_integer
s = create_string_scanner('abc')
+ assert_equal(3, s.match?(/(?<a>abc)/)) # set named_captures
assert_nil(s.scan_integer)
+ assert_equal({}, s.named_captures)
assert_equal(0, s.pos)
refute_predicate(s, :matched?)
@@ -919,16 +1036,30 @@ module StringScannerTests
assert_equal(0, s.pos)
refute_predicate(s, :matched?)
+ s = create_string_scanner('-')
+ assert_nil(s.scan_integer)
+ assert_equal(0, s.pos)
+ refute_predicate(s, :matched?)
+
+ s = create_string_scanner('+')
+ assert_nil(s.scan_integer)
+ assert_equal(0, s.pos)
+ refute_predicate(s, :matched?)
+
huge_integer = '1' * 2_000
s = create_string_scanner(huge_integer)
assert_equal(huge_integer.to_i, s.scan_integer)
assert_equal(2_000, s.pos)
assert_predicate(s, :matched?)
+
+ s = create_string_scanner('abc1')
+ s.pos = 3
+ assert_equal(1, s.scan_integer)
+ assert_equal(4, s.pos)
+ assert_predicate(s, :matched?)
end
def test_scan_integer_unmatch
- omit("scan_integer isn't implemented on TruffleRuby yet") if RUBY_ENGINE == "truffleruby"
-
s = create_string_scanner('123abc')
assert_equal(123, s.scan_integer)
assert_equal(3, s.pos)
@@ -938,24 +1069,32 @@ module StringScannerTests
end
def test_scan_integer_encoding
- omit("scan_integer isn't implemented on TruffleRuby yet") if RUBY_ENGINE == "truffleruby"
-
s = create_string_scanner('123abc'.encode(Encoding::UTF_32LE))
assert_raise(Encoding::CompatibilityError) do
s.scan_integer
end
end
- def test_scan_integer_base_16
- omit("scan_integer isn't implemented on TruffleRuby yet") if RUBY_ENGINE == "truffleruby"
+ def test_scan_integer_matched
+ s = create_string_scanner("42abc")
+ assert_equal(42, s.scan_integer)
+ assert_equal("42", s.matched)
+ s = create_string_scanner("42abc")
+ assert_equal(0x42abc, s.scan_integer(base: 16))
+ assert_equal("42abc", s.matched)
+ end
+
+ def test_scan_integer_base_16
s = create_string_scanner('0')
assert_equal(0x0, s.scan_integer(base: 16))
assert_equal(1, s.pos)
assert_predicate(s, :matched?)
s = create_string_scanner('abc')
+ assert_equal(3, s.match?(/(?<a>abc)/)) # set named_captures
assert_equal(0xabc, s.scan_integer(base: 16))
+ assert_equal({}, s.named_captures)
assert_equal(3, s.pos)
assert_predicate(s, :matched?)
@@ -985,19 +1124,24 @@ module StringScannerTests
assert_predicate(s, :matched?)
s = create_string_scanner('0x')
- assert_nil(s.scan_integer(base: 16))
- assert_equal(0, s.pos)
- refute_predicate(s, :matched?)
+ assert_equal(0, s.scan_integer(base: 16))
+ assert_equal(1, s.pos)
+ assert_predicate(s, :matched?)
+
+ s = create_string_scanner('0xyz')
+ assert_equal(0, s.scan_integer(base: 16))
+ assert_equal(1, s.pos)
+ assert_predicate(s, :matched?)
s = create_string_scanner('-0x')
- assert_nil(s.scan_integer(base: 16))
- assert_equal(0, s.pos)
- refute_predicate(s, :matched?)
+ assert_equal(0, s.scan_integer(base: 16))
+ assert_equal(2, s.pos)
+ assert_predicate(s, :matched?)
s = create_string_scanner('+0x')
- assert_nil(s.scan_integer(base: 16))
- assert_equal(0, s.pos)
- refute_predicate(s, :matched?)
+ assert_equal(0, s.scan_integer(base: 16))
+ assert_equal(2, s.pos)
+ assert_predicate(s, :matched?)
s = create_string_scanner('-123abc')
assert_equal(-0x123abc, s.scan_integer(base: 16))
diff --git a/test/test_bundled_gems.rb b/test/test_bundled_gems.rb
index 19546dd296..0889584185 100644
--- a/test/test_bundled_gems.rb
+++ b/test/test_bundled_gems.rb
@@ -32,4 +32,42 @@ class TestBundlerGem < Gem::TestCase
assert Gem::BUNDLED_GEMS.warning?(path, specs: {})
assert_nil Gem::BUNDLED_GEMS.warning?(path, specs: {})
end
+
+ def test_no_warning_for_hyphenated_gem
+ # When benchmark-ips gem is in specs, requiring "benchmark/ips" should not warn
+ # about the benchmark gem (Bug #21828)
+ assert_nil Gem::BUNDLED_GEMS.warning?("benchmark/ips", specs: {"benchmark-ips" => true})
+ end
+
+ def test_no_warning_for_subfeatures_of_hyphenated_gem
+ # When benchmark-ips gem is in specs, requiring any "benchmark/*" subfeature
+ # should not warn, since hyphenated gems may provide multiple files
+ # (e.g., benchmark-ips provides benchmark/ips, benchmark/timing, benchmark/compare)
+ assert_nil Gem::BUNDLED_GEMS.warning?("benchmark/timing", specs: {"benchmark-ips" => true})
+ assert_nil Gem::BUNDLED_GEMS.warning?("benchmark/compare", specs: {"benchmark-ips" => true})
+ end
+
+ def test_warning_without_hyphenated_gem
+ # When benchmark-ips is NOT in specs, requiring "benchmark/ips" should warn
+ warning = Gem::BUNDLED_GEMS.warning?("benchmark/ips", specs: {})
+ assert warning
+ assert_match(/benchmark/, warning)
+ end
+
+ def test_no_warning_for_subfeature_found_outside_stdlib
+ # When a subfeature like "benchmark/ips" is found on $LOAD_PATH
+ # from a non-standard-library location (e.g., benchmark-ips gem's lib dir),
+ # don't warn even if the gem is not in specs (Bug #21828)
+ Dir.mktmpdir do |dir|
+ FileUtils.mkdir_p(File.join(dir, "benchmark"))
+ File.write(File.join(dir, "benchmark", "ips.rb"), "")
+ original_load_path = $LOAD_PATH.dup
+ $LOAD_PATH.unshift(dir)
+ begin
+ assert_nil Gem::BUNDLED_GEMS.warning?("benchmark/ips", specs: {})
+ ensure
+ $LOAD_PATH.replace(original_load_path)
+ end
+ end
+ end
end
diff --git a/test/test_delegate.rb b/test/test_delegate.rb
index f7bedf37fb..7aa90cb0c6 100644
--- a/test/test_delegate.rb
+++ b/test/test_delegate.rb
@@ -23,7 +23,7 @@ class TestDelegateClass < Test::Unit::TestCase
def test_systemcallerror_eq
e = SystemCallError.new(0)
- assert((SimpleDelegator.new(e) == e) == (e == SimpleDelegator.new(e)), "[ruby-dev:34808]")
+ assert_equal((SimpleDelegator.new(e) == e), (e == SimpleDelegator.new(e)), "[ruby-dev:34808]")
end
class Myclass < DelegateClass(Array);end
@@ -93,15 +93,21 @@ class TestDelegateClass < Test::Unit::TestCase
end
class Parent
- def parent_public; end
+ def parent_public
+ :public
+ end
protected
- def parent_protected; end
+ def parent_protected
+ :protected
+ end
private
- def parent_private; end
+ def parent_private
+ :private
+ end
end
class Child < DelegateClass(Parent)
@@ -157,6 +163,29 @@ class TestDelegateClass < Test::Unit::TestCase
assert_instance_of UnboundMethod, Child.public_instance_method(:to_s)
end
+ def test_call_visibiltiy
+ obj = Child.new(Parent.new)
+ assert_equal :public, obj.parent_public
+ assert_equal :protected, obj.__send__(:parent_protected)
+ assert_raise(NoMethodError) { obj.__send__(:parent_private) }
+ end
+
+ class ClassWithInvalidName
+ define_method(:" ") { :space }
+ define_method(:"\t") { :tab }
+ protected :"\t"
+ end
+
+ def test_delegateclass_invalid_name
+ delegate = DelegateClass(ClassWithInvalidName)
+ instance = delegate.new(ClassWithInvalidName.new)
+ assert_equal :space, instance.send(:" ")
+ assert_equal :space, instance.__send__(:" ")
+
+ assert_equal :tab, instance.send(:"\t")
+ assert_equal :tab, instance.__send__(:"\t")
+ end
+
class IV < DelegateClass(Integer)
attr_accessor :var
@@ -181,8 +210,8 @@ class TestDelegateClass < Test::Unit::TestCase
assert_nothing_raised(bug2679) {d.dup[0] += 1}
assert_raise(FrozenError) {d.clone[0] += 1}
d.freeze
- assert(d.clone.frozen?)
- assert(!d.dup.frozen?)
+ assert_predicate(d.clone, :frozen?)
+ assert_not_predicate(d.dup, :frozen?)
end
def test_frozen
@@ -390,4 +419,20 @@ class TestDelegateClass < Test::Unit::TestCase
a = DelegateClass(k).new(k.new)
assert_equal([1, 0], a.test(1, k: 0))
end
+
+ def test_delegate_class_can_be_used_in_ractors
+ omit "no Ractor#value" unless defined?(Ractor) && Ractor.method_defined?(:value)
+ require_path = File.expand_path(File.join(__dir__, "..", "lib", "delegate.rb"))
+ raise "file doesn't exist: #{require_path}" unless File.exist?(require_path)
+ assert_ractor <<-RUBY
+ require "#{require_path}"
+ class MyClass < DelegateClass(Array);end
+ values = 2.times.map do
+ Ractor.new do
+ MyClass.new([1,2,3]).at(0)
+ end
+ end.map(&:value)
+ assert_equal [1,1], values
+ RUBY
+ end
end
diff --git a/test/test_extlibs.rb b/test/test_extlibs.rb
index 0b4dec359d..122eca3f5c 100644
--- a/test/test_extlibs.rb
+++ b/test/test_extlibs.rb
@@ -10,7 +10,7 @@ class TestExtLibs < Test::Unit::TestCase
add_msg = ". #{add_msg}" if add_msg
log = "#{@extdir}/#{ext}/mkmf.log"
define_method("test_existence_of_#{ext}") do
- assert_separately([], <<-"end;", ignore_stderr: true) # do
+ assert_separately([], <<-"end;", ignore_stderr: true, timeout: 60) # do
log = #{log.dump}
msg = proc {
"extension library `#{ext}' is not found#{add_msg}\n" <<
@@ -53,7 +53,6 @@ class TestExtLibs < Test::Unit::TestCase
check_existence "etc"
check_existence "fcntl"
check_existence "fiber"
- check_existence "fiddle"
check_existence "io/console"
check_existence "io/nonblock"
check_existence "io/wait"
diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb
index f2b7ed713f..9725ab31c1 100644
--- a/test/test_ipaddr.rb
+++ b/test/test_ipaddr.rb
@@ -196,6 +196,24 @@ class TC_IPAddr < Test::Unit::TestCase
}
assert_equal("::192.168.1.2", b.to_s)
assert_equal(Socket::AF_INET6, b.family)
+ assert_equal(128, b.prefix)
+
+ a = IPAddr.new("192.168.0.0/16")
+ assert_warning(/obsolete/) {
+ b = a.ipv4_compat
+ }
+ assert_equal("::192.168.0.0", b.to_s)
+ assert_equal(Socket::AF_INET6, b.family)
+ assert_equal(112, b.prefix)
+ end
+
+ def test_ipv4_compat_with_error_message
+ e = assert_raise(IPAddr::InvalidAddressError) do
+ assert_warning(/obsolete/) {
+ IPAddr.new('2001:db8::').ipv4_compat
+ }
+ end
+ assert_equal('not an IPv4 address: 2001:db8::', e.message)
end
def test_ipv4_mapped
@@ -215,6 +233,13 @@ class TC_IPAddr < Test::Unit::TestCase
assert_equal(Socket::AF_INET6, b.family)
end
+ def test_ipv4_mapped_with_error_message
+ e = assert_raise(IPAddr::InvalidAddressError) do
+ IPAddr.new('2001:db8::').ipv4_mapped
+ end
+ assert_equal('not an IPv4 address: 2001:db8::', e.message)
+ end
+
def test_reverse
assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa", IPAddr.new("3ffe:505:2::f").reverse)
assert_equal("1.2.168.192.in-addr.arpa", IPAddr.new("192.168.2.1").reverse)
@@ -222,16 +247,18 @@ class TC_IPAddr < Test::Unit::TestCase
def test_ip6_arpa
assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa", IPAddr.new("3ffe:505:2::f").ip6_arpa)
- assert_raise(IPAddr::InvalidAddressError) {
+ e = assert_raise(IPAddr::InvalidAddressError) {
IPAddr.new("192.168.2.1").ip6_arpa
}
+ assert_equal('not an IPv6 address: 192.168.2.1', e.message)
end
def test_ip6_int
assert_equal("f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.int", IPAddr.new("3ffe:505:2::f").ip6_int)
- assert_raise(IPAddr::InvalidAddressError) {
+ e = assert_raise(IPAddr::InvalidAddressError) {
IPAddr.new("192.168.2.1").ip6_int
}
+ assert_equal('not an IPv6 address: 192.168.2.1', e.message)
end
def test_prefix_writer
@@ -399,6 +426,46 @@ class TC_Operator < Test::Unit::TestCase
assert_equal("::", @in6_addr_any.to_s)
end
+ def test_plus
+ a = IPAddr.new("192.168.1.10")
+ assert_equal("192.168.1.20", (a + 10).to_s)
+
+ a = IPAddr.new("0.0.0.0")
+ assert_equal("0.0.0.10", (a + 10).to_s)
+
+ a = IPAddr.new("255.255.255.255")
+ assert_raise(IPAddr::InvalidAddressError) { a + 10 }
+
+ a = IPAddr.new("3ffe:505:2::a")
+ assert_equal("3ffe:505:2::14", (a + 10).to_s)
+
+ a = IPAddr.new("::")
+ assert_equal("::a", (a + 10).to_s)
+
+ a = IPAddr.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
+ assert_raise(IPAddr::InvalidAddressError) { a + 10 }
+ end
+
+ def test_minus
+ a = IPAddr.new("192.168.1.10")
+ assert_equal("192.168.1.0", (a - 10).to_s)
+
+ a = IPAddr.new("0.0.0.0")
+ assert_raise(IPAddr::InvalidAddressError) { a - 10 }
+
+ a = IPAddr.new("255.255.255.255")
+ assert_equal("255.255.255.245", (a - 10).to_s)
+
+ a = IPAddr.new("3ffe:505:2::a")
+ assert_equal("3ffe:505:2::", (a - 10).to_s)
+
+ a = IPAddr.new("::")
+ assert_raise(IPAddr::InvalidAddressError) { a - 10 }
+
+ a = IPAddr.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
+ assert_equal("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff5", (a - 10).to_s)
+ end
+
def test_equal
assert_equal(true, @a == IPAddr.new("3FFE:505:2::"))
assert_equal(true, @a == IPAddr.new("3ffe:0505:0002::"))
@@ -408,6 +475,8 @@ class TC_Operator < Test::Unit::TestCase
assert_equal(false, @a != IPAddr.new("3ffe:505:2::"))
assert_equal(false, @a == @inconvertible_range)
assert_equal(false, @a == @inconvertible_string)
+ assert_equal(false, IPAddr.new("0.0.0.0") == nil)
+ assert_equal(false, IPAddr.new("::") == nil)
end
def test_compare
@@ -466,6 +535,9 @@ class TC_Operator < Test::Unit::TestCase
assert_equal(false, IPAddr.new('::ffff:0.0.0.0').loopback?)
assert_equal(false, IPAddr.new('::ffff:192.168.2.0').loopback?)
assert_equal(false, IPAddr.new('::ffff:255.0.0.0').loopback?)
+
+ # Global unicast addresses with 0xffff in group 5 must not be mistaken for ::ffff:127.x.x.x
+ assert_equal(false, IPAddr.new('2001:db8:1:1:0:ffff:7f00:1').loopback?)
end
def test_private?
@@ -516,6 +588,10 @@ class TC_Operator < Test::Unit::TestCase
assert_equal(false, IPAddr.new('::ffff:192.169.0.0').private?)
assert_equal(false, IPAddr.new('::ffff:169.254.0.1').private?)
+
+ # Global unicast addresses with 0xffff in group 5 must not be mistaken for ::ffff:10/172.16/192.168.x
+ assert_equal(false, IPAddr.new('2001:718:1404:c8:0:ffff:ac19:c80e').private?)
+ assert_equal(false, IPAddr.new('2001:db8:1:1:0:ffff:c0a8:1').private?)
end
def test_link_local?
@@ -542,6 +618,9 @@ class TC_Operator < Test::Unit::TestCase
assert_equal(true, IPAddr.new('::ffff:169.254.1.1').link_local?)
assert_equal(true, IPAddr.new('::ffff:169.254.254.255').link_local?)
+
+ # Global unicast addresses with 0xffff in group 5 must not be mistaken for ::ffff:169.254.x.x
+ assert_equal(false, IPAddr.new('2001:db8:1:1:0:ffff:a9fe:101').link_local?)
end
def test_hash
@@ -571,4 +650,21 @@ class TC_Operator < Test::Unit::TestCase
assert_equal(true, s.include?(a5))
assert_equal(true, s.include?(a6))
end
+
+ def test_raises_invalid_address_error_with_error_message
+ e = assert_raise(IPAddr::InvalidAddressError) do
+ IPAddr.new('192.168.0.1000')
+ end
+ assert_equal('invalid address: 192.168.0.1000', e.message)
+
+ e = assert_raise(IPAddr::InvalidAddressError) do
+ IPAddr.new('192.168.01.100')
+ end
+ assert_equal('zero-filled number in IPv4 address is ambiguous: 192.168.01.100', e.message)
+
+ e = assert_raise(IPAddr::InvalidAddressError) do
+ IPAddr.new('INVALID')
+ end
+ assert_equal('invalid address: INVALID', e.message)
+ end
end
diff --git a/test/test_pp.rb b/test/test_pp.rb
index 2646846d8b..922ed371af 100644
--- a/test/test_pp.rb
+++ b/test/test_pp.rb
@@ -2,11 +2,14 @@
require 'pp'
require 'delegate'
+require 'set'
require 'test/unit'
require 'ruby2_keywords'
module PPTestModule
+SetPP = Set.instance_method(:pretty_print).source_location[0].end_with?("/pp.rb")
+
class PPTest < Test::Unit::TestCase
def test_list0123_12
assert_equal("[0, 1, 2, 3]\n", PP.pp([0,1,2,3], ''.dup, 12))
@@ -16,6 +19,10 @@ class PPTest < Test::Unit::TestCase
assert_equal("[0,\n 1,\n 2,\n 3]\n", PP.pp([0,1,2,3], ''.dup, 11))
end
+ def test_set
+ assert_equal("Set[0, 1, 2, 3]\n", PP.pp(Set[0,1,2,3], ''.dup, 16))
+ end if SetPP
+
OverriddenStruct = Struct.new("OverriddenStruct", :members, :class)
def test_struct_override_members # [ruby-core:7865]
a = OverriddenStruct.new(1,2)
@@ -130,6 +137,22 @@ class PPInspectTest < Test::Unit::TestCase
assert_equal("#{a.inspect}\n", result)
end
+ def test_iv_hiding
+ a = Object.new
+ def a.pretty_print_instance_variables() [:@b] end
+ a.instance_eval { @a = "aaa"; @b = "bbb" }
+ assert_match(/\A#<Object:0x[\da-f]+ @b="bbb">\n\z/, PP.pp(a, ''.dup))
+ end
+
+ def test_iv_hiding_via_ruby
+ a = Object.new
+ a.singleton_class.class_eval do
+ private def instance_variables_to_inspect() [:@b] end
+ end
+ a.instance_eval { @a = "aaa"; @b = "bbb" }
+ assert_match(/\A#<Object:0x[\da-f]+ @b="bbb">\n\z/, PP.pp(a, ''.dup))
+ end
+
def test_basic_object
a = BasicObject.new
assert_match(/\A#<BasicObject:0x[\da-f]+>\n\z/, PP.pp(a, ''.dup))
@@ -150,6 +173,12 @@ class PPCycleTest < Test::Unit::TestCase
assert_equal("#{a.inspect}\n", PP.pp(a, ''.dup))
end
+ def test_set
+ s = Set[]
+ s.add s
+ assert_equal("Set[Set[...]]\n", PP.pp(s, ''.dup))
+ end if SetPP
+
S = Struct.new("S", :a, :b)
def test_struct
a = S.new(1,2)
@@ -158,7 +187,14 @@ class PPCycleTest < Test::Unit::TestCase
assert_equal("#{a.inspect}\n", PP.pp(a, ''.dup)) unless RUBY_ENGINE == "truffleruby"
end
- if defined?(Data.define)
+ verbose, $VERBOSE = $VERBOSE, nil
+ begin
+ has_data_define = defined?(Data.define)
+ ensure
+ $VERBOSE = verbose
+ end
+
+ if has_data_define
D = Data.define(:aaa, :bbb)
def test_data
a = D.new("aaa", "bbb")
@@ -223,7 +259,6 @@ class PPSingleLineTest < Test::Unit::TestCase
end
def test_hash_symbol_colon_key
- omit if RUBY_VERSION < "3.4."
no_quote = "{a: 1, a!: 1, a?: 1}"
unicode_quote = "{\u{3042}: 1}"
quote0 = '{"": 1}'
@@ -236,12 +271,41 @@ class PPSingleLineTest < Test::Unit::TestCase
assert_equal(quote1, PP.singleline_pp(eval(quote1), ''.dup))
assert_equal(quote2, PP.singleline_pp(eval(quote2), ''.dup))
assert_equal(quote3, PP.singleline_pp(eval(quote3), ''.dup))
- end
+ end if RUBY_VERSION >= "3.4."
def test_hash_in_array
omit if RUBY_ENGINE == "jruby"
- assert_equal("[{}]", PP.singleline_pp([->(*a){a.last.clear}.ruby2_keywords.call(a: 1)], ''.dup))
- assert_equal("[{}]", PP.singleline_pp([Hash.ruby2_keywords_hash({})], ''.dup))
+ assert_equal("[{}]", passing_keywords {PP.singleline_pp([->(*a){a.last.clear}.ruby2_keywords.call(a: 1)], ''.dup)})
+ assert_equal("[{}]", passing_keywords {PP.singleline_pp([Hash.ruby2_keywords_hash({})], ''.dup)})
+ end
+
+ if RUBY_VERSION >= "3.0"
+ def passing_keywords(&_)
+ yield
+ end
+ else
+ def passing_keywords(&_)
+ verbose, $VERBOSE = $VERBOSE, nil
+ yield
+ ensure
+ $VERBOSE = verbose
+ end
+ end
+
+ def test_direct_pp
+ buffer = String.new
+
+ a = []
+ a << a
+
+ # Isolate the test from any existing Thread.current[:__recursive_key__][:inspect].
+ Thread.new do
+ q = PP::SingleLine.new(buffer)
+ q.pp(a)
+ q.flush
+ end.join
+
+ assert_equal("[[...]]", buffer)
end
end
diff --git a/test/test_prettyprint.rb b/test/test_prettyprint.rb
index 27e7198886..a9ea55d5b3 100644
--- a/test/test_prettyprint.rb
+++ b/test/test_prettyprint.rb
@@ -518,4 +518,75 @@ End
end
+class SingleLineFormat < Test::Unit::TestCase # :nodoc:
+
+ def test_singleline_format_with_breakables
+ singleline_format = PrettyPrint.singleline_format("".dup) do |q|
+ q.group 0, "(", ")" do
+ q.text "abc"
+ q.breakable
+ q.text "def"
+ q.breakable
+ q.text "ghi"
+ q.breakable
+ q.text "jkl"
+ q.breakable
+ q.text "mno"
+ q.breakable
+ q.text "pqr"
+ q.breakable
+ q.text "stu"
+ end
+ end
+ expected = <<'End'.chomp
+(abc def ghi jkl mno pqr stu)
+End
+
+ assert_equal(expected, singleline_format)
+ end
+
+ def test_singleline_format_with_fill_breakables
+ singleline_format = PrettyPrint.singleline_format("".dup) do |q|
+ q.group 0, "(", ")" do
+ q.text "abc"
+ q.fill_breakable
+ q.text "def"
+ q.fill_breakable
+ q.text "ghi"
+ q.fill_breakable
+ q.text "jkl"
+ q.fill_breakable
+ q.text "mno"
+ q.fill_breakable
+ q.text "pqr"
+ q.fill_breakable
+ q.text "stu"
+ end
+ end
+ expected = <<'End'.chomp
+(abc def ghi jkl mno pqr stu)
+End
+
+ assert_equal(expected, singleline_format)
+ end
+
+ def test_singleline_format_with_group_sub
+ singleline_format = PrettyPrint.singleline_format("".dup) do |q|
+ q.group 0, "(", ")" do
+ q.group_sub do
+ q.text "abc"
+ q.breakable
+ q.text "def"
+ end
+ q.breakable
+ q.text "ghi"
+ end
+ end
+ expected = <<'End'.chomp
+(abc def ghi)
+End
+
+ assert_equal(expected, singleline_format)
+ end
+end
end
diff --git a/test/test_pty.rb b/test/test_pty.rb
index 1c0c6fb3e8..81b0d394ff 100644
--- a/test/test_pty.rb
+++ b/test/test_pty.rb
@@ -12,7 +12,7 @@ class TestPTY < Test::Unit::TestCase
RUBY = EnvUtil.rubybin
def test_spawn_without_block
- r, w, pid = PTY.spawn(RUBY, '-e', 'puts "a"')
+ r, w, pid = PTY.spawn(RUBY, '-e', 'puts "a"; sleep 0.1')
rescue RuntimeError
omit $!
else
@@ -24,7 +24,7 @@ class TestPTY < Test::Unit::TestCase
end
def test_spawn_with_block
- PTY.spawn(RUBY, '-e', 'puts "b"') {|r,w,pid|
+ PTY.spawn(RUBY, '-e', 'puts "b"; sleep 0.1') {|r,w,pid|
begin
assert_equal("b\r\n", r.gets)
ensure
@@ -38,7 +38,7 @@ class TestPTY < Test::Unit::TestCase
end
def test_commandline
- commandline = Shellwords.join([RUBY, '-e', 'puts "foo"'])
+ commandline = Shellwords.join([RUBY, '-e', 'puts "foo"; sleep 0.1'])
PTY.spawn(commandline) {|r,w,pid|
begin
assert_equal("foo\r\n", r.gets)
@@ -53,7 +53,7 @@ class TestPTY < Test::Unit::TestCase
end
def test_argv0
- PTY.spawn([RUBY, "argv0"], '-e', 'puts "bar"') {|r,w,pid|
+ PTY.spawn([RUBY, "argv0"], '-e', 'puts "bar"; sleep 0.1') {|r,w,pid|
begin
assert_equal("bar\r\n", r.gets)
ensure
diff --git a/test/test_rbconfig.rb b/test/test_rbconfig.rb
index 1bbf01b9a6..e01264762d 100644
--- a/test/test_rbconfig.rb
+++ b/test/test_rbconfig.rb
@@ -51,4 +51,19 @@ class TestRbConfig < Test::Unit::TestCase
assert_match(/\$\(sitearch|\$\(rubysitearchprefix\)/, val, "#{key} #{bug7823}")
end
end
+
+ def test_limits_and_sizeof_access_in_ractor
+ assert_separately(["-W0"], <<~'RUBY')
+ r = Ractor.new do
+ sizeof_int = RbConfig::SIZEOF["int"]
+ fixnum_max = RbConfig::LIMITS["FIXNUM_MAX"]
+ [sizeof_int, fixnum_max]
+ end
+
+ sizeof_int, fixnum_max = r.value
+
+ assert_kind_of Integer, sizeof_int, "RbConfig::SIZEOF['int'] should be an Integer"
+ assert_kind_of Integer, fixnum_max, "RbConfig::LIMITS['FIXNUM_MAX'] should be an Integer"
+ RUBY
+ end if defined?(Ractor)
end
diff --git a/test/test_time.rb b/test/test_time.rb
index 23e8e104a1..55964d02fc 100644
--- a/test/test_time.rb
+++ b/test/test_time.rb
@@ -74,7 +74,7 @@ class TestTimeExtension < Test::Unit::TestCase # :nodoc:
if defined?(Ractor)
def test_rfc2822_ractor
assert_ractor(<<~RUBY, require: 'time')
- actual = Ractor.new { Time.rfc2822("Fri, 21 Nov 1997 09:55:06 -0600") }.take
+ actual = Ractor.new { Time.rfc2822("Fri, 21 Nov 1997 09:55:06 -0600") }.value
assert_equal(Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600, actual)
RUBY
end
diff --git a/test/test_timeout.rb b/test/test_timeout.rb
index 01156867b0..2703a0314d 100644
--- a/test/test_timeout.rb
+++ b/test/test_timeout.rb
@@ -4,6 +4,23 @@ require 'timeout'
class TestTimeout < Test::Unit::TestCase
+ private def kill_timeout_thread
+ thread = Timeout.const_get(:State).instance.instance_variable_get(:@timeout_thread)
+ if thread
+ thread.kill
+ thread.join
+ end
+ end
+
+ def test_public_methods
+ assert_equal [:timeout], Timeout.private_instance_methods(false)
+ assert_equal [], Timeout.public_instance_methods(false)
+
+ assert_equal [:timeout], Timeout.singleton_class.public_instance_methods(false)
+
+ assert_equal [:Error, :ExitException, :VERSION], Timeout.constants.sort
+ end
+
def test_work_is_done_in_same_thread_as_caller
assert_equal Thread.current, Timeout.timeout(10){ Thread.current }
end
@@ -37,6 +54,12 @@ class TestTimeout < Test::Unit::TestCase
end
end
+ def test_raise_for_string_argument
+ assert_raise(NoMethodError) do
+ Timeout.timeout("1") { sleep(0.01) }
+ end
+ end
+
def test_included
c = Class.new do
include Timeout
@@ -105,8 +128,8 @@ class TestTimeout < Test::Unit::TestCase
def test_nested_timeout_which_error_bubbles_up
raised_exception = nil
begin
- Timeout.timeout(0.1) {
- Timeout.timeout(1) {
+ Timeout.timeout(1) {
+ Timeout.timeout(10) {
raise Timeout::ExitException.new("inner message")
}
}
@@ -212,6 +235,24 @@ class TestTimeout < Test::Unit::TestCase
end
end
+ def test_handle_interrupt_with_exception_class
+ bug11344 = '[ruby-dev:49179] [Bug #11344]'
+ ok = false
+ assert_raise(Timeout::Error) {
+ Thread.handle_interrupt(Timeout::Error => :never) {
+ Timeout.timeout(0.01, Timeout::Error) {
+ sleep 0.2
+ ok = true
+ Thread.handle_interrupt(Timeout::Error => :on_blocking) {
+ sleep 0.2
+ raise "unreachable"
+ }
+ }
+ }
+ }
+ assert(ok, bug11344)
+ end
+
def test_handle_interrupt
bug11344 = '[ruby-dev:49179] [Bug #11344]'
ok = false
@@ -222,6 +263,7 @@ class TestTimeout < Test::Unit::TestCase
ok = true
Thread.handle_interrupt(Timeout::ExitException => :on_blocking) {
sleep 0.2
+ raise "unreachable"
}
}
}
@@ -229,6 +271,94 @@ class TestTimeout < Test::Unit::TestCase
assert(ok, bug11344)
end
+ def test_handle_interrupt_with_interrupt_mask_inheritance
+ issue = 'https://github.com/ruby/timeout/issues/41'
+
+ [
+ -> {}, # not blocking so no opportunity to interrupt
+ -> { sleep 5 }
+ ].each_with_index do |body, idx|
+ # We need to create a new Timeout thread
+ kill_timeout_thread
+
+ # Create the timeout thread under a handle_interrupt(:never)
+ # due to the interrupt mask being inherited
+ Thread.handle_interrupt(Object => :never) {
+ assert_equal :ok, Timeout.timeout(1) { :ok }
+ }
+
+ # Ensure a simple timeout works and the interrupt mask was not inherited
+ assert_raise(Timeout::Error) {
+ Timeout.timeout(0.001) { sleep 1 }
+ }
+
+ r = []
+ # This raises Timeout::ExitException and not Timeout::Error for the non-blocking body
+ # because of the handle_interrupt(:never) which delays raising Timeout::ExitException
+ # on the main thread until getting outside of that handle_interrupt(:never) call.
+ # For this reason we document handle_interrupt(Timeout::ExitException) should not be used.
+ exc = idx == 0 ? Timeout::ExitException : Timeout::Error
+ assert_raise(exc) {
+ Thread.handle_interrupt(Timeout::ExitException => :never) {
+ Timeout.timeout(0.1) do
+ sleep 0.2
+ r << :sleep_before_done
+ Thread.handle_interrupt(Timeout::ExitException => :on_blocking) {
+ r << :body
+ body.call
+ }
+ ensure
+ sleep 0.2
+ r << :ensure_sleep_done
+ end
+ }
+ }
+ assert_equal([:sleep_before_done, :body, :ensure_sleep_done], r, issue)
+ end
+ end
+
+ # Same as above but with an exception class
+ def test_handle_interrupt_with_interrupt_mask_inheritance_with_exception_class
+ issue = 'https://github.com/ruby/timeout/issues/41'
+
+ [
+ -> {}, # not blocking so no opportunity to interrupt
+ -> { sleep 5 }
+ ].each do |body|
+ # We need to create a new Timeout thread
+ kill_timeout_thread
+
+ # Create the timeout thread under a handle_interrupt(:never)
+ # due to the interrupt mask being inherited
+ Thread.handle_interrupt(Object => :never) {
+ assert_equal :ok, Timeout.timeout(1) { :ok }
+ }
+
+ # Ensure a simple timeout works and the interrupt mask was not inherited
+ assert_raise(Timeout::Error) {
+ Timeout.timeout(0.001) { sleep 1 }
+ }
+
+ r = []
+ assert_raise(Timeout::Error) {
+ Thread.handle_interrupt(Timeout::Error => :never) {
+ Timeout.timeout(0.1, Timeout::Error) do
+ sleep 0.2
+ r << :sleep_before_done
+ Thread.handle_interrupt(Timeout::Error => :on_blocking) {
+ r << :body
+ body.call
+ }
+ ensure
+ sleep 0.2
+ r << :ensure_sleep_done
+ end
+ }
+ }
+ assert_equal([:sleep_before_done, :body, :ensure_sleep_done], r, issue)
+ end
+ end
+
def test_fork
omit 'fork not supported' unless Process.respond_to?(:fork)
r, w = IO.pipe
@@ -274,4 +404,134 @@ class TestTimeout < Test::Unit::TestCase
}.join
end;
end
+
+ def test_ractor
+ assert_separately(%w[-rtimeout -W0], <<-'end;')
+ r = Ractor.new do
+ Timeout.timeout(1) { 42 }
+ end.value
+
+ assert_equal 42, r
+
+ r = Ractor.new do
+ begin
+ Timeout.timeout(0.1) { sleep }
+ rescue Timeout::Error
+ :ok
+ end
+ end.value
+
+ assert_equal :ok, r
+ end;
+ end if defined?(::Ractor) && RUBY_VERSION >= '4.0'
+
+ def test_timeout_in_trap_handler
+ # https://github.com/ruby/timeout/issues/17
+
+ # Test as if this was the first timeout usage
+ kill_timeout_thread
+
+ rd, wr = IO.pipe
+
+ signal = :TERM
+
+ original_handler = trap(signal) do
+ begin
+ Timeout.timeout(0.1) do
+ sleep 1
+ end
+ rescue Timeout::Error
+ wr.write "OK"
+ wr.close
+ else
+ wr.write "did not raise"
+ ensure
+ wr.close
+ end
+ end
+
+ begin
+ Process.kill signal, Process.pid
+
+ assert_equal "OK", rd.read
+ rd.close
+ ensure
+ trap(signal, original_handler)
+ end
+ end
+
+ if Fiber.respond_to?(:current_scheduler)
+ # Stubs Fiber.current_scheduler for the duration of the block, then restores it.
+ def with_mock_scheduler(mock)
+ original = Fiber.method(:current_scheduler)
+ Fiber.singleton_class.remove_method(:current_scheduler)
+ Fiber.define_singleton_method(:current_scheduler) { mock }
+ begin
+ yield
+ ensure
+ Fiber.singleton_class.remove_method(:current_scheduler)
+ Fiber.define_singleton_method(:current_scheduler, original)
+ end
+ end
+
+ def test_fiber_scheduler_delegates_to_timeout_after
+ received = nil
+ mock = Object.new
+ mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk|
+ received = [sec, exc, msg]
+ blk.call(sec)
+ end
+
+ with_mock_scheduler(mock) do
+ assert_equal :ok, Timeout.timeout(5) { :ok }
+ end
+
+ assert_equal 5, received[0]
+ assert_instance_of Timeout::ExitException, received[1], "scheduler should receive an ExitException instance when no klass given"
+ assert_equal "execution expired", received[2]
+ end
+
+ def test_fiber_scheduler_delegates_to_timeout_after_with_custom_exception
+ custom_error = Class.new(StandardError)
+ received = nil
+ mock = Object.new
+ mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk|
+ received = [sec, exc, msg]
+ blk.call(sec)
+ end
+
+ with_mock_scheduler(mock) do
+ assert_equal :ok, Timeout.timeout(5, custom_error, "custom message") { :ok }
+ end
+
+ assert_equal [5, custom_error, "custom message"], received
+ end
+
+ def test_fiber_scheduler_timeout_raises_timeout_error
+ mock = Object.new
+ mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk|
+ raise exc # simulate timeout firing
+ end
+
+ with_mock_scheduler(mock) do
+ assert_raise(Timeout::Error) do
+ Timeout.timeout(5) { :should_not_reach }
+ end
+ end
+ end
+
+ def test_fiber_scheduler_timeout_raises_custom_error
+ custom_error = Class.new(StandardError)
+ mock = Object.new
+ mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk|
+ raise exc, msg
+ end
+
+ with_mock_scheduler(mock) do
+ assert_raise_with_message(custom_error, "custom message") do
+ Timeout.timeout(5, custom_error, "custom message") { :should_not_reach }
+ end
+ end
+ end
+ end
end
diff --git a/test/test_tmpdir.rb b/test/test_tmpdir.rb
index adc29183a8..c91fc334ed 100644
--- a/test/test_tmpdir.rb
+++ b/test/test_tmpdir.rb
@@ -134,17 +134,32 @@ class TestTmpdir < Test::Unit::TestCase
def test_ractor
assert_ractor(<<~'end;', require: "tmpdir")
- r = Ractor.new do
- Dir.mktmpdir() do |d|
- Ractor.yield d
- Ractor.receive
+ if defined?(Ractor::Port)
+ port = Ractor::Port.new
+ r = Ractor.new port do |port|
+ Dir.mktmpdir() do |d|
+ port << d
+ Ractor.receive
+ end
+ end
+ dir = port.receive
+ assert_file.directory? dir
+ r.send true
+ r.join
+ assert_file.not_exist? dir
+ else
+ r = Ractor.new do
+ Dir.mktmpdir() do |d|
+ Ractor.yield d
+ Ractor.receive
+ end
end
+ dir = r.take
+ assert_file.directory? dir
+ r.send true
+ r.take
+ assert_file.not_exist? dir
end
- dir = r.take
- assert_file.directory? dir
- r.send true
- r.take
- assert_file.not_exist? dir
end;
end
end
diff --git a/test/test_tsort.rb b/test/test_tsort.rb
deleted file mode 100644
index 354d928908..0000000000
--- a/test/test_tsort.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-# frozen_string_literal: true
-
-require 'tsort'
-require 'test/unit'
-
-class TSortHash < Hash # :nodoc:
- include TSort
- alias tsort_each_node each_key
- def tsort_each_child(node, &block)
- fetch(node).each(&block)
- end
-end
-
-class TSortArray < Array # :nodoc:
- include TSort
- alias tsort_each_node each_index
- def tsort_each_child(node, &block)
- fetch(node).each(&block)
- end
-end
-
-class TSortTest < Test::Unit::TestCase # :nodoc:
- def test_dag
- h = TSortHash[{1=>[2, 3], 2=>[3], 3=>[]}]
- assert_equal([3, 2, 1], h.tsort)
- assert_equal([[3], [2], [1]], h.strongly_connected_components)
- end
-
- def test_cycle
- h = TSortHash[{1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}]
- assert_equal([[4], [2, 3], [1]],
- h.strongly_connected_components.map {|nodes| nodes.sort})
- assert_raise(TSort::Cyclic) { h.tsort }
- end
-
- def test_array
- a = TSortArray[[1], [0], [0], [2]]
- assert_equal([[0, 1], [2], [3]],
- a.strongly_connected_components.map {|nodes| nodes.sort})
-
- a = TSortArray[[], [0]]
- assert_equal([[0], [1]],
- a.strongly_connected_components.map {|nodes| nodes.sort})
- end
-
- def test_s_tsort
- g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
- each_node = lambda {|&b| g.each_key(&b) }
- each_child = lambda {|n, &b| g[n].each(&b) }
- assert_equal([4, 2, 3, 1], TSort.tsort(each_node, each_child))
- g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
- assert_raise(TSort::Cyclic) { TSort.tsort(each_node, each_child) }
- end
-
- def test_s_tsort_each
- g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
- each_node = lambda {|&b| g.each_key(&b) }
- each_child = lambda {|n, &b| g[n].each(&b) }
- r = []
- TSort.tsort_each(each_node, each_child) {|n| r << n }
- assert_equal([4, 2, 3, 1], r)
-
- r = TSort.tsort_each(each_node, each_child).map {|n| n.to_s }
- assert_equal(['4', '2', '3', '1'], r)
- end
-
- def test_s_strongly_connected_components
- g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
- each_node = lambda {|&b| g.each_key(&b) }
- each_child = lambda {|n, &b| g[n].each(&b) }
- assert_equal([[4], [2], [3], [1]],
- TSort.strongly_connected_components(each_node, each_child))
- g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
- assert_equal([[4], [2, 3], [1]],
- TSort.strongly_connected_components(each_node, each_child))
- end
-
- def test_s_each_strongly_connected_component
- g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
- each_node = lambda {|&b| g.each_key(&b) }
- each_child = lambda {|n, &b| g[n].each(&b) }
- r = []
- TSort.each_strongly_connected_component(each_node, each_child) {|scc|
- r << scc
- }
- assert_equal([[4], [2], [3], [1]], r)
- g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
- r = []
- TSort.each_strongly_connected_component(each_node, each_child) {|scc|
- r << scc
- }
- assert_equal([[4], [2, 3], [1]], r)
-
- r = TSort.each_strongly_connected_component(each_node, each_child).map {|scc|
- scc.map(&:to_s)
- }
- assert_equal([['4'], ['2', '3'], ['1']], r)
- end
-
- def test_s_each_strongly_connected_component_from
- g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
- each_child = lambda {|n, &b| g[n].each(&b) }
- r = []
- TSort.each_strongly_connected_component_from(1, each_child) {|scc|
- r << scc
- }
- assert_equal([[4], [2, 3], [1]], r)
-
- r = TSort.each_strongly_connected_component_from(1, each_child).map {|scc|
- scc.map(&:to_s)
- }
- assert_equal([['4'], ['2', '3'], ['1']], r)
- end
-end
-
diff --git a/test/test_unicode_normalize.rb b/test/test_unicode_normalize.rb
index 8789ed92d2..dd06d27131 100644
--- a/test/test_unicode_normalize.rb
+++ b/test/test_unicode_normalize.rb
@@ -209,4 +209,32 @@ class TestUnicodeNormalize
assert_equal true, ascii_string.unicode_normalized?(:nfkc)
assert_equal true, ascii_string.unicode_normalized?(:nfkd)
end
+
+ def test_bug_21559
+ str = "s\u{1611e}\u{323}\u{1611e}\u{307}\u{1611f}"
+ assert_equal str.unicode_normalize(:nfd), str.unicode_normalize(:nfc).unicode_normalize(:nfd)
+ end
+
+ def test_gurung_khema
+ assert_equal "\u{16121 16121 16121 16121 16121 1611E}", "\u{1611E 16121 16121 16121 16121 16121}".unicode_normalize
+ end
+
+ def test_canonical_ordering
+ a = "\u03B1\u0313\u0300\u0345"
+ a_unordered1 = "\u03B1\u0345\u0313\u0300"
+ a_unordered2 = "\u03B1\u0313\u0345\u0300"
+ u1 = "U\u0308\u0304"
+ u2 = "U\u0304\u0308"
+ s = "s\u0323\u0307"
+ s_unordered = "s\u0307\u0323"
+ o = "\u{1611e}\u{1611e}\u{1611f}"
+ # Actual cases called through String#unicode_normalize
+ assert_equal(s + o, UnicodeNormalize.canonical_ordering_one(s_unordered + o))
+ assert_equal(a[1..], UnicodeNormalize.canonical_ordering_one(a_unordered1[1..]))
+ assert_equal(a[1..] + o, UnicodeNormalize.canonical_ordering_one(a_unordered2[1..] + o))
+ # Artificial cases
+ assert_equal(a + u1 + o + u2 + s, UnicodeNormalize.canonical_ordering_one(a + u1 + o + u2 + s))
+ assert_equal(s[1..] + a + a, UnicodeNormalize.canonical_ordering_one(s_unordered[1..] + a_unordered1 + a_unordered2))
+ assert_equal(o + s + u1 + a + o + a + u2 + o, UnicodeNormalize.canonical_ordering_one(o + s_unordered + u1 + a_unordered1 + o + a_unordered2 + u2 + o))
+ end
end
diff --git a/test/uri/test_common.rb b/test/uri/test_common.rb
index 6326aec561..569264005a 100644
--- a/test/uri/test_common.rb
+++ b/test/uri/test_common.rb
@@ -31,12 +31,14 @@ class URI::TestCommon < Test::Unit::TestCase
def test_parser_switch
assert_equal(URI::Parser, URI::RFC3986_Parser)
+ assert_equal(URI::PARSER, URI::RFC3986_PARSER)
refute defined?(URI::REGEXP)
refute defined?(URI::PATTERN)
URI.parser = URI::RFC2396_PARSER
assert_equal(URI::Parser, URI::RFC2396_Parser)
+ assert_equal(URI::PARSER, URI::RFC2396_PARSER)
assert defined?(URI::REGEXP)
assert defined?(URI::PATTERN)
assert defined?(URI::PATTERN::ESCAPED)
@@ -45,6 +47,7 @@ class URI::TestCommon < Test::Unit::TestCase
URI.parser = URI::RFC3986_PARSER
assert_equal(URI::Parser, URI::RFC3986_Parser)
+ assert_equal(URI::PARSER, URI::RFC3986_PARSER)
refute defined?(URI::REGEXP)
refute defined?(URI::PATTERN)
ensure
@@ -75,7 +78,7 @@ class URI::TestCommon < Test::Unit::TestCase
return unless defined?(Ractor)
assert_ractor(<<~RUBY, require: 'uri')
r = Ractor.new { URI.parse("https://ruby-lang.org/").inspect }
- assert_equal(URI.parse("https://ruby-lang.org/").inspect, r.take)
+ assert_equal(URI.parse("https://ruby-lang.org/").inspect, r.value)
RUBY
end
@@ -113,17 +116,18 @@ class URI::TestCommon < Test::Unit::TestCase
def test_register_scheme_with_symbols
# Valid schemes from https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
- some_uri_class = Class.new(URI::Generic)
- assert_raise(NameError) { URI.register_scheme 'ms-search', some_uri_class }
- assert_raise(NameError) { URI.register_scheme 'microsoft.windows.camera', some_uri_class }
- assert_raise(NameError) { URI.register_scheme 'coaps+ws', some_uri_class }
+ list = []
+ %w[ms-search microsoft.windows.camera coaps+ws].each {|name|
+ list << [name, URI.register_scheme(name, Class.new(URI::Generic))]
+ }
- ms_search_class = Class.new(URI::Generic)
- URI.register_scheme 'MS_SEARCH', ms_search_class
- begin
- assert_equal URI::Generic, URI.parse('ms-search://localhost').class
- ensure
- URI.const_get(:Schemes).send(:remove_const, :MS_SEARCH)
+ list.each do |scheme, uri_class|
+ assert_equal uri_class, URI.parse("#{scheme}://localhost").class
+ end
+ ensure
+ schemes = URI.const_get(:Schemes)
+ list.each do |scheme, |
+ schemes.send(:remove_const, schemes.escape(scheme))
end
end
diff --git a/test/uri/test_ftp.rb b/test/uri/test_ftp.rb
index f45bb0667c..3ad7864490 100644
--- a/test/uri/test_ftp.rb
+++ b/test/uri/test_ftp.rb
@@ -33,11 +33,11 @@ class URI::TestFTP < Test::Unit::TestCase
# If you think what's below is wrong, please read RubyForge bug 2055,
# RFC 1738 section 3.2.2, and RFC 2396.
u = URI.parse('ftp://ftp.example.com/foo/bar/file.ext')
- assert(u.path == 'foo/bar/file.ext')
+ assert_equal('foo/bar/file.ext', u.path)
u = URI.parse('ftp://ftp.example.com//foo/bar/file.ext')
- assert(u.path == '/foo/bar/file.ext')
+ assert_equal('/foo/bar/file.ext', u.path)
u = URI.parse('ftp://ftp.example.com/%2Ffoo/bar/file.ext')
- assert(u.path == '/foo/bar/file.ext')
+ assert_equal('/foo/bar/file.ext', u.path)
end
def test_assemble
@@ -45,8 +45,8 @@ class URI::TestFTP < Test::Unit::TestCase
# assuming everyone else has implemented RFC 2396.
uri = URI::FTP.build(['user:password', 'ftp.example.com', nil,
'/path/file.zip', 'i'])
- assert(uri.to_s ==
- 'ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i')
+ assert_equal('ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i',
+ uri.to_s)
end
def test_select
diff --git a/test/uri/test_generic.rb b/test/uri/test_generic.rb
index 8209363b82..94eea71b51 100644
--- a/test/uri/test_generic.rb
+++ b/test/uri/test_generic.rb
@@ -175,6 +175,17 @@ class URI::TestGeneric < Test::Unit::TestCase
# must be empty string to identify as path-abempty, not path-absolute
assert_equal('', url.host)
assert_equal('http:////example.com', url.to_s)
+
+ # sec-2957667
+ url = URI.parse('http://user:pass@example.com').merge('//example.net')
+ assert_equal('http://example.net', url.to_s)
+ assert_nil(url.userinfo)
+ url = URI.join('http://user:pass@example.com', '//example.net')
+ assert_equal('http://example.net', url.to_s)
+ assert_nil(url.userinfo)
+ url = URI.parse('http://user:pass@example.com') + '//example.net'
+ assert_equal('http://example.net', url.to_s)
+ assert_nil(url.userinfo)
end
def test_parse_scheme_with_symbols
@@ -229,9 +240,9 @@ class URI::TestGeneric < Test::Unit::TestCase
u = URI.parse('http://foo/bar/baz')
assert_equal(nil, u.merge!(""))
assert_equal(nil, u.merge!(u))
- assert(nil != u.merge!("."))
+ refute_nil(u.merge!("."))
assert_equal('http://foo/bar/', u.to_s)
- assert(nil != u.merge!("../baz"))
+ refute_nil(u.merge!("../baz"))
assert_equal('http://foo/baz', u.to_s)
url = URI.parse('http://a/b//c') + 'd//e'
@@ -267,6 +278,16 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal(u0, u1)
end
+ def test_merge_authority
+ u = URI.parse('http://user:pass@example.com:8080')
+ u0 = URI.parse('http://new.example.org/path')
+ u1 = u.merge('//new.example.org/path')
+ assert_equal(u0, u1)
+ u0 = URI.parse('http://other@example.net')
+ u1 = u.merge('//other@example.net')
+ assert_equal(u0, u1)
+ end
+
def test_route
url = URI.parse('http://hoge/a.html').route_to('http://hoge/b.html')
assert_equal('b.html', url.to_s)
@@ -338,7 +359,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/c/g', url.to_s)
url = @base_url.route_to('http://a/b/c/g')
assert_kind_of(URI::Generic, url)
- assert('./g' != url.to_s) # ok
+ refute_equal('./g', url.to_s) # ok
assert_equal('g', url.to_s)
# http://a/b/c/d;p?q
@@ -357,7 +378,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/g', url.to_s)
url = @base_url.route_to('http://a/g')
assert_kind_of(URI::Generic, url)
- assert('/g' != url.to_s) # ok
+ refute_equal('/g', url.to_s) # ok
assert_equal('../../g', url.to_s)
# http://a/b/c/d;p?q
@@ -448,7 +469,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/c/', url.to_s)
url = @base_url.route_to('http://a/b/c/')
assert_kind_of(URI::Generic, url)
- assert('.' != url.to_s) # ok
+ refute_equal('.', url.to_s) # ok
assert_equal('./', url.to_s)
# http://a/b/c/d;p?q
@@ -467,7 +488,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/', url.to_s)
url = @base_url.route_to('http://a/b/')
assert_kind_of(URI::Generic, url)
- assert('..' != url.to_s) # ok
+ refute_equal('..', url.to_s) # ok
assert_equal('../', url.to_s)
# http://a/b/c/d;p?q
@@ -495,7 +516,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/', url.to_s)
url = @base_url.route_to('http://a/')
assert_kind_of(URI::Generic, url)
- assert('../..' != url.to_s) # ok
+ refute_equal('../..', url.to_s) # ok
assert_equal('../../', url.to_s)
# http://a/b/c/d;p?q
@@ -586,7 +607,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/g', url.to_s)
url = @base_url.route_to('http://a/g')
assert_kind_of(URI::Generic, url)
- assert('../../../g' != url.to_s) # ok? yes, it confuses you
+ refute_equal('../../../g', url.to_s) # ok? yes, it confuses you
assert_equal('../../g', url.to_s) # and it is clearly
# http://a/b/c/d;p?q
@@ -596,7 +617,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/g', url.to_s)
url = @base_url.route_to('http://a/g')
assert_kind_of(URI::Generic, url)
- assert('../../../../g' != url.to_s) # ok? yes, it confuses you
+ refute_equal('../../../../g', url.to_s) # ok? yes, it confuses you
assert_equal('../../g', url.to_s) # and it is clearly
# http://a/b/c/d;p?q
@@ -606,7 +627,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/g', url.to_s)
url = @base_url.route_to('http://a/b/g')
assert_kind_of(URI::Generic, url)
- assert('./../g' != url.to_s) # ok
+ refute_equal('./../g', url.to_s) # ok
assert_equal('../g', url.to_s)
# http://a/b/c/d;p?q
@@ -616,7 +637,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/c/g/', url.to_s)
url = @base_url.route_to('http://a/b/c/g/')
assert_kind_of(URI::Generic, url)
- assert('./g/.' != url.to_s) # ok
+ refute_equal('./g/.', url.to_s) # ok
assert_equal('g/', url.to_s)
# http://a/b/c/d;p?q
@@ -626,7 +647,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/c/g/h', url.to_s)
url = @base_url.route_to('http://a/b/c/g/h')
assert_kind_of(URI::Generic, url)
- assert('g/./h' != url.to_s) # ok
+ refute_equal('g/./h', url.to_s) # ok
assert_equal('g/h', url.to_s)
# http://a/b/c/d;p?q
@@ -636,7 +657,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/c/h', url.to_s)
url = @base_url.route_to('http://a/b/c/h')
assert_kind_of(URI::Generic, url)
- assert('g/../h' != url.to_s) # ok
+ refute_equal('g/../h', url.to_s) # ok
assert_equal('h', url.to_s)
# http://a/b/c/d;p?q
@@ -646,7 +667,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/c/g;x=1/y', url.to_s)
url = @base_url.route_to('http://a/b/c/g;x=1/y')
assert_kind_of(URI::Generic, url)
- assert('g;x=1/./y' != url.to_s) # ok
+ refute_equal('g;x=1/./y', url.to_s) # ok
assert_equal('g;x=1/y', url.to_s)
# http://a/b/c/d;p?q
@@ -656,7 +677,7 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal('http://a/b/c/y', url.to_s)
url = @base_url.route_to('http://a/b/c/y')
assert_kind_of(URI::Generic, url)
- assert('g;x=1/../y' != url.to_s) # ok
+ refute_equal('g;x=1/../y', url.to_s) # ok
assert_equal('y', url.to_s)
# http://a/b/c/d;p?q
@@ -730,17 +751,18 @@ class URI::TestGeneric < Test::Unit::TestCase
def test_set_component
uri = URI.parse('http://foo:bar@baz')
assert_equal('oof', uri.user = 'oof')
- assert_equal('http://oof:bar@baz', uri.to_s)
+ assert_equal('http://oof@baz', uri.to_s)
assert_equal('rab', uri.password = 'rab')
assert_equal('http://oof:rab@baz', uri.to_s)
assert_equal('foo', uri.userinfo = 'foo')
- assert_equal('http://foo:rab@baz', uri.to_s)
+ assert_equal('http://foo@baz', uri.to_s)
assert_equal(['foo', 'bar'], uri.userinfo = ['foo', 'bar'])
assert_equal('http://foo:bar@baz', uri.to_s)
assert_equal(['foo'], uri.userinfo = ['foo'])
- assert_equal('http://foo:bar@baz', uri.to_s)
+ assert_equal('http://foo@baz', uri.to_s)
assert_equal('zab', uri.host = 'zab')
- assert_equal('http://foo:bar@zab', uri.to_s)
+ assert_equal('http://zab', uri.to_s)
+ uri.userinfo = ['foo', 'bar']
uri.port = ""
assert_nil(uri.port)
uri.port = "80"
@@ -750,7 +772,8 @@ class URI::TestGeneric < Test::Unit::TestCase
uri.port = " 080 "
assert_equal(80, uri.port)
assert_equal(8080, uri.port = 8080)
- assert_equal('http://foo:bar@zab:8080', uri.to_s)
+ assert_equal('http://zab:8080', uri.to_s)
+ uri = URI.parse('http://foo:bar@zab:8080')
assert_equal('/', uri.path = '/')
assert_equal('http://foo:bar@zab:8080/', uri.to_s)
assert_equal('a=1', uri.query = 'a=1')
@@ -804,18 +827,18 @@ class URI::TestGeneric < Test::Unit::TestCase
hierarchical = URI.parse('http://a.b.c/example')
opaque = URI.parse('mailto:mduerst@ifi.unizh.ch')
- assert hierarchical.hierarchical?
- refute opaque.hierarchical?
+ assert_predicate hierarchical, :hierarchical?
+ refute_predicate opaque, :hierarchical?
end
def test_absolute
abs_uri = URI.parse('http://a.b.c/')
not_abs = URI.parse('a.b.c')
- refute not_abs.absolute?
+ refute_predicate not_abs, :absolute?
- assert abs_uri.absolute
- assert abs_uri.absolute?
+ assert_predicate abs_uri, :absolute
+ assert_predicate abs_uri, :absolute?
end
def test_ipv6
@@ -828,8 +851,10 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal("http://[::1]/bar", u.to_s)
u.hostname = "::1"
assert_equal("http://[::1]/bar", u.to_s)
- u.hostname = ""
- assert_equal("http:///bar", u.to_s)
+
+ u = URI("file://foo/bar")
+ u.hostname = ''
+ assert_equal("file:///bar", u.to_s)
end
def test_build
@@ -850,6 +875,19 @@ class URI::TestGeneric < Test::Unit::TestCase
assert_equal("http://[::1]/bar/baz", u.to_s)
assert_equal("[::1]", u.host)
assert_equal("::1", u.hostname)
+
+ assert_raise_with_message(ArgumentError, /URI::Generic/) {
+ URI::Generic.build(nil)
+ }
+
+ c = Class.new(URI::Generic) do
+ def self.component; raise; end
+ end
+ expected = /\(#{URI::Generic::COMPONENT.join(', ')}\)/
+ message = "fallback to URI::Generic::COMPONENT if component raised"
+ assert_raise_with_message(ArgumentError, expected, message) {
+ c.build(nil)
+ }
end
def test_build2
diff --git a/test/uri/test_http.rb b/test/uri/test_http.rb
index e937b1a26b..8816d20175 100644
--- a/test/uri/test_http.rb
+++ b/test/uri/test_http.rb
@@ -19,6 +19,10 @@ class URI::TestHTTP < Test::Unit::TestCase
assert_kind_of(URI::HTTP, u)
end
+ def test_build_empty_host
+ assert_raise(URI::InvalidComponentError) { URI::HTTP.build(host: '') }
+ end
+
def test_parse
u = URI.parse('http://a')
assert_kind_of(URI::HTTP, u)
@@ -33,19 +37,19 @@ class URI::TestHTTP < Test::Unit::TestCase
host = 'aBcD'
u1 = URI.parse('http://' + host + '/eFg?HiJ')
u2 = URI.parse('http://' + host.downcase + '/eFg?HiJ')
- assert(u1.normalize.host == 'abcd')
- assert(u1.normalize.path == u1.path)
- assert(u1.normalize == u2.normalize)
- assert(!u1.normalize.host.equal?(u1.host))
- assert( u2.normalize.host.equal?(u2.host))
+ assert_equal('abcd', u1.normalize.host)
+ assert_equal(u1.path, u1.normalize.path)
+ assert_equal(u2.normalize, u1.normalize)
+ refute_same(u1.host, u1.normalize.host)
+ assert_same(u2.host, u2.normalize.host)
assert_equal('http://abc/', URI.parse('http://abc').normalize.to_s)
end
def test_equal
- assert(URI.parse('http://abc') == URI.parse('http://ABC'))
- assert(URI.parse('http://abc/def') == URI.parse('http://ABC/def'))
- assert(URI.parse('http://abc/def') != URI.parse('http://ABC/DEF'))
+ assert_equal(URI.parse('http://ABC'), URI.parse('http://abc'))
+ assert_equal(URI.parse('http://ABC/def'), URI.parse('http://abc/def'))
+ refute_equal(URI.parse('http://ABC/DEF'), URI.parse('http://abc/def'))
end
def test_request_uri
diff --git a/test/uri/test_mailto.rb b/test/uri/test_mailto.rb
index e7d3142198..6cd3352978 100644
--- a/test/uri/test_mailto.rb
+++ b/test/uri/test_mailto.rb
@@ -141,6 +141,21 @@ class URI::TestMailTo < Test::Unit::TestCase
def test_check_to
u = URI::MailTo.build(['joe@example.com', 'subject=Ruby'])
+ # Valid emails
+ u.to = 'a@valid.com'
+ assert_equal(u.to, 'a@valid.com')
+
+ # Intentionally allowed violations of RFC 5322
+ u.to = 'a..a@valid.com'
+ assert_equal(u.to, 'a..a@valid.com')
+
+ u.to = 'hello.@valid.com'
+ assert_equal(u.to, 'hello.@valid.com')
+
+ u.to = '.hello@valid.com'
+ assert_equal(u.to, '.hello@valid.com')
+
+ # Invalid emails
assert_raise(URI::InvalidComponentError) do
u.to = '#1@mail.com'
end
@@ -148,6 +163,63 @@ class URI::TestMailTo < Test::Unit::TestCase
assert_raise(URI::InvalidComponentError) do
u.to = '@invalid.email'
end
+
+ # Invalid host emails
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@.invalid.email'
+ end
+
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@invalid.email.'
+ end
+
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@invalid..email'
+ end
+
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@-invalid.email'
+ end
+
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@invalid-.email'
+ end
+
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@invalid.-email'
+ end
+
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@invalid.email-'
+ end
+
+ u.to = 'a@'+'invalid'.ljust(63, 'd')+'.email'
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@'+'invalid'.ljust(64, 'd')+'.email'
+ end
+
+ u.to = 'a@invalid.'+'email'.rjust(63, 'e')
+ assert_raise(URI::InvalidComponentError) do
+ u.to = 'a@invalid.'+'email'.rjust(64, 'e')
+ end
+ end
+
+ def test_email_regexp
+ re = URI::MailTo::EMAIL_REGEXP
+
+ repeat = 10
+ longlabel = '.' + 'invalid'.ljust(63, 'd')
+ endlabel = ''
+ seq = (1..3).map {|i| 10**i}
+ rehearsal = 10
+ pre = ->(n) {'a@invalid' + longlabel*(n) + endlabel}
+ assert_linear_performance(seq, rehearsal: rehearsal, pre: pre) do |to|
+ repeat.times {re =~ to or flunk}
+ end
+ endlabel = '.' + 'email'.rjust(64, 'd')
+ assert_linear_performance(seq, rehearsal: rehearsal, pre: pre) do |to|
+ repeat.times {re =~ to and flunk}
+ end
end
def test_to_s
diff --git a/test/uri/test_parser.rb b/test/uri/test_parser.rb
index f455a5cc9b..c14824f5e8 100644
--- a/test/uri/test_parser.rb
+++ b/test/uri/test_parser.rb
@@ -20,17 +20,17 @@ class URI::TestParser < Test::Unit::TestCase
u2 = p.parse(url)
u3 = p.parse(url)
- assert(u0 == u1)
- assert(u0.eql?(u1))
- assert(!u0.equal?(u1))
+ assert_equal(u1, u0)
+ assert_send([u0, :eql?, u1])
+ refute_same(u1, u0)
- assert(u1 == u2)
- assert(!u1.eql?(u2))
- assert(!u1.equal?(u2))
+ assert_equal(u2, u1)
+ assert_not_send([u1, :eql?, u2])
+ refute_same(u1, u2)
- assert(u2 == u3)
- assert(u2.eql?(u3))
- assert(!u2.equal?(u3))
+ assert_equal(u3, u2)
+ assert_send([u2, :eql?, u3])
+ refute_same(u3, u2)
end
def test_parse_rfc2396_parser
@@ -113,4 +113,12 @@ class URI::TestParser < Test::Unit::TestCase
end
end
end
+
+ def test_rfc2822_make_regexp
+ parser = URI::RFC2396_Parser.new
+ regexp = parser.make_regexp("HTTP")
+ assert_match(regexp, "HTTP://EXAMPLE.COM/")
+ assert_match(regexp, "http://example.com/")
+ refute_match(regexp, "https://example.com/")
+ end
end
diff --git a/test/uri/test_ws.rb b/test/uri/test_ws.rb
index f3918f617c..d63ebd4a46 100644
--- a/test/uri/test_ws.rb
+++ b/test/uri/test_ws.rb
@@ -31,19 +31,19 @@ class URI::TestWS < Test::Unit::TestCase
host = 'aBcD'
u1 = URI.parse('ws://' + host + '/eFg?HiJ')
u2 = URI.parse('ws://' + host.downcase + '/eFg?HiJ')
- assert(u1.normalize.host == 'abcd')
- assert(u1.normalize.path == u1.path)
- assert(u1.normalize == u2.normalize)
- assert(!u1.normalize.host.equal?(u1.host))
- assert( u2.normalize.host.equal?(u2.host))
+ assert_equal('abcd', u1.normalize.host)
+ assert_equal(u1.path, u1.normalize.path)
+ assert_equal(u2.normalize, u1.normalize)
+ refute_same(u1.host, u1.normalize.host)
+ assert_same(u2.host, u2.normalize.host)
assert_equal('ws://abc/', URI.parse('ws://abc').normalize.to_s)
end
def test_equal
- assert(URI.parse('ws://abc') == URI.parse('ws://ABC'))
- assert(URI.parse('ws://abc/def') == URI.parse('ws://ABC/def'))
- assert(URI.parse('ws://abc/def') != URI.parse('ws://ABC/DEF'))
+ assert_equal(URI.parse('ws://ABC'), URI.parse('ws://abc'))
+ assert_equal(URI.parse('ws://ABC/def'), URI.parse('ws://abc/def'))
+ refute_equal(URI.parse('ws://ABC/DEF'), URI.parse('ws://abc/def'))
end
def test_request_uri
diff --git a/test/uri/test_wss.rb b/test/uri/test_wss.rb
index 13a2583059..cbef327cc6 100644
--- a/test/uri/test_wss.rb
+++ b/test/uri/test_wss.rb
@@ -31,19 +31,19 @@ class URI::TestWSS < Test::Unit::TestCase
host = 'aBcD'
u1 = URI.parse('wss://' + host + '/eFg?HiJ')
u2 = URI.parse('wss://' + host.downcase + '/eFg?HiJ')
- assert(u1.normalize.host == 'abcd')
- assert(u1.normalize.path == u1.path)
- assert(u1.normalize == u2.normalize)
- assert(!u1.normalize.host.equal?(u1.host))
- assert( u2.normalize.host.equal?(u2.host))
+ assert_equal('abcd', u1.normalize.host)
+ assert_equal(u1.path, u1.normalize.path)
+ assert_equal(u2.normalize, u1.normalize)
+ refute_same(u1.host, u1.normalize.host)
+ assert_same(u2.host, u2.normalize.host)
assert_equal('wss://abc/', URI.parse('wss://abc').normalize.to_s)
end
def test_equal
- assert(URI.parse('wss://abc') == URI.parse('wss://ABC'))
- assert(URI.parse('wss://abc/def') == URI.parse('wss://ABC/def'))
- assert(URI.parse('wss://abc/def') != URI.parse('wss://ABC/DEF'))
+ assert_equal(URI.parse('wss://ABC'), URI.parse('wss://abc'))
+ assert_equal(URI.parse('wss://ABC/def'), URI.parse('wss://abc/def'))
+ refute_equal(URI.parse('wss://ABC/DEF'), URI.parse('wss://abc/def'))
end
def test_request_uri
diff --git a/test/win32/test_registry.rb b/test/win32/test_registry.rb
deleted file mode 100644
index 9a38d0d314..0000000000
--- a/test/win32/test_registry.rb
+++ /dev/null
@@ -1,256 +0,0 @@
-# frozen_string_literal: true
-
-require "rbconfig"
-
-if /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os']
- begin
- require 'win32/registry'
- rescue LoadError
- else
- require 'test/unit'
- end
-end
-
-if defined?(Win32::Registry)
- class TestWin32Registry < Test::Unit::TestCase
- COMPUTERNAME = 'SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ComputerName'
- TEST_REGISTRY_PATH = 'Volatile Environment'
- TEST_REGISTRY_KEY = 'ruby-win32-registry-test-<RND>'
-
- # Create a new registry key per test in an atomic way, which is deleted on teardown.
- #
- # Fills the following instance variables:
- #
- # @test_registry_key - A registry path which is not yet created,
- # but can be created without collisions even when running
- # multiple test processes.
- # @test_registry_rnd - The part of the registry path with a random number.
- # @createopts - Required parameters (desired, opt) for create method in
- # the volatile environment of the registry.
- def setup
- @createopts = [Win32::Registry::KEY_ALL_ACCESS, Win32::Registry::REG_OPTION_VOLATILE]
- 100.times do |i|
- k = TEST_REGISTRY_KEY.gsub("<RND>", i.to_s)
- next unless Win32::Registry::HKEY_CURRENT_USER.create(
- TEST_REGISTRY_PATH + "\\" + k,
- *@createopts
- ).created?
- @test_registry_key = TEST_REGISTRY_PATH + "\\" + k + "\\" + "test\\"
- @test_registry_rnd = k
- break
- end
- omit "Unused registry subkey not found in #{TEST_REGISTRY_KEY}" unless @test_registry_key
- end
-
- def teardown
- Win32::Registry::HKEY_CURRENT_USER.open(TEST_REGISTRY_PATH) do |reg|
- reg.delete_key @test_registry_rnd, true
- end
- end
-
- def test_predefined
- assert_predefined_key Win32::Registry::HKEY_CLASSES_ROOT
- assert_predefined_key Win32::Registry::HKEY_CURRENT_USER
- assert_predefined_key Win32::Registry::HKEY_LOCAL_MACHINE
- assert_predefined_key Win32::Registry::HKEY_USERS
- assert_predefined_key Win32::Registry::HKEY_PERFORMANCE_DATA
- assert_predefined_key Win32::Registry::HKEY_PERFORMANCE_TEXT
- assert_predefined_key Win32::Registry::HKEY_PERFORMANCE_NLSTEXT
- assert_predefined_key Win32::Registry::HKEY_CURRENT_CONFIG
- assert_predefined_key Win32::Registry::HKEY_DYN_DATA
- end
-
- def test_open_no_block
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts).close
-
- reg = Win32::Registry::HKEY_CURRENT_USER.open(@test_registry_key, Win32::Registry::KEY_ALL_ACCESS)
- assert_kind_of Win32::Registry, reg
- assert_equal true, reg.open?
- assert_equal false, reg.created?
- reg["test"] = "abc"
- reg.close
- assert_raise(Win32::Registry::Error) do
- reg["test"] = "abc"
- end
- end
-
- def test_open_with_block
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts).close
-
- regs = []
- Win32::Registry::HKEY_CURRENT_USER.open(@test_registry_key, Win32::Registry::KEY_ALL_ACCESS) do |reg|
- regs << reg
- assert_equal true, reg.open?
- assert_equal false, reg.created?
- reg["test"] = "abc"
- end
-
- assert_equal 1, regs.size
- assert_kind_of Win32::Registry, regs[0]
- assert_raise(Win32::Registry::Error) do
- regs[0]["test"] = "abc"
- end
- end
-
- def test_class_open
- name1, keys1 = Win32::Registry.open(Win32::Registry::HKEY_LOCAL_MACHINE, "SYSTEM") do |reg|
- assert_predicate reg, :open?
- [reg.name, reg.keys]
- end
- name2, keys2 = Win32::Registry::HKEY_LOCAL_MACHINE.open("SYSTEM") do |reg|
- assert_predicate reg, :open?
- [reg.name, reg.keys]
- end
- assert_equal name1, name2
- assert_equal keys1, keys2
- end
-
- def test_read
- computername = ENV['COMPUTERNAME']
- Win32::Registry::HKEY_LOCAL_MACHINE.open(COMPUTERNAME) do |reg|
- assert_equal computername, reg['ComputerName']
- assert_equal [Win32::Registry::REG_SZ, computername], reg.read('ComputerName')
- assert_raise(TypeError) {reg.read('ComputerName', Win32::Registry::REG_DWORD)}
- end
- end
-
- def test_create_no_block
- reg = Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts)
- assert_kind_of Win32::Registry, reg
- assert_equal true, reg.open?
- assert_equal true, reg.created?
- reg["test"] = "abc"
- reg.close
- assert_equal false, reg.open?
- assert_raise(Win32::Registry::Error) do
- reg["test"] = "abc"
- end
- end
-
- def test_create_with_block
- regs = []
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- regs << reg
- reg["test"] = "abc"
- assert_equal true, reg.open?
- assert_equal true, reg.created?
- end
-
- assert_equal 1, regs.size
- assert_kind_of Win32::Registry, regs[0]
- assert_equal false, regs[0].open?
- assert_raise(Win32::Registry::Error) do
- regs[0]["test"] = "abc"
- end
- end
-
- def test_write
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- reg.write_s("key1", "data")
- assert_equal [Win32::Registry::REG_SZ, "data"], reg.read("key1")
- reg.write_i("key2", 0x5fe79027)
- assert_equal [Win32::Registry::REG_DWORD, 0x5fe79027], reg.read("key2")
- end
- end
-
- def test_accessors
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- assert_kind_of Integer, reg.hkey
- assert_kind_of Win32::Registry, reg.parent
- assert_equal "HKEY_CURRENT_USER", reg.parent.name
- assert_equal "Volatile Environment\\#{@test_registry_rnd}\\test\\", reg.keyname
- assert_equal Win32::Registry::REG_CREATED_NEW_KEY, reg.disposition
- end
- end
-
- def test_name
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- assert_equal "HKEY_CURRENT_USER\\Volatile Environment\\#{@test_registry_rnd}\\test\\", reg.name
- end
- end
-
- def test_keys
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- reg.create("key1", *@createopts)
- assert_equal ["key1"], reg.keys
- end
- end
-
- def test_each_key
- keys = []
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- reg.create("key1", *@createopts)
- reg.each_key { |*a| keys << a }
- end
- assert_equal [2], keys.map(&:size)
- assert_equal ["key1"], keys.map(&:first)
- assert_in_delta Win32::Registry.time2wtime(Time.now), keys[0][1], 10_000_000_000, "wtime should roughly match Time.now"
- end
-
- def test_each_key_enum
- keys = nil
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- reg.create("key1", *@createopts)
- reg.create("key2", *@createopts)
- reg.create("key3", *@createopts)
- reg["value1"] = "abcd"
- keys = reg.each_key.to_a
- end
- assert_equal 3, keys.size
- assert_equal [2, 2, 2], keys.map(&:size)
- assert_equal ["key1", "key2", "key3"], keys.map(&:first)
- end
-
- def test_values
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- reg.create("key1", *@createopts)
- reg["value1"] = "abcd"
- assert_equal ["abcd"], reg.values
- end
- end
-
- def test_each_value
- vals = []
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- reg.create("key1", *@createopts)
- reg["value1"] = "abcd"
- reg.each_value { |*a| vals << a }
- end
- assert_equal [["value1", Win32::Registry::REG_SZ, "abcd"]], vals
- end
-
- def test_each_value_enum
- vals = nil
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- reg.create("key1", *@createopts)
- reg["value1"] = "abcd"
- reg["value2"] = 42
- vals = reg.each_value.to_a
- end
- assert_equal [["value1", Win32::Registry::REG_SZ, "abcd"],
- ["value2", Win32::Registry::REG_DWORD, 42]], vals
- end
-
- def test_utf8_encoding
- keys = []
- Win32::Registry::HKEY_CURRENT_USER.create(@test_registry_key, *@createopts) do |reg|
- reg.create("abc EUR", *@createopts)
- reg.create("abc €", *@createopts)
- reg.each_key do |subkey|
- keys << subkey
- end
- end
-
- assert_equal [Encoding::UTF_8] * 2, keys.map(&:encoding)
- assert_equal ["abc EUR", "abc €"], keys
- end
-
- private
-
- def assert_predefined_key(key)
- assert_kind_of Win32::Registry, key
- assert_predicate key, :open?
- refute_predicate key, :created?
- end
- end
-end
diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb
index 5a8463ad8e..48b8f172ff 100644
--- a/test/zlib/test_zlib.rb
+++ b/test/zlib/test_zlib.rb
@@ -9,6 +9,9 @@ require 'securerandom'
begin
require 'zlib'
rescue LoadError
+else
+ z = "/zlib.#{RbConfig::CONFIG["DLEXT"]}"
+ LOADED_ZLIB, = $".select {|f| f.end_with?(z)}
end
if defined? Zlib
@@ -879,6 +882,25 @@ if defined? Zlib
assert_equal(-1, r.pos, "[ruby-core:81488][Bug #13616]")
end
+ def test_ungetc_buffer_underflow
+ initial_bufsize = 1024
+ payload = "A" * initial_bufsize
+ gzip_io = StringIO.new
+ Zlib::GzipWriter.wrap(gzip_io) { |gz| gz.write(payload) }
+ compressed = gzip_io.string
+
+ reader = Zlib::GzipReader.new(StringIO.new(compressed))
+ reader.read(1)
+ overflow_bytes = "B" * (initial_bufsize)
+ reader.ungetc(overflow_bytes)
+ data = reader.read(overflow_bytes.bytesize)
+ assert_equal overflow_bytes.bytesize, data.bytesize, data
+ assert_empty data.delete("B"), data
+ data = reader.read()
+ assert_equal initial_bufsize - 1, data.bytesize, data
+ assert_empty data.delete("A"), data
+ end
+
def test_open
Tempfile.create("test_zlib_gzip_reader_open") {|t|
t.close
@@ -1263,6 +1285,36 @@ if defined? Zlib
end
}
end
+
+ # Test for signal interrupt bug: Z_BUF_ERROR with avail_out > 0
+ # This reproduces the issue where thread wakeup during GzipReader operations
+ # can cause Z_BUF_ERROR to be raised incorrectly
+ def test_thread_wakeup_interrupt
+ pend 'fails' if RUBY_ENGINE == 'truffleruby'
+ content = SecureRandom.base64(5000)
+ gzipped = Zlib.gzip(content)
+
+ 1000.times do
+ thr = Thread.new do
+ loop do
+ Zlib::GzipReader.new(StringIO.new(gzipped)).read
+ end
+ end
+
+ # Wakeup the thread multiple times to trigger interrupts
+ 10.times do
+ thr.wakeup
+ Thread.pass
+ end
+
+ # Give thread a moment to process
+ sleep 0.001
+
+ # Clean up
+ thr.kill
+ thr.join
+ end
+ end
end
class TestZlibGzipWriter < Test::Unit::TestCase
@@ -1525,11 +1577,42 @@ if defined? Zlib
end
def test_gunzip_no_memory_leak
- assert_no_memory_leak(%[-rzlib], "#{<<~"{#"}", "#{<<~'};'}")
+ assert_no_memory_leak(%W[-r#{LOADED_ZLIB}], "#{<<~"{#"}", "#{<<~'};'}")
d = Zlib.gzip("data")
{#
10_000.times {Zlib.gunzip(d)}
};
end
+
+ # Test for signal interrupt bug: Z_BUF_ERROR with avail_out > 0
+ # This reproduces the issue where thread wakeup during GzipReader operations
+ # can cause Z_BUF_ERROR to be raised incorrectly
+ def test_thread_wakeup_interrupt
+ pend 'fails' if RUBY_ENGINE == 'truffleruby'
+
+ content = SecureRandom.base64(5000)
+ gzipped = Zlib.gzip(content)
+
+ 1000.times do
+ thr = Thread.new do
+ loop do
+ Zlib::GzipReader.new(StringIO.new(gzipped)).read
+ end
+ end
+
+ # Wakeup the thread multiple times to trigger interrupts
+ 10.times do
+ thr.wakeup
+ Thread.pass
+ end
+
+ # Give thread a moment to process
+ sleep 0.001
+
+ # Clean up
+ thr.kill
+ thr.join
+ end
+ end
end
end