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.rb8
-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.rb309
-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/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/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.rb107
-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.rb654
-rw-r--r--test/json/json_generic_object_test.rb24
-rw-r--r--test/json/json_parser_test.rb276
-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.rb24
-rw-r--r--test/lib/jit_support.rb12
-rw-r--r--test/mkmf/test_egrep_cpp.rb14
-rw-r--r--test/mkmf/test_pkg_config.rb17
-rw-r--r--test/mmtk/helper.rb4
-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.rb146
-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.rb92
-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.rb135
-rw-r--r--test/openssl/test_ns_spki.rb4
-rw-r--r--test/openssl/test_ocsp.rb43
-rw-r--r--test/openssl/test_ossl.rb69
-rw-r--r--test/openssl/test_pkcs12.rb40
-rw-r--r--test/openssl/test_pkcs7.rb354
-rw-r--r--test/openssl/test_pkey.rb158
-rw-r--r--test/openssl/test_pkey_dh.rb124
-rw-r--r--test/openssl/test_pkey_dsa.rb128
-rw-r--r--test/openssl/test_pkey_ec.rb84
-rw-r--r--test/openssl/test_pkey_rsa.rb333
-rw-r--r--test/openssl/test_provider.rb1
-rw-r--r--test/openssl/test_ssl.rb580
-rw-r--r--test/openssl/test_ssl_session.rb26
-rw-r--r--test/openssl/test_ts.rb48
-rw-r--r--test/openssl/test_x509cert.rb176
-rw-r--r--test/openssl/test_x509crl.rb78
-rw-r--r--test/openssl/test_x509name.rb16
-rw-r--r--test/openssl/test_x509req.rb94
-rw-r--r--test/openssl/test_x509store.rb12
-rw-r--r--test/openssl/utils.rb49
-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.txt2
-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/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/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/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.rb110
-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.txt (renamed from test/prism/fixtures/it_indirect_writes.txt)0
-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/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.rb20
-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/snapshots/it_indirect_writes.txt419
-rw-r--r--test/prism/snapshots/rescue_modifier.txt230
-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_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/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/sentence.rb2
-rw-r--r--test/ruby/test_allocation.rb129
-rw-r--r--test/ruby/test_array.rb86
-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.rb45
-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.rb196
-rw-r--r--test/ruby/test_gc_compact.rb58
-rw-r--r--test/ruby/test_hash.rb72
-rw-r--r--test/ruby/test_integer.rb10
-rw-r--r--test/ruby/test_io.rb150
-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.rb49
-rw-r--r--test/ruby/test_module.rb120
-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.rb72
-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.rb229
-rw-r--r--test/ruby/test_process.rb53
-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.rb2
-rw-r--r--test/ruby/test_rubyoptions.rb130
-rw-r--r--test/ruby/test_set.rb (renamed from test/set/test_set.rb)217
-rw-r--r--test/ruby/test_settracefunc.rb217
-rw-r--r--test/ruby/test_shapes.rb387
-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.rb113
-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.rb188
-rw-r--r--test/ruby/test_zjit.rb556
-rw-r--r--test/rubygems/coverage_setup.rb9
-rw-r--r--test/rubygems/helper.rb145
-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.rb90
-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.rb7
-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.rb144
-rw-r--r--test/rubygems/test_gem_commands_open_command.rb2
-rw-r--r--test/rubygems/test_gem_commands_owner_command.rb28
-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.rb42
-rw-r--r--test/rubygems/test_gem_dependency_installer.rb176
-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_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.rb104
-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_best_set.rb14
-rw-r--r--test/rubygems/test_gem_resolver_git_specification.rb38
-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_specification.rb235
-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.rb123
-rw-r--r--test/socket/test_tcp.rb42
-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.rb152
-rw-r--r--test/test_bundled_gems.rb38
-rw-r--r--test/test_delegate.rb57
-rw-r--r--test/test_extlibs.rb2
-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_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
560 files changed, 25106 insertions, 9472 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 8293408518..83fdba2282 100644
--- a/test/-ext-/bug_reporter/test_bug_reporter.rb
+++ b/test/-ext-/bug_reporter/test_bug_reporter.rb
@@ -6,8 +6,6 @@ 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)
-
description = RUBY_DESCRIPTION
description = description.sub(/\+PRISM /, '') unless ParserSupport.prism_enabled_in_subprocess?
expected_stderr = [
@@ -22,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 8aa5eb9c8d..5f664de502 100644
--- a/test/error_highlight/test_error_highlight.rb
+++ b/test/error_highlight/test_error_highlight.rb
@@ -44,14 +44,16 @@ class ErrorHighlightTest < Test::Unit::TestCase
def assert_error_message(klass, expected_msg, &blk)
omit unless klass < ErrorHighlight::CoreExt
err = assert_raise(klass, &blk)
- 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])
+ 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
@@ -889,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
@@ -1097,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]
^^^
@@ -1111,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
@@ -1126,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
^^^^^^^^
@@ -1188,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
^^^^^^^^^^
@@ -1199,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
@@ -1453,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"
@@ -1521,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/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/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
index 9861181910..a8477dd7be 100755
--- a/test/json/json_coder_test.rb
+++ b/test/json/json_coder_test.rb
@@ -12,12 +12,33 @@ class JSONCoderTest < Test::Unit::TestCase
end
def test_json_coder_with_proc_with_unsupported_value
- coder = JSON::Coder.new do |object|
+ 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
@@ -37,17 +58,97 @@ class JSONCoderTest < Test::Unit::TestCase
end
def test_json_coder_dump_NaN_or_Infinity
- coder = JSON::Coder.new(&:inspect)
+ 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(&:itself)
+ 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 c67cd3349c..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))
@@ -90,6 +82,66 @@ class JSONGeneratorTest < Test::Unit::TestCase
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
@@ -122,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)
@@ -136,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
@@ -165,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) }
@@ -175,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
@@ -199,26 +273,7 @@ class JSONGeneratorTest < Test::Unit::TestCase
)
end
- def test_pretty_state
- state = JSON.create_pretty_state
- assert_equal({
- :allow_nan => false,
- :array_nl => "\n",
- :as_json => false,
- :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,
@@ -235,11 +290,10 @@ 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,
@@ -249,7 +303,7 @@ class JSONGeneratorTest < Test::Unit::TestCase
:script_safe => false,
:strict => false,
:indent => "",
- :max_nesting => 0,
+ :max_nesting => 100,
:object_nl => "",
:space => "",
:space_before => "",
@@ -257,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
@@ -353,50 +495,60 @@ 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
- 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_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
@@ -410,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
@@ -429,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
@@ -436,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)
@@ -464,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)
@@ -471,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
@@ -631,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 = "€™"
@@ -645,29 +949,6 @@ class JSONGeneratorTest < Test::Unit::TestCase
assert_equal JSON.dump(utf8_string), JSON.dump(wrong_encoding_string)
end
end
-
- def test_string_ext_included_calls_super
- included = false
-
- 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
-
- Class.new(String) do
- include JSON::Ext::Generator::GeneratorMethods::String
- 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)
- end
- end
end
def test_nonutf8_encoding
@@ -688,6 +969,129 @@ class JSONGeneratorTest < Test::Unit::TestCase
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: :object_id)
+ 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
+
+ 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
+
+ # 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 d1f084bb63..292ca1a670 100644
--- a/test/json/json_parser_test.rb
+++ b/test/json/json_parser_test.rb
@@ -128,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
@@ -157,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]'))
@@ -311,6 +354,25 @@ class JSONParserTest < Test::Unit::TestCase
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
@@ -326,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') }
@@ -357,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
@@ -432,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
@@ -513,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
@@ -574,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
@@ -633,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
@@ -641,10 +844,26 @@ class JSONParserTest < Test::Unit::TestCase
JSON.parse('{"input":{"firstName":"Bob","lastName":"Mob","email":"bob@example.com"}')
end
if RUBY_ENGINE == "ruby"
- assert_equal %(expected ',' or '}' after object value, got: ''), error.message
+ assert_equal %(expected ',' or '}' after object value, got: EOF at line 1 column 72), error.message
end
end
+ 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
+
+ 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
@@ -652,18 +871,35 @@ class JSONParserTest < Test::Unit::TestCase
end
end
- private
+ def test_parse_whitespace_after_newline
+ assert_equal [], JSON.parse("[\n#{' ' * (8 + 8 + 4 + 3)}]")
+ end
- def string_deduplication_available?
- r1 = rand.to_s
- r2 = r1.dup
- begin
- (-r1).equal?(-r2)
- rescue NoMethodError
- false # No String#-@
+ 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..24cde4348c 100644
--- a/test/json/test_helper.rb
+++ b/test/json/test_helper.rb
@@ -1,5 +1,29 @@
$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'
+ end
+end
+
require 'json'
require 'test/unit'
diff --git a/test/lib/jit_support.rb b/test/lib/jit_support.rb
index 1b15f685a0..386a5a6f1e 100644
--- a/test/lib/jit_support.rb
+++ b/test/lib/jit_support.rb
@@ -10,10 +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 zjit_supported?
+ return @zjit_supported if defined?(@zjit_supported)
+ # nil in mswin
+ @zjit_supported = ![nil, 'no'].include?(RbConfig::CONFIG['ZJIT_SUPPORT'])
+ end
+
+ 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 828b3a2b51..3bede9ed30 100644
--- a/test/mmtk/helper.rb
+++ b/test/mmtk/helper.rb
@@ -18,7 +18,9 @@ module MMTk
end
def teardown
- EnvUtil.timeout_scale = @original_timeout_scale
+ if using_mmtk?
+ EnvUtil.timeout_scale = @original_timeout_scale
+ end
end
private
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 939092e1c1..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
@@ -612,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
@@ -667,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
@@ -785,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)
@@ -963,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..5978ecf673 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)
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 6a12a25aa8..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,6 +94,7 @@ 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
hash = "sha256"
ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
@@ -144,6 +108,7 @@ 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
hash = "sha256"
ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b")
@@ -157,16 +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
+ # 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 9f4b39d4f5..1b9bde53ef 100644
--- a/test/openssl/test_ossl.rb
+++ b/test/openssl/test_ossl.rb
@@ -3,42 +3,52 @@ 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
@@ -63,19 +73,30 @@ class OpenSSL::OSSL < OpenSSL::SSLTestCase
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 7e5bd6f17c..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,6 +250,28 @@ 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"),
@@ -175,6 +287,7 @@ class OpenSSL::TestPKCS7 < OpenSSL::TestCase
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)
@@ -185,12 +298,13 @@ class OpenSSL::TestPKCS7 < OpenSSL::TestCase
# PKCS7#verify can't distinguish verification failure and other errors
store = OpenSSL::X509::Store.new
assert_equal(false, p7.verify([@ee1_cert], store))
- assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.decrypt(@rsa1024) }
+ 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
@@ -204,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
@@ -226,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"
@@ -239,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))
@@ -261,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)
@@ -303,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 8444cfdcda..93d9e1d42f 100644
--- a/test/openssl/test_pkey.rb
+++ b/test/openssl/test_pkey.rb
@@ -8,16 +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_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
@@ -69,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
@@ -152,6 +248,8 @@ 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)
@@ -168,6 +266,25 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase
bob.raw_public_key.unpack1("H*")
end
+ 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") }
@@ -176,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))
@@ -194,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 686c9b97d0..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,18 +108,20 @@ 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.
@@ -123,11 +138,22 @@ 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
@@ -195,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 a8578daf55..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?
@@ -34,9 +34,14 @@ class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
end
def test_new_empty
- key = OpenSSL::PKey::DSA.new
- assert_nil(key.p)
- assert_raise(OpenSSL::PKey::PKeyError) { key.to_der }
+ # 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
@@ -47,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
@@ -92,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
@@ -258,7 +234,7 @@ fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ==
end
def test_dup
- key = Fixtures.pkey("dsa1024")
+ key = Fixtures.pkey("dsa2048")
key2 = key.dup
assert_equal key.params, key2.params
@@ -270,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 891c8601d7..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)
diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb
index 360309b475..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,90 +443,73 @@ 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
@@ -600,7 +533,7 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
end
def test_dup
- key = Fixtures.pkey("rsa1024")
+ key = Fixtures.pkey("rsa-1")
key2 = key.dup
assert_equal key.params, key2.params
@@ -612,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 c6544cc687..e4fd581079 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -39,7 +39,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_ctx_options_config
- omit "LibreSSL does not support OPENSSL_CONF" if libressl?
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
Tempfile.create("openssl.cnf") { |f|
f.puts(<<~EOF)
@@ -230,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|
@@ -242,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
@@ -322,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|
@@ -396,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
@@ -445,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
@@ -510,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
@@ -644,6 +701,10 @@ 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.max_version = OpenSSL::SSL::TLS1_2_VERSION
ctx.ciphers = "aNULL"
@@ -797,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(
@@ -885,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
@@ -1018,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
@@ -1162,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)
}
}
@@ -1207,32 +1277,32 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
OpenSSL::SSL::TLS1_1_VERSION,
OpenSSL::SSL::TLS1_2_VERSION,
OpenSSL::SSL::TLS1_3_VERSION,
- ].compact
+ ]
- # 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
@@ -1271,11 +1341,15 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# 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|
@@ -1290,6 +1364,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
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|
@@ -1304,6 +1379,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# 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
@@ -1318,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
@@ -1331,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|
@@ -1345,7 +1428,11 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
# 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
@@ -1362,6 +1449,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
server_connect(port, ctx2) { |ssl|
if cver >= sver
@@ -1376,7 +1465,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_minmax_version_system_default
- omit "LibreSSL does not support OPENSSL_CONF" if libressl?
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
Tempfile.create("openssl.cnf") { |f|
f.puts(<<~EOF)
@@ -1420,7 +1509,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
def test_respect_system_default_min
- omit "LibreSSL does not support OPENSSL_CONF" if libressl?
+ omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc?
Tempfile.create("openssl.cnf") { |f|
f.puts(<<~EOF)
@@ -1686,6 +1775,9 @@ 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.max_version = OpenSSL::SSL::TLS1_2_VERSION
@@ -1704,30 +1796,27 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
# DHE
- # TODO: SSL_CTX_set1_groups() is required for testing this with TLS 1.3
- ctx_proc2 = proc { |ctx|
- ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
- ctx.ciphers = "EDH"
- ctx.tmp_dh = Fixtures.pkey("dh-1")
- }
- start_server(ctx_proc: ctx_proc2) do |port|
- ctx = OpenSSL::SSL::SSLContext.new
- ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
- 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
}
@@ -1736,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
@@ -1751,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
@@ -1773,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
@@ -1796,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 {
@@ -1807,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
}
@@ -1846,9 +1950,10 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
def test_ciphersuites_method_bogus_csuite
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_ciphersuites: no cipher match/i
+ /SSL_CTX_set_ciphersuites: (no cipher match|NO_CIPHER_MATCH)/i
) { ssl_ctx.ciphersuites = 'BOGUS' }
end
@@ -1886,28 +1991,182 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
def test_ciphers_method_bogus_csuite
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
@@ -1915,90 +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
+ 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
@@ -2054,6 +2356,50 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase
end
end
+ # 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
+
+ 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)
diff --git a/test/openssl/test_ssl_session.rb b/test/openssl/test_ssl_session.rb
index d1ef9cd3db..37874ca273 100644
--- a/test/openssl/test_ssl_session.rb
+++ b/test/openssl/test_ssl_session.rb
@@ -30,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
@@ -56,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
@@ -122,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|
@@ -219,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|
@@ -239,20 +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 "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 = {}
diff --git a/test/openssl/test_ts.rb b/test/openssl/test_ts.rb
index ac0469ad56..cca7898bc1 100644
--- a/test/openssl/test_ts.rb
+++ b/test/openssl/test_ts.rb
@@ -70,15 +70,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 +88,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 +371,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 +472,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 5fc87d9c67..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,57 +234,17 @@ 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
ed25519 = OpenSSL::PKey::generate_key("ED25519")
@@ -299,24 +252,13 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase
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
@@ -325,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)
@@ -338,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
@@ -356,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))
@@ -378,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
@@ -394,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
@@ -419,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 89165388db..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,59 +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
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
@@ -245,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
@@ -274,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 18d3e7f8f3..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,65 +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
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
@@ -161,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 745ae7dd13..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
diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb
index 220edce292..7e6fe8b163 100644
--- a/test/openssl/utils.rb
+++ b/test/openssl/utils.rb
@@ -177,16 +177,16 @@ 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
@@ -201,11 +201,7 @@ class OpenSSL::SSLTestCase < OpenSSL::TestCase
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
@@ -290,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 16af8200ec..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,5 +1,5 @@
x.each { x end
^~~ unexpected 'end', expecting end-of-input
^~~ unexpected 'end', ignoring it
- ^ 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/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/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/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 62bbd8458b..9dd7fbe3fe 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -1,41 +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",
- "numbered_and_write.txt",
- "numbered_or_write.txt",
- "numbered_operator_write.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
@@ -67,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/it_indirect_writes.txt b/test/prism/fixtures/3.3-3.3/it_indirect_writes.txt
index bb87e9483e..bb87e9483e 100644
--- a/test/prism/fixtures/it_indirect_writes.txt
+++ b/test/prism/fixtures/3.3-3.3/it_indirect_writes.txt
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/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 e0e9a45855..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
@@ -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/snapshots/it_indirect_writes.txt b/test/prism/snapshots/it_indirect_writes.txt
deleted file mode 100644
index 165aececc6..0000000000
--- a/test/prism/snapshots/it_indirect_writes.txt
+++ /dev/null
@@ -1,419 +0,0 @@
-@ ProgramNode (location: (1,0)-(23,24))
-├── flags: ∅
-├── locals: []
-└── statements:
- @ StatementsNode (location: (1,0)-(23,24))
- ├── flags: ∅
- └── body: (length: 12)
- ├── @ CallNode (location: (1,0)-(1,15))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (1,0)-(1,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (1,4)-(1,15))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (1,6)-(1,13))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 1)
- │ │ └── @ LocalVariableOperatorWriteNode (location: (1,6)-(1,13))
- │ │ ├── flags: newline
- │ │ ├── name_loc: (1,6)-(1,8) = "it"
- │ │ ├── binary_operator_loc: (1,9)-(1,11) = "+="
- │ │ ├── value:
- │ │ │ @ IntegerNode (location: (1,12)-(1,13))
- │ │ │ ├── flags: static_literal, decimal
- │ │ │ └── value: 1
- │ │ ├── name: :it
- │ │ ├── binary_operator: :+
- │ │ └── depth: 0
- │ ├── opening_loc: (1,4)-(1,5) = "{"
- │ └── closing_loc: (1,14)-(1,15) = "}"
- ├── @ CallNode (location: (3,0)-(3,16))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (3,0)-(3,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (3,4)-(3,16))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (3,6)-(3,14))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 1)
- │ │ └── @ LocalVariableOrWriteNode (location: (3,6)-(3,14))
- │ │ ├── flags: newline
- │ │ ├── name_loc: (3,6)-(3,8) = "it"
- │ │ ├── operator_loc: (3,9)-(3,12) = "||="
- │ │ ├── value:
- │ │ │ @ IntegerNode (location: (3,13)-(3,14))
- │ │ │ ├── flags: static_literal, decimal
- │ │ │ └── value: 1
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (3,4)-(3,5) = "{"
- │ └── closing_loc: (3,15)-(3,16) = "}"
- ├── @ CallNode (location: (5,0)-(5,16))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (5,0)-(5,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (5,4)-(5,16))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (5,6)-(5,14))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 1)
- │ │ └── @ LocalVariableAndWriteNode (location: (5,6)-(5,14))
- │ │ ├── flags: newline
- │ │ ├── name_loc: (5,6)-(5,8) = "it"
- │ │ ├── operator_loc: (5,9)-(5,12) = "&&="
- │ │ ├── value:
- │ │ │ @ IntegerNode (location: (5,13)-(5,14))
- │ │ │ ├── flags: static_literal, decimal
- │ │ │ └── value: 1
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (5,4)-(5,5) = "{"
- │ └── closing_loc: (5,15)-(5,16) = "}"
- ├── @ CallNode (location: (7,0)-(7,19))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (7,0)-(7,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (7,4)-(7,19))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters:
- │ │ @ ItParametersNode (location: (7,4)-(7,19))
- │ │ └── flags: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (7,6)-(7,17))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 2)
- │ │ ├── @ ItLocalVariableReadNode (location: (7,6)-(7,8))
- │ │ │ └── flags: newline
- │ │ └── @ LocalVariableOperatorWriteNode (location: (7,10)-(7,17))
- │ │ ├── flags: newline
- │ │ ├── name_loc: (7,10)-(7,12) = "it"
- │ │ ├── binary_operator_loc: (7,13)-(7,15) = "+="
- │ │ ├── value:
- │ │ │ @ IntegerNode (location: (7,16)-(7,17))
- │ │ │ ├── flags: static_literal, decimal
- │ │ │ └── value: 1
- │ │ ├── name: :it
- │ │ ├── binary_operator: :+
- │ │ └── depth: 0
- │ ├── opening_loc: (7,4)-(7,5) = "{"
- │ └── closing_loc: (7,18)-(7,19) = "}"
- ├── @ CallNode (location: (9,0)-(9,20))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (9,0)-(9,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (9,4)-(9,20))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters:
- │ │ @ ItParametersNode (location: (9,4)-(9,20))
- │ │ └── flags: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (9,6)-(9,18))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 2)
- │ │ ├── @ ItLocalVariableReadNode (location: (9,6)-(9,8))
- │ │ │ └── flags: newline
- │ │ └── @ LocalVariableOrWriteNode (location: (9,10)-(9,18))
- │ │ ├── flags: newline
- │ │ ├── name_loc: (9,10)-(9,12) = "it"
- │ │ ├── operator_loc: (9,13)-(9,16) = "||="
- │ │ ├── value:
- │ │ │ @ IntegerNode (location: (9,17)-(9,18))
- │ │ │ ├── flags: static_literal, decimal
- │ │ │ └── value: 1
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (9,4)-(9,5) = "{"
- │ └── closing_loc: (9,19)-(9,20) = "}"
- ├── @ CallNode (location: (11,0)-(11,20))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (11,0)-(11,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (11,4)-(11,20))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters:
- │ │ @ ItParametersNode (location: (11,4)-(11,20))
- │ │ └── flags: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (11,6)-(11,18))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 2)
- │ │ ├── @ ItLocalVariableReadNode (location: (11,6)-(11,8))
- │ │ │ └── flags: newline
- │ │ └── @ LocalVariableAndWriteNode (location: (11,10)-(11,18))
- │ │ ├── flags: newline
- │ │ ├── name_loc: (11,10)-(11,12) = "it"
- │ │ ├── operator_loc: (11,13)-(11,16) = "&&="
- │ │ ├── value:
- │ │ │ @ IntegerNode (location: (11,17)-(11,18))
- │ │ │ ├── flags: static_literal, decimal
- │ │ │ └── value: 1
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (11,4)-(11,5) = "{"
- │ └── closing_loc: (11,19)-(11,20) = "}"
- ├── @ CallNode (location: (13,0)-(13,19))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (13,0)-(13,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (13,4)-(13,19))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (13,6)-(13,17))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 2)
- │ │ ├── @ LocalVariableOperatorWriteNode (location: (13,6)-(13,13))
- │ │ │ ├── flags: newline
- │ │ │ ├── name_loc: (13,6)-(13,8) = "it"
- │ │ │ ├── binary_operator_loc: (13,9)-(13,11) = "+="
- │ │ │ ├── value:
- │ │ │ │ @ IntegerNode (location: (13,12)-(13,13))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 1
- │ │ │ ├── name: :it
- │ │ │ ├── binary_operator: :+
- │ │ │ └── depth: 0
- │ │ └── @ LocalVariableReadNode (location: (13,15)-(13,17))
- │ │ ├── flags: newline
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (13,4)-(13,5) = "{"
- │ └── closing_loc: (13,18)-(13,19) = "}"
- ├── @ CallNode (location: (15,0)-(15,20))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (15,0)-(15,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (15,4)-(15,20))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (15,6)-(15,18))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 2)
- │ │ ├── @ LocalVariableOrWriteNode (location: (15,6)-(15,14))
- │ │ │ ├── flags: newline
- │ │ │ ├── name_loc: (15,6)-(15,8) = "it"
- │ │ │ ├── operator_loc: (15,9)-(15,12) = "||="
- │ │ │ ├── value:
- │ │ │ │ @ IntegerNode (location: (15,13)-(15,14))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 1
- │ │ │ ├── name: :it
- │ │ │ └── depth: 0
- │ │ └── @ LocalVariableReadNode (location: (15,16)-(15,18))
- │ │ ├── flags: newline
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (15,4)-(15,5) = "{"
- │ └── closing_loc: (15,19)-(15,20) = "}"
- ├── @ CallNode (location: (17,0)-(17,20))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (17,0)-(17,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (17,4)-(17,20))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (17,6)-(17,18))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 2)
- │ │ ├── @ LocalVariableAndWriteNode (location: (17,6)-(17,14))
- │ │ │ ├── flags: newline
- │ │ │ ├── name_loc: (17,6)-(17,8) = "it"
- │ │ │ ├── operator_loc: (17,9)-(17,12) = "&&="
- │ │ │ ├── value:
- │ │ │ │ @ IntegerNode (location: (17,13)-(17,14))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 1
- │ │ │ ├── name: :it
- │ │ │ └── depth: 0
- │ │ └── @ LocalVariableReadNode (location: (17,16)-(17,18))
- │ │ ├── flags: newline
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (17,4)-(17,5) = "{"
- │ └── closing_loc: (17,19)-(17,20) = "}"
- ├── @ CallNode (location: (19,0)-(19,23))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (19,0)-(19,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (19,4)-(19,23))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters:
- │ │ @ ItParametersNode (location: (19,4)-(19,23))
- │ │ └── flags: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (19,6)-(19,21))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 3)
- │ │ ├── @ ItLocalVariableReadNode (location: (19,6)-(19,8))
- │ │ │ └── flags: newline
- │ │ ├── @ LocalVariableOperatorWriteNode (location: (19,10)-(19,17))
- │ │ │ ├── flags: newline
- │ │ │ ├── name_loc: (19,10)-(19,12) = "it"
- │ │ │ ├── binary_operator_loc: (19,13)-(19,15) = "+="
- │ │ │ ├── value:
- │ │ │ │ @ IntegerNode (location: (19,16)-(19,17))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 1
- │ │ │ ├── name: :it
- │ │ │ ├── binary_operator: :+
- │ │ │ └── depth: 0
- │ │ └── @ LocalVariableReadNode (location: (19,19)-(19,21))
- │ │ ├── flags: newline
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (19,4)-(19,5) = "{"
- │ └── closing_loc: (19,22)-(19,23) = "}"
- ├── @ CallNode (location: (21,0)-(21,24))
- │ ├── flags: newline, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :tap
- │ ├── message_loc: (21,0)-(21,3) = "tap"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block:
- │ @ BlockNode (location: (21,4)-(21,24))
- │ ├── flags: ∅
- │ ├── locals: [:it]
- │ ├── parameters:
- │ │ @ ItParametersNode (location: (21,4)-(21,24))
- │ │ └── flags: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (21,6)-(21,22))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 3)
- │ │ ├── @ ItLocalVariableReadNode (location: (21,6)-(21,8))
- │ │ │ └── flags: newline
- │ │ ├── @ LocalVariableOrWriteNode (location: (21,10)-(21,18))
- │ │ │ ├── flags: newline
- │ │ │ ├── name_loc: (21,10)-(21,12) = "it"
- │ │ │ ├── operator_loc: (21,13)-(21,16) = "||="
- │ │ │ ├── value:
- │ │ │ │ @ IntegerNode (location: (21,17)-(21,18))
- │ │ │ │ ├── flags: static_literal, decimal
- │ │ │ │ └── value: 1
- │ │ │ ├── name: :it
- │ │ │ └── depth: 0
- │ │ └── @ LocalVariableReadNode (location: (21,20)-(21,22))
- │ │ ├── flags: newline
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ ├── opening_loc: (21,4)-(21,5) = "{"
- │ └── closing_loc: (21,23)-(21,24) = "}"
- └── @ CallNode (location: (23,0)-(23,24))
- ├── flags: newline, ignore_visibility
- ├── receiver: ∅
- ├── call_operator_loc: ∅
- ├── name: :tap
- ├── message_loc: (23,0)-(23,3) = "tap"
- ├── opening_loc: ∅
- ├── arguments: ∅
- ├── closing_loc: ∅
- └── block:
- @ BlockNode (location: (23,4)-(23,24))
- ├── flags: ∅
- ├── locals: [:it]
- ├── parameters:
- │ @ ItParametersNode (location: (23,4)-(23,24))
- │ └── flags: ∅
- ├── body:
- │ @ StatementsNode (location: (23,6)-(23,22))
- │ ├── flags: ∅
- │ └── body: (length: 3)
- │ ├── @ ItLocalVariableReadNode (location: (23,6)-(23,8))
- │ │ └── flags: newline
- │ ├── @ LocalVariableAndWriteNode (location: (23,10)-(23,18))
- │ │ ├── flags: newline
- │ │ ├── name_loc: (23,10)-(23,12) = "it"
- │ │ ├── operator_loc: (23,13)-(23,16) = "&&="
- │ │ ├── value:
- │ │ │ @ IntegerNode (location: (23,17)-(23,18))
- │ │ │ ├── flags: static_literal, decimal
- │ │ │ └── value: 1
- │ │ ├── name: :it
- │ │ └── depth: 0
- │ └── @ LocalVariableReadNode (location: (23,20)-(23,22))
- │ ├── flags: newline
- │ ├── name: :it
- │ └── depth: 0
- ├── opening_loc: (23,4)-(23,5) = "{"
- └── closing_loc: (23,23)-(23,24) = "}"
diff --git a/test/prism/snapshots/rescue_modifier.txt b/test/prism/snapshots/rescue_modifier.txt
deleted file mode 100644
index 0a27a3bb49..0000000000
--- a/test/prism/snapshots/rescue_modifier.txt
+++ /dev/null
@@ -1,230 +0,0 @@
-@ ProgramNode (location: (1,0)-(7,23))
-├── flags: ∅
-├── locals: [:a]
-└── statements:
- @ StatementsNode (location: (1,0)-(7,23))
- ├── flags: ∅
- └── body: (length: 4)
- ├── @ IfNode (location: (1,0)-(1,15))
- │ ├── flags: newline
- │ ├── if_keyword_loc: (1,11)-(1,13) = "if"
- │ ├── predicate:
- │ │ @ CallNode (location: (1,14)-(1,15))
- │ │ ├── flags: variable_call, ignore_visibility
- │ │ ├── receiver: ∅
- │ │ ├── call_operator_loc: ∅
- │ │ ├── name: :c
- │ │ ├── message_loc: (1,14)-(1,15) = "c"
- │ │ ├── opening_loc: ∅
- │ │ ├── arguments: ∅
- │ │ ├── closing_loc: ∅
- │ │ └── block: ∅
- │ ├── then_keyword_loc: ∅
- │ ├── statements:
- │ │ @ StatementsNode (location: (1,0)-(1,10))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 1)
- │ │ └── @ RescueModifierNode (location: (1,0)-(1,10))
- │ │ ├── flags: newline
- │ │ ├── expression:
- │ │ │ @ CallNode (location: (1,0)-(1,1))
- │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ ├── receiver: ∅
- │ │ │ ├── call_operator_loc: ∅
- │ │ │ ├── name: :a
- │ │ │ ├── message_loc: (1,0)-(1,1) = "a"
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── arguments: ∅
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── block: ∅
- │ │ ├── keyword_loc: (1,2)-(1,8) = "rescue"
- │ │ └── rescue_expression:
- │ │ @ CallNode (location: (1,9)-(1,10))
- │ │ ├── flags: variable_call, ignore_visibility
- │ │ ├── receiver: ∅
- │ │ ├── call_operator_loc: ∅
- │ │ ├── name: :b
- │ │ ├── message_loc: (1,9)-(1,10) = "b"
- │ │ ├── opening_loc: ∅
- │ │ ├── arguments: ∅
- │ │ ├── closing_loc: ∅
- │ │ └── block: ∅
- │ ├── subsequent: ∅
- │ └── end_keyword_loc: ∅
- ├── @ IfNode (location: (3,0)-(3,19))
- │ ├── flags: newline
- │ ├── if_keyword_loc: (3,15)-(3,17) = "if"
- │ ├── predicate:
- │ │ @ CallNode (location: (3,18)-(3,19))
- │ │ ├── flags: variable_call, ignore_visibility
- │ │ ├── receiver: ∅
- │ │ ├── call_operator_loc: ∅
- │ │ ├── name: :d
- │ │ ├── message_loc: (3,18)-(3,19) = "d"
- │ │ ├── opening_loc: ∅
- │ │ ├── arguments: ∅
- │ │ ├── closing_loc: ∅
- │ │ └── block: ∅
- │ ├── then_keyword_loc: ∅
- │ ├── statements:
- │ │ @ StatementsNode (location: (3,0)-(3,14))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 1)
- │ │ └── @ LocalVariableWriteNode (location: (3,0)-(3,14))
- │ │ ├── flags: newline
- │ │ ├── name: :a
- │ │ ├── depth: 0
- │ │ ├── name_loc: (3,0)-(3,1) = "a"
- │ │ ├── value:
- │ │ │ @ RescueModifierNode (location: (3,4)-(3,14))
- │ │ │ ├── flags: ∅
- │ │ │ ├── expression:
- │ │ │ │ @ CallNode (location: (3,4)-(3,5))
- │ │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ │ ├── receiver: ∅
- │ │ │ │ ├── call_operator_loc: ∅
- │ │ │ │ ├── name: :b
- │ │ │ │ ├── message_loc: (3,4)-(3,5) = "b"
- │ │ │ │ ├── opening_loc: ∅
- │ │ │ │ ├── arguments: ∅
- │ │ │ │ ├── closing_loc: ∅
- │ │ │ │ └── block: ∅
- │ │ │ ├── keyword_loc: (3,6)-(3,12) = "rescue"
- │ │ │ └── rescue_expression:
- │ │ │ @ CallNode (location: (3,13)-(3,14))
- │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ ├── receiver: ∅
- │ │ │ ├── call_operator_loc: ∅
- │ │ │ ├── name: :c
- │ │ │ ├── message_loc: (3,13)-(3,14) = "c"
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── arguments: ∅
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── block: ∅
- │ │ └── operator_loc: (3,2)-(3,3) = "="
- │ ├── subsequent: ∅
- │ └── end_keyword_loc: ∅
- ├── @ IfNode (location: (5,0)-(5,20))
- │ ├── flags: newline
- │ ├── if_keyword_loc: (5,16)-(5,18) = "if"
- │ ├── predicate:
- │ │ @ CallNode (location: (5,19)-(5,20))
- │ │ ├── flags: variable_call, ignore_visibility
- │ │ ├── receiver: ∅
- │ │ ├── call_operator_loc: ∅
- │ │ ├── name: :d
- │ │ ├── message_loc: (5,19)-(5,20) = "d"
- │ │ ├── opening_loc: ∅
- │ │ ├── arguments: ∅
- │ │ ├── closing_loc: ∅
- │ │ └── block: ∅
- │ ├── then_keyword_loc: ∅
- │ ├── statements:
- │ │ @ StatementsNode (location: (5,0)-(5,15))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 1)
- │ │ └── @ MultiWriteNode (location: (5,0)-(5,15))
- │ │ ├── flags: newline
- │ │ ├── lefts: (length: 1)
- │ │ │ └── @ LocalVariableTargetNode (location: (5,0)-(5,1))
- │ │ │ ├── flags: ∅
- │ │ │ ├── name: :a
- │ │ │ └── depth: 0
- │ │ ├── rest:
- │ │ │ @ ImplicitRestNode (location: (5,1)-(5,2))
- │ │ │ └── flags: ∅
- │ │ ├── rights: (length: 0)
- │ │ ├── lparen_loc: ∅
- │ │ ├── rparen_loc: ∅
- │ │ ├── operator_loc: (5,3)-(5,4) = "="
- │ │ └── value:
- │ │ @ RescueModifierNode (location: (5,5)-(5,15))
- │ │ ├── flags: ∅
- │ │ ├── expression:
- │ │ │ @ CallNode (location: (5,5)-(5,6))
- │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ ├── receiver: ∅
- │ │ │ ├── call_operator_loc: ∅
- │ │ │ ├── name: :b
- │ │ │ ├── message_loc: (5,5)-(5,6) = "b"
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── arguments: ∅
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── block: ∅
- │ │ ├── keyword_loc: (5,7)-(5,13) = "rescue"
- │ │ └── rescue_expression:
- │ │ @ CallNode (location: (5,14)-(5,15))
- │ │ ├── flags: variable_call, ignore_visibility
- │ │ ├── receiver: ∅
- │ │ ├── call_operator_loc: ∅
- │ │ ├── name: :c
- │ │ ├── message_loc: (5,14)-(5,15) = "c"
- │ │ ├── opening_loc: ∅
- │ │ ├── arguments: ∅
- │ │ ├── closing_loc: ∅
- │ │ └── block: ∅
- │ ├── subsequent: ∅
- │ └── end_keyword_loc: ∅
- └── @ IfNode (location: (7,0)-(7,23))
- ├── flags: newline
- ├── if_keyword_loc: (7,19)-(7,21) = "if"
- ├── predicate:
- │ @ CallNode (location: (7,22)-(7,23))
- │ ├── flags: variable_call, ignore_visibility
- │ ├── receiver: ∅
- │ ├── call_operator_loc: ∅
- │ ├── name: :d
- │ ├── message_loc: (7,22)-(7,23) = "d"
- │ ├── opening_loc: ∅
- │ ├── arguments: ∅
- │ ├── closing_loc: ∅
- │ └── block: ∅
- ├── then_keyword_loc: ∅
- ├── statements:
- │ @ StatementsNode (location: (7,0)-(7,18))
- │ ├── flags: ∅
- │ └── body: (length: 1)
- │ └── @ DefNode (location: (7,0)-(7,18))
- │ ├── flags: newline
- │ ├── name: :a
- │ ├── name_loc: (7,4)-(7,5) = "a"
- │ ├── receiver: ∅
- │ ├── parameters: ∅
- │ ├── body:
- │ │ @ StatementsNode (location: (7,8)-(7,18))
- │ │ ├── flags: ∅
- │ │ └── body: (length: 1)
- │ │ └── @ RescueModifierNode (location: (7,8)-(7,18))
- │ │ ├── flags: ∅
- │ │ ├── expression:
- │ │ │ @ CallNode (location: (7,8)-(7,9))
- │ │ │ ├── flags: variable_call, ignore_visibility
- │ │ │ ├── receiver: ∅
- │ │ │ ├── call_operator_loc: ∅
- │ │ │ ├── name: :b
- │ │ │ ├── message_loc: (7,8)-(7,9) = "b"
- │ │ │ ├── opening_loc: ∅
- │ │ │ ├── arguments: ∅
- │ │ │ ├── closing_loc: ∅
- │ │ │ └── block: ∅
- │ │ ├── keyword_loc: (7,10)-(7,16) = "rescue"
- │ │ └── rescue_expression:
- │ │ @ CallNode (location: (7,17)-(7,18))
- │ │ ├── flags: variable_call, ignore_visibility
- │ │ ├── receiver: ∅
- │ │ ├── call_operator_loc: ∅
- │ │ ├── name: :c
- │ │ ├── message_loc: (7,17)-(7,18) = "c"
- │ │ ├── opening_loc: ∅
- │ │ ├── arguments: ∅
- │ │ ├── closing_loc: ∅
- │ │ └── block: ∅
- │ ├── locals: []
- │ ├── def_keyword_loc: (7,0)-(7,3) = "def"
- │ ├── operator_loc: ∅
- │ ├── lparen_loc: ∅
- │ ├── rparen_loc: ∅
- │ ├── equal_loc: (7,6)-(7,7) = "="
- │ └── end_keyword_loc: ∅
- ├── subsequent: ∅
- └── end_keyword_loc: ∅
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_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/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/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_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 e2f07ba115..cad9bf5cc8 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)
@@ -3597,6 +3610,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)
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 b1defdf82c..6976bd9742 100644
--- a/test/ruby/test_fiber.rb
+++ b/test/ruby/test_fiber.rb
@@ -49,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|
@@ -498,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
@@ -506,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 3bef10dd18..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
@@ -264,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
@@ -289,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]
@@ -296,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)
@@ -315,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
@@ -356,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.
@@ -378,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
@@ -449,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)
@@ -464,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 = {
@@ -531,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
@@ -548,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
@@ -675,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
@@ -716,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')
@@ -731,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
@@ -772,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
@@ -798,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"
@@ -818,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"
@@ -883,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 3eaa93dfae..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
@@ -453,7 +469,7 @@ class TestGCCompact < Test::Unit::TestCase
end;
end
- def test_moving_too_complex_generic_ivar
+ def test_moving_complex_generic_ivar
omit "not compiled with SHAPE_DEBUG" unless defined?(RubyVM::Shape)
assert_separately([], <<~RUBY)
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 3668085d83..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
@@ -1375,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)
@@ -2601,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
@@ -2835,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 }
@@ -2923,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')}
@@ -3826,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")
}
@@ -4262,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|
@@ -4373,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 a865f6100b..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
@@ -1612,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 969cf63311..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
@@ -3269,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
@@ -3292,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
@@ -3371,16 +3370,29 @@ class TestModule < Test::Unit::TestCase
m.const_set(:N, Module.new)
assert_match(/\A#<Module:0x\h+>::N\z/, m::N.name)
- m::N.set_temporary_name("fake_name_under_M")
+ 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)
- m::N.set_temporary_name(nil)
+ assert_raise(FrozenError) {m::N.name.upcase!}
+ assert_same m::N, m::N.set_temporary_name(nil)
assert_nil(m::N.name)
- m.set_temporary_name("fake_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)
- m.set_temporary_name(nil)
+ 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("")
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 ea9752f85a..1554b43f18 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -606,11 +606,11 @@ class TestRubyOptimization < Test::Unit::TestCase
end
class Bug10557
- def [](_)
+ def [](_, &)
block_given?
end
- def []=(_, _)
+ def []=(_, _, &)
block_given?
end
end
@@ -728,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
@@ -946,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
@@ -1080,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}"
@@ -1094,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}"
@@ -1215,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 35aa16063d..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
@@ -1651,33 +1651,103 @@ class TestProc < Test::Unit::TestCase
def test_numparam_is_not_local_variables
"foo".tap do
- _9
+ _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
+ _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
@@ -1686,32 +1756,165 @@ class TestProc < Test::Unit::TestCase
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
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index 5a91e94b09..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 GEM_HOME GEM_PATH]
- 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
@@ -1758,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 = $?
@@ -1800,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
@@ -1993,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
@@ -2384,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
@@ -2771,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 81c2fdf833..44dfbcf9ec 100644
--- a/test/ruby/test_require_lib.rb
+++ b/test/ruby/test_require_lib.rb
@@ -13,7 +13,7 @@ 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
diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb
index c56577228a..4a31f91b4a 100644
--- a/test/ruby/test_rubyoptions.rb
+++ b/test/ruby/test_rubyoptions.rb
@@ -8,8 +8,6 @@ require_relative '../lib/jit_support'
require_relative '../lib/parser_support'
class TestRubyOptions < Test::Unit::TestCase
- 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,8 +20,10 @@ class TestRubyOptions < Test::Unit::TestCase
NO_JIT_DESCRIPTION =
case
- when yjit_enabled?
- RUBY_DESCRIPTION.sub(/\+YJIT( (dev|dev_nodebug|stats))? /, '')
+ when JITSupport.yjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+YJIT( \w+)? /, '')
+ when JITSupport.zjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+ZJIT( \w+)? /, '')
else
RUBY_DESCRIPTION
end
@@ -47,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
@@ -174,7 +179,7 @@ class TestRubyOptions < Test::Unit::TestCase
def test_verbose
assert_in_out_err([{'RUBY_YJIT_ENABLE' => nil}, "-vve", ""]) do |r, e|
assert_match(VERSION_PATTERN, r[0])
- if 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])
@@ -203,6 +208,8 @@ class TestRubyOptions < Test::Unit::TestCase
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
@@ -212,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
@@ -240,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.yjit_enabled? # checking -DYJIT_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])
@@ -260,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"/, [])
@@ -437,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
-
- @verbose = $VERBOSE
- $VERBOSE = nil
+ 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\)]
- 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
@@ -519,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
@@ -785,11 +787,19 @@ 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(RUBY_DESCRIPTION) }\n\n
@@ -832,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)
@@ -858,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
@@ -877,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
@@ -932,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"
@@ -978,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
}
@@ -1276,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 67d8b9028a..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__
@@ -2580,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
@@ -2593,6 +2597,7 @@ CODE
events = []
tp = TracePoint.new(:line) do |tp|
+ next unless tp.path == __FILE__
events << Thread.current
end
@@ -2721,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
@@ -2954,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 0c1d8d424e..ef5dbd9fb1 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,7 +1063,7 @@ 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
@@ -900,16 +1075,29 @@ 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
@@ -920,7 +1108,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))
@@ -940,7 +1128,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
@@ -955,18 +1143,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
@@ -976,6 +1165,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
@@ -991,9 +1181,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
@@ -1036,4 +1225,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 7784e0bdae..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)
@@ -794,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
@@ -811,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
@@ -1476,6 +1480,8 @@ q.pop
end
def test_thread_interrupt_for_killed_thread
+ pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM
+
opts = { timeout: 5, timeout_error: nil }
assert_normal_exit(<<-_end, '[Bug #8996]', **opts)
@@ -1585,4 +1591,107 @@ 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 c0212987e7..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
@@ -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 eaf3e7037e..2411dbc649 100644
--- a/test/rubygems/helper.rb
+++ b/test/rubygems/helper.rb
@@ -3,6 +3,32 @@
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
@@ -12,6 +38,7 @@ require "test/unit"
require "fileutils"
require "pathname"
require "pp"
+require "rubygems/installer"
require "rubygems/package"
require "shellwords"
require "tmpdir"
@@ -19,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.
@@ -60,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
@@ -295,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
@@ -400,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
@@ -418,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
@@ -680,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
##
@@ -713,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|
@@ -799,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
@@ -1022,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
@@ -1184,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).
@@ -1195,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"]
@@ -1567,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..b9a4cf1ce0 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
@@ -527,35 +530,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 +589,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 +1202,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 +1216,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 +1307,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 +1662,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..db738b5e9f 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
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..cc58d7d105 100644
--- a/test/rubygems/test_gem_commands_install_command.rb
+++ b/test/rubygems/test_gem_commands_install_command.rb
@@ -647,17 +647,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 +677,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 +706,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 +880,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 +984,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 +1225,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 +1583,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..cf0fe521a2 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,15 +389,17 @@ 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
end
def test_with_webauthn_enabled_failure
+ pend "Flaky on TruffleRuby" if RUBY_ENGINE == "truffleruby"
response_success = "Owner added successfully."
server = Gem::MockTCPServer.new
error = Gem::WebauthnVerificationError.new("Something went wrong")
@@ -413,9 +418,10 @@ 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: 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..e85d00530e 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"
@@ -58,7 +59,7 @@ class TestGemConfigFile < Gem::TestCase
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"
end
@@ -74,7 +75,7 @@ class TestGemConfigFile < Gem::TestCase
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
end
@@ -83,6 +84,43 @@ 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_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..8d9caf7d90 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
@@ -907,9 +954,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 +1111,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_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 f4e80a801f..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.110"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56cf964f8e44115e50009921ea3d3791b6f74d1ae6d6ed37114fbe03a1cd7308"
+checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
-version = "0.9.110"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "161480347f56473107d4135643b6b1909331eec61445e113b256708a28b691c5"
+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 292a6d984d..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.110"
+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 5ab990b894..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.110"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56cf964f8e44115e50009921ea3d3791b6f74d1ae6d6ed37114fbe03a1cd7308"
+checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
-version = "0.9.110"
+version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "161480347f56473107d4135643b6b1909331eec61445e113b256708a28b691c5"
+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 445bd3a641..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.110"
+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_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..6ebc95ea20 100644
--- a/test/rubygems/test_gem_request_set.rb
+++ b/test/rubygems/test_gem_request_set.rb
@@ -311,6 +311,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_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_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_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_specification.rb b/test/rubygems/test_gem_specification.rb
index 697a26338c..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
@@ -818,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
@@ -837,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
@@ -856,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
@@ -1029,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|
@@ -1248,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
@@ -1554,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
@@ -2216,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
@@ -2227,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
@@ -2671,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
@@ -2808,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
@@ -2883,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
@@ -3009,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
@@ -3664,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
@@ -3892,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 b15cf63297..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
@@ -493,80 +502,54 @@ class TestSocket < Test::Unit::TestCase
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
diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb
index e6a41f5660..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,
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 eb35dfa119..3b6223709c 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)
@@ -429,7 +416,6 @@ module StringScannerTests
end
def test_matched_string
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner('stra strb strc')
s.scan('stra')
assert_equal('stra', s.matched)
@@ -448,7 +434,31 @@ module StringScannerTests
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])
@@ -536,7 +546,6 @@ module StringScannerTests
end
def test_pre_match_string
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner('a b c d e')
s.scan('a')
assert_equal('', s.pre_match)
@@ -581,7 +590,6 @@ module StringScannerTests
end
def test_post_match_string
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
s = create_string_scanner('a b c d e')
s.scan('a')
assert_equal(' b c d e', s.post_match)
@@ -602,18 +610,20 @@ module StringScannerTests
end
def test_terminate
- s = create_string_scanner('ssss')
- s.getch
+ 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
@@ -666,8 +676,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
@@ -737,7 +745,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)
@@ -759,7 +766,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)
@@ -783,7 +789,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)
@@ -802,7 +807,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)
@@ -824,7 +828,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)
@@ -849,11 +852,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
@@ -883,15 +887,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
@@ -939,18 +941,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?)
@@ -974,16 +983,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)
@@ -993,8 +1016,6 @@ 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
@@ -1002,8 +1023,6 @@ module StringScannerTests
end
def test_scan_integer_matched
- omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby"
-
s = create_string_scanner("42abc")
assert_equal(42, s.scan_integer)
assert_equal("42", s.matched)
@@ -1014,15 +1033,15 @@ module StringScannerTests
end
def test_scan_integer_base_16
- omit("scan_integer isn't implemented on TruffleRuby yet") if RUBY_ENGINE == "truffleruby"
-
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?)
@@ -1052,19 +1071,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 8969c3c50f..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" <<
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_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