From b3fb286afdda1e5db95255bc36abfda12d978c57 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 10 Nov 2025 18:00:21 -0600 Subject: [PATCH 001/168] Update compat version to 3.5.0 --- core/pom.xml | 6 +++--- default.build.properties | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 211b620e49e..f8dbfa4f0f3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -55,9 +55,9 @@ DO NOT MODIFY - GENERATED CODE ${build.dir}/test-results provided 2019c - 3.4.5 - 3.4 - 5 + 3.5.0 + 3.5 + 0 diff --git a/default.build.properties b/default.build.properties index c1fcea1fb6c..7d1852da97c 100644 --- a/default.build.properties +++ b/default.build.properties @@ -28,7 +28,7 @@ rake.args= install4j.executable=/Applications/install4j9/bin/install4jc # Ruby versions -version.ruby=3.4.5 -version.ruby.major=3.4 -version.ruby.minor=5 +version.ruby=3.5.0 +version.ruby.major=3.5 +version.ruby.minor=0 From bef058a0432f7a0de5031102d8b659043c1bd583 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 10 Nov 2025 18:02:04 -0600 Subject: [PATCH 002/168] Update most gems for 3.5 Several updated gems do not have releases yet: * RubyGems 4.0.0.dev * bundler 4.0.0.dev * stringio 3.1.8.dev * strscan 3.1.6.dev And the usual CRuby-specific extension gems remain excluded. --- lib/pom.rb | 91 ++- lib/pom.xml | 472 +++++++-------- lib/ruby/stdlib/win32/registry.rb | 925 ------------------------------ pom.rb | 2 +- pom.xml | 2 +- 5 files changed, 291 insertions(+), 1201 deletions(-) delete mode 100644 lib/ruby/stdlib/win32/registry.rb diff --git a/lib/pom.rb b/lib/pom.rb index 5814adb4a7e..e497243f435 100644 --- a/lib/pom.rb +++ b/lib/pom.rb @@ -22,64 +22,54 @@ def log(message = nil) # treat RGs update special: # - we do not want bin/update_rubygems or bin/gem overrides ['rubygems-update', '3.6.9', { bin: false, require_paths: ['lib'] }], - ['benchmark', '0.4.0'], ['bundler', '2.6.9'], ['cgi', '0.4.2'], # Currently using a stub gem for JRuby until we can incorporate our code. # https://github.com/ruby/date/issues/48 - ['date', '3.4.1'], + ['date', '3.5.0'], ['delegate', '0.4.0'], ['did_you_mean', '2.0.0'], - ['digest', '3.2.0'], - ['english', '0.8.0'], + ['digest', '3.2.1'], + ['english', '0.8.1'], # Ongoing discussion about the -java gem, since it just omits the ext: https://github.com/ruby/erb/issues/52 - ['erb', '4.0.4'], + ['erb', '5.1.3'], ['error_highlight', '0.7.0'], # https://github.com/ruby/etc/issues/19 - # ['etc', '1.4.5'], + # ['etc', '1.4.6'], # https://github.com/ruby/fcntl/issues/9 - # ['fcntl', '1.2.0'], + # ['fcntl', '1.3.0'], ['ffi', '1.17.0'], - ['fiddle', '1.1.6'], - ['fileutils', '1.7.3'], + ['fileutils', '1.8.0'], ['find', '0.2.0'], ['forwardable', '1.3.3'], ['io-console', '0.8.1'], # https://github.com/ruby/io-nonblock/issues/4 - # ['io-nonblock', '0.3.1'], - ['io-wait', '0.3.1'], + # ['io-nonblock', '0.3.2'], + ['io-wait', '0.3.3'], ['ipaddr', '1.2.7'], - ['irb', '1.14.3'], ['jar-dependencies', '0.5.4'], ['jruby-readline', '1.3.7'], ['jruby-openssl', '0.15.4'], - ['json', '2.9.1'], - ['logger', '1.6.4'], - ['net-http', '0.6.0'], + ['json', '2.15.2'], + ['net-http', '0.7.0'], ['net-protocol', '0.2.2'], ['open-uri', '0.5.0'], ['open3', '0.2.1'], # https://github.com/ruby/openssl/issues/20#issuecomment-1022872855 - # ['openssl', '3.2.0'], - ['optparse', '0.6.0'], - ['ostruct', '0.6.1'], + # ['openssl', '4.0.0.pre'], + ['optparse', '0.8.0'], # https://github.com/ruby/pathname/issues/17 # ['pathname', '0.4.0'], - ['pp', '0.6.2'], + ['pp', '0.6.3'], ['prettyprint', '0.2.0'], # Not ready to ship in the box yet (native dependencies) - # ['prism', '1.2.0'], - ['pstore', '0.1.4'], - ['psych', '5.2.3'], + # ['prism', '1.6.0'], + ['psych', '5.2.6'], ['rake-ant', '1.0.6'], - ['rdoc', '6.14.0'], - ['reline', '0.6.0'], # https://github.com/ruby/resolv/issues/19 - # ['resolv', '0.6.0'], + # ['resolv', '0.6.3'], ['ruby2_keywords', '0.0.5'], ['securerandom', '0.4.1'], - # https://github.com/ruby/set/issues/21 - # ['set', '1.1.1'], ['shellwords', '0.2.2'], ['singleton', '0.3.0'], ['stringio', '3.1.5'], @@ -91,46 +81,54 @@ def log(message = nil) ['syntax_suggest', '2.0.2'], ['tempfile', '0.3.1'], ['time', '0.4.1'], - ['timeout', '0.4.3'], + ['timeout', '0.4.4'], # https://github.com/ruby/tmpdir/issues/13 # ['tmpdir', '0.3.1'], ['tsort', '0.2.0'], ['un', '0.3.0'], - ['uri', '1.0.3'], - ['weakref', '0.1.3'], + ['uri', '1.1.1'], + ['weakref', '0.1.4'], + ['win32-registry', '0.1.1'], # https://github.com/ruby/win32ole/issues/12 # ['win32ole', '1.9.0'], ['yaml', '0.4.0'] # https://github.com/ruby/zlib/issues/38 - # ['zlib', '3.2.1'], + # ['zlib', '3.2.2'], ] bundled_gems = [ ['abbrev', '0.1.2'], - ['base64', '0.2.0'], + ['base64', '0.3.0'], + ['benchmark', '0.5.0'], # Extension still lives in JRuby. See https://github.com/ruby/bigdecimal/issues/268 - ['bigdecimal', '3.1.9'], - ['csv', '3.3.2'], + ['bigdecimal', '3.3.1'], + ['csv', '3.3.5'], # Newer versions require deep control over CRuby internals, needs work to support JRuby. - # ['debug', '1.10.0'], + # ['debug', '1.11.0'], ['debug', '0.2.1'], - ['drb', '2.2.1'], + ['drb', '2.2.3'], + ['fiddle', '1.1.8'], ['getoptlong', '0.2.1'], - ['matrix', '0.4.2'], - ['minitest', '5.25.4'], + ['irb', '1.15.3'], + ['logger', '1.7.0'], + ['matrix', '0.4.3'], + ['minitest', '5.26.0'], ['mutex_m', '0.3.0'], - ['net-ftp', '0.3.8'], - ['net-imap', '0.5.8'], + ['net-ftp', '0.3.9'], + ['net-imap', '0.5.12'], ['net-pop', '0.1.2'], ['net-smtp', '0.5.1'], ['nkf', '0.2.0'], ['observer', '0.1.2'], - ['power_assert', '2.0.5'], - ['prime', '0.1.3'], + ['ostruct', '0.6.3'], + ['power_assert', '3.0.0'], + ['prime', '0.1.4'], + ['pstore', '0.2.0'], ['racc', '1.8.1'], ['rake', '${rake.version}'], # Depends on many CRuby internals - # ['rbs', '3.8.0'], + # ['rbs', '3.9.5'], + ['rdoc', '6.15.1'], # Ext removed from CRuby in 3.3, equivalent for us would be to remove jruby-readline but unknown implications. # The gem below just attempts to load the extension, and failing that loads reline. Our current readline.rb in # jruby-readline does largely the same, but it finds the extension and does not load reline. @@ -138,15 +136,16 @@ def log(message = nil) # ['readline', '0.0.4'], # Will be solved with readline # ['readline-ext', '0.2.0'], + ['reline', '0.6.2'], # Depends on prism gem with native ext - # ['repl_type_completer', '0.1.1'], + # ['repl_type_completer', '0.1.12'], ['resolv-replace', '0.1.1'], ['rexml', '3.4.4'], ['rinda', '0.2.0'], ['rss', '0.3.1'], # https://github.com/ruby/syslog/issues/1 - # ['syslog', '0.2.0'], - ['test-unit', '3.6.7'] + # ['syslog', '0.3.0'], + ['test-unit', '3.7.0'] # Depends on many CRuby internals # ['typeprof', '0.30.1'], ] diff --git a/lib/pom.xml b/lib/pom.xml index daf890898e6..00c730e81d7 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -44,19 +44,6 @@ DO NOT MODIFY - GENERATED CODE - - rubygems - benchmark - 0.4.0 - gem - provided - - - rubygems - jar-dependencies - - - rubygems bundler @@ -86,7 +73,7 @@ DO NOT MODIFY - GENERATED CODE rubygems date - 3.4.1 + 3.5.0 gem provided @@ -125,7 +112,7 @@ DO NOT MODIFY - GENERATED CODE rubygems digest - 3.2.0 + 3.2.1 gem provided @@ -138,7 +125,7 @@ DO NOT MODIFY - GENERATED CODE rubygems english - 0.8.0 + 0.8.1 gem provided @@ -151,7 +138,7 @@ DO NOT MODIFY - GENERATED CODE rubygems erb - 4.0.4 + 5.1.3 gem provided @@ -187,23 +174,10 @@ DO NOT MODIFY - GENERATED CODE - - rubygems - fiddle - 1.1.6 - gem - provided - - - rubygems - jar-dependencies - - - rubygems fileutils - 1.7.3 + 1.8.0 gem provided @@ -255,7 +229,7 @@ DO NOT MODIFY - GENERATED CODE rubygems io-wait - 0.3.1 + 0.3.3 gem provided @@ -278,19 +252,6 @@ DO NOT MODIFY - GENERATED CODE - - rubygems - irb - 1.14.3 - gem - provided - - - rubygems - jar-dependencies - - - rubygems jar-dependencies @@ -333,20 +294,7 @@ DO NOT MODIFY - GENERATED CODE rubygems json - 2.9.1 - gem - provided - - - rubygems - jar-dependencies - - - - - rubygems - logger - 1.6.4 + 2.15.2 gem provided @@ -359,7 +307,7 @@ DO NOT MODIFY - GENERATED CODE rubygems net-http - 0.6.0 + 0.7.0 gem provided @@ -411,20 +359,7 @@ DO NOT MODIFY - GENERATED CODE rubygems optparse - 0.6.0 - gem - provided - - - rubygems - jar-dependencies - - - - - rubygems - ostruct - 0.6.1 + 0.8.0 gem provided @@ -437,7 +372,7 @@ DO NOT MODIFY - GENERATED CODE rubygems pp - 0.6.2 + 0.6.3 gem provided @@ -460,23 +395,10 @@ DO NOT MODIFY - GENERATED CODE - - rubygems - pstore - 0.1.4 - gem - provided - - - rubygems - jar-dependencies - - - rubygems psych - 5.2.3 + 5.2.6 gem provided @@ -499,32 +421,6 @@ DO NOT MODIFY - GENERATED CODE - - rubygems - rdoc - 6.14.0 - gem - provided - - - rubygems - jar-dependencies - - - - - rubygems - reline - 0.6.0 - gem - provided - - - rubygems - jar-dependencies - - - rubygems ruby2_keywords @@ -697,7 +593,7 @@ DO NOT MODIFY - GENERATED CODE rubygems timeout - 0.4.3 + 0.4.4 gem provided @@ -736,7 +632,7 @@ DO NOT MODIFY - GENERATED CODE rubygems uri - 1.0.3 + 1.1.1 gem provided @@ -749,7 +645,20 @@ DO NOT MODIFY - GENERATED CODE rubygems weakref - 0.1.3 + 0.1.4 + gem + provided + + + rubygems + jar-dependencies + + + + + rubygems + win32-registry + 0.1.1 gem provided @@ -788,7 +697,20 @@ DO NOT MODIFY - GENERATED CODE rubygems base64 - 0.2.0 + 0.3.0 + gem + provided + + + rubygems + jar-dependencies + + + + + rubygems + benchmark + 0.5.0 gem provided @@ -801,7 +723,7 @@ DO NOT MODIFY - GENERATED CODE rubygems bigdecimal - 3.1.9 + 3.3.1 gem provided @@ -814,7 +736,7 @@ DO NOT MODIFY - GENERATED CODE rubygems csv - 3.3.2 + 3.3.5 gem provided @@ -840,7 +762,20 @@ DO NOT MODIFY - GENERATED CODE rubygems drb - 2.2.1 + 2.2.3 + gem + provided + + + rubygems + jar-dependencies + + + + + rubygems + fiddle + 1.1.8 gem provided @@ -863,10 +798,36 @@ DO NOT MODIFY - GENERATED CODE + + rubygems + irb + 1.15.3 + gem + provided + + + rubygems + jar-dependencies + + + + + rubygems + logger + 1.7.0 + gem + provided + + + rubygems + jar-dependencies + + + rubygems matrix - 0.4.2 + 0.4.3 gem provided @@ -879,7 +840,7 @@ DO NOT MODIFY - GENERATED CODE rubygems minitest - 5.25.4 + 5.26.0 gem provided @@ -905,7 +866,7 @@ DO NOT MODIFY - GENERATED CODE rubygems net-ftp - 0.3.8 + 0.3.9 gem provided @@ -918,7 +879,7 @@ DO NOT MODIFY - GENERATED CODE rubygems net-imap - 0.5.8 + 0.5.12 gem provided @@ -980,10 +941,23 @@ DO NOT MODIFY - GENERATED CODE + + rubygems + ostruct + 0.6.3 + gem + provided + + + rubygems + jar-dependencies + + + rubygems power_assert - 2.0.5 + 3.0.0 gem provided @@ -996,7 +970,20 @@ DO NOT MODIFY - GENERATED CODE rubygems prime - 0.1.3 + 0.1.4 + gem + provided + + + rubygems + jar-dependencies + + + + + rubygems + pstore + 0.2.0 gem provided @@ -1032,6 +1019,32 @@ DO NOT MODIFY - GENERATED CODE + + rubygems + rdoc + 6.15.1 + gem + provided + + + rubygems + jar-dependencies + + + + + rubygems + reline + 0.6.2 + gem + provided + + + rubygems + jar-dependencies + + + rubygems resolv-replace @@ -1087,7 +1100,7 @@ DO NOT MODIFY - GENERATED CODE rubygems test-unit - 3.6.7 + 3.7.0 gem provided @@ -1119,43 +1132,35 @@ DO NOT MODIFY - GENERATED CODE specifications/default/* specifications/rubygems-update-3.6.9* - specifications/benchmark-0.4.0* specifications/bundler-2.6.9* specifications/cgi-0.4.2* - specifications/date-3.4.1* + specifications/date-3.5.0* specifications/delegate-0.4.0* specifications/did_you_mean-2.0.0* - specifications/digest-3.2.0* - specifications/english-0.8.0* - specifications/erb-4.0.4* + specifications/digest-3.2.1* + specifications/english-0.8.1* + specifications/erb-5.1.3* specifications/error_highlight-0.7.0* specifications/ffi-1.17.0* - specifications/fiddle-1.1.6* - specifications/fileutils-1.7.3* + specifications/fileutils-1.8.0* specifications/find-0.2.0* specifications/forwardable-1.3.3* specifications/io-console-0.8.1* - specifications/io-wait-0.3.1* + specifications/io-wait-0.3.3* specifications/ipaddr-1.2.7* - specifications/irb-1.14.3* specifications/jar-dependencies-0.5.4* specifications/jruby-readline-1.3.7* specifications/jruby-openssl-0.15.4* - specifications/json-2.9.1* - specifications/logger-1.6.4* - specifications/net-http-0.6.0* + specifications/json-2.15.2* + specifications/net-http-0.7.0* specifications/net-protocol-0.2.2* specifications/open-uri-0.5.0* specifications/open3-0.2.1* - specifications/optparse-0.6.0* - specifications/ostruct-0.6.1* - specifications/pp-0.6.2* + specifications/optparse-0.8.0* + specifications/pp-0.6.3* specifications/prettyprint-0.2.0* - specifications/pstore-0.1.4* - specifications/psych-5.2.3* + specifications/psych-5.2.6* specifications/rake-ant-1.0.6* - specifications/rdoc-6.14.0* - specifications/reline-0.6.0* specifications/ruby2_keywords-0.0.5* specifications/securerandom-0.4.1* specifications/shellwords-0.2.2* @@ -1169,75 +1174,76 @@ DO NOT MODIFY - GENERATED CODE specifications/syntax_suggest-2.0.2* specifications/tempfile-0.3.1* specifications/time-0.4.1* - specifications/timeout-0.4.3* + specifications/timeout-0.4.4* specifications/tsort-0.2.0* specifications/un-0.3.0* - specifications/uri-1.0.3* - specifications/weakref-0.1.3* + specifications/uri-1.1.1* + specifications/weakref-0.1.4* + specifications/win32-registry-0.1.1* specifications/yaml-0.4.0* specifications/abbrev-0.1.2* - specifications/base64-0.2.0* - specifications/bigdecimal-3.1.9* - specifications/csv-3.3.2* + specifications/base64-0.3.0* + specifications/benchmark-0.5.0* + specifications/bigdecimal-3.3.1* + specifications/csv-3.3.5* specifications/debug-0.2.1* - specifications/drb-2.2.1* + specifications/drb-2.2.3* + specifications/fiddle-1.1.8* specifications/getoptlong-0.2.1* - specifications/matrix-0.4.2* - specifications/minitest-5.25.4* + specifications/irb-1.15.3* + specifications/logger-1.7.0* + specifications/matrix-0.4.3* + specifications/minitest-5.26.0* specifications/mutex_m-0.3.0* - specifications/net-ftp-0.3.8* - specifications/net-imap-0.5.8* + specifications/net-ftp-0.3.9* + specifications/net-imap-0.5.12* specifications/net-pop-0.1.2* specifications/net-smtp-0.5.1* specifications/nkf-0.2.0* specifications/observer-0.1.2* - specifications/power_assert-2.0.5* - specifications/prime-0.1.3* + specifications/ostruct-0.6.3* + specifications/power_assert-3.0.0* + specifications/prime-0.1.4* + specifications/pstore-0.2.0* specifications/racc-1.8.1* specifications/rake-${rake.version}* + specifications/rdoc-6.15.1* + specifications/reline-0.6.2* specifications/resolv-replace-0.1.1* specifications/rexml-3.4.4* specifications/rinda-0.2.0* specifications/rss-0.3.1* - specifications/test-unit-3.6.7* + specifications/test-unit-3.7.0* gems/rubygems-update-3.6.9*/**/* - gems/benchmark-0.4.0*/**/* gems/bundler-2.6.9*/**/* gems/cgi-0.4.2*/**/* - gems/date-3.4.1*/**/* + gems/date-3.5.0*/**/* gems/delegate-0.4.0*/**/* gems/did_you_mean-2.0.0*/**/* - gems/digest-3.2.0*/**/* - gems/english-0.8.0*/**/* - gems/erb-4.0.4*/**/* + gems/digest-3.2.1*/**/* + gems/english-0.8.1*/**/* + gems/erb-5.1.3*/**/* gems/error_highlight-0.7.0*/**/* gems/ffi-1.17.0*/**/* - gems/fiddle-1.1.6*/**/* - gems/fileutils-1.7.3*/**/* + gems/fileutils-1.8.0*/**/* gems/find-0.2.0*/**/* gems/forwardable-1.3.3*/**/* gems/io-console-0.8.1*/**/* - gems/io-wait-0.3.1*/**/* + gems/io-wait-0.3.3*/**/* gems/ipaddr-1.2.7*/**/* - gems/irb-1.14.3*/**/* gems/jar-dependencies-0.5.4*/**/* gems/jruby-readline-1.3.7*/**/* gems/jruby-openssl-0.15.4*/**/* - gems/json-2.9.1*/**/* - gems/logger-1.6.4*/**/* - gems/net-http-0.6.0*/**/* + gems/json-2.15.2*/**/* + gems/net-http-0.7.0*/**/* gems/net-protocol-0.2.2*/**/* gems/open-uri-0.5.0*/**/* gems/open3-0.2.1*/**/* - gems/optparse-0.6.0*/**/* - gems/ostruct-0.6.1*/**/* - gems/pp-0.6.2*/**/* + gems/optparse-0.8.0*/**/* + gems/pp-0.6.3*/**/* gems/prettyprint-0.2.0*/**/* - gems/pstore-0.1.4*/**/* - gems/psych-5.2.3*/**/* + gems/psych-5.2.6*/**/* gems/rake-ant-1.0.6*/**/* - gems/rdoc-6.14.0*/**/* - gems/reline-0.6.0*/**/* gems/ruby2_keywords-0.0.5*/**/* gems/securerandom-0.4.1*/**/* gems/shellwords-0.2.2*/**/* @@ -1251,75 +1257,76 @@ DO NOT MODIFY - GENERATED CODE gems/syntax_suggest-2.0.2*/**/* gems/tempfile-0.3.1*/**/* gems/time-0.4.1*/**/* - gems/timeout-0.4.3*/**/* + gems/timeout-0.4.4*/**/* gems/tsort-0.2.0*/**/* gems/un-0.3.0*/**/* - gems/uri-1.0.3*/**/* - gems/weakref-0.1.3*/**/* + gems/uri-1.1.1*/**/* + gems/weakref-0.1.4*/**/* + gems/win32-registry-0.1.1*/**/* gems/yaml-0.4.0*/**/* gems/abbrev-0.1.2*/**/* - gems/base64-0.2.0*/**/* - gems/bigdecimal-3.1.9*/**/* - gems/csv-3.3.2*/**/* + gems/base64-0.3.0*/**/* + gems/benchmark-0.5.0*/**/* + gems/bigdecimal-3.3.1*/**/* + gems/csv-3.3.5*/**/* gems/debug-0.2.1*/**/* - gems/drb-2.2.1*/**/* + gems/drb-2.2.3*/**/* + gems/fiddle-1.1.8*/**/* gems/getoptlong-0.2.1*/**/* - gems/matrix-0.4.2*/**/* - gems/minitest-5.25.4*/**/* + gems/irb-1.15.3*/**/* + gems/logger-1.7.0*/**/* + gems/matrix-0.4.3*/**/* + gems/minitest-5.26.0*/**/* gems/mutex_m-0.3.0*/**/* - gems/net-ftp-0.3.8*/**/* - gems/net-imap-0.5.8*/**/* + gems/net-ftp-0.3.9*/**/* + gems/net-imap-0.5.12*/**/* gems/net-pop-0.1.2*/**/* gems/net-smtp-0.5.1*/**/* gems/nkf-0.2.0*/**/* gems/observer-0.1.2*/**/* - gems/power_assert-2.0.5*/**/* - gems/prime-0.1.3*/**/* + gems/ostruct-0.6.3*/**/* + gems/power_assert-3.0.0*/**/* + gems/prime-0.1.4*/**/* + gems/pstore-0.2.0*/**/* gems/racc-1.8.1*/**/* gems/rake-${rake.version}*/**/* + gems/rdoc-6.15.1*/**/* + gems/reline-0.6.2*/**/* gems/resolv-replace-0.1.1*/**/* gems/rexml-3.4.4*/**/* gems/rinda-0.2.0*/**/* gems/rss-0.3.1*/**/* - gems/test-unit-3.6.7*/**/* + gems/test-unit-3.7.0*/**/* cache/rubygems-update-3.6.9* - cache/benchmark-0.4.0* cache/bundler-2.6.9* cache/cgi-0.4.2* - cache/date-3.4.1* + cache/date-3.5.0* cache/delegate-0.4.0* cache/did_you_mean-2.0.0* - cache/digest-3.2.0* - cache/english-0.8.0* - cache/erb-4.0.4* + cache/digest-3.2.1* + cache/english-0.8.1* + cache/erb-5.1.3* cache/error_highlight-0.7.0* cache/ffi-1.17.0* - cache/fiddle-1.1.6* - cache/fileutils-1.7.3* + cache/fileutils-1.8.0* cache/find-0.2.0* cache/forwardable-1.3.3* cache/io-console-0.8.1* - cache/io-wait-0.3.1* + cache/io-wait-0.3.3* cache/ipaddr-1.2.7* - cache/irb-1.14.3* cache/jar-dependencies-0.5.4* cache/jruby-readline-1.3.7* cache/jruby-openssl-0.15.4* - cache/json-2.9.1* - cache/logger-1.6.4* - cache/net-http-0.6.0* + cache/json-2.15.2* + cache/net-http-0.7.0* cache/net-protocol-0.2.2* cache/open-uri-0.5.0* cache/open3-0.2.1* - cache/optparse-0.6.0* - cache/ostruct-0.6.1* - cache/pp-0.6.2* + cache/optparse-0.8.0* + cache/pp-0.6.3* cache/prettyprint-0.2.0* - cache/pstore-0.1.4* - cache/psych-5.2.3* + cache/psych-5.2.6* cache/rake-ant-1.0.6* - cache/rdoc-6.14.0* - cache/reline-0.6.0* cache/ruby2_keywords-0.0.5* cache/securerandom-0.4.1* cache/shellwords-0.2.2* @@ -1333,37 +1340,46 @@ DO NOT MODIFY - GENERATED CODE cache/syntax_suggest-2.0.2* cache/tempfile-0.3.1* cache/time-0.4.1* - cache/timeout-0.4.3* + cache/timeout-0.4.4* cache/tsort-0.2.0* cache/un-0.3.0* - cache/uri-1.0.3* - cache/weakref-0.1.3* + cache/uri-1.1.1* + cache/weakref-0.1.4* + cache/win32-registry-0.1.1* cache/yaml-0.4.0* cache/abbrev-0.1.2* - cache/base64-0.2.0* - cache/bigdecimal-3.1.9* - cache/csv-3.3.2* + cache/base64-0.3.0* + cache/benchmark-0.5.0* + cache/bigdecimal-3.3.1* + cache/csv-3.3.5* cache/debug-0.2.1* - cache/drb-2.2.1* + cache/drb-2.2.3* + cache/fiddle-1.1.8* cache/getoptlong-0.2.1* - cache/matrix-0.4.2* - cache/minitest-5.25.4* + cache/irb-1.15.3* + cache/logger-1.7.0* + cache/matrix-0.4.3* + cache/minitest-5.26.0* cache/mutex_m-0.3.0* - cache/net-ftp-0.3.8* - cache/net-imap-0.5.8* + cache/net-ftp-0.3.9* + cache/net-imap-0.5.12* cache/net-pop-0.1.2* cache/net-smtp-0.5.1* cache/nkf-0.2.0* cache/observer-0.1.2* - cache/power_assert-2.0.5* - cache/prime-0.1.3* + cache/ostruct-0.6.3* + cache/power_assert-3.0.0* + cache/prime-0.1.4* + cache/pstore-0.2.0* cache/racc-1.8.1* cache/rake-${rake.version}* + cache/rdoc-6.15.1* + cache/reline-0.6.2* cache/resolv-replace-0.1.1* cache/rexml-3.4.4* cache/rinda-0.2.0* cache/rss-0.3.1* - cache/test-unit-3.6.7* + cache/test-unit-3.7.0* diff --git a/lib/ruby/stdlib/win32/registry.rb b/lib/ruby/stdlib/win32/registry.rb deleted file mode 100644 index d0cbb6afcfc..00000000000 --- a/lib/ruby/stdlib/win32/registry.rb +++ /dev/null @@ -1,925 +0,0 @@ -# frozen_string_literal: true -require 'fiddle/import' - -module Win32 - -=begin rdoc -= Win32 Registry - -win32/registry is registry accessor library for Win32 platform. -It uses importer to call Win32 Registry APIs. - -== example - Win32::Registry::HKEY_CURRENT_USER.open('SOFTWARE\foo') do |reg| - value = reg['foo'] # read a value - value = reg['foo', Win32::Registry::REG_SZ] # read a value with type - type, value = reg.read('foo') # read a value - reg['foo'] = 'bar' # write a value - reg['foo', Win32::Registry::REG_SZ] = 'bar' # write a value with type - reg.write('foo', Win32::Registry::REG_SZ, 'bar') # write a value - - reg.each_value { |name, type, data| ... } # Enumerate values - reg.each_key { |key, wtime| ... } # Enumerate subkeys - - reg.delete_value(name) # Delete a value - reg.delete_key(name) # Delete a subkey - reg.delete_key(name, true) # Delete a subkey recursively - end - -= Reference - -== Win32::Registry class - ---- info - ---- num_keys - ---- max_key_length - ---- num_values - ---- max_value_name_length - ---- max_value_length - ---- descriptor_length - ---- wtime - Returns an item of key information. - -=== constants ---- HKEY_CLASSES_ROOT - ---- HKEY_CURRENT_USER - ---- HKEY_LOCAL_MACHINE - ---- HKEY_PERFORMANCE_DATA - ---- HKEY_CURRENT_CONFIG - ---- HKEY_DYN_DATA - - Win32::Registry object whose key is predefined key. -For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/predefined_keys.asp] article. - -=end rdoc - - WCHAR = Encoding::UTF_16LE - WCHAR_NUL = "\0".encode(WCHAR).freeze - WCHAR_CR = "\r".encode(WCHAR).freeze - WCHAR_SIZE = WCHAR_NUL.bytesize - LOCALE = Encoding::UTF_8 - - class Registry - - # - # For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/registry.asp]. - # - # --- HKEY_* - # - # Predefined key ((*handle*)). - # These are Integer, not Win32::Registry. - # - # --- REG_* - # - # Registry value type. - # - # --- KEY_* - # - # Security access mask. - # - # --- KEY_OPTIONS_* - # - # Key options. - # - # --- REG_CREATED_NEW_KEY - # - # --- REG_OPENED_EXISTING_KEY - # - # If the key is created newly or opened existing key. - # See also Registry#disposition method. - module Constants - HKEY_CLASSES_ROOT = 0x80000000 - HKEY_CURRENT_USER = 0x80000001 - HKEY_LOCAL_MACHINE = 0x80000002 - HKEY_USERS = 0x80000003 - HKEY_PERFORMANCE_DATA = 0x80000004 - HKEY_PERFORMANCE_TEXT = 0x80000050 - HKEY_PERFORMANCE_NLSTEXT = 0x80000060 - HKEY_CURRENT_CONFIG = 0x80000005 - HKEY_DYN_DATA = 0x80000006 - - REG_NONE = 0 - REG_SZ = 1 - REG_EXPAND_SZ = 2 - REG_BINARY = 3 - REG_DWORD = 4 - REG_DWORD_LITTLE_ENDIAN = 4 - REG_DWORD_BIG_ENDIAN = 5 - REG_LINK = 6 - REG_MULTI_SZ = 7 - REG_RESOURCE_LIST = 8 - REG_FULL_RESOURCE_DESCRIPTOR = 9 - REG_RESOURCE_REQUIREMENTS_LIST = 10 - REG_QWORD = 11 - REG_QWORD_LITTLE_ENDIAN = 11 - - STANDARD_RIGHTS_READ = 0x00020000 - STANDARD_RIGHTS_WRITE = 0x00020000 - KEY_QUERY_VALUE = 0x0001 - KEY_SET_VALUE = 0x0002 - KEY_CREATE_SUB_KEY = 0x0004 - KEY_ENUMERATE_SUB_KEYS = 0x0008 - KEY_NOTIFY = 0x0010 - KEY_CREATE_LINK = 0x0020 - KEY_READ = STANDARD_RIGHTS_READ | - KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY - KEY_WRITE = STANDARD_RIGHTS_WRITE | - KEY_SET_VALUE | KEY_CREATE_SUB_KEY - KEY_EXECUTE = KEY_READ - KEY_ALL_ACCESS = KEY_READ | KEY_WRITE | KEY_CREATE_LINK - - REG_OPTION_RESERVED = 0x0000 - REG_OPTION_NON_VOLATILE = 0x0000 - REG_OPTION_VOLATILE = 0x0001 - REG_OPTION_CREATE_LINK = 0x0002 - REG_OPTION_BACKUP_RESTORE = 0x0004 - REG_OPTION_OPEN_LINK = 0x0008 - REG_LEGAL_OPTION = REG_OPTION_RESERVED | - REG_OPTION_NON_VOLATILE | REG_OPTION_CREATE_LINK | - REG_OPTION_BACKUP_RESTORE | REG_OPTION_OPEN_LINK - - REG_CREATED_NEW_KEY = 1 - REG_OPENED_EXISTING_KEY = 2 - - REG_WHOLE_HIVE_VOLATILE = 0x0001 - REG_REFRESH_HIVE = 0x0002 - REG_NO_LAZY_FLUSH = 0x0004 - REG_FORCE_RESTORE = 0x0008 - - MAX_KEY_LENGTH = 514 - MAX_VALUE_LENGTH = 32768 - end - include Constants - include Enumerable - - # - # Error - # - class Error < ::StandardError - module Kernel32 - extend Fiddle::Importer - dlload "kernel32.dll" - end - FormatMessageW = Kernel32.extern "int FormatMessageW(int, void *, int, int, void *, int, void *)", :stdcall - def initialize(code) - @code = code - buff = WCHAR_NUL * 1024 - lang = 0 - begin - len = FormatMessageW.call(0x1200, nil, code, lang, buff, 1024, nil) - msg = buff.byteslice(0, len * WCHAR_SIZE) - msg.delete!(WCHAR_CR) - msg.chomp! - msg.encode!(LOCALE) - rescue EncodingError - raise unless lang == 0 - lang = 0x0409 # en_US - retry - end - super msg - end - attr_reader :code - end - - # - # Predefined Keys - # - class PredefinedKey < Registry - def initialize(hkey, keyname) - @hkey = Fiddle::Pointer.new(hkey) - @parent = nil - @keyname = keyname - @disposition = REG_OPENED_EXISTING_KEY - end - - # Predefined keys cannot be closed - def close - raise Error.new(5) ## ERROR_ACCESS_DENIED - end - - # Fake #class method for Registry#open, Registry#create - def class - Registry - end - - # Make all - Constants.constants.grep(/^HKEY_/) do |c| - Registry.const_set c, new(Constants.const_get(c), c.to_s) - end - end - - # - # Win32 APIs - # - module API - include Constants - extend Fiddle::Importer - dlload "advapi32.dll" - [ - "long RegOpenKeyExW(void *, void *, long, long, void *)", - "long RegCreateKeyExW(void *, void *, long, long, long, long, void *, void *, void *)", - "long RegEnumValueW(void *, long, void *, void *, void *, void *, void *, void *)", - "long RegEnumKeyExW(void *, long, void *, void *, void *, void *, void *, void *)", - "long RegQueryValueExW(void *, void *, void *, void *, void *, void *)", - "long RegSetValueExW(void *, void *, long, long, void *, long)", - "long RegDeleteValueW(void *, void *)", - "long RegDeleteKeyW(void *, void *)", - "long RegFlushKey(void *)", - "long RegCloseKey(void *)", - "long RegQueryInfoKeyW(void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *, void *)", - ].each do |fn| - cfunc = extern fn, :stdcall - const_set cfunc.name.intern, cfunc - end - - module_function - - def check(result) - raise Error, result, caller(1) if result != 0 - end - - def win64? - /^(?:x64|x86_64)/ =~ RUBY_PLATFORM - end - - TEMPLATE_HANDLE = 'J<' - - def packhandle(h) - [h].pack(TEMPLATE_HANDLE) - end - - def unpackhandle(h) - (h + [0].pack(TEMPLATE_HANDLE)).unpack1(TEMPLATE_HANDLE) - end - - TEMPLATE_DWORD = 'V' - - def packdw(dw) - [dw].pack(TEMPLATE_DWORD) - end - - def unpackdw(dw) - (dw + [0].pack(TEMPLATE_DWORD)).unpack1(TEMPLATE_DWORD) - end - - TEMPLATE_QWORD = 'Q<' - - def packqw(qw) - [qw].pack(TEMPLATE_QWORD) - end - - def unpackqw(qw) - (qw + [0].pack(TEMPLATE_QWORD)).unpack1(TEMPLATE_QWORD) - end - - def make_wstr(str) - (str+"\0").encode(WCHAR) - end - - def OpenKey(hkey, name, opt, desired) - result = packhandle(0) - check RegOpenKeyExW.call(hkey, make_wstr(name), opt, desired, result) - unpackhandle(result) - end - - def CreateKey(hkey, name, opt, desired) - result = packhandle(0) - disp = packdw(0) - check RegCreateKeyExW.call(hkey, make_wstr(name), 0, 0, opt, desired, - nil, result, disp) - [ unpackhandle(result), unpackdw(disp) ] - end - - def EnumValue(hkey, index) - name = WCHAR_NUL * Constants::MAX_KEY_LENGTH - size = packdw(Constants::MAX_KEY_LENGTH) - check RegEnumValueW.call(hkey, index, name, size, nil, nil, nil, nil) - name.byteslice(0, unpackdw(size) * WCHAR_SIZE) - end - - def EnumKey(hkey, index) - name = WCHAR_NUL * Constants::MAX_KEY_LENGTH - size = packdw(Constants::MAX_KEY_LENGTH) - wtime = ' ' * 8 - check RegEnumKeyExW.call(hkey, index, name, size, nil, nil, nil, wtime) - [ name.byteslice(0, unpackdw(size) * WCHAR_SIZE), unpackqw(wtime) ] - end - - def QueryValue(hkey, name) - type = packdw(0) - size = packdw(0) - name = make_wstr(name) - check RegQueryValueExW.call(hkey, name, nil, type, nil, size) - data = "\0".b * unpackdw(size) - check RegQueryValueExW.call(hkey, name, nil, type, data, size) - [ unpackdw(type), data[0, unpackdw(size)] ] - end - - def SetValue(hkey, name, type, data, size) - case type - when REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ - data = data.encode(WCHAR) - size ||= data.bytesize + WCHAR_SIZE - end - check RegSetValueExW.call(hkey, make_wstr(name), 0, type, data, size) - end - - def DeleteValue(hkey, name) - check RegDeleteValueW.call(hkey, make_wstr(name)) - end - - def DeleteKey(hkey, name) - check RegDeleteKeyW.call(hkey, make_wstr(name)) - end - - def FlushKey(hkey) - check RegFlushKey.call(hkey) - end - - def CloseKey(hkey) - check RegCloseKey.call(hkey) - end - - def QueryInfoKey(hkey) - subkeys = packdw(0) - maxsubkeylen = packdw(0) - values = packdw(0) - maxvaluenamelen = packdw(0) - maxvaluelen = packdw(0) - secdescs = packdw(0) - wtime = ' ' * 8 - check RegQueryInfoKeyW.call(hkey, 0, 0, 0, subkeys, maxsubkeylen, 0, - values, maxvaluenamelen, maxvaluelen, secdescs, wtime) - [ unpackdw(subkeys), unpackdw(maxsubkeylen), unpackdw(values), - unpackdw(maxvaluenamelen), unpackdw(maxvaluelen), - unpackdw(secdescs), unpackqw(wtime) ] - end - end - - # - # Replace %\w+% into the environment value of what is contained between the %'s - # This method is used for REG_EXPAND_SZ. - # - # For detail, see expandEnvironmentStrings[http://msdn.microsoft.com/library/en-us/sysinfo/base/expandenvironmentstrings.asp] \Win32 \API. - # - def self.expand_environ(str) - str.gsub(Regexp.compile("%([^%]+)%".encode(str.encoding))) { - v = $1.encode(LOCALE) - (ENV[v] || ENV[v.upcase])&.encode(str.encoding) || $& - } - end - - @@type2name = %w[ - REG_NONE REG_SZ REG_EXPAND_SZ REG_BINARY REG_DWORD - REG_DWORD_BIG_ENDIAN REG_LINK REG_MULTI_SZ - REG_RESOURCE_LIST REG_FULL_RESOURCE_DESCRIPTOR - REG_RESOURCE_REQUIREMENTS_LIST REG_QWORD - ].inject([]) do |ary, type| - ary[Constants.const_get(type)] = type - ary - end.freeze - - # - # Convert registry type value to readable string. - # - def self.type2name(type) - @@type2name[type] || type.to_s - end - - # - # Convert 64-bit FILETIME integer into Time object. - # - def self.wtime2time(wtime) - Time.at((wtime - 116444736000000000) / 10000000) - end - - # - # Convert Time object or Integer object into 64-bit FILETIME. - # - def self.time2wtime(time) - time.to_i * 10000000 + 116444736000000000 - end - - # - # constructor - # - private_class_method :new - - # - # --- Registry.open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) - # - # --- Registry.open(key, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) { |reg| ... } - # - # Open the registry key subkey under key. - # key is Win32::Registry object of parent key. - # You can use predefined key HKEY_* (see Constants) - # desired and opt is access mask and key option. - # For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/regopenkeyex.asp]. - # If block is given, the key is closed automatically. - def self.open(hkey, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED) - subkey = subkey.chomp('\\') - newkey = API.OpenKey(hkey.instance_variable_get(:@hkey), subkey, opt, desired) - obj = new(newkey, hkey, subkey, REG_OPENED_EXISTING_KEY) - if block_given? - begin - yield obj - ensure - obj.close - end - else - obj - end - end - - # - # --- Registry.create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) - # - # --- Registry.create(key, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) { |reg| ... } - # - # Create or open the registry key subkey under key. - # You can use predefined key HKEY_* (see Constants) - # - # If subkey is already exists, key is opened and Registry#created? - # method will return false. - # - # If block is given, the key is closed automatically. - # - def self.create(hkey, subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED) - newkey, disp = API.CreateKey(hkey.instance_variable_get(:@hkey), subkey, opt, desired) - obj = new(newkey, hkey, subkey, disp) - if block_given? - begin - yield obj - ensure - obj.close - end - else - obj - end - end - - # - # finalizer - # - @@final = proc { |hkey| proc { API.CloseKey(hkey[0]) if hkey[0] } } - - # - # initialize - # - def initialize(hkey, parent, keyname, disposition) - @hkey = Fiddle::Pointer.new(hkey) - @parent = parent - @keyname = keyname - @disposition = disposition - @hkeyfinal = [ hkey ] - ObjectSpace.define_finalizer self, @@final.call(@hkeyfinal) - end - - # Win32::Registry object of parent key, or nil if predefeined key. - attr_reader :parent - # Same as subkey value of Registry.open or - # Registry.create method. - attr_reader :keyname - # Disposition value (REG_CREATED_NEW_KEY or REG_OPENED_EXISTING_KEY). - attr_reader :disposition - - # Returns key handle value. - def hkey - @hkey.to_i - end - - # - # Returns if key is created ((*newly*)). - # (see Registry.create) -- basically you call create - # then when you call created? on the instance returned - # it will tell if it was successful or not - # - def created? - @disposition == REG_CREATED_NEW_KEY - end - - # - # Returns if key is not closed. - # - def open? - !@hkey.nil? - end - - # - # Full path of key such as 'HKEY_CURRENT_USER\SOFTWARE\foo\bar'. - # - def name - parent = self - name = @keyname - while parent = parent.parent - name = parent.keyname + '\\' + name - end - name - end - - def inspect - "\#" - end - - # - # marshalling is not allowed - # - def _dump(depth) - raise TypeError, "can't dump Win32::Registry" - end - - # - # Same as Win32::Registry.open (self, subkey, desired, opt) - # - def open(subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED, &blk) - self.class.open(self, subkey, desired, opt, &blk) - end - - # - # Same as Win32::Registry.create (self, subkey, desired, opt) - # - def create(subkey, desired = KEY_ALL_ACCESS, opt = REG_OPTION_RESERVED, &blk) - self.class.create(self, subkey, desired, opt, &blk) - end - - # - # Close key. - # - # After close, most method raise an error. - # - def close - API.CloseKey(@hkey) - @hkey = @parent = @keyname = nil - @hkeyfinal[0] = nil - end - - # - # Enumerate all values in this registry path. - # - # For each value it yields key, type and data. - # - # key is a String which contains name of key. - # type is a type contant kind of Win32::Registry::REG_* - # data is the value of this key. - # - def each_value - return enum_for(:each_value) unless block_given? - index = 0 - while true - begin - subkey = API.EnumValue(@hkey, index) - rescue Error - break - end - subkey = export_string(subkey) - begin - type, data = read(subkey) - rescue Error - else - yield subkey, type, data - end - index += 1 - end - index - end - alias each each_value - - # - # return values as an array - # - def values - vals_ary = [] - each_value { |*, val| vals_ary << val } - vals_ary - end - - # - # Enumerate all subkeys. - # - # For each subkey it yields subkey and wtime. - # - # subkey is String which contains name of subkey. - # wtime is last write time as FILETIME (64-bit integer). - # (see Registry.wtime2time) - # - def each_key - return enum_for(:each_key) unless block_given? - index = 0 - while true - begin - subkey, wtime = API.EnumKey(@hkey, index) - rescue Error - break - end - subkey = export_string(subkey) - yield subkey, wtime - index += 1 - end - index - end - - # - # return keys as an array - # - def keys - keys_ary = [] - each_key { |key,| keys_ary << key } - keys_ary - end - - # Read a registry value named name and return array of - # [ type, data ]. - # When name is nil, the `default' value is read. - # type is value type. (see Win32::Registry::Constants module) - # data is value data, its class is: - # :REG_SZ, REG_EXPAND_SZ - # String - # :REG_MULTI_SZ - # Array of String - # :REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD - # Integer - # :REG_BINARY, REG_NONE - # String (contains binary data) - # - # When rtype is specified, the value type must be included by - # rtype array, or TypeError is raised. - def read(name, *rtype) - type, data = API.QueryValue(@hkey, name) - unless rtype.empty? or rtype.include?(type) - raise TypeError, "Type mismatch (expect [#{ - rtype.map{|t|Registry.type2name(t)}.join(', ')}] but #{ - Registry.type2name(type)} present)" - end - case type - when REG_SZ, REG_EXPAND_SZ - [ type, data.encode(name.encoding, WCHAR).chop ] - when REG_MULTI_SZ - [ type, data.encode(name.encoding, WCHAR).split(/\0/) ] - when REG_BINARY, REG_NONE - [ type, data ] - when REG_DWORD - [ type, API.unpackdw(data) ] - when REG_DWORD_BIG_ENDIAN - [ type, data.unpack1('N') ] - when REG_QWORD - [ type, API.unpackqw(data) ] - else - raise TypeError, "Type #{Registry.type2name(type)} is not supported." - end - end - - # - # Read a registry value named name and return its value data. - # The class of the value is the same as the #read method returns. - # - # If the value type is REG_EXPAND_SZ, returns value data whose environment - # variables are replaced. - # If the value type is neither REG_SZ, REG_MULTI_SZ, REG_DWORD, - # REG_DWORD_BIG_ENDIAN, nor REG_QWORD, TypeError is raised. - # - # The meaning of rtype is the same as for the #read method. - # - def [](name, *rtype) - type, data = read(name, *rtype) - case type - when REG_SZ, REG_DWORD, REG_QWORD, REG_MULTI_SZ - data - when REG_EXPAND_SZ - Registry.expand_environ(data) - else - raise TypeError, "Type #{Registry.type2name(type)} is not supported." - end - end - - # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) - # registry value named name. - # - # If the values type does not match, TypeError is raised. - def read_s(name) - read(name, REG_SZ)[1] - end - - # - # Read a REG_SZ or REG_EXPAND_SZ registry value named name. - # - # If the value type is REG_EXPAND_SZ, environment variables are replaced. - # Unless the value type is REG_SZ or REG_EXPAND_SZ, TypeError is raised. - # - def read_s_expand(name) - type, data = read(name, REG_SZ, REG_EXPAND_SZ) - if type == REG_EXPAND_SZ - Registry.expand_environ(data) - else - data - end - end - - # - # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) - # registry value named name. - # - # If the values type does not match, TypeError is raised. - # - def read_i(name) - read(name, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD)[1] - end - - # - # Read a REG_SZ(read_s), REG_DWORD(read_i), or REG_BINARY(read_bin) - # registry value named name. - # - # If the values type does not match, TypeError is raised. - # - def read_bin(name) - read(name, REG_BINARY)[1] - end - - # - # Write data to a registry value named name. - # When name is nil, write to the `default' value. - # - # type is type value. (see Registry::Constants module) - # Class of data must be same as which #read - # method returns. - # - def write(name, type, data) - case type - when REG_SZ, REG_EXPAND_SZ - data = data.encode(WCHAR) << WCHAR_NUL - when REG_MULTI_SZ - data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL - when REG_BINARY, REG_NONE - data = data.to_s - when REG_DWORD - data = API.packdw(data.to_i) - when REG_DWORD_BIG_ENDIAN - data = [data.to_i].pack('N') - when REG_QWORD - data = API.packqw(data.to_i) - else - raise TypeError, "Unsupported type #{Registry.type2name(type)}" - end - API.SetValue(@hkey, name, type, data, data.bytesize) - end - - # - # Write value to a registry value named name. - # - # If wtype is specified, the value type is it. - # Otherwise, the value type is depend on class of value: - # :Integer - # REG_DWORD - # :String - # REG_SZ - # :Array - # REG_MULTI_SZ - # - def []=(name, rtype, value = nil) - if value - write name, rtype, value - else - case value = rtype - when Integer - write name, REG_DWORD, value - when String - write name, REG_SZ, value - when Array - write name, REG_MULTI_SZ, value - else - raise TypeError, "Unexpected type #{value.class}" - end - end - value - end - - # - # Write value to a registry value named name. - # - # The value type is REG_SZ(write_s), REG_DWORD(write_i), or - # REG_BINARY(write_bin). - # - def write_s(name, value) - write name, REG_SZ, value.to_s - end - - # - # Write value to a registry value named name. - # - # The value type is REG_SZ(write_s), REG_DWORD(write_i), or - # REG_BINARY(write_bin). - # - def write_i(name, value) - write name, REG_DWORD, value.to_i - end - - # - # Write value to a registry value named name. - # - # The value type is REG_SZ(write_s), REG_DWORD(write_i), or - # REG_BINARY(write_bin). - # - def write_bin(name, value) - write name, REG_BINARY, value.to_s - end - - # - # Delete a registry value named name. - # We can not delete the `default' value. - # - def delete_value(name) - API.DeleteValue(@hkey, name) - end - alias delete delete_value - - # - # Delete a subkey named name and all its values. - # - # If recursive is false, the subkey must not have subkeys. - # Otherwise, this method deletes all subkeys and values recursively. - # - def delete_key(name, recursive = false) - if recursive - open(name, KEY_ALL_ACCESS) do |reg| - reg.keys.each do |key| - begin - reg.delete_key(key, true) - rescue Error - # - end - end - end - API.DeleteKey(@hkey, name) - else - begin - API.EnumKey @hkey, 0 - rescue Error - return API.DeleteKey(@hkey, name) - end - raise Error.new(5) ## ERROR_ACCESS_DENIED - end - end - - # - # Write all the attributes into the registry file. - # - def flush - API.FlushKey @hkey - end - - # - # Returns key information as Array of: - # :num_keys - # The number of subkeys. - # :max_key_length - # Maximum length of name of subkeys. - # :num_values - # The number of values. - # :max_value_name_length - # Maximum length of name of values. - # :max_value_length - # Maximum length of value of values. - # :descriptor_length - # Length of security descriptor. - # :wtime - # Last write time as FILETIME(64-bit integer) - # - # For detail, see RegQueryInfoKey[http://msdn.microsoft.com/library/en-us/sysinfo/base/regqueryinfokey.asp] Win32 API. - # - def info - API.QueryInfoKey(@hkey) - end - - # - # Returns an item of key information. - # - %w[ - num_keys max_key_length - num_values max_value_name_length max_value_length - descriptor_length wtime - ].each_with_index do |s, i| - eval <<-__END__ - def #{s} - info[#{i}] - end - __END__ - end - - private - - def export_string(str, enc = Encoding.default_internal || LOCALE) # :nodoc: - str.encode(enc) - end - end -end diff --git a/pom.rb b/pom.rb index c74a68085bc..5757960fbcf 100644 --- a/pom.rb +++ b/pom.rb @@ -71,7 +71,7 @@ # versions for default gems with bin executables # used in ./lib/pom.rb and ./maven/jruby-stdlib/pom.rb - "rake.version": '13.2.1', + "rake.version": '13.3.1', "jruby-launcher.version": '1.1.6', "ant.version": '1.9.8', "asm.version": '9.7.1', diff --git a/pom.xml b/pom.xml index 114765a5531..b3ed70a941d 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,7 @@ DO NOT MODIFY - GENERATED CODE pom.xml true utf-8 - 13.2.1 + 13.3.1 ${project.version} From 7af7ec52f40e69f61df9dce40ebe5bd328760ea2 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 11 Nov 2025 17:09:18 -0600 Subject: [PATCH 003/168] Ruby 3.5 is officially 4.0 now --- core/pom.xml | 4 ++-- default.build.properties | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index f8dbfa4f0f3..9b8074885ea 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -55,8 +55,8 @@ DO NOT MODIFY - GENERATED CODE ${build.dir}/test-results provided 2019c - 3.5.0 - 3.5 + 4.0.0 + 4.0 0 diff --git a/default.build.properties b/default.build.properties index 7d1852da97c..110dc39d37b 100644 --- a/default.build.properties +++ b/default.build.properties @@ -28,7 +28,7 @@ rake.args= install4j.executable=/Applications/install4j9/bin/install4jc # Ruby versions -version.ruby=3.5.0 -version.ruby.major=3.5 +version.ruby=4.0.0 +version.ruby.major=4.0 version.ruby.minor=0 From b1295a7621de392cbc015a118981dbb65f519a92 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 11 Nov 2025 17:29:13 -0600 Subject: [PATCH 004/168] Ignore missing exts on JRuby This problem does not seem to be going away, so ignore all missing extensions on JRuby for now. See https://github.com/ruby/rubygems/issues/3520 --- lib/ruby/stdlib/rubygems/defaults/jruby.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/ruby/stdlib/rubygems/defaults/jruby.rb b/lib/ruby/stdlib/rubygems/defaults/jruby.rb index f3e4fbf0866..7e755b7c3fc 100644 --- a/lib/ruby/stdlib/rubygems/defaults/jruby.rb +++ b/lib/ruby/stdlib/rubygems/defaults/jruby.rb @@ -57,6 +57,14 @@ def self.path_separator ## JAR FILES: Allow gem path entries to contain jar files class Gem::Specification + ## + # Ignore missing extensions on JRuby + # + # See https://github.com/ruby/rubygems/issues/3520 + def missing_extensions? + false + end + class << self # Replace existing dirs def dirs From 8f6173cfce87314d6390630fef8e22c4e9d9222f Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 12 Nov 2025 11:26:15 -0600 Subject: [PATCH 005/168] Ignore win32/registry.rb now provided by win32-registry gem --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 21a7d72d66b..60d12502420 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ lib/ruby/stdlib/un* lib/ruby/stdlib/uri* lib/ruby/stdlib/yaml* lib/ruby/stdlib/weakref* +lib/ruby/stdlib/win32/registry.rb release.properties share From aee7394763c8fe079f42f7ac9808c1852daa36e1 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 12 Nov 2025 11:54:52 -0600 Subject: [PATCH 006/168] Remove test of automatic jar extension loading We removed the ability to automatically load extensions from jars many years ago, but this test remained. It only continued to pass because some other library loaded the 'json' library which meant that the tested classes were available. When this same example is run outside of the test suite, it fails: ``` $ cx jruby-10.0.2.0 ruby -e "require 'uri:file:./lib/ruby/stdlib/json/ext/parser'; p JSON::Ext::Parser" NameError: uninitialized constant JSON::Ext::Parser const_missing at org/jruby/RubyModule.java:4866
at -e:1 ``` Because this functionality no longer exists, I am removing the test. --- test/jruby/test_load_gem_extensions.rb | 39 -------------------------- 1 file changed, 39 deletions(-) delete mode 100644 test/jruby/test_load_gem_extensions.rb diff --git a/test/jruby/test_load_gem_extensions.rb b/test/jruby/test_load_gem_extensions.rb deleted file mode 100644 index fcb37d727fe..00000000000 --- a/test/jruby/test_load_gem_extensions.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'test/unit' -require 'test/jruby/test_helper' -require 'jruby/path_helper' - -class TestLoadGemExtensions < Test::Unit::TestCase - include TestHelper - - def setup - @prev_loaded_features = $LOADED_FEATURES.dup - @prev_load_path = $LOAD_PATH.dup - end - - def teardown - $LOADED_FEATURES.replace(@prev_loaded_features) - $LOAD_PATH.clear - $LOAD_PATH.concat(@prev_load_path) - end - - def test_require_extension_file_via_uri_protocol - return skip 'needs jruby-home from filesystem' if inside_jar? - require 'uri:file:./lib/ruby/stdlib/json/ext/parser' - # just check if extension class exists - JSON::Ext::Parser - end - - def test_require_extension_file_via_uri_classloader_protocol - return skip 'needs jruby-home from filesystem' if inside_jar? - require 'uri:classloader:/lib/ruby/stdlib/json/ext/generator' - # just check if extension class exists - JSON::Ext::Generator - end - - private - - def inside_jar? - JRuby.runtime.instance_config.jruby_home =~ /META-INF/ - end - -end From 123a357fe019d1f295ba2d1ba1f61b1125b02164 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 12 Nov 2025 12:06:38 -0600 Subject: [PATCH 007/168] Replace loaded features rather than clear if it is frozen --- core/src/main/java/org/jruby/runtime/load/LoadService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/jruby/runtime/load/LoadService.java b/core/src/main/java/org/jruby/runtime/load/LoadService.java index fd505c2d0c1..e5d7a54dbe7 100644 --- a/core/src/main/java/org/jruby/runtime/load/LoadService.java +++ b/core/src/main/java/org/jruby/runtime/load/LoadService.java @@ -988,7 +988,11 @@ public String getPathForLocation(String filename) { } public void tearDown() { - loadedFeatures.clear(); + if (loadedFeatures.isFrozen()) { + loadedFeatures = new StringArraySet(runtime); + } else { + loadedFeatures.clear(); + } librarySearcher.tearDown(); } } From 227f4053993ec1a8c58ce61bca6eec3fe0cf4c73 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 12 Nov 2025 12:15:23 -0600 Subject: [PATCH 008/168] Bump up size of jruby-jars A few additional stdlib were added in 4.0 so this archive is now a bit larger. --- test/check_versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/check_versions.sh b/test/check_versions.sh index e00018a4b00..41003f36126 100755 --- a/test/check_versions.sh +++ b/test/check_versions.sh @@ -62,7 +62,7 @@ function check { } check lib/target/jruby-stdlib-$jar_version.jar 19 -check maven/jruby-jars/pkg/jruby-jars-$gem_version.gem 32 +check maven/jruby-jars/pkg/jruby-jars-$gem_version.gem 35 check maven/jruby-jars/lib/jruby-core-$jar_version-complete.jar 17 check maven/jruby-jars/lib/jruby-stdlib-$jar_version.jar 19 check maven/jruby-complete/target/jruby-complete-$jar_version.jar 36 From aa4642eca709b5366ab04dad6d42d7fcceba7711 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 12 Nov 2025 13:20:01 -0600 Subject: [PATCH 009/168] Split these specs to test each feature separately Testing all of these features together does not show useful information when one of them is not pre-required, and basing the spec name off the list of expected features means it cannot be reliably tagged for exclusion. The change here makes this a separate subprocess run for each provided library, which improves the spec in a few different ways: * Descriptions are meaningful and can be individually tagged. Previously, any change to the list of features would cause the spec description to change and error output did not clearly indicate which feature failed. * Failed requires are represented with the string "error" so that failure output is useful. Previous logic would just report that the subprocess failed. * Implementation-specific features do not have to be filtered out. Both CRuby and JRuby have features at boot that do not exist in other implementations, and may add others in the future. The spec should not need updating when that happens. The require check and the loaded feature check are combined into a single run for efficiency. --- spec/ruby/core/kernel/require_spec.rb | 33 +++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 81777c5313a..85b7ffd48b7 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -22,23 +22,28 @@ provided << "pathname" end - it "#{provided.join(', ')} are already required" do - out = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems --disable-did-you-mean') - features = out.lines.map { |line| File.basename(line.chomp, '.*') } - - # Ignore CRuby internals - features -= %w[encdb transdb windows_1252 windows_31j] - features.reject! { |feature| feature.end_with?('-fake') } + provided_requires = provided.dup.to_h {|f| [f,f]} + ruby_version_is "3.5" do + provided_requires["pathname"] = "pathname.so" + end - features.sort.should == provided.sort + provided.each do |feature| + it "#{feature} is already required and provided in loaded features at boot" do + feature_require = provided_requires[feature] - ruby_version_is "3.5" do - provided.map! { |f| f == "pathname" ? "pathname.so" : f } + code = <<~RUBY + loaded_feature_base = $\".map{|f| File.basename(f, '.*')} + required = begin + require(#{feature_require.inspect}) + rescue LoadError + "error" + end + feature = loaded_feature_base.include?(#{feature.inspect}) + p({required:, feature:}) + RUBY + output = ruby_exe(code, options: '--disable-gems').chomp + output.should == "{required: false, feature: true}" end - - code = provided.map { |f| "puts require #{f.inspect}\n" }.join - required = ruby_exe(code, options: '--disable-gems') - required.should == "false\n" * provided.size end it_behaves_like :kernel_require_basic, :require, CodeLoadingSpecs::Method.new From ff383dab7237de22a5bb90090bd2034d37e0d74f Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 12 Nov 2025 14:09:25 -0600 Subject: [PATCH 010/168] Remove defunct spec description This spec was split into a separate spec for each feature to avoid the description changing and to make the failures more useful. --- spec/tags/ruby/core/kernel/require_tags.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/tags/ruby/core/kernel/require_tags.txt b/spec/tags/ruby/core/kernel/require_tags.txt index 0fc730ffde5..0fe0f945263 100644 --- a/spec/tags/ruby/core/kernel/require_tags.txt +++ b/spec/tags/ruby/core/kernel/require_tags.txt @@ -11,6 +11,5 @@ fails:Kernel#require (path resolution) loads c-extension file when passed absolu fails:Kernel.require (path resolution) loads c-extension file when passed absolute path without extension when no .rb is present fails:Kernel#require (path resolution) loads .bundle file when passed absolute path with .so fails:Kernel.require (path resolution) loads .bundle file when passed absolute path with .so -fails:Kernel#require complex, enumerator, fiber, rational, thread, ruby2_keywords are already required fails:Kernel#require does not store the missing path in a LoadError object when c-extension file exists but loading fails and passed absolute path without extension fails:Kernel.require does not store the missing path in a LoadError object when c-extension file exists but loading fails and passed absolute path without extension From e15f05126a1b905bc9c0b31d6628d0c86c078570 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 5 Nov 2025 02:31:46 -0600 Subject: [PATCH 011/168] First pass moving Set to core and passing all tests * Removed SortedSet and related files (gem TBD). * Removed stdlib .rb stub. * Fixed inspect output, intersect? with Array, and infinite enumerables for new tests. * Moved @hash variable to a bound Java field. --- core/src/main/java/org/jruby/Ruby.java | 10 +- .../org/jruby/ext/monitor/MonitorLibrary.java | 3 - .../main/java/org/jruby/ext/set/RubySet.java | 131 +-- .../java/org/jruby/ext/set/RubySortedSet.java | 249 ----- .../java/org/jruby/ext/set/SetLibrary.java | 50 - core/src/main/ruby/jruby/kernel.rb | 1 + core/src/main/ruby/jruby/{ => kernel}/set.rb | 0 lib/ruby/stdlib/set.rb | 2 - test/mri.core.index | 1 + .../fake_sorted_set_gem/sorted_set.rb | 9 - test/mri/set/test_set.rb | 897 ------------------ test/mri/set/test_sorted_set.rb | 45 - 12 files changed, 80 insertions(+), 1318 deletions(-) delete mode 100644 core/src/main/java/org/jruby/ext/set/RubySortedSet.java delete mode 100644 core/src/main/java/org/jruby/ext/set/SetLibrary.java rename core/src/main/ruby/jruby/{ => kernel}/set.rb (100%) delete mode 100644 lib/ruby/stdlib/set.rb delete mode 100644 test/mri/set/fixtures/fake_sorted_set_gem/sorted_set.rb delete mode 100644 test/mri/set/test_set.rb delete mode 100644 test/mri/set/test_sorted_set.rb diff --git a/core/src/main/java/org/jruby/Ruby.java b/core/src/main/java/org/jruby/Ruby.java index 80cf21fb3f2..59acc441d81 100644 --- a/core/src/main/java/org/jruby/Ruby.java +++ b/core/src/main/java/org/jruby/Ruby.java @@ -50,6 +50,7 @@ import org.jruby.exceptions.LocalJumpError; import org.jruby.exceptions.SystemExit; import org.jruby.ext.jruby.JRubyUtilLibrary; +import org.jruby.ext.set.RubySet; import org.jruby.ext.thread.ConditionVariable; import org.jruby.ext.thread.Mutex; import org.jruby.ext.thread.Queue; @@ -517,6 +518,8 @@ private Ruby(RubyInstanceConfig config) { dataClass = RubyData.createDataClass(context, objectClass); + setClass = RubySet.createSetClass(context, objectClass, enumerableModule); + // everything booted, so SizedQueue should be available; set up root fiber ThreadFiber.initRootFiber(context, context.getThread()); @@ -601,6 +604,7 @@ private void initBootLibraries(ThreadContext context) { loadService.provide("thread.rb"); loadService.provide("fiber.rb"); loadService.provide("ruby2_keywords.rb"); + loadService.provide("set.rb"); // Load preludes initRubyPreludes(); @@ -2487,6 +2491,10 @@ public RubyClass getData() { return dataClass; } + public RubyClass getSet() { + return setClass; + } + /** The default Ruby Random object for this runtime */ private RubyRandom defaultRandom; @@ -5021,7 +5029,6 @@ public void setChdirThread(RubyThread thread) { public RubyThread getChdirThread() { return this.chdirCurrentThread; } public RubyStackTraceElement getChdirLocation() { return this.chdirLocation; } - /** * Because RubyString.equals does not consider encoding, and MRI's logic for deduplication does need to consider * encoding, we use a wrapper object as the key. These wrappers need to be used on all get operations, so if we @@ -5253,6 +5260,7 @@ public interface RecursiveFunctionEx extends ThreadContext.RecursiveFunctionE private final RubyClass closedQueueError; private final RubyClass sizedQueueClass; private final RubyClass dataClass; + private final RubyClass setClass; private RubyClass tmsStruct; private RubyClass passwdStruct; diff --git a/core/src/main/java/org/jruby/ext/monitor/MonitorLibrary.java b/core/src/main/java/org/jruby/ext/monitor/MonitorLibrary.java index 2454dd0e119..62777e4baef 100644 --- a/core/src/main/java/org/jruby/ext/monitor/MonitorLibrary.java +++ b/core/src/main/java/org/jruby/ext/monitor/MonitorLibrary.java @@ -29,9 +29,6 @@ package org.jruby.ext.monitor; import org.jruby.Ruby; -import org.jruby.ext.set.EnumerableExt; -import org.jruby.ext.set.RubySet; -import org.jruby.ext.set.RubySortedSet; import org.jruby.runtime.load.Library; public final class MonitorLibrary implements Library { diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index d79680d3da7..d18ce1699e0 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -34,6 +34,7 @@ import org.jruby.RubyEnumerator.SizeFn; import org.jruby.anno.JRubyMethod; import org.jruby.api.Access; +import org.jruby.api.Error; import org.jruby.javasupport.JavaUtil; import org.jruby.runtime.*; import org.jruby.runtime.builtin.IRubyObject; @@ -44,6 +45,7 @@ import org.jruby.util.io.RubyOutputStream; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Array; import java.util.Collection; import java.util.IdentityHashMap; @@ -70,15 +72,24 @@ */ @org.jruby.anno.JRubyClass(name="Set", include = { "Enumerable" }) public class RubySet extends RubyObject implements Set { + private RubyHash hash; - static RubyClass createSetClass(ThreadContext context, RubyClass Object, RubyModule Enumerable) { + public static RubyClass createSetClass(ThreadContext context, RubyClass Object, RubyModule Enumerable) { RubyClass Set = defineClass(context, "Set", Object, RubySet::new). reifiedClass(RubySet.class). include(context, Enumerable). defineMethods(context, RubySet.class). tap(c -> c.marshalWith(new SetMarshal(c.getMarshal()))); - loadService(context).require("jruby/set.rb"); + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + Set.getVariableTableManager().getVariableAccessorForRubyVar("@hash", lookup.findGetter(RubySet.class, "hash", RubyHash.class), lookup.findSetter(RubySet.class, "hash", RubyHash.class)); + } catch (NoSuchFieldException | IllegalAccessException e) { + // should not happen + throw new RuntimeException(e); + } + + Enumerable.defineMethods(context, EnumerableExt.class); return Set; } @@ -106,24 +117,16 @@ public void marshalTo(ThreadContext context, RubyOutputStream out, Object obj, R @SuppressWarnings("removal") public Object unmarshalFrom(Ruby runtime, RubyClass type, org.jruby.runtime.marshal.UnmarshalStream unmarshalStream) throws IOException { Object result = defaultMarshal.unmarshalFrom(runtime, type, unmarshalStream); - ((RubySet) result).unmarshal(); return result; } public Object unmarshalFrom(ThreadContext context, RubyInputStream in, RubyClass type, MarshalLoader loader) { Object result = defaultMarshal.unmarshalFrom(context, in, type, loader); - ((RubySet) result).unmarshal(); return result; } } - void unmarshal() { - this.hash = (RubyHash) getInstanceVariable("@hash"); - } - - RubyHash hash; // @hash - protected RubySet(Ruby runtime, RubyClass klass) { super(runtime, klass); } @@ -138,20 +141,15 @@ private RubySet(Ruby runtime, RubyHash hash) { // ... this is important with Rails using Sprockets at its marshalling Set instances final void allocHash(final ThreadContext context) { - setHash(new RubyHash(context.runtime, context.fals)); + this.hash = new RubyHash(context.runtime, context.fals); } final void allocHash(final Ruby runtime) { - setHash(new RubyHash(runtime, runtime.getFalse())); + this.hash = new RubyHash(runtime, runtime.getFalse()); } final void allocHash(final ThreadContext context, final int size) { - setHash(new RubyHash(context.runtime, context.fals, size)); - } - - final void setHash(final RubyHash hash) { - this.hash = hash; - setInstanceVariable("@hash", hash); // MRI compat with set.rb + this.hash = new RubyHash(context.runtime, context.fals, size); } /** @@ -226,30 +224,15 @@ public IRubyObject initialize(ThreadContext context, Block block) { * initialize(enum = nil, &block) */ @JRubyMethod(visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject enume, Block block) { + public IRubyObject initialize(final ThreadContext context, IRubyObject enume, final Block block) { if ( enume.isNil() ) return initialize(context, block); - if ( block.isGiven() ) { - return initWithEnum(context, enume, block); - } - - allocHash(context); - return sites(context).merge.call(context, this, this, enume); // TODO site-cache - } - - protected IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) { - return switch (args.length) { - case 0 -> initialize(context, block); - case 1 -> initialize(context, args[0], block); - default -> throw argumentError(context, args.length, 1); - }; - } - - private IRubyObject initWithEnum(final ThreadContext context, final IRubyObject enume, final Block block) { if (enume instanceof RubyArray ary) { allocHash(context, ary.size()); for ( int i = 0; i < ary.size(); i++ ) { - invokeAdd(context, block.yield(context, ary.eltInternal(i))); + IRubyObject key = ary.eltInternal(i); + if (block.isGiven()) key = block.yield(context, key); + invokeAdd(context, key); } return ary; // done } else if (enume instanceof RubySet set) { @@ -259,15 +242,34 @@ private IRubyObject initWithEnum(final ThreadContext context, final IRubyObject } return set; // done } else { + if (enume.getType().isKindOfModule(context.runtime.getEnumerable()) && enume.respondsTo("size")) { + IRubyObject size = enume.callMethod(context, "size"); + if (size instanceof RubyFloat flote && flote.isInfinite()) { + throw Error.argumentError(context, "cannot initialize Set from an object with infinite size"); + } + } allocHash(context); // set.rb do_with_enum : - return doWithEnum(context, enume, new EachBody(context) { - IRubyObject yieldImpl(ThreadContext context, IRubyObject val) { - return invokeAdd(context, block.yield(context, val)); - } - }); + if (block.isGiven()) { + return doWithEnum(context, enume, new EachBody(context) { + IRubyObject yieldImpl(ThreadContext context2, IRubyObject val) { + return invokeAdd(context2, block.yield(context2, val)); + } + }); + } } + + allocHash(context); + return sites(context).merge.call(context, this, this, enume); // TODO site-cache + } + + protected IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) { + return switch (args.length) { + case 0 -> initialize(context, block); + case 1 -> initialize(context, args[0], block); + default -> throw argumentError(context, args.length, 1); + }; } // set.rb do_with_enum (block is required) @@ -289,7 +291,8 @@ private static IRubyObject doWithEnum(final ThreadContext context, final IRubyOb public IRubyObject instance_variable_set(IRubyObject name, IRubyObject value) { if (getRuntime().newSymbol("@hash").equals(name)) { if (value instanceof RubyHash) { - setHash((RubyHash) value); return value; + this.hash = (RubyHash) value; + return value; } } return super.instance_variable_set(name, value); @@ -327,7 +330,7 @@ protected final IRubyObject doYield(ThreadContext context, Block block, IRubyObj @JRubyMethod(frame = true) public IRubyObject initialize_dup(ThreadContext context, IRubyObject orig) { sites(context).initialize_dup_super.call(context, this, this, orig); - setHash((RubyHash) (((RubySet) orig).hash).dup(context)); + this.hash = (RubyHash) (((RubySet) orig).hash).dup(context); return this; } @@ -337,7 +340,7 @@ public IRubyObject initialize_clone(ThreadContext context, IRubyObject[] args) { sites(context).initialize_clone_super.call(context, this, this, args); IRubyObject orig = args[0]; - setHash((RubyHash) (((RubySet) orig).hash).rbClone(context)); + this.hash = (RubyHash) (((RubySet) orig).hash).rbClone(context); return this; } @@ -564,9 +567,13 @@ public IRubyObject proper_subset_p(final ThreadContext context, IRubyObject setA */ @JRubyMethod(name = "intersect?") public IRubyObject intersect_p(final ThreadContext context, IRubyObject setArg) { - if (!(setArg instanceof RubySet set)) throw argumentError(context, "value must be a set"); - - return asBoolean(context, intersect(set)); + return asBoolean(context, + intersect( + switch (setArg) { + case RubySet set -> set; + case RubyArray ary -> newSet(context, context.runtime.getSet(), ary); + default -> throw argumentError(context, "value must be a set or array"); + })); } public boolean intersect(final RubySet set) { @@ -590,9 +597,7 @@ public boolean intersect(final RubySet set) { */ @JRubyMethod(name = "disjoint?") public IRubyObject disjoint_p(final ThreadContext context, IRubyObject setArg) { - if (!(setArg instanceof RubySet set)) throw argumentError(context, "value must be a set"); - - return asBoolean(context, !intersect(set)); + return asBoolean(context, !intersect_p(context, setArg).isTrue()); } @JRubyMethod @@ -747,8 +752,12 @@ public RubySet rb_merge(final ThreadContext context, IRubyObject enume) { /** * Merges the elements of the given enumerable object to the set and returns self. */ - @JRubyMethod(name = "merge", required=1, rest=true) + @JRubyMethod(name = "merge", required=0, rest=true) public RubySet rb_merge(final ThreadContext context, IRubyObject... args) { + if (args.length > 0 && args[args.length - 1] instanceof RubyHash) { + throw argumentError(context, "no keywords accepted"); + } + var length = args.length; for (int i = 0; i < length; i++) { var arg = args[i]; @@ -851,7 +860,7 @@ IRubyObject yieldImpl(ThreadContext context, IRubyObject obj) { */ @JRubyMethod(name = "^") public IRubyObject op_xor(final ThreadContext context, IRubyObject enume) { - RubySet newSet = new RubySet(context.runtime, Access.getClass(context, "Set")); + RubySet newSet = new RubySet(context.runtime, getMetaClass()); newSet.initialize(context, enume, Block.NULL_BLOCK); // Set.new(enum) for (IRubyObject o : elementsOrdered()) { if (newSet.containsImpl(o)) { @@ -1140,7 +1149,7 @@ public RubyString inspect(ThreadContext context) { try { context.runtime.registerInspecting(this); inspectSet(context, str); - return str.cat('>'); + return str; } finally { context.runtime.unregisterInspecting(this); } @@ -1149,26 +1158,24 @@ public RubyString inspect(ThreadContext context) { private RubyString inspectEmpty(ThreadContext context) { RubyString str = RubyString.newStringLight(context.runtime, 16, USASCIIEncoding.INSTANCE); inspectPrefix(context, str, getMetaClass()); - str.cat('{').cat('}').cat('>'); // "#" + str.cat('[').cat(']'); // "#" return str; } private RubyString inspectRecurse(ThreadContext context) { RubyString str = RubyString.newStringLight(context.runtime, 20, USASCIIEncoding.INSTANCE); inspectPrefix(context, str, getMetaClass()); - str.cat('{').cat(RECURSIVE_BYTES).cat('}').cat('>'); // "#" + str.cat('[').cat(RECURSIVE_BYTES).cat(']'); // "#" return str; } private static RubyString inspectPrefix(ThreadContext context, final RubyString str, final RubyClass metaClass) { - str.cat('#').cat('<').cat(metaClass.getRealClass().getName(context).getBytes(RubyEncoding.UTF8)); - str.cat(':').cat(' '); - return str; + return str.catString(metaClass.getRealClass().getName(context)); } private void inspectSet(final ThreadContext context, final RubyString str) { - str.cat((byte) '{'); + str.cat((byte) '['); boolean notFirst = false; @@ -1179,7 +1186,7 @@ private void inspectSet(final ThreadContext context, final RubyString str) { str.catWithCodeRange(s); } - str.cat((byte) '}'); + str.cat((byte) ']'); } // pp (in __jruby/set.rb__) @@ -1196,7 +1203,7 @@ protected final Set elements() { // NOTE: implementation does not expect to be used for altering contents using iterator protected Set elementsOrdered() { - return elements(); // potentially -> to be re-defined by SortedSet + return elements(); } @Deprecated(since = "10.0.0.0") diff --git a/core/src/main/java/org/jruby/ext/set/RubySortedSet.java b/core/src/main/java/org/jruby/ext/set/RubySortedSet.java deleted file mode 100644 index df18bed363d..00000000000 --- a/core/src/main/java/org/jruby/ext/set/RubySortedSet.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - **** BEGIN LICENSE BLOCK ***** - * Version: EPL 1.0/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Eclipse Public - * License Version 1.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of - * the License at http://www.eclipse.org/legal/epl-v20.html - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - * - * Copyright (C) 2016 Karol Bucek - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the EPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the EPL, the GPL or the LGPL. - ***** END LICENSE BLOCK *****/ - -package org.jruby.ext.set; - -import org.jruby.*; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.Arity; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -import java.util.*; - -import static org.jruby.RubyArray.DefaultComparator; -import static org.jruby.api.Define.defineClass; - -/** - * Native implementation of Ruby's SortedSet (set.rb replacement). - * - * @author kares - */ -@org.jruby.anno.JRubyClass(name="SortedSet", parent = "Set") -public class RubySortedSet extends RubySet implements SortedSet { - - static RubyClass createSortedSetClass(ThreadContext context, RubyClass Set) { - return defineClass(context, "SortedSet", Set, RubySortedSet::new). - reifiedClass(RubySortedSet.class). - defineMethods(context, RubySortedSet.class); - } - - private static class OrderComparator extends DefaultComparator { - - private final Ruby runtime; - - OrderComparator(final Ruby runtime) { - super(null); this.runtime = runtime; - } - - @Override - protected ThreadContext context() { - return runtime.getCurrentContext(); - } - - // NOTE: need a custom impl so that we use special care when compare returns 0 - // this is required since/if we're using a TreeSet which assumes 0 -> eql == true - - public int compare(IRubyObject obj1, IRubyObject obj2) { - final int cmp = super.compare(obj1, obj2); - if (cmp == 0) { - return equalInternal(context(), obj1, obj2) ? 0 : 1; - } - return cmp; - } - - } - - private final TreeSet order; - - protected RubySortedSet(Ruby runtime, RubyClass klass) { - super(runtime, klass); - order = new TreeSet<>(new OrderComparator(runtime)); - } - - @Override - void unmarshal() { - super.unmarshal(); - IRubyObject[] elems = hash.keys().toJavaArrayMaybeUnsafe(); - for ( int i=0; i") ) { - // throw argumentError(context, "value must respond to <=>"); - //} - super.addImpl(context, obj); // @hash[obj] = true - order.add(obj); - } - - @Override - protected void addImplSet(final ThreadContext context, final RubySet set) { - super.addImplSet(context, set); - order.addAll(set.elements()); - } - - @Override - protected boolean deleteImpl(final IRubyObject obj) { - if (super.deleteImpl(obj)) { - order.remove(obj); - return true; - } - return false; - } - - @Override - protected void deleteImplIterator(final IRubyObject obj, final Iterator it) { - super.deleteImpl(obj); - // iterator over elementsOrdered() - it.remove(); // order.remove(obj) - } - - @Override - protected void clearImpl(ThreadContext context) { - hash.rb_clear(context); - order.clear(); - } - - @JRubyMethod(name = "sort") // re-def Enumerable#sort - public RubyArray sort(final ThreadContext context) { - return RubyArray.newArray(context.runtime, order); // instead of this.hash.keys(); - } - - @Override - public RubyArray to_a(final ThreadContext context) { - return sort(context); // instead of this.hash.keys(); - } - - @Override - public IRubyObject initialize_dup(ThreadContext context, IRubyObject orig) { - super.initialize_dup(context, orig); - if (this != orig) order.addAll(((RubySortedSet) orig).order); - return this; - } - - @JRubyMethod(frame = true, keywords = true, required = 1, optional = 1, checkArity = false) - public IRubyObject initialize_clone(ThreadContext context, IRubyObject[] args) { - Arity.checkArgumentCount(context, args, 1, 2); - - super.initialize_clone(context, args); - IRubyObject orig = args[0]; - if (this != orig) order.addAll(((RubySortedSet) orig).order); - return this; - } - - // NOTE: weirdly Set/SortedSet in Ruby do not have sort! - - @Override - protected Set elementsOrdered() { return order; } - - @Override - public Iterator iterator() { - return new JavaIterator(); - } - - // java.util.SortedSet : - - public Comparator comparator() { - return order.comparator(); - } - - public Object first() { - return firstValue().toJava(Object.class); - } - - public IRubyObject firstValue() { - return order.first(); - } - - public Object last() { - return lastValue().toJava(Object.class); - } - - public IRubyObject lastValue() { - return order.last(); - } - - public SortedSet headSet(Object toElement) { - throw new UnsupportedOperationException("NOT IMPLEMENTED"); - } - - public SortedSet subSet(Object fromElement, Object toElement) { - throw new UnsupportedOperationException("NOT IMPLEMENTED"); - } - - public SortedSet tailSet(Object fromElement) { - throw new UnsupportedOperationException("NOT IMPLEMENTED"); - } - - public SortedSet rawHeadSet(IRubyObject toElement) { - return order.headSet(toElement); - } - - public SortedSet rawSubSet(IRubyObject fromElement, IRubyObject toElement) { - return order.subSet(fromElement, toElement); - } - - public SortedSet rawTailSet(IRubyObject fromElement) { - return order.tailSet(fromElement); - } - - private class JavaIterator implements Iterator { - - private final Iterator rawIterator; - - JavaIterator() { - rawIterator = RubySortedSet.this.order.iterator(); - } - - @Override - public boolean hasNext() { - return rawIterator.hasNext(); - } - - @Override - public Object next() { - return rawIterator.next().toJava(Object.class); - } - - @Override - public void remove() { - rawIterator.remove(); - } - - } - -} diff --git a/core/src/main/java/org/jruby/ext/set/SetLibrary.java b/core/src/main/java/org/jruby/ext/set/SetLibrary.java deleted file mode 100644 index 45f3c005ae9..00000000000 --- a/core/src/main/java/org/jruby/ext/set/SetLibrary.java +++ /dev/null @@ -1,50 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - * Version: EPL 2.0/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Eclipse Public - * License Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of - * the License at http://www.eclipse.org/legal/epl-v20.html - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - * - * Copyright (C) 2016 Karol Bucek - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the EPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the EPL, the GPL or the LGPL. - ***** END LICENSE BLOCK *****/ - -package org.jruby.ext.set; - -import org.jruby.Ruby; -import org.jruby.runtime.load.Library; - -import static org.jruby.api.Access.enumerableModule; -import static org.jruby.api.Access.objectClass; - -public final class SetLibrary implements Library { - - public void load(Ruby runtime, boolean wrap) { - SetLibrary.load(runtime); - } - - public static void load(Ruby runtime) { - var context = runtime.getCurrentContext(); - var Object = objectClass(context); - var Enumerable = enumerableModule(context).defineMethods(context, EnumerableExt.class); - var Set = RubySet.createSetClass(context, Object, Enumerable); - RubySortedSet.createSortedSetClass(context, Set); - } -} diff --git a/core/src/main/ruby/jruby/kernel.rb b/core/src/main/ruby/jruby/kernel.rb index a02eebec64b..3e7bd25bf3b 100644 --- a/core/src/main/ruby/jruby/kernel.rb +++ b/core/src/main/ruby/jruby/kernel.rb @@ -29,3 +29,4 @@ module JRuby load 'jruby/kernel/thread.rb' load 'jruby/kernel/integer.rb' load 'jruby/kernel/time.rb' +load 'jruby/kernel/set.rb' diff --git a/core/src/main/ruby/jruby/set.rb b/core/src/main/ruby/jruby/kernel/set.rb similarity index 100% rename from core/src/main/ruby/jruby/set.rb rename to core/src/main/ruby/jruby/kernel/set.rb diff --git a/lib/ruby/stdlib/set.rb b/lib/ruby/stdlib/set.rb deleted file mode 100644 index 53b79579daf..00000000000 --- a/lib/ruby/stdlib/set.rb +++ /dev/null @@ -1,2 +0,0 @@ -# Load the built-in set library -JRuby::Util.load_ext("org.jruby.ext.set.SetLibrary") diff --git a/test/mri.core.index b/test/mri.core.index index edddc11f461..590cfb38260 100644 --- a/test/mri.core.index +++ b/test/mri.core.index @@ -140,6 +140,7 @@ ruby/test_require.rb ruby/test_rubyoptions.rb # accesses RubyVM during load #ruby/test_rubyvm.rb +ruby/test_set.rb # launches many subprocesses, moved to extra #ruby/test_settracefunc.rb # depends on RubyVM::Shape diff --git a/test/mri/set/fixtures/fake_sorted_set_gem/sorted_set.rb b/test/mri/set/fixtures/fake_sorted_set_gem/sorted_set.rb deleted file mode 100644 index f45a7663037..00000000000 --- a/test/mri/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/mri/set/test_set.rb b/test/mri/set/test_set.rb deleted file mode 100644 index 565946096ee..00000000000 --- a/test/mri/set/test_set.rb +++ /dev/null @@ -1,897 +0,0 @@ -# frozen_string_literal: false -require 'test/unit' -require 'set' - -class TC_Set < Test::Unit::TestCase - class Set2 < Set - end - - def test_aref - assert_nothing_raised { - Set[] - Set[nil] - Set[1,2,3] - } - - assert_equal(0, Set[].size) - assert_equal(1, Set[nil].size) - assert_equal(1, Set[[]].size) - assert_equal(1, Set[[nil]].size) - - set = Set[2,4,6,4] - assert_equal(Set.new([2,4,6]), set) - end - - def test_s_new - assert_nothing_raised { - Set.new() - Set.new(nil) - Set.new([]) - Set.new([1,2]) - Set.new('a'..'c') - } - assert_raise(ArgumentError) { - Set.new(false) - } - assert_raise(ArgumentError) { - Set.new(1) - } - assert_raise(ArgumentError) { - Set.new(1,2) - } - - assert_equal(0, Set.new().size) - assert_equal(0, Set.new(nil).size) - assert_equal(0, Set.new([]).size) - assert_equal(1, Set.new([nil]).size) - - ary = [2,4,6,4] - set = Set.new(ary) - ary.clear - assert_equal(false, set.empty?) - assert_equal(3, set.size) - - ary = [1,2,3] - - s = Set.new(ary) { |o| o * 2 } - assert_equal([2,4,6], s.sort) - end - - def test_clone - set1 = Set.new - set2 = set1.clone - set1 << 'abc' - assert_equal(Set.new, set2) - end - - def test_dup - set1 = Set[1,2] - set2 = set1.dup - - assert_not_same(set1, set2) - - assert_equal(set1, set2) - - set1.add(3) - - assert_not_equal(set1, set2) - end - - def test_size - assert_equal(0, Set[].size) - assert_equal(2, Set[1,2].size) - assert_equal(2, Set[1,2,1].size) - end - - def test_empty? - assert_equal(true, Set[].empty?) - assert_equal(false, Set[1, 2].empty?) - end - - def test_clear - set = Set[1,2] - ret = set.clear - - assert_same(set, ret) - assert_equal(true, set.empty?) - end - - def test_replace - set = Set[1,2] - ret = set.replace('a'..'c') - - assert_same(set, ret) - assert_equal(Set['a','b','c'], set) - - set = Set[1,2] - assert_raise(ArgumentError) { - set.replace(3) - } - assert_equal(Set[1,2], set) - end - - def test_to_a - set = Set[1,2,3,2] - ary = set.to_a - - assert_equal([1,2,3], ary.sort) - end - - def test_flatten - # test1 - set1 = Set[ - 1, - Set[ - 5, - Set[7, - Set[0] - ], - Set[6,2], - 1 - ], - 3, - Set[3,4] - ] - - set2 = set1.flatten - set3 = Set.new(0..7) - - assert_not_same(set2, set1) - assert_equal(set3, set2) - - # test2; destructive - orig_set1 = set1 - set1.flatten! - - assert_same(orig_set1, set1) - assert_equal(set3, set1) - - # test3; multiple occurrences of a set in an set - set1 = Set[1, 2] - set2 = Set[set1, Set[set1, 4], 3] - - assert_nothing_raised { - set2.flatten! - } - - assert_equal(Set.new(1..4), set2) - - # test4; recursion - set2 = Set[] - set1 = Set[1, set2] - set2.add(set1) - - assert_raise(ArgumentError) { - set1.flatten! - } - - # test5; miscellaneous - empty = Set[] - set = Set[Set[empty, "a"],Set[empty, "b"]] - - assert_nothing_raised { - set.flatten - } - - set1 = empty.merge(Set["no_more", set]) - - assert_nil(Set.new(0..31).flatten!) - - x = Set[Set[],Set[1,2]].flatten! - y = Set[1,2] - - assert_equal(x, y) - end - - def test_include? - set = Set[1,2,3] - - assert_equal(true, set.include?(1)) - assert_equal(true, set.include?(2)) - assert_equal(true, set.include?(3)) - assert_equal(false, set.include?(0)) - assert_equal(false, set.include?(nil)) - - set = Set["1",nil,"2",nil,"0","1",false] - assert_equal(true, set.include?(nil)) - assert_equal(true, set.include?(false)) - assert_equal(true, set.include?("1")) - assert_equal(false, set.include?(0)) - assert_equal(false, set.include?(true)) - end - - def test_eqq - set = Set[1,2,3] - - assert_equal(true, set === 1) - assert_equal(true, set === 2) - assert_equal(true, set === 3) - assert_equal(false, set === 0) - assert_equal(false, set === nil) - - set = Set["1",nil,"2",nil,"0","1",false] - assert_equal(true, set === nil) - assert_equal(true, set === false) - assert_equal(true, set === "1") - assert_equal(false, set === 0) - assert_equal(false, set === true) - end - - def test_superset? - set = Set[1,2,3] - - assert_raise(ArgumentError) { - set.superset?() - } - - assert_raise(ArgumentError) { - set.superset?(2) - } - - assert_raise(ArgumentError) { - set.superset?([2]) - } - - [Set, Set2].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) - assert_equal(false, set.superset?(klass[1,2,3,4]), klass.name) - assert_equal(false, set.superset?(klass[1,4]), klass.name) - - assert_equal(true, set >= klass[1,2,3], klass.name) - assert_equal(true, set >= klass[1,2], klass.name) - - assert_equal(true, Set[].superset?(klass[]), klass.name) - } - end - - def test_proper_superset? - set = Set[1,2,3] - - assert_raise(ArgumentError) { - set.proper_superset?() - } - - assert_raise(ArgumentError) { - set.proper_superset?(2) - } - - assert_raise(ArgumentError) { - set.proper_superset?([2]) - } - - [Set, Set2].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) - assert_equal(false, set.proper_superset?(klass[1,2,3,4]), klass.name) - assert_equal(false, set.proper_superset?(klass[1,4]), klass.name) - - assert_equal(false, set > klass[1,2,3], klass.name) - assert_equal(true, set > klass[1,2], klass.name) - - assert_equal(false, Set[].proper_superset?(klass[]), klass.name) - } - end - - def test_subset? - set = Set[1,2,3] - - assert_raise(ArgumentError) { - set.subset?() - } - - assert_raise(ArgumentError) { - set.subset?(2) - } - - assert_raise(ArgumentError) { - set.subset?([2]) - } - - [Set, Set2].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) - assert_equal(false, set.subset?(klass[]), klass.name) - - assert_equal(true, set <= klass[1,2,3], klass.name) - assert_equal(true, set <= klass[1,2,3,4], klass.name) - - assert_equal(true, Set[].subset?(klass[1]), klass.name) - assert_equal(true, Set[].subset?(klass[]), klass.name) - } - end - - def test_proper_subset? - set = Set[1,2,3] - - assert_raise(ArgumentError) { - set.proper_subset?() - } - - assert_raise(ArgumentError) { - set.proper_subset?(2) - } - - assert_raise(ArgumentError) { - set.proper_subset?([2]) - } - - [Set, Set2].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) - assert_equal(false, set.proper_subset?(klass[]), klass.name) - - assert_equal(false, set < klass[1,2,3], klass.name) - assert_equal(true, set < klass[1,2,3,4], klass.name) - - assert_equal(false, Set[].proper_subset?(klass[]), klass.name) - } - end - - def test_spacecraft_operator - set = Set[1,2,3] - - assert_nil(set <=> 2) - - assert_nil(set <=> set.to_a) - - [Set, Set2].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) - assert_equal(+1, set <=> klass[2,3] , klass.name) - assert_equal(+1, set <=> klass[] , klass.name) - - assert_equal(0, Set[] <=> klass[], klass.name) - } - end - - def assert_intersect(expected, set, other) - case expected - when true - assert_send([set, :intersect?, other]) - assert_send([set, :intersect?, other.to_a]) - assert_send([other, :intersect?, set]) - assert_not_send([set, :disjoint?, other]) - assert_not_send([set, :disjoint?, other.to_a]) - assert_not_send([other, :disjoint?, set]) - when false - assert_not_send([set, :intersect?, other]) - assert_not_send([set, :intersect?, other.to_a]) - assert_not_send([other, :intersect?, set]) - assert_send([set, :disjoint?, other]) - assert_send([set, :disjoint?, other.to_a]) - assert_send([other, :disjoint?, set]) - when Class - assert_raise(expected) { - set.intersect?(other) - } - assert_raise(expected) { - set.disjoint?(other) - } - else - raise ArgumentError, "%s: unsupported expected value: %s" % [__method__, expected.inspect] - end - end - - def test_intersect? - set = Set[3,4,5] - - assert_intersect(ArgumentError, set, 3) - assert_intersect(true, set, Set[2,4,6]) - - assert_intersect(true, set, set) - assert_intersect(true, set, Set[2,4]) - assert_intersect(true, set, Set[5,6,7]) - assert_intersect(true, set, Set[1,2,6,8,4]) - - assert_intersect(false, set, Set[]) - assert_intersect(false, set, Set[0,2]) - assert_intersect(false, set, Set[0,2,6]) - assert_intersect(false, set, Set[0,2,6,8,10]) - - # Make sure set hasn't changed - assert_equal(Set[3,4,5], set) - end - - def test_each - ary = [1,3,5,7,10,20] - set = Set.new(ary) - - ret = set.each { |o| } - assert_same(set, ret) - - e = set.each - assert_instance_of(Enumerator, e) - - assert_nothing_raised { - set.each { |o| - ary.delete(o) or raise "unexpected element: #{o}" - } - - ary.empty? or raise "forgotten elements: #{ary.join(', ')}" - } - - assert_equal(6, e.size) - set << 42 - assert_equal(7, e.size) - end - - def test_add - set = Set[1,2,3] - - ret = set.add(2) - assert_same(set, ret) - assert_equal(Set[1,2,3], set) - - ret = set.add?(2) - assert_nil(ret) - assert_equal(Set[1,2,3], set) - - ret = set.add(4) - assert_same(set, ret) - assert_equal(Set[1,2,3,4], set) - - ret = set.add?(5) - assert_same(set, ret) - assert_equal(Set[1,2,3,4,5], set) - end - - def test_delete - set = Set[1,2,3] - - ret = set.delete(4) - assert_same(set, ret) - assert_equal(Set[1,2,3], set) - - ret = set.delete?(4) - assert_nil(ret) - assert_equal(Set[1,2,3], set) - - ret = set.delete(2) - assert_equal(set, ret) - assert_equal(Set[1,3], set) - - ret = set.delete?(1) - assert_equal(set, ret) - assert_equal(Set[3], set) - end - - def test_delete_if - set = Set.new(1..10) - ret = set.delete_if { |i| i > 10 } - assert_same(set, ret) - assert_equal(Set.new(1..10), set) - - set = Set.new(1..10) - ret = set.delete_if { |i| i % 3 == 0 } - assert_same(set, ret) - assert_equal(Set[1,2,4,5,7,8,10], set) - - set = Set.new(1..10) - enum = set.delete_if - assert_equal(set.size, enum.size) - assert_same(set, enum.each { |i| i % 3 == 0 }) - assert_equal(Set[1,2,4,5,7,8,10], set) - end - - def test_keep_if - set = Set.new(1..10) - ret = set.keep_if { |i| i <= 10 } - assert_same(set, ret) - assert_equal(Set.new(1..10), set) - - set = Set.new(1..10) - ret = set.keep_if { |i| i % 3 != 0 } - assert_same(set, ret) - assert_equal(Set[1,2,4,5,7,8,10], set) - - set = Set.new(1..10) - enum = set.keep_if - assert_equal(set.size, enum.size) - assert_same(set, enum.each { |i| i % 3 != 0 }) - assert_equal(Set[1,2,4,5,7,8,10], set) - end - - def test_collect! - set = Set[1,2,3,'a','b','c',-1..1,2..4] - - ret = set.collect! { |i| - case i - when Numeric - i * 2 - when String - i.upcase - else - nil - end - } - - assert_same(set, ret) - assert_equal(Set[2,4,6,'A','B','C',nil], set) - - set = Set[1,2,3,'a','b','c',-1..1,2..4] - enum = set.collect! - - assert_equal(set.size, enum.size) - assert_same(set, enum.each { |i| - case i - when Numeric - i * 2 - when String - i.upcase - else - nil - end - }) - assert_equal(Set[2,4,6,'A','B','C',nil], set) - end - - def test_reject! - set = Set.new(1..10) - - ret = set.reject! { |i| i > 10 } - assert_nil(ret) - assert_equal(Set.new(1..10), set) - - ret = set.reject! { |i| i % 3 == 0 } - assert_same(set, ret) - assert_equal(Set[1,2,4,5,7,8,10], set) - - set = Set.new(1..10) - enum = set.reject! - assert_equal(set.size, enum.size) - assert_same(set, enum.each { |i| i % 3 == 0 }) - assert_equal(Set[1,2,4,5,7,8,10], set) - end - - def test_select! - set = Set.new(1..10) - ret = set.select! { |i| i <= 10 } - assert_equal(nil, ret) - assert_equal(Set.new(1..10), set) - - set = Set.new(1..10) - ret = set.select! { |i| i % 3 != 0 } - assert_same(set, ret) - assert_equal(Set[1,2,4,5,7,8,10], set) - - set = Set.new(1..10) - enum = set.select! - assert_equal(set.size, enum.size) - assert_equal(nil, enum.each { |i| i <= 10 }) - assert_equal(Set.new(1..10), set) - end - - def test_filter! - set = Set.new(1..10) - ret = set.filter! { |i| i <= 10 } - assert_equal(nil, ret) - assert_equal(Set.new(1..10), set) - - set = Set.new(1..10) - ret = set.filter! { |i| i % 3 != 0 } - assert_same(set, ret) - assert_equal(Set[1,2,4,5,7,8,10], set) - - set = Set.new(1..10) - enum = set.filter! - assert_equal(set.size, enum.size) - assert_equal(nil, enum.each { |i| i <= 10 }) - assert_equal(Set.new(1..10), set) - end - - def test_merge - set = Set[1,2,3] - ret = set.merge([2,4,6]) - assert_same(set, ret) - assert_equal(Set[1,2,3,4,6], set) - - set = Set[1,2,3] - ret = set.merge() - assert_same(set, ret) - assert_equal(Set[1,2,3], set) - - set = Set[1,2,3] - ret = set.merge([2,4,6], Set[4,5,6]) - assert_same(set, ret) - assert_equal(Set[1,2,3,4,5,6], set) - - assert_raise(ArgumentError) { - Set[].merge(a: 1) - } - end - - def test_subtract - set = Set[1,2,3] - - ret = set.subtract([2,4,6]) - assert_same(set, ret) - assert_equal(Set[1,3], set) - end - - def test_plus - set = Set[1,2,3] - - ret = set + [2,4,6] - assert_not_same(set, ret) - assert_equal(Set[1,2,3,4,6], ret) - end - - def test_minus - set = Set[1,2,3] - - ret = set - [2,4,6] - assert_not_same(set, ret) - assert_equal(Set[1,3], ret) - end - - def test_and - set = Set[1,2,3,4] - - ret = set & [2,4,6] - assert_not_same(set, ret) - assert_equal(Set[2,4], ret) - 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) - - set2 = Set2[1,2,3,4] - ret2 = set2 ^ [2,4,5,5] - assert_instance_of(Set2, ret2) - assert_equal(Set2[1,3,5], ret2) - end - - def test_eq - set1 = Set[2,3,1] - set2 = Set[1,2,3] - - assert_equal(set1, set1) - assert_equal(set1, set2) - assert_not_equal(Set[1], [1]) - - set1 = Class.new(Set)["a", "b"] - set1.add(set1).reset # Make recursive - set2 = Set["a", "b", Set["a", "b", set1]] - - assert_equal(set1, set2) - - assert_not_equal(Set[Exception.new,nil], Set[Exception.new,Exception.new], "[ruby-dev:26127]") - end - - def test_classify - set = Set.new(1..10) - ret = set.classify { |i| i % 3 } - - assert_equal(3, ret.size) - assert_instance_of(Hash, ret) - ret.each_value { |value| assert_instance_of(Set, value) } - assert_equal(Set[3,6,9], ret[0]) - assert_equal(Set[1,4,7,10], ret[1]) - assert_equal(Set[2,5,8], ret[2]) - - set = Set.new(1..10) - enum = set.classify - - assert_equal(set.size, enum.size) - ret = enum.each { |i| i % 3 } - assert_equal(3, ret.size) - assert_instance_of(Hash, ret) - ret.each_value { |value| assert_instance_of(Set, value) } - assert_equal(Set[3,6,9], ret[0]) - assert_equal(Set[1,4,7,10], ret[1]) - assert_equal(Set[2,5,8], ret[2]) - end - - def test_divide - set = Set.new(1..10) - ret = set.divide { |i| i % 3 } - - assert_equal(3, ret.size) - n = 0 - ret.each { |s| n += s.size } - assert_equal(set.size, n) - assert_equal(set, ret.flatten) - - set = Set[7,10,5,11,1,3,4,9,0] - ret = set.divide { |a,b| (a - b).abs == 1 } - - assert_equal(4, ret.size) - n = 0 - ret.each { |s| n += s.size } - assert_equal(set.size, n) - assert_equal(set, ret.flatten) - ret.each { |s| - if s.include?(0) - assert_equal(Set[0,1], s) - elsif s.include?(3) - assert_equal(Set[3,4,5], s) - elsif s.include?(7) - assert_equal(Set[7], s) - elsif s.include?(9) - assert_equal(Set[9,10,11], s) - else - raise "unexpected group: #{s.inspect}" - end - } - - set = Set.new(1..10) - enum = set.divide - ret = enum.each { |i| i % 3 } - - assert_equal(set.size, enum.size) - assert_equal(3, ret.size) - n = 0 - ret.each { |s| n += s.size } - assert_equal(set.size, n) - assert_equal(set, ret.flatten) - end - - def test_freeze - orig = set = Set[1,2,3] - assert_equal false, set.frozen? - set << 4 - assert_same orig, set.freeze - assert_equal true, set.frozen? - assert_raise(FrozenError) { - set << 5 - } - assert_equal 4, set.size - end - - def test_freeze_dup - set1 = Set[1,2,3] - set1.freeze - set2 = set1.dup - - assert_not_predicate set2, :frozen? - assert_nothing_raised { - set2.add 4 - } - end - - def test_freeze_clone - set1 = Set[1,2,3] - set1.freeze - set2 = set1.clone - - assert_predicate set2, :frozen? - assert_raise(FrozenError) { - set2.add 5 - } - end - - def test_freeze_clone_false - set1 = Set[1,2,3] - set1.freeze - set2 = set1.clone(freeze: false) - - assert_not_predicate set2, :frozen? - set2.add 5 - assert_equal Set[1,2,3,5], set2 - assert_equal Set[1,2,3], set1 - end if Kernel.instance_method(:initialize_clone).arity != 1 - - def test_join - assert_equal('123', Set[1, 2, 3].join) - assert_equal('1 & 2 & 3', Set[1, 2, 3].join(' & ')) - end - - def test_inspect - set1 = Set[1, 2] - assert_equal('#', set1.inspect) - - set2 = Set[Set[0], 1, 2, set1] - assert_equal('#, 1, 2, #}>', set2.inspect) - - set1.add(set2) - assert_equal('#, 1, 2, #}>}>', set2.inspect) - end - - def test_to_s - set1 = Set[1, 2] - assert_equal('#', set1.to_s) - - set2 = Set[Set[0], 1, 2, set1] - assert_equal('#, 1, 2, #}>', set2.to_s) - - set1.add(set2) - assert_equal('#, 1, 2, #}>}>', set2.to_s) - end - - def test_compare_by_identity - a1, a2 = "a", "a" - b1, b2 = "b", "b" - c = "c" - array = [a1, b1, c, a2, b2] - - iset = Set.new.compare_by_identity - assert_send([iset, :compare_by_identity?]) - iset.merge(array) - assert_equal(5, iset.size) - assert_equal(array.map(&:object_id).sort, iset.map(&:object_id).sort) - - set = Set.new - assert_not_send([set, :compare_by_identity?]) - set.merge(array) - assert_equal(3, set.size) - assert_equal(array.uniq.sort, set.sort) - end - - def test_reset - [Set, Class.new(Set)].each { |klass| - a = [1, 2] - b = [1] - set = klass.new([a, b]) - - b << 2 - set.reset - - assert_equal(klass.new([a]), set, klass.name) - } - end -end - -class TC_Enumerable < Test::Unit::TestCase - def test_to_set - ary = [2,5,4,3,2,1,3] - - set = ary.to_set - assert_instance_of(Set, set) - assert_equal([1,2,3,4,5], set.sort) - - set = ary.to_set { |o| o * -2 } - assert_instance_of(Set, set) - assert_equal([-10,-8,-6,-4,-2], set.sort) - - assert_same set, set.to_set - assert_not_same set, set.to_set { |o| o } - end -end - -class TC_Set_Builtin < Test::Unit::TestCase - private def should_omit? - (RUBY_VERSION.scan(/\d+/).map(&:to_i) <=> [3, 2]) < 0 || - !File.exist?(File.expand_path('../prelude.rb', __dir__)) - end - - def test_Set - omit "skipping the test for the builtin Set" if should_omit? - - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") - begin; - assert_nothing_raised do - set = Set.new([1, 2]) - assert_equal('Set', set.class.name) - end - end; - - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") - begin; - assert_nothing_raised do - set = Set[1, 2] - assert_equal('Set', set.class.name) - end - end; - end - - def test_to_set - omit "skipping the test for the builtin Enumerable#to_set" if should_omit? - - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") - begin; - assert_nothing_raised do - set = [1, 2].to_set - assert_equal('Set', set.class.name) - end - end; - end -end diff --git a/test/mri/set/test_sorted_set.rb b/test/mri/set/test_sorted_set.rb deleted file mode 100644 index f7ad7af2993..00000000000 --- a/test/mri/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 From 85541f7fbd628201e6a6b057c2a6747bbdac8052 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 12 Nov 2025 12:22:18 -0600 Subject: [PATCH 012/168] Add CRuby's test_set.rb --- test/mri/ruby/test_set.rb | 995 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 995 insertions(+) create mode 100644 test/mri/ruby/test_set.rb diff --git a/test/mri/ruby/test_set.rb b/test/mri/ruby/test_set.rb new file mode 100644 index 00000000000..af5f65bea0f --- /dev/null +++ b/test/mri/ruby/test_set.rb @@ -0,0 +1,995 @@ +# frozen_string_literal: false +require 'test/unit' +require 'set' + +class TC_Set < Test::Unit::TestCase + class Set2 < Set + end + + 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 + assert_nothing_raised { + Set[] + Set[nil] + Set[1,2,3] + } + + assert_equal(0, Set[].size) + assert_equal(1, Set[nil].size) + assert_equal(1, Set[[]].size) + assert_equal(1, Set[[nil]].size) + + set = Set[2,4,6,4] + assert_equal(Set.new([2,4,6]), set) + end + + def test_s_new + assert_nothing_raised { + Set.new() + Set.new(nil) + Set.new([]) + Set.new([1,2]) + Set.new('a'..'c') + } + assert_raise(ArgumentError) { + Set.new(false) + } + assert_raise(ArgumentError) { + Set.new(1) + } + assert_raise(ArgumentError) { + Set.new(1,2) + } + + assert_equal(0, Set.new().size) + assert_equal(0, Set.new(nil).size) + assert_equal(0, Set.new([]).size) + assert_equal(1, Set.new([nil]).size) + + ary = [2,4,6,4] + set = Set.new(ary) + ary.clear + assert_equal(false, set.empty?) + assert_equal(3, set.size) + + ary = [1,2,3] + + s = Set.new(ary) { |o| o * 2 } + assert_equal([2,4,6], s.sort) + + assert_raise(ArgumentError) { + Set.new((1..)) + } + assert_raise(ArgumentError) { + Set.new((1..), &:succ) + } + assert_raise(ArgumentError) { + Set.new(1.upto(Float::INFINITY)) + } + + assert_raise(ArgumentError) { + Set.new(Object.new) + } + end + + def test_clone + set1 = Set.new + set2 = set1.clone + set1 << 'abc' + assert_equal(Set.new, set2) + end + + def test_dup + set1 = Set[1,2] + set2 = set1.dup + + assert_not_same(set1, set2) + + assert_equal(set1, set2) + + set1.add(3) + + assert_not_equal(set1, set2) + end + + def test_size + assert_equal(0, Set[].size) + assert_equal(2, Set[1,2].size) + assert_equal(2, Set[1,2,1].size) + end + + def test_empty? + assert_equal(true, Set[].empty?) + assert_equal(false, Set[1, 2].empty?) + end + + def test_clear + set = Set[1,2] + ret = set.clear + + assert_same(set, ret) + assert_equal(true, set.empty?) + end + + def test_replace + set = Set[1,2] + ret = set.replace('a'..'c') + + assert_same(set, ret) + 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) + } + assert_equal(Set[1,2], set) + end + + def test_to_a + set = Set[1,2,3,2] + ary = set.to_a + + assert_equal([1,2,3], ary.sort) + end + + def test_flatten + # test1 + set1 = Set[ + 1, + Set[ + 5, + Set[7, + Set[0] + ], + Set[6,2], + 1 + ], + 3, + Set[3,4] + ] + + set2 = set1.flatten + set3 = Set.new(0..7) + + assert_not_same(set2, set1) + assert_equal(set3, set2) + + # test2; destructive + orig_set1 = set1 + set1.flatten! + + assert_same(orig_set1, set1) + assert_equal(set3, set1) + + # test3; multiple occurrences of a set in an set + set1 = Set[1, 2] + set2 = Set[set1, Set[set1, 4], 3] + + assert_nothing_raised { + set2.flatten! + } + + assert_equal(Set.new(1..4), set2) + + # test4; recursion + set2 = Set[] + set1 = Set[1, set2] + set2.add(set1) + + assert_raise(ArgumentError) { + set1.flatten! + } + + # test5; miscellaneous + empty = Set[] + set = Set[Set[empty, "a"],Set[empty, "b"]] + + assert_nothing_raised { + set.flatten + } + + set1 = empty.merge(Set["no_more", set]) + + assert_nil(Set.new(0..31).flatten!) + + x = Set[Set[],Set[1,2]].flatten! + y = Set[1,2] + + assert_equal(x, y) + end + + def test_include? + set = Set[1,2,3] + + assert_equal(true, set.include?(1)) + assert_equal(true, set.include?(2)) + assert_equal(true, set.include?(3)) + assert_equal(false, set.include?(0)) + assert_equal(false, set.include?(nil)) + + set = Set["1",nil,"2",nil,"0","1",false] + assert_equal(true, set.include?(nil)) + assert_equal(true, set.include?(false)) + assert_equal(true, set.include?("1")) + assert_equal(false, set.include?(0)) + assert_equal(false, set.include?(true)) + end + + def test_eqq + set = Set[1,2,3] + + assert_equal(true, set === 1) + assert_equal(true, set === 2) + assert_equal(true, set === 3) + assert_equal(false, set === 0) + assert_equal(false, set === nil) + + set = Set["1",nil,"2",nil,"0","1",false] + assert_equal(true, set === nil) + assert_equal(true, set === false) + assert_equal(true, set === "1") + assert_equal(false, set === 0) + assert_equal(false, set === true) + end + + def test_superset? + set = Set[1,2,3] + + assert_raise(ArgumentError) { + set.superset?() + } + + assert_raise(ArgumentError) { + set.superset?(2) + } + + assert_raise(ArgumentError) { + set.superset?([2]) + } + + [Set, Set2].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) + assert_equal(false, set.superset?(klass[1,2,3,4]), klass.name) + assert_equal(false, set.superset?(klass[1,4]), klass.name) + + assert_equal(true, set >= klass[1,2,3], klass.name) + assert_equal(true, set >= klass[1,2], klass.name) + + assert_equal(true, Set[].superset?(klass[]), klass.name) + } + end + + def test_proper_superset? + set = Set[1,2,3] + + assert_raise(ArgumentError) { + set.proper_superset?() + } + + assert_raise(ArgumentError) { + set.proper_superset?(2) + } + + assert_raise(ArgumentError) { + set.proper_superset?([2]) + } + + [Set, Set2].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) + assert_equal(false, set.proper_superset?(klass[1,2,3,4]), klass.name) + assert_equal(false, set.proper_superset?(klass[1,4]), klass.name) + + assert_equal(false, set > klass[1,2,3], klass.name) + assert_equal(true, set > klass[1,2], klass.name) + + assert_equal(false, Set[].proper_superset?(klass[]), klass.name) + } + end + + def test_subset? + set = Set[1,2,3] + + assert_raise(ArgumentError) { + set.subset?() + } + + assert_raise(ArgumentError) { + set.subset?(2) + } + + assert_raise(ArgumentError) { + set.subset?([2]) + } + + [Set, Set2].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) + assert_equal(false, set.subset?(klass[]), klass.name) + + assert_equal(true, set <= klass[1,2,3], klass.name) + assert_equal(true, set <= klass[1,2,3,4], klass.name) + + assert_equal(true, Set[].subset?(klass[1]), klass.name) + assert_equal(true, Set[].subset?(klass[]), klass.name) + } + end + + def test_proper_subset? + set = Set[1,2,3] + + assert_raise(ArgumentError) { + set.proper_subset?() + } + + assert_raise(ArgumentError) { + set.proper_subset?(2) + } + + assert_raise(ArgumentError) { + set.proper_subset?([2]) + } + + [Set, Set2].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) + assert_equal(false, set.proper_subset?(klass[]), klass.name) + + assert_equal(false, set < klass[1,2,3], klass.name) + assert_equal(true, set < klass[1,2,3,4], klass.name) + + assert_equal(false, Set[].proper_subset?(klass[]), klass.name) + } + end + + def test_spacecraft_operator + set = Set[1,2,3] + + assert_nil(set <=> 2) + + assert_nil(set <=> set.to_a) + + [Set, Set2].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) + assert_equal(+1, set <=> klass[2,3] , klass.name) + assert_equal(+1, set <=> klass[] , klass.name) + + assert_equal(0, Set[] <=> klass[], klass.name) + } + end + + def assert_intersect(expected, set, other) + case expected + when true + assert_send([set, :intersect?, other]) + assert_send([set, :intersect?, other.to_a]) + assert_send([other, :intersect?, set]) + assert_not_send([set, :disjoint?, other]) + assert_not_send([set, :disjoint?, other.to_a]) + assert_not_send([other, :disjoint?, set]) + when false + assert_not_send([set, :intersect?, other]) + assert_not_send([set, :intersect?, other.to_a]) + assert_not_send([other, :intersect?, set]) + assert_send([set, :disjoint?, other]) + assert_send([set, :disjoint?, other.to_a]) + assert_send([other, :disjoint?, set]) + when Class + assert_raise(expected) { + set.intersect?(other) + } + assert_raise(expected) { + set.disjoint?(other) + } + else + raise ArgumentError, "%s: unsupported expected value: %s" % [__method__, expected.inspect] + end + end + + def test_intersect? + set = Set[3,4,5] + + assert_intersect(ArgumentError, set, 3) + assert_intersect(true, set, Set[2,4,6]) + + assert_intersect(true, set, set) + assert_intersect(true, set, Set[2,4]) + assert_intersect(true, set, Set[5,6,7]) + assert_intersect(true, set, Set[1,2,6,8,4]) + + assert_intersect(false, set, Set[]) + assert_intersect(false, set, Set[0,2]) + assert_intersect(false, set, Set[0,2,6]) + assert_intersect(false, set, Set[0,2,6,8,10]) + + # Make sure set hasn't changed + assert_equal(Set[3,4,5], set) + end + + def test_each + ary = [1,3,5,7,10,20] + set = Set.new(ary) + + ret = set.each { |o| } + assert_same(set, ret) + + e = set.each + assert_instance_of(Enumerator, e) + + assert_nothing_raised { + set.each { |o| + ary.delete(o) or raise "unexpected element: #{o}" + } + + ary.empty? or raise "forgotten elements: #{ary.join(', ')}" + } + + assert_equal(6, e.size) + set << 42 + assert_equal(7, e.size) + end + + def test_add + set = Set[1,2,3] + + ret = set.add(2) + assert_same(set, ret) + assert_equal(Set[1,2,3], set) + + ret = set.add?(2) + assert_nil(ret) + assert_equal(Set[1,2,3], set) + + ret = set.add(4) + assert_same(set, ret) + assert_equal(Set[1,2,3,4], set) + + ret = set.add?(5) + assert_same(set, ret) + assert_equal(Set[1,2,3,4,5], set) + end + + def test_delete + set = Set[1,2,3] + + ret = set.delete(4) + assert_same(set, ret) + assert_equal(Set[1,2,3], set) + + ret = set.delete?(4) + assert_nil(ret) + assert_equal(Set[1,2,3], set) + + ret = set.delete(2) + assert_equal(set, ret) + assert_equal(Set[1,3], set) + + ret = set.delete?(1) + assert_equal(set, ret) + assert_equal(Set[3], set) + end + + def test_delete_if + set = Set.new(1..10) + ret = set.delete_if { |i| i > 10 } + assert_same(set, ret) + assert_equal(Set.new(1..10), set) + + set = Set.new(1..10) + ret = set.delete_if { |i| i % 3 == 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.delete_if + assert_equal(set.size, enum.size) + assert_same(set, enum.each { |i| i % 3 == 0 }) + assert_equal(Set[1,2,4,5,7,8,10], set) + end + + def test_keep_if + set = Set.new(1..10) + ret = set.keep_if { |i| i <= 10 } + assert_same(set, ret) + assert_equal(Set.new(1..10), set) + + set = Set.new(1..10) + ret = set.keep_if { |i| i % 3 != 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.keep_if + assert_equal(set.size, enum.size) + assert_same(set, enum.each { |i| i % 3 != 0 }) + assert_equal(Set[1,2,4,5,7,8,10], set) + end + + def test_collect! + set = Set[1,2,3,'a','b','c',-1..1,2..4] + + ret = set.collect! { |i| + case i + when Numeric + i * 2 + when String + i.upcase + else + nil + end + } + + assert_same(set, ret) + assert_equal(Set[2,4,6,'A','B','C',nil], set) + + set = Set[1,2,3,'a','b','c',-1..1,2..4] + enum = set.collect! + + assert_equal(set.size, enum.size) + assert_same(set, enum.each { |i| + case i + when Numeric + i * 2 + when String + i.upcase + else + nil + end + }) + assert_equal(Set[2,4,6,'A','B','C',nil], set) + end + + def test_reject! + set = Set.new(1..10) + + ret = set.reject! { |i| i > 10 } + assert_nil(ret) + assert_equal(Set.new(1..10), set) + + ret = set.reject! { |i| i % 3 == 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.reject! + assert_equal(set.size, enum.size) + assert_same(set, enum.each { |i| i % 3 == 0 }) + assert_equal(Set[1,2,4,5,7,8,10], set) + end + + def test_select! + set = Set.new(1..10) + ret = set.select! { |i| i <= 10 } + assert_equal(nil, ret) + assert_equal(Set.new(1..10), set) + + set = Set.new(1..10) + ret = set.select! { |i| i % 3 != 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.select! + assert_equal(set.size, enum.size) + assert_equal(nil, enum.each { |i| i <= 10 }) + assert_equal(Set.new(1..10), set) + end + + def test_filter! + set = Set.new(1..10) + ret = set.filter! { |i| i <= 10 } + assert_equal(nil, ret) + assert_equal(Set.new(1..10), set) + + set = Set.new(1..10) + ret = set.filter! { |i| i % 3 != 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.filter! + assert_equal(set.size, enum.size) + assert_equal(nil, enum.each { |i| i <= 10 }) + assert_equal(Set.new(1..10), set) + end + + def test_merge + set = Set[1,2,3] + ret = set.merge([2,4,6]) + assert_same(set, ret) + assert_equal(Set[1,2,3,4,6], set) + + set = Set[1,2,3] + ret = set.merge() + assert_same(set, ret) + assert_equal(Set[1,2,3], set) + + set = Set[1,2,3] + ret = set.merge([2,4,6], Set[4,5,6]) + assert_same(set, ret) + assert_equal(Set[1,2,3,4,5,6], set) + + assert_raise(ArgumentError) { + Set[].merge(a: 1) + } + 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] + + ret = set.subtract([2,4,6]) + assert_same(set, ret) + assert_equal(Set[1,3], set) + end + + def test_plus + set = Set[1,2,3] + + ret = set + [2,4,6] + assert_not_same(set, ret) + assert_equal(Set[1,2,3,4,6], ret) + end + + def test_minus + set = Set[1,2,3] + + ret = set - [2,4,6] + assert_not_same(set, ret) + assert_equal(Set[1,3], ret) + end + + def test_and + set = Set[1,2,3,4] + + ret = set & [2,4,6] + assert_not_same(set, ret) + assert_equal(Set[2,4], ret) + 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) + + set2 = Set2[1,2,3,4] + ret2 = set2 ^ [2,4,5,5] + assert_instance_of(Set2, ret2) + assert_equal(Set2[1,3,5], ret2) + end + + def test_eq + set1 = Set[2,3,1] + set2 = Set[1,2,3] + + assert_equal(set1, set1) + assert_equal(set1, set2) + assert_not_equal(Set[1], [1]) + + set1 = Class.new(Set)["a", "b"] + set1.add(set1).reset # Make recursive + set2 = Set["a", "b", Set["a", "b", set1]] + + assert_equal(set1, set2) + + assert_not_equal(Set[Exception.new,nil], Set[Exception.new,Exception.new], "[ruby-dev:26127]") + end + + def test_classify + set = Set.new(1..10) + ret = set.classify { |i| i % 3 } + + assert_equal(3, ret.size) + assert_instance_of(Hash, ret) + ret.each_value { |value| assert_instance_of(Set, value) } + assert_equal(Set[3,6,9], ret[0]) + assert_equal(Set[1,4,7,10], ret[1]) + assert_equal(Set[2,5,8], ret[2]) + + set = Set.new(1..10) + enum = set.classify + + assert_equal(set.size, enum.size) + ret = enum.each { |i| i % 3 } + assert_equal(3, ret.size) + assert_instance_of(Hash, ret) + ret.each_value { |value| assert_instance_of(Set, value) } + assert_equal(Set[3,6,9], ret[0]) + assert_equal(Set[1,4,7,10], ret[1]) + assert_equal(Set[2,5,8], ret[2]) + end + + def test_divide + set = Set.new(1..10) + ret = set.divide { |i| i % 3 } + + assert_equal(3, ret.size) + n = 0 + ret.each { |s| n += s.size } + assert_equal(set.size, n) + assert_equal(set, ret.flatten) + + set = Set[7,10,5,11,1,3,4,9,0] + ret = set.divide { |a,b| (a - b).abs == 1 } + + assert_equal(4, ret.size) + n = 0 + ret.each { |s| n += s.size } + assert_equal(set.size, n) + assert_equal(set, ret.flatten) + ret.each { |s| + if s.include?(0) + assert_equal(Set[0,1], s) + elsif s.include?(3) + assert_equal(Set[3,4,5], s) + elsif s.include?(7) + assert_equal(Set[7], s) + elsif s.include?(9) + assert_equal(Set[9,10,11], s) + else + raise "unexpected group: #{s.inspect}" + end + } + + set = Set.new(1..10) + enum = set.divide + ret = enum.each { |i| i % 3 } + + assert_equal(set.size, enum.size) + assert_equal(3, ret.size) + n = 0 + 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 + orig = set = Set[1,2,3] + assert_equal false, set.frozen? + set << 4 + assert_same orig, set.freeze + assert_equal true, set.frozen? + assert_raise(FrozenError) { + set << 5 + } + assert_equal 4, set.size + end + + def test_freeze_dup + set1 = Set[1,2,3] + set1.freeze + set2 = set1.dup + + assert_not_predicate set2, :frozen? + assert_nothing_raised { + set2.add 4 + } + end + + def test_freeze_clone + set1 = Set[1,2,3] + set1.freeze + set2 = set1.clone + + assert_predicate set2, :frozen? + assert_raise(FrozenError) { + set2.add 5 + } + end + + def test_freeze_clone_false + set1 = Set[1,2,3] + set1.freeze + set2 = set1.clone(freeze: false) + + assert_not_predicate set2, :frozen? + set2.add 5 + assert_equal Set[1,2,3,5], set2 + assert_equal Set[1,2,3], set1 + end if Kernel.instance_method(:initialize_clone).arity != 1 + + def test_join + assert_equal('123', Set[1, 2, 3].join) + assert_equal('1 & 2 & 3', Set[1, 2, 3].join(' & ')) + end + + def test_inspect + set1 = Set[1, 2] + 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) + + set1.add(set2) + assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.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) + + set2 = Set[Set[0], 1, 2, set1] + 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) + end + + def test_compare_by_identity + a1, a2 = "a", "a" + b1, b2 = "b", "b" + c = "c" + array = [a1, b1, c, a2, b2] + + iset = Set.new.compare_by_identity + assert_send([iset, :compare_by_identity?]) + iset.merge(array) + assert_equal(5, iset.size) + assert_equal(array.map(&:object_id).sort, iset.map(&:object_id).sort) + + set = Set.new + assert_not_send([set, :compare_by_identity?]) + set.merge(array) + assert_equal(3, set.size) + assert_equal(array.uniq.sort, set.sort) + end + + def test_reset + [Set, Class.new(Set)].each { |klass| + a = [1, 2] + b = [1] + set = klass.new([a, b]) + + b << 2 + set.reset + + 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 +end + +class TC_Enumerable < Test::Unit::TestCase + def test_to_set + ary = [2,5,4,3,2,1,3] + + set = ary.to_set + assert_instance_of(Set, set) + assert_equal([1,2,3,4,5], set.sort) + + set = ary.to_set { |o| o * -2 } + assert_instance_of(Set, set) + assert_equal([-10,-8,-6,-4,-2], set.sort) + + assert_same set, set.to_set + assert_not_same set, set.to_set { |o| o } + end +end + +class TC_Set_Builtin < Test::Unit::TestCase + private def should_omit? + (RUBY_VERSION.scan(/\d+/).map(&:to_i) <=> [3, 2]) < 0 || + !File.exist?(File.expand_path('../prelude.rb', __dir__)) + end + + def test_Set + omit "skipping the test for the builtin Set" if should_omit? + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_nothing_raised do + set = Set.new([1, 2]) + assert_equal('Set', set.class.name) + end + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_nothing_raised do + set = Set[1, 2] + assert_equal('Set', set.class.name) + end + end; + end + + def test_to_set + omit "skipping the test for the builtin Enumerable#to_set" if should_omit? + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_nothing_raised do + set = [1, 2].to_set + assert_equal('Set', set.class.name) + end + end; + end +end From 7019118d7b3a76a776d636d54c1fb74221c9e98a Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 12 Nov 2025 12:35:37 -0600 Subject: [PATCH 013/168] Update JRuby's test_set for core set move * Fix inspect output expectations. * Guard SortedSet tests behind a require, since that collection is now in a separate library (knu/sorted_set). These tests probably should move to the external library once we work out the details (see knu/sorted_set#7) --- test/jruby/test_set.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/jruby/test_set.rb b/test/jruby/test_set.rb index 04f8925679b..2a15bc1da95 100644 --- a/test/jruby/test_set.rb +++ b/test/jruby/test_set.rb @@ -11,10 +11,7 @@ def test_sub_set assert_same SubSet, set.class assert set.is_a?(SubSet) assert set.is_a?(Set) - assert hash = set.instance_variable_get(:@hash) - assert hash.is_a?(Hash) - assert_equal Hash.new, hash - assert_equal '#', set.inspect + assert_equal 'SubSet[]', set.inspect assert_false Set.new.equal?(SubSet.new) assert_true Set.new.eql?(SubSet.new) @@ -83,13 +80,17 @@ def test_dup_to_a def test_to_java assert set = Set.new.to_java - assert_equal "#", set.toString + assert_equal "Set[]", set.toString assert_equal org.jruby.ext.set.RubySet, set.class assert set.is_a?(java.util.Set) assert_equal java.util.HashSet.new, set + end if defined? JRUBY_VERSION + + def test_to_java_sorted_set + require "sorted_set" rescue skip "sorted_set not available" assert set = SortedSet.new([2, 1]).to_java - assert set.toString.start_with?('# Date: Fri, 14 Nov 2025 19:26:58 -0600 Subject: [PATCH 014/168] Remove @hash instance variable and all references to it --- .../main/java/org/jruby/ext/set/RubySet.java | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index d18ce1699e0..aebb73976a3 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -81,14 +81,6 @@ public static RubyClass createSetClass(ThreadContext context, RubyClass Object, defineMethods(context, RubySet.class). tap(c -> c.marshalWith(new SetMarshal(c.getMarshal()))); - MethodHandles.Lookup lookup = MethodHandles.lookup(); - try { - Set.getVariableTableManager().getVariableAccessorForRubyVar("@hash", lookup.findGetter(RubySet.class, "hash", RubyHash.class), lookup.findSetter(RubySet.class, "hash", RubyHash.class)); - } catch (NoSuchFieldException | IllegalAccessException e) { - // should not happen - throw new RuntimeException(e); - } - Enumerable.defineMethods(context, EnumerableExt.class); return Set; @@ -285,19 +277,6 @@ private static IRubyObject doWithEnum(final ThreadContext context, final IRubyOb throw argumentError(context, "value must be enumerable"); } - // YAML doesn't have proper treatment for Set serialization, it dumps it just like - // any Ruby object, meaning on YAML.load will allocate an "initialize" all i-vars! - @Override - public IRubyObject instance_variable_set(IRubyObject name, IRubyObject value) { - if (getRuntime().newSymbol("@hash").equals(name)) { - if (value instanceof RubyHash) { - this.hash = (RubyHash) value; - return value; - } - } - return super.instance_variable_set(name, value); - } - IRubyObject invokeAdd(final ThreadContext context, final IRubyObject val) { return sites(context).add.call(context, this, this, val); } @@ -633,7 +612,7 @@ protected void addImpl(final Ruby runtime, final IRubyObject obj) { } protected void addImpl(final ThreadContext context, final IRubyObject obj) { - hash.fastASetCheckString(context.runtime, obj, context.tru); // @hash[obj] = true + hash.fastASetCheckString(context.runtime, obj, context.tru); } protected void addImplSet(final ThreadContext context, final RubySet set) { @@ -878,7 +857,7 @@ public IRubyObject op_xor(final ThreadContext context, IRubyObject enume) { public IRubyObject op_equal(ThreadContext context, IRubyObject other) { if (this == other) return context.tru; if (getMetaClass().isInstance(other)) { - return hash.op_equal(context, ((RubySet) other).hash); // @hash == ... + return hash.op_equal(context, ((RubySet) other).hash); } if (other instanceof RubySet that) { if (size() == that.size()) { // && includes all of our elements : @@ -915,7 +894,7 @@ public boolean eql(IRubyObject otherArg) { } @JRubyMethod - public RubyFixnum hash(ThreadContext context) { // @hash.hash + public RubyFixnum hash(ThreadContext context) { RubyHash hash = this.hash; return hash == null ? From c411a9d5d647ecc6df2778f2a2e44e0116248eb9 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 12:39:51 -0600 Subject: [PATCH 015/168] Remove defunct library/set spec tags --- spec/tags/ruby/library/set/compare_by_identity_tags.txt | 1 - spec/tags/ruby/library/set/disjoint_tags.txt | 2 -- spec/tags/ruby/library/set/divide_tags.txt | 1 - spec/tags/ruby/library/set/equal_value_tags.txt | 1 - spec/tags/ruby/library/set/flatten_tags.txt | 2 -- spec/tags/ruby/library/set/intersect_tags.txt | 2 -- spec/tags/ruby/library/set/merge_tags.txt | 1 - spec/tags/ruby/library/set/proper_subset_tags.txt | 1 - spec/tags/ruby/library/set/proper_superset_tags.txt | 1 - spec/tags/ruby/library/set/sortedset/sortedset_tags.txt | 1 - spec/tags/ruby/library/set/subset_tags.txt | 1 - spec/tags/ruby/library/set/superset_tags.txt | 1 - 12 files changed, 15 deletions(-) delete mode 100644 spec/tags/ruby/library/set/compare_by_identity_tags.txt delete mode 100644 spec/tags/ruby/library/set/disjoint_tags.txt delete mode 100644 spec/tags/ruby/library/set/divide_tags.txt delete mode 100644 spec/tags/ruby/library/set/equal_value_tags.txt delete mode 100644 spec/tags/ruby/library/set/flatten_tags.txt delete mode 100644 spec/tags/ruby/library/set/intersect_tags.txt delete mode 100644 spec/tags/ruby/library/set/merge_tags.txt delete mode 100644 spec/tags/ruby/library/set/proper_subset_tags.txt delete mode 100644 spec/tags/ruby/library/set/proper_superset_tags.txt delete mode 100644 spec/tags/ruby/library/set/sortedset/sortedset_tags.txt delete mode 100644 spec/tags/ruby/library/set/subset_tags.txt delete mode 100644 spec/tags/ruby/library/set/superset_tags.txt diff --git a/spec/tags/ruby/library/set/compare_by_identity_tags.txt b/spec/tags/ruby/library/set/compare_by_identity_tags.txt deleted file mode 100644 index bcee51db40b..00000000000 --- a/spec/tags/ruby/library/set/compare_by_identity_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#compare_by_identity is not equal to set what does not compare by identity diff --git a/spec/tags/ruby/library/set/disjoint_tags.txt b/spec/tags/ruby/library/set/disjoint_tags.txt deleted file mode 100644 index d4942290175..00000000000 --- a/spec/tags/ruby/library/set/disjoint_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Set#disjoint? when comparing to a Set-like object returns false when a Set has at least one element in common with a Set-like object -fails:Set#disjoint? when comparing to a Set-like object returns true when a Set has no element in common with a Set-like object diff --git a/spec/tags/ruby/library/set/divide_tags.txt b/spec/tags/ruby/library/set/divide_tags.txt deleted file mode 100644 index 981ba7bb891..00000000000 --- a/spec/tags/ruby/library/set/divide_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#divide when passed a block with an arity of 2 returns an enumerator when not passed a block diff --git a/spec/tags/ruby/library/set/equal_value_tags.txt b/spec/tags/ruby/library/set/equal_value_tags.txt deleted file mode 100644 index f6d94262e10..00000000000 --- a/spec/tags/ruby/library/set/equal_value_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#== when comparing to a Set-like object returns true when a Set and a Set-like object contain the same elements diff --git a/spec/tags/ruby/library/set/flatten_tags.txt b/spec/tags/ruby/library/set/flatten_tags.txt deleted file mode 100644 index 471ffcdf732..00000000000 --- a/spec/tags/ruby/library/set/flatten_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Set#flatten when Set contains a Set-like object returns a copy of self with each included Set-like object flattened -fails:Set#flatten! when Set contains a Set-like object flattens self, including Set-like objects diff --git a/spec/tags/ruby/library/set/intersect_tags.txt b/spec/tags/ruby/library/set/intersect_tags.txt deleted file mode 100644 index 7424ffa1f94..00000000000 --- a/spec/tags/ruby/library/set/intersect_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Set#intersect? when comparing to a Set-like object returns true when a Set has at least one element in common with a Set-like object -fails:Set#intersect? when comparing to a Set-like object returns false when a Set has no element in common with a Set-like object diff --git a/spec/tags/ruby/library/set/merge_tags.txt b/spec/tags/ruby/library/set/merge_tags.txt deleted file mode 100644 index 2fc9a275286..00000000000 --- a/spec/tags/ruby/library/set/merge_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#merge accepts only a single argument diff --git a/spec/tags/ruby/library/set/proper_subset_tags.txt b/spec/tags/ruby/library/set/proper_subset_tags.txt deleted file mode 100644 index afc54a8a70b..00000000000 --- a/spec/tags/ruby/library/set/proper_subset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#proper_subset? when comparing to a Set-like object returns true if passed a Set-like object that self is a proper subset of diff --git a/spec/tags/ruby/library/set/proper_superset_tags.txt b/spec/tags/ruby/library/set/proper_superset_tags.txt deleted file mode 100644 index 43d1f3b7657..00000000000 --- a/spec/tags/ruby/library/set/proper_superset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#proper_superset? when comparing to a Set-like object returns true if passed a Set-like object that self is a proper superset of diff --git a/spec/tags/ruby/library/set/sortedset/sortedset_tags.txt b/spec/tags/ruby/library/set/sortedset/sortedset_tags.txt deleted file mode 100644 index 821c591e82a..00000000000 --- a/spec/tags/ruby/library/set/sortedset/sortedset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:SortedSet raises error including message that it has been extracted from the set stdlib diff --git a/spec/tags/ruby/library/set/subset_tags.txt b/spec/tags/ruby/library/set/subset_tags.txt deleted file mode 100644 index 3d8e572c30a..00000000000 --- a/spec/tags/ruby/library/set/subset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#subset? when comparing to a Set-like object returns true if passed a Set-like object that self is a subset of diff --git a/spec/tags/ruby/library/set/superset_tags.txt b/spec/tags/ruby/library/set/superset_tags.txt deleted file mode 100644 index b73761ff264..00000000000 --- a/spec/tags/ruby/library/set/superset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#superset? when comparing to a Set-like object returns true if passed a Set-like object that self is a superset of From 235d23b3616fdd173d0a9eee72d3b3a60a30eda7 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 12:40:20 -0600 Subject: [PATCH 016/168] Align Hash#== behavior with CRuby --- core/src/main/java/org/jruby/RubyHash.java | 34 ++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyHash.java b/core/src/main/java/org/jruby/RubyHash.java index 6332f11c697..7b462ef6fad 100644 --- a/core/src/main/java/org/jruby/RubyHash.java +++ b/core/src/main/java/org/jruby/RubyHash.java @@ -1393,7 +1393,37 @@ public void visit(ThreadContext context, RubyHash self, IRubyObject key, IRubyOb @Override @JRubyMethod(name = "==") public IRubyObject op_equal(final ThreadContext context, IRubyObject other) { - return RecursiveComparator.compare(context, FindMismatchUsingEqualVisitor, this, other, false); + return hashEqual(context, this, other, false); + } + + static IRubyObject hashEqual(final ThreadContext context, RubyHash hash1, IRubyObject _hash2, boolean eql) { + if (hash1 == _hash2) return context.tru; + if (!(_hash2 instanceof RubyHash hash2)) { + if (!_hash2.respondsTo("to_hash")) { + return context.fals; + } + if (eql) { + if (Helpers.rbEql(context, _hash2, hash1).isTrue()) { + return context.tru; + } else { + return context.fals; + } + } else { + return Helpers.rbEqual(context, _hash2, hash1); + } + } + if (hash1.size() != hash2.size()) return context.fals; + if (!hash1.isEmpty() && !hash2.isEmpty()) { + if (hash1.isComparedByIdentity() != hash2.isComparedByIdentity()) { + return context.fals; + } else { + return RecursiveComparator.compare( + context, + eql ? FindMismatchUsingEqlVisitor : FindMismatchUsingEqualVisitor, + hash1, hash2, eql); + } + } + return context.tru; } /** rb_hash_eql @@ -1401,7 +1431,7 @@ public IRubyObject op_equal(final ThreadContext context, IRubyObject other) { */ @JRubyMethod(name = "eql?") public IRubyObject op_eql(final ThreadContext context, IRubyObject other) { - return RecursiveComparator.compare(context, FindMismatchUsingEqlVisitor, this, other, true); + return hashEqual(context, this, other, true); } /** rb_hash_aref From 2d5e1f7cd7933b7bf5d3844d22e86bd4b87470aa Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 12:59:20 -0600 Subject: [PATCH 017/168] Align Set#compare_by_identity with CRuby --- core/src/main/java/org/jruby/ext/set/RubySet.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index aebb73976a3..0098c670896 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -425,6 +425,8 @@ public RubySet to_set(final ThreadContext context, final IRubyObject[] args, fin @JRubyMethod public IRubyObject compare_by_identity(ThreadContext context) { + if (hash.isComparedByIdentity()) return this; + if (isFrozen()) throw context.runtime.newFrozenError("Set", this); this.hash.compare_by_identity(context); return this; } From ddee6f5555cd089127f35780c8ed32961d1b7d34 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 13:00:04 -0600 Subject: [PATCH 018/168] Align Set#disjoint? and Set#intersect? with CRuby --- core/src/main/java/org/jruby/ext/set/RubySet.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index 0098c670896..64bc60d82b4 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -548,13 +548,12 @@ public IRubyObject proper_subset_p(final ThreadContext context, IRubyObject setA */ @JRubyMethod(name = "intersect?") public IRubyObject intersect_p(final ThreadContext context, IRubyObject setArg) { - return asBoolean(context, - intersect( - switch (setArg) { - case RubySet set -> set; - case RubyArray ary -> newSet(context, context.runtime.getSet(), ary); - default -> throw argumentError(context, "value must be a set or array"); - })); + if (setArg instanceof RubySet other) { + return asBoolean(context, intersect(other)); + } else if (setArg.getType().isKindOfModule(enumerableModule(context))) { + return setArg.callMethod(context, "any?", this); + } + throw argumentError(context, "value must be a set or array"); } public boolean intersect(final RubySet set) { From c57e6103e8d6835b0100a8f8cc624d05494a62b9 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 13:45:41 -0600 Subject: [PATCH 019/168] Implement union-find with path compression Based on code from CRuby by Tomoya Ishida: https://github.com/ruby/ruby/pull/13680 --- .../main/java/org/jruby/ext/set/RubySet.java | 144 +++++++----------- 1 file changed, 57 insertions(+), 87 deletions(-) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index 64bc60d82b4..ea997c7564f 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -34,6 +34,7 @@ import org.jruby.RubyEnumerator.SizeFn; import org.jruby.anno.JRubyMethod; import org.jruby.api.Access; +import org.jruby.api.Create; import org.jruby.api.Error; import org.jruby.javasupport.JavaUtil; import org.jruby.runtime.*; @@ -45,7 +46,6 @@ import org.jruby.util.io.RubyOutputStream; import java.io.IOException; -import java.lang.invoke.MethodHandles; import java.lang.reflect.Array; import java.util.Collection; import java.util.IdentityHashMap; @@ -55,7 +55,6 @@ import static org.jruby.RubyEnumerator.enumeratorizeWithSize; import static org.jruby.api.Access.enumerableModule; import static org.jruby.api.Access.getModule; -import static org.jruby.api.Access.hashClass; import static org.jruby.api.Access.loadService; import static org.jruby.api.Access.objectClass; import static org.jruby.api.Convert.asBoolean; @@ -944,7 +943,7 @@ public IRubyObject classify(ThreadContext context, final Block block) { public IRubyObject divide(ThreadContext context, final Block block) { if (!block.isGiven()) return enumeratorizeWithSize(context, this, "divide", RubySet::size); - if (block.getSignature().arityValue() == 2) return divideTSort(context, block); + if (block.getSignature().arityValue() == 2) return setDivideArity2(context, block); RubyHash vals = (RubyHash) classify(context, block); final RubySet set = new RubySet(context.runtime, Access.getClass(context, "Set")); @@ -955,106 +954,77 @@ public IRubyObject divide(ThreadContext context, final Block block) { return set; } - private IRubyObject divideTSort(ThreadContext context, final Block block) { - final RubyHash dig = DivideTSortHash.newInstance(context); + // MRI: set_divide_arity2 + IRubyObject setDivideArity2(ThreadContext context, Block block) { + RubyClass setClass = getMetaClass(); - /* - each { |u| - dig[u] = a = [] - each{ |v| func.call(u, v) and a << v } - } - */ - for ( IRubyObject u : elementsOrdered() ) { - var a = newArray(context); - dig.fastASet(u, a); - for ( IRubyObject v : elementsOrdered() ) { - IRubyObject ret = block.call(context, u, v); - if ( ret.isTrue() ) a.append(context, v); - } - } - - /* - set = Set.new() - dig.each_strongly_connected_component { |css| - set.add(self.class.new(css)) - } - set - */ - final RubyClass Set = Access.getClass(context, "Set"); - final RubySet set = new RubySet(context.runtime, Set); - set.allocHash(context, dig.size()); - sites(context).each_strongly_connected_component.call(context, this, dig, new Block( - new JavaInternalBlockBody(context.runtime, Signature.ONE_REQUIRED) { - @Override - public IRubyObject yield(ThreadContext context, IRubyObject[] args) { - return doYield(context, null, args[0]); - } + RubyArray items = to_a(context); + items.setFrozen(true); + int size = items.size(); - @Override - protected IRubyObject doYield(ThreadContext context, Block block, IRubyObject css) { - // set.add( self.class.new(css) ) : - set.addImpl(context, newSet(context, Set, (RubyArray) css)); - return context.nil; - } - }) - ); + int[] tmp_array = new int[size]; + int[] uf_parents = new int[size]; - return set; - } + for (int i = 0; i < size; i++) { + uf_parents[i] = i; + } - // NOTE: a replacement for set.rb's eval in Set#divide : `class << dig = {} ...` - public static final class DivideTSortHash extends RubyHash { + for (int i = 0; i < size - 1; i++) { + IRubyObject item1 = items.eltOk(i); - private static final String NAME = "DivideTSortHash"; // private constant under Set:: + for (int j = i + 1; j < size; j++) { + IRubyObject item2 = items.eltOk(j); - static DivideTSortHash newInstance(final ThreadContext context) { - RubyClass Set = Access.getClass(context, "Set"); - RubyClass klass = (RubyClass) Set.getConstantAt(context, NAME, true); - if (klass == null) { // initialize on-demand when Set#divide is first called - synchronized (DivideTSortHash.class) { - klass = (RubyClass) Set.getConstantAt(context, NAME, true); - if (klass == null) { - var Hash = hashClass(context); - klass = Set.defineClassUnder(context, NAME, Hash, Hash.getAllocator()). - include(context, getTSort(context)). - defineMethods(context, DivideTSortHash.class); - Set.setConstantVisibility(context, NAME, true); // private - } + if (block.yieldSpecific(context, item1, item2).isTrue() && + block.yieldSpecific(context, item2, item1).isTrue()) { + setDivideUnionFindMerge(context, uf_parents, i, j, tmp_array); } } - return new DivideTSortHash(context.runtime, klass); } - DivideTSortHash(final Ruby runtime, final RubyClass metaClass) { - super(runtime, metaClass); - } + RubySet finalSet = newSet(context.runtime, setClass); + RubyHash hash = Create.newHash(context); - /* - class << dig = {} # :nodoc: - include TSort - - alias tsort_each_node each_key - def tsort_each_child(node, &block) - fetch(node).each(&block) - end - end - */ + for (int i = 0; i < size; i++) { + IRubyObject v = items.eltOk(i); - @JRubyMethod - public IRubyObject tsort_each_node(ThreadContext context, Block block) { - return each_key(context, block); - } + int root = setDivideUnionFindRoot(context, uf_parents, i, tmp_array); + + IRubyObject set = hash.op_aref(context, asFixnum(context, root)); - @JRubyMethod - public IRubyObject tsort_each_child(ThreadContext context, IRubyObject node, Block block) { - IRubyObject set = fetch(context, node, Block.NULL_BLOCK); - if ( set instanceof RubySet ) { - return ((RubySet) set).each(context, block); + if (set.isNil()) { + set = newSet(context.runtime, setClass); + hash.op_aset(context, asFixnum(context, root), set); + finalSet.add(context, set); } - // some Enumerable (we do not expect this to happen) - return sites(context).each.call(context, this, set, block); + + ((RubySet) set).add(context, v); } + return finalSet; + } + + // MRI: set_divide_union_find_merge + static void setDivideUnionFindMerge(ThreadContext context, int[] uf_parents, int i, int j, int[] tmp_array) { + int root_i = setDivideUnionFindRoot(context, uf_parents, i, tmp_array); + int root_j = setDivideUnionFindRoot(context, uf_parents, j, tmp_array); + if (root_i != root_j) uf_parents[root_j] = root_i; + } + + // MRI: set_divide_union_find_root + static int setDivideUnionFindRoot(ThreadContext context, int[] uf_parents, int index, int[] tmp_array) { + int root = uf_parents[index]; + int update_size = 0; + while (root != index) { + tmp_array[update_size++] = index; + index = root; + root = uf_parents[index]; + } + for (int j = 0; j < update_size; j++) { + int idx = tmp_array[j]; + uf_parents[idx] = root; + } + return root; } @JRubyMethod(name = "<=>") From 18cb385c9503a9afd89c83e99de19477bd43f812 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 13:47:10 -0600 Subject: [PATCH 020/168] Propagate wrapped block's signature When the incoming block's signature is important to the iteration method being called, the previous logic would hide that signature. The change here propagates it through the CallBlock. --- core/src/main/java/org/jruby/RubyEnumerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/jruby/RubyEnumerator.java b/core/src/main/java/org/jruby/RubyEnumerator.java index d262b47f0a4..541c95492be 100644 --- a/core/src/main/java/org/jruby/RubyEnumerator.java +++ b/core/src/main/java/org/jruby/RubyEnumerator.java @@ -328,7 +328,7 @@ public IRubyObject each(ThreadContext context, Block block) { private IRubyObject __each__(ThreadContext context, final Block block) { if (methodArgsHasKeywords) context.callInfo = CALL_KEYWORD; return object.callMethod(context, method, methodArgs, - CallBlock.newCallClosure(context, this, Signature.OPTIONAL, (ctx, args, blk) -> { + CallBlock.newCallClosure(context, this, block.getSignature(), (ctx, args, blk) -> { IRubyObject ret = block.yieldValues(ctx, args); IRubyObject val = feedValue.use_value(ctx); return val.isNil() ? ret : val; From b3e8a0bf9a4e9ea9e1114c2e6ccc732b329453ae Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 14:17:08 -0600 Subject: [PATCH 021/168] Align Set#initialize enum logic with CRuby This does not exactly match the new native Set impl, but it passes specs and tests related to initialize. --- core/src/main/java/org/jruby/ext/set/RubySet.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index ea997c7564f..8a7442e1a80 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -241,18 +241,20 @@ public IRubyObject initialize(final ThreadContext context, IRubyObject enume, fi } allocHash(context); - // set.rb do_with_enum : if (block.isGiven()) { return doWithEnum(context, enume, new EachBody(context) { IRubyObject yieldImpl(ThreadContext context2, IRubyObject val) { return invokeAdd(context2, block.yield(context2, val)); } }); + } else { + return doWithEnum(context, enume, new EachBody(context) { + IRubyObject yieldImpl(ThreadContext context2, IRubyObject val) { + return invokeAdd(context2, val); + } + }); } } - - allocHash(context); - return sites(context).merge.call(context, this, this, enume); // TODO site-cache } protected IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) { From 2e946dd83b90ea89ff09724b1f5e531c203e0076 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 14:22:32 -0600 Subject: [PATCH 022/168] Update Set#^ behavior to match CRuby --- core/src/main/java/org/jruby/ext/set/RubySet.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index 8a7442e1a80..58fb5fb8c37 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -841,8 +841,13 @@ IRubyObject yieldImpl(ThreadContext context, IRubyObject obj) { */ @JRubyMethod(name = "^") public IRubyObject op_xor(final ThreadContext context, IRubyObject enume) { - RubySet newSet = new RubySet(context.runtime, getMetaClass()); - newSet.initialize(context, enume, Block.NULL_BLOCK); // Set.new(enum) + RubySet newSet; + if (enume instanceof RubySet set) { + newSet = set; + } else { + newSet = new RubySet(context.runtime, getMetaClass()); + newSet.initialize(context, enume, Block.NULL_BLOCK); // Set.new(enum) + } for (IRubyObject o : elementsOrdered()) { if (newSet.containsImpl(o)) { newSet.deleteImpl(o); // exclusive or From 1cd56847d5c6b8955710dcc3ff27d3dbb0521454 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 14:22:49 -0600 Subject: [PATCH 023/168] Update prelude from CRuby This updates some logic related to the new native Set. --- core/src/main/ruby/jruby/kernel/prelude.rb | 30 +++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/core/src/main/ruby/jruby/kernel/prelude.rb b/core/src/main/ruby/jruby/kernel/prelude.rb index e68b1f341c9..06949076c84 100644 --- a/core/src/main/ruby/jruby/kernel/prelude.rb +++ b/core/src/main/ruby/jruby/kernel/prelude.rb @@ -1,25 +1,43 @@ class Binding # :nodoc: - def irb - require 'irb' - irb + def irb(...) + begin + require 'irb' + rescue LoadError, Gem::LoadError + Gem::BUNDLED_GEMS.force_activate 'irb' + require 'irb' + end + irb(...) end + + # suppress redefinition warning + alias irb irb # :nodoc: end module Kernel + # :stopdoc: def pp(*objs) require 'pp' pp(*objs) end + # suppress redefinition warning + alias pp pp + private :pp + # :startdoc: end -autoload :Set, 'set' - module Enumerable # Makes a set from the enumerable object with given arguments. - def to_set(klass = Set, *args, &block) + # Passing arguments to this method is deprecated. + def to_set(*args, &block) + klass = if args.empty? + Set + else + warn "passing arguments to Enumerable#to_set is deprecated", uplevel: 1 + args.shift + end klass.new(self, *args, &block) end end From 0b82e3b91333d7ee52ac50dcc5a91200ae251b69 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 14:23:34 -0600 Subject: [PATCH 024/168] Update Set spec tags Only fails are now related to us not tracking iteration-in-progress for either Set or Hash. --- spec/tags/ruby/core/set/add_tags.txt | 2 +- spec/tags/ruby/core/set/compare_by_identity_tags.txt | 1 - spec/tags/ruby/core/set/disjoint_tags.txt | 2 -- spec/tags/ruby/core/set/divide_tags.txt | 1 - spec/tags/ruby/core/set/equal_value_tags.txt | 1 - spec/tags/ruby/core/set/flatten_tags.txt | 2 -- spec/tags/ruby/core/set/intersect_tags.txt | 2 -- spec/tags/ruby/core/set/merge_tags.txt | 2 +- spec/tags/ruby/core/set/proper_subset_tags.txt | 1 - spec/tags/ruby/core/set/proper_superset_tags.txt | 1 - spec/tags/ruby/core/set/replace_tags.txt | 2 +- spec/tags/ruby/core/set/sortedset/sortedset_tags.txt | 1 - spec/tags/ruby/core/set/subset_tags.txt | 1 - spec/tags/ruby/core/set/superset_tags.txt | 1 - 14 files changed, 3 insertions(+), 17 deletions(-) delete mode 100644 spec/tags/ruby/core/set/compare_by_identity_tags.txt delete mode 100644 spec/tags/ruby/core/set/disjoint_tags.txt delete mode 100644 spec/tags/ruby/core/set/divide_tags.txt delete mode 100644 spec/tags/ruby/core/set/equal_value_tags.txt delete mode 100644 spec/tags/ruby/core/set/flatten_tags.txt delete mode 100644 spec/tags/ruby/core/set/intersect_tags.txt delete mode 100644 spec/tags/ruby/core/set/proper_subset_tags.txt delete mode 100644 spec/tags/ruby/core/set/proper_superset_tags.txt delete mode 100644 spec/tags/ruby/core/set/sortedset/sortedset_tags.txt delete mode 100644 spec/tags/ruby/core/set/subset_tags.txt delete mode 100644 spec/tags/ruby/core/set/superset_tags.txt diff --git a/spec/tags/ruby/core/set/add_tags.txt b/spec/tags/ruby/core/set/add_tags.txt index f5466e162b8..a9e2007811e 100644 --- a/spec/tags/ruby/core/set/add_tags.txt +++ b/spec/tags/ruby/core/set/add_tags.txt @@ -1 +1 @@ -fails:Set#add? raises RuntimeError when called during iteration +fails(we do not track iteration in progress in set or hash):Set#add? raises RuntimeError when called during iteration diff --git a/spec/tags/ruby/core/set/compare_by_identity_tags.txt b/spec/tags/ruby/core/set/compare_by_identity_tags.txt deleted file mode 100644 index bcee51db40b..00000000000 --- a/spec/tags/ruby/core/set/compare_by_identity_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#compare_by_identity is not equal to set what does not compare by identity diff --git a/spec/tags/ruby/core/set/disjoint_tags.txt b/spec/tags/ruby/core/set/disjoint_tags.txt deleted file mode 100644 index d4942290175..00000000000 --- a/spec/tags/ruby/core/set/disjoint_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Set#disjoint? when comparing to a Set-like object returns false when a Set has at least one element in common with a Set-like object -fails:Set#disjoint? when comparing to a Set-like object returns true when a Set has no element in common with a Set-like object diff --git a/spec/tags/ruby/core/set/divide_tags.txt b/spec/tags/ruby/core/set/divide_tags.txt deleted file mode 100644 index 981ba7bb891..00000000000 --- a/spec/tags/ruby/core/set/divide_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#divide when passed a block with an arity of 2 returns an enumerator when not passed a block diff --git a/spec/tags/ruby/core/set/equal_value_tags.txt b/spec/tags/ruby/core/set/equal_value_tags.txt deleted file mode 100644 index f6d94262e10..00000000000 --- a/spec/tags/ruby/core/set/equal_value_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#== when comparing to a Set-like object returns true when a Set and a Set-like object contain the same elements diff --git a/spec/tags/ruby/core/set/flatten_tags.txt b/spec/tags/ruby/core/set/flatten_tags.txt deleted file mode 100644 index 471ffcdf732..00000000000 --- a/spec/tags/ruby/core/set/flatten_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Set#flatten when Set contains a Set-like object returns a copy of self with each included Set-like object flattened -fails:Set#flatten! when Set contains a Set-like object flattens self, including Set-like objects diff --git a/spec/tags/ruby/core/set/intersect_tags.txt b/spec/tags/ruby/core/set/intersect_tags.txt deleted file mode 100644 index 7424ffa1f94..00000000000 --- a/spec/tags/ruby/core/set/intersect_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Set#intersect? when comparing to a Set-like object returns true when a Set has at least one element in common with a Set-like object -fails:Set#intersect? when comparing to a Set-like object returns false when a Set has no element in common with a Set-like object diff --git a/spec/tags/ruby/core/set/merge_tags.txt b/spec/tags/ruby/core/set/merge_tags.txt index ba19680d75f..8cc9ec6f114 100644 --- a/spec/tags/ruby/core/set/merge_tags.txt +++ b/spec/tags/ruby/core/set/merge_tags.txt @@ -1 +1 @@ -fails:Set#merge raises RuntimeError when called during iteration +fails(we do not track iteration in progress in set or hash):Set#merge raises RuntimeError when called during iteration diff --git a/spec/tags/ruby/core/set/proper_subset_tags.txt b/spec/tags/ruby/core/set/proper_subset_tags.txt deleted file mode 100644 index afc54a8a70b..00000000000 --- a/spec/tags/ruby/core/set/proper_subset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#proper_subset? when comparing to a Set-like object returns true if passed a Set-like object that self is a proper subset of diff --git a/spec/tags/ruby/core/set/proper_superset_tags.txt b/spec/tags/ruby/core/set/proper_superset_tags.txt deleted file mode 100644 index 43d1f3b7657..00000000000 --- a/spec/tags/ruby/core/set/proper_superset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#proper_superset? when comparing to a Set-like object returns true if passed a Set-like object that self is a proper superset of diff --git a/spec/tags/ruby/core/set/replace_tags.txt b/spec/tags/ruby/core/set/replace_tags.txt index 4b3bc38e648..3c02349edce 100644 --- a/spec/tags/ruby/core/set/replace_tags.txt +++ b/spec/tags/ruby/core/set/replace_tags.txt @@ -1 +1 @@ -fails:Set#replace raises RuntimeError when called during iteration +fails(we do not track iteration in progress in set or hash):Set#replace raises RuntimeError when called during iteration diff --git a/spec/tags/ruby/core/set/sortedset/sortedset_tags.txt b/spec/tags/ruby/core/set/sortedset/sortedset_tags.txt deleted file mode 100644 index 821c591e82a..00000000000 --- a/spec/tags/ruby/core/set/sortedset/sortedset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:SortedSet raises error including message that it has been extracted from the set stdlib diff --git a/spec/tags/ruby/core/set/subset_tags.txt b/spec/tags/ruby/core/set/subset_tags.txt deleted file mode 100644 index 3d8e572c30a..00000000000 --- a/spec/tags/ruby/core/set/subset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#subset? when comparing to a Set-like object returns true if passed a Set-like object that self is a subset of diff --git a/spec/tags/ruby/core/set/superset_tags.txt b/spec/tags/ruby/core/set/superset_tags.txt deleted file mode 100644 index b73761ff264..00000000000 --- a/spec/tags/ruby/core/set/superset_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Set#superset? when comparing to a Set-like object returns true if passed a Set-like object that self is a superset of From 3ea2f656c8079233c53e54be2d8e74d8e0eba548 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 15:16:42 -0600 Subject: [PATCH 025/168] Simulate old Set marshaling for compat --- .../main/java/org/jruby/ext/set/RubySet.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index 58fb5fb8c37..78c71c23474 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -101,7 +101,15 @@ public void marshalTo(Ruby runtime, Object obj, RubyClass type, org.jruby.runtim } public void marshalTo(ThreadContext context, RubyOutputStream out, Object obj, RubyClass type, MarshalDumper marshalStream) { - defaultMarshal.marshalTo(context, out, obj, type, marshalStream); + RubySet set = (RubySet) obj; + + // create dummy object with extra @hash ivar and dump variables from that + RubyObject dummy = new RubyObject(context.runtime, type); + dummy.setInstanceVariable("@hash", set.hash); + + set.copyInstanceVariablesInto(dummy); + + marshalStream.dumpVariables(context, out, dummy); } @Deprecated(since = "10.0.0.0", forRemoval = true) @@ -112,8 +120,17 @@ public Object unmarshalFrom(Ruby runtime, RubyClass type, org.jruby.runtime.mars } public Object unmarshalFrom(ThreadContext context, RubyInputStream in, RubyClass type, MarshalLoader loader) { - Object result = defaultMarshal.unmarshalFrom(context, in, type, loader); - return result; + RubySet set = new RubySet(context.runtime, type); + + // unmarshal as dummy object and extra @hash ivar + RubyObject dummy = (RubyObject) loader.entry(new RubyObject(context.runtime, type)); + loader.ivar(context, in, null, dummy, null); + + set.hash = (RubyHash) dummy.getInstanceVariables().removeInstanceVariable("@hash"); + + dummy.copyInstanceVariablesInto(set); + + return set; } } From c258550ddcca3d3fbae48d846f4987e0359b045f Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 15:22:19 -0600 Subject: [PATCH 026/168] Remove passing excludes for Set --- test/mri/excludes/TC_Set.rb | 1 - test/mri/excludes/TC_SortedSet.rb | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 test/mri/excludes/TC_Set.rb delete mode 100644 test/mri/excludes/TC_SortedSet.rb diff --git a/test/mri/excludes/TC_Set.rb b/test/mri/excludes/TC_Set.rb deleted file mode 100644 index ec32cd9f90b..00000000000 --- a/test/mri/excludes/TC_Set.rb +++ /dev/null @@ -1 +0,0 @@ -exclude :"test_intersect?", "work in progress" diff --git a/test/mri/excludes/TC_SortedSet.rb b/test/mri/excludes/TC_SortedSet.rb deleted file mode 100644 index 48d8e35bd4c..00000000000 --- a/test/mri/excludes/TC_SortedSet.rb +++ /dev/null @@ -1,2 +0,0 @@ -exclude :test_error, "work in progress" -exclude :test_sortedset, "needs investigation" From 55d295cbd724e0897a01b9024b2b99e8513b16ff Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 15:38:46 -0600 Subject: [PATCH 027/168] Prealloc Set's Hash along default paths Paths that will immmediately initialize the wrapped Hash do not need to pre-allocate. This fixes issues with Set.allocate returning an object that has a null Hash and can't be used for anything. --- .../java/org/jruby/ext/set/EnumerableExt.java | 2 +- .../main/java/org/jruby/ext/set/RubySet.java | 34 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/org/jruby/ext/set/EnumerableExt.java b/core/src/main/java/org/jruby/ext/set/EnumerableExt.java index 6fed4387907..f0a46680b71 100644 --- a/core/src/main/java/org/jruby/ext/set/EnumerableExt.java +++ b/core/src/main/java/org/jruby/ext/set/EnumerableExt.java @@ -45,7 +45,7 @@ public abstract class EnumerableExt { //@JRubyMethod public static IRubyObject to_set(final ThreadContext context, final IRubyObject self, final Block block) { - RubySet set = new RubySet(context.runtime, Access.getClass(context, "Set")); + RubySet set = new RubySet(context.runtime, Access.getClass(context, "Set"), false); set.initialize(context, self, block); return set; // return runtime.getClass("Set").newInstance(context, self, block); } diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index 78c71c23474..ea2c3c05cae 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -120,7 +120,7 @@ public Object unmarshalFrom(Ruby runtime, RubyClass type, org.jruby.runtime.mars } public Object unmarshalFrom(ThreadContext context, RubyInputStream in, RubyClass type, MarshalLoader loader) { - RubySet set = new RubySet(context.runtime, type); + RubySet set = new RubySet(context.runtime, type, false); // unmarshal as dummy object and extra @hash ivar RubyObject dummy = (RubyObject) loader.entry(new RubyObject(context.runtime, type)); @@ -136,14 +136,18 @@ public Object unmarshalFrom(ThreadContext context, RubyInputStream in, RubyClass } protected RubySet(Ruby runtime, RubyClass klass) { + this(runtime, klass, true); + } + + protected RubySet(Ruby runtime, RubyClass klass, boolean alloc) { super(runtime, klass); + if (alloc) allocHash(runtime); } - /* - private RubySet(Ruby runtime, RubyHash hash) { - super(runtime, runtime.getClass("Set")); - allocHash(hash); - } */ + private RubySet(Ruby runtime, RubyClass klass, RubyHash hash) { + super(runtime, klass); + this.hash = hash; + } // since MRI uses Hash.new(false) we'll (initially) strive for maximum compatibility // ... this is important with Rails using Sprockets at its marshalling Set instances @@ -188,13 +192,11 @@ public static RubySet newSet(final Ruby runtime) { * @return a new Set */ public static RubySet newSet(final Ruby runtime, final RubyClass metaclass) { - RubySet set = new RubySet(runtime, metaclass); - set.allocHash(runtime); - return set; + return new RubySet(runtime, metaclass); } private static RubySet newSet(final ThreadContext context, final RubyClass metaClass, final RubyArray elements) { - final RubySet set = new RubySet(context.runtime, metaClass); + final RubySet set = new RubySet(context.runtime, metaClass, false); return set.initSet(context, elements.toJavaArrayMaybeUnsafe(), 0, elements.size()); } @@ -213,7 +215,7 @@ final RubySet initSet(final ThreadContext context, final IRubyObject[] elements, public static RubySet create(final ThreadContext context, IRubyObject self, IRubyObject... ary) { final Ruby runtime = context.runtime; - RubySet set = new RubySet(runtime, (RubyClass) self); + RubySet set = new RubySet(runtime, (RubyClass) self, false); return set.initSet(context, ary, 0, ary.length); } @@ -411,7 +413,7 @@ public RubyArray to_a(final ThreadContext context) { @JRubyMethod public RubySet to_set(final ThreadContext context, final Block block) { if ( block.isGiven() ) { - RubySet set = new RubySet(context.runtime, getMetaClass()); + RubySet set = new RubySet(context.runtime, getMetaClass(), false); set.initialize(context, this, block); return set; } @@ -436,7 +438,7 @@ public RubySet to_set(final ThreadContext context, final IRubyObject[] args, fin rest = args; } - RubySet set = new RubySet(context.runtime, (RubyClass) klass); + RubySet set = new RubySet(context.runtime, (RubyClass) klass, false); set.initialize(context, rest, block); return set; } @@ -826,7 +828,7 @@ public IRubyObject op_diff(final ThreadContext context, IRubyObject enume) { */ @JRubyMethod(name = "&", alias = { "intersection" }) public IRubyObject op_and(final ThreadContext context, IRubyObject enume) { - final RubySet newSet = new RubySet(context.runtime, getMetaClass()); + final RubySet newSet = new RubySet(context.runtime, getMetaClass(), false); if (enume instanceof RubySet set) { newSet.allocHash(context, set.size()); for ( IRubyObject obj : set.elementsOrdered() ) { @@ -862,7 +864,7 @@ public IRubyObject op_xor(final ThreadContext context, IRubyObject enume) { if (enume instanceof RubySet set) { newSet = set; } else { - newSet = new RubySet(context.runtime, getMetaClass()); + newSet = new RubySet(context.runtime, getMetaClass(), false); newSet.initialize(context, enume, Block.NULL_BLOCK); // Set.new(enum) } for (IRubyObject o : elementsOrdered()) { @@ -970,7 +972,7 @@ public IRubyObject divide(ThreadContext context, final Block block) { if (block.getSignature().arityValue() == 2) return setDivideArity2(context, block); RubyHash vals = (RubyHash) classify(context, block); - final RubySet set = new RubySet(context.runtime, Access.getClass(context, "Set")); + final RubySet set = new RubySet(context.runtime, Access.getClass(context, "Set"), false); set.allocHash(context, vals.size()); for ( IRubyObject val : (Collection) vals.directValues() ) { set.invokeAdd(context, val); From 0cbc9eb8307768b693614dd08e37642fb53e344d Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 15:39:44 -0600 Subject: [PATCH 028/168] Remove defunct Set tests from JRuby suite --- test/jruby/test_set.rb | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/test/jruby/test_set.rb b/test/jruby/test_set.rb index 2a15bc1da95..7296bcebbfc 100644 --- a/test/jruby/test_set.rb +++ b/test/jruby/test_set.rb @@ -4,20 +4,6 @@ # JRuby's Set impl specific or low-level details. class TestSet < Test::Unit::TestCase - class SubSet < Set ; end - - def test_sub_set - set = SubSet.new - assert_same SubSet, set.class - assert set.is_a?(SubSet) - assert set.is_a?(Set) - assert_equal 'SubSet[]', set.inspect - - assert_false Set.new.equal?(SubSet.new) - assert_true Set.new.eql?(SubSet.new) - assert_true ( SubSet.new == Set.new ) - end - def test_allocate set = Set.allocate assert_same Set, set.class @@ -86,17 +72,6 @@ def test_to_java assert_equal java.util.HashSet.new, set end if defined? JRUBY_VERSION - def test_to_java_sorted_set - require "sorted_set" rescue skip "sorted_set not available" - - assert set = SortedSet.new([2, 1]).to_java - assert set.toString.start_with?('SortedSet[]') - assert_equal org.jruby.ext.set.RubySortedSet, set.class - assert set.is_a?(java.util.Set) - assert set.is_a?(java.util.SortedSet) - assert_equal java.util.TreeSet.new([1, 2]), set - end if defined? JRUBY_VERSION - def test_cmp_0_but_not_eql set = Set[1, 2] assert_equal set.to_a, set.dup.to_a From 270c0a71cc88a9d6ff1b9ca6682dc673b07577df Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 23:03:49 -0600 Subject: [PATCH 029/168] Avoid array allocation for *nil, by not calling nil.to_a For Ruby 4.0 compatibility. See https://bugs.ruby-lang.org/issues/21047 See https://github.com/jruby/jruby/issues/9061 --- .../org/jruby/ir/runtime/IRRuntimeHelpers.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java index e8d6ffd2bf2..a40f3e1b0a5 100644 --- a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java +++ b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java @@ -2036,12 +2036,7 @@ public static RubyArray irSplat(ThreadContext context, IRubyObject ary) { */ @JIT @Interp public static RubyArray splatArray(ThreadContext context, IRubyObject ary, boolean dupArray) { - IRubyObject tmp = TypeConverter.convertToTypeWithCheck(context, ary, arrayClass(context), sites(context).to_a_checked); - - if (tmp.isNil()) return newArray(context, ary); - if (dupArray) return ((RubyArray) tmp).aryDup(); - - return (RubyArray) tmp; + return dupArray ? splatArrayDup(context, ary) : splatArray(context, ary); } /** @@ -2051,6 +2046,8 @@ public static RubyArray splatArray(ThreadContext context, IRubyObject ary, boole */ @JIT @Interp public static RubyArray splatArray(ThreadContext context, IRubyObject ary) { + if (ary.isNil()) return context.runtime.getEmptyFrozenArray(); + IRubyObject tmp = TypeConverter.convertToTypeWithCheck(context, ary, arrayClass(context), sites(context).to_a_checked); if (tmp.isNil()) return newArray(context, ary); @@ -2065,9 +2062,13 @@ public static RubyArray splatArray(ThreadContext context, IRubyObject ary) { */ @JIT @Interp public static RubyArray splatArrayDup(ThreadContext context, IRubyObject ary) { + if (ary.isNil()) return newEmptyArray(context); + IRubyObject tmp = TypeConverter.convertToTypeWithCheck(context, ary, arrayClass(context), sites(context).to_a_checked); - return tmp.isNil() ? newArray(context, ary) : ((RubyArray) tmp).aryDup(); + if (tmp.isNil()) return newArray(context, ary); + + return ((RubyArray) tmp).aryDup(); } public static IRubyObject irToAry(ThreadContext context, IRubyObject value) { From 1c48b59632a600a2bb47d2bc9139c03dc72c8bb6 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 01:02:51 -0600 Subject: [PATCH 030/168] No numbered parameters in Binding#local_variables For Ruby 4.0 compatibility. See https://github.com/ruby/ruby/pull/12746 See https://github.com/jruby/jruby/issues/9061 --- core/src/main/java/org/jruby/RubyBinding.java | 27 ++++- .../java/org/jruby/parser/RubyParserBase.java | 5 +- .../java/org/jruby/parser/StaticScope.java | 104 +++++++++++++++--- 3 files changed, 119 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyBinding.java b/core/src/main/java/org/jruby/RubyBinding.java index e51f821590f..66465e4d8ec 100644 --- a/core/src/main/java/org/jruby/RubyBinding.java +++ b/core/src/main/java/org/jruby/RubyBinding.java @@ -36,7 +36,9 @@ import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; +import org.jruby.exceptions.RaiseException; import org.jruby.ext.ripper.RubyLexer; +import org.jruby.parser.StaticScope; import org.jruby.runtime.Arity; import org.jruby.runtime.Binding; import org.jruby.runtime.Block; @@ -149,9 +151,10 @@ public IRubyObject local_variable_defined_p(ThreadContext context, IRubyObject s public IRubyObject local_variable_get(ThreadContext context, IRubyObject symbol) { String id = checkLocalId(context, symbol); DynamicScope evalScope = binding.getEvalScope(context.runtime); - int slot = evalScope.getStaticScope().isDefined(id); + StaticScope staticScope = evalScope.getStaticScope(); + int slot = staticScope.isDefinedNotImplicit(id); - if (slot == -1) throw nameError(context, str(context.runtime, "local variable '", symbol, "' not defined for " + inspect(context)), symbol); + if (slot < 0) throw undefinedVariableError(context, symbol); return evalScope.getValueOrNil(slot & 0xffff, slot >> 16, context.nil); } @@ -160,7 +163,11 @@ public IRubyObject local_variable_get(ThreadContext context, IRubyObject symbol) public IRubyObject local_variable_set(ThreadContext context, IRubyObject symbol, IRubyObject value) { String id = checkLocalId(context, symbol); DynamicScope evalScope = binding.getEvalScope(context.runtime); - int slot = evalScope.getStaticScope().isDefined(id); + StaticScope staticScope = evalScope.getStaticScope(); + int slot = staticScope.isDefinedNotImplicit(id); + + // for the implicit "it" variable + if (slot == StaticScope.IMPLICIT) throw undefinedVariableError(context, symbol); if (slot == -1) { // Yay! New variable associated with this binding slot = evalScope.getStaticScope().addVariable(id.intern()); @@ -170,10 +177,24 @@ public IRubyObject local_variable_set(ThreadContext context, IRubyObject symbol, return evalScope.setValue(slot & 0xffff, value, slot >> 16); } + private static boolean isNumberedVariable(String id) { + return id.length() == 2 && id.charAt(0) == '_' && Character.isDigit(id.charAt(1)); + } + + private RaiseException numberedParameterError(ThreadContext context, IRubyObject symbol) { + return nameError(context, str(context.runtime, "numbered parameter '", symbol, "' is not a local variable"), symbol); + } + + private RaiseException undefinedVariableError(ThreadContext context, IRubyObject symbol) { + return nameError(context, str(context.runtime, "local variable '", symbol, "' not defined for " + inspect(context)), symbol); + } + // MRI: check_local_id private String checkLocalId(ThreadContext context, IRubyObject obj) { String id = RubySymbol.idStringFromObject(context, obj); + if (isNumberedVariable(id)) throw numberedParameterError(context, obj); + if (!RubyLexer.isIdentifierChar(id.charAt(0))) { throw nameError(context, str(context.runtime, "wrong local variable name '", obj, "' for ", this), id); } diff --git a/core/src/main/java/org/jruby/parser/RubyParserBase.java b/core/src/main/java/org/jruby/parser/RubyParserBase.java index 14f5d2645ab..c6b7925d978 100644 --- a/core/src/main/java/org/jruby/parser/RubyParserBase.java +++ b/core/src/main/java/org/jruby/parser/RubyParserBase.java @@ -216,7 +216,7 @@ private ListNode makePreNumArgs(int paramCount) { for (int i = 1; i <= paramCount; i++) { RubySymbol name = symbolID(new ByteList(("_" + i).getBytes())); - list.add(new ArgumentNode(lexer.getRubySourceline(), name, getCurrentScope().addVariableThisScope(name.idString()))); + list.add(new ArgumentNode(lexer.getRubySourceline(), name, getCurrentScope().addImplicitVariableThisScope(name.idString()))); } return list; @@ -2465,6 +2465,9 @@ protected Node it_id() { } protected void set_it_id(Node node) { + if (node != null) { + currentScope.markImplicitVariable(((DVarNode) node).getIndex()); + } this.itId = node; } diff --git a/core/src/main/java/org/jruby/parser/StaticScope.java b/core/src/main/java/org/jruby/parser/StaticScope.java index c1270d97128..9cd2fc288a5 100644 --- a/core/src/main/java/org/jruby/parser/StaticScope.java +++ b/core/src/main/java/org/jruby/parser/StaticScope.java @@ -86,6 +86,8 @@ public class StaticScope implements Serializable, Cloneable { public static final int MAX_SPECIALIZED_SIZE = 50; private static final long serialVersionUID = 3423852552352498148L; + public static final int IMPLICIT = -2; + // Next immediate scope. Variable and constant scoping rules make use of this variable // in different ways. protected StaticScope enclosingScope; @@ -100,6 +102,8 @@ public class StaticScope implements Serializable, Cloneable { // as key to Symbol table for actual encoded versions]. private String[] variableNames; + private BitSet implicitVariables; + private int variableNamesLength; // Arity of this scope if there is one @@ -260,6 +264,36 @@ public int addVariableThisScope(String name) { if (slot >= 0) return slot; + // Clear constructor since we are adding a name + return addVariableName(name); + } + + /** + * Add an implicit variable ("it", "_1") to this (current) scope unless it is already defined in the + * current scope. The variable will be marked as implicit to omit it from local variable lists and functions. + * + * @param name of new variable + * @return index of variable + */ + public int addImplicitVariableThisScope(String name) { + int slot = exists(name); + + if (slot >= 0) return slot; + + slot = addVariableName(name); + + markImplicitVariable(slot); + + return slot; + } + + public void markImplicitVariable(int slot) { + if (implicitVariables == null) implicitVariables = new BitSet(); + + implicitVariables.set(slot); + } + + private int addVariableName(String name) { // Clear constructor since we are adding a name constructor = null; @@ -283,13 +317,7 @@ public int addVariable(String name) { if (slot >= 0) return slot; // Clear constructor since we are adding a name - constructor = null; - - // This is perhaps innefficient timewise? Optimal spacewise - growVariableNames(name); - - // Returns slot of variable - return variableNames.length - 1; + return addVariableName(name); } public String[] getVariables() { @@ -396,6 +424,18 @@ public int exists(String name) { return findVariableName(name); } + /** + * Does the variable exist and not implicit? + * + * @param name of the variable to find + * @return index of variable; -1 if it does not exist; -2 if is implicit. + */ + public int existsAndNotImplicit(String name) { + int slot = findVariableName(name); + if (slot >= 0 && isImplicitVariable(slot)) return IMPLICIT; + return slot; + } + private int findVariableName(String name) { for (int i = 0; i < variableNames.length; i++) { if (name.equals(variableNames[i])) return i; @@ -414,6 +454,24 @@ public int isDefined(String name) { return isDefined(name, 0); } + /** + * Is this name visible to the current scope and not an implicit variable ("it", "_1", etc). + * + * @param name to be looked for + * @return -1 if it is not defined; -2 if it is implicit; or a location where the left-most 16 bits of number of scopes down it is and the + * right-most 16 bits represents its index in that scope. + */ + public int isDefinedNotImplicit(String name) { + return isDefinedNotImplicit(name, 0); + } + + /** + * @return whether the given slot contains an implicit variable ("it", "_1", etc). + */ + public boolean isImplicitVariable(int slot) { + return implicitVariables != null && implicitVariables.get(slot); + } + /** * Make a DASgn or LocalAsgn node based on scope logic * @@ -496,20 +554,27 @@ public T collectVariables(ThreadContext context, BiFunction dedup = new HashMap<>(); while (current.isBlockOrEval) { - for (String name : current.variableNames) { - dedup.computeIfAbsent(name, key -> {collectionPopulator.accept(collection, key); return key;}); - } + addVariableNamesToCollection(current, collectionPopulator, dedup, collection); current = current.enclosingScope; } // once more for method scope - for (String name : current.variableNames) { - dedup.computeIfAbsent(name, key -> {collectionPopulator.accept(collection, key); return key;}); - } + addVariableNamesToCollection(current, collectionPopulator, dedup, collection); return collection; } + private static void addVariableNamesToCollection(StaticScope current, BiConsumer collectionPopulator, HashMap dedup, T collection) { + BitSet implicitVariables = current.implicitVariables; + for (int i = 0; i < current.variableNamesLength; i++) { + if (implicitVariables != null && implicitVariables.get(i)) continue; + + String name = current.variableNames[i]; + dedup.computeIfAbsent(name, key -> { + collectionPopulator.accept(collection, key); return key;}); + } + } + @Deprecated(since = "10.0.0.0") public RubyArray getLocalVariables(Ruby runtime) { return getLocalVariables(runtime.getCurrentContext()); @@ -542,6 +607,19 @@ public int isDefined(String name, int depth) { } } + public int isDefinedNotImplicit(String name, int depth) { + int slot = existsAndNotImplicit(name); + if (slot == IMPLICIT) return slot; + + if (isBlockOrEval) { + if (slot >= 0) return (depth << 16) | slot; + + return enclosingScope.isDefinedNotImplicit(name, depth + 1); + } else { + return (depth << 16) | slot; + } + } + public AssignableNode addAssign(int line, RubySymbol symbolID, Node value) { int slot = addVariable(symbolID.idString()); // No bit math to store level since we know level is zero for this case From dc7c280353f9eddc60a2d24fab3a66f21819e502 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 17 Nov 2025 23:37:07 -0600 Subject: [PATCH 031/168] Selective inspect of instance variables For Ruby 4.0 compatibility. See https://github.com/ruby/ruby/pull/13555 See https://github.com/jruby/jruby/issues/9061 --- .../main/java/org/jruby/RubyBasicObject.java | 76 ++++++++++++++----- .../jruby/javasupport/JavaProxyMethods.java | 2 +- .../java/org/jruby/runtime/JavaSites.java | 1 + 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyBasicObject.java b/core/src/main/java/org/jruby/RubyBasicObject.java index 6b9248de9d5..3f447c189ef 100644 --- a/core/src/main/java/org/jruby/RubyBasicObject.java +++ b/core/src/main/java/org/jruby/RubyBasicObject.java @@ -1127,13 +1127,20 @@ public IRubyObject inspect() { // MRI: rb_obj_inspect public IRubyObject inspect(ThreadContext context) { return !isImmediate() && !(this instanceof RubyModule) && hasVariables() ? - hashyInspect() : to_s(context); + hashyInspect(context) : to_s(context); } + @Deprecated(since = "10.1.0.0") public final IRubyObject hashyInspect() { - final Ruby runtime = getRuntime(); + return hashyInspect(getRuntime().getCurrentContext()); + } + + public final IRubyObject hashyInspect(ThreadContext context) { + IRubyObject ivars = Helpers.invokeChecked(context, this, sites(context).instance_variables_to_inspect_checked); - RubyString part = inspectPrefix(runtime.getCurrentContext(), metaClass.getRealClass(), inspectHashCode()); + RubyString part = inspectPrefix(context, metaClass.getRealClass(), inspectHashCode()); + + Ruby runtime = context.runtime; if (runtime.isInspecting(this)) { encStrBufCat(runtime, part, SPACE_DOT_DOT_DOT_GT); @@ -1141,7 +1148,7 @@ public final IRubyObject hashyInspect() { } try { runtime.registerInspecting(this); - return inspectObj(runtime, part); + return inspectObj(context, part, ivars); } finally { runtime.unregisterInspecting(this); } @@ -1182,31 +1189,58 @@ protected int inspectHashCode() { * The internal helper method that takes care of the part of the * inspection that inspects instance variables. */ - private RubyString inspectObj(final Ruby runtime, RubyString part) { - final ThreadContext context = runtime.getCurrentContext(); + private RubyString inspectObj(final ThreadContext context, RubyString part, IRubyObject ivars) { + Ruby runtime = context.runtime; + + if (ivars == null) { + // no ivars specified, do all of them + boolean first = true; + for (Map.Entry entry : metaClass.getVariableTableManager().getVariableAccessorsForRead().entrySet()) { + VariableAccessor accessor = entry.getValue(); + String name = entry.getKey(); - boolean first = true; - for (Map.Entry entry : metaClass.getVariableTableManager().getVariableAccessorsForRead().entrySet()) { - Object value = entry.getValue().get(this); - if (!(value instanceof IRubyObject)) continue; - RubySymbol symbol = asSymbol(context, entry.getKey()); - if (!symbol.validInstanceVariableName()) continue; + if (appendInstanceVariable(context, part, accessor, name, first, runtime)) continue; - IRubyObject obj = (IRubyObject) value; + first = false; + } + } else if (!ivars.isNil()) { + // ivars specified, do only those + RubyArray ivarsAry = Convert.castAsArray(context, ivars); - if (!first) encStrBufCat(runtime, part, COMMA); - encStrBufCat(runtime, part, SPACE); - // FIXME: bytelist_love: EPICLY wrong but something in MRI gets around identifiers of arbitrary encoding. - encStrBufCat(runtime, part, symbol.asString().encode(context, encodingService(context).convertEncodingToRubyEncoding(part.getEncoding())).asString().getByteList()); - encStrBufCat(runtime, part, EQUALS); - encStrBufCat(runtime, part, sites(context).inspect.call(context, obj, obj).convertToString().getByteList()); + boolean first = true; + Map accessors = metaClass.getVariableTableManager().getVariableAccessorsForRead(); + for (int i = 0; i < ivarsAry.size(); i++) { + String name = ivarsAry.eltOk(i).toString(); + VariableAccessor accessor = accessors.get(name); + if (accessor == null) continue; + + if (appendInstanceVariable(context, part, accessor, name, first, runtime)) continue; + + first = false; + } + } // else ivars was provided and is nil, so do none - first = false; - } encStrBufCat(runtime, part, GT); return part; } + private boolean appendInstanceVariable(ThreadContext context, RubyString part, VariableAccessor accessor, String name, boolean first, Ruby runtime) { + Object value = accessor.get(this); + if (!(value instanceof IRubyObject)) return true; + RubySymbol symbol = asSymbol(context, name); + if (!symbol.validInstanceVariableName()) return true; + + IRubyObject obj = (IRubyObject) value; + + if (!first) encStrBufCat(runtime, part, COMMA); + encStrBufCat(runtime, part, SPACE); + // FIXME: bytelist_love: EPICLY wrong but something in MRI gets around identifiers of arbitrary encoding. + encStrBufCat(runtime, part, symbol.asString().encode(context, encodingService(context).convertEncodingToRubyEncoding(part.getEncoding())).asString().getByteList()); + encStrBufCat(runtime, part, EQUALS); + encStrBufCat(runtime, part, sites(context).inspect.call(context, obj, obj).convertToString().getByteList()); + return false; + } + // Methods of the Object class (rb_obj_*): diff --git a/core/src/main/java/org/jruby/javasupport/JavaProxyMethods.java b/core/src/main/java/org/jruby/javasupport/JavaProxyMethods.java index b6f735e781c..d945eaf4cc2 100644 --- a/core/src/main/java/org/jruby/javasupport/JavaProxyMethods.java +++ b/core/src/main/java/org/jruby/javasupport/JavaProxyMethods.java @@ -130,7 +130,7 @@ public static IRubyObject inspect(IRubyObject recv) { @JRubyMethod public static IRubyObject inspect(ThreadContext context, IRubyObject recv) { - return recv instanceof RubyBasicObject basic ? basic.hashyInspect() : recv.inspect(context); + return recv instanceof RubyBasicObject basic ? basic.hashyInspect(context) : recv.inspect(context); } @JRubyMethod diff --git a/core/src/main/java/org/jruby/runtime/JavaSites.java b/core/src/main/java/org/jruby/runtime/JavaSites.java index 557454bca93..c6660eee963 100644 --- a/core/src/main/java/org/jruby/runtime/JavaSites.java +++ b/core/src/main/java/org/jruby/runtime/JavaSites.java @@ -77,6 +77,7 @@ public static class BasicObjectSites { public final CallSite match = new FunctionalCachingCallSite("=~"); public final CallSite call = new FunctionalCachingCallSite("call"); public final CallSite op_equal = new FunctionalCachingCallSite("=="); + public final CheckedSites instance_variables_to_inspect_checked = new CheckedSites("instance_variables_to_inspect"); } public static class ObjectSites { From 3eb70db9edbcadc22e02a65744aac540e048ce47 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 11:06:57 -0600 Subject: [PATCH 032/168] Another gem update for Ruby 4.0 --- lib/pom.rb | 14 +++++++------- lib/pom.xml | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/pom.rb b/lib/pom.rb index e497243f435..0e762733af7 100644 --- a/lib/pom.rb +++ b/lib/pom.rb @@ -32,7 +32,7 @@ def log(message = nil) ['digest', '3.2.1'], ['english', '0.8.1'], # Ongoing discussion about the -java gem, since it just omits the ext: https://github.com/ruby/erb/issues/52 - ['erb', '5.1.3'], + ['erb', '6.0.0'], ['error_highlight', '0.7.0'], # https://github.com/ruby/etc/issues/19 # ['etc', '1.4.6'], @@ -50,7 +50,7 @@ def log(message = nil) ['jar-dependencies', '0.5.4'], ['jruby-readline', '1.3.7'], ['jruby-openssl', '0.15.4'], - ['json', '2.15.2'], + ['json', '2.16.0'], ['net-http', '0.7.0'], ['net-protocol', '0.2.2'], ['open-uri', '0.5.0'], @@ -112,7 +112,7 @@ def log(message = nil) ['irb', '1.15.3'], ['logger', '1.7.0'], ['matrix', '0.4.3'], - ['minitest', '5.26.0'], + ['minitest', '5.26.1'], ['mutex_m', '0.3.0'], ['net-ftp', '0.3.9'], ['net-imap', '0.5.12'], @@ -121,7 +121,7 @@ def log(message = nil) ['nkf', '0.2.0'], ['observer', '0.1.2'], ['ostruct', '0.6.3'], - ['power_assert', '3.0.0'], + ['power_assert', '3.0.1'], ['prime', '0.1.4'], ['pstore', '0.2.0'], ['racc', '1.8.1'], @@ -136,7 +136,7 @@ def log(message = nil) # ['readline', '0.0.4'], # Will be solved with readline # ['readline-ext', '0.2.0'], - ['reline', '0.6.2'], + ['reline', '0.6.3'], # Depends on prism gem with native ext # ['repl_type_completer', '0.1.12'], ['resolv-replace', '0.1.1'], @@ -145,9 +145,9 @@ def log(message = nil) ['rss', '0.3.1'], # https://github.com/ruby/syslog/issues/1 # ['syslog', '0.3.0'], - ['test-unit', '3.7.0'] + ['test-unit', '3.7.1'] # Depends on many CRuby internals - # ['typeprof', '0.30.1'], + # ['typeprof', '0.31.0'], ] project 'JRuby Lib Setup' do diff --git a/lib/pom.xml b/lib/pom.xml index 00c730e81d7..44ba0f459e5 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -138,7 +138,7 @@ DO NOT MODIFY - GENERATED CODE rubygems erb - 5.1.3 + 6.0.0 gem provided @@ -294,7 +294,7 @@ DO NOT MODIFY - GENERATED CODE rubygems json - 2.15.2 + 2.16.0 gem provided @@ -840,7 +840,7 @@ DO NOT MODIFY - GENERATED CODE rubygems minitest - 5.26.0 + 5.26.1 gem provided @@ -957,7 +957,7 @@ DO NOT MODIFY - GENERATED CODE rubygems power_assert - 3.0.0 + 3.0.1 gem provided @@ -1035,7 +1035,7 @@ DO NOT MODIFY - GENERATED CODE rubygems reline - 0.6.2 + 0.6.3 gem provided @@ -1100,7 +1100,7 @@ DO NOT MODIFY - GENERATED CODE rubygems test-unit - 3.7.0 + 3.7.1 gem provided @@ -1139,7 +1139,7 @@ DO NOT MODIFY - GENERATED CODE specifications/did_you_mean-2.0.0* specifications/digest-3.2.1* specifications/english-0.8.1* - specifications/erb-5.1.3* + specifications/erb-6.0.0* specifications/error_highlight-0.7.0* specifications/ffi-1.17.0* specifications/fileutils-1.8.0* @@ -1151,7 +1151,7 @@ DO NOT MODIFY - GENERATED CODE specifications/jar-dependencies-0.5.4* specifications/jruby-readline-1.3.7* specifications/jruby-openssl-0.15.4* - specifications/json-2.15.2* + specifications/json-2.16.0* specifications/net-http-0.7.0* specifications/net-protocol-0.2.2* specifications/open-uri-0.5.0* @@ -1193,7 +1193,7 @@ DO NOT MODIFY - GENERATED CODE specifications/irb-1.15.3* specifications/logger-1.7.0* specifications/matrix-0.4.3* - specifications/minitest-5.26.0* + specifications/minitest-5.26.1* specifications/mutex_m-0.3.0* specifications/net-ftp-0.3.9* specifications/net-imap-0.5.12* @@ -1202,18 +1202,18 @@ DO NOT MODIFY - GENERATED CODE specifications/nkf-0.2.0* specifications/observer-0.1.2* specifications/ostruct-0.6.3* - specifications/power_assert-3.0.0* + specifications/power_assert-3.0.1* specifications/prime-0.1.4* specifications/pstore-0.2.0* specifications/racc-1.8.1* specifications/rake-${rake.version}* specifications/rdoc-6.15.1* - specifications/reline-0.6.2* + specifications/reline-0.6.3* specifications/resolv-replace-0.1.1* specifications/rexml-3.4.4* specifications/rinda-0.2.0* specifications/rss-0.3.1* - specifications/test-unit-3.7.0* + specifications/test-unit-3.7.1* gems/rubygems-update-3.6.9*/**/* gems/bundler-2.6.9*/**/* gems/cgi-0.4.2*/**/* @@ -1222,7 +1222,7 @@ DO NOT MODIFY - GENERATED CODE gems/did_you_mean-2.0.0*/**/* gems/digest-3.2.1*/**/* gems/english-0.8.1*/**/* - gems/erb-5.1.3*/**/* + gems/erb-6.0.0*/**/* gems/error_highlight-0.7.0*/**/* gems/ffi-1.17.0*/**/* gems/fileutils-1.8.0*/**/* @@ -1234,7 +1234,7 @@ DO NOT MODIFY - GENERATED CODE gems/jar-dependencies-0.5.4*/**/* gems/jruby-readline-1.3.7*/**/* gems/jruby-openssl-0.15.4*/**/* - gems/json-2.15.2*/**/* + gems/json-2.16.0*/**/* gems/net-http-0.7.0*/**/* gems/net-protocol-0.2.2*/**/* gems/open-uri-0.5.0*/**/* @@ -1276,7 +1276,7 @@ DO NOT MODIFY - GENERATED CODE gems/irb-1.15.3*/**/* gems/logger-1.7.0*/**/* gems/matrix-0.4.3*/**/* - gems/minitest-5.26.0*/**/* + gems/minitest-5.26.1*/**/* gems/mutex_m-0.3.0*/**/* gems/net-ftp-0.3.9*/**/* gems/net-imap-0.5.12*/**/* @@ -1285,18 +1285,18 @@ DO NOT MODIFY - GENERATED CODE gems/nkf-0.2.0*/**/* gems/observer-0.1.2*/**/* gems/ostruct-0.6.3*/**/* - gems/power_assert-3.0.0*/**/* + gems/power_assert-3.0.1*/**/* gems/prime-0.1.4*/**/* gems/pstore-0.2.0*/**/* gems/racc-1.8.1*/**/* gems/rake-${rake.version}*/**/* gems/rdoc-6.15.1*/**/* - gems/reline-0.6.2*/**/* + gems/reline-0.6.3*/**/* gems/resolv-replace-0.1.1*/**/* gems/rexml-3.4.4*/**/* gems/rinda-0.2.0*/**/* gems/rss-0.3.1*/**/* - gems/test-unit-3.7.0*/**/* + gems/test-unit-3.7.1*/**/* cache/rubygems-update-3.6.9* cache/bundler-2.6.9* cache/cgi-0.4.2* @@ -1305,7 +1305,7 @@ DO NOT MODIFY - GENERATED CODE cache/did_you_mean-2.0.0* cache/digest-3.2.1* cache/english-0.8.1* - cache/erb-5.1.3* + cache/erb-6.0.0* cache/error_highlight-0.7.0* cache/ffi-1.17.0* cache/fileutils-1.8.0* @@ -1317,7 +1317,7 @@ DO NOT MODIFY - GENERATED CODE cache/jar-dependencies-0.5.4* cache/jruby-readline-1.3.7* cache/jruby-openssl-0.15.4* - cache/json-2.15.2* + cache/json-2.16.0* cache/net-http-0.7.0* cache/net-protocol-0.2.2* cache/open-uri-0.5.0* @@ -1359,7 +1359,7 @@ DO NOT MODIFY - GENERATED CODE cache/irb-1.15.3* cache/logger-1.7.0* cache/matrix-0.4.3* - cache/minitest-5.26.0* + cache/minitest-5.26.1* cache/mutex_m-0.3.0* cache/net-ftp-0.3.9* cache/net-imap-0.5.12* @@ -1368,18 +1368,18 @@ DO NOT MODIFY - GENERATED CODE cache/nkf-0.2.0* cache/observer-0.1.2* cache/ostruct-0.6.3* - cache/power_assert-3.0.0* + cache/power_assert-3.0.1* cache/prime-0.1.4* cache/pstore-0.2.0* cache/racc-1.8.1* cache/rake-${rake.version}* cache/rdoc-6.15.1* - cache/reline-0.6.2* + cache/reline-0.6.3* cache/resolv-replace-0.1.1* cache/rexml-3.4.4* cache/rinda-0.2.0* cache/rss-0.3.1* - cache/test-unit-3.7.0* + cache/test-unit-3.7.1* From 337a420f4a1e9a52ce6cba62f6fa0ec5ad67902d Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 11:13:01 -0600 Subject: [PATCH 033/168] Deprecate ObjectSpace._id2ref For Ruby 4.0 compatibility. See https://bugs.ruby-lang.org/issues/15408 --- core/src/main/java/org/jruby/RubyObjectSpace.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/org/jruby/RubyObjectSpace.java b/core/src/main/java/org/jruby/RubyObjectSpace.java index 6954131e928..6ba78da0324 100644 --- a/core/src/main/java/org/jruby/RubyObjectSpace.java +++ b/core/src/main/java/org/jruby/RubyObjectSpace.java @@ -42,6 +42,7 @@ import org.jruby.anno.JRubyMethod; import org.jruby.anno.JRubyModule; +import org.jruby.api.Warn; import org.jruby.exceptions.StopIteration; import org.jruby.javasupport.JavaPackage; import org.jruby.runtime.Arity; @@ -137,8 +138,11 @@ public static IRubyObject id2ref(IRubyObject recv, IRubyObject id) { return id2ref(((RubyBasicObject) recv).getCurrentContext(), recv, id); } + @Deprecated(since = "10.1.0.0") @JRubyMethod(name = "_id2ref", module = true, visibility = PRIVATE) public static IRubyObject id2ref(ThreadContext context, IRubyObject recv, IRubyObject id) { + Warn.warnDeprecated(context, "ObjectSpace._id2ref is deprecated"); + long longId = castAsFixnum(context, id).getValue(); if (longId == 0) return context.fals; if (longId == 20) return context.tru; From fd9f761e2efd3bae86e3a4db059609f9035d906b Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 11:29:08 -0600 Subject: [PATCH 034/168] Add Math.{expm1,log1p} For Ruby 4.0 compatibility. See https://bugs.ruby-lang.org/issues/21527 See https://github.com/jruby/jruby/issues/9061 --- core/src/main/java/org/jruby/RubyMath.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/src/main/java/org/jruby/RubyMath.java b/core/src/main/java/org/jruby/RubyMath.java index c55a2062d31..b4308f02a08 100644 --- a/core/src/main/java/org/jruby/RubyMath.java +++ b/core/src/main/java/org/jruby/RubyMath.java @@ -720,4 +720,20 @@ private static boolean negZero(final double x) { } } + + @JRubyMethod(module = true, visibility = Visibility.PRIVATE) + public static IRubyObject expm1(ThreadContext context, IRubyObject unused, IRubyObject x) { + return asFloat(context, Math.expm1(toDouble(context, x))); + } + + @JRubyMethod(module = true, visibility = Visibility.PRIVATE) + public static IRubyObject log1p(ThreadContext context, IRubyObject unused, IRubyObject x) { + double xDouble = toDouble(context, x); + + if (xDouble < -1.0) { + throw context.runtime.newMathDomainError("Numerical argument is out of domain - log1p"); + } + + return asFloat(context, Math.log1p(xDouble)); + } } From 5ec0f809ac131003adaf5a843ec094b9a0f86f3d Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 11:35:12 -0600 Subject: [PATCH 035/168] Remove deprecated open with pipe For Ruby 4.0 compatibility. See https://bugs.ruby-lang.org/issues/19630 See https://github.com/jruby/jruby/issues/9061 --- core/src/main/java/org/jruby/RubyIO.java | 9 --------- core/src/main/java/org/jruby/RubyKernel.java | 11 ----------- .../main/java/org/jruby/util/io/PopenExecutor.java | 1 + 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyIO.java b/core/src/main/java/org/jruby/RubyIO.java index 1811c39aa80..336e3b2f07d 100644 --- a/core/src/main/java/org/jruby/RubyIO.java +++ b/core/src/main/java/org/jruby/RubyIO.java @@ -4372,15 +4372,6 @@ static RubyIO ioOpen(ThreadContext context, IRubyObject recv, RubyString filenam private static RubyIO ioOpenGeneric(ThreadContext context, IRubyObject recv, IRubyObject filename, int oflags, int fmode, IOEncodable convconfig, int perm) { if ((filename instanceof RubyString name) && name.isEmpty()) throw context.runtime.newErrnoENOENTError(); - IRubyObject cmd; - if ((recv == ioClass(context)) && (cmd = PopenExecutor.checkPipeCommand(context, filename)) != context.nil) { - warnDeprecated(context, "IO process creation with a leading '|' is deprecated and will be removed in Ruby 4.0; use IO.popen instead"); - if (PopenExecutor.nativePopenAvailable(context.runtime)) { - return (RubyIO) PopenExecutor.pipeOpen(context, cmd, OpenFile.ioOflagsModestr(context, oflags), fmode, convconfig); - } else { - throw argumentError(context, "pipe open is not supported without native subprocess logic"); - } - } return (RubyIO) ((RubyFile) fileClass(context).allocate(context)). fileOpenGeneric(context, filename, oflags, fmode, convconfig, perm); } diff --git a/core/src/main/java/org/jruby/RubyKernel.java b/core/src/main/java/org/jruby/RubyKernel.java index 3b5314d27e5..b2b7c34dbf9 100644 --- a/core/src/main/java/org/jruby/RubyKernel.java +++ b/core/src/main/java/org/jruby/RubyKernel.java @@ -304,17 +304,6 @@ public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObj tmp = RubyFile.get_path(context, tmp); if (tmp == context.nil) { redirect = true; - } else { - IRubyObject cmd = PopenExecutor.checkPipeCommand(context, tmp); - if (cmd != context.nil) { - warnDeprecatedForRemovalAlternate(context, "Calling Kernel#open with a leading '|'", "4.0", "IO.popen"); - if (PopenExecutor.nativePopenAvailable(context.runtime)) { - args[0] = cmd; - return PopenExecutor.popen(context, args, ioClass(context), block); - } - - throw argumentError(context, "pipe open is not supported without native subprocess logic"); - } } } } diff --git a/core/src/main/java/org/jruby/util/io/PopenExecutor.java b/core/src/main/java/org/jruby/util/io/PopenExecutor.java index 3f90eceab34..69053e79beb 100644 --- a/core/src/main/java/org/jruby/util/io/PopenExecutor.java +++ b/core/src/main/java/org/jruby/util/io/PopenExecutor.java @@ -73,6 +73,7 @@ public static boolean nativePopenAvailable(Ruby runtime) { } // MRI: check_pipe_command + @Deprecated(since = "10.1.0.0", forRemoval = true) public static IRubyObject checkPipeCommand(ThreadContext context, IRubyObject filenameOrCommand) { RubyString filenameStr = filenameOrCommand.convertToString(); ByteList filenameByteList = filenameStr.getByteList(); From c7b56395293c2d61aede3d507131651b9f951ee5 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 11:57:35 -0600 Subject: [PATCH 036/168] Align IO.select timeout logic with CRuby Includes https://bugs.ruby-lang.org/issues/20610 for Ruby 4.0 compatibility. See https://github.com/jruby/jruby/issues/9061 --- core/src/main/java/org/jruby/RubyIO.java | 11 +----- core/src/main/java/org/jruby/RubyTime.java | 37 +++++++++++-------- .../java/org/jruby/runtime/JavaSites.java | 4 +- .../src/main/java/org/jruby/util/Numeric.java | 9 +++++ 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyIO.java b/core/src/main/java/org/jruby/RubyIO.java index 336e3b2f07d..cec47dd22e4 100644 --- a/core/src/main/java/org/jruby/RubyIO.java +++ b/core/src/main/java/org/jruby/RubyIO.java @@ -4129,18 +4129,11 @@ public static IRubyObject select(ThreadContext context, IRubyObject recv, IRubyO read = argv[0]; } final Long timeout; - if (_timeout.isNil()) { + if (_timeout.isNil() || Numeric.isPositiveInfinity(_timeout)) { timeout = null; } else { - try { // MRI calls to_f even if not respond_to? (or respond_to_missing?) :to_f - _timeout = sites(context).to_f.call(context, _timeout, _timeout); - } - catch (RaiseException e) { - TypeConverter.handleUncoercibleObject(context.runtime, _timeout, floatClass(context), true); - throw e; // won't happen - } - final double t = _timeout.convertToFloat().asDouble(context); + double t = RubyTime.convertTimeInterval(context, _timeout); if ( t < 0 ) throw argumentError(context, "negative timeout"); timeout = (long) (t * 1000); // ms } diff --git a/core/src/main/java/org/jruby/RubyTime.java b/core/src/main/java/org/jruby/RubyTime.java index d6a99b6450b..96a34dfd341 100644 --- a/core/src/main/java/org/jruby/RubyTime.java +++ b/core/src/main/java/org/jruby/RubyTime.java @@ -64,6 +64,7 @@ import org.jruby.util.RubyDateFormatter; import org.jruby.util.RubyTimeParser; import org.jruby.util.Sprintf; +import org.jruby.util.TypeConverter; import org.jruby.util.time.TimeArgs; import java.io.Serializable; @@ -2161,28 +2162,32 @@ public static double convertTimeInterval(ThreadContext context, IRubyObject sec) // NOTE: we can probably do better here, but we're matching MRI behavior // this is for converting custom objects such as ActiveSupport::Duration - else if ( sites(context).respond_to_divmod.respondsTo(context, sec, sec) ) { - IRubyObject result = sites(context).divmod.call(context, sec, sec, 1); - if (!(result instanceof RubyArray arr)) throw typeError(context, "unexpected divmod result: into ", result, ""); + else { + IRubyObject ary = Helpers.invokeChecked(context, sec, sites(context).divmod_checked, RubyFixnum.one(context.runtime)); + RubyArray arr; + if (ary != null && !(ary = TypeConverter.checkArrayType(context, sites(context).to_a_checked, ary)).isNil()) { + arr = (RubyArray) ary; + seconds = ((RubyNumeric) arr.eltOk(0)).asDouble(context) + // div + ((RubyNumeric) arr.eltOk(1)).asDouble(context); // mod + } else { + boolean raise = true; + seconds = 0; + + if (sec instanceof JavaProxy) { + try { // support java.lang.Number proxies + seconds = sec.convertToFloat().value; + raise = false; + } catch (TypeError ex) { /* fallback bellow to raising a TypeError */ } + } - seconds = ((RubyNumeric) arr.eltOk(0)).asDouble(context) + // div - ((RubyNumeric) arr.eltOk(1)).asDouble(context); // mod - } else { - boolean raise = true; - seconds = 0; - - if (sec instanceof JavaProxy) { - try { // support java.lang.Number proxies - seconds = sec.convertToFloat().value; - raise = false; - } catch (TypeError ex) { /* fallback bellow to raising a TypeError */ } + if (raise) throw typeError(context, "can't convert ", sec, " into time interval"); } - - if (raise) throw typeError(context, "can't convert ", sec, " into time interval"); } if (seconds < 0) throw argumentError(context,"time interval must not be negative"); + if (Double.isNaN(seconds)) throw rangeError(context, seconds + " out of Time range"); + return seconds; } diff --git a/core/src/main/java/org/jruby/runtime/JavaSites.java b/core/src/main/java/org/jruby/runtime/JavaSites.java index c6660eee963..57edbf2346e 100644 --- a/core/src/main/java/org/jruby/runtime/JavaSites.java +++ b/core/src/main/java/org/jruby/runtime/JavaSites.java @@ -334,8 +334,8 @@ public static class TimeSites { public final CachingCallSite to_r = new FunctionalCachingCallSite("to_r"); public final CheckedSites checked_to_r = new CheckedSites("to_r"); - public final RespondToCallSite respond_to_divmod = new RespondToCallSite("divmod"); - public final CachingCallSite divmod = new FunctionalCachingCallSite("divmod"); + public final CheckedSites divmod_checked = new CheckedSites("divmod"); + public final CheckedSites to_a_checked = new CheckedSites("to_a"); } public static class EnumerableSites { diff --git a/core/src/main/java/org/jruby/util/Numeric.java b/core/src/main/java/org/jruby/util/Numeric.java index 31f16061ba1..0fa3e694e72 100644 --- a/core/src/main/java/org/jruby/util/Numeric.java +++ b/core/src/main/java/org/jruby/util/Numeric.java @@ -938,6 +938,15 @@ public static boolean f_eqeq_p(ThreadContext context, IRubyObject x, IRubyObject return x.op_eqq(context, y).isTrue(); } + // MRI: is_pos_inf + public static boolean isPositiveInfinity(IRubyObject x) { + double f; + if (!(x instanceof RubyFloat flote)) + return false; + f = flote.getValue(); + return Double.isInfinite(f) && 0 < f; + } + @Deprecated(since = "10.0.0.0") public static void checkInteger(ThreadContext context, IRubyObject obj) { if (!(obj instanceof RubyInteger)) throw typeError(context, "not an integer"); From 51c0e4b3f772c8d407ef85c931d2cb57faba21c1 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 18 Nov 2025 14:37:13 -0600 Subject: [PATCH 037/168] Mild refactoring of RubyKernel.raise * Modify to throw from a single place, to allow pulling out more of exception-juggling logic. * Modify Java exception unwrapping to return the throwable rather than throw immediately. --- core/src/main/java/org/jruby/RubyKernel.java | 117 ++++++++++++------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyKernel.java b/core/src/main/java/org/jruby/RubyKernel.java index b2b7c34dbf9..870ebde2a5c 100644 --- a/core/src/main/java/org/jruby/RubyKernel.java +++ b/core/src/main/java/org/jruby/RubyKernel.java @@ -1037,18 +1037,23 @@ public static IRubyObject raise(ThreadContext context, IRubyObject self, IRubyOb IRubyObject cause = context.getErrorInfo(); // returns nil for no error-info - maybeRaiseJavaException(context, arg0); + Throwable throwable = unwrapJavaException(context, arg0); - RaiseException raise = arg0 instanceof RubyString ? - ((RubyException) context.runtime.getRuntimeError().newInstance(context, arg0)).toThrowable() : - convertToException(context, arg0, null).toThrowable(); + if (throwable == null) { + RaiseException raise = arg0 instanceof RubyString ? + ((RubyException) context.runtime.getRuntimeError().newInstance(context, arg0)).toThrowable() : + convertToException(context, arg0, null).toThrowable(); - var exception = raise.getException(); + var exception = raise.getException(); - if (context.runtime.isDebug()) printExceptionSummary(context, exception); - if (exception.getCause() == null && cause != exception) exception.setCause(cause); + if (context.runtime.isDebug()) printExceptionSummary(context, exception); + if (exception.getCause() == null && cause != exception) exception.setCause(cause); - throw raise; + throwable = raise; + } + + Helpers.throwException(throwable); + return null; // not reached } @JRubyMethod(name = {"raise", "fail"}, optional = 3, checkArity = false, module = true, visibility = PRIVATE, omit = true) @@ -1077,44 +1082,58 @@ public static IRubyObject raise(ThreadContext context, IRubyObject recv, IRubyOb if ( cause == null ) cause = context.getErrorInfo(); // returns nil for no error-info } - maybeRaiseJavaException(context, args, argc); + Throwable throwable = unwrapJavaException(context, args, argc); - RaiseException raise; - switch (argc) { - case 0: - IRubyObject lastException = globalVariables(context).get("$!"); - if (lastException.isNil()) { - raise = RaiseException.from(context.runtime, runtimeErrorClass(context), ""); - } else { - // non RubyException value is allowed to be assigned as $!. - raise = ((RubyException) lastException).toThrowable(); - } - break; - case 1: - if (args[0] instanceof RubyString) { - raise = ((RubyException) runtimeErrorClass(context).newInstance(context, args, block)).toThrowable(); - } else { - raise = convertToException(context, args[0], null).toThrowable(); - } - break; - case 2: - raise = convertToException(context, args[0], args[1]).toThrowable(); - break; - default: - RubyException exception = convertToException(context, args[0], args[1]); - exception.setBacktrace(context, args[2]); - raise = exception.toThrowable(); - break; - } + if (throwable == null) { + RaiseException raise; + switch (argc) { + case 0: + IRubyObject lastException = globalVariables(context).get("$!"); + if (lastException.isNil()) { + raise = RaiseException.from(context.runtime, runtimeErrorClass(context), ""); + } else { + // non RubyException value is allowed to be assigned as $!. + raise = ((RubyException) lastException).toThrowable(); + } + break; + case 1: + if (args[0] instanceof RubyString) { + raise = ((RubyException) runtimeErrorClass(context).newInstance(context, args, block)).toThrowable(); + } else { + raise = convertToException(context, args[0], null).toThrowable(); + } + break; + case 2: + raise = convertToException(context, args[0], args[1]).toThrowable(); + break; + default: + RubyException exception = convertToException(context, args[0], args[1]); + exception.setBacktrace(context, args[2]); + raise = exception.toThrowable(); + break; + } + + var exception = raise.getException(); + if (context.runtime.isDebug()) printExceptionSummary(context, exception); + if (forceCause || argc > 0 && exception.getCause() == null && cause != exception) exception.setCause(cause); - var exception = raise.getException(); - if (context.runtime.isDebug()) printExceptionSummary(context, exception); - if (forceCause || argc > 0 && exception.getCause() == null && cause != exception) exception.setCause(cause); + throwable = raise; + } - throw raise; + Helpers.throwException(throwable); + return null; // not reached } - private static void maybeRaiseJavaException(ThreadContext context, final IRubyObject[] args, final int argc) { + /** + * Locate and unwrap (if possible) a Java exception to be raised. This path will check for a Java exception in $! + * (if no arguments are passed) or as the first argument (if one argument is passed). + * + * @param context the current context + * @param args the arguments passed to Kernel#raise + * @param argc the count of arguments passed to Kernel#raise + * @return a Java Throwable for the unwrapped exception, or null if no Java exception is available + */ + private static Throwable unwrapJavaException(ThreadContext context, final IRubyObject[] args, final int argc) { // Check for a Java exception IRubyObject maybeException = null; switch (argc) { @@ -1126,10 +1145,18 @@ private static void maybeRaiseJavaException(ThreadContext context, final IRubyOb break; } - maybeRaiseJavaException(context, maybeException); + return unwrapJavaException(context, maybeException); } - private static void maybeRaiseJavaException(ThreadContext context, final IRubyObject arg) { + /** + * Unwrap (if possible) a Java exception to be raised from the given argument. If the argument does not wrap a Java + * exception, this will return null. + * + * @param context the current context + * @param arg the argument to unwrap, if it is a Java exception + * @return a Java Throwable for the unwrapped exception, or null if no Java exception is available + */ + private static Throwable unwrapJavaException(ThreadContext context, final IRubyObject arg) { // Check for a Java exception if (arg instanceof ConcreteJavaProxy) { // looks like someone's trying to raise a Java exception. Let them. @@ -1138,9 +1165,9 @@ private static void maybeRaiseJavaException(ThreadContext context, final IRubyOb if (!(maybeThrowable instanceof Throwable)) throw typeError(context, "can't raise a non-Throwable Java object"); final Throwable ex = (Throwable) maybeThrowable; - Helpers.throwException(ex); - return; // not reached + return ex; // not reached } + return null; } private static RubyException convertToException(ThreadContext context, IRubyObject obj, IRubyObject optionalMessage) { From 0fe5c5596c32250d1457ba22a55b3ede67d9e7aa Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 19 Nov 2025 16:14:33 -0600 Subject: [PATCH 038/168] Implement cause: for Thread#raise and Fiber#raise Mostly green but a few odd edge cases remain. --- core/src/main/java/org/jruby/RubyThread.java | 88 +++++++++++++------ .../java/org/jruby/ext/fiber/ThreadFiber.java | 2 +- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyThread.java b/core/src/main/java/org/jruby/RubyThread.java index 2afc0d84aac..5685b2f0442 100644 --- a/core/src/main/java/org/jruby/RubyThread.java +++ b/core/src/main/java/org/jruby/RubyThread.java @@ -1492,9 +1492,9 @@ public final IRubyObject raise(IRubyObject exception, RubyString message) { return genericRaise(context, context.getThread(), exception, message); } - @JRubyMethod(optional = 3, checkArity = false) + @JRubyMethod(optional = 4, checkArity = false) public IRubyObject raise(ThreadContext context, IRubyObject[] args, Block block) { - Arity.checkArgumentCount(context, args, 0, 3); + Arity.checkArgumentCount(context, args, 0, 4); return genericRaise(context, context.getThread(), args); } @@ -1536,42 +1536,80 @@ private IRubyObject genericRaise(ThreadContext context, RubyThread currentThread public static IRubyObject prepareRaiseException(ThreadContext context, IRubyObject[] args) { IRubyObject errorInfo = context.getErrorInfo(); - if (args.length == 0) { - if (errorInfo.isNil()) { - // We force RaiseException here to populate backtrace - return RaiseException.from(context.runtime, runtimeErrorClass(context), "").getException(); + int argc = Arity.checkArgumentCount(context, args, 0, 4); + IRubyObject tmp = context.nil; + + // semi extract_raise_opts, duplicated from RubyKernel.raise + IRubyObject cause = null; + if (argc > 0) { + IRubyObject last = args[argc - 1]; + if (last instanceof RubyHash opt) { + RubySymbol key; + if (!opt.isEmpty() && (opt.has_key_p(context, key = asSymbol(context, "cause")) == context.tru)) { + cause = opt.delete(context, key, Block.NULL_BLOCK); + if (opt.isEmpty() && --argc == 0) { // more opts will be passed along + throw argumentError(context, "only cause is given with no arguments"); + } + } } - return errorInfo; } - final IRubyObject arg = args[0]; + switch (argc) { + case 0: + if (errorInfo.isNil()) { + // We force RaiseException here to populate backtrace + tmp = RaiseException.from(context.runtime, runtimeErrorClass(context), "").getException(); + } else if (errorInfo instanceof ConcreteJavaProxy) { + return errorInfo; + } else { + tmp = errorInfo; + } + break; + case 1: { + final IRubyObject arg = args[0]; - IRubyObject tmp; - final RubyException exception; - if (args.length == 1) { - if (arg instanceof RubyString) { - tmp = runtimeErrorClass(context).newInstance(context, args, Block.NULL_BLOCK); - } else if (arg instanceof ConcreteJavaProxy ) { - return arg; - } else { - if (!arg.respondsTo("exception")) throw typeError(context, "exception class/object expected"); - tmp = arg.callMethod(context, "exception"); + if (arg instanceof RubyString) { + tmp = runtimeErrorClass(context).newInstance(context, arg); + } else if (arg instanceof ConcreteJavaProxy) { + tmp = arg; + } else { + if (!arg.respondsTo("exception")) throw typeError(context, "exception class/object expected"); + tmp = arg.callMethod(context, "exception"); + } } - } else { - if (!arg.respondsTo("exception")) throw typeError(context, "exception class/object expected"); - tmp = arg.callMethod(context, "exception", args[1]); + break; + + case 2: + case 3: { + final IRubyObject arg0 = args[0]; + final IRubyObject message = args[1]; + if (!arg0.respondsTo("exception")) throw typeError(context, "exception class/object expected"); + tmp = arg0.callMethod(context, "exception", message); + } + break; } + if (!exceptionClass(context).isInstance(tmp)) throw typeError(context, "exception object expected"); - exception = (RubyException) tmp; + RubyException exception = (RubyException) tmp; - if (args.length == 3) { + if (argc == 3) { exception.set_backtrace(context, args[2]); } - IRubyObject cause = errorInfo; - if (cause != exception) { + if (cause == null) { + cause = errorInfo; + } + + if (!cause.isNil() && cause != exception) { + // check for circular causes + for (Object cur = cause; cur instanceof RubyException curException && cur != null;) { + if (cur == exception) { + throw argumentError(context, "circular causes"); + } + cur = curException.getCause(); + } exception.setCause(cause); } diff --git a/core/src/main/java/org/jruby/ext/fiber/ThreadFiber.java b/core/src/main/java/org/jruby/ext/fiber/ThreadFiber.java index 84c15addc5f..33886a71db0 100644 --- a/core/src/main/java/org/jruby/ext/fiber/ThreadFiber.java +++ b/core/src/main/java/org/jruby/ext/fiber/ThreadFiber.java @@ -227,7 +227,7 @@ private static void fiberCalledAcrossThreads(Ruby runtime) { throw runtime.newFiberError("fiber called across threads"); } - @JRubyMethod(rest = true) + @JRubyMethod(optional = 4) public IRubyObject raise(ThreadContext context, IRubyObject[] args) { return raise(context, RubyThread.prepareRaiseException(context, args)); } From 8bbc0d54da66219f08218935871d710d8e060515 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 30 Dec 2025 16:26:44 -0600 Subject: [PATCH 039/168] Add backward compat for Set subclasses See https://github.com/ruby/ruby/pull/15228 --- .../main/java/org/jruby/ext/set/RubySet.java | 22 +- lib/ruby/stdlib/set/subclass_compatible.rb | 353 ++++++++++++++++++ 2 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 lib/ruby/stdlib/set/subclass_compatible.rb diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index 82d6f7d2285..c82347fbd59 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -77,8 +77,12 @@ public static RubyClass createSetClass(ThreadContext context, RubyClass Object, RubyClass Set = defineClass(context, "Set", Object, RubySet::new). reifiedClass(RubySet.class). include(context, Enumerable). - defineMethods(context, RubySet.class). - tap(c -> c.marshalWith(new SetMarshal(c.getMarshal()))); + tap((RubyClass c) -> c.marshalWith(new SetMarshal(c.getMarshal()))); + + // Define CoreSet before defining inherited (and other methods) on Set + Set.defineClassUnder(context, "CoreSet", Set, RubySet::new); + + Set.defineMethods(context, RubySet.class); Enumerable.defineMethods(context, EnumerableExt.class); @@ -1173,6 +1177,20 @@ private void inspectSet(final ThreadContext context, final RubyString str) { // return pp.callMethod(context, "text", str); // pp.text ... //} + @JRubyMethod(meta = true) + public static IRubyObject inherited(ThreadContext context, IRubyObject klass, IRubyObject subclass) { + Ruby runtime = context.runtime; + if (klass == runtime.getSet()) { + // When subclassing directly from Set, include the compatibility layer + runtime.getLoadService().require("set/subclass_compatible.rb"); + RubyModule SubclassCompatible = (RubyModule) ((RubyClass) klass).getConstant(context, "SubclassCompatible"); + RubyModule subklass = (RubyModule) subclass; + subklass.includeModule(SubclassCompatible); + ((RubyModule) SubclassCompatible.getConstant(context, "ClassMethods")).extend_object(context, subklass); + } + return context.nil; + } + protected final Set elements() { return hash.directKeySet(); // Hash view -> no copying } diff --git a/lib/ruby/stdlib/set/subclass_compatible.rb b/lib/ruby/stdlib/set/subclass_compatible.rb new file mode 100644 index 00000000000..ab0aedc0e5b --- /dev/null +++ b/lib/ruby/stdlib/set/subclass_compatible.rb @@ -0,0 +1,353 @@ +# frozen_string_literal: true + +# :markup: markdown +# +# set/subclass_compatible.rb - Provides compatibility for set subclasses +# +# Copyright (c) 2002-2024 Akinori MUSHA +# +# Documentation by Akinori MUSHA and Gavin Sinclair. +# +# All rights reserved. You can redistribute and/or modify it under the same +# terms as Ruby. + + +class Set + # This module is automatically included in subclasses of Set, to + # make them backwards compatible with the pure-Ruby set implementation + # used before Ruby 4. Users who want to use Set subclasses without + # this compatibility layer should subclass from Set::CoreSet. + # + # Note that Set subclasses that access `@hash` are not compatible even + # with this support. Such subclasses must be updated to support Ruby 4. + module SubclassCompatible + module ClassMethods + def [](*ary) + new(ary) + end + end + + # Creates a new set containing the elements of the given enumerable + # object. + # + # If a block is given, the elements of enum are preprocessed by the + # given block. + # + # Set.new([1, 2]) #=> # + # Set.new([1, 2, 1]) #=> # + # Set.new([1, 'c', :s]) #=> # + # Set.new(1..5) #=> # + # Set.new([1, 2, 3]) { |x| x * x } #=> # + def initialize(enum = nil, &block) # :yields: o + enum.nil? and return + + if block + do_with_enum(enum) { |o| add(block[o]) } + else + merge(enum) + end + end + + def do_with_enum(enum, &block) # :nodoc: + if enum.respond_to?(:each_entry) + enum.each_entry(&block) if block + elsif enum.respond_to?(:each) + enum.each(&block) if block + else + raise ArgumentError, "value must be enumerable" + end + end + private :do_with_enum + + def replace(enum) + if enum.instance_of?(self.class) + super + else + do_with_enum(enum) # make sure enum is enumerable before calling clear + clear + merge(enum) + end + end + + def to_set(*args, &block) + klass = if args.empty? + Set + else + warn "passing arguments to Enumerable#to_set is deprecated", uplevel: 1 + args.shift + end + return self if instance_of?(Set) && klass == Set && block.nil? && args.empty? + klass.new(self, *args, &block) + end + + def flatten_merge(set, seen = {}) # :nodoc: + set.each { |e| + if e.is_a?(Set) + case seen[e_id = e.object_id] + when true + raise ArgumentError, "tried to flatten recursive Set" + when false + next + end + + seen[e_id] = true + flatten_merge(e, seen) + seen[e_id] = false + else + add(e) + end + } + + self + end + protected :flatten_merge + + def flatten + self.class.new.flatten_merge(self) + end + + def flatten! + replace(flatten()) if any?(Set) + end + + def superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size >= set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias >= superset? + + def proper_superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size > set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias > proper_superset? + + def subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size <= set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias <= subset? + + def proper_subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size < set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias < proper_subset? + + def <=>(set) + return unless set.is_a?(Set) + + case size <=> set.size + when -1 then -1 if proper_subset?(set) + when +1 then +1 if proper_superset?(set) + else 0 if self.==(set) + end + end + + def intersect?(set) + case set + when Set + if size < set.size + any?(set) + else + set.any?(self) + end + when Enumerable + set.any?(self) + else + raise ArgumentError, "value must be enumerable" + end + end + + def disjoint?(set) + !intersect?(set) + end + + def add?(o) + add(o) unless include?(o) + end + + def delete?(o) + delete(o) if include?(o) + end + + def delete_if(&block) + block_given? or return enum_for(__method__) { size } + select(&block).each { |o| delete(o) } + self + end + + def keep_if(&block) + block_given? or return enum_for(__method__) { size } + reject(&block).each { |o| delete(o) } + self + end + + def collect! + block_given? or return enum_for(__method__) { size } + set = self.class.new + each { |o| set << yield(o) } + replace(set) + end + alias map! collect! + + def reject!(&block) + block_given? or return enum_for(__method__) { size } + n = size + delete_if(&block) + self if size != n + end + + def select!(&block) + block_given? or return enum_for(__method__) { size } + n = size + keep_if(&block) + self if size != n + end + + alias filter! select! + + def merge(*enums, **nil) + enums.each do |enum| + if enum.instance_of?(self.class) + super(enum) + else + do_with_enum(enum) { |o| add(o) } + end + end + + self + end + + def subtract(enum) + do_with_enum(enum) { |o| delete(o) } + self + end + + def |(enum) + dup.merge(enum) + end + alias + | + alias union | + + def -(enum) + dup.subtract(enum) + end + alias difference - + + def &(enum) + n = self.class.new + if enum.is_a?(Set) + if enum.size > size + each { |o| n.add(o) if enum.include?(o) } + else + enum.each { |o| n.add(o) if include?(o) } + end + else + do_with_enum(enum) { |o| n.add(o) if include?(o) } + end + n + end + alias intersection & + + def ^(enum) + n = self.class.new(enum) + each { |o| n.add(o) unless n.delete?(o) } + n + end + + def ==(other) + if self.equal?(other) + true + elsif other.instance_of?(self.class) + super + elsif other.is_a?(Set) && self.size == other.size + other.all? { |o| include?(o) } + else + false + end + end + + def eql?(o) # :nodoc: + return false unless o.is_a?(Set) + super + end + + def classify + block_given? or return enum_for(__method__) { size } + + h = {} + + each { |i| + (h[yield(i)] ||= self.class.new).add(i) + } + + h + end + + def join(separator=nil) + to_a.join(separator) + end + + InspectKey = :__inspect_key__ # :nodoc: + + # Returns a string containing a human-readable representation of the + # set ("#"). + def inspect + ids = (Thread.current[InspectKey] ||= []) + + if ids.include?(object_id) + return sprintf('#<%s: {...}>', self.class.name) + end + + ids << object_id + begin + return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2]) + ensure + ids.pop + end + end + + alias to_s inspect + + def pretty_print(pp) # :nodoc: + pp.group(1, sprintf('#<%s:', self.class.name), '>') { + pp.breakable + pp.group(1, '{', '}') { + pp.seplist(self) { |o| + pp.pp o + } + } + } + end + + def pretty_print_cycle(pp) # :nodoc: + pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...') + end + end + private_constant :SubclassCompatible +end From f3a452590546173e04267e3858ed2c337f1638f0 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 30 Dec 2025 16:27:33 -0600 Subject: [PATCH 040/168] Update tests from CRuby 4.0.0 release --- test/mri/-ext-/box/test_load_ext.rb | 97 + .../-ext-/bug_reporter/test_bug_reporter.rb | 10 +- test/mri/-ext-/debug/test_debug.rb | 54 + test/mri/-ext-/gvl/test_last_thread.rb | 3 +- test/mri/-ext-/marshal/test_internal_ivar.rb | 10 +- .../test_interrupt_with_scheduler.rb | 54 + test/mri/-ext-/stack/test_stack_overflow.rb | 55 + test/mri/-ext-/string/test_capacity.rb | 2 +- test/mri/-ext-/symbol/test_type.rb | 16 +- test/mri/-ext-/test_abi.rb | 12 +- .../-ext-/thread/test_instrumentation_api.rb | 6 +- .../-ext-/thread/test_lock_native_thread.rb | 4 + test/mri/-ext-/tracepoint/test_tracepoint.rb | 2 +- test/mri/cgi/test_cgi_escape.rb | 325 ++ test/mri/coverage/test_coverage.rb | 17 + test/mri/date/test_date.rb | 4 + test/mri/date/test_date_conv.rb | 15 +- test/mri/date/test_date_parse.rb | 29 +- test/mri/date/test_date_ractor.rb | 2 +- test/mri/date/test_switch_hitter.rb | 5 + .../spell_checking/test_method_name_check.rb | 4 + .../spell_checking/test_require_path_check.rb | 6 +- .../did_you_mean/test_ractor_compatibility.rb | 12 +- test/mri/digest/test_ractor.rb | 6 +- test/mri/dtrace/helper.rb | 6 +- test/mri/erb/test_erb.rb | 80 +- .../error_highlight/test_error_highlight.rb | 295 +- test/mri/etc/test_etc.rb | 75 +- test/mri/fiber/scheduler.rb | 158 +- test/mri/fiber/test_io.rb | 59 +- test/mri/fiber/test_io_close.rb | 107 + test/mri/fiber/test_ractor.rb | 2 +- test/mri/fiber/test_scheduler.rb | 173 + test/mri/fiber/test_sleep.rb | 4 +- test/mri/fiber/test_thread.rb | 41 + test/mri/fileutils/test_fileutils.rb | 95 +- test/mri/io/console/test_io_console.rb | 30 +- test/mri/io/console/test_ractor.rb | 12 +- test/mri/io/wait/test_io_wait.rb | 36 +- test/mri/io/wait/test_io_wait_uncommon.rb | 15 + test/mri/io/wait/test_ractor.rb | 6 +- test/mri/json/fixtures/fail15.json | 1 + test/mri/json/fixtures/fail16.json | 1 + test/mri/json/fixtures/fail17.json | 1 + test/mri/json/fixtures/fail26.json | 1 + test/mri/json/fixtures/pass1.json | 2 +- test/mri/json/json_addition_test.rb | 14 +- test/mri/json/json_coder_test.rb | 149 + test/mri/json/json_common_interface_test.rb | 124 +- test/mri/json/json_encoding_test.rb | 188 +- test/mri/json/json_ext_parser_test.rb | 39 +- test/mri/json/json_fixtures_test.rb | 38 +- test/mri/json/json_generator_test.rb | 549 ++- test/mri/json/json_generic_object_test.rb | 33 +- test/mri/json/json_parser_test.rb | 246 +- test/mri/json/json_ryu_fallback_test.rb | 169 + test/mri/json/ractor_test.rb | 74 +- test/mri/json/test_helper.rb | 24 + test/mri/lib/jit_support.rb | 16 +- test/mri/mkmf/test_pkg_config.rb | 17 +- test/mri/mmtk/helper.rb | 32 + test/mri/mmtk/test_configuration.rb | 93 + test/mri/net/http/test_http.rb | 47 +- test/mri/net/http/test_http_request.rb | 34 +- test/mri/net/http/test_https.rb | 84 +- test/mri/net/http/test_https_proxy.rb | 16 +- test/mri/net/http/utils.rb | 18 +- test/mri/objspace/test_objspace.rb | 94 +- test/mri/objspace/test_ractor.rb | 74 +- test/mri/open-uri/utils.rb | 10 +- test/mri/openssl/fixtures/pkey/mldsa65-1.pem | 88 + test/mri/openssl/fixtures/pkey/mldsa65-2.pem | 88 + test/mri/openssl/test_asn1.rb | 92 +- test/mri/openssl/test_bn.rb | 52 +- test/mri/openssl/test_buffering.rb | 2 +- test/mri/openssl/test_cipher.rb | 36 +- test/mri/openssl/test_config.rb | 13 +- test/mri/openssl/test_digest.rb | 57 +- test/mri/openssl/test_fips.rb | 7 +- test/mri/openssl/test_kdf.rb | 3 - test/mri/openssl/test_ns_spki.rb | 4 +- test/mri/openssl/test_ocsp.rb | 14 +- test/mri/openssl/test_ossl.rb | 75 +- test/mri/openssl/test_pkcs12.rb | 2 + test/mri/openssl/test_pkcs7.rb | 380 +- test/mri/openssl/test_pkey.rb | 214 +- test/mri/openssl/test_pkey_dh.rb | 145 +- test/mri/openssl/test_pkey_dsa.rb | 149 +- test/mri/openssl/test_pkey_ec.rb | 110 +- test/mri/openssl/test_pkey_rsa.rb | 338 +- test/mri/openssl/test_provider.rb | 1 + test/mri/openssl/test_ssl.rb | 785 +++- test/mri/openssl/test_ssl_session.rb | 45 +- test/mri/openssl/test_ts.rb | 48 +- test/mri/openssl/test_x509cert.rb | 225 +- test/mri/openssl/test_x509crl.rb | 80 +- test/mri/openssl/test_x509name.rb | 16 +- test/mri/openssl/test_x509req.rb | 96 +- test/mri/openssl/test_x509store.rb | 19 +- test/mri/openssl/utils.rb | 70 +- test/mri/optparse/test_load.rb | 61 +- test/mri/optparse/test_optparse.rb | 17 +- test/mri/optparse/test_placearg.rb | 25 + test/mri/optparse/test_switch.rb | 50 + test/mri/pathname/test_pathname.rb | 34 +- test/mri/pathname/test_ractor.rb | 12 +- test/mri/prism/api/freeze_test.rb | 60 + test/mri/prism/api/parse_test.rb | 37 +- test/mri/prism/encoding/encodings_test.rb | 18 +- .../regular_expression_encoding_test.rb | 4 +- .../errors/3.3-3.3/circular_parameters.txt | 12 + .../prism/errors/3.3-3.4/leading_logical.txt | 34 + .../errors/3.3-3.4/private_endless_method.txt | 3 + .../3.4/block_args_in_array_assignment.txt | 3 + .../dont_allow_return_inside_sclass_body.txt | 3 + .../errors/3.4/it_with_ordinary_parameter.txt | 3 + .../3.4/keyword_args_in_array_assignment.txt | 3 + .../errors/block_args_with_endless_def.txt | 5 + test/mri/prism/errors/command_calls.txt | 7 + test/mri/prism/errors/command_calls_31.txt | 17 + test/mri/prism/errors/command_calls_32.txt | 19 + test/mri/prism/errors/command_calls_33.txt | 6 + test/mri/prism/errors/command_calls_34.txt | 24 + test/mri/prism/errors/command_calls_35.txt | 46 + .../prism/errors/def_with_optional_splat.txt | 6 + test/mri/prism/errors/defined_empty.txt | 3 + .../destroy_call_operator_write_arguments.txt | 11 + .../errors/endless_method_command_call.txt | 3 + ...endless_method_command_call_parameters.txt | 27 + .../escape_unicode_curly_whitespace.txt | 5 + .../heredoc_percent_q_newline_delimiter.txt | 11 + .../errors/label_in_interpolated_string.txt | 14 + .../errors/pattern-capture-in-alt-array.txt | 4 + .../errors/pattern-capture-in-alt-hash.txt | 3 + .../errors/pattern-capture-in-alt-name.txt | 3 + .../errors/pattern-capture-in-alt-top.txt | 4 + .../errors/pattern_arithmetic_expressions.txt | 3 + .../errors/pattern_match_implicit_rest.txt | 3 + test/mri/prism/errors/pattern_string_key.txt | 8 + test/mri/prism/errors/xstring_concat.txt | 5 + test/mri/prism/errors_test.rb | 68 +- .../block_args_in_array_assignment.txt | 1 + test/mri/prism/fixtures/3.3-3.3/it.txt | 5 + .../fixtures/3.3-3.3/it_indirect_writes.txt | 23 + .../3.3-3.3/it_read_and_assignment.txt | 1 + .../3.3-3.3/it_with_ordinary_parameter.txt | 1 + .../keyword_args_in_array_assignment.txt | 1 + .../fixtures/3.3-3.3/return_in_sclass.txt | 1 + .../fixtures/3.4/circular_parameters.txt | 4 + test/mri/prism/fixtures/3.4/it.txt | 5 + .../prism/fixtures/3.4/it_indirect_writes.txt | 23 + .../fixtures/3.4/it_read_and_assignment.txt | 1 + .../4.0/endless_methods_command_call.txt | 8 + .../prism/fixtures/4.0/leading_logical.txt | 21 + test/mri/prism/fixtures/begin_rescue.txt | 6 + test/mri/prism/fixtures/break.txt | 4 + test/mri/prism/fixtures/case_in_hash_key.txt | 6 + test/mri/prism/fixtures/character_literal.txt | 2 + .../prism/fixtures/command_method_call_2.txt | 3 + .../prism/fixtures/command_method_call_3.txt | 19 + test/mri/prism/fixtures/comment_single.txt | 1 + test/mri/prism/fixtures/defined.txt | 9 + test/mri/prism/fixtures/dstring.txt | 13 + test/mri/prism/fixtures/dsym_str.txt | 3 + test/mri/prism/fixtures/encoding_binary.txt | 9 + test/mri/prism/fixtures/encoding_euc_jp.txt | 6 + .../endless_method_as_default_arg.txt | 11 + test/mri/prism/fixtures/endless_methods.txt | 2 + .../heredoc_percent_q_newline_delimiter.txt | 22 + .../fixtures/heredocs_with_fake_newlines.txt | 55 + test/mri/prism/fixtures/it_assignment.txt | 1 + .../prism/fixtures/keyword_method_names.txt | 9 - test/mri/prism/fixtures/lambda.txt | 4 + test/mri/prism/fixtures/methods.txt | 2 + test/mri/prism/fixtures/next.txt | 4 + test/mri/prism/fixtures/patterns.txt | 5 + test/mri/prism/fixtures/ranges.txt | 2 + test/mri/prism/fixtures/regex.txt | 10 + .../fixtures/regex_with_fake_newlines.txt | 41 + test/mri/prism/fixtures/rescue.txt | 4 + test/mri/prism/fixtures/return.txt | 3 + .../string_concatination_frozen_false.txt | 5 + .../string_concatination_frozen_true.txt | 5 + test/mri/prism/fixtures/strings.txt | 78 + test/mri/prism/fixtures/symbols.txt | 11 + .../mri/prism/fixtures/unary_method_calls.txt | 2 + test/mri/prism/fixtures/variables.txt | 2 + test/mri/prism/fixtures/whitequark/LICENSE | 3 +- .../fixtures/whitequark/arg_combinations.txt | 29 + .../whitequark/block_arg_combinations.txt | 57 + .../prism/fixtures/whitequark/block_kwarg.txt | 1 + .../whitequark/block_kwarg_combinations.txt | 5 + .../emit_arg_inside_procarg0_legacy.txt | 1 + .../fixtures/whitequark/find_pattern.txt | 7 + .../whitequark/kwarg_combinations.txt | 7 + .../fixtures/whitequark/kwarg_no_paren.txt | 5 + .../whitequark/lvar_injecting_match.txt | 2 + .../fixtures/whitequark/marg_combinations.txt | 19 + .../multiple_args_with_trailing_comma.txt | 1 + .../pattern_matching_const_pattern.txt | 11 + .../whitequark/pattern_matching_constants.txt | 5 + .../pattern_matching_explicit_array_match.txt | 19 + .../pattern_matching_expr_in_paren.txt | 1 + .../whitequark/pattern_matching_hash.txt | 48 + .../pattern_matching_if_unless_modifiers.txt | 3 + .../pattern_matching_implicit_array_match.txt | 15 + .../pattern_matching_keyword_variable.txt | 1 + .../whitequark/pattern_matching_lambda.txt | 1 + .../whitequark/pattern_matching_match_alt.txt | 1 + .../whitequark/pattern_matching_match_as.txt | 1 + .../pattern_matching_nil_pattern.txt | 1 + .../whitequark/pattern_matching_no_body.txt | 1 + .../whitequark/pattern_matching_ranges.txt | 11 + .../pattern_matching_single_match.txt | 1 + .../prism/fixtures/whitequark/pin_expr.txt | 14 + .../fixtures/whitequark/procarg0_legacy.txt | 1 + .../fixtures/whitequark/ruby_bug_18878.txt | 1 + .../fixtures/whitequark/ruby_bug_19281.txt | 7 + .../fixtures/whitequark/ruby_bug_19539.txt | 9 + test/mri/prism/fixtures/xstring.txt | 8 + test/mri/prism/fixtures_test.rb | 24 +- test/mri/prism/lex_test.rb | 29 +- test/mri/prism/locals_test.rb | 12 +- test/mri/prism/ractor_test.rb | 74 + test/mri/prism/result/named_capture_test.rb | 29 + test/mri/prism/result/source_location_test.rb | 2 +- test/mri/prism/result/string_test.rb | 32 + test/mri/prism/result/warnings_test.rb | 2 +- test/mri/prism/ruby/dispatcher_test.rb | 19 +- test/mri/prism/ruby/location_test.rb | 9 +- .../prism/ruby/parameters_signature_test.rb | 7 +- test/mri/prism/ruby/parser_test.rb | 225 +- test/mri/prism/ruby/ripper_test.rb | 35 +- test/mri/prism/ruby/ruby_parser_test.rb | 53 +- test/mri/prism/snippets_test.rb | 13 +- test/mri/prism/test_helper.rb | 90 +- test/mri/prism/unescape_test.rb | 7 +- test/mri/psych/test_data.rb | 94 + test/mri/psych/test_date_time.rb | 15 + test/mri/psych/test_exception.rb | 13 + test/mri/psych/test_object_references.rb | 5 + test/mri/psych/test_psych.rb | 11 + test/mri/psych/test_psych_set.rb | 57 + test/mri/psych/test_ractor.rb | 6 +- test/mri/psych/test_safe_load.rb | 32 + test/mri/psych/test_scalar_scanner.rb | 5 + test/mri/psych/test_serialize_subclasses.rb | 18 + test/mri/psych/test_set.rb | 61 +- test/mri/psych/test_stream.rb | 8 + test/mri/psych/test_stringio.rb | 14 + test/mri/psych/test_yaml.rb | 58 +- test/mri/psych/test_yaml_special_cases.rb | 12 + test/mri/psych/test_yamlstore.rb | 16 +- test/mri/psych/visitors/test_to_ruby.rb | 6 + test/mri/psych/visitors/test_yaml_tree.rb | 21 + test/mri/resolv/test_dns.rb | 9 + test/mri/resolv/test_win32_config.rb | 26 + test/mri/ripper/assert_parse_files.rb | 3 +- test/mri/ripper/test_files_ext.rb | 3 +- test/mri/ripper/test_files_lib.rb | 3 +- test/mri/ripper/test_files_sample.rb | 3 +- test/mri/ripper/test_files_test.rb | 3 +- test/mri/ripper/test_files_test_1.rb | 3 +- test/mri/ripper/test_files_test_2.rb | 3 +- test/mri/ripper/test_lexer.rb | 77 + test/mri/ripper/test_parser_events.rb | 7 + test/mri/ripper/test_ripper.rb | 9 +- test/mri/ruby/box/a.1_1_0.rb | 17 + test/mri/ruby/box/a.1_2_0.rb | 17 + test/mri/ruby/box/a.rb | 15 + test/mri/ruby/box/autoloading.rb | 8 + test/mri/ruby/box/blank.rb | 2 + test/mri/ruby/box/blank1.rb | 2 + test/mri/ruby/box/blank2.rb | 2 + test/mri/ruby/box/box.rb | 10 + test/mri/ruby/box/call_proc.rb | 5 + test/mri/ruby/box/call_toplevel.rb | 8 + test/mri/ruby/box/consts.rb | 148 + test/mri/ruby/box/define_toplevel.rb | 5 + test/mri/ruby/box/global_vars.rb | 37 + test/mri/ruby/box/instance_variables.rb | 21 + test/mri/ruby/box/line_splitter.rb | 9 + test/mri/ruby/box/load_path.rb | 26 + test/mri/ruby/box/open_class_with_include.rb | 31 + test/mri/ruby/box/proc_callee.rb | 14 + test/mri/ruby/box/proc_caller.rb | 5 + test/mri/ruby/box/procs.rb | 64 + test/mri/ruby/box/raise.rb | 3 + test/mri/ruby/box/returns_proc.rb | 12 + test/mri/ruby/box/singleton_methods.rb | 65 + test/mri/ruby/box/string_ext.rb | 13 + test/mri/ruby/box/string_ext_caller.rb | 5 + test/mri/ruby/box/string_ext_calling.rb | 1 + test/mri/ruby/box/string_ext_eval_caller.rb | 12 + test/mri/ruby/box/top_level.rb | 33 + test/mri/ruby/enc/test_case_comprehensive.rb | 61 +- test/mri/ruby/enc/test_emoji_breaks.rb | 2 +- test/mri/ruby/test_alias.rb | 13 + test/mri/ruby/test_allocation.rb | 129 +- test/mri/ruby/test_array.rb | 128 +- test/mri/ruby/test_ast.rb | 263 +- test/mri/ruby/test_autoload.rb | 24 +- test/mri/ruby/test_backtrace.rb | 12 + test/mri/ruby/test_bignum.rb | 46 + test/mri/ruby/test_box.rb | 857 ++++ test/mri/ruby/test_call.rb | 78 + test/mri/ruby/test_class.rb | 124 +- test/mri/ruby/test_compile_prism.rb | 72 +- test/mri/ruby/test_data.rb | 6 + test/mri/ruby/test_defined.rb | 20 + test/mri/ruby/test_dir.rb | 15 + test/mri/ruby/test_encoding.rb | 44 +- test/mri/ruby/test_enumerator.rb | 28 +- test/mri/ruby/test_env.rb | 514 ++- test/mri/ruby/test_exception.rb | 27 + test/mri/ruby/test_fiber.rb | 5 +- test/mri/ruby/test_file.rb | 19 +- test/mri/ruby/test_file_exhaustive.rb | 37 +- test/mri/ruby/test_float.rb | 8 +- test/mri/ruby/test_frozen.rb | 16 + test/mri/ruby/test_gc.rb | 107 +- test/mri/ruby/test_gc_compact.rb | 39 +- test/mri/ruby/test_hash.rb | 68 +- test/mri/ruby/test_integer.rb | 8 +- test/mri/ruby/test_io.rb | 180 +- test/mri/ruby/test_io_buffer.rb | 251 +- test/mri/ruby/test_io_m17n.rb | 41 +- test/mri/ruby/test_iseq.rb | 140 +- test/mri/ruby/test_keyword.rb | 34 +- test/mri/ruby/test_literal.rb | 6 + test/mri/ruby/test_m17n.rb | 188 +- test/mri/ruby/test_marshal.rb | 50 +- test/mri/ruby/test_math.rb | 32 +- test/mri/ruby/test_memory_view.rb | 2 +- test/mri/ruby/test_method.rb | 15 +- test/mri/ruby/test_module.rb | 88 +- test/mri/ruby/test_nomethod_error.rb | 28 + test/mri/ruby/test_numeric.rb | 30 +- test/mri/ruby/test_object.rb | 94 +- test/mri/ruby/test_object_id.rb | 303 ++ test/mri/ruby/test_objectspace.rb | 54 +- test/mri/ruby/test_optimization.rb | 59 +- test/mri/ruby/test_parse.rb | 43 +- test/mri/ruby/test_pattern_matching.rb | 42 +- test/mri/ruby/test_proc.rb | 280 +- test/mri/ruby/test_process.rb | 67 +- test/mri/ruby/test_ractor.rb | 229 + test/mri/ruby/test_range.rb | 32 +- test/mri/ruby/test_rational.rb | 10 +- test/mri/ruby/test_refinement.rb | 47 + test/mri/ruby/test_regexp.rb | 189 +- test/mri/ruby/test_require.rb | 46 +- test/mri/ruby/test_require_lib.rb | 7 +- test/mri/ruby/test_rubyoptions.rb | 171 +- test/mri/ruby/test_set.rb | 115 +- test/mri/ruby/test_settracefunc.rb | 216 +- test/mri/ruby/test_shapes.rb | 252 +- test/mri/ruby/test_string.rb | 342 +- test/mri/ruby/test_struct.rb | 10 +- test/mri/ruby/test_super.rb | 15 + test/mri/ruby/test_syntax.rb | 92 +- test/mri/ruby/test_thread.rb | 100 +- test/mri/ruby/test_thread_cv.rb | 2 +- test/mri/ruby/test_thread_queue.rb | 8 +- test/mri/ruby/test_time_tz.rb | 1 - test/mri/ruby/test_transcode.rb | 87 + test/mri/ruby/test_variable.rb | 115 +- test/mri/ruby/test_vm_dump.rb | 5 +- test/mri/ruby/test_yjit.rb | 56 +- test/mri/ruby/test_zjit.rb | 3803 +++++++++++++++++ test/mri/rubygems/helper.rb | 74 +- test/mri/rubygems/installer_test_case.rb | 19 +- test/mri/rubygems/mock_gem_ui.rb | 2 +- test/mri/rubygems/package/tar_test_case.rb | 38 +- test/mri/rubygems/test_bundled_ca.rb | 2 +- test/mri/rubygems/test_config.rb | 7 - test/mri/rubygems/test_gem.rb | 32 +- .../test_gem_bundler_version_finder.rb | 77 +- test/mri/rubygems/test_gem_command_manager.rb | 45 +- .../test_gem_commands_build_command.rb | 10 - .../test_gem_commands_cert_command.rb | 8 +- .../test_gem_commands_exec_command.rb | 7 +- .../test_gem_commands_help_command.rb | 2 +- .../test_gem_commands_info_command.rb | 2 +- .../test_gem_commands_install_command.rb | 146 +- .../test_gem_commands_owner_command.rb | 16 +- .../test_gem_commands_pristine_command.rb | 54 +- .../test_gem_commands_push_command.rb | 18 +- .../test_gem_commands_setup_command.rb | 31 +- .../test_gem_commands_signin_command.rb | 2 +- .../test_gem_commands_sources_command.rb | 563 ++- .../test_gem_commands_update_command.rb | 34 +- .../test_gem_commands_yank_command.rb | 14 +- .../rubygems/test_gem_dependency_installer.rb | 169 +- .../rubygems/test_gem_ext_cargo_builder.rb | 62 +- .../ext/custom_name_lib/Cargo.lock | 10 +- .../ext/custom_name_lib/Cargo.toml | 2 +- .../rust_ruby_example/Cargo.lock | 10 +- .../rust_ruby_example/Cargo.toml | 2 +- ...m_ext_cargo_builder_link_flag_converter.rb | 2 +- .../rubygems/test_gem_ext_cmake_builder.rb | 97 +- .../rubygems/test_gem_ext_ext_conf_builder.rb | 33 +- .../mri/rubygems/test_gem_ext_rake_builder.rb | 2 +- test/mri/rubygems/test_gem_gem_runner.rb | 11 - .../rubygems/test_gem_gemcutter_utilities.rb | 12 +- .../test_gem_install_update_options.rb | 12 + test/mri/rubygems/test_gem_installer.rb | 269 +- test/mri/rubygems/test_gem_name_tuple.rb | 37 + test/mri/rubygems/test_gem_package.rb | 76 +- test/mri/rubygems/test_gem_package_old.rb | 2 +- .../test_gem_package_tar_header_ractor.rb | 61 + .../rubygems/test_gem_package_tar_writer.rb | 33 +- test/mri/rubygems/test_gem_platform.rb | 305 +- test/mri/rubygems/test_gem_rdoc.rb | 14 +- test/mri/rubygems/test_gem_remote_fetcher.rb | 2 +- .../rubygems/test_gem_remote_fetcher_s3.rb | 296 +- test/mri/rubygems/test_gem_request.rb | 24 +- .../test_gem_request_connection_pools.rb | 12 + test/mri/rubygems/test_gem_requirement.rb | 22 +- .../rubygems/test_gem_resolver_best_set.rb | 14 + test/mri/rubygems/test_gem_safe_marshal.rb | 16 +- .../rubygems/test_gem_security_trust_dir.rb | 6 +- test/mri/rubygems/test_gem_source_list.rb | 127 +- test/mri/rubygems/test_gem_specification.rb | 96 +- test/mri/rubygems/test_gem_uri.rb | 2 +- test/mri/rubygems/test_gem_util.rb | 11 - test/mri/rubygems/test_gem_version.rb | 33 +- test/mri/rubygems/test_webauthn_listener.rb | 2 +- test/mri/socket/test_addrinfo.rb | 6 + test/mri/socket/test_socket.rb | 43 +- test/mri/socket/test_tcp.rb | 42 +- test/mri/socket/test_unix.rb | 21 +- test/mri/stringio/test_ractor.rb | 6 +- test/mri/stringio/test_stringio.rb | 75 +- test/mri/strscan/test_ractor.rb | 6 +- test/mri/strscan/test_stringscanner.rb | 7 +- test/mri/test_delegate.rb | 41 +- test/mri/test_extlibs.rb | 4 +- test/mri/test_ipaddr.rb | 61 + test/mri/test_pp.rb | 72 +- test/mri/test_pty.rb | 8 +- test/mri/test_rbconfig.rb | 15 + test/mri/test_time.rb | 2 +- test/mri/test_timeout.rb | 179 + test/mri/test_tmpdir.rb | 33 +- test/mri/test_unicode_normalize.rb | 28 + test/mri/uri/test_common.rb | 26 +- test/mri/uri/test_ftp.rb | 10 +- test/mri/uri/test_generic.rb | 92 +- test/mri/uri/test_http.rb | 20 +- test/mri/uri/test_mailto.rb | 72 + test/mri/uri/test_parser.rb | 26 +- test/mri/uri/test_ws.rb | 16 +- test/mri/uri/test_wss.rb | 16 +- test/mri/yaml/test_store.rb | 12 +- test/mri/zlib/test_zlib.rb | 5 +- 456 files changed, 21106 insertions(+), 4346 deletions(-) create mode 100644 test/mri/-ext-/box/test_load_ext.rb create mode 100644 test/mri/-ext-/scheduler/test_interrupt_with_scheduler.rb create mode 100644 test/mri/-ext-/stack/test_stack_overflow.rb create mode 100644 test/mri/cgi/test_cgi_escape.rb create mode 100644 test/mri/fiber/test_io_close.rb create mode 100644 test/mri/json/fixtures/fail15.json create mode 100644 test/mri/json/fixtures/fail16.json create mode 100644 test/mri/json/fixtures/fail17.json create mode 100644 test/mri/json/fixtures/fail26.json create mode 100755 test/mri/json/json_coder_test.rb create mode 100644 test/mri/json/json_ryu_fallback_test.rb create mode 100644 test/mri/mmtk/helper.rb create mode 100644 test/mri/mmtk/test_configuration.rb create mode 100644 test/mri/openssl/fixtures/pkey/mldsa65-1.pem create mode 100644 test/mri/openssl/fixtures/pkey/mldsa65-2.pem create mode 100644 test/mri/optparse/test_switch.rb create mode 100644 test/mri/prism/api/freeze_test.rb create mode 100644 test/mri/prism/errors/3.3-3.3/circular_parameters.txt create mode 100644 test/mri/prism/errors/3.3-3.4/leading_logical.txt create mode 100644 test/mri/prism/errors/3.3-3.4/private_endless_method.txt create mode 100644 test/mri/prism/errors/3.4/block_args_in_array_assignment.txt create mode 100644 test/mri/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt create mode 100644 test/mri/prism/errors/3.4/it_with_ordinary_parameter.txt create mode 100644 test/mri/prism/errors/3.4/keyword_args_in_array_assignment.txt create mode 100644 test/mri/prism/errors/block_args_with_endless_def.txt create mode 100644 test/mri/prism/errors/command_calls_31.txt create mode 100644 test/mri/prism/errors/command_calls_32.txt create mode 100644 test/mri/prism/errors/command_calls_33.txt create mode 100644 test/mri/prism/errors/command_calls_34.txt create mode 100644 test/mri/prism/errors/command_calls_35.txt create mode 100644 test/mri/prism/errors/def_with_optional_splat.txt create mode 100644 test/mri/prism/errors/defined_empty.txt create mode 100644 test/mri/prism/errors/destroy_call_operator_write_arguments.txt create mode 100644 test/mri/prism/errors/endless_method_command_call.txt create mode 100644 test/mri/prism/errors/endless_method_command_call_parameters.txt create mode 100644 test/mri/prism/errors/escape_unicode_curly_whitespace.txt create mode 100644 test/mri/prism/errors/heredoc_percent_q_newline_delimiter.txt create mode 100644 test/mri/prism/errors/label_in_interpolated_string.txt create mode 100644 test/mri/prism/errors/pattern-capture-in-alt-array.txt create mode 100644 test/mri/prism/errors/pattern-capture-in-alt-hash.txt create mode 100644 test/mri/prism/errors/pattern-capture-in-alt-name.txt create mode 100644 test/mri/prism/errors/pattern-capture-in-alt-top.txt create mode 100644 test/mri/prism/errors/pattern_arithmetic_expressions.txt create mode 100644 test/mri/prism/errors/pattern_match_implicit_rest.txt create mode 100644 test/mri/prism/errors/pattern_string_key.txt create mode 100644 test/mri/prism/errors/xstring_concat.txt create mode 100644 test/mri/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt create mode 100644 test/mri/prism/fixtures/3.3-3.3/it.txt create mode 100644 test/mri/prism/fixtures/3.3-3.3/it_indirect_writes.txt create mode 100644 test/mri/prism/fixtures/3.3-3.3/it_read_and_assignment.txt create mode 100644 test/mri/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt create mode 100644 test/mri/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt create mode 100644 test/mri/prism/fixtures/3.3-3.3/return_in_sclass.txt create mode 100644 test/mri/prism/fixtures/3.4/circular_parameters.txt create mode 100644 test/mri/prism/fixtures/3.4/it.txt create mode 100644 test/mri/prism/fixtures/3.4/it_indirect_writes.txt create mode 100644 test/mri/prism/fixtures/3.4/it_read_and_assignment.txt create mode 100644 test/mri/prism/fixtures/4.0/endless_methods_command_call.txt create mode 100644 test/mri/prism/fixtures/4.0/leading_logical.txt create mode 100644 test/mri/prism/fixtures/case_in_hash_key.txt create mode 100644 test/mri/prism/fixtures/character_literal.txt create mode 100644 test/mri/prism/fixtures/command_method_call_2.txt create mode 100644 test/mri/prism/fixtures/command_method_call_3.txt create mode 100644 test/mri/prism/fixtures/comment_single.txt create mode 100644 test/mri/prism/fixtures/encoding_binary.txt create mode 100644 test/mri/prism/fixtures/encoding_euc_jp.txt create mode 100644 test/mri/prism/fixtures/endless_method_as_default_arg.txt create mode 100644 test/mri/prism/fixtures/heredoc_percent_q_newline_delimiter.txt create mode 100644 test/mri/prism/fixtures/heredocs_with_fake_newlines.txt create mode 100644 test/mri/prism/fixtures/it_assignment.txt create mode 100644 test/mri/prism/fixtures/regex_with_fake_newlines.txt create mode 100644 test/mri/prism/fixtures/string_concatination_frozen_false.txt create mode 100644 test/mri/prism/fixtures/string_concatination_frozen_true.txt create mode 100644 test/mri/prism/fixtures/unary_method_calls.txt create mode 100644 test/mri/prism/fixtures/whitequark/arg_combinations.txt create mode 100644 test/mri/prism/fixtures/whitequark/block_arg_combinations.txt create mode 100644 test/mri/prism/fixtures/whitequark/block_kwarg.txt create mode 100644 test/mri/prism/fixtures/whitequark/block_kwarg_combinations.txt create mode 100644 test/mri/prism/fixtures/whitequark/emit_arg_inside_procarg0_legacy.txt create mode 100644 test/mri/prism/fixtures/whitequark/find_pattern.txt create mode 100644 test/mri/prism/fixtures/whitequark/kwarg_combinations.txt create mode 100644 test/mri/prism/fixtures/whitequark/kwarg_no_paren.txt create mode 100644 test/mri/prism/fixtures/whitequark/marg_combinations.txt create mode 100644 test/mri/prism/fixtures/whitequark/multiple_args_with_trailing_comma.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_const_pattern.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_constants.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_explicit_array_match.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_expr_in_paren.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_hash.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_if_unless_modifiers.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_implicit_array_match.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_keyword_variable.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_lambda.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_match_alt.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_match_as.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_nil_pattern.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_no_body.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_ranges.txt create mode 100644 test/mri/prism/fixtures/whitequark/pattern_matching_single_match.txt create mode 100644 test/mri/prism/fixtures/whitequark/pin_expr.txt create mode 100644 test/mri/prism/fixtures/whitequark/procarg0_legacy.txt create mode 100644 test/mri/prism/fixtures/whitequark/ruby_bug_18878.txt create mode 100644 test/mri/prism/fixtures/whitequark/ruby_bug_19281.txt create mode 100644 test/mri/prism/fixtures/whitequark/ruby_bug_19539.txt create mode 100644 test/mri/prism/ractor_test.rb create mode 100644 test/mri/prism/result/named_capture_test.rb create mode 100644 test/mri/prism/result/string_test.rb create mode 100644 test/mri/psych/test_data.rb create mode 100644 test/mri/psych/test_psych_set.rb create mode 100644 test/mri/psych/test_stringio.rb create mode 100644 test/mri/resolv/test_win32_config.rb create mode 100644 test/mri/ruby/box/a.1_1_0.rb create mode 100644 test/mri/ruby/box/a.1_2_0.rb create mode 100644 test/mri/ruby/box/a.rb create mode 100644 test/mri/ruby/box/autoloading.rb create mode 100644 test/mri/ruby/box/blank.rb create mode 100644 test/mri/ruby/box/blank1.rb create mode 100644 test/mri/ruby/box/blank2.rb create mode 100644 test/mri/ruby/box/box.rb create mode 100644 test/mri/ruby/box/call_proc.rb create mode 100644 test/mri/ruby/box/call_toplevel.rb create mode 100644 test/mri/ruby/box/consts.rb create mode 100644 test/mri/ruby/box/define_toplevel.rb create mode 100644 test/mri/ruby/box/global_vars.rb create mode 100644 test/mri/ruby/box/instance_variables.rb create mode 100644 test/mri/ruby/box/line_splitter.rb create mode 100644 test/mri/ruby/box/load_path.rb create mode 100644 test/mri/ruby/box/open_class_with_include.rb create mode 100644 test/mri/ruby/box/proc_callee.rb create mode 100644 test/mri/ruby/box/proc_caller.rb create mode 100644 test/mri/ruby/box/procs.rb create mode 100644 test/mri/ruby/box/raise.rb create mode 100644 test/mri/ruby/box/returns_proc.rb create mode 100644 test/mri/ruby/box/singleton_methods.rb create mode 100644 test/mri/ruby/box/string_ext.rb create mode 100644 test/mri/ruby/box/string_ext_caller.rb create mode 100644 test/mri/ruby/box/string_ext_calling.rb create mode 100644 test/mri/ruby/box/string_ext_eval_caller.rb create mode 100644 test/mri/ruby/box/top_level.rb create mode 100644 test/mri/ruby/test_box.rb create mode 100644 test/mri/ruby/test_object_id.rb create mode 100644 test/mri/ruby/test_ractor.rb create mode 100644 test/mri/ruby/test_zjit.rb create mode 100644 test/mri/rubygems/test_gem_package_tar_header_ractor.rb diff --git a/test/mri/-ext-/box/test_load_ext.rb b/test/mri/-ext-/box/test_load_ext.rb new file mode 100644 index 00000000000..ea3744375ea --- /dev/null +++ b/test/mri/-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/mri/-ext-/bug_reporter/test_bug_reporter.rb b/test/mri/-ext-/bug_reporter/test_bug_reporter.rb index 0036137f020..83fdba22829 100644 --- a/test/mri/-ext-/bug_reporter/test_bug_reporter.rb +++ b/test/mri/-ext-/bug_reporter/test_bug_reporter.rb @@ -6,12 +6,8 @@ class TestBugReporter < Test::Unit::TestCase def test_bug_reporter_add - pend "macOS 15 is not working with this test" if macos?(15) - - omit "flaky with RJIT" if JITSupport.rjit_enabled? description = RUBY_DESCRIPTION description = description.sub(/\+PRISM /, '') unless ParserSupport.prism_enabled_in_subprocess? - description = description.sub(/\+RJIT /, '') unless JITSupport.rjit_force_enabled? expected_stderr = [ :*, /\[BUG\]\sSegmentation\sfault.*\n/, @@ -24,8 +20,10 @@ def test_bug_reporter_add 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/mri/-ext-/debug/test_debug.rb b/test/mri/-ext-/debug/test_debug.rb index 98e178e34f4..c9263d76fa6 100644 --- a/test/mri/-ext-/debug/test_debug.rb +++ b/test/mri/-ext-/debug/test_debug.rb @@ -76,3 +76,57 @@ def test_lazy_block 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/mri/-ext-/gvl/test_last_thread.rb b/test/mri/-ext-/gvl/test_last_thread.rb index f1bebafeea9..bcda0e33856 100644 --- a/test/mri/-ext-/gvl/test_last_thread.rb +++ b/test/mri/-ext-/gvl/test_last_thread.rb @@ -15,8 +15,7 @@ def test_last_thread 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/mri/-ext-/marshal/test_internal_ivar.rb b/test/mri/-ext-/marshal/test_internal_ivar.rb index faabe14ab2d..8b4667fdf90 100644 --- a/test/mri/-ext-/marshal/test_internal_ivar.rb +++ b/test/mri/-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 @@ def test_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/mri/-ext-/scheduler/test_interrupt_with_scheduler.rb b/test/mri/-ext-/scheduler/test_interrupt_with_scheduler.rb new file mode 100644 index 00000000000..eb7a0647e58 --- /dev/null +++ b/test/mri/-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/mri/-ext-/stack/test_stack_overflow.rb b/test/mri/-ext-/stack/test_stack_overflow.rb new file mode 100644 index 00000000000..8eddec6ab5b --- /dev/null +++ b/test/mri/-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.alloca_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.alloca_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.alloca_overflow + end + + assert_raise(SystemStackError) do + fiber.resume + end + RUBY + end +end diff --git a/test/mri/-ext-/string/test_capacity.rb b/test/mri/-ext-/string/test_capacity.rb index bcca64d85a4..df000f7cdb8 100644 --- a/test/mri/-ext-/string/test_capacity.rb +++ b/test/mri/-ext-/string/test_capacity.rb @@ -66,7 +66,7 @@ def capa(str) end def embed_header_size - 3 * RbConfig::SIZEOF['void*'] + GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*'] end def max_embed_len diff --git a/test/mri/-ext-/symbol/test_type.rb b/test/mri/-ext-/symbol/test_type.rb index 2b0fbe5b798..ed019062faf 100644 --- a/test/mri/-ext-/symbol/test_type.rb +++ b/test/mri/-ext-/symbol/test_type.rb @@ -123,16 +123,20 @@ def test_attrset 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/mri/-ext-/test_abi.rb b/test/mri/-ext-/test_abi.rb index d3ea6bb9b10..7f30feb9449 100644 --- a/test/mri/-ext-/test_abi.rb +++ b/test/mri/-ext-/test_abi.rb @@ -9,7 +9,11 @@ def test_require_lib_with_incorrect_abi_on_dev_ruby 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 @@ def test_enable_abi_check_using_environment_variable 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/mri/-ext-/thread/test_instrumentation_api.rb b/test/mri/-ext-/thread/test_instrumentation_api.rb index 663e41be537..ba410693048 100644 --- a/test/mri/-ext-/thread/test_instrumentation_api.rb +++ b/test/mri/-ext-/thread/test_instrumentation_api.rb @@ -151,7 +151,7 @@ def test_blocking_on_ractor end full_timeline = record do - ractor.take + ractor.value end timeline = timeline_for(Thread.current, full_timeline) @@ -161,6 +161,8 @@ def test_blocking_on_ractor 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 @@ def test_sleeping_inside_ractor thread = Ractor.new{ sleep 0.1 Thread.current - }.take + }.value sleep 0.1 end diff --git a/test/mri/-ext-/thread/test_lock_native_thread.rb b/test/mri/-ext-/thread/test_lock_native_thread.rb index 8a5ba78838d..b4044b2b935 100644 --- a/test/mri/-ext-/thread/test_lock_native_thread.rb +++ b/test/mri/-ext-/thread/test_lock_native_thread.rb @@ -15,6 +15,8 @@ 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 @@ def test_lock_native_thread 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/mri/-ext-/tracepoint/test_tracepoint.rb b/test/mri/-ext-/tracepoint/test_tracepoint.rb index bf66d8f1051..debddd83d04 100644 --- a/test/mri/-ext-/tracepoint/test_tracepoint.rb +++ b/test/mri/-ext-/tracepoint/test_tracepoint.rb @@ -83,7 +83,7 @@ def run(hook) end def test_teardown_with_active_GC_end_hook - assert_separately([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}') + assert_separately([], 'require("-test-/tracepoint"); Bug.after_gc_exit_hook = proc {}; GC.start') end end diff --git a/test/mri/cgi/test_cgi_escape.rb b/test/mri/cgi/test_cgi_escape.rb new file mode 100644 index 00000000000..73d99e8aacd --- /dev/null +++ b/test/mri/cgi/test_cgi_escape.rb @@ -0,0 +1,325 @@ +# frozen_string_literal: true +require 'test/unit' +require 'cgi/escape' +require 'stringio' +require_relative 'update_env' + + +class CGIEscapeTest < Test::Unit::TestCase + include CGI::Escape + 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_escape + assert_equal('%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93', CGI.escape(@str1)) + assert_equal('%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93'.ascii_only?, CGI.escape(@str1).ascii_only?) if defined?(::Encoding) + end + + def test_cgi_escape_with_unreserved_characters + assert_equal("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~", + CGI.escape("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"), + "should not escape any unreserved characters, as per RFC3986 Section 2.3") + end + + def test_cgi_escape_with_invalid_byte_sequence + assert_equal('%C0%3C%3C', CGI.escape("\xC0\<\<".dup.force_encoding("UTF-8"))) + end + + def test_cgi_escape_preserve_encoding + assert_equal(Encoding::US_ASCII, CGI.escape("\xC0\<\<".dup.force_encoding("US-ASCII")).encoding) + assert_equal(Encoding::ASCII_8BIT, CGI.escape("\xC0\<\<".dup.force_encoding("ASCII-8BIT")).encoding) + assert_equal(Encoding::UTF_8, CGI.escape("\xC0\<\<".dup.force_encoding("UTF-8")).encoding) + end + + def test_cgi_unescape + str = CGI.unescape('%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93') + assert_equal(@str1, str) + return unless defined?(::Encoding) + + assert_equal(@str1.encoding, str.encoding) + assert_equal("\u{30E1 30E2 30EA 691C 7D22}", CGI.unescape("\u{30E1 30E2 30EA}%E6%A4%9C%E7%B4%A2")) + end + + def test_cgi_unescape_preserve_encoding + assert_equal(Encoding::US_ASCII, CGI.unescape("%C0%3C%3C".dup.force_encoding("US-ASCII")).encoding) + assert_equal(Encoding::ASCII_8BIT, CGI.unescape("%C0%3C%3C".dup.force_encoding("ASCII-8BIT")).encoding) + assert_equal(Encoding::UTF_8, CGI.unescape("%C0%3C%3C".dup.force_encoding("UTF-8")).encoding) + end + + def test_cgi_unescape_accept_charset + return unless defined?(::Encoding) + + assert_raise(TypeError) {CGI.unescape('', nil)} + assert_separately(%w[-rcgi/escape], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + assert_equal("", CGI.unescape('')) + end; + end + + def test_cgi_escapeURIComponent + assert_equal('%26%3C%3E%22%20%E3%82%86%E3%82%93%E3%82%86%E3%82%93', CGI.escapeURIComponent(@str1)) + assert_equal('%26%3C%3E%22%20%E3%82%86%E3%82%93%E3%82%86%E3%82%93'.ascii_only?, CGI.escapeURIComponent(@str1).ascii_only?) if defined?(::Encoding) + end + + def test_cgi_escape_uri_component + assert_equal('%26%3C%3E%22%20%E3%82%86%E3%82%93%E3%82%86%E3%82%93', CGI.escape_uri_component(@str1)) + end + + def test_cgi_escapeURIComponent_with_unreserved_characters + assert_equal("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~", + CGI.escapeURIComponent("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"), + "should not encode any unreserved characters, as per RFC3986 Section 2.3") + end + + def test_cgi_escapeURIComponent_with_invalid_byte_sequence + assert_equal('%C0%3C%3C', CGI.escapeURIComponent("\xC0\<\<".dup.force_encoding("UTF-8"))) + end + + def test_cgi_escapeURIComponent_preserve_encoding + assert_equal(Encoding::US_ASCII, CGI.escapeURIComponent("\xC0\<\<".dup.force_encoding("US-ASCII")).encoding) + assert_equal(Encoding::ASCII_8BIT, CGI.escapeURIComponent("\xC0\<\<".dup.force_encoding("ASCII-8BIT")).encoding) + assert_equal(Encoding::UTF_8, CGI.escapeURIComponent("\xC0\<\<".dup.force_encoding("UTF-8")).encoding) + end + + def test_cgi_unescapeURIComponent + str = CGI.unescapeURIComponent('%26%3C%3E%22%20%E3%82%86%E3%82%93%E3%82%86%E3%82%93') + assert_equal(@str1, str) + return unless defined?(::Encoding) + + assert_equal("foo+bar", CGI.unescapeURIComponent("foo+bar")) + + assert_equal(@str1.encoding, str.encoding) + assert_equal("\u{30E1 30E2 30EA 691C 7D22}", CGI.unescapeURIComponent("\u{30E1 30E2 30EA}%E6%A4%9C%E7%B4%A2")) + end + + def test_cgi_unescape_uri_component + str = CGI.unescape_uri_component('%26%3C%3E%22%20%E3%82%86%E3%82%93%E3%82%86%E3%82%93') + assert_equal(@str1, str) + end + + def test_cgi_unescapeURIComponent_preserve_encoding + assert_equal(Encoding::US_ASCII, CGI.unescapeURIComponent("%C0%3C%3C".dup.force_encoding("US-ASCII")).encoding) + assert_equal(Encoding::ASCII_8BIT, CGI.unescapeURIComponent("%C0%3C%3C".dup.force_encoding("ASCII-8BIT")).encoding) + assert_equal(Encoding::UTF_8, CGI.unescapeURIComponent("%C0%3C%3C".dup.force_encoding("UTF-8")).encoding) + end + + def test_cgi_unescapeURIComponent_accept_charset + return unless defined?(::Encoding) + + assert_raise(TypeError) {CGI.unescapeURIComponent('', nil)} + assert_separately(%w[-rcgi/escape], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + assert_equal("", CGI.unescapeURIComponent('')) + end; + end + + def test_cgi_escapeHTML + assert_equal("'&"><", CGI.escapeHTML("'&\"><")) + end + + def test_cgi_escape_html_duplicated + orig = "Ruby".dup.force_encoding("US-ASCII") + str = CGI.escapeHTML(orig) + assert_equal(orig, str) + assert_not_same(orig, str) + end + + def assert_cgi_escape_html_preserve_encoding(str, encoding) + assert_equal(encoding, CGI.escapeHTML(str.dup.force_encoding(encoding)).encoding) + end + + def test_cgi_escape_html_preserve_encoding + Encoding.list do |enc| + assert_cgi_escape_html_preserve_encoding("'&\"><", enc) + assert_cgi_escape_html_preserve_encoding("Ruby", enc) + end + end + + def test_cgi_escape_html_dont_freeze + assert_not_predicate CGI.escapeHTML("'&\"><".dup), :frozen? + assert_not_predicate CGI.escapeHTML("'&\"><".freeze), :frozen? + assert_not_predicate CGI.escapeHTML("Ruby".dup), :frozen? + assert_not_predicate CGI.escapeHTML("Ruby".freeze), :frozen? + end + + def test_cgi_escape_html_large + return if RUBY_ENGINE == 'jruby' + ulong_max, size_max = RbConfig::LIMITS.values_at("ULONG_MAX", "SIZE_MAX") + return unless ulong_max < size_max # Platforms not concerned + + size = (ulong_max / 6 + 1) + begin + str = '"' * size + escaped = CGI.escapeHTML(str) + rescue NoMemoryError + omit "Not enough memory" + rescue => e + end + assert_raise_with_message(ArgumentError, /overflow/, ->{"length = #{escaped.length}"}) do + raise e if e + end + end + + def test_cgi_unescapeHTML + assert_equal("'&\"><", CGI.unescapeHTML("'&"><")) + end + + def test_cgi_unescapeHTML_invalid + assert_equal('&<&>"&abcdefghijklmn', CGI.unescapeHTML('&<&>"&abcdefghijklmn')) + end + + module UnescapeHTMLTests + def test_cgi_unescapeHTML_following_known_first_letter + assert_equal('&a>&q>&l>&g>', CGI.unescapeHTML('&a>&q>&l>&g>')) + end + + def test_cgi_unescapeHTML_following_number_sign + assert_equal('&#>&#x>', CGI.unescapeHTML('&#>&#x>')) + end + + def test_cgi_unescapeHTML_following_invalid_numeric + assert_equal('�>�>', CGI.unescapeHTML('�>�>')) + end + end + + include UnescapeHTMLTests + + Encoding.list.each do |enc| + begin + escaped = "'&"><".encode(enc) + unescaped = "'&\"><".encode(enc) + rescue Encoding::ConverterNotFoundError + next + else + define_method("test_cgi_escapeHTML:#{enc.name}") do + assert_equal(escaped, CGI.escapeHTML(unescaped)) + end + define_method("test_cgi_unescapeHTML:#{enc.name}") do + assert_equal(unescaped, CGI.unescapeHTML(escaped)) + end + end + end + + Encoding.list.each do |enc| + next unless enc.ascii_compatible? + begin + escaped = "%25+%2B" + unescaped = "% +".encode(enc) + rescue Encoding::ConverterNotFoundError + next + else + define_method("test_cgi_escape:#{enc.name}") do + assert_equal(escaped, CGI.escape(unescaped)) + end + define_method("test_cgi_unescape:#{enc.name}") do + assert_equal(unescaped, CGI.unescape(escaped, enc)) + end + end + end + + def test_cgi_unescapeHTML_uppercasecharacter + assert_equal("\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86", CGI.unescapeHTML("あいう")) + end + + def test_cgi_include_escape + assert_equal('%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93', escape(@str1)) + end + + def test_cgi_include_escapeHTML + assert_equal("'&"><", escapeHTML("'&\"><")) + end + + def test_cgi_include_h + assert_equal("'&"><", h("'&\"><")) + end + + def test_cgi_include_unescape + str = unescape('%26%3C%3E%22+%E3%82%86%E3%82%93%E3%82%86%E3%82%93') + assert_equal(@str1, str) + return unless defined?(::Encoding) + + assert_equal(@str1.encoding, str.encoding) + assert_equal("\u{30E1 30E2 30EA 691C 7D22}", unescape("\u{30E1 30E2 30EA}%E6%A4%9C%E7%B4%A2")) + end + + def test_cgi_include_unescapeHTML + assert_equal("'&\"><", unescapeHTML("'&"><")) + end + + def test_cgi_escapeElement + assert_equal("
<A HREF="url"></A>", escapeElement('
', "A", "IMG")) + assert_equal("
<A HREF="url"></A>", escapeElement('
', ["A", "IMG"])) + assert_equal("
<A HREF="url"></A>", escape_element('
', "A", "IMG")) + assert_equal("
<A HREF="url"></A>", escape_element('
', ["A", "IMG"])) + + assert_equal("<A <A HREF="url"></A>", escapeElement('', "A", "IMG")) + assert_equal("<A <A HREF="url"></A>", escapeElement('', ["A", "IMG"])) + assert_equal("<A <A HREF="url"></A>", escape_element('', "A", "IMG")) + assert_equal("<A <A HREF="url"></A>", escape_element('', ["A", "IMG"])) + + assert_equal("<A <A ", escapeElement('', unescapeElement(escapeHTML('
'), "A", "IMG")) + assert_equal('<BR>', unescapeElement(escapeHTML('
'), ["A", "IMG"])) + assert_equal('<BR>', unescape_element(escapeHTML('
'), "A", "IMG")) + assert_equal('<BR>', unescape_element(escapeHTML('
'), ["A", "IMG"])) + + assert_equal('', unescapeElement(escapeHTML(''), "A", "IMG")) + assert_equal('', unescapeElement(escapeHTML(''), ["A", "IMG"])) + assert_equal('', unescape_element(escapeHTML(''), "A", "IMG")) + assert_equal('', unescape_element(escapeHTML(''), ["A", "IMG"])) + + assert_equal('])) + end + + def test_cgi_unescapeHTML_with_invalid_byte_sequence + input = "\xFF&" + assert_equal(input, CGI.unescapeHTML(input)) + end +end diff --git a/test/mri/coverage/test_coverage.rb b/test/mri/coverage/test_coverage.rb index 9db1f8f2530..80f8930472a 100644 --- a/test/mri/coverage/test_coverage.rb +++ b/test/mri/coverage/test_coverage.rb @@ -192,6 +192,23 @@ def test_eval_coverage 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/mri/date/test_date.rb b/test/mri/date/test_date.rb index 3f9c893efa6..7e37fc94d26 100644 --- a/test/mri/date/test_date.rb +++ b/test/mri/date/test_date.rb @@ -134,6 +134,10 @@ def test_hash assert_equal(9, h[Date.new(1999,5,25)]) 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 diff --git a/test/mri/date/test_date_conv.rb b/test/mri/date/test_date_conv.rb index ed478b41bb3..8d810844355 100644 --- a/test/mri/date/test_date_conv.rb +++ b/test/mri/date/test_date_conv.rb @@ -82,18 +82,17 @@ def test_to_time__from_datetime 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]) - if Time.allocate.respond_to?(:subsec) + # TruffleRuby does not support more than nanoseconds + unless RUBY_ENGINE == 'truffleruby' 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]) + [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.subsec]) end end diff --git a/test/mri/date/test_date_parse.rb b/test/mri/date/test_date_parse.rb index 16362e3bfff..720624c02e3 100644 --- a/test/mri/date/test_date_parse.rb +++ b/test/mri/date/test_date_parse.rb @@ -544,6 +544,8 @@ def test__parse__2 h = Date._parse('') assert_equal({}, h) + + assert_raise(TypeError) {Date._parse(nil)} end def test_parse @@ -589,10 +591,13 @@ def test__parse_odd_offset end def test__parse_too_long_year - 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]) + # 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 str = "Jan - 1" + "0" * 100_000 h = EnvUtil.timeout(3) {Date._parse(str, limit: 100_010)} @@ -1301,4 +1306,20 @@ def test_length_limit 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/mri/date/test_date_ractor.rb b/test/mri/date/test_date_ractor.rb index 7ec953d87ae..91ea38bb934 100644 --- a/test/mri/date/test_date_ractor.rb +++ b/test/mri/date/test_date_ractor.rb @@ -8,7 +8,7 @@ def code(klass = Date, share: false) 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/mri/date/test_switch_hitter.rb b/test/mri/date/test_switch_hitter.rb index bdf299e0304..cc757825373 100644 --- a/test/mri/date/test_switch_hitter.rb +++ b/test/mri/date/test_switch_hitter.rb @@ -97,6 +97,11 @@ def test_jd [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/mri/did_you_mean/spell_checking/test_method_name_check.rb b/test/mri/did_you_mean/spell_checking/test_method_name_check.rb index 4daaf7cec74..2ae5fa7d03f 100644 --- a/test/mri/did_you_mean/spell_checking/test_method_name_check.rb +++ b/test/mri/did_you_mean/spell_checking/test_method_name_check.rb @@ -98,6 +98,8 @@ def nil.empty? 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 @@ def test_does_not_append_suggestions_twice 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/mri/did_you_mean/spell_checking/test_require_path_check.rb b/test/mri/did_you_mean/spell_checking/test_require_path_check.rb index d6c06e99991..1b96772d211 100644 --- a/test/mri/did_you_mean/spell_checking/test_require_path_check.rb +++ b/test/mri/did_you_mean/spell_checking/test_require_path_check.rb @@ -7,11 +7,11 @@ class RequirePathCheckTest < Test::Unit::TestCase def test_load_error_from_require_has_suggestions error = assert_raise LoadError do - require 'open_struct' + require 'open' end - assert_correction 'ostruct', error.corrections - assert_match "Did you mean? ostruct", get_message(error) + assert_correction 'open3', error.corrections + assert_match "Did you mean? open3", get_message(error) end def test_load_error_from_require_for_nested_files_has_suggestions diff --git a/test/mri/did_you_mean/test_ractor_compatibility.rb b/test/mri/did_you_mean/test_ractor_compatibility.rb index 7385f106122..3166d0b6c5f 100644 --- a/test/mri/did_you_mean/test_ractor_compatibility.rb +++ b/test/mri/did_you_mean/test_ractor_compatibility.rb @@ -14,7 +14,7 @@ class ::Book; end 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 @@ def test_key_name_suggestion_works_in_ractor 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 @@ def test_method_name_suggestion_works_in_ractor 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 @@ def test_pattern_key_name_suggestion_works_in_ractor 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 FirstNameError < NameError; end 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 @@ def test_variable_name_suggestion_works_in_ractor 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/mri/digest/test_ractor.rb b/test/mri/digest/test_ractor.rb index b34a3653b4c..d7b03eaebac 100644 --- a/test/mri/digest/test_ractor.rb +++ b/test/mri/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 @@ def test_s_hexdigest [r, hexdigest] end rs.each do |r, hexdigest| - puts r.take == hexdigest + puts r.value == hexdigest end end; end diff --git a/test/mri/dtrace/helper.rb b/test/mri/dtrace/helper.rb index 7fa16965f12..9e8c7ecd52c 100644 --- a/test/mri/dtrace/helper.rb +++ b/test/mri/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/mri/erb/test_erb.rb b/test/mri/erb/test_erb.rb index 555345a1401..09496d31e25 100644 --- a/test/mri/erb/test_erb.rb +++ b/test/mri/erb/test_erb.rb @@ -24,29 +24,6 @@ def test_with_filename 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 @@ def test_html_escape 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 @@ def test_version 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 = <') + erb = @erb.new(src, trim_mode: '>') assert_equal(ans.chomp, erb.result) ans = <') + erb = @erb.new(src, trim_mode: '<>') assert_equal(ans, erb.result) ans = <') + erb = @erb.new(src, trim_mode: '%>') assert_equal(ans.chomp, erb.result) ans = <') + erb = @erb.new(src, trim_mode: '%<>') assert_equal(ans, erb.result) end @@ -679,27 +650,6 @@ def test_half_working_comment_backward_compatibility 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)} diff --git a/test/mri/error_highlight/test_error_highlight.rb b/test/mri/error_highlight/test_error_highlight.rb index b75bf6d06cc..ec39b1c4db6 100644 --- a/test/mri/error_highlight/test_error_highlight.rb +++ b/test/mri/error_highlight/test_error_highlight.rb @@ -44,6 +44,17 @@ def preprocess(msg) def assert_error_message(klass, expected_msg, &blk) omit unless klass < ErrorHighlight::CoreExt err = assert_raise(klass, &blk) + unless klass == ArgumentError && err.message =~ /\A(?:wrong number of arguments|missing keyword[s]?|unknown keyword[s]?|no keywords accepted)\b/ + spot = ErrorHighlight.spot(err) + if spot + assert_kind_of(Integer, spot[:first_lineno]) + assert_kind_of(Integer, spot[:first_column]) + assert_kind_of(Integer, spot[:last_lineno]) + assert_kind_of(Integer, spot[:last_column]) + assert_kind_of(String, spot[:snippet]) + assert_kind_of(Array, spot[:script_lines]) + end + end assert_equal(preprocess(expected_msg).chomp, err.detailed_message(highlight: false).sub(/ \((?:NoMethod|Name)Error\)/, "")) end else @@ -880,27 +891,13 @@ def test_COLON2_4 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 @@ -1102,12 +1099,13 @@ def test_args_CALL_2 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 @@ -1190,16 +1188,16 @@ def test_args_OP_ASGN1_aref_1 end def test_args_OP_ASGN1_aref_2 - v = [] + v = method(:raise).to_proc assert_error_message(ArgumentError, <<~END) do -wrong number of arguments (given 0, expected 1..2) (ArgumentError) +ArgumentError (ArgumentError) - v [ ] += 42 - ^^^^^^^^ + v [ArgumentError] += 42 + ^^^^^^^^^^^^^^^^^^^^ END - v [ ] += 42 + v [ArgumentError] += 42 end end @@ -1444,6 +1442,199 @@ def exc.backtrace_locations = [] end end + begin + ->{}.call(1) + rescue ArgumentError => exc + MethodDefLocationSupported = + RubyVM::AbstractSyntaxTree.respond_to?(:node_id_for_backtrace_location) && + RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(exc.backtrace_locations.first) + end + + def process_callee_snippet(str) + return str if MethodDefLocationSupported + + str.sub(/\n +\|.*\n +\^+\n\z/, "") + end + + WRONG_NUMBER_OF_ARGUMENTS_LINENO = __LINE__ + 1 + def wrong_number_of_arguments_test(x, y) + x + y + end + + def test_wrong_number_of_arguments_for_method + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +wrong number of arguments (given 1, expected 2) (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | wrong_number_of_arguments_test(1) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ WRONG_NUMBER_OF_ARGUMENTS_LINENO } + | def wrong_number_of_arguments_test(x, y) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + END + + wrong_number_of_arguments_test(1) + end + end + + KEYWORD_TEST_LINENO = __LINE__ + 1 + def keyword_test(kw1:, kw2:, kw3:) + kw1 + kw2 + kw3 + end + + def test_missing_keyword + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +missing keyword: :kw3 (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | keyword_test(kw1: 1, kw2: 2) + ^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ + END + + keyword_test(kw1: 1, kw2: 2) + end + end + + def test_missing_keywords # multiple missing keywords + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +missing keywords: :kw2, :kw3 (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | keyword_test(kw1: 1) + ^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ + END + + keyword_test(kw1: 1) + end + end + + def test_unknown_keyword + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +unknown keyword: :kw4 (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4) + ^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ + END + + keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4) + end + end + + def test_unknown_keywords + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +unknown keywords: :kw4, :kw5 (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5) + ^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ + END + + keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5) + end + end + + WRONG_NUBMER_OF_ARGUMENTS_TEST2_LINENO = __LINE__ + 1 + def wrong_number_of_arguments_test2( + long_argument_name_x, + long_argument_name_y, + long_argument_name_z + ) + long_argument_name_x + long_argument_name_y + long_argument_name_z + end + + def test_wrong_number_of_arguments_for_method2 + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +wrong number of arguments (given 1, expected 3) (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | wrong_number_of_arguments_test2(1) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ WRONG_NUBMER_OF_ARGUMENTS_TEST2_LINENO } + | def wrong_number_of_arguments_test2( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + END + + wrong_number_of_arguments_test2(1) + end + end + + def test_wrong_number_of_arguments_for_lambda_literal + v = -> {} + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +wrong number of arguments (given 1, expected 0) (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | v.call(1) + ^^^^^ + callee: #{ __FILE__ }:#{ lineno - 1 } + | v = -> {} + ^^ + END + + v.call(1) + end + end + + def test_wrong_number_of_arguments_for_lambda_method + v = lambda { } + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +wrong number of arguments (given 1, expected 0) (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | v.call(1) + ^^^^^ + callee: #{ __FILE__ }:#{ lineno - 1 } + | v = lambda { } + ^ + END + + v.call(1) + end + end + + DEFINE_METHOD_TEST_LINENO = __LINE__ + 1 + define_method :define_method_test do |x, y| + x + y + end + + def test_wrong_number_of_arguments_for_define_method + lineno = __LINE__ + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do +wrong number of arguments (given 1, expected 2) (ArgumentError) + + caller: #{ __FILE__ }:#{ lineno + 12 } + | define_method_test(1) + ^^^^^^^^^^^^^^^^^^ + callee: #{ __FILE__ }:#{ DEFINE_METHOD_TEST_LINENO } + | define_method :define_method_test do |x, y| + ^^ + END + + define_method_test(1) + end + end + def test_spoofed_filename Tempfile.create(["error_highlight_test", ".rb"], binmode: true) do |tmp| tmp << "module Dummy\nend\n" @@ -1512,6 +1703,54 @@ def test_spot_with_node 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/mri/etc/test_etc.rb b/test/mri/etc/test_etc.rb index c15c4f6e1e7..c2e3af6317e 100644 --- a/test/mri/etc/test_etc.rb +++ b/test/mri/etc/test_etc.rb @@ -21,7 +21,7 @@ def test_passwd 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 @@ def test_pathconf 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 @@ def test_sysconfdir 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/mri/fiber/scheduler.rb b/test/mri/fiber/scheduler.rb index ac19bba7a29..8f1ce4376b2 100644 --- a/test/mri/fiber/scheduler.rb +++ b/test/mri/fiber/scheduler.rb @@ -65,63 +65,79 @@ def next_timeout 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 @@ def io_select(...) 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 @@ def unblock(blocker, fiber) 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 @@ def address_resolve(hostname) end def blocking_operation_wait(work) - thread = Thread.new(&work) + thread = Thread.new{work.call} thread.join @@ -441,6 +488,33 @@ def blocking(&block) 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/mri/fiber/test_io.rb b/test/mri/fiber/test_io.rb index 39e32c59876..eea06f97c82 100644 --- a/test/mri/fiber/test_io.rb +++ b/test/mri/fiber/test_io.rb @@ -9,7 +9,7 @@ def test_read 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 @@ def test_heavy_read 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 @@ def test_heavy_read 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 @@ def test_close_while_reading_on_thread # 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/mri/fiber/test_io_close.rb b/test/mri/fiber/test_io_close.rb new file mode 100644 index 00000000000..742b40841d9 --- /dev/null +++ b/test/mri/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/mri/fiber/test_ractor.rb b/test/mri/fiber/test_ractor.rb index 3c4ccbd8e5f..7dd82eda627 100644 --- a/test/mri/fiber/test_ractor.rb +++ b/test/mri/fiber/test_ractor.rb @@ -17,7 +17,7 @@ def test_ractor_shareable Fiber.current.class end.resume end - assert_equal(Fiber, r.take) + assert_equal(Fiber, r.value) end; end end diff --git a/test/mri/fiber/test_scheduler.rb b/test/mri/fiber/test_scheduler.rb index 62424fc4893..d3696267f79 100644 --- a/test/mri/fiber/test_scheduler.rb +++ b/test/mri/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 @@ def scheduler.io_wait 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 @@ def test_autoload 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 @@ def test_condition_variable 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/mri/fiber/test_sleep.rb b/test/mri/fiber/test_sleep.rb index a7e88c03674..187f59dbd48 100644 --- a/test/mri/fiber/test_sleep.rb +++ b/test/mri/fiber/test_sleep.rb @@ -35,13 +35,13 @@ def test_sleep_returns_seconds_slept 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/mri/fiber/test_thread.rb b/test/mri/fiber/test_thread.rb index 5e3cc6d0e13..0247f330d94 100644 --- a/test/mri/fiber/test_thread.rb +++ b/test/mri/fiber/test_thread.rb @@ -90,6 +90,47 @@ def test_thread_join_blocking 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 diff --git a/test/mri/fileutils/test_fileutils.rb b/test/mri/fileutils/test_fileutils.rb index d2096a04cc1..92308d95573 100644 --- a/test/mri/fileutils/test_fileutils.rb +++ b/test/mri/fileutils/test_fileutils.rb @@ -955,16 +955,27 @@ def test_ln_pathname 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 @@ def test_ln_sf_pathname 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 @@ def test_chmod_symbol_mode # 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 @@ def test_remove_dir_file_perm 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/mri/io/console/test_io_console.rb b/test/mri/io/console/test_io_console.rb index 2bf3df64392..c3f9c91c7de 100644 --- a/test/mri/io/console/test_io_console.rb +++ b/test/mri/io/console/test_io_console.rb @@ -7,6 +7,11 @@ 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 @@ def assert_ctrl(expect, cc, r, w) 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 @@ def test_intr # 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 @@ def test_intr 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 @@ def test_sync 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 @@ def test_ttyname 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/mri/io/console/test_ractor.rb b/test/mri/io/console/test_ractor.rb index b30988f47e0..dff0c67eab0 100644 --- a/test/mri/io/console/test_ractor.rb +++ b/test/mri/io/console/test_ractor.rb @@ -8,6 +8,10 @@ def test_ractor 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 @@ def test_ractor 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/mri/io/wait/test_io_wait.rb b/test/mri/io/wait/test_io_wait.rb index cbc01f96224..ffe3b1f760a 100644 --- a/test/mri/io/wait/test_io_wait.rb +++ b/test/mri/io/wait/test_io_wait.rb @@ -4,9 +4,6 @@ require 'timeout' require 'socket' -# For `IO#ready?` and `IO#nread`: -require 'io/wait' - class TestIOWait < Test::Unit::TestCase def setup @@ -22,33 +19,6 @@ def teardown @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) @@ -78,7 +48,8 @@ def test_wait_eof 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 @@ def test_wait_readable_eof 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/mri/io/wait/test_io_wait_uncommon.rb b/test/mri/io/wait/test_io_wait_uncommon.rb index 0f922f4e24b..15b13b51b10 100644 --- a/test/mri/io/wait/test_io_wait_uncommon.rb +++ b/test/mri/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 @@ def test_wait_readable_zero 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/mri/io/wait/test_ractor.rb b/test/mri/io/wait/test_ractor.rb index 800216e610f..c77a29bff3e 100644 --- a/test/mri/io/wait/test_ractor.rb +++ b/test/mri/io/wait/test_ractor.rb @@ -7,11 +7,15 @@ def test_ractor 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/mri/json/fixtures/fail15.json b/test/mri/json/fixtures/fail15.json new file mode 100644 index 00000000000..fc8376b605d --- /dev/null +++ b/test/mri/json/fixtures/fail15.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"] \ No newline at end of file diff --git a/test/mri/json/fixtures/fail16.json b/test/mri/json/fixtures/fail16.json new file mode 100644 index 00000000000..c43ae3c2868 --- /dev/null +++ b/test/mri/json/fixtures/fail16.json @@ -0,0 +1 @@ +["Illegal backslash escape: \'"] \ No newline at end of file diff --git a/test/mri/json/fixtures/fail17.json b/test/mri/json/fixtures/fail17.json new file mode 100644 index 00000000000..62b9214aeda --- /dev/null +++ b/test/mri/json/fixtures/fail17.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"] \ No newline at end of file diff --git a/test/mri/json/fixtures/fail26.json b/test/mri/json/fixtures/fail26.json new file mode 100644 index 00000000000..845d26a6a54 --- /dev/null +++ b/test/mri/json/fixtures/fail26.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "] \ No newline at end of file diff --git a/test/mri/json/fixtures/pass1.json b/test/mri/json/fixtures/pass1.json index 7828fcc1374..fa9058b1366 100644 --- a/test/mri/json/fixtures/pass1.json +++ b/test/mri/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/mri/json/json_addition_test.rb b/test/mri/json/json_addition_test.rb index 1eb269c2f68..4d8d1868732 100644 --- a/test/mri/json/json_addition_test.rb +++ b/test/mri/json/json_addition_test.rb @@ -44,10 +44,6 @@ def to_json(*args) end class B - def self.json_creatable? - false - end - def to_json(*args) { 'json_class' => self.class.name, @@ -56,10 +52,6 @@ def to_json(*args) end class C - def self.json_creatable? - false - end - def to_json(*args) { 'json_class' => 'JSONAdditionTest::Nix', @@ -69,7 +61,6 @@ def to_json(*args) 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 @@ def test_extended_json 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 @@ def test_extended_json_default 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 @@ def test_extended_json_disabled 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/mri/json/json_coder_test.rb b/test/mri/json/json_coder_test.rb new file mode 100755 index 00000000000..47e12ff919d --- /dev/null +++ b/test/mri/json/json_coder_test.rb @@ -0,0 +1,149 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative 'test_helper' + +class JSONCoderTest < Test::Unit::TestCase + def test_json_coder_with_proc + coder = JSON::Coder.new do |object| + "[Object object]" + end + assert_equal %(["[Object object]"]), coder.dump([Object.new]) + end + + def test_json_coder_with_proc_with_unsupported_value + coder = JSON::Coder.new do |object, is_key| + assert_equal false, is_key + Object.new + end + assert_raise(JSON::GeneratorError) { coder.dump([Object.new]) } + end + + def test_json_coder_hash_key + obj = Object.new + coder = JSON::Coder.new do |obj, is_key| + assert_equal true, is_key + obj.to_s + end + assert_equal %({#{obj.to_s.inspect}:1}), coder.dump({ obj => 1 }) + + coder = JSON::Coder.new { 42 } + error = assert_raise JSON::GeneratorError do + coder.dump({ obj => 1 }) + end + assert_equal "Integer not allowed as object key in JSON", error.message + end + + def test_json_coder_options + coder = JSON::Coder.new(array_nl: "\n") do |object| + 42 + end + + assert_equal "[\n42\n]", coder.dump([Object.new]) + end + + def test_json_coder_load + coder = JSON::Coder.new + assert_equal [1,2,3], coder.load("[1,2,3]") + end + + def test_json_coder_load_options + coder = JSON::Coder.new(symbolize_names: true) + assert_equal({a: 1}, coder.load('{"a":1}')) + end + + def test_json_coder_dump_NaN_or_Infinity + coder = JSON::Coder.new { |o| o.inspect } + assert_equal "NaN", coder.load(coder.dump(Float::NAN)) + assert_equal "Infinity", coder.load(coder.dump(Float::INFINITY)) + assert_equal "-Infinity", coder.load(coder.dump(-Float::INFINITY)) + end + + def test_json_coder_dump_NaN_or_Infinity_loop + coder = JSON::Coder.new { |o| o.itself } + error = assert_raise JSON::GeneratorError do + coder.dump(Float::NAN) + end + assert_include error.message, "NaN not allowed in JSON" + end + + def test_json_coder_string_invalid_encoding + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object + end + + error = assert_raise JSON::GeneratorError do + coder.dump("\xFF") + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.dup + end + + error = assert_raise JSON::GeneratorError do + coder.dump("\xFF") + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.bytes + end + + assert_equal "[255]", coder.dump("\xFF") + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "Array not allowed as object key in JSON", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + [object].pack("m") + end + + assert_equal '"/w==\\n"', coder.dump("\xFF") + assert_equal 1, calls + + assert_equal '{"/w==\\n":1}', coder.dump({ "\xFF" => 1 }) + assert_equal 2, calls + end + + def test_depth + coder = JSON::Coder.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ", depth: 1) + assert_equal %({\n "foo": 42\n }), coder.dump(foo: 42) + end + + def test_nesting_recovery + coder = JSON::Coder.new + ary = [] + ary << ary + assert_raise JSON::NestingError do + coder.dump(ary) + end + assert_equal '{"a":1}', coder.dump({ a: 1 }) + end +end diff --git a/test/mri/json/json_common_interface_test.rb b/test/mri/json/json_common_interface_test.rb index 1f157da0260..3dfd0623cd9 100644 --- a/test/mri/json/json_common_interface_test.rb +++ b/test/mri/json/json_common_interface_test.rb @@ -68,11 +68,6 @@ def test_create_id 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 +86,30 @@ def test_fast_generate 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 +129,7 @@ def test_load 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,6 +149,7 @@ def test_load_with_proc 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 @@ -138,6 +158,88 @@ def test_load_null assert_raise(JSON::ParserError) { JSON.load('', nil, :allow_blank => false) } 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 too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' obj = eval(too_deep) @@ -174,9 +276,9 @@ def test_dump_in_io 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 @@ -211,6 +313,12 @@ def test_load_file_with_bad_default_external_encoding 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/mri/json/json_encoding_test.rb b/test/mri/json/json_encoding_test.rb index afffd8976ad..7ac06b2a7b0 100644 --- a/test/mri/json/json_encoding_test.rb +++ b/test/mri/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 @@ def test_generate 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 @@ def test_unicode 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 @@ def test_chars 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 @@ def test_chars 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/mri/json/json_ext_parser_test.rb b/test/mri/json/json_ext_parser_test.rb index da615049893..e610f642f19 100644 --- a/test/mri/json/json_ext_parser_test.rb +++ b/test/mri/json/json_ext_parser_test.rb @@ -6,24 +6,43 @@ class JSONExtParserTest < Test::Unit::TestCase def test_allocate parser = JSON::Ext::Parser.new("{}") - assert_raise(TypeError, '[ruby-core:35079]') do - parser.__send__(:initialize, "{}") - end + parser.__send__(:initialize, "{}") + assert_equal "{}", parser.source + parser = JSON::Ext::Parser.allocate - assert_raise(TypeError, '[ruby-core:35079]') { parser.source } + assert_nil parser.source 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/mri/json/json_fixtures_test.rb b/test/mri/json/json_fixtures_test.rb index adcdffbb5e2..c0d10379393 100644 --- a/test/mri/json/json_fixtures_test.rb +++ b/test/mri/json/json_fixtures_test.rb @@ -2,39 +2,27 @@ require_relative 'test_helper' class JSONFixturesTest < Test::Unit::TestCase - def setup - fixtures = File.join(File.dirname(__FILE__), 'fixtures/{fail,pass}*.json') - passed, failed = Dir[fixtures].partition { |f| f['pass'] } - @passed = passed.inject([]) { |a, f| a << [ f, File.read(f) ] }.sort - @failed = failed.inject([]) { |a, f| a << [ f, File.read(f) ] }.sort - end + fixtures = File.join(File.dirname(__FILE__), 'fixtures/{fail,pass}*.json') + passed, failed = Dir[fixtures].partition { |f| f['pass'] } - def test_passing - verbose_bak, $VERBOSE = $VERBOSE, nil - for name, source in @passed - begin - assert JSON.parse(source), - "Did not pass for fixture '#{name}': #{source.inspect}" - rescue => e - warn "\nCaught #{e.class}(#{e}) for fixture '#{name}': #{source.inspect}\n#{e.backtrace * "\n"}" - raise e - end + passed.each do |f| + name = File.basename(f).gsub(".", "_") + 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 - ensure - $VERBOSE = verbose_bak end - def test_failing - for name, source in @failed + failed.each do |f| + name = File.basename(f).gsub(".", "_") + source = File.read(f) + define_method("test_#{name}") do assert_raise(JSON::ParserError, JSON::NestingError, "Did not fail for fixture '#{name}': #{source.inspect}") do JSON.parse(source) end end end - - def test_sanity - assert(@passed.size > 5) - assert(@failed.size > 20) - end end diff --git a/test/mri/json/json_generator_test.rb b/test/mri/json/json_generator_test.rb index 8dd3913d625..9f8b35de093 100755 --- a/test/mri/json/json_generator_test.rb +++ b/test/mri/json/json_generator_test.rb @@ -86,6 +86,50 @@ def test_dump_strict assert_equal '42', dump(42, strict: true) assert_equal 'true', dump(true, strict: true) + + assert_equal '"hello"', dump(:hello, strict: true) + assert_equal '"hello"', :hello.to_json(strict: true) + assert_equal '"World"', "World".to_json(strict: true) + end + + def test_state_depth_to_json + depth = Object.new + def depth.to_json(state) + JSON::State.from_state(state).depth.to_s + end + + assert_equal "0", JSON.generate(depth) + assert_equal "[1]", JSON.generate([depth]) + assert_equal %({"depth":1}), JSON.generate(depth: depth) + assert_equal "[[2]]", JSON.generate([[depth]]) + assert_equal %([{"depth":2}]), JSON.generate([{depth: depth}]) + + state = JSON::State.new + assert_equal "0", state.generate(depth) + assert_equal "[1]", state.generate([depth]) + assert_equal %({"depth":1}), state.generate(depth: depth) + assert_equal "[[2]]", state.generate([[depth]]) + assert_equal %([{"depth":2}]), state.generate([{depth: depth}]) + end + + def test_state_depth_to_json_recursive + recur = Object.new + def recur.to_json(state = nil, *) + state = JSON::State.from_state(state) + if state.depth < 3 + state.generate([state.depth, self]) + else + state.generate([state.depth]) + end + end + + assert_raise(NestingError) { JSON.generate(recur, max_nesting: 3) } + assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4) + + state = JSON::State.new(max_nesting: 3) + assert_raise(NestingError) { state.generate(recur) } + state.max_nesting = 4 + assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4) end def test_generate_pretty @@ -118,6 +162,22 @@ def test_generate_pretty assert_equal '666', pretty_generate(666) end + def test_generate_pretty_custom + state = State.new(:space_before => "", :space => "", :indent => "", :object_nl => "\n\n", :array_nl => "") + json = pretty_generate({1=>{}, 2=>['a','b'], 3=>4}, state) + assert_equal(<<~'JSON'.chomp, json) + { + + "1":{}, + + "2":["a","b"], + + "3":4 + + } + JSON + end + def test_generate_custom state = State.new(:space_before => " ", :space => " ", :indent => "", :object_nl => "\n", :array_nl => "") json = generate({1=>{2=>3,4=>[5,6]}}, state) @@ -132,15 +192,17 @@ def test_generate_custom end def test_fast_generate - json = fast_generate(@hash) - assert_equal(parse(@json2), parse(json)) - parsed_json = parse(json) - assert_equal(@hash, parsed_json) - json = fast_generate({1=>2}) - assert_equal('{"1":2}', json) - parsed_json = parse(json) - assert_equal({"1"=>2}, parsed_json) - assert_equal '666', fast_generate(666) + assert_deprecated_warning(/fast_generate/) do + json = fast_generate(@hash) + assert_equal(parse(@json2), parse(json)) + parsed_json = parse(json) + assert_equal(@hash, parsed_json) + json = fast_generate({1=>2}) + assert_equal('{"1":2}', json) + parsed_json = parse(json) + assert_equal({"1"=>2}, parsed_json) + assert_equal '666', fast_generate(666) + end end def test_own_state @@ -161,7 +223,9 @@ def test_states assert_equal('{"1":2}', json) s = JSON.state.new assert s.check_circular? - assert s[:check_circular?] + assert_deprecated_warning(/JSON::State/) do + assert s[:check_circular?] + end h = { 1=>2 } h[3] = h assert_raise(JSON::NestingError) { generate(h) } @@ -171,7 +235,9 @@ def test_states a << a assert_raise(JSON::NestingError) { generate(a, s) } assert s.check_circular? - assert s[:check_circular?] + assert_deprecated_warning(/JSON::State/) do + assert s[:check_circular?] + end end def test_falsy_state @@ -195,29 +261,12 @@ def test_falsy_state ) end - def test_pretty_state - state = JSON.create_pretty_state - assert_equal({ - :allow_nan => false, - :array_nl => "\n", - :ascii_only => false, - :buffer_initial_length => 1024, - :depth => 0, - :script_safe => false, - :strict => false, - :indent => " ", - :max_nesting => 100, - :object_nl => "\n", - :space => " ", - :space_before => "", - }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s }) - end - - def test_safe_state + def test_state_defaults state = JSON::State.new assert_equal({ :allow_nan => false, :array_nl => "", + :as_json => false, :ascii_only => false, :buffer_initial_length => 1024, :depth => 0, @@ -229,20 +278,20 @@ def test_safe_state :space => "", :space_before => "", }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s }) - end - def test_fast_state - state = JSON.create_fast_state + state = JSON::State.new(allow_duplicate_key: true) assert_equal({ + :allow_duplicate_key => true, :allow_nan => false, :array_nl => "", + :as_json => false, :ascii_only => false, :buffer_initial_length => 1024, :depth => 0, :script_safe => false, :strict => false, :indent => "", - :max_nesting => 0, + :max_nesting => 100, :object_nl => "", :space => "", :space_before => "", @@ -250,34 +299,122 @@ def test_fast_state 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 @@ -368,28 +505,37 @@ def to_s end def test_hash_likeness_set_symbol - state = JSON.state.new - assert_equal nil, state[:foo] - assert_equal nil.class, state[:foo].class - assert_equal nil, state['foo'] - state[:foo] = :bar - assert_equal :bar, state[:foo] - assert_equal :bar, state['foo'] - state_hash = state.to_hash - assert_kind_of Hash, state_hash - assert_equal :bar, state_hash[:foo] + assert_deprecated_warning(/JSON::State/) do + state = JSON.state.new + assert_equal nil, state[:foo] + assert_equal nil.class, state[:foo].class + assert_equal nil, state['foo'] + state[:foo] = :bar + assert_equal :bar, state[:foo] + assert_equal :bar, state['foo'] + state_hash = state.to_hash + assert_kind_of Hash, state_hash + assert_equal :bar, state_hash[:foo] + end end def test_hash_likeness_set_string + assert_deprecated_warning(/JSON::State/) do + state = JSON.state.new + assert_equal nil, state[:foo] + assert_equal nil, state['foo'] + state['foo'] = :bar + assert_equal :bar, state[:foo] + assert_equal :bar, state['foo'] + state_hash = state.to_hash + assert_kind_of Hash, state_hash + assert_equal :bar, state_hash[:foo] + end + end + + def test_json_state_to_h_roundtrip state = JSON.state.new - assert_equal nil, state[:foo] - assert_equal nil, state['foo'] - state['foo'] = :bar - assert_equal :bar, state[:foo] - assert_equal :bar, state['foo'] - state_hash = state.to_hash - assert_kind_of Hash, state_hash - assert_equal :bar, state_hash[:foo] + assert_equal state.to_h, JSON.state.new(state.to_h).to_h end def test_json_generate @@ -398,10 +544,30 @@ def test_json_generate 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 @@ -424,18 +590,34 @@ def test_backslash json = '["\\\\.(?i:gif|jpe?g|png)$"]' assert_equal json, generate(data) # - data = [ '\\"' ] - json = '["\\\\\""]' + data = [ '\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$' ] + json = '["\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$"]' + assert_equal json, generate(data) + # + data = [ '\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"' ] + json = '["\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\""]' assert_equal json, generate(data) # data = [ '/' ] json = '["/"]' assert_equal json, generate(data) # + data = [ '////////////////////////////////////////////////////////////////////////////////////' ] + json = '["////////////////////////////////////////////////////////////////////////////////////"]' + assert_equal json, generate(data) + # data = [ '/' ] json = '["\/"]' assert_equal json, generate(data, :script_safe => true) # + data = [ '///////////' ] + json = '["\/\/\/\/\/\/\/\/\/\/\/"]' + assert_equal json, generate(data, :script_safe => true) + # + data = [ '///////////////////////////////////////////////////////' ] + json = '["\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/"]' + assert_equal json, generate(data, :script_safe => true) + # data = [ "\u2028\u2029" ] json = '["\u2028\u2029"]' assert_equal json, generate(data, :script_safe => true) @@ -452,6 +634,22 @@ def test_backslash 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 = ["'"] json = '["\\\'"]' assert_equal '["\'"]', generate(data) @@ -459,6 +657,72 @@ def test_backslash data = ["倩", "瀨"] json = '["倩","瀨"]' assert_equal json, generate(data, script_safe: true) + # + data = '["This is a "test" of the emergency broadcast system."]' + json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\"" + assert_equal json, generate(data) + # + data = '\tThis is a test of the emergency broadcast system.' + json = "\"\\\\tThis is a test of the emergency broadcast system.\"" + assert_equal json, generate(data) + # + data = 'This\tis a test of the emergency broadcast system.' + json = "\"This\\\\tis a test of the emergency broadcast system.\"" + assert_equal json, generate(data) + # + data = 'This is\ta test of the emergency broadcast system.' + json = "\"This is\\\\ta test of the emergency broadcast system.\"" + assert_equal json, generate(data) + # + data = 'This is a test of the emergency broadcast\tsystem.' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\"" + assert_equal json, generate(data) + # + data = 'This is a test of the emergency broadcast\tsystem.\n' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\"" + assert_equal json, generate(data) + data = '"' * 15 + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\"" + assert_equal json, generate(data) + data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a" + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\"" + assert_equal json, generate(data) + data = "\u0001\u0001\u0001\u0001" + json = "\"\\u0001\\u0001\\u0001\\u0001\"" + assert_equal json, generate(data) + data = "\u0001a\u0001a\u0001a\u0001a" + json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\"" + assert_equal json, generate(data) + data = "\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\"" + assert_equal json, generate(data) + data = "\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal json, generate(data) + data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal json, generate(data) + data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002" + json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\"" + assert_equal json, generate(data) + data = "ab\u0002c" + json = "\"ab\\u0002c\"" + assert_equal json, generate(data) + data = "ab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal json, generate(data) + data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal json, generate(data) + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\"" + assert_equal json, generate(data) + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\"" + assert_equal json, generate(data) + data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t" + json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\"" + assert_equal json, generate(data) end def test_string_subclass @@ -619,6 +883,22 @@ def test_string_subclass_with_to_s 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 = "€™" @@ -661,4 +941,123 @@ def test_string_ext_included_calls_super def test_nonutf8_encoding assert_equal("\"5\u{b0}\"", "5\xb0".dup.force_encoding(Encoding::ISO_8859_1).to_json) end + + def test_utf8_multibyte + assert_equal('["foßbar"]', JSON.generate(["foßbar"])) + assert_equal('"n€ßt€ð2"', JSON.generate("n€ßt€ð2")) + assert_equal('"\"\u0000\u001f"', JSON.generate("\"\u0000\u001f")) + end + + def test_fragment + fragment = JSON::Fragment.new(" 42") + assert_equal '{"number": 42}', JSON.generate({ number: fragment }) + assert_equal '{"number": 42}', JSON.generate({ number: fragment }, strict: true) + end + + def test_json_generate_as_json_convert_to_proc + object = Object.new + assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: -> (o, is_key) { o.object_id }) + end + + def test_as_json_nan_does_not_call_to_json + def (obj = Object.new).to_json(*) + "null" + end + assert_raise(JSON::GeneratorError) do + JSON.generate(Float::NAN, strict: true, as_json: proc { obj }) + end + end + + def assert_float_roundtrip(expected, actual) + assert_equal(expected, JSON.generate(actual)) + assert_equal(actual, JSON.parse(JSON.generate(actual)), "JSON: #{JSON.generate(actual)}") + end + + def test_json_generate_float + assert_float_roundtrip "-1.0", -1.0 + assert_float_roundtrip "1.0", 1.0 + assert_float_roundtrip "0.0", 0.0 + assert_float_roundtrip "12.2", 12.2 + assert_float_roundtrip "2.34375", 7.5 / 3.2 + assert_float_roundtrip "12.0", 12.0 + assert_float_roundtrip "100.0", 100.0 + assert_float_roundtrip "1000.0", 1000.0 + + if RUBY_ENGINE == "jruby" + assert_float_roundtrip "1.7468619377842371E9", 1746861937.7842371 + else + assert_float_roundtrip "1746861937.7842371", 1746861937.7842371 + end + + if RUBY_ENGINE == "ruby" + assert_float_roundtrip "100000000000000.0", 100000000000000.0 + assert_float_roundtrip "1e+15", 1e+15 + assert_float_roundtrip "-100000000000000.0", -100000000000000.0 + assert_float_roundtrip "-1e+15", -1e+15 + assert_float_roundtrip "1111111111111111.1", 1111111111111111.1 + assert_float_roundtrip "1.1111111111111112e+16", 11111111111111111.1 + assert_float_roundtrip "-1111111111111111.1", -1111111111111111.1 + assert_float_roundtrip "-1.1111111111111112e+16", -11111111111111111.1 + + assert_float_roundtrip "-0.000000022471348024634545", -2.2471348024634545e-08 + assert_float_roundtrip "-0.0000000022471348024634545", -2.2471348024634545e-09 + assert_float_roundtrip "-2.2471348024634546e-10", -2.2471348024634545e-10 + end + end + + def test_numbers_of_various_sizes + numbers = [ + 0, 1, -1, 9, -9, 13, -13, 91, -91, 513, -513, 7513, -7513, + 17591, -17591, -4611686018427387904, 4611686018427387903, + 2**62, 2**63, 2**64, -(2**62), -(2**63), -(2**64) + ] + + numbers.each do |number| + assert_equal "[#{number}]", JSON.generate([number]) + end + end + + def test_generate_duplicate_keys_allowed + hash = { foo: 1, "foo" => 2 } + assert_equal %({"foo":1,"foo":2}), JSON.generate(hash, allow_duplicate_key: true) + end + + def test_generate_duplicate_keys_deprecated + hash = { foo: 1, "foo" => 2 } + assert_deprecated_warning(/allow_duplicate_key/) do + assert_equal %({"foo":1,"foo":2}), JSON.generate(hash) + end + end + + def test_generate_duplicate_keys_disallowed + hash = { foo: 1, "foo" => 2 } + error = assert_raise JSON::GeneratorError do + JSON.generate(hash, allow_duplicate_key: false) + end + assert_equal %(detected duplicate key "foo" in #{hash.inspect}), error.message + end + + 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 end diff --git a/test/mri/json/json_generic_object_test.rb b/test/mri/json/json_generic_object_test.rb index c14f5713b06..57e3bf3c52b 100644 --- a/test/mri/json/json_generic_object_test.rb +++ b/test/mri/json/json_generic_object_test.rb @@ -1,11 +1,20 @@ # frozen_string_literal: true require_relative 'test_helper' -class JSONGenericObjectTest < Test::Unit::TestCase - include JSON +# ostruct is required to test JSON::GenericObject +begin + require "ostruct" +rescue LoadError + return +end +class JSONGenericObjectTest < Test::Unit::TestCase def setup - @go = GenericObject[ :a => 1, :b => 2 ] + if defined?(JSON::GenericObject) + @go = JSON::GenericObject[ :a => 1, :b => 2 ] + else + omit("JSON::GenericObject is not available") + end end def test_attributes @@ -37,23 +46,23 @@ def test_parse_json ) 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 def test_from_hash - result = GenericObject.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 @@ -79,4 +88,4 @@ def switch_json_creatable ensure JSON::GenericObject.json_creatable = false end -end if defined?(JSON::GenericObject) +end diff --git a/test/mri/json/json_parser_test.rb b/test/mri/json/json_parser_test.rb index c01e28910f1..ec9391909d7 100644 --- a/test/mri/json/json_parser_test.rb +++ b/test/mri/json/json_parser_test.rb @@ -104,6 +104,14 @@ def test_parse_numbers assert_raise(JSON::ParserError) { parse('+23') } assert_raise(JSON::ParserError) { parse('.23') } assert_raise(JSON::ParserError) { parse('023') } + assert_raise(JSON::ParserError) { parse('-023') } + assert_raise(JSON::ParserError) { parse('023.12') } + assert_raise(JSON::ParserError) { parse('-023.12') } + assert_raise(JSON::ParserError) { parse('023e12') } + assert_raise(JSON::ParserError) { parse('-023e12') } + assert_raise(JSON::ParserError) { parse('-') } + assert_raise(JSON::ParserError) { parse('-.1') } + assert_raise(JSON::ParserError) { parse('-e0') } assert_equal(23, parse('23')) assert_equal(-23, parse('-23')) assert_equal_float(3.141, parse('3.141')) @@ -120,6 +128,13 @@ def test_parse_numbers 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")) end def test_parse_bigdecimals @@ -149,6 +164,20 @@ def test_parse_complex_objects 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_arrays assert_equal([1,2,3], parse('[1,2,3]')) assert_equal([1.2,2,3], parse('[1.2,2,3]')) @@ -297,6 +326,32 @@ def test_parse_broken_string end end + def test_invalid_unicode_escape + assert_raise(JSON::ParserError) { parse('"\u"') } + assert_raise(JSON::ParserError) { parse('"\ua"') } + assert_raise(JSON::ParserError) { parse('"\uaa"') } + assert_raise(JSON::ParserError) { parse('"\uaaa"') } + assert_equal "\uaaaa", parse('"\uaaaa"') + + assert_raise(JSON::ParserError) { parse('"\u______"') } + assert_raise(JSON::ParserError) { parse('"\u1_____"') } + assert_raise(JSON::ParserError) { parse('"\u11____"') } + assert_raise(JSON::ParserError) { parse('"\u111___"') } + end + + def test_unicode_followed_by_newline + # Ref: https://github.com/ruby/json/issues/912 + assert_equal "🌌\n".bytes, JSON.parse('"\ud83c\udf0c\n"').bytes + assert_equal "🌌\n", JSON.parse('"\ud83c\udf0c\n"') + assert_predicate JSON.parse('"\ud83c\udf0c\n"'), :valid_encoding? + end + + def test_invalid_surogates + assert_raise(JSON::ParserError) { parse('"\\uD800"') } + assert_raise(JSON::ParserError) { parse('"\\uD800_________________"') } + assert_raise(JSON::ParserError) { parse('"\\uD800\\u0041"') } + end + def test_parse_big_integers json1 = JSON(orig = (1 << 31) - 1) assert_equal orig, parse(json1) @@ -310,6 +365,52 @@ def test_parse_big_integers 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 # (obj) { obj == "a" ? fake_key : obj }) + end + end + end + def test_some_wrong_inputs assert_raise(ParserError) { parse('[] bla') } assert_raise(ParserError) { parse('[] 1') } @@ -341,10 +442,8 @@ def test_freeze assert_predicate parse('[]', :freeze => true), :frozen? assert_predicate parse('"foo"', :freeze => true), :frozen? - if string_deduplication_available? - assert_same(-'foo', parse('"foo"', :freeze => true)) - assert_same(-'foo', parse('{"foo": 1}', :freeze => true).keys.first) - end + assert_same(-'foo', parse('"foo"', :freeze => true)) + assert_same(-'foo', parse('{"foo": 1}', :freeze => true).keys.first) end def test_parse_comments @@ -393,6 +492,11 @@ def test_parse_comments } JSON assert_equal({ "key1" => "value1" }, parse(json)) + assert_equal({}, parse('{} /**/')) + assert_raise(ParserError) { parse('{} /* comment not closed') } + assert_raise(ParserError) { parse('{} /*/') } + assert_raise(ParserError) { parse('{} /x wrong comment') } + assert_raise(ParserError) { parse('{} /') } end def test_nesting @@ -427,13 +531,97 @@ def test_backslash data = ['"'] assert_equal data, parse(json) # - json = '["\\\'"]' - data = ["'"] + json = '["\\/"]' + data = ["/"] assert_equal data, parse(json) json = '["\/"]' data = [ '/' ] assert_equal data, parse(json) + + data = ['"""""""""""""""""""""""""'] + json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]' + assert_equal data, parse(json) + + data = '["This is a "test" of the emergency broadcast system."]' + json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\"" + assert_equal data, parse(json) + + data = '\tThis is a test of the emergency broadcast system.' + json = "\"\\\\tThis is a test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This\tis a test of the emergency broadcast system.' + json = "\"This\\\\tis a test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This is\ta test of the emergency broadcast system.' + json = "\"This is\\\\ta test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This is a test of the emergency broadcast\tsystem.' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\"" + assert_equal data, parse(json) + + data = 'This is a test of the emergency broadcast\tsystem.\n' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\"" + assert_equal data, parse(json) + + data = '"' * 15 + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\"" + assert_equal data, parse(json) + + data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a" + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\"" + assert_equal data, parse(json) + + data = "\u0001\u0001\u0001\u0001" + json = "\"\\u0001\\u0001\\u0001\\u0001\"" + assert_equal data, parse(json) + + data = "\u0001a\u0001a\u0001a\u0001a" + json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002" + json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\"" + assert_equal data, parse(json) + + data = "ab\u0002c" + json = "\"ab\\u0002c\"" + assert_equal data, parse(json) + + data = "ab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal data, parse(json) + + data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal data, parse(json) + + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\"" + assert_equal data, parse(json) + + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\"" + assert_equal data, parse(json) + + data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t" + json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\"" + assert_equal data, parse(json) end class SubArray < Array @@ -492,6 +680,7 @@ def test_parse_array_custom_array_derived_class def test_parse_array_custom_non_array_derived_class res = parse('[1,2]', :array_class => SubArrayWrapper) assert_equal([1,2], res.data) + assert_equal(1, res[0]) assert_equal(SubArrayWrapper, res.class) assert res.shifted? end @@ -553,6 +742,7 @@ def item_set? def test_parse_object_custom_non_hash_derived_class res = parse('{"foo":"bar"}', :object_class => SubOpenStruct) assert_equal "bar", res.foo + assert_equal "bar", res[:foo] assert_equal(SubOpenStruct, res.class) assert res.item_set? end @@ -612,7 +802,7 @@ def test_parse_error_message_length error = assert_raise(JSON::ParserError) do JSON.parse('{"foo": ' + ('A' * 500) + '}') end - assert_operator 60, :>, error.message.bytesize + assert_operator 80, :>, error.message.bytesize end def test_parse_error_incomplete_hash @@ -620,22 +810,46 @@ def test_parse_error_incomplete_hash JSON.parse('{"input":{"firstName":"Bob","lastName":"Mob","email":"bob@example.com"}') end if RUBY_ENGINE == "ruby" - assert_equal %(unexpected token at '{"input":{"firstName":"Bob","las'), error.message + assert_equal %(expected ',' or '}' after object value, got: EOF at line 1 column 72), error.message end end - private + def test_parse_error_snippet + omit "C ext only test" unless RUBY_ENGINE == "ruby" + + error = assert_raise(JSON::ParserError) { JSON.parse("あああああああああああああああああああああああ") } + assert_equal "unexpected character: 'ああああああああああ' at line 1 column 1", error.message - def string_deduplication_available? - r1 = rand.to_s - r2 = r1.dup - begin - (-r1).equal?(-r2) - rescue NoMethodError - false # No String#-@ + error = assert_raise(JSON::ParserError) { JSON.parse("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 + JSON.parse("/foo/bar") end end + def test_parse_whitespace_after_newline + assert_equal [], JSON.parse("[\n#{' ' * (8 + 8 + 4 + 3)}]") + end + + def test_frozen + parser_config = JSON::Parser::Config.new({}).freeze + assert_raise FrozenError do + parser_config.send(:initialize, {}) + end + end + + 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/mri/json/json_ryu_fallback_test.rb b/test/mri/json/json_ryu_fallback_test.rb new file mode 100644 index 00000000000..59ba76d392f --- /dev/null +++ b/test/mri/json/json_ryu_fallback_test.rb @@ -0,0 +1,169 @@ +# 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 +end diff --git a/test/mri/json/ractor_test.rb b/test/mri/json/ractor_test.rb index f857c9a8bf1..e53c405a744 100644 --- a/test/mri/json/ractor_test.rb +++ b/test/mri/json/ractor_test.rb @@ -8,8 +8,19 @@ 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 @@ def test_generate 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 @@ def test_generate _, 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/mri/json/test_helper.rb b/test/mri/json/test_helper.rb index d849e28b9b8..24cde4348cd 100644 --- a/test/mri/json/test_helper.rb +++ b/test/mri/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/mri/lib/jit_support.rb b/test/mri/lib/jit_support.rb index cf3baaaeb75..386a5a6f1ef 100644 --- a/test/mri/lib/jit_support.rb +++ b/test/mri/lib/jit_support.rb @@ -10,24 +10,20 @@ def yjit_supported? end def yjit_enabled? - defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled? + defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? end def yjit_force_enabled? "#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?YJIT_FORCE_ENABLE\b/) end - def rjit_supported? - return @rjit_supported if defined?(@rjit_supported) + def zjit_supported? + return @zjit_supported if defined?(@zjit_supported) # nil in mswin - @rjit_supported = ![nil, 'no'].include?(RbConfig::CONFIG['RJIT_SUPPORT']) + @zjit_supported = ![nil, 'no'].include?(RbConfig::CONFIG['ZJIT_SUPPORT']) end - def rjit_enabled? - defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? - end - - def rjit_force_enabled? - "#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?RJIT_FORCE_ENABLE\b/) + def zjit_enabled? + defined?(RubyVM::ZJIT) && RubyVM::ZJIT.enabled? end end diff --git a/test/mri/mkmf/test_pkg_config.rb b/test/mri/mkmf/test_pkg_config.rb index f6a960c7d99..adf5fa6e92b 100644 --- a/test/mri/mkmf/test_pkg_config.rb +++ b/test/mri/mkmf/test_pkg_config.rb @@ -46,21 +46,26 @@ def test_pkgconfig_with_option_returns_nil_on_error 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/mri/mmtk/helper.rb b/test/mri/mmtk/helper.rb new file mode 100644 index 00000000000..3bede9ed301 --- /dev/null +++ b/test/mri/mmtk/helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "test/unit" +require "core_assertions" + +module MMTk + class TestCase < ::Test::Unit::TestCase + include Test::Unit::CoreAssertions + + def setup + omit "Not running on MMTk" unless using_mmtk? + + @original_timeout_scale = EnvUtil.timeout_scale + timeout_scale = ENV["RUBY_TEST_TIMEOUT_SCALE"].to_f + EnvUtil.timeout_scale = timeout_scale if timeout_scale > 0 + + super + end + + def teardown + if using_mmtk? + EnvUtil.timeout_scale = @original_timeout_scale + end + end + + private + + def using_mmtk? + GC.config[:implementation] == "mmtk" + end + end +end diff --git a/test/mri/mmtk/test_configuration.rb b/test/mri/mmtk/test_configuration.rb new file mode 100644 index 00000000000..427cd9a0794 --- /dev/null +++ b/test/mri/mmtk/test_configuration.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require_relative "helper" + +module MMTk + class TestConfiguration < TestCase + def test_MMTK_THREADS + assert_separately([{ "MMTK_THREADS" => "5" }], <<~RUBY) + assert_equal(5, GC.config[:mmtk_worker_count]) + RUBY + + assert_separately([{ "MMTK_THREADS" => "1" }], <<~RUBY) + assert_equal(1, GC.config[:mmtk_worker_count]) + RUBY + end + + %w(NoGC MarkSweep Immix).each do |plan| + define_method(:"test_MMTK_PLAN_#{plan}") do + assert_separately([{ "MMTK_PLAN" => plan }], <<~RUBY) + assert_equal("#{plan}", GC.config[:mmtk_plan]) + RUBY + end + end + + %w(fixed dynamic).each do |heap| + define_method(:"test_MMTK_HEAP_MODE_#{heap}") do + assert_separately([{ "MMTK_HEAP_MODE" => heap }], <<~RUBY) + assert_equal("#{heap}", GC.config[:mmtk_heap_mode]) + RUBY + end + end + + def test_MMTK_HEAP_MIN + # Defaults to 1MiB + assert_separately([], <<~RUBY) + assert_equal(1 * 1024 * 1024, GC.config[:mmtk_heap_min]) + RUBY + + assert_separately([{ "MMTK_HEAP_MODE" => "dynamic", "MMTK_HEAP_MIN" => "1" }], <<~RUBY) + assert_equal(1, GC.config[:mmtk_heap_min]) + RUBY + + assert_separately([{ "MMTK_HEAP_MODE" => "dynamic", "MMTK_HEAP_MIN" => "10MiB", "MMTK_HEAP_MAX" => "1GiB" }], <<~RUBY) + assert_equal(10 * 1024 * 1024, GC.config[:mmtk_heap_min]) + RUBY + end + + def test_MMTK_HEAP_MIN_is_ignored_for_fixed_heaps + assert_separately([{ "MMTK_HEAP_MODE" => "fixed", "MMTK_HEAP_MIN" => "1" }], <<~RUBY) + assert_nil(GC.config[:mmtk_heap_min]) + RUBY + end + + def test_MMTK_HEAP_MAX + assert_separately([{ "MMTK_HEAP_MODE" => "fixed", "MMTK_HEAP_MAX" => "100MiB" }], <<~RUBY) + assert_equal(100 * 1024 * 1024, GC.config[:mmtk_heap_max]) + RUBY + end + + %w(MMTK_THREADS MMTK_HEAP_MIN MMTK_HEAP_MAX MMTK_HEAP_MODE MMTK_PLAN).each do |var| + define_method(:"test_invalid_#{var}") do + exit_code = assert_in_out_err( + [{ var => "foobar" }, "--"], + "", + [], + ["[FATAL] Invalid #{var} foobar"] + ) + + assert_equal(1, exit_code.exitstatus) + end + end + + def test_MMTK_HEAP_MIN_greater_than_or_equal_to_MMTK_HEAP_MAX + exit_code = assert_in_out_err( + [{ "MMTK_HEAP_MIN" => "100MiB", "MMTK_HEAP_MAX" => "10MiB" }, "--"], + "", + [], + ["[FATAL] MMTK_HEAP_MIN(104857600) >= MMTK_HEAP_MAX(10485760)"] + ) + + assert_equal(1, exit_code.exitstatus) + + exit_code = assert_in_out_err( + [{ "MMTK_HEAP_MIN" => "10MiB", "MMTK_HEAP_MAX" => "10MiB" }, "--"], + "", + [], + ["[FATAL] MMTK_HEAP_MIN(10485760) >= MMTK_HEAP_MAX(10485760)"] + ) + + assert_equal(1, exit_code.exitstatus) + end + end +end diff --git a/test/mri/net/http/test_http.rb b/test/mri/net/http/test_http.rb index a49cc87e8d2..4e7fa22756c 100644 --- a/test/mri/net/http/test_http.rb +++ b/test/mri/net/http/test_http.rb @@ -494,12 +494,10 @@ def _test_post__no_data(http) 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 @@ def test_timeout_during_HTTP_session_write 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 @@ def test_timeout_during_non_chunked_streamed_HTTP_session_write 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 @@ def test_partial_response 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/mri/net/http/test_http_request.rb b/test/mri/net/http/test_http_request.rb index 7fd82b03539..9f5cf4f8f56 100644 --- a/test/mri/net/http/test_http_request.rb +++ b/test/mri/net/http/test_http_request.rb @@ -74,6 +74,18 @@ def test_initialize_GET_uri 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 @@ def test_header_set '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/mri/net/http/test_https.rb b/test/mri/net/http/test_https.rb index e860c8745ed..f5b21b901ff 100644 --- a/test/mri/net/http/test_https.rb +++ b/test/mri/net/http/test_https.rb @@ -7,6 +7,8 @@ # should skip this test end +return unless defined?(OpenSSL::SSL) + class TestNetHTTPS < Test::Unit::TestCase include TestNetHTTPUtils @@ -19,7 +21,6 @@ def self.read_fixture(key) 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 @@ def self.read_fixture(key) '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 @@ def test_get_SNI 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 @@ def test_get_SNI_proxy 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 @@ def test_get_SNI_failure 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 @@ def test_post 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 @@ def test_session_reuse 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 @@ def test_certificate_verify_failure 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 @@ def test_max_version 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 @@ def test_max_version 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 @@ def self.read_fixture(key) 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 @@ def self.read_fixture(key) '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 @@ def test_identity_verify_failure 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/mri/net/http/test_https_proxy.rb b/test/mri/net/http/test_https_proxy.rb index f4c6aa0b6a0..237c16e64de 100644 --- a/test/mri/net/http/test_https_proxy.rb +++ b/test/mri/net/http/test_https_proxy.rb @@ -5,14 +5,10 @@ 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 @@ def read_fixture(key) 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 @@ def test_https_proxy_ssl_connection assert_join_threads([client_thread, server_thread]) } end -end if defined?(OpenSSL) +end diff --git a/test/mri/net/http/utils.rb b/test/mri/net/http/utils.rb index 6e7106cd487..0b9e440e7cb 100644 --- a/test/mri/net/http/utils.rb +++ b/test/mri/net/http/utils.rb @@ -1,6 +1,5 @@ # frozen_string_literal: false require 'socket' -require 'openssl' module TestNetHTTPUtils @@ -14,17 +13,16 @@ def initialize(config, &block) @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 @block = block end - # NOTE: patched by JRuby def start @thread = Thread.new do loop do @@ -43,7 +41,6 @@ def run(socket) handle_request(socket) end - # NOTE: patched by JRuby def shutdown @thread&.kill @thread&.join @@ -73,6 +70,11 @@ def handle_request(socket) 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}/"] @@ -308,16 +310,18 @@ def handle_post(path, headers, socket) 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/mri/objspace/test_objspace.rb b/test/mri/objspace/test_objspace.rb index e0dde3621cc..3d5b2a4d2ae 100644 --- a/test/mri/objspace/test_objspace.rb +++ b/test/mri/objspace/test_objspace.rb @@ -32,7 +32,7 @@ def test_memsize_of_root_shared_string 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?} + ObjectSpace.each_object(String) {|x| break c = x if a == x and x.frozen?} rv_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] assert_equal([rv_size, rv_size, a.length + 1 + rv_size], [a, b, c].map {|x| ObjectSpace.memsize_of(x)}) end @@ -203,8 +203,9 @@ def test_trace_object_allocations 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 +288,33 @@ def test_trace_object_allocations_gc_stress 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 +336,7 @@ def test_trace_object_allocations_compaction 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,11 +360,25 @@ def test_dump_flags # 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 @@ -486,6 +528,20 @@ def test_dump_dynamic_symbol assert_match(/"value":"foobar\h+"/, dump) end + def test_dump_outputs_object_id + obj = Object.new + + # Doesn't output object_id when it has not been seen + dump = ObjectSpace.dump(obj) + assert_not_include(dump, "\"object_id\"") + + id = obj.object_id + + # Outputs object_id when it has been seen + dump = ObjectSpace.dump(obj) + assert_include(dump, "\"object_id\":#{id}") + end + def test_dump_includes_imemo_type assert_in_out_err(%w[-robjspace], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| begin; @@ -771,6 +827,27 @@ def dump_my_heap_please end end + def test_dump_all_with_ractors + assert_ractor("#{<<-"begin;"}#{<<-'end;'}") + begin; + require "objspace" + require "tempfile" + require "json" + rs = 4.times.map do + Ractor.new do + Tempfile.create do |f| + ObjectSpace.dump_all(output: f) + f.close + File.readlines(f.path).each do |line| + JSON.parse(line) + end + end + end + end + rs.each(&:join) + end; + end + def test_dump_uninitialized_file assert_in_out_err(%[-robjspace], <<-RUBY) do |(output), (error)| puts ObjectSpace.dump(File.allocate) @@ -949,6 +1026,13 @@ def test_escape_class_name assert_equal class_name, JSON.parse(json)["name"] 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/mri/objspace/test_ractor.rb b/test/mri/objspace/test_ractor.rb index 4901eeae2e1..996d83fbd21 100644 --- a/test/mri/objspace/test_ractor.rb +++ b/test/mri/objspace/test_ractor.rb @@ -5,12 +5,78 @@ def test_tracing_does_not_crash 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', require: 'objspace') + 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/mri/open-uri/utils.rb b/test/mri/open-uri/utils.rb index 4b7c2b1c519..e7ca64bd1c2 100644 --- a/test/mri/open-uri/utils.rb +++ b/test/mri/open-uri/utils.rb @@ -1,6 +1,5 @@ require 'socket' require 'net/http' -require 'tmpdir' begin require 'openssl' rescue LoadError @@ -17,7 +16,6 @@ def mount_proc(path, proc) @procs[path] = proc end - # NOTE: patched by JRuby def start @thread = Thread.new do loop do @@ -28,9 +26,9 @@ def start end end - # NOTE: patched by JRuby def shutdown @thread.kill + @server.close end private @@ -162,7 +160,6 @@ def initialize(host, port, auth_proc = nil, log, access_log) @access_log = access_log end - # NOTE: patched by JRuby def start @thread = Thread.new do loop do @@ -182,9 +179,9 @@ def start end end - # NOTE: patched by JRuby def shutdown @thread.kill + @server.close end private @@ -273,7 +270,6 @@ def initialize(cert, key, dh, bind_addr, port, log) @ssl_server = OpenSSL::SSL::SSLServer.new(@server, context) end - # NOTE: patched by JRuby def start @thread = Thread.new do loop do @@ -285,8 +281,8 @@ def start end end - # NOTE: patched by JRuby def shutdown + @thread.kill @server.close end diff --git a/test/mri/openssl/fixtures/pkey/mldsa65-1.pem b/test/mri/openssl/fixtures/pkey/mldsa65-1.pem new file mode 100644 index 00000000000..21f08e3ac60 --- /dev/null +++ b/test/mri/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/mri/openssl/fixtures/pkey/mldsa65-2.pem b/test/mri/openssl/fixtures/pkey/mldsa65-2.pem new file mode 100644 index 00000000000..0ae64c2c5d2 --- /dev/null +++ b/test/mri/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/mri/openssl/test_asn1.rb b/test/mri/openssl/test_asn1.rb index 354b5878954..5978ecf6736 100644 --- a/test/mri/openssl/test_asn1.rb +++ b/test/mri/openssl/test_asn1.rb @@ -6,7 +6,7 @@ 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 @@ def test_null 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 @@ def test_object_identifier 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 @@ def test_sequence ]) 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 @@ def test_set 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 @@ def test_generalizedtime 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 @@ def test_basic_asn1data 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/mri/openssl/test_bn.rb b/test/mri/openssl/test_bn.rb index 1217f250a79..f663102d45c 100644 --- a/test/mri/openssl/test_bn.rb +++ b/test/mri/openssl/test_bn.rb @@ -321,6 +321,8 @@ def test_argument_error 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 @@ def test_get_flags_and_set_flags 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/mri/openssl/test_buffering.rb b/test/mri/openssl/test_buffering.rb index 7575c5b4fe4..466bbcfa231 100644 --- a/test/mri/openssl/test_buffering.rb +++ b/test/mri/openssl/test_buffering.rb @@ -31,7 +31,7 @@ def sysread(size) end def syswrite(str) - @io << str + @io.append_as_bytes(str) str.size end end diff --git a/test/mri/openssl/test_cipher.rb b/test/mri/openssl/test_cipher.rb index cd0b3dcb445..93766cfc88c 100644 --- a/test/mri/openssl/test_cipher.rb +++ b/test/mri/openssl/test_cipher.rb @@ -112,6 +112,9 @@ def test_initialize cipher = OpenSSL::Cipher.new("DES-EDE3-CBC") assert_raise(RuntimeError) { cipher.__send__(:initialize, "DES-EDE3-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 @@ -182,6 +185,10 @@ def test_update_raise_if_key_not_set 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 +219,8 @@ def test_aes_ccm 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 +273,19 @@ def test_aes_gcm 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 @@ -337,6 +345,24 @@ def test_aes_ocb_tag_len end if has_cipher?("aes-128-ocb") + def test_aes_gcm_siv + # 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 +389,7 @@ def test_aes_keywrap_pad 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/mri/openssl/test_config.rb b/test/mri/openssl/test_config.rb index 759a5bbd441..c10a855a4bf 100644 --- a/test/mri/openssl/test_config.rb +++ b/test/mri/openssl/test_config.rb @@ -43,6 +43,9 @@ def test_s_parse 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 @@ def test_get_value 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/mri/openssl/test_digest.rb b/test/mri/openssl/test_digest.rb index 988330e4058..2ef84cfa4c7 100644 --- a/test/mri/openssl/test_digest.rb +++ b/test/mri/openssl/test_digest.rb @@ -10,6 +10,12 @@ def setup @d2 = OpenSSL::Digest::MD5.new end + def test_initialize + assert_raise(OpenSSL::Digest::DigestError) { + OpenSSL::Digest.new("no such algorithm") + } + end + def test_digest null_hex = "d41d8cd98f00b204e9800998ecf8427e" null_bin = [null_hex].pack("H*") @@ -62,8 +68,17 @@ def test_digest_constants 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 +103,6 @@ def test_sha2 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 +114,22 @@ def test_sha512_truncate 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 + # 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") @@ -134,22 +147,6 @@ def test_digests assert_include digests, "sha256" 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 - - def digest_available?(name) - @digests ||= OpenSSL::Digest.digests - @digests.include?(name) - end end end diff --git a/test/mri/openssl/test_fips.rb b/test/mri/openssl/test_fips.rb index 4a3dd43a418..efc2655e25b 100644 --- a/test/mri/openssl/test_fips.rb +++ b/test/mri/openssl/test_fips.rb @@ -28,6 +28,8 @@ def test_fips_mode_get_is_false_on_fips_mode_disabled end def test_fips_mode_is_reentrant + return if aws_lc? # AWS-LC's FIPS mode is decided at compile time. + assert_separately(["-ropenssl"], <<~"end;") OpenSSL.fips_mode = false OpenSSL.fips_mode = false @@ -35,7 +37,10 @@ def test_fips_mode_is_reentrant 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/mri/openssl/test_kdf.rb b/test/mri/openssl/test_kdf.rb index f4790c96afa..6a12a25aa83 100644 --- a/test/mri/openssl/test_kdf.rb +++ b/test/mri/openssl/test_kdf.rb @@ -132,7 +132,6 @@ def test_scrypt_rfc7914_second end def test_hkdf_rfc5869_test_case_1 - pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0 hash = "sha256" ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") salt = B("000102030405060708090a0b0c") @@ -146,7 +145,6 @@ def test_hkdf_rfc5869_test_case_1 end def test_hkdf_rfc5869_test_case_3 - pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0 hash = "sha256" ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") salt = B("") @@ -160,7 +158,6 @@ def test_hkdf_rfc5869_test_case_3 end def test_hkdf_rfc5869_test_case_4 - pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0 hash = "sha1" ikm = B("0b0b0b0b0b0b0b0b0b0b0b") salt = B("000102030405060708090a0b0c") diff --git a/test/mri/openssl/test_ns_spki.rb b/test/mri/openssl/test_ns_spki.rb index d76fc9e5cfb..04844292897 100644 --- a/test/mri/openssl/test_ns_spki.rb +++ b/test/mri/openssl/test_ns_spki.rb @@ -17,8 +17,8 @@ def setup 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/mri/openssl/test_ocsp.rb b/test/mri/openssl/test_ocsp.rb index cf96fc22e51..b9b66ad37ad 100644 --- a/test/mri/openssl/test_ocsp.rb +++ b/test/mri/openssl/test_ocsp.rb @@ -13,7 +13,7 @@ def setup # @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 @@ def setup 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 @@ def setup 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 @@ def test_certificate_id_issuer_name_hash 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 diff --git a/test/mri/openssl/test_ossl.rb b/test/mri/openssl/test_ossl.rb index 3a90ead10a1..51262985f56 100644 --- a/test/mri/openssl/test_ossl.rb +++ b/test/mri/openssl/test_ossl.rb @@ -3,51 +3,45 @@ 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_secure_compare - refute OpenSSL.secure_compare("aaa", "a") - refute OpenSSL.secure_compare("aaa", "aa") + assert_false(OpenSSL.secure_compare("aaa", "a")) + assert_false(OpenSSL.secure_compare("aaa", "aa")) - assert OpenSSL.secure_compare("aaa", "aaa") + assert_true(OpenSSL.secure_compare("aaa", "aaa")) - refute OpenSSL.secure_compare("aaa", "aaaa") - refute OpenSSL.secure_compare("aaa", "baa") - refute OpenSSL.secure_compare("aaa", "aba") - refute OpenSSL.secure_compare("aaa", "aab") - refute OpenSSL.secure_compare("aaa", "aaab") - refute OpenSSL.secure_compare("aaa", "b") - refute OpenSSL.secure_compare("aaa", "bb") - refute OpenSSL.secure_compare("aaa", "bbb") - refute OpenSSL.secure_compare("aaa", "bbbb") + assert_false(OpenSSL.secure_compare("aaa", "aaaa")) + assert_false(OpenSSL.secure_compare("aaa", "baa")) + assert_false(OpenSSL.secure_compare("aaa", "aba")) + assert_false(OpenSSL.secure_compare("aaa", "aab")) + assert_false(OpenSSL.secure_compare("aaa", "aaab")) + assert_false(OpenSSL.secure_compare("aaa", "b")) + assert_false(OpenSSL.secure_compare("aaa", "bb")) + assert_false(OpenSSL.secure_compare("aaa", "bbb")) + assert_false(OpenSSL.secure_compare("aaa", "bbbb")) end def test_memcmp_timing - begin - require "benchmark" - rescue LoadError - pend "Benchmark is not available in this environment. Please install it with `gem install benchmark`." - end - # Ensure using fixed_length_secure_compare takes almost exactly the same amount of time to compare two different strings. # Regular string comparison will short-circuit on the first non-matching character, failing this test. # NOTE: this test may be susceptible to noise if the system running the tests is otherwise under load. @@ -58,24 +52,41 @@ def test_memcmp_timing a_b_time = a_c_time = 0 100.times do - a_b_time += Benchmark.measure { 100.times { OpenSSL.fixed_length_secure_compare(a, b) } }.real - a_c_time += Benchmark.measure { 100.times { OpenSSL.fixed_length_secure_compare(a, c) } }.real + t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 100.times { OpenSSL.fixed_length_secure_compare(a, b) } + t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 100.times { OpenSSL.fixed_length_secure_compare(a, c) } + t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + a_b_time += t2 - t1 + a_c_time += t3 - t2 end assert_operator(a_b_time, :<, a_c_time * 10, "fixed_length_secure_compare timing test failed") assert_operator(a_c_time, :<, a_b_time * 10, "fixed_length_secure_compare timing test failed") - end + end if ENV["OSSL_TEST_ALL"] == "1" def test_error_data - # X509V3_EXT_nconf_nid() called from OpenSSL::X509::ExtensionFactory#create_ext is a function - # that uses ERR_raise_data() to append additional information about the error. + # X509V3_EXT_nconf_nid() called from + # OpenSSL::X509::ExtensionFactory#create_ext is a function that uses + # ERR_raise_data() to append additional information about the error. # # The generated message should look like: # "subjectAltName = IP:not.a.valid.ip.address: bad ip address (value=not.a.valid.ip.address)" # "subjectAltName = IP:not.a.valid.ip.address: error in extension (name=subjectAltName, value=IP:not.a.valid.ip.address)" + # + # The string inside parentheses is the ERR_TXT_STRING data, and is appended + # by ossl_make_error(), so we check it here. ef = OpenSSL::X509::ExtensionFactory.new - assert_raise_with_message(OpenSSL::X509::ExtensionError, /value=(IP:)?not.a.valid.ip.address\)/) { + e = assert_raise(OpenSSL::X509::ExtensionError) { ef.create_ext("subjectAltName", "IP:not.a.valid.ip.address") } + assert_match(/not.a.valid.ip.address\)\z/, e.message) + + # We currently craft the strings based on ERR_error_string()'s style: + # error:::: (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/mri/openssl/test_pkcs12.rb b/test/mri/openssl/test_pkcs12.rb index 68a23b28c08..1b5328774e4 100644 --- a/test/mri/openssl/test_pkcs12.rb +++ b/test/mri/openssl/test_pkcs12.rb @@ -178,6 +178,8 @@ def test_create_with_mac_itr 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", diff --git a/test/mri/openssl/test_pkcs7.rb b/test/mri/openssl/test_pkcs7.rb index 862716b4d82..b3129c0cdfe 100644 --- a/test/mri/openssl/test_pkcs7.rb +++ b/test/mri/openssl/test_pkcs7.rb @@ -6,92 +6,125 @@ 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 @@ def test_signed_add_signer 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 @@ def test_enveloped assert_equal(:enveloped, p7.type) assert_equal(2, recip.size) - assert_equal(@ca_cert.subject.to_s, recip[0].issuer.to_s) - assert_equal(2, recip[0].serial) - assert_equal(data, p7.decrypt(@rsa1024, @ee1_cert)) + assert_equal(@ca_cert.subject, recip[0].issuer) + assert_equal(@ee1_cert.serial, recip[0].serial) + assert_equal(16, @ee1_key.decrypt(recip[0].enc_key).size) + assert_equal(data, p7.decrypt(@ee1_key, @ee1_cert)) - assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s) - assert_equal(3, recip[1].serial) - assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert)) + assert_equal(@ca_cert.subject, recip[1].issuer) + assert_equal(@ee2_cert.serial, recip[1].serial) + assert_equal(data, p7.decrypt(@ee2_key, @ee2_cert)) - assert_equal(data, p7.decrypt(@rsa1024)) + assert_equal(data, p7.decrypt(@ee1_key)) + + assert_raise(OpenSSL::PKCS7::PKCS7Error) { + p7.decrypt(@ca_key, @ca_cert) + } # Default cipher has been removed in v3.3 assert_raise_with_message(ArgumentError, /RC2-40-CBC/) { @@ -160,9 +250,61 @@ def test_enveloped } end + def test_enveloped_add_recipient + omit_on_fips # PKCS #1 v1.5 padding + + data = "aaaaa\nbbbbb\nccccc\n" + ktri_ee1 = OpenSSL::PKCS7::RecipientInfo.new(@ee1_cert) + ktri_ee2 = OpenSSL::PKCS7::RecipientInfo.new(@ee2_cert) + + tmp = OpenSSL::PKCS7.new + tmp.type = :enveloped + tmp.cipher = "AES-128-CBC" + tmp.add_recipient(ktri_ee1) + tmp.add_recipient(ktri_ee2) + tmp.add_data(data) + + p7 = OpenSSL::PKCS7.new(tmp.to_der) + assert_equal(:enveloped, p7.type) + assert_equal(data, p7.decrypt(@ee1_key, @ee1_cert)) + assert_equal(data, p7.decrypt(@ee2_key, @ee2_cert)) + assert_equal([@ee1_cert.serial, @ee2_cert.serial].sort, + p7.recipients.map(&:serial).sort) + end + + def test_data + asn1 = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::ObjectId("pkcs7-data"), + OpenSSL::ASN1::OctetString("content", 0, :EXPLICIT), + ]) + p7 = OpenSSL::PKCS7.new + p7.type = :data + p7.data = "content" + assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.add_certificate(@ee1_cert) } + assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.certificates = [@ee1_cert] } + assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.cipher = "aes-128-cbc" } + assert_equal(asn1.to_der, p7.to_der) + + p7 = OpenSSL::PKCS7.new(asn1) + assert_equal(:data, p7.type) + assert_equal(false, p7.detached) + assert_equal(false, p7.detached?) + # Not applicable + assert_nil(p7.certificates) + assert_nil(p7.crls) + # Not applicable. Should they return nil or raise an exception instead? + assert_equal([], p7.signers) + assert_equal([], p7.recipients) + # PKCS7#verify can't distinguish verification failure and other errors + store = OpenSSL::X509::Store.new + assert_equal(false, p7.verify([@ee1_cert], store)) + assert_match(/wrong content type|WRONG_CONTENT_TYPE/, p7.error_string) + assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.decrypt(@ee1_key) } + end + def test_empty_signed_data_ruby_bug_19974 data = "-----BEGIN PKCS7-----\nMAsGCSqGSIb3DQEHAg==\n-----END PKCS7-----\n" - assert_raise(ArgumentError) { OpenSSL::PKCS7.new(data) } + assert_raise(OpenSSL::PKCS7::PKCS7Error) { OpenSSL::PKCS7.new(data) } data = < "abcd" }) @@ -90,8 +180,6 @@ def test_hmac_sign_verify def test_ed25519 # Ed25519 is not FIPS-approved. omit_on_fips - # See EVP_PKEY_sign in Changelog for 3.7.0: https://github.com/libressl/portable/blob/master/ChangeLog - omit "Ed25519 not supported" unless openssl?(1, 1, 1) || libressl?(3, 7, 0) # Test vector from RFC 8032 Section 7.1 TEST 2 priv_pem = <<~EOF @@ -112,18 +200,14 @@ def test_ed25519 assert_equal pub_pem, priv.public_to_pem assert_equal pub_pem, pub.public_to_pem - begin - assert_equal "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", - priv.raw_private_key.unpack1("H*") - assert_equal OpenSSL::PKey.new_raw_private_key("ED25519", priv.raw_private_key).private_to_pem, - priv.private_to_pem - assert_equal "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", - priv.raw_public_key.unpack1("H*") - assert_equal OpenSSL::PKey.new_raw_public_key("ED25519", priv.raw_public_key).public_to_pem, - pub.public_to_pem - rescue NoMethodError - pend "running OpenSSL version does not have raw public key support" - end + assert_equal "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", + priv.raw_private_key.unpack1("H*") + assert_equal OpenSSL::PKey.new_raw_private_key("ED25519", priv.raw_private_key).private_to_pem, + priv.private_to_pem + assert_equal "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", + priv.raw_public_key.unpack1("H*") + assert_equal OpenSSL::PKey.new_raw_public_key("ED25519", priv.raw_public_key).public_to_pem, + pub.public_to_pem sig = [<<~EOF.gsub(/[^0-9a-f]/, "")].pack("H*") 92a009a9f0d4cab8720e820b5f642540 @@ -146,6 +230,8 @@ def test_ed25519 end def test_x25519 + omit_on_fips + # Test vector from RFC 7748 Section 6.1 alice_pem = <<~EOF -----BEGIN PRIVATE KEY----- @@ -158,39 +244,48 @@ def test_x25519 -----END PUBLIC KEY----- EOF shared_secret = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742" - begin - alice = OpenSSL::PKey.read(alice_pem) - bob = OpenSSL::PKey.read(bob_pem) - rescue OpenSSL::PKey::PKeyError - # OpenSSL < 1.1.0 - pend "X25519 is not implemented" - end + + 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) - begin - alice_private = OpenSSL::PKey.new_raw_private_key("X25519", alice.raw_private_key) - bob_public = OpenSSL::PKey.new_raw_public_key("X25519", bob.raw_public_key) - alice_private_raw = alice.raw_private_key.unpack1("H*") - bob_public_raw = bob.raw_public_key.unpack1("H*") - rescue NoMethodError - # OpenSSL < 1.1.1 - pend "running OpenSSL version does not have raw public key support" - end + + alice_private = OpenSSL::PKey.new_raw_private_key("X25519", alice.raw_private_key) + bob_public = OpenSSL::PKey.new_raw_public_key("X25519", bob.raw_public_key) assert_equal alice_private.private_to_pem, alice.private_to_pem assert_equal bob_public.public_to_pem, bob.public_to_pem assert_equal "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", - alice_private_raw + alice.raw_private_key.unpack1("H*") assert_equal "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", - bob_public_raw + bob.raw_public_key.unpack1("H*") end - def raw_initialize - pend "Ed25519 is not implemented" unless openssl?(1, 1, 1) # >= v1.1.1 + def test_ml_dsa + # AWS-LC also supports ML-DSA, but it's implemented in a different way + return unless openssl?(3, 5, 0) + + pkey = OpenSSL::PKey.generate_key("ML-DSA-44") + assert_match(/type_name=ML-DSA-44/, pkey.inspect) + sig = pkey.sign(nil, "data") + assert_equal(2420, sig.bytesize) + assert_equal(true, pkey.verify(nil, sig, "data")) + + pub2 = OpenSSL::PKey.read(pkey.public_to_der) + assert_equal(true, pub2.verify(nil, sig, "data")) + raw_public_key = pkey.raw_public_key + assert_equal(1312, raw_public_key.bytesize) + pub3 = OpenSSL::PKey.new_raw_public_key("ML-DSA-44", raw_public_key) + assert_equal(true, pub3.verify(nil, sig, "data")) + end + + def test_raw_initialize_errors assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("foo123", "xxx") } @@ -198,10 +293,10 @@ def raw_initialize 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)) @@ -216,7 +311,14 @@ def test_compare? 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/mri/openssl/test_pkey_dh.rb b/test/mri/openssl/test_pkey_dh.rb index d32ffaf6b11..cd13283a2a7 100644 --- a/test/mri/openssl/test_pkey_dh.rb +++ b/test/mri/openssl/test_pkey_dh.rb @@ -4,36 +4,43 @@ 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 @@ def test_derive_key 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 @@ def test_DHparams ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- EOF + assert_equal(pem, dh_params.export) key = OpenSSL::PKey::DH.new(pem) - assert_same_dh dh_params, key + assert_same_dh_params(dh_params, key) + assert_no_key(key) key = OpenSSL::PKey.read(pem) - assert_same_dh dh_params, key - - assert_equal asn1.to_der, dh.to_der - assert_equal pem, dh.export + assert_same_dh_params(dh_params, key) + assert_no_key(key) + + key = OpenSSL::PKey.generate_key(dh_params) + assert_same_dh_params(dh_params, key) + assert_key(key) + assert_equal(dh_params.to_der, key.to_der) + assert_equal(dh_params.to_pem, key.to_pem) end def test_public_key @@ -95,23 +108,25 @@ def test_public_key def test_generate_key # Deprecated in v3.0.0; incompatible with OpenSSL 3.0 - # Creates a copy with params only - dh = Fixtures.pkey("dh2048_ffdhe2048").public_key + dh = Fixtures.pkey("dh2048_ffdhe2048") assert_no_key(dh) dh.generate_key! assert_key(dh) - dh2 = dh.public_key + dh2 = OpenSSL::PKey::DH.new(dh.to_der) dh2.generate_key! + assert_not_equal(dh.pub_key, dh2.pub_key) assert_equal(dh.compute_key(dh2.pub_key), dh2.compute_key(dh.pub_key)) end if !openssl?(3, 0, 0) def test_params_ok? + omit_on_fips + # Skip the tests in old OpenSSL version 1.1.1c or early versions before # applying the following commits in OpenSSL 1.1.1d to make `DH_check` # function pass the RFC 7919 FFDHE group texts. # https://github.com/openssl/openssl/pull/9435 - unless openssl?(1, 1, 1, 4) + if openssl? && !openssl?(1, 1, 1, 4) pend 'DH check for RFC 7919 FFDHE group texts is not implemented' end @@ -123,11 +138,41 @@ def test_params_ok? ])) assert_equal(true, dh1.params_ok?) - dh2 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Integer(dh0.p + 1), - OpenSSL::ASN1::Integer(dh0.g) - ])) - assert_equal(false, dh2.params_ok?) + # AWS-LC automatically does parameter checks on the parsed params. + if aws_lc? + assert_raise(OpenSSL::PKey::PKeyError) { + OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(dh0.p + 1), + OpenSSL::ASN1::Integer(dh0.g) + ])) + } + else + dh2 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(dh0.p + 1), + OpenSSL::ASN1::Integer(dh0.g) + ])) + assert_equal(false, dh2.params_ok?) + end + + end + + def test_params + dh = Fixtures.pkey("dh2048_ffdhe2048") + assert_kind_of(OpenSSL::BN, dh.p) + assert_equal(dh.p, dh.params["p"]) + assert_kind_of(OpenSSL::BN, dh.g) + assert_equal(dh.g, dh.params["g"]) + assert_nil(dh.pub_key) + assert_nil(dh.params["pub_key"]) + assert_nil(dh.priv_key) + assert_nil(dh.params["priv_key"]) + + dhkey = OpenSSL::PKey.generate_key(dh) + assert_equal(dh.params["p"], dhkey.params["p"]) + assert_kind_of(OpenSSL::BN, dhkey.pub_key) + assert_equal(dhkey.pub_key, dhkey.params["pub_key"]) + assert_kind_of(OpenSSL::BN, dhkey.priv_key) + assert_equal(dhkey.priv_key, dhkey.params["priv_key"]) end def test_dup @@ -176,14 +221,14 @@ def assert_no_key(dh) 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/mri/openssl/test_pkey_dsa.rb b/test/mri/openssl/test_pkey_dsa.rb index 3e8a83b2d01..1ec0bf0b4d4 100644 --- a/test/mri/openssl/test_pkey_dsa.rb +++ b/test/mri/openssl/test_pkey_dsa.rb @@ -10,7 +10,7 @@ def setup end def test_private - key = Fixtures.pkey("dsa1024") + key = Fixtures.pkey("dsa2048") assert_equal true, key.private? key2 = OpenSSL::PKey::DSA.new(key.to_der) assert_equal true, key2.private? @@ -33,6 +33,17 @@ def test_new_break end end + def test_new_empty + # pkeys are immutable with OpenSSL >= 3.0 + if openssl?(3, 0, 0) + assert_raise(ArgumentError) { OpenSSL::PKey::DSA.new } + else + key = OpenSSL::PKey::DSA.new + assert_nil(key.p) + assert_raise(OpenSSL::PKey::PKeyError) { key.to_der } + end + end + def test_generate # DSA.generate used to call DSA_generate_parameters_ex(), which adjusts the # size of q according to the size of p @@ -41,11 +52,11 @@ def test_generate assert_equal 1024, key1024.p.num_bits assert_equal 160, key1024.q.num_bits - key2048 = OpenSSL::PKey::DSA.generate(2048) - assert_equal 2048, key2048.p.num_bits - assert_equal 256, key2048.q.num_bits - if ENV["OSSL_TEST_ALL"] == "1" # slow + key2048 = OpenSSL::PKey::DSA.generate(2048) + assert_equal 2048, key2048.p.num_bits + assert_equal 256, key2048.q.num_bits + key3072 = OpenSSL::PKey::DSA.generate(3072) assert_equal 3072, key3072.p.num_bits assert_equal 256, key3072.q.num_bits @@ -86,122 +97,93 @@ def test_sign_verify_raw sig = key.syssign(digest) assert_equal true, key.sysverify(digest, sig) assert_equal false, key.sysverify(digest, invalid_sig) - assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) } + assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) } assert_equal true, key.verify_raw(nil, sig, digest) assert_equal false, key.verify_raw(nil, invalid_sig, digest) - assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) } + assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) } # Sign by #sign_raw sig = key.sign_raw(nil, digest) assert_equal true, key.sysverify(digest, sig) assert_equal false, key.sysverify(digest, invalid_sig) - assert_raise(OpenSSL::PKey::DSAError) { key.sysverify(digest, malformed_sig) } + assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) } assert_equal true, key.verify_raw(nil, sig, digest) assert_equal false, key.verify_raw(nil, invalid_sig, digest) - assert_raise(OpenSSL::PKey::PKeyError) { key.verify_raw(nil, malformed_sig, digest) } + assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) } end def test_DSAPrivateKey # OpenSSL DSAPrivateKey format; similar to RSAPrivateKey - dsa512 = Fixtures.pkey("dsa512") + orig = Fixtures.pkey("dsa2048") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), - OpenSSL::ASN1::Integer(dsa512.p), - OpenSSL::ASN1::Integer(dsa512.q), - OpenSSL::ASN1::Integer(dsa512.g), - OpenSSL::ASN1::Integer(dsa512.pub_key), - OpenSSL::ASN1::Integer(dsa512.priv_key) + OpenSSL::ASN1::Integer(orig.p), + OpenSSL::ASN1::Integer(orig.q), + OpenSSL::ASN1::Integer(orig.g), + OpenSSL::ASN1::Integer(orig.pub_key), + OpenSSL::ASN1::Integer(orig.priv_key) ]) key = OpenSSL::PKey::DSA.new(asn1.to_der) assert_predicate key, :private? - assert_same_dsa dsa512, key - - pem = <<~EOF - -----BEGIN DSA PRIVATE KEY----- - MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok - RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D - AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR - S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++ - Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S - 55jreJD3Se3slps= - -----END DSA PRIVATE KEY----- - EOF + assert_same_dsa orig, key + + pem = der_to_pem(asn1.to_der, "DSA PRIVATE KEY") key = OpenSSL::PKey::DSA.new(pem) - assert_same_dsa dsa512, key + assert_same_dsa orig, key - assert_equal asn1.to_der, dsa512.to_der - assert_equal pem, dsa512.export + assert_equal asn1.to_der, orig.to_der + assert_equal pem, orig.export end def test_DSAPrivateKey_encrypted - # key = abcdef - dsa512 = Fixtures.pkey("dsa512") - pem = <<~EOF - -----BEGIN DSA PRIVATE KEY----- - Proc-Type: 4,ENCRYPTED - DEK-Info: AES-128-CBC,F8BB7BFC7EAB9118AC2E3DA16C8DB1D9 - - D2sIzsM9MLXBtlF4RW42u2GB9gX3HQ3prtVIjWPLaKBYoToRUiv8WKsjptfZuLSB - 74ZPdMS7VITM+W1HIxo/tjS80348Cwc9ou8H/E6WGat8ZUk/igLOUEII+coQS6qw - QpuLMcCIavevX0gjdjEIkojBB81TYDofA1Bp1z1zDI/2Zhw822xapI79ZF7Rmywt - OSyWzFaGipgDpdFsGzvT6//z0jMr0AuJVcZ0VJ5lyPGQZAeVBlbYEI4T72cC5Cz7 - XvLiaUtum6/sASD2PQqdDNpgx/WA6Vs1Po2kIUQIM5TIwyJI0GdykZcYm6xIK/ta - Wgx6c8K+qBAIVrilw3EWxw== - -----END DSA PRIVATE KEY----- - EOF + # OpenSSL DSAPrivateKey with OpenSSL encryption + orig = Fixtures.pkey("dsa2048") + + pem = der_to_encrypted_pem(orig.to_der, "DSA PRIVATE KEY", "abcdef") key = OpenSSL::PKey::DSA.new(pem, "abcdef") - assert_same_dsa dsa512, key + assert_same_dsa orig, key key = OpenSSL::PKey::DSA.new(pem) { "abcdef" } - assert_same_dsa dsa512, key + assert_same_dsa orig, key cipher = OpenSSL::Cipher.new("aes-128-cbc") - exported = dsa512.to_pem(cipher, "abcdef\0\1") - assert_same_dsa dsa512, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1") - assert_raise(OpenSSL::PKey::DSAError) { + exported = orig.to_pem(cipher, "abcdef\0\1") + assert_same_dsa orig, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1") + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey::DSA.new(exported, "abcdef") } end def test_PUBKEY - dsa512 = Fixtures.pkey("dsa512") - dsa512pub = OpenSSL::PKey::DSA.new(dsa512.public_to_der) + orig = Fixtures.pkey("dsa2048") + pub = OpenSSL::PKey::DSA.new(orig.public_to_der) asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("DSA"), OpenSSL::ASN1::Sequence([ - OpenSSL::ASN1::Integer(dsa512.p), - OpenSSL::ASN1::Integer(dsa512.q), - OpenSSL::ASN1::Integer(dsa512.g) + OpenSSL::ASN1::Integer(orig.p), + OpenSSL::ASN1::Integer(orig.q), + OpenSSL::ASN1::Integer(orig.g) ]) ]), OpenSSL::ASN1::BitString( - OpenSSL::ASN1::Integer(dsa512.pub_key).to_der + OpenSSL::ASN1::Integer(orig.pub_key).to_der ) ]) key = OpenSSL::PKey::DSA.new(asn1.to_der) assert_not_predicate key, :private? - assert_same_dsa dsa512pub, key - - pem = <<~EOF - -----BEGIN PUBLIC KEY----- - MIHxMIGoBgcqhkjOOAQBMIGcAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgT - YiEEHaOYhkIxv0OkRZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB - 4DZGH7UyarcaGy6DAkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqo - ji3/lHdKoVdTQNuRS/m6DlCwhjRjiQ/lBRgCLCcaA0QAAkEAjN891JBjzpMj4bWg - sACmMggFf57DS0Ti+5++Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxX - oXi9OA== - -----END PUBLIC KEY----- - EOF + assert_same_dsa pub, key + + pem = der_to_pem(asn1.to_der, "PUBLIC KEY") key = OpenSSL::PKey::DSA.new(pem) - assert_same_dsa dsa512pub, key + assert_same_dsa pub, key assert_equal asn1.to_der, key.to_der assert_equal pem, key.export - assert_equal asn1.to_der, dsa512.public_to_der + assert_equal asn1.to_der, orig.public_to_der assert_equal asn1.to_der, key.public_to_der - assert_equal pem, dsa512.public_to_pem + assert_equal pem, orig.public_to_pem assert_equal pem, key.public_to_pem end @@ -230,8 +212,29 @@ def test_read_DSAPublicKey_pem assert_equal(nil, key.priv_key) end + def test_params + key = Fixtures.pkey("dsa2048") + assert_kind_of(OpenSSL::BN, key.p) + assert_equal(key.p, key.params["p"]) + assert_kind_of(OpenSSL::BN, key.q) + assert_equal(key.q, key.params["q"]) + assert_kind_of(OpenSSL::BN, key.g) + assert_equal(key.g, key.params["g"]) + assert_kind_of(OpenSSL::BN, key.pub_key) + assert_equal(key.pub_key, key.params["pub_key"]) + assert_kind_of(OpenSSL::BN, key.priv_key) + assert_equal(key.priv_key, key.params["priv_key"]) + + pubkey = OpenSSL::PKey.read(key.public_to_der) + assert_equal(key.params["p"], pubkey.params["p"]) + assert_equal(key.pub_key, pubkey.pub_key) + assert_equal(key.pub_key, pubkey.params["pub_key"]) + assert_nil(pubkey.priv_key) + assert_nil(pubkey.params["priv_key"]) + end + def test_dup - key = Fixtures.pkey("dsa1024") + key = Fixtures.pkey("dsa2048") key2 = key.dup assert_equal key.params, key2.params @@ -243,7 +246,7 @@ def test_dup 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/mri/openssl/test_pkey_ec.rb b/test/mri/openssl/test_pkey_ec.rb index 2cb8e287ab7..88085bc68c7 100644 --- a/test/mri/openssl/test_pkey_ec.rb +++ b/test/mri/openssl/test_pkey_ec.rb @@ -4,19 +4,9 @@ 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 @@ def test_ec_key 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 @@ def test_builtin_curves 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 @@ def test_generate 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 @@ def test_marshal end def test_check_key + omit_on_fips + key0 = Fixtures.pkey("p256") assert_equal(true, key0.check_key) assert_equal(true, key0.private?) @@ -88,16 +99,25 @@ def test_check_key assert_equal(true, key2.check_key) # Behavior of EVP_PKEY_public_check changes between OpenSSL 1.1.1 and 3.0 - key4 = Fixtures.pkey("p256_too_large") - assert_raise(OpenSSL::PKey::ECError) { key4.check_key } - - key5 = Fixtures.pkey("p384_invalid") - assert_raise(OpenSSL::PKey::ECError) { key5.check_key } + # The public key does not match the private key + ec_key_data = <<~EOF + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIP+TT0V8Fndsnacji9tyf6hmhHywcOWTee9XkiBeJoVloAoGCCqGSM49 + AwEHoUQDQgAEBkhhJIU/2/YdPSlY2I1k25xjK4trr5OXSgXvBC21PtY0HQ7lor7A + jzT0giJITqmcd81fwGw5+96zLcdxTF1hVQ== + -----END EC PRIVATE KEY----- + EOF + 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 @@ -143,19 +163,19 @@ def test_sign_verify_raw 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 @@ -251,7 +271,7 @@ def test_ECPrivateKey_encrypted 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 @@ -300,7 +320,10 @@ def test_ec_group 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 @@ -346,18 +369,26 @@ def test_ec_point 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) @@ -421,28 +452,6 @@ def test_ec_point_mul # 3 * (6, 3) + 3 * (5, 1) = (7, 6) result_a2 = point_a.mul(3, 3) assert_equal B(%w{ 04 07 06 }), result_a2.to_octet_string(:uncompressed) - EnvUtil.suppress_warning do # Point#mul(ary, ary [, bn]) is deprecated - begin - result_b1 = point_a.mul([3], []) - rescue NotImplementedError - # LibreSSL and OpenSSL 3.0 do no longer support this form of calling - next - end - - # 3 * point_a = 3 * (6, 3) = (16, 13) - result_b1 = point_a.mul([3], []) - assert_equal B(%w{ 04 10 0D }), result_b1.to_octet_string(:uncompressed) - # 3 * point_a + 2 * point_a = 3 * (6, 3) + 2 * (6, 3) = (7, 11) - result_b1 = point_a.mul([3, 2], [point_a]) - assert_equal B(%w{ 04 07 0B }), result_b1.to_octet_string(:uncompressed) - # 3 * point_a + 5 * point_a.group.generator = 3 * (6, 3) + 5 * (5, 1) = (13, 10) - result_b1 = point_a.mul([3], [], 5) - assert_equal B(%w{ 04 0D 0A }), result_b1.to_octet_string(:uncompressed) - - assert_raise(ArgumentError) { point_a.mul([1], [point_a]) } - assert_raise(TypeError) { point_a.mul([1], nil) } - assert_raise(TypeError) { point_a.mul([nil], []) } - end rescue OpenSSL::PKey::EC::Group::Error # CentOS patches OpenSSL to reject curves defined over Fp where p < 256 bits raise if $!.message !~ /unsupported field/ @@ -455,6 +464,9 @@ def test_ec_point_mul # invalid argument point = p256_key.public_key assert_raise(TypeError) { point.mul(nil) } + + # mul with arrays was removed in version 4.0.0 + assert_raise(NotImplementedError) { point.mul([1], []) } end # test Group: asn1_flag, point_conversion diff --git a/test/mri/openssl/test_pkey_rsa.rb b/test/mri/openssl/test_pkey_rsa.rb index 02693c277bd..86f51cf4385 100644 --- a/test/mri/openssl/test_pkey_rsa.rb +++ b/test/mri/openssl/test_pkey_rsa.rb @@ -6,40 +6,38 @@ 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 @@ def test_new_public_exponent 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 @@ def test_sign_verify_options 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 @@ def test_sign_verify_raw_legacy # 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 @@ def test_verify_empty_rsa 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 @@ def test_sign_verify_pss # 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 @@ def test_encrypt_decrypt_legacy 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 @@ def test_RSAPrivateKey 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 @@ def test_RSAPrivateKey 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 @@ def test_PUBKEY ]), 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,38 +443,21 @@ def test_pem_passwd 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 @@ -542,44 +475,65 @@ def test_private_encoding_encrypted assert_match (/BEGIN ENCRYPTED PRIVATE KEY/), encoded.lines[0] assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdef") - # certtool --load-privkey=test/openssl/fixtures/pkey/rsa2048.pem --to-p8 --password=abcdef + # 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 pem = <<~EOF -----BEGIN ENCRYPTED PRIVATE KEY----- - MIIFOTBjBgkqhkiG9w0BBQ0wVjA1BgkqhkiG9w0BBQwwKAQSsTIsinrhNMr4owUz - cwYGgB0lAgMJJ8ACARAwCgYIKoZIhvcNAgkwHQYJYIZIAWUDBAECBBDtDYqmQOLV - Nh0T0DslWgovBIIE0ESbJey2Pjf9brTp9/41CPnI9Ev78CGSv8Ihyuynu6G7oj7N - G7jUB1pVMQ7ivebF5DmM0qHAix6fDqJetB3WCnRQpMLyIdq5VrnKwFNhwGYduWA5 - IyaAc4DHj02e6YLyBTIKpu79OSFxLrnLCRaTbvZIUQaGhyd6pB7iAhqz5YBC0rpa - iMK5TRlNGPYG9n2eGFOhvUsbJ4T8VDzjpVWw0VNRaukXtg4xiR6o1f0qSXqAb5d9 - REq5DfaQfoOKTV9j7KJHDRrBQG81vkU4K+xILrCBfbcYb82aCoinwSep9LC30HaH - LZ0hYQOuD/k/UbgjToS2wyMnkz75MN5ZNhDMZl/mACQdsMMtIxG37Mpo1Ca33uZi - 71TCOEKIblZS11L1YhIni9Af8pOuHJBWwezP2zN2nPwV6OhgL7Jlax7ICQOPC6L/ - yRGgC5eT4lDDAuTy0IdUhr0r5XrFzZR0/5Vgsq9cGfk9QkXOoETRhQVkEfUDdCs6 - 6CK+SwUR9qh5824ShODFG0SQpsqBPIVtkGrypBSUJtICmGMOAsclB7RDN7/opJwp - qv/iRJ5dhWrhRgQ/DfYifvO5On7RgC2hm48gF3Pt6XCA857ryyYxLYeMY42tAUqp - Hmc9HL7bMYF/jl3cJ32+gLvI3PBVvrvyeAhRo6z7MFVe9I04OywV6BHUx1Us6ybF - qkYnSpcJZdu7HyvzXm7XWLFmt7K5BlAgnFsa/8+cI1BGPgQRc1j0SWepXsSwFZX6 - JkNQ0dewq4uRJXbGyQgfh5I5ETpqDhSt2JfBwAoze6cx3DPC711PUamxyWMiejs+ - mYdia4p62NxaUvyXWmCGIEOzajRwywEhf9OLAmfqTN41TIrEL4BUxqtzDyw8Nl8T - KB7nJEC366jFASfumNQkXXyH5yBIF+XwwSKUOObRZVn2rUzFToo51hHu9efxHoXa - jZlpfglWijkmOuwoIGlGHOq8gUn76oq9WbV+YO+fWm/mf4S3ECzmYzxb6a1uCTy/ - Itkm2qOe3yTM1t+oCqZ0/MeTZ84ALQaWv5reQfjronPZ1jeNtxrYz28tJ4KwBn0U - bJReXbOLsHAymipncxlmaevbx4GPTduu/lbpxefoN95w+SpEdyTmVWrfaCTgAbad - EzcRl60my3xOMQ7CaUbRgGiwohqHDvuXzeqoZ96u6CwfAoEfy4jETmKLRH6uTtj7 - 4jdTyoqyizjpvaM8LPspBS+oqFwLxBjpseQuScrZO1BjPxrImLy2/VRqwJ+CF4FB - iijEgDgDc1EMIGe5YmOAV+i22n9RqX+2IvkYp7CWXrB9/lmirLFukd7hT8DLPUGq - AvSZwTPbDPoZKG3DAebC3DbiC7A3x0KZp24doNRLamZ/MyKHo2Rzl0UhkzDU0ly2 - eAnyNYsOAQck+C6L+ieD95Gksm9YJWurwttm5JragbIJwMCrsBQd4bXDkKdRhxS2 - JpS0dT/aoDmgTzoG07x4cZk0rjBkfX1ta0j0b1lz7/PZXl9AbRvFdq5sJpmv4Ryz - S+OERqo4IEfJJq2WJ92WR+HLGV3Gvsdb7znZTEF1tp4pWOLAt83Pry282UJxO7Pe - ySf/868TEmXams06GYvH+7cMiIT2m9Dc+EFgNaPmm0uMmJ+ZjqHKSOLzrL7C + 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----- EOF assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdef") end + def test_params + key = Fixtures.pkey("rsa2048") + assert_equal(2048, key.n.num_bits) + assert_equal(key.n, key.params["n"]) + assert_equal(65537, key.e) + assert_equal(key.e, key.params["e"]) + [:d, :p, :q, :dmp1, :dmq1, :iqmp].each do |name| + assert_kind_of(OpenSSL::BN, key.send(name)) + assert_equal(key.send(name), key.params[name.to_s]) + end + + pubkey = OpenSSL::PKey.read(key.public_to_der) + assert_equal(key.n, pubkey.n) + assert_equal(key.e, pubkey.e) + [:d, :p, :q, :dmp1, :dmq1, :iqmp].each do |name| + assert_nil(pubkey.send(name)) + assert_nil(pubkey.params[name.to_s]) + end + end + def test_dup - key = Fixtures.pkey("rsa1024") + key = Fixtures.pkey("rsa-1") key2 = key.dup assert_equal key.params, key2.params @@ -591,7 +545,7 @@ def test_dup 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/mri/openssl/test_provider.rb b/test/mri/openssl/test_provider.rb index 6f85c00c988..10081e208ce 100644 --- a/test/mri/openssl/test_provider.rb +++ b/test/mri/openssl/test_provider.rb @@ -46,6 +46,7 @@ def test_openssl_legacy_provider 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/mri/openssl/test_ssl.rb b/test/mri/openssl/test_ssl.rb index 459efcc18ea..5d20ccd1f4b 100644 --- a/test/mri/openssl/test_ssl.rb +++ b/test/mri/openssl/test_ssl.rb @@ -39,8 +39,7 @@ def test_ctx_options end def test_ctx_options_config - omit "LibreSSL does not support OPENSSL_CONF" if libressl? - omit "OpenSSL < 1.1.1 does not support system_default" if openssl? && !openssl?(1, 1, 1) + omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? Tempfile.create("openssl.cnf") { |f| f.puts(<<~EOF) @@ -231,6 +230,34 @@ def test_add_certificate_multiple_certs end end + def test_extra_chain_cert_auto_chain + start_server { |port| + server_connect(port) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + assert_equal @svr_cert.to_der, ssl.peer_cert.to_der + assert_equal [@svr_cert], ssl.peer_cert_chain + } + } + + # AWS-LC enables SSL_MODE_NO_AUTO_CHAIN by default + unless aws_lc? + ctx_proc = -> ctx { + # Sanity check: start_server won't set extra_chain_cert + assert_nil ctx.extra_chain_cert + ctx.cert_store = OpenSSL::X509::Store.new.tap { |store| + store.add_cert(@ca_cert) + } + } + start_server(ctx_proc: ctx_proc) { |port| + server_connect(port) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + assert_equal @svr_cert.to_der, ssl.peer_cert.to_der + assert_equal [@svr_cert, @ca_cert], ssl.peer_cert_chain + } + } + end + end + def test_sysread_and_syswrite start_server { |port| server_connect(port) { |ssl| @@ -243,6 +270,11 @@ def test_sysread_and_syswrite ssl.syswrite(str) assert_same buf, ssl.sysread(str.size, buf) assert_equal(str, buf) + + obj = Object.new + obj.define_singleton_method(:to_str) { str } + ssl.syswrite(obj) + assert_equal(str, ssl.sysread(str.bytesize)) } } end @@ -256,11 +288,16 @@ def test_read_with_timeout ssl.syswrite(str) assert_equal(str, ssl.sysread(str.bytesize)) - ssl.timeout = 1 - assert_raise(IO::TimeoutError) {ssl.read(1)} + ssl.timeout = 0.1 + assert_raise(IO::TimeoutError) { ssl.sysread(1) } ssl.syswrite(str) assert_equal(str, ssl.sysread(str.bytesize)) + + buf = "orig".b + assert_raise(IO::TimeoutError) { ssl.sysread(1, buf) } + assert_equal("orig", buf) + assert_nothing_raised { buf.clear } end end end @@ -344,27 +381,27 @@ def test_verify_mode_server_cert empty_store = OpenSSL::X509::Store.new # Valid certificate, SSL_VERIFY_PEER + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.cert_store = populated_store assert_nothing_raised { - ctx = OpenSSL::SSL::SSLContext.new - ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER - ctx.cert_store = populated_store server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } # Invalid certificate, SSL_VERIFY_NONE + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE + ctx.cert_store = empty_store assert_nothing_raised { - ctx = OpenSSL::SSL::SSLContext.new - ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE - ctx.cert_store = empty_store server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } # Invalid certificate, SSL_VERIFY_PEER - assert_handshake_error { - ctx = OpenSSL::SSL::SSLContext.new - ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER - ctx.cert_store = empty_store - server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.cert_store = empty_store + assert_raise(OpenSSL::SSL::SSLError) { + server_connect(port, ctx) } } end @@ -392,10 +429,15 @@ def test_verify_mode_client_cert_required 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| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?(3, 2, 0) - }) { |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 @@ -437,9 +479,13 @@ def test_client_cert_cb_ignore_error end def test_client_ca - pend "LibreSSL 3.2 has broken client CA support" if libressl?(3, 2, 0) + 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 @@ -505,7 +551,7 @@ def test_verify_result 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 @@ -609,12 +655,9 @@ def test_finished_messages start_server(accept_proc: proc { |server| server_finished = server.finished_message server_peer_finished = server.peer_finished_message - }, ctx_proc: proc { |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl?(3, 2, 0) }) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE - ctx.max_version = :TLS1_2 if libressl?(3, 2, 0) && !libressl?(3, 3, 0) server_connect(port, ctx) { |ssl| ssl.puts "abc"; ssl.gets @@ -642,8 +685,12 @@ def test_sslctx_set_params end def test_post_connect_check_with_anon_ciphers + # DH missing the q value on unknown named parameters is not FIPS-approved. + omit_on_fips + omit "AWS-LC does not support DHE ciphersuites" if aws_lc? + ctx_proc = -> ctx { - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "aNULL" ctx.tmp_dh = Fixtures.pkey("dh-1") ctx.security_level = 0 @@ -651,7 +698,7 @@ def test_post_connect_check_with_anon_ciphers start_server(ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "aNULL" ctx.security_level = 0 server_connect(port, ctx) { |ssl| @@ -795,11 +842,6 @@ def test_post_connection_check_wildcard_san # 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?(3, 5, 0) - 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( @@ -883,11 +925,17 @@ def test_post_connection_check_wildcard_cn 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 @@ -924,7 +972,7 @@ def socketpair end def test_keylog_cb - pend "Keylog callback is not supported" if !openssl?(1, 1, 1) || libressl? + omit "Keylog callback is not supported" if libressl? prefix = 'CLIENT_RANDOM' context = OpenSSL::SSL::SSLContext.new @@ -944,30 +992,28 @@ def test_keylog_cb end end - if tls13_supported? - prefixes = [ - 'SERVER_HANDSHAKE_TRAFFIC_SECRET', - 'EXPORTER_SECRET', - 'SERVER_TRAFFIC_SECRET_0', - 'CLIENT_HANDSHAKE_TRAFFIC_SECRET', - 'CLIENT_TRAFFIC_SECRET_0', - ] - context = OpenSSL::SSL::SSLContext.new - context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION - cb_called = false - context.keylog_cb = proc do |_sock, line| - cb_called = true - assert_not_nil(prefixes.delete(line.split.first)) - end + prefixes = [ + 'SERVER_HANDSHAKE_TRAFFIC_SECRET', + 'EXPORTER_SECRET', + 'SERVER_TRAFFIC_SECRET_0', + 'CLIENT_HANDSHAKE_TRAFFIC_SECRET', + 'CLIENT_TRAFFIC_SECRET_0', + ] + context = OpenSSL::SSL::SSLContext.new + context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION + cb_called = false + context.keylog_cb = proc do |_sock, line| + cb_called = true + assert_not_nil(prefixes.delete(line.split.first)) + end - start_server do |port| - server_connect(port, context) do |ssl| - ssl.puts "abc" - assert_equal("abc\n", ssl.gets) - assert_equal(true, cb_called) - end - assert_equal(0, prefixes.size) + start_server do |port| + server_connect(port, context) do |ssl| + ssl.puts "abc" + assert_equal("abc\n", ssl.gets) + assert_equal(true, cb_called) end + assert_equal(0, prefixes.size) end end @@ -1077,13 +1123,11 @@ def test_accept_errors_include_peeraddr def test_verify_hostname_on_connect ctx_proc = proc { |ctx| - san = "DNS:a.example.com,DNS:*.b.example.com" - san += ",DNS:c*.example.com,DNS:d.*.example.com" unless libressl?(3, 2, 2) exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], - ["subjectAltName", san], + ["subjectAltName", "DNS:a.example.com,DNS:*.b.example.com," \ + "DNS:c*.example.com,DNS:d.*.example.com"], ] - ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) ctx.key = @svr_key } @@ -1105,7 +1149,6 @@ def test_verify_hostname_on_connect ["cx.example.com", true], ["d.x.example.com", false], ].each do |name, expected_ok| - next if name.start_with?('cx') if libressl?(3, 2, 2) begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) @@ -1114,7 +1157,7 @@ def test_verify_hostname_on_connect ssl.connect ssl.puts "abc"; assert_equal "abc\n", ssl.gets else - assert_handshake_error { ssl.connect } + assert_raise(OpenSSL::SSL::SSLError) { ssl.connect } end ensure ssl.close if ssl @@ -1152,7 +1195,7 @@ def test_verify_hostname_failure_error_code sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.hostname = "b.example.com" - assert_handshake_error { ssl.connect } + assert_raise(OpenSSL::SSL::SSLError) { ssl.connect } assert_equal false, verify_callback_ok assert_equal OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH, verify_callback_err ensure @@ -1165,9 +1208,7 @@ def test_connect_certificate_verify_failed_exception_message 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) } } @@ -1209,34 +1250,33 @@ def check_supported_protocol_versions OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_1_VERSION, OpenSSL::SSL::TLS1_2_VERSION, - # OpenSSL 1.1.1 - defined?(OpenSSL::SSL::TLS1_3_VERSION) && OpenSSL::SSL::TLS1_3_VERSION, - ].compact + OpenSSL::SSL::TLS1_3_VERSION, + ] - # Prepare for testing & do sanity check supported = [] - possible_versions.each do |ver| - catch(:unsupported) { - ctx_proc = proc { |ctx| - begin - ctx.min_version = ctx.max_version = ver - rescue ArgumentError, OpenSSL::SSL::SSLError - throw :unsupported - end + ctx_proc = proc { |ctx| + # The default security level is 1 in OpenSSL <= 3.1, 2 in OpenSSL >= 3.2 + # In OpenSSL >= 3.0, TLS 1.1 or older is disabled at level 1 + ctx.security_level = 0 + # Explicitly reset them to avoid influenced by OPENSSL_CONF + ctx.min_version = ctx.max_version = nil + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + possible_versions.each do |ver| + ctx = OpenSSL::SSL::SSLContext.new + ctx.security_level = 0 + ctx.min_version = ctx.max_version = ver + server_connect(port, ctx) { |ssl| + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| - begin - server_connect(port) { |ssl| - ssl.puts "abc"; assert_equal "abc\n", ssl.gets - } - rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET - else - supported << ver - end - end - } + supported << ver + rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET + end end - assert_not_empty supported + + # Sanity check: in our test suite we assume these are always supported + assert_include(supported, OpenSSL::SSL::TLS1_2_VERSION) + assert_include(supported, OpenSSL::SSL::TLS1_3_VERSION) supported end @@ -1254,7 +1294,7 @@ def test_set_params_min_version start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.set_params(cert_store: store, verify_hostname: false) - assert_handshake_error { server_connect(port, ctx) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } } end end @@ -1270,18 +1310,20 @@ def test_minmax_version OpenSSL::SSL::TLS1_VERSION => { name: "TLSv1", method: "TLSv1" }, OpenSSL::SSL::TLS1_1_VERSION => { name: "TLSv1.1", method: "TLSv1_1" }, OpenSSL::SSL::TLS1_2_VERSION => { name: "TLSv1.2", method: "TLSv1_2" }, - # OpenSSL 1.1.1 - defined?(OpenSSL::SSL::TLS1_3_VERSION) && OpenSSL::SSL::TLS1_3_VERSION => - { name: "TLSv1.3", method: nil }, + OpenSSL::SSL::TLS1_3_VERSION => { name: "TLSv1.3", method: nil }, } # Server enables a single version supported.each do |ver| - ctx_proc = proc { |ctx| ctx.min_version = ctx.max_version = ver } + ctx_proc = proc { |ctx| + ctx.security_level = 0 + ctx.min_version = ctx.max_version = ver + } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| supported.each do |cver| # Client enables a single version ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.security_level = 0 ctx1.min_version = ctx1.max_version = cver if ver == cver server_connect(port, ctx1) { |ssl| @@ -1289,13 +1331,14 @@ def test_minmax_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else - assert_handshake_error { server_connect(port, ctx1) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } end # There is no version-specific SSL methods for TLS 1.3 if cver <= OpenSSL::SSL::TLS1_2_VERSION # Client enables a single version using #ssl_version= ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.security_level = 0 ctx2.ssl_version = vmap[cver][:method] if ver == cver server_connect(port, ctx2) { |ssl| @@ -1303,13 +1346,14 @@ def test_minmax_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else - assert_handshake_error { server_connect(port, ctx2) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) } end end end # Client enables all supported versions ctx3 = OpenSSL::SSL::SSLContext.new + ctx3.security_level = 0 ctx3.min_version = ctx3.max_version = nil server_connect(port, ctx3) { |ssl| assert_equal vmap[ver][:name], ssl.ssl_version @@ -1324,12 +1368,17 @@ def test_minmax_version # 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 @@ -1337,6 +1386,8 @@ def test_minmax_version # 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| @@ -1344,14 +1395,18 @@ def test_minmax_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else - assert_handshake_error { server_connect(port, ctx2) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) } end end } # Server sets max_version (latest is disabled) sver = supported[-2] - ctx_proc = proc { |ctx| ctx.max_version = sver } + ctx_proc = proc { |ctx| + ctx.security_level = 0 + ctx.min_version = 0 + ctx.max_version = sver + } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| supported.each do |cver| # Client sets min_version @@ -1363,11 +1418,13 @@ def test_minmax_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else - assert_handshake_error { server_connect(port, ctx1) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } end # Client sets max_version ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.security_level = 0 + ctx2.min_version = 0 ctx2.max_version = cver server_connect(port, ctx2) { |ssl| if cver >= sver @@ -1381,15 +1438,106 @@ def test_minmax_version } end + def test_minmax_version_system_default + omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? + + Tempfile.create("openssl.cnf") { |f| + f.puts(<<~EOF) + openssl_conf = default_conf + [default_conf] + ssl_conf = ssl_sect + [ssl_sect] + system_default = ssl_default_sect + [ssl_default_sect] + MaxProtocol = TLSv1.2 + EOF + f.close + + start_server(ignore_listener_error: true) do |port| + assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") + sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) + ctx = OpenSSL::SSL::SSLContext.new + ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.connect + assert_equal("TLSv1.2", ssl.ssl_version) + ssl.puts("abc"); assert_equal("abc\n", ssl.gets) + ssl.close + end; + + assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") + sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) + ctx = OpenSSL::SSL::SSLContext.new + ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION + ctx.max_version = nil + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.connect + assert_equal("TLSv1.3", ssl.ssl_version) + ssl.puts("abc"); assert_equal("abc\n", ssl.gets) + ssl.close + end; + end + } + end + + def test_respect_system_default_min + omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? + + Tempfile.create("openssl.cnf") { |f| + f.puts(<<~EOF) + openssl_conf = default_conf + [default_conf] + ssl_conf = ssl_sect + [ssl_sect] + system_default = ssl_default_sect + [ssl_default_sect] + MinProtocol = TLSv1.3 + EOF + f.close + + ctx_proc = proc { |ctx| + ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") + sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) + ctx = OpenSSL::SSL::SSLContext.new + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + assert_raise(OpenSSL::SSL::SSLError) do + ssl.connect + end + ssl.close + end; + end + + ctx_proc = proc { |ctx| + ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") + sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) + ctx = OpenSSL::SSL::SSLContext.new + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.connect + assert_equal("TLSv1.3", ssl.ssl_version) + ssl.puts("abc"); assert_equal("abc\n", ssl.gets) + ssl.close + end; + end + } + end + def test_options_disable_versions # It's recommended to use SSLContext#{min,max}_version= instead in real # applications. The purpose of this test case is to check that SSL options # are properly propagated to OpenSSL library. supported = check_supported_protocol_versions - if !defined?(OpenSSL::SSL::TLS1_3_VERSION) || - !supported.include?(OpenSSL::SSL::TLS1_2_VERSION) || - !supported.include?(OpenSSL::SSL::TLS1_3_VERSION) || - !defined?(OpenSSL::SSL::OP_NO_TLSv1_3) # LibreSSL < 3.4 + if !supported.include?(OpenSSL::SSL::TLS1_2_VERSION) || + !supported.include?(OpenSSL::SSL::TLS1_3_VERSION) pend "this test case requires both TLS 1.2 and TLS 1.3 to be supported " \ "and enabled by default" end @@ -1404,7 +1552,7 @@ def test_options_disable_versions # Client only supports TLS 1.2 ctx1 = OpenSSL::SSL::SSLContext.new ctx1.min_version = ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION - assert_handshake_error { server_connect(port, ctx1) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } # Client only supports TLS 1.3 ctx2 = OpenSSL::SSL::SSLContext.new @@ -1420,7 +1568,7 @@ def test_options_disable_versions # Client doesn't support TLS 1.2 ctx1 = OpenSSL::SSL::SSLContext.new ctx1.options |= OpenSSL::SSL::OP_NO_TLSv1_2 - assert_handshake_error { server_connect(port, ctx1) { } } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } # Client supports TLS 1.2 by default ctx2 = OpenSSL::SSL::SSLContext.new @@ -1444,7 +1592,7 @@ def test_renegotiation_cb num_handshakes = 0 renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 } ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb } - start_server_version(:SSLv23, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc) { |port| server_connect(port) { |ssl| assert_equal(1, num_handshakes) ssl.puts "abc"; assert_equal "abc\n", ssl.gets @@ -1460,7 +1608,7 @@ def test_alpn_protocol_selection_ary } ctx.alpn_protocols = advertised } - start_server_version(:SSLv23, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.alpn_protocols = advertised server_connect(port, ctx) { |ssl| @@ -1502,9 +1650,10 @@ def test_npn_protocol_selection_ary advertised = ["http/1.1", "spdy/2"] ctx_proc = proc { |ctx| ctx.npn_protocols = advertised } - start_server_version(:TLSv1_2, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc) { |port| selector = lambda { |which| ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { protocols.send(which) } server_connect(port, ctx) { |ssl| assert_equal(advertised.send(which), ssl.npn_protocol) @@ -1524,9 +1673,10 @@ def advertised.each yield "spdy/2" end ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised } - start_server_version(:TLSv1_2, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc) { |port| selector = lambda { |selected, which| ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { protocols.to_a.send(which) } server_connect(port, ctx) { |ssl| assert_equal(selected, ssl.npn_protocol) @@ -1541,8 +1691,9 @@ def test_npn_protocol_selection_cancel return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } - start_server_version(:TLSv1_2, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new } assert_raise(RuntimeError) { server_connect(port, ctx) } } @@ -1551,22 +1702,22 @@ def test_npn_protocol_selection_cancel def test_npn_advertised_protocol_too_long return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) - ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["a" * 256] } - start_server_version(:TLSv1_2, ctx_proc) { |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.npn_select_cb = -> (protocols) { protocols.first } - assert_handshake_error { server_connect(port, ctx) } - } + ctx = OpenSSL::SSL::SSLContext.new + assert_raise(OpenSSL::SSL::SSLError) do + ctx.npn_protocols = ["a" * 256] + ctx.setup + end end def test_npn_selected_protocol_too_long return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } - start_server_version(:TLSv1_2, ctx_proc) { |port| + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { "a" * 256 } - assert_handshake_error { server_connect(port, ctx) } + assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } } end @@ -1598,14 +1749,17 @@ def test_sync_close_without_connect end def test_get_ephemeral_key + # kRSA is not FIPS-approved. + omit_on_fips + # kRSA ctx_proc1 = proc { |ctx| - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kRSA" } start_server(ctx_proc: ctx_proc1, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kRSA" begin server_connect(port, ctx) { |ssl| assert_nil ssl.tmp_key } @@ -1616,30 +1770,27 @@ def test_get_ephemeral_key end # DHE - # TODO: How to test this with TLS 1.3? - ctx_proc2 = proc { |ctx| - ctx.ssl_version = :TLSv1_2 - ctx.ciphers = "EDH" - ctx.tmp_dh = Fixtures.pkey("dh-1") - } - start_server(ctx_proc: ctx_proc2) do |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_2 - ctx.ciphers = "EDH" - server_connect(port, ctx) { |ssl| - assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key - } + # OpenSSL 3.0 added support for named FFDHE groups in TLS 1.3 + # LibreSSL does not support named FFDHE groups currently + # AWS-LC does not support DHE ciphersuites + if openssl?(3, 0, 0) + start_server do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.groups = "ffdhe3072" + server_connect(port, ctx) { |ssl| + assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key + assert_equal 3072, ssl.tmp_key.p.num_bits + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + } + end end # ECDHE ctx_proc3 = proc { |ctx| - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" } start_server(ctx_proc: ctx_proc3) do |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - server_connect(port, ctx) { |ssl| + server_connect(port) { |ssl| assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key ssl.puts "abc"; assert_equal "abc\n", ssl.gets } @@ -1648,11 +1799,11 @@ def test_get_ephemeral_key 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 @@ -1663,11 +1814,15 @@ def test_fallback_scsv 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 @@ -1685,19 +1840,24 @@ def test_fallback_scsv # 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 @@ -1708,6 +1868,10 @@ def test_fallback_scsv 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 { @@ -1727,11 +1891,6 @@ def test_tmp_dh_callback end def test_ciphersuites_method_tls_connection - ssl_ctx = OpenSSL::SSL::SSLContext.new - if !tls13_supported? || !ssl_ctx.respond_to?(:ciphersuites=) - pend 'TLS 1.3 not supported' - end - csuite = ['TLS_AES_128_GCM_SHA256', 'TLSv1.3', 128, 128] inputs = [csuite[0], [csuite[0]], [csuite]] @@ -1743,11 +1902,7 @@ def test_ciphersuites_method_tls_connection server_connect(port, cli_ctx) do |ssl| assert_equal('TLSv1.3', ssl.ssl_version) - if libressl?(3, 4, 0) && !libressl?(3, 5, 0) - assert_equal("AEAD-AES128-GCM-SHA256", ssl.cipher[0]) - else - assert_equal(csuite[0], ssl.cipher[0]) - end + assert_equal(csuite[0], ssl.cipher[0]) ssl.puts('abc'); assert_equal("abc\n", ssl.gets) end end @@ -1756,26 +1911,21 @@ def test_ciphersuites_method_tls_connection def test_ciphersuites_method_nil_argument ssl_ctx = OpenSSL::SSL::SSLContext.new - pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=) - assert_nothing_raised { ssl_ctx.ciphersuites = nil } end def test_ciphersuites_method_frozen_object ssl_ctx = OpenSSL::SSL::SSLContext.new - pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=) - ssl_ctx.freeze assert_raise(FrozenError) { ssl_ctx.ciphersuites = 'TLS_AES_256_GCM_SHA384' } end def test_ciphersuites_method_bogus_csuite ssl_ctx = OpenSSL::SSL::SSLContext.new - pend 'ciphersuites= method is missing' unless ssl_ctx.respond_to?(:ciphersuites=) - + # AWS-LC has slightly different error messages in all-caps. assert_raise_with_message( OpenSSL::SSL::SSLError, - /SSL_CTX_set_ciphersuites: no cipher match/i + /SSL_CTX_set_ciphersuites: (no cipher match|NO_CIPHER_MATCH)/i ) { ssl_ctx.ciphersuites = 'BOGUS' } end @@ -1811,34 +1961,184 @@ def test_ciphers_method_frozen_object end def test_ciphers_method_bogus_csuite - omit "Old #{OpenSSL::OPENSSL_LIBRARY_VERSION}" if - year = OpenSSL::OPENSSL_LIBRARY_VERSION[/\A OpenSSL\s+[01]\..*\s\K\d+\z/x] and - year.to_i <= 2018 - ssl_ctx = OpenSSL::SSL::SSLContext.new + # AWS-LC has slightly different error messages in all-caps. assert_raise_with_message( OpenSSL::SSL::SSLError, - /SSL_CTX_set_cipher_list: no cipher match/i + /SSL_CTX_set_cipher_list: (no cipher match|NO_CIPHER_MATCH)/i ) { ssl_ctx.ciphers = 'BOGUS' } end + def test_sigalgs + omit "SSL_CTX_set1_sigalgs_list() not supported" if libressl? + + svr_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:localhost", false], + ] + ecdsa_key = Fixtures.pkey("p256") + ecdsa_cert = issue_cert(@svr, ecdsa_key, 10, svr_exts, @ca_cert, @ca_key) + + ctx_proc = -> ctx { + # Unset values set by start_server + ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + ctx.add_certificate(ecdsa_cert, ecdsa_key, [@ca_cert]) # ECDSA + } + start_server(ctx_proc: ctx_proc) do |port| + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.sigalgs = "rsa_pss_rsae_sha256" + server_connect(port, ctx1) { |ssl| + assert_kind_of(OpenSSL::PKey::RSA, ssl.peer_cert.public_key) + ssl.puts("abc"); ssl.gets + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.sigalgs = "ed25519:ecdsa_secp256r1_sha256" + server_connect(port, ctx2) { |ssl| + assert_kind_of(OpenSSL::PKey::EC, ssl.peer_cert.public_key) + ssl.puts("abc"); ssl.gets + } + end + + # Frozen + ssl_ctx = OpenSSL::SSL::SSLContext.new + ssl_ctx.freeze + assert_raise(FrozenError) { ssl_ctx.sigalgs = "ECDSA+SHA256:RSA+SHA256" } + + # Bogus + ssl_ctx = OpenSSL::SSL::SSLContext.new + assert_raise(TypeError) { ssl_ctx.sigalgs = nil } + assert_raise(OpenSSL::SSL::SSLError) { ssl_ctx.sigalgs = "BOGUS" } + end + + def test_client_sigalgs + omit "SSL_CTX_set1_client_sigalgs_list() not supported" if libressl? || aws_lc? + + cli_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:localhost", false], + ] + ecdsa_key = Fixtures.pkey("p256") + ecdsa_cert = issue_cert(@cli, ecdsa_key, 10, cli_exts, @ca_cert, @ca_key) + + ctx_proc = -> ctx { + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + ctx.cert_store = store + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + ctx.client_sigalgs = "ECDSA+SHA256" + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.add_certificate(@cli_cert, @cli_key) # RSA + assert_handshake_error { + server_connect(port, ctx1) { |ssl| + ssl.puts("abc"); ssl.gets + } + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.add_certificate(ecdsa_cert, ecdsa_key) # ECDSA + server_connect(port, ctx2) { |ssl| + ssl.puts("abc"); ssl.gets + } + end + end + + def test_get_sigalg + # SSL_get0_signature_name() not supported + # SSL_get0_peer_signature_name() not supported + return unless openssl?(3, 5, 0) + + server_proc = -> (ctx, ssl) { + assert_equal('rsa_pss_rsae_sha256', ssl.sigalg) + assert_nil(ssl.peer_sigalg) + + readwrite_loop(ctx, ssl) + } + start_server(server_proc: server_proc) do |port| + cli_ctx = OpenSSL::SSL::SSLContext.new + server_connect(port, cli_ctx) do |ssl| + assert_nil(ssl.sigalg) + assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg) + ssl.puts "abc"; ssl.gets + end + end + end + + def test_pqc_sigalg + # PQC algorithm ML-DSA (FIPS 204) is supported on OpenSSL 3.5 or later. + return unless openssl?(3, 5, 0) + + mldsa = Fixtures.pkey("mldsa65-1") + mldsa_ca_key = Fixtures.pkey("mldsa65-2") + mldsa_ca_cert = issue_cert(@ca, mldsa_ca_key, 1, @ca_exts, nil, nil, + digest: nil) + mldsa_cert = issue_cert(@svr, mldsa, 60, [], mldsa_ca_cert, mldsa_ca_key, + digest: nil) + rsa = Fixtures.pkey("rsa-1") + rsa_cert = issue_cert(@svr, rsa, 61, [], @ca_cert, @ca_key) + ctx_proc = -> ctx { + # Unset values set by start_server + ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.sigalgs = "rsa_pss_rsae_sha256:mldsa65" + ctx.add_certificate(mldsa_cert, mldsa) + ctx.add_certificate(rsa_cert, rsa) + } + + server_proc = -> (ctx, ssl) { + assert_equal('mldsa65', ssl.sigalg) + + readwrite_loop(ctx, ssl) + } + start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + # Set signature algorithm because while OpenSSL may use ML-DSA by + # default, the system OpenSSL configuration affects the used signature + # algorithm. + ctx.sigalgs = 'mldsa65' + server_connect(port, ctx) { |ssl| + assert_equal('mldsa65', ssl.peer_sigalg) + ssl.puts "abc"; ssl.gets + } + end + + server_proc = -> (ctx, ssl) { + assert_equal('rsa_pss_rsae_sha256', ssl.sigalg) + + readwrite_loop(ctx, ssl) + } + start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.sigalgs = 'rsa_pss_rsae_sha256' + server_connect(port, ctx) { |ssl| + assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg) + ssl.puts "abc"; ssl.gets + } + end + end + def test_connect_works_when_setting_dh_callback_to_nil + omit "AWS-LC does not support DHE ciphersuites" if aws_lc? + ctx_proc = -> ctx { ctx.max_version = :TLS1_2 ctx.ciphers = "DH:!NULL" # use DH ctx.tmp_dh_callback = nil } start_server(ctx_proc: ctx_proc) do |port| - EnvUtil.suppress_warning { # uses default callback - assert_nothing_raised { - server_connect(port) { } - } - } + assert_nothing_raised { server_connect(port) { } } end end def test_tmp_dh + # DH missing the q value on unknown named parameters is not FIPS-approved. + omit_on_fips + omit "AWS-LC does not support DHE ciphersuites" if aws_lc? + dh = Fixtures.pkey("dh-1") ctx_proc = -> ctx { ctx.max_version = :TLS1_2 @@ -1852,86 +2152,125 @@ def test_tmp_dh end end - def test_ecdh_curves_tls12 + def test_set_groups_tls12 ctx_proc = -> ctx { # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kEECDH" - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| # Test 1: Client=P-256:P-384, Server=P-384:P-521 --> P-384 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" + ctx.groups = "P-256:P-384" server_connect(port, ctx) { |ssl| cs = ssl.cipher[0] assert_match (/\AECDH/), cs + # SSL_get0_group_name() is supported on OpenSSL 3.2 or later. + assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0) assert_equal "secp384r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } # Test 2: Client=P-256, Server=P-521:P-384 --> Fail ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) { } } # Test 3: Client=P-521:P-384, Server=P-521:P-384 --> P-521 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-521:P-384" + ctx.groups = "P-521:P-384" server_connect(port, ctx) { |ssl| assert_equal "secp521r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } + + # Test 4: #ecdh_curves= alias + ctx = OpenSSL::SSL::SSLContext.new + ctx.ecdh_curves = "P-256:P-384" + server_connect(port, ctx) { |ssl| + assert_equal "secp384r1", ssl.tmp_key.group.curve_name + } end end - def test_ecdh_curves_tls13 - pend "TLS 1.3 not supported" unless tls13_supported? - + def test_set_groups_tls13 ctx_proc = -> ctx { # Assume TLS 1.3 is enabled and chosen by default - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" # disable P-521 + ctx.groups = "P-256:P-384" # disable P-521 server_connect(port, ctx) { |ssl| assert_equal "TLSv1.3", ssl.ssl_version + # SSL_get0_group_name() is supported on OpenSSL 3.2 or later. + assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0) assert_equal "secp384r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end + def test_pqc_group + # PQC algorithm ML-KEM (FIPS 203) is supported on OpenSSL 3.5 or later. + return unless openssl?(3, 5, 0) + + [ + 'X25519MLKEM768', + 'SecP256r1MLKEM768', + 'SecP384r1MLKEM1024' + ].each do |group| + ctx_proc = -> ctx { + ctx.groups = group + } + start_server(ctx_proc: ctx_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.groups = group + server_connect(port, ctx) { |ssl| + assert_equal(group, ssl.group) + ssl.puts "abc"; ssl.gets + } + end + end + end + def test_security_level ctx = OpenSSL::SSL::SSLContext.new - begin - ctx.security_level = 1 - rescue NotImplementedError + ctx.security_level = 1 + if aws_lc? # AWS-LC does not support security levels. assert_equal(0, ctx.security_level) return end assert_equal(1, ctx.security_level) - dsa512 = Fixtures.pkey("dsa512") - dsa512_cert = issue_cert(@svr, dsa512, 50, [], @ca_cert, @ca_key) - rsa1024 = Fixtures.pkey("rsa1024") - rsa1024_cert = issue_cert(@svr, rsa1024, 51, [], @ca_cert, @ca_key) + # See SSL_CTX_set_security_level(3). Definitions of security levels may + # change in future OpenSSL versions. As of OpenSSL 1.1.0: + # - Level 1 requires 160-bit ECC keys or 1024-bit RSA keys. + # - Level 2 requires 224-bit ECC keys or 2048-bit RSA keys. + begin + ec112 = OpenSSL::PKey::EC.generate("secp112r1") + ec112_cert = issue_cert(@svr, ec112, 50, [], @ca_cert, @ca_key) + ec192 = OpenSSL::PKey::EC.generate("prime192v1") + ec192_cert = issue_cert(@svr, ec192, 51, [], @ca_cert, @ca_key) + rescue OpenSSL::PKey::PKeyError + # Distro-provided OpenSSL may refuse to generate small keys + return + end assert_raise(OpenSSL::SSL::SSLError) { - # 512 bit DSA key is rejected because it offers < 80 bits of security - ctx.add_certificate(dsa512_cert, dsa512) + ctx.add_certificate(ec112_cert, ec112) } assert_nothing_raised { - ctx.add_certificate(rsa1024_cert, rsa1024) + ctx.add_certificate(ec192_cert, ec192) } ctx.security_level = 2 assert_raise(OpenSSL::SSL::SSLError) { # < 112 bits of security - ctx.add_certificate(rsa1024_cert, rsa1024) + ctx.add_certificate(ec192_cert, ec192) } end @@ -1987,22 +2326,52 @@ def test_export_keying_material end end - private + # OpenSSL::Buffering requires $/ accessible from non-main Ractors (Ruby 4.0) + # https://bugs.ruby-lang.org/issues/21109 + # + # Hangs on Windows + # https://bugs.ruby-lang.org/issues/21537 + if respond_to?(:ractor) && RUBY_VERSION >= "4.0" && RUBY_PLATFORM !~ /mswin|mingw/ + ractor + def test_ractor_client + start_server { |port| + s = Ractor.new(port, @ca_cert) { |port, ca_cert| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + ctx.cert_store = OpenSSL::X509::Store.new.tap { |store| + store.add_cert(ca_cert) + } + begin + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.connect + ssl.puts("abc") + ssl.gets + ensure + ssl.close + sock.close + end + }.value + assert_equal("abc\n", s) + } + end - def start_server_version(version, ctx_proc = nil, - server_proc = method(:readwrite_loop), &blk) - ctx_wrap = Proc.new { |ctx| - ctx.ssl_version = version - ctx_proc.call(ctx) if ctx_proc - } - start_server( - ctx_proc: ctx_wrap, - server_proc: server_proc, - ignore_listener_error: true, - &blk - ) + ractor + def test_ractor_set_params + # We cannot actually test default stores in the test suite as it depends + # on the environment, but at least check that it does not raise an + # exception + ok = Ractor.new { + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params + ctx.cert_store.kind_of?(OpenSSL::X509::Store) + }.value + assert(ok, "ctx.cert_store is an instance of OpenSSL::X509::Store") + end end + private + def server_connect(port, ctx = nil) sock = TCPSocket.new("127.0.0.1", port) ssl = ctx ? OpenSSL::SSL::SSLSocket.new(sock, ctx) : OpenSSL::SSL::SSLSocket.new(sock) diff --git a/test/mri/openssl/test_ssl_session.rb b/test/mri/openssl/test_ssl_session.rb index 25ba6a8c45e..37874ca2733 100644 --- a/test/mri/openssl/test_ssl_session.rb +++ b/test/mri/openssl/test_ssl_session.rb @@ -5,7 +5,9 @@ class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase def test_session - ctx_proc = proc { |ctx| ctx.ssl_version = :TLSv1_2 } + ctx_proc = proc { |ctx| + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + } start_server(ctx_proc: ctx_proc) do |port| server_connect_with_session(port, nil, nil) { |ssl| session = ssl.session @@ -28,9 +30,10 @@ def test_session end end + # PEM file updated to use TLS 1.2 with ECDHE-RSA-AES256-SHA. DUMMY_SESSION = <<__EOS__ -----BEGIN SSL SESSION PARAMETERS----- -MIIDzQIBAQICAwEEAgA5BCAF219w9ZEV8dNA60cpEGOI34hJtIFbf3bkfzSgMyad +MIIDzQIBAQICAwMEAsAUBCAF219w9ZEV8dNA60cpEGOI34hJtIFbf3bkfzSgMyad MQQwyGLbkCxE4OiMLdKKem+pyh8V7ifoP7tCxhdmwoDlJxI1v6nVCjai+FGYuncy NNSWoQYCBE4DDWuiAwIBCqOCAo4wggKKMIIBcqADAgECAgECMA0GCSqGSIb3DQEB BQUAMD0xEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5 @@ -54,9 +57,10 @@ def test_session -----END SSL SESSION PARAMETERS----- __EOS__ + # PEM file updated to use TLS 1.1 with ECDHE-RSA-AES256-SHA. DUMMY_SESSION_NO_EXT = <<-__EOS__ -----BEGIN SSL SESSION PARAMETERS----- -MIIDCAIBAQICAwAEAgA5BCDyAW7rcpzMjDSosH+Tv6sukymeqgq3xQVVMez628A+ +MIIDCAIBAQICAwIEAsAUBCDyAW7rcpzMjDSosH+Tv6sukymeqgq3xQVVMez628A+ lAQw9TrKzrIqlHEh6ltuQaqv/Aq83AmaAlogYktZgXAjOGnhX7ifJDNLMuCfQq53 hPAaoQYCBE4iDeeiBAICASyjggKOMIICijCCAXKgAwIBAgIBAjANBgkqhkiG9w0B AQUFADA9MRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVi @@ -120,7 +124,8 @@ def test_resumption 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?(3, 2, 0) + # Session tickets must be retrieved via ctx.session_new_cb in TLS 1.3 in AWS-LC. + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? || aws_lc? } start_server(ctx_proc: ctx_proc) do |port| sess1 = server_connect_with_session(port, nil, nil) { |ssl| @@ -143,7 +148,7 @@ def test_resumption def test_server_session_cache ctx_proc = Proc.new do |ctx| - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.options |= OpenSSL::SSL::OP_NO_TICKET end @@ -197,7 +202,7 @@ def test_server_session_cache 10.times do |i| connections = i cctx = OpenSSL::SSL::SSLContext.new - cctx.ssl_version = :TLSv1_2 + cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION server_connect_with_session(port, cctx, first_session) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets first_session ||= ssl.session @@ -217,7 +222,7 @@ def test_server_session_cache # Skipping tests that use session_remove_cb by default because it may cause # deadlock. - TEST_SESSION_REMOVE_CB = ENV["OSSL_TEST_ALL"] == "1" + TEST_SESSION_REMOVE_CB = ENV["OSSL_TEST_UNSAFE"] == "1" def test_ctx_client_session_cb_tls12 start_server do |port| @@ -237,21 +242,25 @@ def test_ctx_client_session_cb_tls12 end server_connect_with_session(port, ctx, nil) { |ssl| - assert_equal(1, ctx.session_cache_stats[:cache_num]) assert_equal(1, ctx.session_cache_stats[:connect_good]) assert_equal([ssl, ssl.session], called[:new]) - assert_equal(true, ctx.session_remove(ssl.session)) - assert_equal(false, ctx.session_remove(ssl.session)) - if TEST_SESSION_REMOVE_CB - assert_equal([ctx, ssl.session], called[:remove]) + # AWS-LC doesn't support internal session caching on the client, but + # the callback is still enabled as expected. + unless aws_lc? + assert_equal(1, ctx.session_cache_stats[:cache_num]) + assert_equal(true, ctx.session_remove(ssl.session)) + if TEST_SESSION_REMOVE_CB + assert_equal([ctx, ssl.session], called[:remove]) + end end + assert_equal(false, ctx.session_remove(ssl.session)) } end end def test_ctx_client_session_cb_tls13 - omit "TLS 1.3 not supported" unless tls13_supported? omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl? + omit "AWS-LC does not support internal session caching on the client" if aws_lc? start_server do |port| called = {} @@ -274,7 +283,6 @@ def test_ctx_client_session_cb_tls13 end def test_ctx_client_session_cb_tls13_exception - omit "TLS 1.3 not supported" unless tls13_supported? omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl? server_proc = lambda do |ctx, ssl| @@ -301,11 +309,11 @@ def test_ctx_server_session_cb connections = nil called = {} cctx = OpenSSL::SSL::SSLContext.new - cctx.ssl_version = :TLSv1_2 + cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION sctx = nil ctx_proc = Proc.new { |ctx| sctx = ctx - ctx.ssl_version = :TLSv1_2 + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.options |= OpenSSL::SSL::OP_NO_TICKET # get_cb is called whenever a client proposed to resume a session but @@ -375,11 +383,6 @@ def test_ctx_server_session_cb connections = 2 sess2 = server_connect_with_session(port, cctx, sess0.dup) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets - if !ssl.session_reused? && openssl?(1, 1, 0) && !openssl?(1, 1, 0, 7) - # OpenSSL >= 1.1.0, < 1.1.0g - pend "External session cache is not working; " \ - "see https://github.com/openssl/openssl/pull/4014" - end assert_equal true, ssl.session_reused? ssl.session } diff --git a/test/mri/openssl/test_ts.rb b/test/mri/openssl/test_ts.rb index ac0469ad567..cca7898bc13 100644 --- a/test/mri/openssl/test_ts.rb +++ b/test/mri/openssl/test_ts.rb @@ -70,15 +70,14 @@ def ts_cert_ee 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 @@ def test_request_assignment 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 @@ def test_no_cert_requested 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 @@ def test_verify_direct_unrelated_untrusted 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/mri/openssl/test_x509cert.rb b/test/mri/openssl/test_x509cert.rb index 6c16218f387..9e0aa4edf6b 100644 --- a/test/mri/openssl/test_x509cert.rb +++ b/test/mri/openssl/test_x509cert.rb @@ -6,17 +6,16 @@ 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 @@ def test_public_key ["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 @@ def test_extension_factory ["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 @@ def test_extension_factory ["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 @@ def test_extension_factory 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 @@ def test_akiski 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 @@ def test_crl_uris 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 @@ def test_crl_uris_multiple_general_names [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 @@ def test_crl_uris_no_uris [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 @@ def test_aia 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 @@ def test_aia 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,84 +234,31 @@ def test_invalid_extension } 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)) - 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") - } + assert_equal(false, cert.verify(@rsa1)) end - def test_sign_and_verify_ed25519 + def test_sign_and_verify_nil_digest # Ed25519 is not FIPS-approved. omit_on_fips - # See ASN1_item_sign_ctx in ChangeLog for 3.8.1: https://github.com/libressl/portable/blob/master/ChangeLog - omit "Ed25519 not supported" unless openssl?(1, 1, 1) || libressl?(3, 8, 1) ed25519 = OpenSSL::PKey::generate_key("ED25519") cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil) assert_equal(true, cert.verify(ed25519)) end - def test_dsa_with_sha2 - cert = issue_cert(@ca, @dsa256, 1, [], nil, nil, digest: "sha256") - assert_equal("dsa_with_SHA256", cert.signature_algorithm) - # TODO: need more tests for dsa + sha2 - - # SHA1 is allowed from OpenSSL 1.0.0 (0.9.8 requires DSS1) - cert = issue_cert(@ca, @dsa256, 1, [], nil, nil, digest: "sha1") - assert_equal("dsaWithSHA1", cert.signature_algorithm) - rescue OpenSSL::X509::CertificateError # RHEL 9 disables SHA1 - end - def test_check_private_key - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) - assert_equal(true, cert.check_private_key(@rsa2048)) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + assert_equal(true, cert.check_private_key(@rsa1)) end def test_read_from_file - cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) Tempfile.create("cert") { |f| f << cert.to_pem f.rewind @@ -327,12 +267,12 @@ def test_read_from_file 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) @@ -340,15 +280,15 @@ def test_read_der_then_pem 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 @@ -358,11 +298,19 @@ def test_eq 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)) @@ -370,41 +318,58 @@ def test_marshal end def test_load_file_empty_pem - empty_path = Fixtures.file_path("pkey", "empty.pem") - assert_raise(OpenSSL::X509::CertificateError) do - OpenSSL::X509::Certificate.load_file(empty_path) + Tempfile.create("empty.pem") do |f| + f.close + + assert_raise(OpenSSL::X509::CertificateError) do + OpenSSL::X509::Certificate.load_file(f.path) + end end end def test_load_file_fullchain_pem - fullchain_path = Fixtures.file_path("pkey", "fullchain.pem") - certificates = OpenSSL::X509::Certificate.load_file(fullchain_path) - assert_equal 2, certificates.size - assert_equal "/CN=www.codeotaku.com", certificates[0].subject.to_s - assert_equal "/C=US/O=Let's Encrypt/CN=R3", certificates[1].subject.to_s + 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 + f.puts cert2.to_pem + f.close + + certificates = OpenSSL::X509::Certificate.load_file(f.path) + assert_equal 2, certificates.size + assert_equal @ee1, certificates[0].subject + assert_equal @ca, certificates[1].subject + end end def test_load_file_certificate_der - fullchain_path = Fixtures.file_path("pkey", "certificate.der") - certificates = OpenSSL::X509::Certificate.load_file(fullchain_path) + cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) + Tempfile.create("certificate.der", binmode: true) do |f| + f.write cert.to_der + f.close - # DER encoding can only contain one certificate: - assert_equal 1, certificates.size - assert_equal "/CN=www.codeotaku.com", certificates[0].subject.to_s + certificates = OpenSSL::X509::Certificate.load_file(f.path) + + # DER encoding can only contain one certificate: + assert_equal 1, certificates.size + assert_equal cert.to_der, certificates[0].to_der + end end def test_load_file_fullchain_garbage - fullchain_path = Fixtures.file_path("pkey", "garbage.txt") + Tempfile.create("garbage.txt") do |f| + f.puts "not a certificate" + f.close - assert_raise(OpenSSL::X509::CertificateError) do - OpenSSL::X509::Certificate.load_file(fullchain_path) + assert_raise(OpenSSL::X509::CertificateError) do + OpenSSL::X509::Certificate.load_file(f.path) + end end end def test_tbs_precert_bytes - pend "LibreSSL < 3.5 does not have i2d_re_X509_tbs" if libressl? && !libressl?(3, 5, 0) - - 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/mri/openssl/test_x509crl.rb b/test/mri/openssl/test_x509crl.rb index e5fa6f9989c..81c9247df2c 100644 --- a/test/mri/openssl/test_x509crl.rb +++ b/test/mri/openssl/test_x509crl.rb @@ -6,25 +6,21 @@ 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 @@ def test_revoked [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 @@ def test_revoked 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 @@ def test_extension ["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,61 +156,55 @@ def test_extension 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 - # See ASN1_item_sign_ctx in ChangeLog for 3.8.1: https://github.com/libressl/portable/blob/master/ChangeLog - omit "Ed25519 not supported" unless openssl?(1, 1, 1) || libressl?(3, 8, 1) ed25519 = OpenSSL::PKey::generate_key("ED25519") cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil) crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, ed25519, nil) - assert_equal(false, crl_error_returns_false { crl.verify(@rsa1024) }) - assert_equal(false, crl_error_returns_false { crl.verify(@rsa2048) }) + assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) }) assert_equal(false, crl.verify(OpenSSL::PKey::generate_key("ED25519"))) assert_equal(true, crl.verify(ed25519)) crl.version = 0 @@ -247,8 +237,8 @@ def test_revoked_to_der 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 @@ -276,8 +266,8 @@ def test_eq 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/mri/openssl/test_x509name.rb b/test/mri/openssl/test_x509name.rb index c6d15219f58..223c575e4e9 100644 --- a/test/mri/openssl/test_x509name.rb +++ b/test/mri/openssl/test_x509name.rb @@ -423,24 +423,14 @@ def test_spaceship 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/mri/openssl/test_x509req.rb b/test/mri/openssl/test_x509req.rb index 1bf457ecf66..b198a1185ab 100644 --- a/test/mri/openssl/test_x509req.rb +++ b/test/mri/openssl/test_x509req.rb @@ -6,10 +6,8 @@ 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 @@ def issue_csr(ver, dn, key, digest) 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 @@ def test_attr 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,67 +94,44 @@ def test_attr 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 - # See ASN1_item_sign_ctx in ChangeLog for 3.8.1: https://github.com/libressl/portable/blob/master/ChangeLog - omit "Ed25519 not supported" unless openssl?(1, 1, 1) || libressl?(3, 8, 1) ed25519 = OpenSSL::PKey::generate_key("ED25519") req = issue_csr(0, @dn, ed25519, nil) - assert_equal(false, request_error_returns_false { req.verify(@rsa1024) }) - assert_equal(false, request_error_returns_false { req.verify(@rsa2048) }) + assert_equal(false, request_error_returns_false { req.verify(@rsa1) }) + assert_equal(false, request_error_returns_false { req.verify(@rsa2) }) assert_equal(false, req.verify(OpenSSL::PKey::generate_key("ED25519"))) assert_equal(true, req.verify(ed25519)) - req.public_key = @rsa1024.public_key + req.public_key = @rsa1 assert_equal(false, req.verify(ed25519)) end def test_dup - req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256')) + req = issue_csr(0, @dn, @rsa1, "SHA256") assert_equal(req.to_der, req.dup.to_der) end def test_eq - req1 = issue_csr(0, @dn, @rsa1024, "sha256") - req2 = issue_csr(0, @dn, @rsa1024, "sha256") - req3 = issue_csr(0, @dn, @rsa1024, "sha512") + req1 = issue_csr(0, @dn, @rsa1, "SHA256") + req2 = issue_csr(0, @dn, @rsa1, "SHA256") + req3 = issue_csr(0, @dn, @rsa1, "SHA512") assert_equal false, req1 == 12345 assert_equal true, req1 == req2 @@ -163,7 +139,7 @@ def test_eq 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/mri/openssl/test_x509store.rb b/test/mri/openssl/test_x509store.rb index d6c0e707a27..c13beae364f 100644 --- a/test/mri/openssl/test_x509store.rb +++ b/test/mri/openssl/test_x509store.rb @@ -91,6 +91,18 @@ def test_verify_simple assert_match(/ok/i, store.error_string) assert_equal(OpenSSL::X509::V_OK, store.error) assert_equal([ee1_cert, ca2_cert, ca1_cert], store.chain) + + # Manually instantiated StoreContext + # Nothing trusted + store = OpenSSL::X509::Store.new + ctx = OpenSSL::X509::StoreContext.new(store, ee1_cert) + assert_nil(ctx.current_cert) + assert_nil(ctx.current_crl) + assert_equal(false, ctx.verify) + assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ctx.error) + assert_equal(0, ctx.error_depth) + assert_equal([ee1_cert], ctx.chain) + assert_equal(ee1_cert, ctx.current_cert) end def test_verify_callback @@ -329,15 +341,12 @@ def test_verify_with_crl end def test_add_cert_duplicate - # Up until OpenSSL 1.1.0, X509_STORE_add_{cert,crl}() returned an error - # if the given certificate is already in the X509_STORE - return if openssl?(1, 1, 0) || libressl? ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA") ca1_key = Fixtures.pkey("rsa-1") ca1_cert = issue_cert(ca1, ca1_key, 1, [], nil, nil) store = OpenSSL::X509::Store.new store.add_cert(ca1_cert) - assert_raise(OpenSSL::X509::StoreError){ + assert_nothing_raised { store.add_cert(ca1_cert) # add same certificate twice } @@ -349,7 +358,7 @@ def test_add_cert_duplicate crl2 = issue_crl(revoke_info, 2, now+1800, now+3600, [], ca1_cert, ca1_key, "sha256") store.add_crl(crl1) - assert_raise(OpenSSL::X509::StoreError){ + assert_nothing_raised { store.add_crl(crl2) # add CRL issued by same CA twice. } end diff --git a/test/mri/openssl/utils.rb b/test/mri/openssl/utils.rb index f6c84eef67c..7e6fe8b1633 100644 --- a/test/mri/openssl/utils.rb +++ b/test/mri/openssl/utils.rb @@ -24,10 +24,6 @@ def read_file(category, name) @file_cache[[category, name]] ||= File.read(File.join(__dir__, "fixtures", category, name + ".pem")) end - - def file_path(category, name) - File.join(__dir__, "fixtures", category, name) - end end module_function @@ -107,7 +103,7 @@ def get_subject_key_id(cert, hex: true) end def openssl?(major = nil, minor = nil, fix = nil, patch = 0, status = 0) - return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL") + return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL") || OpenSSL::OPENSSL_VERSION.include?("AWS-LC") return true unless major OpenSSL::OPENSSL_VERSION_NUMBER >= major * 0x10000000 + minor * 0x100000 + fix * 0x1000 + patch * 0x10 + @@ -119,6 +115,10 @@ def libressl?(major = nil, minor = nil, fix = nil) return false unless version !major || (version.map(&:to_i) <=> [major, minor, fix]) >= 0 end + + def aws_lc? + OpenSSL::OPENSSL_VERSION.include?("AWS-LC") + end end class OpenSSL::TestCase < Test::Unit::TestCase @@ -177,43 +177,31 @@ def setup @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") @svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") @cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") - ca_exts = [ + @ca_exts = [ ["basicConstraints","CA:TRUE",true], ["keyUsage","cRLSign,keyCertSign",true], ] - ee_exts = [ + @ee_exts = [ ["keyUsage","keyEncipherment,digitalSignature",true], ] - @ca_cert = issue_cert(@ca, @ca_key, 1, ca_exts, nil, nil) - @svr_cert = issue_cert(@svr, @svr_key, 2, ee_exts, @ca_cert, @ca_key) - @cli_cert = issue_cert(@cli, @cli_key, 3, ee_exts, @ca_cert, @ca_key) + @ca_cert = issue_cert(@ca, @ca_key, 1, @ca_exts, nil, nil) + @svr_cert = issue_cert(@svr, @svr_key, 2, @ee_exts, @ca_cert, @ca_key) + @cli_cert = issue_cert(@cli, @cli_key, 3, @ee_exts, @ca_cert, @ca_key) @server = nil end - def tls13_supported? - return false unless defined?(OpenSSL::SSL::TLS1_3_VERSION) - ctx = OpenSSL::SSL::SSLContext.new - ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION - true - rescue - end - def readwrite_loop(ctx, ssl) while line = ssl.gets ssl.write(line) end end - def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, start_immediately: true, + def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, ctx_proc: nil, server_proc: method(:readwrite_loop), accept_proc: proc{}, ignore_listener_error: false, &block) IO.pipe {|stop_pipe_r, stop_pipe_w| - store = OpenSSL::X509::Store.new - store.add_cert(@ca_cert) - store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT ctx = OpenSSL::SSL::SSLContext.new - ctx.cert_store = store ctx.cert = @svr_cert ctx.key = @svr_key ctx.verify_mode = verify_mode @@ -224,7 +212,6 @@ def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, start_immediately: true port = tcps.connect_address.ip_port ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) - ssls.start_immediately = start_immediately threads = [] begin @@ -299,6 +286,41 @@ def check_component(base, test, keys) 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/mri/optparse/test_load.rb b/test/mri/optparse/test_load.rb index 0ebe855682a..f664cfbf722 100644 --- a/test/mri/optparse/test_load.rb +++ b/test/mri/optparse/test_load.rb @@ -31,7 +31,13 @@ def assert_load(result) 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 @@ def setup_options(env, dir, suffix = nil) begin yield dir, optdir ensure - File.unlink(file) + File.unlink(file) rescue nil Dir.rmdir(optdir) rescue nil end else @@ -50,7 +56,7 @@ def setup_options(env, dir, suffix = nil) 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 @@ def setup_options_xdg_config_home(&block) 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 @@ def setup_options_xdg_config_dirs(&block) 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 @@ def test_load_home_options 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 @@ def test_load_xdg_config_home 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 @@ def test_load_home_config 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 @@ def test_load_home_config_settings 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/mri/optparse/test_optparse.rb b/test/mri/optparse/test_optparse.rb index 7f35cb4a8a0..ff334009a69 100644 --- a/test/mri/optparse/test_optparse.rb +++ b/test/mri/optparse/test_optparse.rb @@ -184,10 +184,9 @@ def test_help_pager 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 @@ def stdout.tty?; true; end 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/mri/optparse/test_placearg.rb b/test/mri/optparse/test_placearg.rb index a8a11e676b8..d5be5a66fb1 100644 --- a/test/mri/optparse/test_placearg.rb +++ b/test/mri/optparse/test_placearg.rb @@ -7,6 +7,10 @@ def setup @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 @@ def test_lambda 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/mri/optparse/test_switch.rb b/test/mri/optparse/test_switch.rb new file mode 100644 index 00000000000..b06f4e310bc --- /dev/null +++ b/test/mri/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=", ["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=", {"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=", 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/mri/pathname/test_pathname.rb b/test/mri/pathname/test_pathname.rb index 6a4bb784bd0..3114d1458d2 100644 --- a/test/mri/pathname/test_pathname.rb +++ b/test/mri/pathname/test_pathname.rb @@ -348,7 +348,7 @@ def has_symlink? rescue NotImplementedError return false rescue Errno::ENOENT - return false + return true rescue Errno::EACCES return false end @@ -370,10 +370,11 @@ def has_hardlink? 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 +435,7 @@ def realdirpath(path) 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 +484,27 @@ def test_initialize 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 + 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')) @@ -682,6 +699,7 @@ def test_each_child 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 +726,7 @@ def test_each_line 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 +834,7 @@ def test_atime 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 +1071,11 @@ def test_lutime 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 +1345,8 @@ def test_s_glob 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/mri/pathname/test_ractor.rb b/test/mri/pathname/test_ractor.rb index 3d7b63deed4..737e4a4111d 100644 --- a/test/mri/pathname/test_ractor.rb +++ b/test/mri/pathname/test_ractor.rb @@ -9,14 +9,22 @@ def setup 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/mri/prism/api/freeze_test.rb b/test/mri/prism/api/freeze_test.rb new file mode 100644 index 00000000000..5533a003313 --- /dev/null +++ b/test/mri/prism/api/freeze_test.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Prism + class FreezeTest < TestCase + def test_parse + assert_frozen(Prism.parse("1 + 2; %i{foo} + %i{bar}", freeze: true)) + end + + def test_lex + assert_frozen(Prism.lex("1 + 2; %i{foo} + %i{bar}", freeze: true)) + end + + def test_parse_lex + assert_frozen(Prism.parse_lex("1 + 2; %i{foo} + %i{bar}", freeze: true)) + assert_frozen(Prism.parse_lex("# encoding: euc-jp\n%i{foo}", freeze: true)) + end + + def test_parse_comments + assert_frozen(Prism.parse_comments("# comment", freeze: true)) + end + + def test_parse_stream + assert_frozen(Prism.parse_stream(StringIO.new("1 + 2; %i{foo} + %i{bar}"), freeze: true)) + end + + if !ENV["PRISM_BUILD_MINIMAL"] + def test_dump + assert_frozen(Prism.dump("1 + 2; %i{foo} + %i{bar}", freeze: true)) + end + end + + private + + def assert_frozen_each(value) + assert_predicate value, :frozen? + + value.instance_variables.each do |name| + case (child = value.instance_variable_get(name)) + when Array + child.each { |item| assert_frozen_each(item) } + when Hash + child.each { |key, item| assert_frozen_each(key); assert_frozen_each(item) } + else + assert_frozen_each(child) + end + end + end + + if defined?(Ractor.shareable?) + def assert_frozen(value) + assert_frozen_each(value) + assert Ractor.shareable?(value), -> { binding.irb } + end + else + alias assert_frozen assert_frozen_each + end + end +end diff --git a/test/mri/prism/api/parse_test.rb b/test/mri/prism/api/parse_test.rb index ee8061c98c5..bbf28201ffe 100644 --- a/test/mri/prism/api/parse_test.rb +++ b/test/mri/prism/api/parse_test.rb @@ -116,6 +116,15 @@ def test_version assert Prism.parse_success?("1 + 1", version: "3.4.9") assert Prism.parse_success?("1 + 1", version: "3.4.10") + 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 @@ -133,10 +142,36 @@ def test_version # Not supported version (too new) assert_raise ArgumentError do - Prism.parse("1 + 1", version: "3.5.0") + Prism.parse("1 + 1", version: "3.6.0") + 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_scopes + assert_kind_of Prism::CallNode, Prism.parse_statement("foo") + assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]]) + assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [Prism.scope(locals: [:foo])]) + + assert Prism.parse_failure?("foo(*)") + assert Prism.parse_success?("foo(*)", scopes: [Prism.scope(forwarding: [:*])]) + + assert Prism.parse_failure?("foo(**)") + assert Prism.parse_success?("foo(**)", scopes: [Prism.scope(forwarding: [:**])]) + + assert Prism.parse_failure?("foo(&)") + assert Prism.parse_success?("foo(&)", scopes: [Prism.scope(forwarding: [:&])]) + + assert Prism.parse_failure?("foo(...)") + assert Prism.parse_success?("foo(...)", scopes: [Prism.scope(forwarding: [:"..."])]) + end + private def find_source_file_node(program) diff --git a/test/mri/prism/encoding/encodings_test.rb b/test/mri/prism/encoding/encodings_test.rb index 4ad2b465cc1..b008fc3fa10 100644 --- a/test/mri/prism/encoding/encodings_test.rb +++ b/test/mri/prism/encoding/encodings_test.rb @@ -56,21 +56,11 @@ def assert_encoding_identifier(name, character) # 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 @@ def assert_encoding(encoding, name, range) 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/mri/prism/encoding/regular_expression_encoding_test.rb b/test/mri/prism/encoding/regular_expression_encoding_test.rb index 5d062fe59a2..e2daae1d7fa 100644 --- a/test/mri/prism/encoding/regular_expression_encoding_test.rb +++ b/test/mri/prism/encoding/regular_expression_encoding_test.rb @@ -119,8 +119,8 @@ def assert_regular_expression_encoding_flags(encoding, regexps) 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.last.clear - actual.last.clear + expected.pop + actual.pop end end diff --git a/test/mri/prism/errors/3.3-3.3/circular_parameters.txt b/test/mri/prism/errors/3.3-3.3/circular_parameters.txt new file mode 100644 index 00000000000..ef9642b075a --- /dev/null +++ b/test/mri/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/mri/prism/errors/3.3-3.4/leading_logical.txt b/test/mri/prism/errors/3.3-3.4/leading_logical.txt new file mode 100644 index 00000000000..2a702e281d5 --- /dev/null +++ b/test/mri/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/mri/prism/errors/3.3-3.4/private_endless_method.txt b/test/mri/prism/errors/3.3-3.4/private_endless_method.txt new file mode 100644 index 00000000000..8aae5e0cd39 --- /dev/null +++ b/test/mri/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/mri/prism/errors/3.4/block_args_in_array_assignment.txt b/test/mri/prism/errors/3.4/block_args_in_array_assignment.txt new file mode 100644 index 00000000000..71dca8452b9 --- /dev/null +++ b/test/mri/prism/errors/3.4/block_args_in_array_assignment.txt @@ -0,0 +1,3 @@ +matrix[5, &block] = 8 + ^~~~~~ unexpected block arg given in index assignment; blocks are not allowed in index assignment expressions + diff --git a/test/mri/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt b/test/mri/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt new file mode 100644 index 00000000000..c29fe017282 --- /dev/null +++ b/test/mri/prism/errors/3.4/dont_allow_return_inside_sclass_body.txt @@ -0,0 +1,3 @@ +class << A; return; end + ^~~~~~ Invalid return in class/module body + diff --git a/test/mri/prism/errors/3.4/it_with_ordinary_parameter.txt b/test/mri/prism/errors/3.4/it_with_ordinary_parameter.txt new file mode 100644 index 00000000000..ff9c4276cac --- /dev/null +++ b/test/mri/prism/errors/3.4/it_with_ordinary_parameter.txt @@ -0,0 +1,3 @@ +proc { || it } + ^~ 'it' is not allowed when an ordinary parameter is defined + diff --git a/test/mri/prism/errors/3.4/keyword_args_in_array_assignment.txt b/test/mri/prism/errors/3.4/keyword_args_in_array_assignment.txt new file mode 100644 index 00000000000..e379ec0ef46 --- /dev/null +++ b/test/mri/prism/errors/3.4/keyword_args_in_array_assignment.txt @@ -0,0 +1,3 @@ +matrix[5, axis: :y] = 8 + ^~~~~~~~ unexpected keyword arg given in index assignment; keywords are not allowed in index assignment expressions + diff --git a/test/mri/prism/errors/block_args_with_endless_def.txt b/test/mri/prism/errors/block_args_with_endless_def.txt new file mode 100644 index 00000000000..a7242160d29 --- /dev/null +++ b/test/mri/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/mri/prism/errors/command_calls.txt b/test/mri/prism/errors/command_calls.txt index 19812a1d0a6..6601e5fbbc6 100644 --- a/test/mri/prism/errors/command_calls.txt +++ b/test/mri/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/mri/prism/errors/command_calls_31.txt b/test/mri/prism/errors/command_calls_31.txt new file mode 100644 index 00000000000..e662b254444 --- /dev/null +++ b/test/mri/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/mri/prism/errors/command_calls_32.txt b/test/mri/prism/errors/command_calls_32.txt new file mode 100644 index 00000000000..14488ca3355 --- /dev/null +++ b/test/mri/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/mri/prism/errors/command_calls_33.txt b/test/mri/prism/errors/command_calls_33.txt new file mode 100644 index 00000000000..13e3b35c9e8 --- /dev/null +++ b/test/mri/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/mri/prism/errors/command_calls_34.txt b/test/mri/prism/errors/command_calls_34.txt new file mode 100644 index 00000000000..ce62bc1492a --- /dev/null +++ b/test/mri/prism/errors/command_calls_34.txt @@ -0,0 +1,24 @@ +foo(bar 1 do end, 2) + ^ invalid comma + ^ unexpected integer; expected a `)` to close the arguments + ^ unexpected integer, expecting end-of-input + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +foo(bar 1 do end,) + ^ invalid comma + +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/mri/prism/errors/command_calls_35.txt b/test/mri/prism/errors/command_calls_35.txt new file mode 100644 index 00000000000..45f569b117c --- /dev/null +++ b/test/mri/prism/errors/command_calls_35.txt @@ -0,0 +1,46 @@ +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 '=>'; expected a `)` to close the arguments + ^ 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/mri/prism/errors/def_with_optional_splat.txt b/test/mri/prism/errors/def_with_optional_splat.txt new file mode 100644 index 00000000000..74a833ceec0 --- /dev/null +++ b/test/mri/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/mri/prism/errors/defined_empty.txt b/test/mri/prism/errors/defined_empty.txt new file mode 100644 index 00000000000..4d7ea764131 --- /dev/null +++ b/test/mri/prism/errors/defined_empty.txt @@ -0,0 +1,3 @@ +defined?() + ^ expected an expression after `defined?` + diff --git a/test/mri/prism/errors/destroy_call_operator_write_arguments.txt b/test/mri/prism/errors/destroy_call_operator_write_arguments.txt new file mode 100644 index 00000000000..c3c72f92260 --- /dev/null +++ b/test/mri/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 write target + ^~ unexpected operator after a call with arguments + ^~ unexpected operator after a call with a block +''while= + ^~~~~ expected a predicate expression for the `while` statement + ^ unexpected '='; target cannot be written + diff --git a/test/mri/prism/errors/endless_method_command_call.txt b/test/mri/prism/errors/endless_method_command_call.txt new file mode 100644 index 00000000000..e6a328c2944 --- /dev/null +++ b/test/mri/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/mri/prism/errors/endless_method_command_call_parameters.txt b/test/mri/prism/errors/endless_method_command_call_parameters.txt new file mode 100644 index 00000000000..5dc92ce7f9f --- /dev/null +++ b/test/mri/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/mri/prism/errors/escape_unicode_curly_whitespace.txt b/test/mri/prism/errors/escape_unicode_curly_whitespace.txt new file mode 100644 index 00000000000..324d8a2ae51 --- /dev/null +++ b/test/mri/prism/errors/escape_unicode_curly_whitespace.txt @@ -0,0 +1,5 @@ +"\u{ + ^ invalid Unicode escape sequence + ^ unterminated Unicode escape +61}" + diff --git a/test/mri/prism/errors/heredoc_percent_q_newline_delimiter.txt b/test/mri/prism/errors/heredoc_percent_q_newline_delimiter.txt new file mode 100644 index 00000000000..73664c071f6 --- /dev/null +++ b/test/mri/prism/errors/heredoc_percent_q_newline_delimiter.txt @@ -0,0 +1,11 @@ +%q +#{< [a, b] | 2 + ^ variable capture in alternative pattern + ^ variable capture in alternative pattern + diff --git a/test/mri/prism/errors/pattern-capture-in-alt-hash.txt b/test/mri/prism/errors/pattern-capture-in-alt-hash.txt new file mode 100644 index 00000000000..150b3baecc4 --- /dev/null +++ b/test/mri/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/mri/prism/errors/pattern-capture-in-alt-name.txt b/test/mri/prism/errors/pattern-capture-in-alt-name.txt new file mode 100644 index 00000000000..cbf2bae85fe --- /dev/null +++ b/test/mri/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/mri/prism/errors/pattern-capture-in-alt-top.txt b/test/mri/prism/errors/pattern-capture-in-alt-top.txt new file mode 100644 index 00000000000..bdf3a7f6374 --- /dev/null +++ b/test/mri/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/mri/prism/errors/pattern_arithmetic_expressions.txt b/test/mri/prism/errors/pattern_arithmetic_expressions.txt new file mode 100644 index 00000000000..cfb36505312 --- /dev/null +++ b/test/mri/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/mri/prism/errors/pattern_match_implicit_rest.txt b/test/mri/prism/errors/pattern_match_implicit_rest.txt new file mode 100644 index 00000000000..8602c0add06 --- /dev/null +++ b/test/mri/prism/errors/pattern_match_implicit_rest.txt @@ -0,0 +1,3 @@ +a=>b, *, + ^ expected a pattern expression after `,` + diff --git a/test/mri/prism/errors/pattern_string_key.txt b/test/mri/prism/errors/pattern_string_key.txt new file mode 100644 index 00000000000..9f28feddb96 --- /dev/null +++ b/test/mri/prism/errors/pattern_string_key.txt @@ -0,0 +1,8 @@ +case:a +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 + ^ expected an `end` to close the `case` statement + diff --git a/test/mri/prism/errors/xstring_concat.txt b/test/mri/prism/errors/xstring_concat.txt new file mode 100644 index 00000000000..f4d453d68d1 --- /dev/null +++ b/test/mri/prism/errors/xstring_concat.txt @@ -0,0 +1,5 @@ +<<`EOC` "bar" +^~~~~~~ expected a string for concatenation +echo foo +EOC + diff --git a/test/mri/prism/errors_test.rb b/test/mri/prism/errors_test.rb index 62bbd8458b2..706b7395574 100644 --- a/test/mri/prism/errors_test.rb +++ b/test/mri/prism/errors_test.rb @@ -1,41 +1,19 @@ # 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 - - 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 + filepaths = Dir["**/*.txt", base: base] 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 @@ -86,29 +64,33 @@ def test_invalid_message_name assert_equal :"", Prism.parse_statement("+.@foo,+=foo").write_name 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_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 - source.each_line do |line| - assert_predicate Prism.parse(line, version: "3.3.0"), :failure? - assert_predicate Prism.parse(line), :success? - end + # 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 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) + refute_valid_syntax(source) if CURRENT_MAJOR_MINOR == version - result = Prism.parse(source) + result = Prism.parse(source, version: version) errors = result.errors refute_empty errors, "Expected errors in #{filepath}" diff --git a/test/mri/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt b/test/mri/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt new file mode 100644 index 00000000000..6d6b052681b --- /dev/null +++ b/test/mri/prism/fixtures/3.3-3.3/block_args_in_array_assignment.txt @@ -0,0 +1 @@ +matrix[5, &block] = 8 diff --git a/test/mri/prism/fixtures/3.3-3.3/it.txt b/test/mri/prism/fixtures/3.3-3.3/it.txt new file mode 100644 index 00000000000..5410b01e711 --- /dev/null +++ b/test/mri/prism/fixtures/3.3-3.3/it.txt @@ -0,0 +1,5 @@ +x do + it +end + +-> { it } diff --git a/test/mri/prism/fixtures/3.3-3.3/it_indirect_writes.txt b/test/mri/prism/fixtures/3.3-3.3/it_indirect_writes.txt new file mode 100644 index 00000000000..bb87e9483e2 --- /dev/null +++ b/test/mri/prism/fixtures/3.3-3.3/it_indirect_writes.txt @@ -0,0 +1,23 @@ +tap { it += 1 } + +tap { it ||= 1 } + +tap { it &&= 1 } + +tap { it; it += 1 } + +tap { it; it ||= 1 } + +tap { it; it &&= 1 } + +tap { it += 1; it } + +tap { it ||= 1; it } + +tap { it &&= 1; it } + +tap { it; it += 1; it } + +tap { it; it ||= 1; it } + +tap { it; it &&= 1; it } diff --git a/test/mri/prism/fixtures/3.3-3.3/it_read_and_assignment.txt b/test/mri/prism/fixtures/3.3-3.3/it_read_and_assignment.txt new file mode 100644 index 00000000000..2cceeb2a548 --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt b/test/mri/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt new file mode 100644 index 00000000000..178b641e6b9 --- /dev/null +++ b/test/mri/prism/fixtures/3.3-3.3/it_with_ordinary_parameter.txt @@ -0,0 +1 @@ +proc { || it } diff --git a/test/mri/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt b/test/mri/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt new file mode 100644 index 00000000000..88016c2afe8 --- /dev/null +++ b/test/mri/prism/fixtures/3.3-3.3/keyword_args_in_array_assignment.txt @@ -0,0 +1 @@ +matrix[5, axis: :y] = 8 diff --git a/test/mri/prism/fixtures/3.3-3.3/return_in_sclass.txt b/test/mri/prism/fixtures/3.3-3.3/return_in_sclass.txt new file mode 100644 index 00000000000..f1fde5771af --- /dev/null +++ b/test/mri/prism/fixtures/3.3-3.3/return_in_sclass.txt @@ -0,0 +1 @@ +class << A; return; end diff --git a/test/mri/prism/fixtures/3.4/circular_parameters.txt b/test/mri/prism/fixtures/3.4/circular_parameters.txt new file mode 100644 index 00000000000..11537023ada --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/3.4/it.txt b/test/mri/prism/fixtures/3.4/it.txt new file mode 100644 index 00000000000..5410b01e711 --- /dev/null +++ b/test/mri/prism/fixtures/3.4/it.txt @@ -0,0 +1,5 @@ +x do + it +end + +-> { it } diff --git a/test/mri/prism/fixtures/3.4/it_indirect_writes.txt b/test/mri/prism/fixtures/3.4/it_indirect_writes.txt new file mode 100644 index 00000000000..bb87e9483e2 --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/3.4/it_read_and_assignment.txt b/test/mri/prism/fixtures/3.4/it_read_and_assignment.txt new file mode 100644 index 00000000000..2cceeb2a548 --- /dev/null +++ b/test/mri/prism/fixtures/3.4/it_read_and_assignment.txt @@ -0,0 +1 @@ +42.tap { p it; it = it; p it } diff --git a/test/mri/prism/fixtures/4.0/endless_methods_command_call.txt b/test/mri/prism/fixtures/4.0/endless_methods_command_call.txt new file mode 100644 index 00000000000..91a9d156d55 --- /dev/null +++ b/test/mri/prism/fixtures/4.0/endless_methods_command_call.txt @@ -0,0 +1,8 @@ +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 diff --git a/test/mri/prism/fixtures/4.0/leading_logical.txt b/test/mri/prism/fixtures/4.0/leading_logical.txt new file mode 100644 index 00000000000..feb5ee245c8 --- /dev/null +++ b/test/mri/prism/fixtures/4.0/leading_logical.txt @@ -0,0 +1,21 @@ +1 +&& 2 +&& 3 + +1 +|| 2 +|| 3 + +1 +and 2 +and 3 + +1 +or 2 +or 3 + +1 +andfoo + +2 +orfoo diff --git a/test/mri/prism/fixtures/begin_rescue.txt b/test/mri/prism/fixtures/begin_rescue.txt index 0a56fbef9f5..790574f4ffe 100644 --- a/test/mri/prism/fixtures/begin_rescue.txt +++ b/test/mri/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/mri/prism/fixtures/break.txt b/test/mri/prism/fixtures/break.txt index 5532322c5ce..d823f866df7 100644 --- a/test/mri/prism/fixtures/break.txt +++ b/test/mri/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/mri/prism/fixtures/case_in_hash_key.txt b/test/mri/prism/fixtures/case_in_hash_key.txt new file mode 100644 index 00000000000..75ac8a846f7 --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/character_literal.txt b/test/mri/prism/fixtures/character_literal.txt new file mode 100644 index 00000000000..920332123f1 --- /dev/null +++ b/test/mri/prism/fixtures/character_literal.txt @@ -0,0 +1,2 @@ +# encoding: Windows-31J +p ?\u3042"" diff --git a/test/mri/prism/fixtures/command_method_call_2.txt b/test/mri/prism/fixtures/command_method_call_2.txt new file mode 100644 index 00000000000..165c45987aa --- /dev/null +++ b/test/mri/prism/fixtures/command_method_call_2.txt @@ -0,0 +1,3 @@ +foo(bar baz do end) + +foo(bar baz, bat) diff --git a/test/mri/prism/fixtures/command_method_call_3.txt b/test/mri/prism/fixtures/command_method_call_3.txt new file mode 100644 index 00000000000..6de0446aa9b --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/comment_single.txt b/test/mri/prism/fixtures/comment_single.txt new file mode 100644 index 00000000000..72037a6ea1a --- /dev/null +++ b/test/mri/prism/fixtures/comment_single.txt @@ -0,0 +1 @@ +foo # Bar \ No newline at end of file diff --git a/test/mri/prism/fixtures/defined.txt b/test/mri/prism/fixtures/defined.txt index 247fa94e3a4..09fc0a29e74 100644 --- a/test/mri/prism/fixtures/defined.txt +++ b/test/mri/prism/fixtures/defined.txt @@ -8,3 +8,12 @@ defined? 1 defined?("foo" ) + +defined? +1 + +defined? +(1) + +defined? +() diff --git a/test/mri/prism/fixtures/dstring.txt b/test/mri/prism/fixtures/dstring.txt index 085e0c6852a..ef698d8fe9a 100644 --- a/test/mri/prism/fixtures/dstring.txt +++ b/test/mri/prism/fixtures/dstring.txt @@ -27,3 +27,16 @@ foo\\\\ " foo\\\\\ " + +" +foo\ +b\nar +#{} +" + +"foo +\n#{}bar\n\n#{} +a\nb\n#{}\nc\n" + +" +’" diff --git a/test/mri/prism/fixtures/dsym_str.txt b/test/mri/prism/fixtures/dsym_str.txt index ee68dde88d5..0af0a8ddaf7 100644 --- a/test/mri/prism/fixtures/dsym_str.txt +++ b/test/mri/prism/fixtures/dsym_str.txt @@ -1,2 +1,5 @@ :"foo bar" + +:" +’" diff --git a/test/mri/prism/fixtures/encoding_binary.txt b/test/mri/prism/fixtures/encoding_binary.txt new file mode 100644 index 00000000000..f3dfc85abd3 --- /dev/null +++ b/test/mri/prism/fixtures/encoding_binary.txt @@ -0,0 +1,9 @@ +# encoding: binary + +"\xcd" + +:"\xcd" + +/#{"\xcd"}/ + +%W[\xC0] diff --git a/test/mri/prism/fixtures/encoding_euc_jp.txt b/test/mri/prism/fixtures/encoding_euc_jp.txt new file mode 100644 index 00000000000..bbee76eae51 --- /dev/null +++ b/test/mri/prism/fixtures/encoding_euc_jp.txt @@ -0,0 +1,6 @@ +# encoding: euc-jp + +# \x8E indicates a double-byte character, \x01 is not a valid second byte in euc-jp +"\x8E\x01" + +%W["\x8E\x01"] diff --git a/test/mri/prism/fixtures/endless_method_as_default_arg.txt b/test/mri/prism/fixtures/endless_method_as_default_arg.txt new file mode 100644 index 00000000000..0063d9a8fa6 --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/endless_methods.txt b/test/mri/prism/fixtures/endless_methods.txt index 8c2f2a30cc5..7eb3bf43189 100644 --- a/test/mri/prism/fixtures/endless_methods.txt +++ b/test/mri/prism/fixtures/endless_methods.txt @@ -3,3 +3,5 @@ def foo = 1 def bar = A "" def method = 1 + 2 + 3 + +x = def f = p 1 diff --git a/test/mri/prism/fixtures/heredoc_percent_q_newline_delimiter.txt b/test/mri/prism/fixtures/heredoc_percent_q_newline_delimiter.txt new file mode 100644 index 00000000000..dbfa0bf4b4a --- /dev/null +++ b/test/mri/prism/fixtures/heredoc_percent_q_newline_delimiter.txt @@ -0,0 +1,22 @@ +%Q +#{< foo: bar do end +def foo(*, **) + ->() { bar(*, **) } +end + p{|a: b|} diff --git a/test/mri/prism/fixtures/methods.txt b/test/mri/prism/fixtures/methods.txt index d59196bdfdf..eb0a5ca3dcf 100644 --- a/test/mri/prism/fixtures/methods.txt +++ b/test/mri/prism/fixtures/methods.txt @@ -109,6 +109,8 @@ def foo = 123 def a(*); b(*); end +def a(*, **); b { c(*, **) }; end + def a(...); b(...); end def a(...); b(1, 2, ...); end diff --git a/test/mri/prism/fixtures/next.txt b/test/mri/prism/fixtures/next.txt index 2ef14c6304e..0d2d6a11f50 100644 --- a/test/mri/prism/fixtures/next.txt +++ b/test/mri/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/mri/prism/fixtures/patterns.txt b/test/mri/prism/fixtures/patterns.txt index f4f3489e4d9..449dac619b4 100644 --- a/test/mri/prism/fixtures/patterns.txt +++ b/test/mri/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/mri/prism/fixtures/ranges.txt b/test/mri/prism/fixtures/ranges.txt index e2e4136ae96..87eac6d2419 100644 --- a/test/mri/prism/fixtures/ranges.txt +++ b/test/mri/prism/fixtures/ranges.txt @@ -2,6 +2,8 @@ (..2) +foo ((1..1)) + 1...2 foo[...2] diff --git a/test/mri/prism/fixtures/regex.txt b/test/mri/prism/fixtures/regex.txt index 4623733f589..712bfc081ac 100644 --- a/test/mri/prism/fixtures/regex.txt +++ b/test/mri/prism/fixtures/regex.txt @@ -46,3 +46,13 @@ tap { /(?)/ =~ to_s } def foo(nil:) = /(?)/ =~ "" /(?-x:#)/x + +/a +b\ +c\ +d\\\ +e\\ +f\ +/ + +// diff --git a/test/mri/prism/fixtures/regex_with_fake_newlines.txt b/test/mri/prism/fixtures/regex_with_fake_newlines.txt new file mode 100644 index 00000000000..d92a2e4adea --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/rescue.txt b/test/mri/prism/fixtures/rescue.txt index 99170fbe0ff..f4364630291 100644 --- a/test/mri/prism/fixtures/rescue.txt +++ b/test/mri/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/mri/prism/fixtures/return.txt b/test/mri/prism/fixtures/return.txt index a8b5b95fabe..952fb80da85 100644 --- a/test/mri/prism/fixtures/return.txt +++ b/test/mri/prism/fixtures/return.txt @@ -22,3 +22,6 @@ return() return(1) +(return 1) + +foo && (return 1) diff --git a/test/mri/prism/fixtures/string_concatination_frozen_false.txt b/test/mri/prism/fixtures/string_concatination_frozen_false.txt new file mode 100644 index 00000000000..abe9301408e --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/string_concatination_frozen_true.txt b/test/mri/prism/fixtures/string_concatination_frozen_true.txt new file mode 100644 index 00000000000..829777f0a70 --- /dev/null +++ b/test/mri/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/mri/prism/fixtures/strings.txt b/test/mri/prism/fixtures/strings.txt index 6963d75b2da..1419f975b7b 100644 --- a/test/mri/prism/fixtures/strings.txt +++ b/test/mri/prism/fixtures/strings.txt @@ -40,6 +40,15 @@ # "bar" +" +foo\ +b\nar +" + +"foo +\nbar\n\n +a\nb\n\nc\n" + %q{abc} %s[abc] @@ -64,6 +73,62 @@ %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] @@ -78,6 +143,11 @@ '\\ foo \\ bar' +'foo\ +bar\\ +baz +' + "#$foo" "#@foo" @@ -86,6 +156,10 @@ "\7 \43 \141" +"ち\xE3\x81\xFF" + +"\777" + %[abc] %(abc) @@ -100,6 +174,10 @@ %Q{abc} +%Q(\«) + +%q(\«) + %^#$^# %@#@# diff --git a/test/mri/prism/fixtures/symbols.txt b/test/mri/prism/fixtures/symbols.txt index 7563eb874f6..34895b9e9fd 100644 --- a/test/mri/prism/fixtures/symbols.txt +++ b/test/mri/prism/fixtures/symbols.txt @@ -4,6 +4,17 @@ :"abc#{1}" +:" +foo\ +b\nar +" + +:" +foo\ +b\nar +#{} +" + [:Υ, :ά, :ŗ, :ρ] :-@ diff --git a/test/mri/prism/fixtures/unary_method_calls.txt b/test/mri/prism/fixtures/unary_method_calls.txt new file mode 100644 index 00000000000..dda85e4bdbf --- /dev/null +++ b/test/mri/prism/fixtures/unary_method_calls.txt @@ -0,0 +1,2 @@ +42.~@ +42.!@ diff --git a/test/mri/prism/fixtures/variables.txt b/test/mri/prism/fixtures/variables.txt index 1545c30c80f..4f4dc6f9c82 100644 --- a/test/mri/prism/fixtures/variables.txt +++ b/test/mri/prism/fixtures/variables.txt @@ -45,3 +45,5 @@ Foo = 1, 2 (a; b; c) a, (b, c), d = [] + +(a,), = [] diff --git a/test/mri/prism/fixtures/whitequark/LICENSE b/test/mri/prism/fixtures/whitequark/LICENSE index 971310e3d68..43f97889853 100644 --- a/test/mri/prism/fixtures/whitequark/LICENSE +++ b/test/mri/prism/fixtures/whitequark/LICENSE @@ -1,4 +1,5 @@ -Copyright (c) 2013-2016 whitequark +Copyright (c) 2013-2024 parser project contributors +Copyright (c) 2013-2016 Catherine Parts of the source are derived from ruby_parser: Copyright (c) Ryan Davis, seattle.rb diff --git a/test/mri/prism/fixtures/whitequark/arg_combinations.txt b/test/mri/prism/fixtures/whitequark/arg_combinations.txt new file mode 100644 index 00000000000..801b1e47f4c --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/arg_combinations.txt @@ -0,0 +1,29 @@ +def f &b; end + +def f *r, &b; end + +def f *r, p, &b; end + +def f ; end + +def f a, &b; end + +def f a, *r, &b; end + +def f a, *r, p, &b; end + +def f a, o=1, &b; end + +def f a, o=1, *r, &b; end + +def f a, o=1, *r, p, &b; end + +def f a, o=1, p, &b; end + +def f o=1, &b; end + +def f o=1, *r, &b; end + +def f o=1, *r, p, &b; end + +def f o=1, p, &b; end diff --git a/test/mri/prism/fixtures/whitequark/block_arg_combinations.txt b/test/mri/prism/fixtures/whitequark/block_arg_combinations.txt new file mode 100644 index 00000000000..ccb9cfea56c --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/block_arg_combinations.txt @@ -0,0 +1,57 @@ +f{ } + +f{ | | } + +f{ |&b| } + +f{ |*, &b| } + +f{ |*r, p, &b| } + +f{ |*s, &b| } + +f{ |*s| } + +f{ |*| } + +f{ |; +a +| } + +f{ |;a| } + +f{ |a, &b| } + +f{ |a, *, &b| } + +f{ |a, *r, p, &b| } + +f{ |a, *s, &b| } + +f{ |a, *s| } + +f{ |a, *| } + +f{ |a, c| } + +f{ |a, o=1, &b| } + +f{ |a, o=1, *r, p, &b| } + +f{ |a, o=1, o1=2, *r, &b| } + +f{ |a, o=1, p, &b| } + +f{ |a,| } + +f{ |a| } + +f{ |o=1, &b| } + +f{ |o=1, *r, &b| } + +f{ |o=1, *r, p, &b| } + +f{ |o=1, p, &b| } + +f{ || } diff --git a/test/mri/prism/fixtures/whitequark/block_kwarg.txt b/test/mri/prism/fixtures/whitequark/block_kwarg.txt new file mode 100644 index 00000000000..9f1283371f4 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/block_kwarg.txt @@ -0,0 +1 @@ +f{ |foo:| } diff --git a/test/mri/prism/fixtures/whitequark/block_kwarg_combinations.txt b/test/mri/prism/fixtures/whitequark/block_kwarg_combinations.txt new file mode 100644 index 00000000000..3dbb961777a --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/block_kwarg_combinations.txt @@ -0,0 +1,5 @@ +f{ |**baz, &b| } + +f{ |foo: 1, &b| } + +f{ |foo: 1, bar: 2, **baz, &b| } diff --git a/test/mri/prism/fixtures/whitequark/emit_arg_inside_procarg0_legacy.txt b/test/mri/prism/fixtures/whitequark/emit_arg_inside_procarg0_legacy.txt new file mode 100644 index 00000000000..a985f15b6e0 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/emit_arg_inside_procarg0_legacy.txt @@ -0,0 +1 @@ +f{ |a| } diff --git a/test/mri/prism/fixtures/whitequark/find_pattern.txt b/test/mri/prism/fixtures/whitequark/find_pattern.txt new file mode 100644 index 00000000000..fb8999ae288 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/find_pattern.txt @@ -0,0 +1,7 @@ +case foo; in *, 42, * then true; end + +case foo; in Array[*, 1, *] then true; end + +case foo; in String(*, 1, *) then true; end + +case foo; in [*x, 1 => a, *y] then true; end diff --git a/test/mri/prism/fixtures/whitequark/kwarg_combinations.txt b/test/mri/prism/fixtures/whitequark/kwarg_combinations.txt new file mode 100644 index 00000000000..1bd792856e2 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/kwarg_combinations.txt @@ -0,0 +1,7 @@ +def f (foo: 1, &b); end + +def f (foo: 1, bar: 2, **baz, &b); end + +def f **baz, &b; end + +def f *, **; end diff --git a/test/mri/prism/fixtures/whitequark/kwarg_no_paren.txt b/test/mri/prism/fixtures/whitequark/kwarg_no_paren.txt new file mode 100644 index 00000000000..cf29c273d0d --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/kwarg_no_paren.txt @@ -0,0 +1,5 @@ +def f foo: +; end + +def f foo: -1 +; end diff --git a/test/mri/prism/fixtures/whitequark/lvar_injecting_match.txt b/test/mri/prism/fixtures/whitequark/lvar_injecting_match.txt index ba814a1088a..2d18c84b571 100644 --- a/test/mri/prism/fixtures/whitequark/lvar_injecting_match.txt +++ b/test/mri/prism/fixtures/whitequark/lvar_injecting_match.txt @@ -1 +1,3 @@ +/(?a)/ =~ 'a'; /#{}(?b)/ =~ 'b'; a; b + /(?bar)/ =~ 'bar'; match diff --git a/test/mri/prism/fixtures/whitequark/marg_combinations.txt b/test/mri/prism/fixtures/whitequark/marg_combinations.txt new file mode 100644 index 00000000000..aff34dcb620 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/marg_combinations.txt @@ -0,0 +1,19 @@ +def f (((a))); end + +def f ((*)); end + +def f ((*, p)); end + +def f ((*r)); end + +def f ((*r, p)); end + +def f ((a, *)); end + +def f ((a, *, p)); end + +def f ((a, *r)); end + +def f ((a, *r, p)); end + +def f ((a, a1)); end diff --git a/test/mri/prism/fixtures/whitequark/multiple_args_with_trailing_comma.txt b/test/mri/prism/fixtures/whitequark/multiple_args_with_trailing_comma.txt new file mode 100644 index 00000000000..b6907387578 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/multiple_args_with_trailing_comma.txt @@ -0,0 +1 @@ +f{ |a, b,| } diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_const_pattern.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_const_pattern.txt new file mode 100644 index 00000000000..82821dbc83c --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_const_pattern.txt @@ -0,0 +1,11 @@ +case foo; in A() then true; end + +case foo; in A(1, 2) then true; end + +case foo; in A(x:) then true; end + +case foo; in A[1, 2] then true; end + +case foo; in A[] then true; end + +case foo; in A[x:] then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_constants.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_constants.txt new file mode 100644 index 00000000000..aba764ff356 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_constants.txt @@ -0,0 +1,5 @@ +case foo; in ::A then true; end + +case foo; in A then true; end + +case foo; in A::B then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_explicit_array_match.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_explicit_array_match.txt new file mode 100644 index 00000000000..61e518d0fba --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_explicit_array_match.txt @@ -0,0 +1,19 @@ +case foo; in [*, x] then true; end + +case foo; in [*x, y] then true; end + +case foo; in [x, *, y] then true; end + +case foo; in [x, *y, z] then true; end + +case foo; in [x, y, *] then true; end + +case foo; in [x, y, *z] then true; end + +case foo; in [x, y,] then true; end + +case foo; in [x, y] then true; end + +case foo; in [x,] then nil; end + +case foo; in [x] then nil; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_expr_in_paren.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_expr_in_paren.txt new file mode 100644 index 00000000000..9b2e91b70dc --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_expr_in_paren.txt @@ -0,0 +1 @@ +case foo; in (1) then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_hash.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_hash.txt new file mode 100644 index 00000000000..b26f2c59dca --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_hash.txt @@ -0,0 +1,48 @@ +case foo; + in a: {b:}, c: + p c + ; end + +case foo; + in {Foo: 42 + } + false + ; end + +case foo; + in {a: + 2} + false + ; end + +case foo; + in {a: + } + true + ; end + +case foo; + in {a: 1 + } + false + ; end + +case foo; in ** then true; end + +case foo; in **a then true; end + +case foo; in a: 1 then true; end + +case foo; in a: 1, _a:, ** then true; end + +case foo; in a: 1, b: 2 then true; end + +case foo; in a: then true; end + +case foo; in a:, b: then true; end + +case foo; in { a: 1 } then true; end + +case foo; in { a: 1, } then true; end + +case foo; in {} then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_if_unless_modifiers.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_if_unless_modifiers.txt new file mode 100644 index 00000000000..05ca3173554 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_if_unless_modifiers.txt @@ -0,0 +1,3 @@ +case foo; in x if true; nil; end + +case foo; in x unless true; nil; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_implicit_array_match.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_implicit_array_match.txt new file mode 100644 index 00000000000..360c5150a51 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_implicit_array_match.txt @@ -0,0 +1,15 @@ +case foo; in * then nil; end + +case foo; in *x then nil; end + +case foo; in *x, y, z then nil; end + +case foo; in 1, "a", [], {} then nil; end + +case foo; in x, *y, z then nil; end + +case foo; in x, then nil; end + +case foo; in x, y then nil; end + +case foo; in x, y, then nil; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_keyword_variable.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_keyword_variable.txt new file mode 100644 index 00000000000..83c7a06b09e --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_keyword_variable.txt @@ -0,0 +1 @@ +case foo; in self then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_lambda.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_lambda.txt new file mode 100644 index 00000000000..eeccdc70e0c --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_lambda.txt @@ -0,0 +1 @@ +case foo; in ->{ 42 } then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_match_alt.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_match_alt.txt new file mode 100644 index 00000000000..6c6549916d3 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_match_alt.txt @@ -0,0 +1 @@ +case foo; in 1 | 2 then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_match_as.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_match_as.txt new file mode 100644 index 00000000000..2a105f9f367 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_match_as.txt @@ -0,0 +1 @@ +case foo; in 1 => a then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_nil_pattern.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_nil_pattern.txt new file mode 100644 index 00000000000..46dce94a2dc --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_nil_pattern.txt @@ -0,0 +1 @@ +case foo; in **nil then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_no_body.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_no_body.txt new file mode 100644 index 00000000000..73b471d3521 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_no_body.txt @@ -0,0 +1 @@ +case foo; in 1; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_ranges.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_ranges.txt new file mode 100644 index 00000000000..7e603e77b05 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_ranges.txt @@ -0,0 +1,11 @@ +case foo; in ...2 then true; end + +case foo; in ..2 then true; end + +case foo; in 1.. then true; end + +case foo; in 1... then true; end + +case foo; in 1...2 then true; end + +case foo; in 1..2 then true; end diff --git a/test/mri/prism/fixtures/whitequark/pattern_matching_single_match.txt b/test/mri/prism/fixtures/whitequark/pattern_matching_single_match.txt new file mode 100644 index 00000000000..c174376585d --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pattern_matching_single_match.txt @@ -0,0 +1 @@ +case foo; in x then x; end diff --git a/test/mri/prism/fixtures/whitequark/pin_expr.txt b/test/mri/prism/fixtures/whitequark/pin_expr.txt new file mode 100644 index 00000000000..a072c7c1947 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/pin_expr.txt @@ -0,0 +1,14 @@ +case foo; in ^$TestPatternMatching; end + +case foo; in ^(0+0) then nil; end + +case foo; in ^(1 +); end + +case foo; in ^(42) then nil; end + +case foo; in ^@@TestPatternMatching; end + +case foo; in ^@a; end + +case foo; in { foo: ^(42) } then nil; end diff --git a/test/mri/prism/fixtures/whitequark/procarg0_legacy.txt b/test/mri/prism/fixtures/whitequark/procarg0_legacy.txt new file mode 100644 index 00000000000..a985f15b6e0 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/procarg0_legacy.txt @@ -0,0 +1 @@ +f{ |a| } diff --git a/test/mri/prism/fixtures/whitequark/ruby_bug_18878.txt b/test/mri/prism/fixtures/whitequark/ruby_bug_18878.txt new file mode 100644 index 00000000000..583280c9e33 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/ruby_bug_18878.txt @@ -0,0 +1 @@ +Foo::Bar { |a| 42 } diff --git a/test/mri/prism/fixtures/whitequark/ruby_bug_19281.txt b/test/mri/prism/fixtures/whitequark/ruby_bug_19281.txt new file mode 100644 index 00000000000..cd154f97569 --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/ruby_bug_19281.txt @@ -0,0 +1,7 @@ +a.b (1;2),(3),(4) + +a.b (;),(),() + +p (1;2),(3),(4) + +p (;),(),() diff --git a/test/mri/prism/fixtures/whitequark/ruby_bug_19539.txt b/test/mri/prism/fixtures/whitequark/ruby_bug_19539.txt new file mode 100644 index 00000000000..b2d052d94da --- /dev/null +++ b/test/mri/prism/fixtures/whitequark/ruby_bug_19539.txt @@ -0,0 +1,9 @@ +<<' FOO' +[Bug #19539] + FOO + + +<<-' FOO' +[Bug #19539] + FOO + diff --git a/test/mri/prism/fixtures/xstring.txt b/test/mri/prism/fixtures/xstring.txt index 7ec09468d8c..465a14e84bb 100644 --- a/test/mri/prism/fixtures/xstring.txt +++ b/test/mri/prism/fixtures/xstring.txt @@ -11,3 +11,11 @@ `` %x{} + +` +foo\ +b\nar +` + +` +’` diff --git a/test/mri/prism/fixtures_test.rb b/test/mri/prism/fixtures_test.rb index 7225b4ac66c..7df97029d3a 100644 --- a/test/mri/prism/fixtures_test.rb +++ b/test/mri/prism/fixtures_test.rb @@ -8,13 +8,25 @@ module Prism class FixturesTest < TestCase except = [] - # 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" if RUBY_VERSION < "3.3.0" + if RUBY_VERSION < "3.3.0" + # 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" - Fixture.each(except: except) do |fixture| + # 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 + + except << "command_method_call_2.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/mri/prism/lex_test.rb b/test/mri/prism/lex_test.rb index 7eac677ef70..68e47a09640 100644 --- a/test/mri/prism/lex_test.rb +++ b/test/mri/prism/lex_test.rb @@ -7,15 +7,13 @@ 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. + # https://bugs.ruby-lang.org/issues/21756 "spanning_heredoc.txt", - "spanning_heredoc_newlines.txt" + # Prism emits a single string in some cases when ripper splits them up + "whitequark/dedenting_heredoc.txt", + "heredocs_with_fake_newlines.txt", + # Prism emits BEG for `on_regexp_end` + "spanning_heredoc_newlines.txt", ] if RUBY_VERSION < "3.3.0" @@ -28,9 +26,20 @@ class LexTest < TestCase # 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| + # https://bugs.ruby-lang.org/issues/21168#note-5 + except << "command_method_call_2.txt" + + Fixture.each_for_current_ruby(except: except) do |fixture| define_method(fixture.test_name) { assert_lex(fixture) } end @@ -79,7 +88,7 @@ def test_parse_lex_file def assert_lex(fixture) source = fixture.read - result = Prism.lex_compat(source) + result = Prism.lex_compat(source, version: "current") assert_equal [], result.errors Prism.lex_ripper(source).zip(result.value).each do |(ripper, prism)| diff --git a/test/mri/prism/locals_test.rb b/test/mri/prism/locals_test.rb index e0e9a458559..4f8d6080e80 100644 --- a/test/mri/prism/locals_test.rb +++ b/test/mri/prism/locals_test.rb @@ -13,11 +13,6 @@ # 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,13 @@ class LocalsTest < TestCase 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", ] - Fixture.each(except: except) do |fixture| + Fixture.each_for_current_ruby(except: except) do |fixture| define_method(fixture.test_name) { assert_locals(fixture) } end diff --git a/test/mri/prism/ractor_test.rb b/test/mri/prism/ractor_test.rb new file mode 100644 index 00000000000..0e008ffb088 --- /dev/null +++ b/test/mri/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/mri/prism/result/named_capture_test.rb b/test/mri/prism/result/named_capture_test.rb new file mode 100644 index 00000000000..36cb9108992 --- /dev/null +++ b/test/mri/prism/result/named_capture_test.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Prism + class NamedCaptureTest < TestCase + def test_hex_escapes + assert_equal :😀, parse_name("\\xf0\\x9f\\x98\\x80") + end + + def test_unicode_escape + assert_equal :し, parse_name("\\u3057") + end + + def test_unicode_escapes_bracess + assert_equal :😀, parse_name("\\u{1f600}") + end + + def test_octal_escapes + assert_equal :😀, parse_name("\\xf0\\x9f\\x98\\200") + end + + private + + def parse_name(content) + Prism.parse_statement("/(?<#{content}>)/ =~ ''").targets.first.name + end + end +end diff --git a/test/mri/prism/result/source_location_test.rb b/test/mri/prism/result/source_location_test.rb index 7bdc707658e..38b971d02b8 100644 --- a/test/mri/prism/result/source_location_test.rb +++ b/test/mri/prism/result/source_location_test.rb @@ -13,7 +13,7 @@ def test_AliasMethodNode 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 diff --git a/test/mri/prism/result/string_test.rb b/test/mri/prism/result/string_test.rb new file mode 100644 index 00000000000..48c7f592eb9 --- /dev/null +++ b/test/mri/prism/result/string_test.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Prism + class StringTest < TestCase + def test_regular_expression_node_unescaped_frozen + node = Prism.parse_statement("/foo/") + assert_predicate node.unescaped, :frozen? + end + + def test_source_file_node_filepath_frozen + node = Prism.parse_statement("__FILE__") + assert_predicate node.filepath, :frozen? + end + + def test_string_node_unescaped_frozen + node = Prism.parse_statement('"foo"') + assert_predicate node.unescaped, :frozen? + end + + def test_symbol_node_unescaped_frozen + node = Prism.parse_statement(":foo") + assert_predicate node.unescaped, :frozen? + end + + def test_xstring_node_unescaped_frozen + node = Prism.parse_statement("`foo`") + assert_predicate node.unescaped, :frozen? + end + end +end diff --git a/test/mri/prism/result/warnings_test.rb b/test/mri/prism/result/warnings_test.rb index 04542dbada1..4643fb134fe 100644 --- a/test/mri/prism/result/warnings_test.rb +++ b/test/mri/prism/result/warnings_test.rb @@ -339,7 +339,7 @@ def test_unreachable_statement 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/mri/prism/ruby/dispatcher_test.rb b/test/mri/prism/ruby/dispatcher_test.rb index 1b6d7f4117e..83eb29e1f3b 100644 --- a/test/mri/prism/ruby/dispatcher_test.rb +++ b/test/mri/prism/ruby/dispatcher_test.rb @@ -25,9 +25,12 @@ def on_integer_node_enter(node) 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 @@ def foo 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/mri/prism/ruby/location_test.rb b/test/mri/prism/ruby/location_test.rb index 33f844243c0..5e2ab63802b 100644 --- a/test/mri/prism/ruby/location_test.rb +++ b/test/mri/prism/ruby/location_test.rb @@ -13,19 +13,22 @@ def test_join 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 diff --git a/test/mri/prism/ruby/parameters_signature_test.rb b/test/mri/prism/ruby/parameters_signature_test.rb index 9256bcc0703..ea1eea106ba 100644 --- a/test/mri/prism/ruby/parameters_signature_test.rb +++ b/test/mri/prism/ruby/parameters_signature_test.rb @@ -54,9 +54,10 @@ 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 diff --git a/test/mri/prism/ruby/parser_test.rb b/test/mri/prism/ruby/parser_test.rb index 606a0e54f64..df290a6a8e2 100644 --- a/test/mri/prism/ruby/parser_test.rb +++ b/test/mri/prism/ruby/parser_test.rb @@ -6,6 +6,7 @@ verbose, $VERBOSE = $VERBOSE, nil require "parser/ruby33" require "prism/translation/parser33" + require "prism/translation/parser34" 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 +17,19 @@ # 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 +# method out in order to accept such code. +# https://github.com/whitequark/parser/blob/v3.3.6.0/lib/parser/builders/default.rb#L2289-L2295 +Parser::Builders::Default.prepend( + Module.new { + def string_value(token) + value(token) + end + } +) # Modify the source map == check so that it doesn't check against the node # itself so we don't get into a recursive loop. @@ -42,6 +56,22 @@ def ==(other) 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 = [ @@ -53,134 +83,131 @@ class ParserTest < TestCase "seattlerb/heredoc_nested.txt", # https://github.com/whitequark/parser/issues/1016 - "whitequark/unary_num_pow_precedence.txt" - ] + "whitequark/unary_num_pow_precedence.txt", - # These files are either failing to parse or failing to translate, so we'll - # skip them for now. - skip_all = skip_incorrect | [ - "regex.txt", - "unescaping.txt", - "seattlerb/bug190.txt", + # https://github.com/whitequark/parser/issues/950 + "whitequark/dedenting_interpolating_heredoc_fake_line_continuation.txt", + + # Contains an escaped multibyte character. This is supposed to drop to backslash + "seattlerb/regexp_escape_extended.txt", + + # https://github.com/whitequark/parser/issues/1020 + # These contain consecutive \r characters, followed by \n. Prism only receives + # the already modified source buffer which dropped one \r but must know the + # original code to parse it correctly. "seattlerb/heredoc_with_extra_carriage_returns_windows.txt", "seattlerb/heredoc_with_only_carriage_returns_windows.txt", "seattlerb/heredoc_with_only_carriage_returns.txt", - "seattlerb/parse_line_heredoc_hardnewline.txt", - "seattlerb/pctW_lineno.txt", + + # https://github.com/whitequark/parser/issues/1026 + # Regex with \c escape + "unescaping.txt", "seattlerb/regexp_esc_C_slash.txt", - "unparser/corpus/literal/literal.txt", - "unparser/corpus/semantic/dstr.txt", - "whitequark/dedenting_interpolating_heredoc_fake_line_continuation.txt", - "whitequark/parser_slash_slash_n_escaping_in_literals.txt", - "whitequark/ruby_bug_11989.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 = [ - "comments.txt", "dash_heredocs.txt", - "dos_endings.txt", "embdoc_no_newline_at_end.txt", - "heredoc_with_comment.txt", - "heredocs_with_ignored_newlines.txt", - "indented_file_end.txt", "methods.txt", - "strings.txt", - "tilde_heredocs.txt", - "xstring_with_backslash.txt", - "seattlerb/backticks_interpolation_line.txt", "seattlerb/bug169.txt", "seattlerb/case_in.txt", - "seattlerb/class_comments.txt", "seattlerb/difficult4__leading_dots2.txt", "seattlerb/difficult6__7.txt", "seattlerb/difficult6__8.txt", - "seattlerb/dsym_esc_to_sym.txt", - "seattlerb/heredoc__backslash_dos_format.txt", - "seattlerb/heredoc_backslash_nl.txt", - "seattlerb/heredoc_comma_arg.txt", - "seattlerb/heredoc_squiggly_blank_line_plus_interpolation.txt", - "seattlerb/heredoc_squiggly_blank_lines.txt", - "seattlerb/heredoc_squiggly_interp.txt", - "seattlerb/heredoc_squiggly_tabs_extra.txt", - "seattlerb/heredoc_squiggly_tabs.txt", - "seattlerb/heredoc_squiggly_visually_blank_lines.txt", - "seattlerb/heredoc_squiggly.txt", "seattlerb/heredoc_unicode.txt", - "seattlerb/heredoc_with_carriage_return_escapes_windows.txt", - "seattlerb/heredoc_with_carriage_return_escapes.txt", - "seattlerb/heredoc_with_interpolation_and_carriage_return_escapes_windows.txt", - "seattlerb/heredoc_with_interpolation_and_carriage_return_escapes.txt", - "seattlerb/interpolated_symbol_array_line_breaks.txt", - "seattlerb/interpolated_word_array_line_breaks.txt", - "seattlerb/label_vs_string.txt", - "seattlerb/module_comments.txt", - "seattlerb/non_interpolated_symbol_array_line_breaks.txt", - "seattlerb/non_interpolated_word_array_line_breaks.txt", - "seattlerb/parse_line_block_inline_comment_leading_newlines.txt", - "seattlerb/parse_line_block_inline_comment.txt", - "seattlerb/parse_line_block_inline_multiline_comment.txt", - "seattlerb/parse_line_dstr_escaped_newline.txt", "seattlerb/parse_line_heredoc.txt", - "seattlerb/parse_line_multiline_str_literal_n.txt", - "seattlerb/parse_line_str_with_newline_escape.txt", "seattlerb/pct_w_heredoc_interp_nested.txt", - "seattlerb/qsymbols_empty_space.txt", - "seattlerb/qw_escape_term.txt", - "seattlerb/qWords_space.txt", - "seattlerb/read_escape_unicode_curlies.txt", - "seattlerb/read_escape_unicode_h4.txt", "seattlerb/required_kwarg_no_value.txt", - "seattlerb/slashy_newlines_within_string.txt", - "seattlerb/str_double_escaped_newline.txt", - "seattlerb/str_double_newline.txt", - "seattlerb/str_evstr_escape.txt", - "seattlerb/str_newline_hash_line_number.txt", - "seattlerb/str_single_newline.txt", - "seattlerb/symbols_empty_space.txt", "seattlerb/TestRubyParserShared.txt", "unparser/corpus/literal/assignment.txt", - "unparser/corpus/literal/dstr.txt", - "unparser/corpus/semantic/opasgn.txt", + "unparser/corpus/literal/literal.txt", "whitequark/args.txt", "whitequark/beginless_erange_after_newline.txt", "whitequark/beginless_irange_after_newline.txt", - "whitequark/bug_ascii_8bit_in_literal.txt", - "whitequark/bug_def_no_paren_eql_begin.txt", - "whitequark/dedenting_heredoc.txt", - "whitequark/dedenting_non_interpolating_heredoc_line_continuation.txt", "whitequark/forward_arg_with_open_args.txt", - "whitequark/interp_digit_var.txt", + "whitequark/kwarg_no_paren.txt", "whitequark/lbrace_arg_after_command_args.txt", "whitequark/multiple_pattern_matches.txt", "whitequark/newline_in_hash_argument.txt", - "whitequark/parser_bug_640.txt", - "whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt", - "whitequark/ruby_bug_11990.txt", + "whitequark/pattern_matching_expr_in_paren.txt", + "whitequark/pattern_matching_hash.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 + it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/3.4/it.txt") + + buffer = Parser::Source::Buffer.new(it_fixture_path) + buffer.source = it_fixture_path.read + actual_ast = Prism::Translation::Parser34.new.tokenize(buffer)[0] + + it_block_parameter_sexp = parse_sexp { + s(:begin, + s(:itblock, + s(:send, nil, :x), :it, + s(:lvar, :it)), + s(:itblock, + s(:lambda), :it, + s(:lvar, :it))) + } + + assert_equal(it_block_parameter_sexp, actual_ast.to_sexp) + end + private def assert_equal_parses(fixture, compare_asts: true, compare_tokens: true, compare_comments: true) @@ -192,17 +219,13 @@ def assert_equal_parses(fixture, compare_asts: true, compare_tokens: true, compa 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 @@ -213,7 +236,7 @@ def assert_equal_parses(fixture, compare_asts: true, compare_tokens: true, compa 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 @@ -248,22 +271,14 @@ def assert_equal_asts_message(expected_ast, actual_ast) def assert_equal_tokens(expected_tokens, actual_tokens) if expected_tokens != actual_tokens - expected_index = 0 - actual_index = 0 - - while expected_index < expected_tokens.length - expected_token = expected_tokens[expected_index] - actual_token = actual_tokens.fetch(actual_index, []) + index = 0 + max_index = [expected_tokens, actual_tokens].map(&:size).max - expected_index += 1 - actual_index += 1 + while index <= max_index + expected_token = expected_tokens.fetch(index, []) + actual_token = actual_tokens.fetch(index, []) - # The parser gem always has a space before a string end in list - # literals, but we don't. So we'll skip over the space. - if expected_token[0] == :tSPACE && actual_token[0] == :tSTRING_END - expected_index += 1 - next - end + index += 1 # There are a lot of tokens that have very specific meaning according # to the context of the parser. We don't expose that information in @@ -287,5 +302,9 @@ def assert_equal_comments(expected_comments, actual_comments) "actual: #{actual_comments.inspect}" } end + + def parse_sexp(&block) + Class.new { extend AST::Sexp }.instance_eval(&block).to_sexp + end end end diff --git a/test/mri/prism/ruby/ripper_test.rb b/test/mri/prism/ruby/ripper_test.rb index 8db47da3d35..bd63302efcf 100644 --- a/test/mri/prism/ruby/ripper_test.rb +++ b/test/mri/prism/ruby/ripper_test.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -return if RUBY_VERSION < "3.3" +return if RUBY_VERSION < "3.3" || RUBY_ENGINE != "ruby" require_relative "../test_helper" @@ -9,29 +9,37 @@ 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 + # Skip these tests that we haven't implemented yet. omitted = [ "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", @@ -45,11 +53,12 @@ class RipperTest < TestCase "whitequark/dedenting_heredoc.txt", "whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt", "whitequark/parser_slash_slash_n_escaping_in_literals.txt", + "whitequark/ruby_bug_18878.txt", "whitequark/send_block_chain_cmd.txt", "whitequark/slash_newline_in_heredocs.txt" ] - Fixture.each(except: incorrect | omitted) do |fixture| + Fixture.each_for_current_ruby(except: incorrect | omitted) do |fixture| define_method(fixture.test_name) { assert_ripper(fixture.read) } end diff --git a/test/mri/prism/ruby/ruby_parser_test.rb b/test/mri/prism/ruby/ruby_parser_test.rb index a13daeeb849..4b7e9c93edd 100644 --- a/test/mri/prism/ruby/ruby_parser_test.rb +++ b/test/mri/prism/ruby/ruby_parser_test.rb @@ -13,40 +13,32 @@ 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 = [ - "newline_terminated.txt", + "character_literal.txt", + "encoding_euc_jp.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_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 failures = [ "alias.txt", + "dsym_str.txt", "dos_endings.txt", + "heredoc_percent_q_newline_delimiter.txt", + "heredocs_with_fake_newlines.txt", "heredocs_with_ignored_newlines.txt", "method_calls.txt", "methods.txt", @@ -64,7 +56,9 @@ class RubyParserTest < TestCase "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", @@ -80,7 +74,22 @@ class RubyParserTest < TestCase "whitequark/pattern_matching_single_line_allowed_omission_of_parentheses.txt", "whitequark/pattern_matching_single_line.txt", "whitequark/ruby_bug_11989.txt", - "whitequark/slash_newline_in_heredocs.txt" + "whitequark/ruby_bug_18878.txt", + "whitequark/ruby_bug_19281.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.4/circular_parameters.txt", + + "4.0/endless_methods_command_call.txt", + "4.0/leading_logical.txt", + + # https://bugs.ruby-lang.org/issues/21168#note-5 + "command_method_call_2.txt", ] Fixture.each(except: failures) do |fixture| @@ -95,10 +104,16 @@ def assert_ruby_parser(fixture, allowed_failure) 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/mri/prism/snippets_test.rb b/test/mri/prism/snippets_test.rb index 26847da184c..3c28d27a250 100644 --- a/test/mri/prism/snippets_test.rb +++ b/test/mri/prism/snippets_test.rb @@ -5,6 +5,7 @@ module Prism class SnippetsTest < TestCase except = [ + "encoding_binary.txt", "newline_terminated.txt", "seattlerb/begin_rescue_else_ensure_no_bodies.txt", "seattlerb/case_in.txt", @@ -17,24 +18,24 @@ class SnippetsTest < TestCase "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/mri/prism/test_helper.rb b/test/mri/prism/test_helper.rb index b8485002831..43771110b42 100644 --- a/test/mri/prism/test_helper.rb +++ b/test/mri/prism/test_helper.rb @@ -38,7 +38,7 @@ class TestCase < ::Test::Unit::TestCase # 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 +55,45 @@ def full_path 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 +235,41 @@ def self.each_encoding 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 +372,16 @@ def assert_equal_nodes(expected, actual, compare_location: true, parent: nil) 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/mri/prism/unescape_test.rb b/test/mri/prism/unescape_test.rb index f9e5a60e45a..d241f28c087 100644 --- a/test/mri/prism/unescape_test.rb +++ b/test/mri/prism/unescape_test.rb @@ -2,7 +2,9 @@ require_relative "test_helper" -return if RUBY_VERSION < "3.1.0" || Prism::BACKEND == :FFI +return if Prism::BACKEND == :FFI +return if RUBY_VERSION < "3.1.0" +return if RUBY_VERSION >= "3.4.0" module Prism class UnescapeTest < TestCase @@ -204,6 +206,9 @@ def assert_context(context) # \C-a \C-b \C-c ... assert_unescape(context, "C-#{chr}") + # \C-\a \C-\b \C-\c ... + assert_unescape(context, "C-\\#{chr}") + # \ca \cb \cc ... assert_unescape(context, "c#{chr}") diff --git a/test/mri/psych/test_data.rb b/test/mri/psych/test_data.rb new file mode 100644 index 00000000000..57c3478193a --- /dev/null +++ b/test/mri/psych/test_data.rb @@ -0,0 +1,94 @@ +# 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(:foo, :bar) + e = assert_raise(ArgumentError) { Psych.unsafe_load d } + assert_equal 'unknown keywords: :a, :b', e.message + ensure + TestData.send :remove_const, :D + end + end +end + diff --git a/test/mri/psych/test_date_time.rb b/test/mri/psych/test_date_time.rb index 4565b8e7641..79a48e24720 100644 --- a/test/mri/psych/test_date_time.rb +++ b/test/mri/psych/test_date_time.rb @@ -85,5 +85,20 @@ def test_alias_with_time 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/mri/psych/test_exception.rb b/test/mri/psych/test_exception.rb index c1e69ab18d7..6fd92abf9d2 100644 --- a/test/mri/psych/test_exception.rb +++ b/test/mri/psych/test_exception.rb @@ -82,6 +82,19 @@ def test_load_stream_takes_file 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 '()', 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/mri/psych/test_object_references.rb b/test/mri/psych/test_object_references.rb index 86bb9034b9b..0498d54eecc 100644 --- a/test/mri/psych/test_object_references.rb +++ b/test/mri/psych/test_object_references.rb @@ -31,6 +31,11 @@ def test_struct_has_references 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/mri/psych/test_psych.rb b/test/mri/psych/test_psych.rb index 42586a87793..4455c471e71 100644 --- a/test/mri/psych/test_psych.rb +++ b/test/mri/psych/test_psych.rb @@ -89,6 +89,7 @@ def test_dump_stream 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 @@ def test_libyaml_version 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 @@ def test_load_stream_default_fallback 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/mri/psych/test_psych_set.rb b/test/mri/psych/test_psych_set.rb new file mode 100644 index 00000000000..c72cd73f180 --- /dev/null +++ b/test/mri/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/mri/psych/test_ractor.rb b/test/mri/psych/test_ractor.rb index 1b0d8106098..f1c8327aa34 100644 --- a/test/mri/psych/test_ractor.rb +++ b/test/mri/psych/test_ractor.rb @@ -7,7 +7,7 @@ def test_ractor_round_trip 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 @@ def test_ractor_config 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 @@ def test_ractor_constants 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/mri/psych/test_safe_load.rb b/test/mri/psych/test_safe_load.rb index a9ed7375281..e6ca1e142b6 100644 --- a/test/mri/psych/test_safe_load.rb +++ b/test/mri/psych/test_safe_load.rb @@ -114,6 +114,38 @@ def test_anon_struct 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/mri/psych/test_scalar_scanner.rb b/test/mri/psych/test_scalar_scanner.rb index 2637a74df82..bc6a74ad8b2 100644 --- a/test/mri/psych/test_scalar_scanner.rb +++ b/test/mri/psych/test_scalar_scanner.rb @@ -138,6 +138,11 @@ def test_scan_strings_with_strict_int_delimiters 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/mri/psych/test_serialize_subclasses.rb b/test/mri/psych/test_serialize_subclasses.rb index 344c79b3eff..640c331337d 100644 --- a/test/mri/psych/test_serialize_subclasses.rb +++ b/test/mri/psych/test_serialize_subclasses.rb @@ -35,5 +35,23 @@ def test_struct_subclass 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/mri/psych/test_set.rb b/test/mri/psych/test_set.rb index b4968d34252..ccd591c6263 100644 --- a/test/mri/psych/test_set.rb +++ b/test/mri/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/mri/psych/test_stream.rb b/test/mri/psych/test_stream.rb index 9b71c6d9965..ae940d1ee4e 100644 --- a/test/mri/psych/test_stream.rb +++ b/test/mri/psych/test_stream.rb @@ -54,6 +54,14 @@ def test_load_stream_yields_documents 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/mri/psych/test_stringio.rb b/test/mri/psych/test_stringio.rb new file mode 100644 index 00000000000..7fef1402a0a --- /dev/null +++ b/test/mri/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/mri/psych/test_yaml.rb b/test/mri/psych/test_yaml.rb index 812a15dfcc7..134c346c905 100644 --- a/test/mri/psych/test_yaml.rb +++ b/test/mri/psych/test_yaml.rb @@ -2,11 +2,11 @@ # frozen_string_literal: true require_relative 'helper' -require 'ostruct' # [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 @@ -15,8 +15,14 @@ def teardown end def test_y_method - assert_raise(NoMethodError) do - OpenStruct.new.y 1 + begin + require 'ostruct' + + assert_raise(NoMethodError) do + OpenStruct.new.y 1 + end + rescue LoadError + omit("OpenStruct is not available") end end @@ -30,6 +36,10 @@ def test_multiline_regexp 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)) @@ -1032,7 +1042,6 @@ def test_ranges 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( @@ -1064,6 +1073,47 @@ def test_ruby_struct 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" ) + ) + ], < '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/mri/psych/visitors/test_yaml_tree.rb b/test/mri/psych/visitors/test_yaml_tree.rb index 01e685134ae..bd3919f83d6 100644 --- a/test/mri/psych/visitors/test_yaml_tree.rb +++ b/test/mri/psych/visitors/test_yaml_tree.rb @@ -73,6 +73,27 @@ def test_override_method 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/mri/resolv/test_dns.rb b/test/mri/resolv/test_dns.rb index 0a06fba3e70..d5d2648e1bc 100644 --- a/test/mri/resolv/test_dns.rb +++ b/test/mri/resolv/test_dns.rb @@ -525,6 +525,8 @@ def test_no_server 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 @@ def test_too_big_label_address 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 diff --git a/test/mri/resolv/test_win32_config.rb b/test/mri/resolv/test_win32_config.rb new file mode 100644 index 00000000000..6167af6605d --- /dev/null +++ b/test/mri/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/mri/ripper/assert_parse_files.rb b/test/mri/ripper/assert_parse_files.rb index 81f59b7124b..0d583a99e3e 100644 --- a/test/mri/ripper/assert_parse_files.rb +++ b/test/mri/ripper/assert_parse_files.rb @@ -3,8 +3,7 @@ module TestRipper; end class TestRipper::Generic < Test::Unit::TestCase - # Modified for JRuby - SRCDIR = File.expand_path("../../../..", __FILE__) + SRCDIR = File.expand_path("../../..", __FILE__) def assert_parse_files(dir, pattern = "**/*.rb", exclude: nil, gc_stress: GC.stress, test_ratio: nil) test_ratio ||= ENV["TEST_RIPPER_RATIO"]&.tap {|s|break s.to_f} || 0.05 # testing all files needs too long time... diff --git a/test/mri/ripper/test_files_ext.rb b/test/mri/ripper/test_files_ext.rb index 121fff38e66..b77ec0fc8de 100644 --- a/test/mri/ripper/test_files_ext.rb +++ b/test/mri/ripper/test_files_ext.rb @@ -1,7 +1,6 @@ require_relative 'assert_parse_files.rb' class TestRipper::Generic - # Modified for JRuby internal Ruby sources, roughly equivalent to ext - %w[core/src/main/ruby].each do |dir| + %w[ext].each do |dir| define_method("test_parse_files:#{dir}") do assert_parse_files(dir) end diff --git a/test/mri/ripper/test_files_lib.rb b/test/mri/ripper/test_files_lib.rb index 9e249ae8a9e..19f204da568 100644 --- a/test/mri/ripper/test_files_lib.rb +++ b/test/mri/ripper/test_files_lib.rb @@ -1,7 +1,6 @@ require_relative 'assert_parse_files.rb' class TestRipper::Generic - # Modified for JRuby stdlib location - %w[lib/ruby/stdlib].each do |dir| + %w[lib].each do |dir| define_method("test_parse_files:#{dir}") do assert_parse_files(dir, "*.rb") end diff --git a/test/mri/ripper/test_files_sample.rb b/test/mri/ripper/test_files_sample.rb index cf8f93b2d13..57538b1358b 100644 --- a/test/mri/ripper/test_files_sample.rb +++ b/test/mri/ripper/test_files_sample.rb @@ -1,7 +1,6 @@ require_relative 'assert_parse_files.rb' class TestRipper::Generic - # Modified for JRuby's samples dir - %w[samples].each do |dir| + %w[sample].each do |dir| define_method("test_parse_files:#{dir}") do assert_parse_files(dir) end diff --git a/test/mri/ripper/test_files_test.rb b/test/mri/ripper/test_files_test.rb index e8fd01e1c53..5a8e368c718 100644 --- a/test/mri/ripper/test_files_test.rb +++ b/test/mri/ripper/test_files_test.rb @@ -1,7 +1,6 @@ require_relative 'assert_parse_files.rb' class TestRipper::Generic - # Modified for JRuby's copy of CRuby tests - %w[test/mri].each do |dir| + %w[test].each do |dir| define_method("test_parse_files:#{dir}") do assert_parse_files(dir, "*.rb") end diff --git a/test/mri/ripper/test_files_test_1.rb b/test/mri/ripper/test_files_test_1.rb index 71f6d81c508..25db87783ea 100644 --- a/test/mri/ripper/test_files_test_1.rb +++ b/test/mri/ripper/test_files_test_1.rb @@ -1,7 +1,6 @@ require_relative 'assert_parse_files.rb' class TestRipper::Generic - # Modified for JRuby's copy of CRuby tests - Dir["#{SRCDIR}/test/mri/[-a-n]*/"].each do |dir| + Dir["#{SRCDIR}/test/[-a-n]*/"].each do |dir| dir = dir[(SRCDIR.length+1)..-2] define_method("test_parse_files:#{dir}") do assert_parse_files(dir) diff --git a/test/mri/ripper/test_files_test_2.rb b/test/mri/ripper/test_files_test_2.rb index d7fd0a90fa0..24e935e71eb 100644 --- a/test/mri/ripper/test_files_test_2.rb +++ b/test/mri/ripper/test_files_test_2.rb @@ -1,7 +1,6 @@ require_relative 'assert_parse_files.rb' class TestRipper::Generic - # Modified for JRuby's copy of CRuby tests - Dir["#{SRCDIR}/test/mri/[o-z]*/"].each do |dir| + Dir["#{SRCDIR}/test/[o-z]*/"].each do |dir| dir = dir[(SRCDIR.length+1)..-2] define_method("test_parse_files:#{dir}") do assert_parse_files(dir) diff --git a/test/mri/ripper/test_lexer.rb b/test/mri/ripper/test_lexer.rb index 4e8c0003db7..7a2c22ff2d1 100644 --- a/test/mri/ripper/test_lexer.rb +++ b/test/mri/ripper/test_lexer.rb @@ -344,6 +344,47 @@ def test_nested_heredoc ] assert_lexer(expected, code) + + code = <<~'HEREDOC' + <

{") end end; + + # [Bug #21004] + assert_no_memory_leak(%w(-rripper), "", <<~RUBY, rss: true) + 1_000_000.times do + Ripper.parse("-> do it end") + end + RUBY end def test_sexp_no_memory_leak diff --git a/test/mri/ruby/box/a.1_1_0.rb b/test/mri/ruby/box/a.1_1_0.rb new file mode 100644 index 00000000000..03225850975 --- /dev/null +++ b/test/mri/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/mri/ruby/box/a.1_2_0.rb b/test/mri/ruby/box/a.1_2_0.rb new file mode 100644 index 00000000000..29813ea57b2 --- /dev/null +++ b/test/mri/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/mri/ruby/box/a.rb b/test/mri/ruby/box/a.rb new file mode 100644 index 00000000000..26a622c92b5 --- /dev/null +++ b/test/mri/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/mri/ruby/box/autoloading.rb b/test/mri/ruby/box/autoloading.rb new file mode 100644 index 00000000000..cba57ab3776 --- /dev/null +++ b/test/mri/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/mri/ruby/box/blank.rb b/test/mri/ruby/box/blank.rb new file mode 100644 index 00000000000..6d201b09669 --- /dev/null +++ b/test/mri/ruby/box/blank.rb @@ -0,0 +1,2 @@ +module Blank1 +end diff --git a/test/mri/ruby/box/blank1.rb b/test/mri/ruby/box/blank1.rb new file mode 100644 index 00000000000..6d201b09669 --- /dev/null +++ b/test/mri/ruby/box/blank1.rb @@ -0,0 +1,2 @@ +module Blank1 +end diff --git a/test/mri/ruby/box/blank2.rb b/test/mri/ruby/box/blank2.rb new file mode 100644 index 00000000000..ba38c1d6dbe --- /dev/null +++ b/test/mri/ruby/box/blank2.rb @@ -0,0 +1,2 @@ +module Blank2 +end diff --git a/test/mri/ruby/box/box.rb b/test/mri/ruby/box/box.rb new file mode 100644 index 00000000000..3b7da14e9d7 --- /dev/null +++ b/test/mri/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/mri/ruby/box/call_proc.rb b/test/mri/ruby/box/call_proc.rb new file mode 100644 index 00000000000..8acf538fc18 --- /dev/null +++ b/test/mri/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/mri/ruby/box/call_toplevel.rb b/test/mri/ruby/box/call_toplevel.rb new file mode 100644 index 00000000000..c311a37028d --- /dev/null +++ b/test/mri/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/mri/ruby/box/consts.rb b/test/mri/ruby/box/consts.rb new file mode 100644 index 00000000000..e40cd5c50c7 --- /dev/null +++ b/test/mri/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/mri/ruby/box/define_toplevel.rb b/test/mri/ruby/box/define_toplevel.rb new file mode 100644 index 00000000000..aa77db3a135 --- /dev/null +++ b/test/mri/ruby/box/define_toplevel.rb @@ -0,0 +1,5 @@ +def foo + "foooooooooo" +end + +foo # should not raise errors diff --git a/test/mri/ruby/box/global_vars.rb b/test/mri/ruby/box/global_vars.rb new file mode 100644 index 00000000000..590363f6173 --- /dev/null +++ b/test/mri/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/mri/ruby/box/instance_variables.rb b/test/mri/ruby/box/instance_variables.rb new file mode 100644 index 00000000000..1562ad5d450 --- /dev/null +++ b/test/mri/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/mri/ruby/box/line_splitter.rb b/test/mri/ruby/box/line_splitter.rb new file mode 100644 index 00000000000..2596975ad73 --- /dev/null +++ b/test/mri/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/mri/ruby/box/load_path.rb b/test/mri/ruby/box/load_path.rb new file mode 100644 index 00000000000..7e5a83ef96c --- /dev/null +++ b/test/mri/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/mri/ruby/box/open_class_with_include.rb b/test/mri/ruby/box/open_class_with_include.rb new file mode 100644 index 00000000000..ad8fd58ea03 --- /dev/null +++ b/test/mri/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/mri/ruby/box/proc_callee.rb b/test/mri/ruby/box/proc_callee.rb new file mode 100644 index 00000000000..d30ab5d9f3b --- /dev/null +++ b/test/mri/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/mri/ruby/box/proc_caller.rb b/test/mri/ruby/box/proc_caller.rb new file mode 100644 index 00000000000..8acf538fc18 --- /dev/null +++ b/test/mri/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/mri/ruby/box/procs.rb b/test/mri/ruby/box/procs.rb new file mode 100644 index 00000000000..1c39a8231bf --- /dev/null +++ b/test/mri/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/mri/ruby/box/raise.rb b/test/mri/ruby/box/raise.rb new file mode 100644 index 00000000000..efb67f85c5b --- /dev/null +++ b/test/mri/ruby/box/raise.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +raise "Yay!" diff --git a/test/mri/ruby/box/returns_proc.rb b/test/mri/ruby/box/returns_proc.rb new file mode 100644 index 00000000000..bb816e5024d --- /dev/null +++ b/test/mri/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/mri/ruby/box/singleton_methods.rb b/test/mri/ruby/box/singleton_methods.rb new file mode 100644 index 00000000000..05470932d25 --- /dev/null +++ b/test/mri/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/mri/ruby/box/string_ext.rb b/test/mri/ruby/box/string_ext.rb new file mode 100644 index 00000000000..d8c5a3d661f --- /dev/null +++ b/test/mri/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/mri/ruby/box/string_ext_caller.rb b/test/mri/ruby/box/string_ext_caller.rb new file mode 100644 index 00000000000..b8345d98edf --- /dev/null +++ b/test/mri/ruby/box/string_ext_caller.rb @@ -0,0 +1,5 @@ +module Foo + def self.yay + String.new.yay + end +end diff --git a/test/mri/ruby/box/string_ext_calling.rb b/test/mri/ruby/box/string_ext_calling.rb new file mode 100644 index 00000000000..6467b728dd9 --- /dev/null +++ b/test/mri/ruby/box/string_ext_calling.rb @@ -0,0 +1 @@ +Foo.yay diff --git a/test/mri/ruby/box/string_ext_eval_caller.rb b/test/mri/ruby/box/string_ext_eval_caller.rb new file mode 100644 index 00000000000..0e6b20c19f5 --- /dev/null +++ b/test/mri/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/mri/ruby/box/top_level.rb b/test/mri/ruby/box/top_level.rb new file mode 100644 index 00000000000..90df1455782 --- /dev/null +++ b/test/mri/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/mri/ruby/enc/test_case_comprehensive.rb b/test/mri/ruby/enc/test_case_comprehensive.rb index de18ac865c1..b812b88b832 100644 --- a/test/mri/ruby/enc/test_case_comprehensive.rb +++ b/test/mri/ruby/enc/test_case_comprehensive.rb @@ -161,15 +161,14 @@ def self.generate_unicode_case_mapping_tests(encoding) end end - def self.generate_case_mapping_tests(encoding) + def self.generate_single_byte_case_mapping_tests(encoding) all_tests - # preselect codepoints to speed up testing for small encodings - codepoints = @@codepoints.select do |code| + # precalculate codepoints to speed up testing for small encodings + codepoints = [] + (0..255).each do |cp| begin - code.encode(encoding) - true - rescue Encoding::UndefinedConversionError - false + codepoints << cp.chr(encoding).encode('UTF-8') + rescue Encoding::UndefinedConversionError, RangeError end end all_tests.each do |test| @@ -264,23 +263,23 @@ def self.generate_ascii_only_case_mapping_tests(encoding) end end - generate_case_mapping_tests 'US-ASCII' - generate_case_mapping_tests 'ASCII-8BIT' - generate_case_mapping_tests 'ISO-8859-1' - generate_case_mapping_tests 'ISO-8859-2' - generate_case_mapping_tests 'ISO-8859-3' - generate_case_mapping_tests 'ISO-8859-4' - generate_case_mapping_tests 'ISO-8859-5' - generate_case_mapping_tests 'ISO-8859-6' - generate_case_mapping_tests 'ISO-8859-7' - generate_case_mapping_tests 'ISO-8859-8' - generate_case_mapping_tests 'ISO-8859-9' - generate_case_mapping_tests 'ISO-8859-10' - generate_case_mapping_tests 'ISO-8859-11' - generate_case_mapping_tests 'ISO-8859-13' - generate_case_mapping_tests 'ISO-8859-14' - generate_case_mapping_tests 'ISO-8859-15' - generate_case_mapping_tests 'ISO-8859-16' + generate_single_byte_case_mapping_tests 'US-ASCII' + generate_single_byte_case_mapping_tests 'ASCII-8BIT' + generate_single_byte_case_mapping_tests 'ISO-8859-1' + generate_single_byte_case_mapping_tests 'ISO-8859-2' + generate_single_byte_case_mapping_tests 'ISO-8859-3' + generate_single_byte_case_mapping_tests 'ISO-8859-4' + generate_single_byte_case_mapping_tests 'ISO-8859-5' + generate_single_byte_case_mapping_tests 'ISO-8859-6' + generate_single_byte_case_mapping_tests 'ISO-8859-7' + generate_single_byte_case_mapping_tests 'ISO-8859-8' + generate_single_byte_case_mapping_tests 'ISO-8859-9' + generate_single_byte_case_mapping_tests 'ISO-8859-10' + generate_single_byte_case_mapping_tests 'ISO-8859-11' + generate_single_byte_case_mapping_tests 'ISO-8859-13' + generate_single_byte_case_mapping_tests 'ISO-8859-14' + generate_single_byte_case_mapping_tests 'ISO-8859-15' + generate_single_byte_case_mapping_tests 'ISO-8859-16' generate_ascii_only_case_mapping_tests 'KOI8-R' generate_ascii_only_case_mapping_tests 'KOI8-U' generate_ascii_only_case_mapping_tests 'Big5' @@ -291,14 +290,14 @@ def self.generate_ascii_only_case_mapping_tests(encoding) generate_ascii_only_case_mapping_tests 'GBK' generate_ascii_only_case_mapping_tests 'Shift_JIS' generate_ascii_only_case_mapping_tests 'Windows-31J' - generate_case_mapping_tests 'Windows-1250' - generate_case_mapping_tests 'Windows-1251' - generate_case_mapping_tests 'Windows-1252' - generate_case_mapping_tests 'Windows-1253' - generate_case_mapping_tests 'Windows-1254' - generate_case_mapping_tests 'Windows-1255' + generate_single_byte_case_mapping_tests 'Windows-1250' + generate_single_byte_case_mapping_tests 'Windows-1251' + generate_single_byte_case_mapping_tests 'Windows-1252' + generate_single_byte_case_mapping_tests 'Windows-1253' + generate_single_byte_case_mapping_tests 'Windows-1254' + generate_single_byte_case_mapping_tests 'Windows-1255' generate_ascii_only_case_mapping_tests 'Windows-1256' - generate_case_mapping_tests 'Windows-1257' + generate_single_byte_case_mapping_tests 'Windows-1257' generate_unicode_case_mapping_tests 'UTF-8' generate_unicode_case_mapping_tests 'UTF-16BE' generate_unicode_case_mapping_tests 'UTF-16LE' diff --git a/test/mri/ruby/enc/test_emoji_breaks.rb b/test/mri/ruby/enc/test_emoji_breaks.rb index bb5114680e6..0873e681c39 100644 --- a/test/mri/ruby/enc/test_emoji_breaks.rb +++ b/test/mri/ruby/enc/test_emoji_breaks.rb @@ -53,7 +53,7 @@ def self.files 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/mri/ruby/test_alias.rb b/test/mri/ruby/test_alias.rb index 6d4fcc085b3..539cd494887 100644 --- a/test/mri/ruby/test_alias.rb +++ b/test/mri/ruby/test_alias.rb @@ -328,4 +328,17 @@ def foo = 3 } end; end + + def test_undef_method_error_message_with_zsuper_method + modules = [ + Module.new { private :class }, + Module.new { prepend Module.new { private :class } }, + ] + message = "undefined method 'class' for module '%s'" + modules.each do |mod| + assert_raise_with_message(NameError, message % mod) do + mod.alias_method :xyz, :class + end + end + end end diff --git a/test/mri/ruby/test_allocation.rb b/test/mri/ruby/test_allocation.rb index 9ba01dfcf94..90d7c04f9b0 100644 --- a/test/mri/ruby/test_allocation.rb +++ b/test/mri/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 @@ def self.num_allocations #{checks} - unless failures.empty? - assert_equal(true, false, failures.join("\n")) - end + assert_empty(failures) RUBY end @@ -94,13 +98,14 @@ class MethodCall < self 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 @@ def self.optional(x=nil#{block}); end 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 @@ def self.splat(*x#{block}); end 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 @@ def test_required_and_positional_splat_parameters 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 @@ def test_positional_splat_and_post_parameters 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 @@ def self.keyword(a: nil#{block}); end 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 @@ def self.keyword_splat(**kw#{block}); end 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 @@ def self.keyword_and_keyword_splat(a: 1, **kw#{block}); end 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 @@ def self.required_and_keyword(b, a: nil#{block}); end 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 @@ def self.splat_and_keyword(*b, a: nil#{block}); end 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 @@ def self.required_and_keyword_splat(b, **kw#{block}); end 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 @@ def self.splat_and_keyword_splat(*b, **kw#{block}); end 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 @@ def self.splat_and_keyword_splat(*b, **kw#{block}); end 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 @@ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end 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 @@ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end 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 @@ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end 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 @@ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) end 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 @@ def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) end 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 @@ def self.argument_forwarding(...); end 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 @@ def self.argument_forwarding(...); t(...) end 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 @@ def self.r2k(*a#{block}); end 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 @@ def self.r2k(*a#{block}); end 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 @@ def self.r2k(*a#{block}); end 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 @@ def self.keyword(a: nil, b: nil#{block}); end 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 WithBlock < self def block ', &block' end + def only_block + '&block' + end end end @@ -807,13 +910,15 @@ def test_no_array_allocation_with_splat_and_nonstatic_keywords 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 @@ def test_no_array_allocation_with_splat_and_nonstatic_keywords 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 WithBlock < self def block ', &block' end + def only_block + '&block' + end end end end diff --git a/test/mri/ruby/test_array.rb b/test/mri/ruby/test_array.rb index 797ae95e978..d93b86e7953 100644 --- a/test/mri/ruby/test_array.rb +++ b/test/mri/ruby/test_array.rb @@ -1309,32 +1309,7 @@ def test_pack 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 @@ -2716,6 +2691,18 @@ def test_fetch assert_equal(2, [0, 1].fetch(2, 2)) end + def test_fetch_values + ary = @cls[1, 2, 3] + assert_equal([], ary.fetch_values()) + assert_equal([1], ary.fetch_values(0)) + assert_equal([3, 1, 3], ary.fetch_values(2, 0, -1)) + assert_raise(TypeError) {ary.fetch_values("")} + assert_raise(IndexError) {ary.fetch_values(10)} + assert_raise(IndexError) {ary.fetch_values(-20)} + assert_equal(["10 not found"], ary.fetch_values(10) {|i| "#{i} not found"}) + assert_equal(["10 not found", 3], ary.fetch_values(10, 2) {|i| "#{i} not found"}) + end + def test_index2 a = [0, 1, 2] assert_equal(a, a.index.to_a) @@ -3032,13 +3019,12 @@ def test_shuffle end end - def test_shuffle_random - gen = proc do - 10000000 - end - class << gen - alias rand call - end + def test_shuffle_random_out_of_range + gen = random_generator {10000000} + assert_raise(RangeError) { + [*0..2].shuffle(random: gen) + } + gen = random_generator {-1} assert_raise(RangeError) { [*0..2].shuffle(random: gen) } @@ -3046,27 +3032,16 @@ class << gen def test_shuffle_random_clobbering ary = (0...10000).to_a - gen = proc do + gen = random_generator do ary.replace([]) 0.5 end - class << gen - alias rand call - end assert_raise(RuntimeError) {ary.shuffle!(random: gen)} end def test_shuffle_random_zero - zero = Object.new - def zero.to_int - 0 - end - gen_to_int = proc do |max| - zero - end - class << gen_to_int - alias rand call - end + zero = Struct.new(:to_int).new(0) + gen_to_int = random_generator {|max| zero} ary = (0...10000).to_a assert_equal(ary.rotate, ary.shuffle(random: gen_to_int)) end @@ -3134,19 +3109,11 @@ def test_sample_unknown_keyword def test_sample_random_generator ary = (0...10000).to_a assert_raise(ArgumentError) {ary.sample(1, 2, random: nil)} - gen0 = proc do |max| - max/2 - end - class << gen0 - alias rand call - end - gen1 = proc do |max| + gen0 = random_generator {|max| max/2} + gen1 = random_generator do |max| ary.replace([]) max/2 end - class << gen1 - alias rand call - end assert_equal(5000, ary.sample(random: gen0)) assert_nil(ary.sample(random: gen1)) assert_equal([], ary) @@ -3177,20 +3144,23 @@ class << gen1 end def test_sample_random_generator_half - half = Object.new - def half.to_int - 5000 - end - gen_to_int = proc do |max| - half - end - class << gen_to_int - alias rand call - end + half = Struct.new(:to_int).new(5000) + gen_to_int = random_generator {|max| half} ary = (0...10000).to_a assert_equal(5000, ary.sample(random: gen_to_int)) end + def test_sample_random_out_of_range + gen = random_generator {10000000} + assert_raise(RangeError) { + [*0..2].sample(random: gen) + } + gen = random_generator {-1} + assert_raise(RangeError) { + [*0..2].sample(random: gen) + } + end + def test_sample_random_invalid_generator ary = (0..10).to_a assert_raise(NoMethodError) { @@ -3614,6 +3584,23 @@ def test_array_safely_modified_by_sort_block assert_equal((1..67).to_a.reverse, var_0) end + def test_find + ary = [1, 2, 3, 4, 5] + assert_equal(2, ary.find {|x| x % 2 == 0 }) + assert_equal(nil, ary.find {|x| false }) + assert_equal(:foo, ary.find(proc { :foo }) {|x| false }) + end + + def test_rfind + ary = [1, 2, 3, 4, 5] + assert_equal(4, ary.rfind {|x| x % 2 == 0 }) + assert_equal(1, ary.rfind {|x| x < 2 }) + assert_equal(5, ary.rfind {|x| x > 4 }) + assert_equal(nil, ary.rfind {|x| false }) + assert_equal(:foo, ary.rfind(proc { :foo }) {|x| false }) + assert_equal(nil, ary.rfind {|x| ary.clear; false }) + end + private def need_continuation unless respond_to?(:callcc, true) @@ -3621,6 +3608,13 @@ def need_continuation end omit 'requires callcc support' unless respond_to?(:callcc, true) end + + def random_generator(&block) + class << block + alias rand call + end + block + end end class TestArraySubclass < TestArray diff --git a/test/mri/ruby/test_ast.rb b/test/mri/ruby/test_ast.rb index edfc25c43d9..c7a946dec86 100644 --- a/test/mri/ruby/test_ast.rb +++ b/test/mri/ruby/test_ast.rb @@ -48,7 +48,7 @@ def initialize(path, src: nil) @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 @@ def validate_not_cared 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 @@ def validate_not_cared0(node) 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 @@ -337,6 +337,19 @@ def test_invalid_yield 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 +365,50 @@ def test_node_id_for_location 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]' @@ -788,7 +845,7 @@ def test_keep_script_lines_for_of 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 +922,7 @@ def test_e_option 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 @@ -1376,6 +1433,145 @@ def test_case3_locations 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]]) + + node = ast_parse("foo(1..2)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 5, 1, 7]]) + + node = ast_parse("foo(1..2, 3)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 5, 1, 7]]) + + node = ast_parse("foo(..2)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 7], [1, 4, 1, 6]]) + end + + def test_dot3_locations + node = ast_parse("1...2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 5], [1, 1, 1, 4]]) + + node = ast_parse("foo(1...2)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 9], [1, 5, 1, 8]]) + + node = ast_parse("foo(1...2, 3)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 9], [1, 5, 1, 8]]) + + node = ast_parse("foo(...2)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 4, 1, 7]]) + end + + def test_evstr_locations + node = ast_parse('"#{foo}"') + assert_locations(node.children[-1].children[1].locations, [[1, 0, 1, 8], [1, 1, 1, 3], [1, 6, 1, 7]]) + + node = ast_parse('"#$1"') + assert_locations(node.children[-1].children[1].locations, [[1, 0, 1, 5], [1, 1, 1, 2], nil]) + end + + def test_flip2_locations + node = ast_parse("if 'a'..'z'; foo; end") + assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 11], [1, 6, 1, 8]]) + + node = ast_parse('if 1..5; foo; end') + assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 7], [1, 4, 1, 6]]) + end + + def test_flip3_locations + node = ast_parse("if 'a'...('z'); foo; end") + assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 14], [1, 6, 1, 9]]) + + node = ast_parse('if 1...5; foo; end') + assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 8], [1, 4, 1, 7]]) + end + + def test_for_locations + node = ast_parse("for a in b; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 3], [1, 6, 1, 8], nil, [1, 12, 1, 15]]) + + node = ast_parse("for a in b do; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 18], [1, 0, 1, 3], [1, 6, 1, 8], [1, 11, 1, 13], [1, 15, 1, 18]]) + end + + def test_lambda_locations + node = ast_parse("-> (a, b) { foo }") + assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 2], [1, 10, 1, 11], [1, 16, 1, 17]]) + + node = ast_parse("-> (a, b) do foo end") + 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]]) + + node = ast_parse("1 if 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4], nil, nil]) + + node = ast_parse("if 1; elsif 2; else end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 23], [1, 0, 1, 2], [1, 4, 1, 5], [1, 20, 1, 23]]) + assert_locations(node.children[-1].children[-1].locations, [[1, 6, 1, 19], [1, 6, 1, 11], [1, 13, 1, 14], [1, 20, 1, 23]]) + + node = ast_parse("true ? 1 : 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 12], nil, [1, 9, 1, 10], nil]) + + node = ast_parse("case a; in b if c; end") + 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]]) @@ -1411,11 +1607,27 @@ def test_op_asgn2_locations 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]]) end + def test_regx_locations + node = ast_parse("/foo/") + assert_locations(node.children[-1].locations, [[1, 0, 1, 5], [1, 0, 1, 1], [1, 1, 1, 4], [1, 4, 1, 5]]) + + node = ast_parse("/foo/i") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 1], [1, 1, 1, 4], [1, 4, 1, 6]]) + end + def test_return_locations node = ast_parse("return 1") assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 6]]) @@ -1424,6 +1636,14 @@ def test_return_locations 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]]) @@ -1438,6 +1658,14 @@ def test_splat_locations assert_locations(node.children[-1].children[1].children[0].children[0].locations, [[1, 13, 1, 15], [1, 13, 1, 14]]) end + def test_super_locations + node = ast_parse("super 1") + assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 0, 1, 5], nil, nil]) + + node = ast_parse("super(1)") + assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 5], [1, 5, 1, 6], [1, 7, 1, 8]]) + end + def test_unless_locations node = ast_parse("unless cond then 1 else 2 end") assert_locations(node.children[-1].locations, [[1, 0, 1, 29], [1, 0, 1, 6], [1, 12, 1, 16], [1, 26, 1, 29]]) @@ -1460,6 +1688,15 @@ def test_valias_locations 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 @@ -1483,10 +1720,24 @@ def test_until_locations assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil]) end + def test_yield_locations + node = ast_parse("def foo; yield end") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 14], [1, 9, 1, 14], nil, nil]) + + node = ast_parse("def foo; yield() end") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 16], [1, 9, 1, 14], [1, 14, 1, 15], [1, 15, 1, 16]]) + + node = ast_parse("def foo; yield 1, 2 end") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 19], [1, 9, 1, 14], nil, nil]) + + 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 + private def ast_parse(src, **options) begin - verbose_bak, $VERBOSE = $VERBOSE, false + verbose_bak, $VERBOSE = $VERBOSE, nil RubyVM::AbstractSyntaxTree.parse(src, **options) ensure $VERBOSE = verbose_bak diff --git a/test/mri/ruby/test_autoload.rb b/test/mri/ruby/test_autoload.rb index ca3e3d5f7fe..50949274a36 100644 --- a/test/mri/ruby/test_autoload.rb +++ b/test/mri/ruby/test_autoload.rb @@ -224,11 +224,18 @@ def ruby_impl_require 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 @@ def ruby_impl_require 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 @@ def test_require_implemented_in_ruby_is_called 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 @@ def test_autoload_while_autoloading 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 @@ -599,4 +613,10 @@ module SomeNamespace RUBY end end + + private + + def assert_separately(*args, **kwargs) + super(*args, **{ timeout: 60 }.merge(kwargs)) + end end diff --git a/test/mri/ruby/test_backtrace.rb b/test/mri/ruby/test_backtrace.rb index fca7b620302..dad7dfcb558 100644 --- a/test/mri/ruby/test_backtrace.rb +++ b/test/mri/ruby/test_backtrace.rb @@ -454,4 +454,16 @@ def (foo::Bar).baz = raise foo::Bar.baz end; end + + def test_backtrace_internal_frame + backtrace = tap { break caller_locations(0) } + assert_equal(__FILE__, backtrace[1].path) # not "" + 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/mri/ruby/test_bignum.rb b/test/mri/ruby/test_bignum.rb index beef33e2a60..c366f794b2a 100644 --- a/test/mri/ruby/test_bignum.rb +++ b/test/mri/ruby/test_bignum.rb @@ -605,6 +605,49 @@ def test_aref 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 @@ def test_digits 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/mri/ruby/test_box.rb b/test/mri/ruby/test_box.rb new file mode 100644 index 00000000000..a531afa6796 --- /dev/null +++ b/test/mri/ruby/test_box.rb @@ -0,0 +1,857 @@ +# frozen_string_literal: true + +require 'test/unit' + +class TestBox < Test::Unit::TestCase + EXPERIMENTAL_WARNING_LINE_PATTERNS = [ + /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 !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 '1', ENV['RUBY_BOX'] + assert 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 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 Ruby::Box.current.inspect.include?("main") + 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 !global_variables.include?(:$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_load_path_and_loaded_features + setup_box + + assert $LOAD_PATH.respond_to?(: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 @box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank1.rb')) + # assert !@box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank2.rb')) + # assert @box::LoadPathCheck.require_blank2 + # assert @box::LoadPathCheck.current_loaded_features.include?(File.join(box_dir, 'blank2.rb')) + + assert !$LOADED_FEATURES.include?(File.join(box_dir, 'blank1.rb')) + assert !$LOADED_FEATURES.include?(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_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 Ruby::Box.root.respond_to?(:root?) + assert Ruby::Box.main.respond_to?(:main?) + + assert Ruby::Box.root.root? + assert 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 !Ruby::Box.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES.include?("/tmp/foobar")') + assert "FooClass", Ruby::Box.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s') + + assert_equal 1, $a + assert !$LOADED_FEATURES.include?("/tmp/barbaz") + assert !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_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 +end diff --git a/test/mri/ruby/test_call.rb b/test/mri/ruby/test_call.rb index ffbda1fdb96..1b30ed34d88 100644 --- a/test/mri/ruby/test_call.rb +++ b/test/mri/ruby/test_call.rb @@ -374,6 +374,84 @@ def o.foo(a, **h)= h[:splat_modified] = true 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/mri/ruby/test_class.rb b/test/mri/ruby/test_class.rb index 456362ef217..8f12e06685b 100644 --- a/test/mri/ruby/test_class.rb +++ b/test/mri/ruby/test_class.rb @@ -259,6 +259,46 @@ def test_initialize_copy 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 @@ def test_uninitialized 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 @@ -565,7 +601,7 @@ def test_singleton_class_of_frozen_object 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 @@ -700,9 +736,11 @@ def xyzzy 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 +879,70 @@ def test_instance_freeze_dont_freeze_the_class_bug_19164 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_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;'}" + 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/mri/ruby/test_compile_prism.rb b/test/mri/ruby/test_compile_prism.rb index a2e8b7bd97d..76b961b37ef 100644 --- a/test/mri/ruby/test_compile_prism.rb +++ b/test/mri/ruby/test_compile_prism.rb @@ -213,6 +213,12 @@ def test_DefinedNode assert_prism_eval("defined?(a(itself))") assert_prism_eval("defined?(itself(itself))") + # method chain with a block on the inside + assert_prism_eval("defined?(itself { 1 }.itself)") + + # method chain with parenthesized receiver + assert_prism_eval("defined?((itself).itself)") + # Method chain on a constant assert_prism_eval(<<~RUBY) class PrismDefinedNode @@ -1040,13 +1046,19 @@ def o.bar = @ret.length < 3 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("r = []; for foo, in [1,2,3] do r << foo end; r") - assert_prism_eval("for foo, in [1,2,3] do end") + assert_prism_eval("r = []; for i, j in {a: 'b'} do; r << [i, j]; end; r") - assert_prism_eval("for i, j in {a: 'b'} do; i; j; end") + # 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 ############################################################################ @@ -2174,6 +2186,56 @@ def o.foo(...) = 1.times { bar(...) } 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") diff --git a/test/mri/ruby/test_data.rb b/test/mri/ruby/test_data.rb index bb38f8ec919..dd698fdcc4a 100644 --- a/test/mri/ruby/test_data.rb +++ b/test/mri/ruby/test_data.rb @@ -280,4 +280,10 @@ def test_marshal 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/mri/ruby/test_defined.rb b/test/mri/ruby/test_defined.rb index 3a8065d9590..db1fdc8e25a 100644 --- a/test/mri/ruby/test_defined.rb +++ b/test/mri/ruby/test_defined.rb @@ -243,6 +243,26 @@ def test_defined_empty_paren_arg 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/mri/ruby/test_dir.rb b/test/mri/ruby/test_dir.rb index 78371a096b4..edb5210af16 100644 --- a/test/mri/ruby/test_dir.rb +++ b/test/mri/ruby/test_dir.rb @@ -641,6 +641,21 @@ def test_home_at_startup_windows 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/mri/ruby/test_encoding.rb b/test/mri/ruby/test_encoding.rb index 388b94df39a..5c1eb50bb13 100644 --- a/test/mri/ruby/test_encoding.rb +++ b/test/mri/ruby/test_encoding.rb @@ -33,7 +33,7 @@ def test_singleton 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 @@ def test_nonascii_library_path 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 rs.empty? + 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 rs.empty? + end; + end end diff --git a/test/mri/ruby/test_enumerator.rb b/test/mri/ruby/test_enumerator.rb index cd62cd8acb7..9b972d7b22e 100644 --- a/test/mri/ruby/test_enumerator.rb +++ b/test/mri/ruby/test_enumerator.rb @@ -886,6 +886,7 @@ def test_chain_undef_methods 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 @@ def test_produce 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 @@ def test_produce "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/mri/ruby/test_env.rb b/test/mri/ruby/test_env.rb index a68d9c00a28..2727620c198 100644 --- a/test/mri/ruby/test_env.rb +++ b/test/mri/ruby/test_env.rb @@ -363,7 +363,7 @@ def test_inspect_encoding ENV.clear key = "VAR\u{e5 e1 e2 e4 e3 101 3042}" ENV[key] = "foo" - assert_equal(%{{"VAR\u{e5 e1 e2 e4 e3 101 3042}" => "foo"}}, ENV.inspect) + assert_equal(%{{#{(key.encode(ENCODING) rescue key.b).inspect} => "foo"}}, ENV.inspect) end def test_to_a @@ -601,13 +601,13 @@ def str_for_yielding_exception_class(code_str, exception_var: "raised_exception" 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 +649,101 @@ def check(as, bs) 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 +751,87 @@ def test_key_in_ractor 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 +840,11 @@ def test_keys_in_ractor 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 +852,11 @@ def test_each_key_in_ractor 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 +864,11 @@ def test_values_in_ractor 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 +876,11 @@ def test_each_value_in_ractor 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 +889,116 @@ def test_each_pair_in_ractor 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 +1011,16 @@ def test_select_in_ractor 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 +1033,49 @@ def test_filter_in_ractor 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 +1084,20 @@ def test_to_s_in_ractor 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 +1113,14 @@ def test_inspect_in_ractor 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 +1137,59 @@ def test_rehash_in_ractor 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 +1197,7 @@ def test_assoc_in_ractor 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 +1205,29 @@ def test_assoc_in_ractor 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 +1240,39 @@ def test_rassoc_in_ractor 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 +1280,86 @@ def test_reject_in_ractor 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,34 +1367,34 @@ def test_huge_value_in_ractor 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.frozen?] + port.send [v.frozen?] end ENV.each_key do |k| - Ractor.yield [k.frozen?] + port.send [k.frozen?] end ENV.each_value do |v| - Ractor.yield [v.frozen?] + port.send [v.frozen?] 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].frozen?, "[\#{k.dump}]"] + port.send [ENV.fetch(k).frozen?, "fetch(\#{k.dump})"] end - Ractor.yield "finished" + port.send "finished" end - while((params=r.take) != "finished") + while((params=port.receive) != "finished") assert(*params) end end; @@ -1401,7 +1402,7 @@ def test_frozen_in_ractor 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 +1412,9 @@ def test_shared_substring_in_ractor 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 +1430,7 @@ def test_ivar_in_env_should_not_be_access_from_non_main_ractors 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 +1438,7 @@ def test_ivar_in_env_should_not_be_access_from_non_main_ractors 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 +1446,7 @@ def test_ivar_in_env_should_not_be_access_from_non_main_ractors e end - assert_equal Ractor::IsolationError, r_set.take.class + assert_equal Ractor::IsolationError, r_set.value.class RUBY end @@ -1492,11 +1493,8 @@ def test_memory_leak_shift def test_utf8 text = "testing \u{e5 e1 e2 e4 e3 101 3042}" - test = ENV["test"] ENV["test"] = text assert_equal text, ENV["test"] - ensure - ENV["test"] = test end def test_utf8_empty diff --git a/test/mri/ruby/test_exception.rb b/test/mri/ruby/test_exception.rb index 84581180b60..17ff5a2e829 100644 --- a/test/mri/ruby/test_exception.rb +++ b/test/mri/ruby/test_exception.rb @@ -1525,4 +1525,31 @@ def detailed_message(**) 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 to Array (TestException::Ex#to_a gives Integer)") do + x(*a) + end + assert_raise_with_message(TypeError, "can't convert TestException::Ex to Hash (TestException::Ex#to_hash gives Integer)") do + x(**a) + end + assert_raise_with_message(TypeError, "can't convert TestException::Ex to Proc (TestException::Ex#to_proc gives Integer)") do + x(&a) + end + end end diff --git a/test/mri/ruby/test_fiber.rb b/test/mri/ruby/test_fiber.rb index 19cd52f7c8f..b7d2b71c196 100644 --- a/test/mri/ruby/test_fiber.rb +++ b/test/mri/ruby/test_fiber.rb @@ -34,7 +34,6 @@ def test_term end def test_many_fibers - omit 'This is unstable on GitHub Actions --jit-wait. TODO: debug it' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? max = 1000 assert_equal(max, max.times{ Fiber.new{} @@ -50,7 +49,7 @@ def test_many_fibers end def test_many_fibers_with_threads - assert_normal_exit <<-SRC, timeout: (/solaris/i =~ RUBY_PLATFORM ? 1000 : 60) + assert_normal_exit <<-SRC, timeout: 60 max = 1000 @cnt = 0 (1..100).map{|ti| @@ -499,7 +498,7 @@ def test_create_fiber_in_new_thread 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 diff --git a/test/mri/ruby/test_file.rb b/test/mri/ruby/test_file.rb index eae9a8e7b03..a3d6221c0f9 100644 --- a/test/mri/ruby/test_file.rb +++ b/test/mri/ruby/test_file.rb @@ -372,9 +372,9 @@ def measure_time 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 @@ def test_stat 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/mri/ruby/test_file_exhaustive.rb b/test/mri/ruby/test_file_exhaustive.rb index f3068cb1891..394dc47603f 100644 --- a/test/mri/ruby/test_file_exhaustive.rb +++ b/test/mri/ruby/test_file_exhaustive.rb @@ -6,7 +6,8 @@ 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 @@ def test_path [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) @@ -1278,9 +1299,10 @@ def test_dirname 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 @@ -1475,6 +1497,7 @@ def test_flock_shared 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/mri/ruby/test_float.rb b/test/mri/ruby/test_float.rb index f2c56d1b41f..d0d180593ab 100644 --- a/test/mri/ruby/test_float.rb +++ b/test/mri/ruby/test_float.rb @@ -838,6 +838,10 @@ def test_Float 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 +861,9 @@ def o.to_f; inf = Float::INFINITY; inf/inf; end 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/mri/ruby/test_frozen.rb b/test/mri/ruby/test_frozen.rb index 2918a2afd82..6721cb11286 100644 --- a/test/mri/ruby/test_frozen.rb +++ b/test/mri/ruby/test_frozen.rb @@ -27,4 +27,20 @@ def test_setting_ivar_on_frozen_string_with_ivars 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: #") { A.singleton_class.instance_variable_set(:@a, 1) } + end end diff --git a/test/mri/ruby/test_gc.rb b/test/mri/ruby/test_gc.rb index 72fab5c43cb..6639013a54c 100644 --- a/test/mri/ruby/test_gc.rb +++ b/test/mri/ruby/test_gc.rb @@ -75,12 +75,9 @@ def test_gc_config_setting_returns_updated_config_hash 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 @@ def test_stat_constraints 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] @@ -234,6 +231,9 @@ def test_stat_heap end assert_equal (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) * (2**i), stat_heap[:slot_size] + assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots] + assert_operator stat_heap[:heap_free_slots], :<=, stat[:heap_free_slots] + assert_operator stat_heap[:heap_final_slots], :<=, stat[:heap_final_slots] assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages] assert_operator stat_heap[:heap_eden_slots], :>=, 0 assert_operator stat_heap[:total_allocated_pages], :>=, 0 @@ -253,7 +253,6 @@ def test_stat_heap end def test_stat_heap_all - omit "flaky with RJIT, which allocates objects itself" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? stat_heap_all = {} stat_heap = {} # Initialize to prevent GC in future calls @@ -265,7 +264,7 @@ def test_stat_heap_all GC.stat_heap(i, stat_heap) # Remove keys that can vary between invocations - %i(total_allocated_objects).each do |sym| + %i(total_allocated_objects heap_live_slots heap_free_slots).each do |sym| stat_heap[sym] = stat_heap_all[i][sym] = 0 end @@ -290,6 +289,9 @@ def test_stat_heap_constraints hash.each { |k, v| stat_heap_sum[k] += v } end + assert_equal stat[:heap_live_slots], stat_heap_sum[:heap_live_slots] + assert_equal stat[:heap_free_slots], stat_heap_sum[:heap_free_slots] + assert_equal stat[:heap_final_slots], stat_heap_sum[:heap_final_slots] assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages] assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] assert_equal stat[:total_allocated_objects], stat_heap_sum[:total_allocated_objects] @@ -297,7 +299,7 @@ def test_stat_heap_constraints 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) @@ -357,13 +359,14 @@ def test_latest_gc_info_need_major_by 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. @@ -393,12 +396,10 @@ def test_latest_gc_info_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| + ary = Array.new(count) do |i| obj = Object.new - ary[i] = obj wmap[obj] = nil + obj end # Run full GC to collect stats about weak references @@ -411,6 +412,8 @@ def test_latest_gc_info_weak_references_count 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 @@ -418,8 +421,8 @@ def test_latest_gc_info_weak_references_count # Run full GC again to collect stats about weak references GC.start - # Sometimes the WeakMap has one element, which might be held on by registers. - assert_operator(wmap.size, :<=, 1) + # Sometimes the WeakMap has a few elements, which might be held on by registers. + assert_operator(wmap.size, :<=, count / 1000) 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) @@ -450,7 +453,7 @@ def obj.bar() raise "obj.foo is called, but this is obj.bar" end 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) @@ -533,6 +536,8 @@ def test_gc_parameter end def test_gc_parameter_init_slots + 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 @@ -677,13 +682,39 @@ def test_thrashing_for_young_objects # 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_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] @@ -717,6 +748,7 @@ def test_exception_in_finalizer end def test_interrupt_in_finalizer + omit 'randomly hangs on many platforms' if ENV.key?('GITHUB_ACTIONS') bug10595 = '[ruby-core:66825] [Bug #10595]' src = <<-'end;' Signal.trap(:INT, 'DEFAULT') @@ -732,7 +764,7 @@ def test_interrupt_in_finalizer 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 @@ -799,6 +831,8 @@ def test_vm_object end def test_exception_in_finalizer_procs + require '-test-/stack' + omit 'failing with ASAN' if Thread.asan? assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2]) c1 = proc do puts "c1" @@ -819,6 +853,8 @@ def test_exception_in_finalizer_procs end def test_exception_in_finalizer_method + require '-test-/stack' + omit 'failing with ASAN' if Thread.asan? assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2]) def self.c1(x) puts "c1" @@ -884,4 +920,25 @@ def test_old_to_young_reference assert_include ObjectSpace.dump(young_obj), '"old":true' end end + + def test_finalizer_not_run_with_vm_lock + assert_ractor(<<~'RUBY') + 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/mri/ruby/test_gc_compact.rb b/test/mri/ruby/test_gc_compact.rb index 4c8aa20215f..a03535171c4 100644 --- a/test/mri/ruby/test_gc_compact.rb +++ b/test/mri/ruby/test_gc_compact.rb @@ -324,7 +324,7 @@ def test_moving_arrays_up_heaps }.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, :>=, ARY_COUNT - 15) refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) end; end @@ -356,11 +356,27 @@ def add_ivars 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 - 15) 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[:SIZE_POOL_COUNT] == 1 + + assert_separately([], "#{<<~"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 @@ -377,7 +393,7 @@ def test_moving_strings_up_heaps 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 - 15) refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) end; end @@ -452,4 +468,21 @@ def set_a assert_raise(FrozenError) { a.set_a } end; end + + def test_moving_too_complex_generic_ivar + omit "not compiled with SHAPE_DEBUG" unless defined?(RubyVM::Shape) + + assert_separately([], <<~RUBY) + RubyVM::Shape.exhaust_shapes + + obj = [] + obj.instance_variable_set(:@fixnum, 123) + obj.instance_variable_set(:@str, "hello") + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_equal(123, obj.instance_variable_get(:@fixnum)) + assert_equal("hello", obj.instance_variable_get(:@str)) + RUBY + end end diff --git a/test/mri/ruby/test_hash.rb b/test/mri/ruby/test_hash.rb index 87eb1912d98..32384f5a5c1 100644 --- a/test/mri/ruby/test_hash.rb +++ b/test/mri/ruby/test_hash.rb @@ -880,21 +880,20 @@ def test_inspect 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 @@ def test_update5 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 @@ def test_transform_values_bang 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 @@ def test_AREF_fstring_key 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 @@ def hash_iter_recursion(h, level) 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 @@ def test_bug_12706 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 @@ def hash 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/mri/ruby/test_integer.rb b/test/mri/ruby/test_integer.rb index 1dbb3fbb457..f9bf4fa20c8 100644 --- a/test/mri/ruby/test_integer.rb +++ b/test/mri/ruby/test_integer.rb @@ -158,7 +158,9 @@ def obj.to_i; "str"; end 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 @@ def test_square_root 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)) diff --git a/test/mri/ruby/test_io.rb b/test/mri/ruby/test_io.rb index ec080080c57..1adf47ac51a 100644 --- a/test/mri/ruby/test_io.rb +++ b/test/mri/ruby/test_io.rb @@ -467,6 +467,24 @@ def test_each_codepoint_closed } end + def test_each_codepoint_with_ungetc + bug21562 = '[ruby-core:123176] [Bug #21562]' + with_read_pipe("") {|p| + p.binmode + p.ungetc("aa") + a = "" + p.each_codepoint { |c| a << c } + assert_equal("aa", a, bug21562) + } + with_read_pipe("") {|p| + p.set_encoding("ascii-8bit", universal_newline: true) + p.ungetc("aa") + a = "" + p.each_codepoint { |c| a << c } + assert_equal("aa", a, bug21562) + } + end + def test_rubydev33072 t = make_tempfile path = t.path @@ -681,7 +699,6 @@ def test_copy_stream_file_to_pipe if have_nonblock? def test_copy_stream_no_busy_wait - omit "RJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? omit "multiple threads already active" if Thread.list.size > 1 msg = 'r58534 [ruby-core:80969] [Backport #13533]' @@ -1142,6 +1159,34 @@ def test_copy_stream_pathname_to_pathname } end + def test_copy_stream_dup_buffer + bug21131 = '[ruby-core:120961] [Bug #21131]' + mkcdtmpdir do + dst_class = Class.new do + def initialize(&block) + @block = block + end + + def write(data) + @block.call(data.dup) + data.bytesize + end + end + + rng = Random.new(42) + body = Tempfile.new("ruby-bug", binmode: true) + body.write(rng.bytes(16_385)) + body.rewind + + payload = [] + IO.copy_stream(body, dst_class.new{payload << it}) + body.rewind + assert_equal(body.read, payload.join, bug21131) + ensure + body&.close + end + end + def test_copy_stream_write_in_binmode bug8767 = '[ruby-core:56518] [Bug #8767]' mkcdtmpdir { @@ -1705,7 +1750,6 @@ def test_read_nonblock_invalid_exception end if have_nonblock? def test_read_nonblock_no_exceptions - omit '[ruby-core:90895] RJIT worker may leave fd open in a forked child' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # TODO: consider acquiring GVL from RJIT worker. with_pipe {|r, w| assert_equal :wait_readable, r.read_nonblock(4096, exception: false) w.puts "HI!" @@ -2515,10 +2559,6 @@ def test_autoclose end def test_autoclose_true_closed_by_finalizer - # http://ci.rvm.jp/results/trunk-rjit@silicon-docker/1465760 - # http://ci.rvm.jp/results/trunk-rjit@silicon-docker/1469765 - omit 'this randomly fails with RJIT' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? - feature2250 = '[ruby-core:26222]' pre = 'ft2250' t = Tempfile.new(pre) @@ -2579,36 +2619,15 @@ def o.to_open(kw); kw; end assert_equal({:a=>1}, open(o, {a: 1})) end - def test_open_pipe - assert_deprecated_warning(/Kernel#open with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - open("|" + EnvUtil.rubybin, "r+") do |f| - f.puts "puts 'foo'" - f.close_write - assert_equal("foo\n", f.read) - end - end - end + def test_path_with_pipe + mkcdtmpdir do + cmd = "|echo foo" + assert_file.not_exist?(cmd) - def test_read_command - assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - assert_equal("foo\n", IO.read("|echo foo")) - end - assert_raise(Errno::ENOENT, Errno::EINVAL) do - File.read("|#{EnvUtil.rubybin} -e puts") - end - assert_raise(Errno::ENOENT, Errno::EINVAL) do - File.binread("|#{EnvUtil.rubybin} -e puts") - end - assert_raise(Errno::ENOENT, Errno::EINVAL) do - Class.new(IO).read("|#{EnvUtil.rubybin} -e puts") - end - assert_raise(Errno::ENOENT, Errno::EINVAL) do - Class.new(IO).binread("|#{EnvUtil.rubybin} -e puts") - end - assert_raise(Errno::ESPIPE) do - assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - IO.read("|#{EnvUtil.rubybin} -e 'puts :foo'", 1, 1) - end + pipe_errors = [Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EPERM] + assert_raise(*pipe_errors) { open(cmd, "r+") } + assert_raise(*pipe_errors) { IO.read(cmd) } + assert_raise(*pipe_errors) { IO.foreach(cmd) {|x| assert false } } end end @@ -2813,19 +2832,6 @@ def test_reopen_ivar end def test_foreach - a = [] - - assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x } - end - assert_equal(["foo\n", "bar\n", "baz\n"], a) - - a = [] - assert_deprecated_warning(/IO process creation with a leading '\|'/) do # https://bugs.ruby-lang.org/issues/19630 - IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x } - end - assert_equal(["zot\n"], a) - make_tempfile {|t| a = [] IO.foreach(t.path) {|x| a << x } @@ -2901,10 +2907,10 @@ def test_print end def test_print_separators - EnvUtil.suppress_warning { - $, = ':' - $\ = "\n" - } + assert_deprecated_warning(/non-nil '\$,'/) {$, = ":"} + assert_raise(TypeError) {$, = 1} + assert_deprecated_warning(/non-nil '\$\\'/) {$\ = "\n"} + assert_raise(TypeError) {$/ = 1} pipe(proc do |w| w.print('a') EnvUtil.suppress_warning {w.print('a','b','c')} @@ -3804,7 +3810,7 @@ def test_io_select_with_many_files end tempfiles = [] - (0..fd_setsize+1).map {|i| + (0...fd_setsize).map {|i| tempfiles << Tempfile.create("test_io_select_with_many_files") } @@ -4240,6 +4246,23 @@ def test_select_exceptfds end end if Socket.const_defined?(:MSG_OOB) + def test_select_timeout + assert_equal(nil, IO.select(nil,nil,nil,0)) + assert_equal(nil, IO.select(nil,nil,nil,0.0)) + assert_raise(TypeError) { IO.select(nil,nil,nil,"invalid-timeout") } + assert_raise(ArgumentError) { IO.select(nil,nil,nil,-1) } + assert_raise(ArgumentError) { IO.select(nil,nil,nil,-0.1) } + assert_raise(ArgumentError) { IO.select(nil,nil,nil,-Float::INFINITY) } + assert_raise(RangeError) { IO.select(nil,nil,nil,Float::NAN) } + IO.pipe {|r, w| + w << "x" + ret = [[r], [], []] + assert_equal(ret, IO.select([r],nil,nil,0.1)) + assert_equal(ret, IO.select([r],nil,nil,1)) + assert_equal(ret, IO.select([r],nil,nil,Float::INFINITY)) + } + end + def test_recycled_fd_close dot = -'.' IO.pipe do |sig_rd, sig_wr| @@ -4351,4 +4374,55 @@ def test_stdout_to_closed_pipe 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/mri/ruby/test_io_buffer.rb b/test/mri/ruby/test_io_buffer.rb index 55296c1f232..e996fc39b88 100644 --- a/test/mri/ruby/test_io_buffer.rb +++ b/test/mri/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] @@ -73,12 +74,64 @@ def test_new_readonly 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") @@ -121,6 +174,16 @@ def test_string_mapped_buffer_locked 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 @@ def test_zero_length_get_string :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 @@ def test_get_set_value 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,7 @@ def test_each_byte 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_zero_length_each_byte @@ -421,7 +502,21 @@ def test_zero_length_each_byte 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 @@ -683,4 +778,156 @@ def test_set_string_null_destination 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 end diff --git a/test/mri/ruby/test_io_m17n.rb b/test/mri/ruby/test_io_m17n.rb index b01d627d920..83d4fb0c7b5 100644 --- a/test/mri/ruby/test_io_m17n.rb +++ b/test/mri/ruby/test_io_m17n.rb @@ -1395,30 +1395,6 @@ def test_popenv_r_enc_enc_in_opt2 } 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 @@ def test_pos_dont_move_cursor_position 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 @@ def test_each_codepoint_need_more 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/mri/ruby/test_iseq.rb b/test/mri/ruby/test_iseq.rb index 9eb9c84602f..fa716787fe9 100644 --- a/test/mri/ruby/test_iseq.rb +++ b/test/mri/ruby/test_iseq.rb @@ -92,7 +92,7 @@ def test_cdhash_after_roundtrip 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 @@ def bar(a, b); a + b; end 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 @@ def (Object.new).touch(*) # :nodoc: 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 @@ def (Object.new).touch(req, *) 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 @@ def (Object.new).touch(**) # :nodoc: 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 @@ def (Object.new).touch(&) # :nodoc: 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 - 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 + eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}") 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 @@ def test_ractor_shareable_value_frozen_core # 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 @@ -297,6 +292,56 @@ def test_invalid_source 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 @@ -566,16 +611,20 @@ def hexdump(bin) } 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 +642,7 @@ def assert_iseq_to_binary(code, mesg = nil) 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 @@ -668,7 +717,7 @@ def self.foo 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 +813,7 @@ def test_iseq_builtin_to_a 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 +853,7 @@ def Float(n) GC.start Float(30) } - assert_equal :new, r.take + assert_equal :new, r.value RUBY end @@ -855,9 +904,28 @@ def test_unreachable_next_in_block 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 +936,7 @@ def test_loading_kwargs_memory_leak 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 +987,10 @@ def test_while_in_until_condition 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/mri/ruby/test_keyword.rb b/test/mri/ruby/test_keyword.rb index 4563308fa2e..c836abd0c66 100644 --- a/test/mri/ruby/test_keyword.rb +++ b/test/mri/ruby/test_keyword.rb @@ -2424,6 +2424,21 @@ class << c 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 @@ def test_proc_ruby2_keywords 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 @@ def method_missing(*args) 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 @@ def m(a: []) 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/mri/ruby/test_literal.rb b/test/mri/ruby/test_literal.rb index 1fdc6aa853a..dbff3c47342 100644 --- a/test/mri/ruby/test_literal.rb +++ b/test/mri/ruby/test_literal.rb @@ -97,6 +97,12 @@ def test_string assert_equal "ab", eval("?a 'b'") assert_equal "a\nb", eval("< 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/mri/ruby/test_module.rb b/test/mri/ruby/test_module.rb index 4c171bb4394..30a7c5d9bc1 100644 --- a/test/mri/ruby/test_module.rb +++ b/test/mri/ruby/test_module.rb @@ -412,19 +412,6 @@ def test_constants 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 @@ def x 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) @@ -1291,8 +1273,11 @@ def test_const_set_invalid_name 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 @@ def test_attr 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 @@ def test_attr 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 @@ -3020,17 +3016,17 @@ def test_frozen_visibility 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 @@ -3207,7 +3203,6 @@ def test_define_method_with_unbound_method end def test_redefinition_mismatch - omit "Investigating trunk-rjit failure on ci.rvm.jp" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? m = Module.new m.module_eval "A = 1", __FILE__, line = __LINE__ e = assert_raise_with_message(TypeError, /is not a module/) { @@ -3365,6 +3360,53 @@ def test_module_clone_memory_leak CODE end + def test_set_temporary_name + m = Module.new + assert_nil m.name + + m.const_set(:N, Module.new) + + assert_match(/\A#::N\z/, m::N.name) + assert_same m::N, m::N.set_temporary_name(name = "fake_name_under_M") + name.upcase! + assert_equal("fake_name_under_M", m::N.name) + assert_raise(FrozenError) {m::N.name.upcase!} + assert_same m::N, m::N.set_temporary_name(nil) + assert_nil(m::N.name) + + m::N.const_set(:O, Module.new) + m.const_set(:Recursive, m) + m::N.const_set(:Recursive, m) + m.const_set(:A, 42) + + assert_same m, m.set_temporary_name(name = "fake_name") + name.upcase! + assert_equal("fake_name", m.name) + assert_raise(FrozenError) {m.name.upcase!} + assert_equal("fake_name::N", m::N.name) + assert_equal("fake_name::N::O", m::N::O.name) + + assert_same m, m.set_temporary_name(nil) + assert_nil m.name + assert_nil m::N.name + assert_nil m::N::O.name + + assert_raise_with_message(ArgumentError, "empty class/module name") do + m.set_temporary_name("") + end + %w[A A::B ::A ::A::B].each do |name| + assert_raise_with_message(ArgumentError, /must not be a constant path/) do + m.set_temporary_name(name) + end + end + + [Object, User, AClass].each do |mod| + assert_raise_with_message(RuntimeError, /permanent name/) do + mod.set_temporary_name("fake_name") + end + end + end + private def assert_top_method_is_private(method) diff --git a/test/mri/ruby/test_nomethod_error.rb b/test/mri/ruby/test_nomethod_error.rb index 6d413e63914..aa2a88b2d8f 100644 --- a/test/mri/ruby/test_nomethod_error.rb +++ b/test/mri/ruby/test_nomethod_error.rb @@ -106,4 +106,32 @@ def name 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/mri/ruby/test_numeric.rb b/test/mri/ruby/test_numeric.rb index ab492743f6e..3bf93ef20dc 100644 --- a/test/mri/ruby/test_numeric.rb +++ b/test/mri/ruby/test_numeric.rb @@ -18,18 +18,24 @@ def test_coerce 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" diff --git a/test/mri/ruby/test_object.rb b/test/mri/ruby/test_object.rb index 7d004226295..f4dfe2251b8 100644 --- a/test/mri/ruby/test_object.rb +++ b/test/mri/ruby/test_object.rb @@ -280,6 +280,12 @@ def test_methods_prepend_singleton 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 @@ def test_instance_variable_get 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 @@ def test_instance_variable_set 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 @@ def test_instance_variable_defined 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,41 @@ def test_remove_instance_variable 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 + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + c = Class.new do + attr_reader :a, :b, :c - 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' + def initialize + @a = nil + @b = nil + @c = nil + end + end - 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) + 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) + end; end def test_convert_string @@ -950,6 +953,19 @@ def initialize 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_singleton_methods diff --git a/test/mri/ruby/test_object_id.rb b/test/mri/ruby/test_object_id.rb new file mode 100644 index 00000000000..adb819febce --- /dev/null +++ b/test/mri/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 + @too_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), :too_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), :too_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), :too_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 + @too_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), :too_complex?) + end + end +end diff --git a/test/mri/ruby/test_objectspace.rb b/test/mri/ruby/test_objectspace.rb index 5c79983b7e8..a479547599a 100644 --- a/test/mri/ruby/test_objectspace.rb +++ b/test/mri/ruby/test_objectspace.rb @@ -8,7 +8,7 @@ def self.deftest_id2ref(obj) 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 @@ def test_id2ref_liveness 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 @@ def test_count_objects 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 @@ class << fin } 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 @@ def test_each_object_recursive_key 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/mri/ruby/test_optimization.rb b/test/mri/ruby/test_optimization.rb index 5aaf9647a89..089c5fbd1d0 100644 --- a/test/mri/ruby/test_optimization.rb +++ b/test/mri/ruby/test_optimization.rb @@ -591,7 +591,6 @@ def run(current, final) end def test_tailcall_not_to_grow_stack - omit 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? bug16161 = '[ruby-core:94881]' tailcall("#{<<-"begin;"}\n#{<<~"end;"}") @@ -607,11 +606,11 @@ def foo(n) end class Bug10557 - def [](_) + def [](_, &) block_given? end - def []=(_, _) + def []=(_, _, &) block_given? end end @@ -1216,4 +1215,58 @@ def == x 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/mri/ruby/test_parse.rb b/test/mri/ruby/test_parse.rb index a2f64200f37..9fa4dad41e2 100644 --- a/test/mri/ruby/test_parse.rb +++ b/test/mri/ruby/test_parse.rb @@ -186,6 +186,15 @@ def foo 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 @@ def test_call_method 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 @@ def test_define_singleton_error 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 @@ def test_question 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 @@ def test_shareable_constant_value_ignored 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 @@ -1721,6 +1753,15 @@ def o.freeze; self; end end; end + def test_shareable_constant_value_massign + a = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: experimental_everything + A, = 1 + end; + assert_equal(1, a) + end + def test_if_after_class assert_valid_syntax('module if true; Object end::Kernel; end') assert_valid_syntax('module while true; break Object end::Kernel; end') diff --git a/test/mri/ruby/test_pattern_matching.rb b/test/mri/ruby/test_pattern_matching.rb index 92a3244fc2a..96aa2a7fd6b 100644 --- a/test/mri/ruby/test_pattern_matching.rb +++ b/test/mri/ruby/test_pattern_matching.rb @@ -197,11 +197,49 @@ def test_alternative_pattern 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/mri/ruby/test_proc.rb b/test/mri/ruby/test_proc.rb index 6ca9e0cfb44..f74342322f5 100644 --- a/test/mri/ruby/test_proc.rb +++ b/test/mri/ruby/test_proc.rb @@ -1382,7 +1382,8 @@ def test_parameters assert_equal([[:opt, :a], [:rest, :b], [:opt, :c]], proc {|a, *b, c|}.parameters) assert_equal([[:opt, :a], [:rest, :b], [:opt, :c], [:block, :d]], proc {|a, *b, c, &d|}.parameters) assert_equal([[:opt, :a], [:opt, :b], [:rest, :c], [:opt, :d], [:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters) - assert_equal([[:opt, nil], [:block, :b]], proc {|(a), &b|a}.parameters) + assert_equal([[:opt], [:block, :b]], proc {|(a), &b|a}.parameters) + assert_equal([[:opt], [:rest, :_], [:opt]], proc {|(a_), *_, (b_)|}.parameters) assert_equal([[:opt, :a], [:opt, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:opt, :f], [:opt, :g], [:block, :h]], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters) assert_equal([[:req]], method(:putc).parameters) @@ -1390,6 +1391,8 @@ def test_parameters pr = eval("proc{|"+"(_),"*30+"|}") assert_empty(pr.parameters.map{|_,n|n}.compact) + + assert_equal([[:opt]], proc { it }.parameters) end def test_proc_autosplat_with_multiple_args_with_ruby2_keywords_splat_bug_19759 @@ -1438,6 +1441,9 @@ def test_parameters_lambda assert_equal([[:opt, :a]], lambda {|a|}.parameters(lambda: false)) assert_equal([[:opt, :a], [:opt, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:opt, :f], [:opt, :g], [:block, :h]], lambda {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: false)) + + assert_equal([[:req]], proc { it }.parameters(lambda: true)) + assert_equal([[:opt]], lambda { it }.parameters(lambda: false)) end def pm0() end @@ -1627,6 +1633,10 @@ def test_local_variable_get 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 @@ -1639,6 +1649,274 @@ def test_local_variable_set assert_equal(20, b.eval("b")) end + def test_numparam_is_not_local_variables + "foo".tap do + _9 and flunk + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + "bar".tap do + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + end + + "foo".tap do + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + "bar".tap do + _9 and flunk + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + end + end + + def test_implicit_parameters_for_numparams + x = x = 1 + assert_raise(NameError) { binding.implicit_parameter_get(:x) } + assert_raise(NameError) { binding.implicit_parameter_defined?(:x) } + + "foo".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + "bar".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + end + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + end + + "foo".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + "bar".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + end + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + end + end + + def test_it_is_not_local_variable + "foo".tap do + it + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + "bar".tap do + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + "bar".tap do + it + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + + "foo".tap do + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + "bar".tap do + it + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + end + + def test_implicit_parameters_for_it + "foo".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + "bar".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + + "foo".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + "bar".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + end + + def test_implicit_parameters_for_it_complex + "foo".tap do + it = it = "bar" + + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + + assert_equal([:it], binding.local_variables) + assert_equal("bar", binding.local_variable_get(:it)) + assert_equal(true, binding.local_variable_defined?(:it)) + end + + "foo".tap do + it or flunk + + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:it)) + + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + + "foo".tap do + it or flunk + it = it = "bar" + + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:it)) + + assert_equal([:it], binding.local_variables) + assert_equal("bar", binding.local_variable_get(:it)) + assert_equal(true, binding.local_variable_defined?(:it)) + end + end + + def test_implicit_parameters_for_it_and_numparams + "foo".tap do + it or flunk + "bar".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal("bar", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + end + + "foo".tap do + _5 and flunk + "bar".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_5) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + end + end + + def test_implicit_parameter_invalid_name + message_pattern = /is not an implicit parameter/ + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?(:foo) } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get(:foo) } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?("wrong_implicit_parameter_name_#{rand(10000)}") } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get("wrong_implicit_parameter_name_#{rand(10000)}") } + end + def test_local_variable_set_wb assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 30) b = binding diff --git a/test/mri/ruby/test_process.rb b/test/mri/ruby/test_process.rb index af72053234f..857ceab6762 100644 --- a/test/mri/ruby/test_process.rb +++ b/test/mri/ruby/test_process.rb @@ -58,6 +58,8 @@ def rlimit_exist? 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 @@ def test_rlimit_name } 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 @@ def test_overwrite_ENV end; end - MANDATORY_ENVS = %w[RUBYLIB RJIT_SEARCH_BUILD_DIR] - case RbConfig::CONFIG['target_os'] - when /linux/ - MANDATORY_ENVS << 'LD_PRELOAD' - when /mswin|mingw/ - MANDATORY_ENVS.concat(%w[HOME USER TMPDIR]) - 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) @@ -1468,15 +1476,6 @@ def test_status assert_equal(s, s) assert_equal(s, s.to_i) - assert_deprecated_warn(/\buse .*Process::Status/) do - assert_equal(s.to_i & 0x55555555, s & 0x55555555) - end - assert_deprecated_warn(/\buse .*Process::Status/) do - assert_equal(s.to_i >> 1, s >> 1) - end - assert_raise(ArgumentError) do - s >> -1 - end assert_equal(false, s.stopped?) assert_equal(nil, s.stopsig) @@ -1691,9 +1690,10 @@ def test_uid_from_name 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 @@ -1702,12 +1702,7 @@ def test_gid_from_name if g = Etc.getgrgid(Process.gid) assert_equal(Process.gid, Process::GID.from_name(g.name), g.name) end - expected_excs = [ArgumentError] - expected_excs << Errno::ENOENT if defined?(Errno::ENOENT) - expected_excs << Errno::ESRCH if defined?(Errno::ESRCH) # WSL 2 actually raises Errno::ESRCH - expected_excs << Errno::EBADF if defined?(Errno::EBADF) - expected_excs << Errno::EPERM if defined?(Errno::EPERM) - exc = assert_raise(*expected_excs) do + exc = assert_raise_kind_of(ArgumentError, SystemCallError) do Process::GID.from_name("\u{4e0d 5b58 5728}") # fu son zai ("absent" in Kanji) end assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError) @@ -1762,11 +1757,7 @@ def test_wait_and_sigchild end assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable' end - if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # checking -DRJIT_FORCE_ENABLE. It may trigger extra SIGCHLD. - assert_equal [true], signal_received.uniq, "[ruby-core:19744]" - else - assert_equal [true], signal_received, "[ruby-core:19744]" - end + assert_equal [true], signal_received, "[ruby-core:19744]" rescue NotImplementedError, ArgumentError ensure begin @@ -1776,15 +1767,12 @@ def test_wait_and_sigchild 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 = $? @@ -1818,9 +1806,6 @@ def test_spawn_too_long_path 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 @@ -2793,7 +2778,9 @@ def test_warmup_frees_pages 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_slots)) + assert_equal(0, GC.stat(:heap_empty_pages)) assert_operator(GC.stat(:total_freed_pages), :>, 0) end; diff --git a/test/mri/ruby/test_ractor.rb b/test/mri/ruby/test_ractor.rb new file mode 100644 index 00000000000..6ae511217ac --- /dev/null +++ b/test/mri/ruby/test_ractor.rb @@ -0,0 +1,229 @@ +# 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 #", exception: Ractor::Error) + + x = str.instance_exec { method(:to_s).to_proc } + assert_unshareable(x, "can not make shareable object for #", exception: Ractor::Error) + + x = str.instance_exec { method(:itself).to_proc } + assert_unshareable(x, "can not make shareable object for #", 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 #", exception: Ractor::Error) + + x = str.instance_exec { method(:to_s).to_proc } + assert_unshareable(x, "can not make shareable object for #", exception: Ractor::Error) + + x = str.instance_exec { method(:itself).to_proc } + assert_unshareable(x, "can not make shareable object for #", exception: Ractor::Error) +=end + 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 # because it refers unshareable objects", exception: Ractor::Error) + 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_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', 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 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/mri/ruby/test_range.rb b/test/mri/ruby/test_range.rb index 480f213029e..ff17dca69ee 100644 --- a/test/mri/ruby/test_range.rb +++ b/test/mri/ruby/test_range.rb @@ -36,6 +36,7 @@ def test_range_string 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 @@ def test_max 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) @@ -594,6 +597,22 @@ def test_step_ruby_core_35753 assert_equal(4, (1.0...5.6).step(1.5).to_a.size) end + def test_step_with_nonnumeric_endpoint + num = Data.define(:value) do + def coerce(o); [o, 100]; end + def <=>(o) value<=>o; end + def +(o) with(value: value + o) end + end + i = num.new(100) + + assert_equal([100], (100..100).step(10).to_a) + assert_equal([], (100...100).step(10).to_a) + assert_equal([100], (100..i).step(10).to_a) + assert_equal([i], (i..100).step(10).to_a) + assert_equal([], (100...i).step(10).to_a) + assert_equal([], (i...100).step(10).to_a) + end + def test_each a = [] (0..10).each {|x| a << x } @@ -854,16 +873,20 @@ def test_begin_end 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) @@ -1435,6 +1458,12 @@ def test_to_a 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 @@ -1491,6 +1520,7 @@ def test_overlap? 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/mri/ruby/test_rational.rb b/test/mri/ruby/test_rational.rb index 89bb7b20a8c..e0edbde4637 100644 --- a/test/mri/ruby/test_rational.rb +++ b/test/mri/ruby/test_rational.rb @@ -117,9 +117,13 @@ def test_conv assert_equal(Rational(111, 1000), Rational('1.11e-1')) 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?) } diff --git a/test/mri/ruby/test_refinement.rb b/test/mri/ruby/test_refinement.rb index 6ce434790be..209e55294b1 100644 --- a/test/mri/ruby/test_refinement.rb +++ b/test/mri/ruby/test_refinement.rb @@ -1933,6 +1933,29 @@ module PublicCows 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 @@ -2712,6 +2735,30 @@ def test 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 + private def eval_using(mod, s) diff --git a/test/mri/ruby/test_regexp.rb b/test/mri/ruby/test_regexp.rb index a4e9d7ec8e2..2d7a67dd549 100644 --- a/test/mri/ruby/test_regexp.rb +++ b/test/mri/ruby/test_regexp.rb @@ -999,6 +999,18 @@ def test_regsub_K 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_ignorecase v = assert_deprecated_warning(/variable \$= is no longer effective/) { $= } assert_equal(false, v) @@ -1024,10 +1036,12 @@ def test_match_without_regexp [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 +1310,9 @@ def test_posix_bracket 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 +1516,120 @@ def test_unicode_age_15_0 "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 @@ -1612,6 +1743,33 @@ def test_conditional_expression 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)(? nil}, "-vve", ""]) do |r, e| assert_match(VERSION_PATTERN, r[0]) - if self.class.rjit_enabled? && !JITSupport.rjit_force_enabled? - assert_equal(NO_JIT_DESCRIPTION, r[0]) - elsif self.class.yjit_enabled? && !JITSupport.yjit_force_enabled? + if (JITSupport.yjit_enabled? && !JITSupport.yjit_force_enabled?) || JITSupport.zjit_enabled? assert_equal(NO_JIT_DESCRIPTION, r[0]) else assert_equal(RUBY_DESCRIPTION, r[0]) @@ -212,15 +199,12 @@ def test_enable assert_in_out_err(%w(--enable all -e) + [""], "", [], []) assert_in_out_err(%w(--enable-all -e) + [""], "", [], []) assert_in_out_err(%w(--enable=all -e) + [""], "", [], []) - elsif JITSupport.rjit_supported? - # Avoid failing tests by RJIT warnings - assert_in_out_err(%w(--enable all --disable rjit -e) + [""], "", [], []) - assert_in_out_err(%w(--enable-all --disable-rjit -e) + [""], "", [], []) - assert_in_out_err(%w(--enable=all --disable=rjit -e) + [""], "", [], []) end assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [], /unknown argument for --enable: 'foobarbazqux'/) assert_in_out_err(%w(--enable), "", [], /missing argument for --enable/) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: true) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: nil) end def test_disable @@ -230,7 +214,7 @@ def test_disable assert_in_out_err(%w(--disable foobarbazqux -e) + [""], "", [], /unknown argument for --disable: 'foobarbazqux'/) assert_in_out_err(%w(--disable), "", [], /missing argument for --disable/) - assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], []) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", ["nil"], [], gems: false) assert_in_out_err(%w(--disable-did_you_mean -e) + ['p defined? DidYouMean'], "", ["nil"], []) assert_in_out_err(%w(-e) + ['p defined? DidYouMean'], "", ["nil"], []) end @@ -258,7 +242,7 @@ def test_version assert_match(VERSION_PATTERN, r[0]) if ENV['RUBY_YJIT_ENABLE'] == '1' assert_equal(NO_JIT_DESCRIPTION, r[0]) - elsif self.class.rjit_enabled? || self.class.yjit_enabled? # checking -D(M|Y)JIT_FORCE_ENABLE + elsif JITSupport.yjit_enabled? || JITSupport.zjit_enabled? # checking -DYJIT_FORCE_ENABLE assert_equal(EnvUtil.invoke_ruby(['-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0]) else assert_equal(RUBY_DESCRIPTION, r[0]) @@ -267,46 +251,6 @@ def test_version end end - def test_rjit_disabled_version - return unless JITSupport.rjit_supported? - return if JITSupport.yjit_force_enabled? - - env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children - [ - %w(--version --rjit --disable=rjit), - %w(--version --enable=rjit --disable=rjit), - %w(--version --enable-rjit --disable-rjit), - ].each do |args| - assert_in_out_err([env] + args) do |r, e| - assert_match(VERSION_PATTERN, r[0]) - assert_match(NO_JIT_DESCRIPTION, r[0]) - assert_equal([], e) - end - end - end - - def test_rjit_version - return unless JITSupport.rjit_supported? - return if JITSupport.yjit_force_enabled? - - env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children - [ - %w(--version --rjit), - %w(--version --enable=rjit), - %w(--version --enable-rjit), - ].each do |args| - assert_in_out_err([env] + args) do |r, e| - assert_match(VERSION_PATTERN_WITH_RJIT, r[0]) - if JITSupport.rjit_force_enabled? - assert_equal(RUBY_DESCRIPTION, r[0]) - else - assert_equal(EnvUtil.invoke_ruby([env, '--rjit', '-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0]) - end - assert_equal([], e) - end - end - end - def test_enabled_gc omit unless /linux|darwin/ =~ RUBY_PLATFORM @@ -318,6 +262,8 @@ def test_enabled_gc 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"/, []) @@ -441,11 +387,15 @@ def test_invalid_option assert_in_out_err(%W(-\r -e) + [""], "", [], []) - assert_in_out_err(%W(-\rx), "", [], /invalid option -[\r\n] \(-h will show valid options\) \(RuntimeError\)/) + assert_in_out_err(%W(-\rx), "", [], /invalid option -\\r \(-h will show valid options\) \(RuntimeError\)/) - assert_in_out_err(%W(-\x01), "", [], /invalid option -\x01 \(-h will show valid options\) \(RuntimeError\)/) + assert_in_out_err(%W(-\x01), "", [], /invalid option -\\x01 \(-h will show valid options\) \(RuntimeError\)/) assert_in_out_err(%w(-Z), "", [], /invalid option -Z \(-h will show valid options\) \(RuntimeError\)/) + + assert_in_out_err(%W(-\u{1f608}), "", [], + /invalid option -(\\xf0|\u{1f608}) \(-h will show valid options\) \(RuntimeError\)/, + encoding: Encoding::UTF_8) end def test_rubyopt @@ -573,6 +523,8 @@ def test_sflag 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 @@ -839,14 +791,22 @@ module SEGVTest unless /mswin|mingw/ =~ RUBY_PLATFORM opts[:rlimit_core] = 0 end + opts[:failed] = proc do |status, message = "", out = ""| + if (sig = status.termsig) && Signal.list["SEGV"] == sig + out = "" + end + Test::Unit::CoreAssertions::FailDesc[status, message] + end ExecOptions = opts.freeze + # The regexp list that should match the entire stderr output. + # see assert_pattern_list ExpectedStderrList = [ %r( - -e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n + (?:-e:(?:1:)?\s)?\[BUG\]\sSegmentation\sfault.*\n )x, %r( - #{ Regexp.quote((TestRubyOptions.rjit_enabled? && !JITSupport.rjit_force_enabled?) ? NO_JIT_DESCRIPTION : RUBY_DESCRIPTION) }\n\n + #{ Regexp.quote(RUBY_DESCRIPTION) }\n\n )x, %r( (?:--\s(?:.+\n)*\n)? @@ -886,20 +846,24 @@ module SEGVTest 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) @@ -912,13 +876,12 @@ def test_segv_test 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 @@ -931,8 +894,6 @@ def test_segv_setproctitle 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 @@ -986,6 +947,27 @@ def test_crash_report_pipe 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" @@ -1032,7 +1014,7 @@ def test_script_from_stdin 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 } @@ -1328,17 +1310,12 @@ def test_free_at_exit_env_var end def test_toplevel_ruby - reserved = ["", [], /::Ruby is reserved/] - env = {"RUBYOPT"=>""} - args = %w[-e Ruby=1] - assert_in_out_err([env, *args]) - assert_in_out_err([env, "-w", *args], *reserved) - assert_in_out_err([env, "-W:deprecated", *args], *reserved) - assert_in_out_err([env, "-w", "-W:no-deprecated", *args]) - - args = ["-e", "class A; Ruby=1; end"] - assert_in_out_err([env, *args]) - assert_in_out_err([env, "-w", *args]) - assert_in_out_err([env, "-W:deprecated", *args]) + 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/mri/ruby/test_set.rb b/test/mri/ruby/test_set.rb index af5f65bea0f..70a61aa3b5d 100644 --- a/test/mri/ruby/test_set.rb +++ b/test/mri/ruby/test_set.rb @@ -3,8 +3,11 @@ 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] @@ -81,20 +84,6 @@ def test_s_new s = Set.new(ary) { |o| o * 2 } assert_equal([2,4,6], s.sort) - - assert_raise(ArgumentError) { - Set.new((1..)) - } - assert_raise(ArgumentError) { - Set.new((1..), &:succ) - } - assert_raise(ArgumentError) { - Set.new(1.upto(Float::INFINITY)) - } - - assert_raise(ArgumentError) { - Set.new(Object.new) - } end def test_clone @@ -278,7 +267,7 @@ def test_superset? 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) @@ -307,7 +296,7 @@ def test_proper_superset? 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) @@ -336,7 +325,7 @@ def test_subset? 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) @@ -365,7 +354,7 @@ def test_proper_subset? 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) @@ -385,7 +374,7 @@ def test_spacecraft_operator 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) @@ -701,15 +690,28 @@ def test_and 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 - set2 = Set2[1,2,3,4] - ret2 = set2 ^ [2,4,5,5] - assert_instance_of(Set2, ret2) - assert_equal(Set2[1,3,5], ret2) + def test_xor_does_not_mutate_other_set + a = Set[1] + b = Set[1, 2] + original_b = b.dup + + result = a ^ b + + assert_equal(original_b, b) + assert_equal(Set[2], result) end def test_eq @@ -861,9 +863,13 @@ def test_inspect set1.add(set2) assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.inspect) - c = Class.new(Set) + 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 @@ -936,6 +942,27 @@ def test_larger_sets 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 @@ -953,6 +980,36 @@ def test_to_set assert_same set, set.to_set assert_not_same set, set.to_set { |o| o } 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 class TC_Set_Builtin < Test::Unit::TestCase diff --git a/test/mri/ruby/test_settracefunc.rb b/test/mri/ruby/test_settracefunc.rb index 37358757a67..776534a2b54 100644 --- a/test/mri/ruby/test_settracefunc.rb +++ b/test/mri/ruby/test_settracefunc.rb @@ -845,6 +845,9 @@ def test_tracepoint_disable 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 @@ def m 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{ @@ -2262,9 +2265,6 @@ def test_thread_add_trace_func } # it is dirty hack. usually we shouldn't use such technique Thread.pass until t.status == 'sleep' - # When RJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop. - # This sleep forces it to reach m2t_q.pop for --jit-wait. - sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? t.add_trace_func proc{|ev, file, line, *args| if file == __FILE__ @@ -2724,7 +2724,7 @@ def obj.example end def test_disable_local_tracepoint_in_trace - assert_normal_exit <<-EOS + assert_normal_exit(<<-EOS, timeout: 60) def foo trace = TracePoint.new(:b_return){|tp| tp.disable @@ -2957,4 +2957,210 @@ def test_tracepoint_thread_end_with_exception 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 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/mri/ruby/test_shapes.rb b/test/mri/ruby/test_shapes.rb index 9b025043844..453ca8f6a72 100644 --- a/test/mri/ruby/test_shapes.rb +++ b/test/mri/ruby/test_shapes.rb @@ -2,6 +2,7 @@ require 'test/unit' require 'objspace' require 'json' +require 'securerandom' # These test the functionality of object shapes class TestShapes < Test::Unit::TestCase @@ -92,15 +93,18 @@ def write_iv # 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.id, parent_id: e.parent_id, depth: e.depth, type: e.type}, + {id: a.id, parent_id: a.parent_id, depth: a.depth, type: a.type}, + ) end - def refute_shape_equal(shape1, shape2) - refute_equal(shape1.id, shape2.id) + def refute_shape_equal(e, a) + refute_equal( + {id: e.id, parent_id: e.parent_id, depth: e.depth, type: e.type}, + {id: a.id, parent_id: a.parent_id, depth: a.depth, type: a.type}, + ) end def test_iv_order_correct_on_complex_objects @@ -146,11 +150,14 @@ class Hi; 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), :too_complex? + + MANY_IVS.times do obj.instance_variable_set(:"@a#{_1}", 1) end - assert_false RubyVM::Shape.of(obj).too_complex? + refute_predicate RubyVM::Shape.of(obj), :too_complex? end def test_removing_when_too_many_ivs_on_class @@ -596,8 +603,8 @@ class TooComplex assert_predicate RubyVM::Shape.of(tc), :too_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 @@ -622,6 +629,97 @@ class TooComplex end; end + def test_too_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, :too_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, :too_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_too_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_too_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, :too_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, :too_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, :too_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_too_complex_obj_ivar_ractor_share assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; @@ -632,10 +730,10 @@ def test_too_complex_obj_ivar_ractor_share 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 @@ -650,10 +748,10 @@ def test_too_complex_generic_ivar_ractor_share 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 @@ -721,9 +819,42 @@ def test_delete_iv_after_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), :too_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), :too_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 @@ -786,13 +917,15 @@ def test_remove_instance_variable_when_out_of_shapes 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 @@ -852,13 +985,13 @@ def test_shape_order 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.raw_id, add_foo_shape.parent.raw_id) + assert_equal(1, add_foo_shape.next_field_index) example.remove_foo # makes a transition remove_foo_shape = RubyVM::Shape.of(example) @@ -868,8 +1001,8 @@ def test_iv_index 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.raw_id, bar_shape.parent_id) + assert_equal(1, bar_shape.next_field_index) end def test_remove_then_add_again @@ -888,7 +1021,7 @@ class TestObject; end 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,20 +1033,32 @@ def test_array_has_root_shape 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.raw_id) end def test_basic_shape_transition - omit "Failing with RJIT for some reason" if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? obj = Example.new shape = RubyVM::Shape.of(obj) refute_equal(RubyVM::Shape.root_shape, shape) @@ -921,7 +1066,7 @@ def test_basic_shape_transition 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)) @@ -956,11 +1101,12 @@ def test_duplicating_too_complex_objects_memory_leak 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 @@ -977,6 +1123,7 @@ def test_freezing_and_duplicating_string_with_ivars 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 @@ -993,8 +1140,7 @@ def test_cloning_with_freeze_option 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) + assert_predicate(RubyVM::Shape.of(obj2), :shape_frozen?) end def test_freezing_and_cloning_object_with_ivars @@ -1037,4 +1183,30 @@ def ensure_complex tc.send("a#{_1}_m") end end + + def assert_too_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), :too_complex? + (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i| + obj.remove_instance_variable("@ivar#{i}") + end + assert_predicate RubyVM::Shape.of(obj), :too_complex? + end + + def test_object_too_complex_during_delete + assert_too_complex_during_delete(Class.new.new) + end + + def test_class_too_complex_during_delete + assert_too_complex_during_delete(Module.new) + end + + def test_generic_too_complex_during_delete + assert_too_complex_during_delete(Class.new(Array).new) + end end if defined?(RubyVM::Shape) diff --git a/test/mri/ruby/test_string.rb b/test/mri/ruby/test_string.rb index d2099607fd4..2458d38ef4b 100644 --- a/test/mri/ruby/test_string.rb +++ b/test/mri/ruby/test_string.rb @@ -872,6 +872,10 @@ def test_undump 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 } @@ -1869,6 +1873,13 @@ def test_split_with_block 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 +1887,24 @@ def test_split_with_block 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 +2053,117 @@ def test_strip! 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("hllo"), S("hello").sub(/([aeiou])/, S('<\1>'))) @@ -2462,33 +2599,7 @@ def test_unpack 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 @@ -2780,14 +2891,21 @@ def (hyphen = Object.new).to_str; "-"; end 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 +2956,45 @@ def test_substr_negative_begin 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 +3007,16 @@ def test_casecmp? 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 +3089,6 @@ def test_lstrip_bang s5 = S("\u0000\u3042") assert_equal("\u3042", s5.lstrip!) assert_equal("\u3042", s5) - end def test_delete_prefix_type_error @@ -3246,18 +3388,12 @@ def test_ascii_incomat_inspect 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,6 +3450,11 @@ def test_byteslice 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) @@ -3397,6 +3538,12 @@ def test_uplus_minus 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 +3578,17 @@ def test_uminus_no_freeze_not_bare 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 +3895,96 @@ def test_chilled_string_substring 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 +4031,10 @@ def assert_byteindex(expected, string, match, *rest) 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/mri/ruby/test_struct.rb b/test/mri/ruby/test_struct.rb index 3d727adf04e..01e5cc68f6e 100644 --- a/test/mri/ruby/test_struct.rb +++ b/test/mri/ruby/test_struct.rb @@ -535,6 +535,8 @@ def test_parameters 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) code = proc do @@ -542,12 +544,18 @@ def test_named_structs_are_not_rooted 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/mri/ruby/test_super.rb b/test/mri/ruby/test_super.rb index 8e973b0f7f4..25bad2242af 100644 --- a/test/mri/ruby/test_super.rb +++ b/test/mri/ruby/test_super.rb @@ -759,4 +759,19 @@ def initialize inherited = inherited_class.new assert_equal 2, inherited.test # it may read index=1 while it should be index=2 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/mri/ruby/test_syntax.rb b/test/mri/ruby/test_syntax.rb index 62f1d99bdc8..94a2e03940b 100644 --- a/test/mri/ruby/test_syntax.rb +++ b/test/mri/ruby/test_syntax.rb @@ -1259,6 +1259,56 @@ def test_fluent_dot assert_valid_syntax("a #\n#\n&.foo\n") end + def test_fluent_and + omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION + + 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 + omit if /\+PRISM\b/ =~ RUBY_DESCRIPTION + + 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/) @@ -1794,15 +1844,12 @@ def test_methoddef_endless_command 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 @@ -1946,6 +1993,28 @@ def test_it 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 def test_value_expr_in_condition @@ -2018,10 +2087,11 @@ def obj1.bar(*args, **kws, &block) 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 +2120,7 @@ def obj3.bar(*args, &block) 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}) } diff --git a/test/mri/ruby/test_thread.rb b/test/mri/ruby/test_thread.rb index 6620ccbf336..c08d41cb863 100644 --- a/test/mri/ruby/test_thread.rb +++ b/test/mri/ruby/test_thread.rb @@ -243,6 +243,10 @@ def test_join2 def test_join_argument_conversion t = Thread.new {} + + # Make sure that the thread terminates + Thread.pass while t.status + assert_raise(TypeError) {t.join(:foo)} limit = Struct.new(:to_f, :count).new(0.05) @@ -323,7 +327,6 @@ def test_wakeup s += 1 end Thread.pass until t.stop? - sleep 1 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? # t.stop? behaves unexpectedly with --jit-wait assert_equal(1, s) t.wakeup Thread.pass while t.alive? @@ -1479,9 +1482,6 @@ def test_thread_native_thread_id_across_fork_on_linux def test_thread_interrupt_for_killed_thread opts = { timeout: 5, timeout_error: nil } - # prevent SIGABRT from slow shutdown with RJIT - opts[:reprieve] = 3 if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? - assert_normal_exit(<<-_end, '[Bug #8996]', **opts) Thread.report_on_exception = false trap(:TERM){exit} @@ -1557,4 +1557,96 @@ def test_pending_interrupt? assert_equal(true, t.pending_interrupt?(Exception)) assert_equal(false, t.pending_interrupt?(ArgumentError)) end + + def test_deadlock_backtrace + bug21127 = '[ruby-core:120930] [Bug #21127]' + + expected_stderr = [ + /-:12:in 'Thread#join': No live threads left. Deadlock\? \(fatal\)\n/, + /2 threads, 2 sleeps current:\w+ main thread:\w+\n/, + /\* #\n/, + :*, + /^\s*-:6:in 'Object#frame_for_deadlock_test_2'/, + :*, + /\* #\n/, + :*, + /^\s*-:2:in 'Object#frame_for_deadlock_test_1'/, + :*, + ] + + assert_in_out_err([], <<-INPUT, [], expected_stderr, bug21127) + def frame_for_deadlock_test_1 + yield + end + + def frame_for_deadlock_test_2 + yield + end + + q = Thread::Queue.new + t = Thread.new { frame_for_deadlock_test_1 { q.pop } } + + frame_for_deadlock_test_2 { t.join } + INPUT + end + + # [Bug #21342] + def test_unlock_locked_mutex_with_collected_fiber + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + 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_separately([], "#{<<~"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_separately([], "#{<<~"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 end diff --git a/test/mri/ruby/test_thread_cv.rb b/test/mri/ruby/test_thread_cv.rb index eb88b9606ce..81201f134f0 100644 --- a/test/mri/ruby/test_thread_cv.rb +++ b/test/mri/ruby/test_thread_cv.rb @@ -76,7 +76,7 @@ def test_condvar_wait_and_broadcast 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/mri/ruby/test_thread_queue.rb b/test/mri/ruby/test_thread_queue.rb index 545bf98888b..9485528977e 100644 --- a/test/mri/ruby/test_thread_queue.rb +++ b/test/mri/ruby/test_thread_queue.rb @@ -235,8 +235,14 @@ def test_thr_kill 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 diff --git a/test/mri/ruby/test_time_tz.rb b/test/mri/ruby/test_time_tz.rb index f66cd9bec2e..473c3cabcb4 100644 --- a/test/mri/ruby/test_time_tz.rb +++ b/test/mri/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/mri/ruby/test_transcode.rb b/test/mri/ruby/test_transcode.rb index 63d37f4ba4f..15e290728be 100644 --- a/test/mri/ruby/test_transcode.rb +++ b/test/mri/ruby/test_transcode.rb @@ -2320,6 +2320,93 @@ def test_newline_options 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 rs.empty? + 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;'}") + 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 rs.empty? + 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 rs.empty? + 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 rs.empty? + end; + end + private def assert_conversion_both_ways_utf8(utf8, raw, encoding) diff --git a/test/mri/ruby/test_variable.rb b/test/mri/ruby/test_variable.rb index 49fec2d40e9..13b8a7905f2 100644 --- a/test/mri/ruby/test_variable.rb +++ b/test/mri/ruby/test_variable.rb @@ -388,6 +388,61 @@ def test_special_constant_ivars 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 +462,21 @@ def test_external_ivars } 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 +496,55 @@ def test_many_instance_variables 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/mri/ruby/test_vm_dump.rb b/test/mri/ruby/test_vm_dump.rb index 709fd5eadf5..d183e033915 100644 --- a/test/mri/ruby/test_vm_dump.rb +++ b/test/mri/ruby/test_vm_dump.rb @@ -5,8 +5,7 @@ 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 @@ def test_darwin_invalid_call 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/mri/ruby/test_yjit.rb b/test/mri/ruby/test_yjit.rb index 0e476588f49..2096585451c 100644 --- a/test/mri/ruby/test_yjit.rb +++ b/test/mri/ruby/test_yjit.rb @@ -142,6 +142,36 @@ def test_yjit_enable_with_monkey_patch 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") @@ -1316,7 +1346,7 @@ def +(x) = self - -x 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 +1534,6 @@ def test = $stderr.to_i RUBY end - def test_opt_aref_with - assert_compiles(<<~RUBY, insns: %i[opt_aref_with], result: "bar", frozen_string_literal: false) - h = {"foo" => "bar"} - - h["foo"] - RUBY - end - def test_proc_block_arg assert_compiles(<<~RUBY, result: [:proc, :no_block]) def yield_if_given = block_given? ? yield : :no_block @@ -1637,7 +1659,7 @@ def test = :ok [ stats[:object_shape_count].is_a?(Integer), - stats[:ratio_in_yjit].is_a?(Float), + stats[:ratio_in_yjit].nil? || stats[:ratio_in_yjit].is_a?(Float), ].all? RUBY end @@ -1648,7 +1670,7 @@ def test = :ok 3.times { test } # Collect single stat. - stat = RubyVM::YJIT.runtime_stats(:ratio_in_yjit) + stat = RubyVM::YJIT.runtime_stats(:yjit_alloc_size) # Ensure this invocation had stats. return true unless RubyVM::YJIT.runtime_stats[:all_stats] @@ -1750,6 +1772,18 @@ def req2kws = yield a: 1, b: 2 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 + private def code_gc_helpers diff --git a/test/mri/ruby/test_zjit.rb b/test/mri/ruby/test_zjit.rb new file mode 100644 index 00000000000..805ecb98b20 --- /dev/null +++ b/test/mri/ruby/test_zjit.rb @@ -0,0 +1,3803 @@ +# 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_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=, 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_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_call_itself + assert_compiles '42', <<~RUBY, call_threshold: 2 + def test = 42.itself + test + test + RUBY + end + + def test_nil + assert_compiles 'nil', %q{ + def test = nil + test + } + end + + def test_putobject + assert_compiles '1', %q{ + def test = 1 + test + } + end + + def test_putstring + assert_compiles '""', %q{ + def test = "#{""}" + test + }, insns: [:putstring] + end + + def test_putchilldedstring + assert_compiles '""', %q{ + def test = "" + test + }, insns: [:putchilledstring] + end + + def test_leave_param + assert_compiles '5', %q{ + def test(n) = n + test(5) + } + end + + def test_getglobal_with_warning + assert_compiles('"rescued"', %q{ + Warning[:deprecated] = true + + module Warning + def warn(message) + raise + end + end + + def test + $= + rescue + "rescued" + end + + $VERBOSE = true + test + }, insns: [:getglobal]) + end + + def test_setglobal + assert_compiles '1', %q{ + def test + $a = 1 + $a + end + + test + }, insns: [:setglobal] + end + + def test_string_intern + assert_compiles ':foo123', %q{ + def test + :"foo#{123}" + end + + test + }, insns: [:intern] + end + + def test_duphash + assert_compiles '{a: 1}', %q{ + def test + {a: 1} + end + + test + }, insns: [:duphash] + end + + def test_pushtoarray + assert_compiles '[1, 2, 3]', %q{ + def test + [*[], 1, 2, 3] + end + test + }, insns: [:pushtoarray] + end + + def test_splatarray_new_array + assert_compiles '[1, 2, 3]', %q{ + def test a + [*a, 3] + end + test [1, 2] + }, insns: [:splatarray] + end + + def test_splatarray_existing_array + assert_compiles '[1, 2, 3]', %q{ + def foo v + [1, 2, v] + end + def test a + foo(*a) + end + test [3] + }, insns: [:splatarray] + end + + def test_concattoarray + assert_compiles '[1, 2, 3]', %q{ + def test(*a) + [1, 2, *a] + end + test 3 + }, insns: [:concattoarray] + end + + def test_definedivar + assert_compiles '[nil, "instance-variable", nil]', %q{ + def test + v0 = defined?(@a) + @a = nil + v1 = defined?(@a) + remove_instance_variable :@a + v2 = defined?(@a) + [v0, v1, v2] + end + test + }, insns: [:definedivar] + end + + def test_setglobal_with_trace_var_exception + assert_compiles '"rescued"', %q{ + def test + $a = 1 + rescue + "rescued" + end + + trace_var(:$a) { raise } + test + }, insns: [:setglobal] + 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_toplevel_local_after_eval + # 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 + eval('b = 3') + c = 4 + print [a, b, c] + }) + assert_success(out, err, status) + assert_equal "[1, 3, 4]", out + end + + def test_getlocal_after_eval + assert_compiles '2', %q{ + def test + a = 1 + eval('a = 2') + a + end + test + } + end + + def test_getlocal_after_instance_eval + assert_compiles '2', %q{ + def test + a = 1 + instance_eval('a = 2') + a + end + test + } + end + + def test_getlocal_after_module_eval + assert_compiles '2', %q{ + def test + a = 1 + Kernel.module_eval('a = 2') + a + end + test + } + end + + def test_getlocal_after_class_eval + assert_compiles '2', %q{ + def test + a = 1 + Kernel.class_eval('a = 2') + a + end + test + } + end + + def test_setlocal + assert_compiles '3', %q{ + def test(n) + m = n + m + end + test(3) + } + end + + def test_return_nonparam_local + # Use dead code (if false) to create a local without initialization instructions. + assert_compiles 'nil', %q{ + def foo(a) + if false + x = nil + end + x + end + def test = foo(1) + test + test + }, call_threshold: 2 + end + + def test_nonparam_local_nil_in_jit_call + # Non-parameter locals must be initialized to nil in JIT-to-JIT calls. + # Use dead code (if false) to create locals without initialization instructions. + # Then eval a string that accesses the uninitialized locals. + assert_compiles '["x", "x", "x", "x"]', %q{ + def f(a) + a ||= 1 + if false; b = 1; end + eval("-> { p 'x#{b}' }") + end + + 4.times.map { f(1).call } + }, call_threshold: 2 + end + + def test_setlocal_on_eval + assert_compiles '1', %q{ + @b = binding + eval('a = 1', @b) + eval('a', @b) + } + end + + def test_optional_arguments + assert_compiles '[[1, 2, 3], [10, 20, 3], [100, 200, 300]]', %q{ + def test(a, b = 2, c = 3) + [a, b, c] + end + [test(1), test(10, 20), test(100, 200, 300)] + } + end + + def test_optional_arguments_setlocal + assert_compiles '[[2, 2], [1, nil]]', %q{ + def test(a = (b = 2)) + [a, b] + end + [test, test(1)] + } + end + + def test_optional_arguments_cyclic + assert_compiles '[nil, 1]', %q{ + test = proc { |a=a| a } + [test.call, test.call(1)] + } + end + + def test_optional_arguments_side_exit + # This leads to FailedOptionalArguments, so not using assert_compiles + assert_runs '[:foo, nil, 1]', %q{ + def test(a = (def foo = nil)) = a + [test, (undef :foo), test(1)] + } + end + + def test_getblockparamproxy + assert_compiles '1', %q{ + def test(&block) + 0.then(&block) + end + test { 1 } + }, insns: [:getblockparamproxy] + end + + def test_call_a_forwardable_method + assert_runs '[]', %q{ + def test_root = forwardable + def forwardable(...) = Array.[](...) + test_root + test_root + }, call_threshold: 2 + end + + def test_setlocal_on_eval_with_spill + assert_compiles '1', %q{ + @b = binding + eval('a = 1; itself', @b) + eval('a', @b) + } + end + + def test_nested_local_access + assert_compiles '[1, 2, 3]', %q{ + 1.times do |l2| + 1.times do |l1| + define_method(:test) do + l1 = 1 + l2 = 2 + l3 = 3 + [l1, l2, l3] + end + end + end + + test + test + test + }, call_threshold: 3, insns: [:getlocal, :setlocal, :getlocal_WC_0, :setlocal_WC_1] + end + + def test_send_with_local_written_by_blockiseq + assert_compiles '[1, 2]', %q{ + def test + l1 = nil + l2 = nil + tap do |_| + l1 = 1 + tap do |_| + l2 = 2 + end + end + + [l1, l2] + end + + test + test + }, call_threshold: 2 + end + + def test_send_without_block + assert_compiles '[1, 2, 3]', %q{ + def foo = 1 + def bar(a) = a - 1 + def baz(a, b) = a - b + + def test1 = foo + def test2 = bar(3) + def test3 = baz(4, 1) + + [test1, test2, test3] + } + end + + def test_send_with_six_args + assert_compiles '[1, 2, 3, 4, 5, 6]', %q{ + def foo(a1, a2, a3, a4, a5, a6) + [a1, a2, a3, a4, a5, a6] + end + + def test + foo(1, 2, 3, 4, 5, 6) + end + + test # profile send + test + }, call_threshold: 2 + end + + def test_send_on_heap_object_in_spilled_arg + # This leads to a register spill, so not using `assert_compiles` + assert_runs 'Hash', %q{ + def entry(a1, a2, a3, a4, a5, a6, a7, a8, a9) + a9.itself.class + end + + entry(1, 2, 3, 4, 5, 6, 7, 8, {}) # profile + entry(1, 2, 3, 4, 5, 6, 7, 8, {}) + }, call_threshold: 2 + 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_send_optional_arguments + assert_compiles '[[1, 2], [3, 4]]', %q{ + def test(a, b = 2) = [a, b] + def entry = [test(1), test(3, 4)] + entry + entry + }, call_threshold: 2 + end + + def test_send_nil_block_arg + assert_compiles 'false', %q{ + def test = block_given? + def entry = test(&nil) + test + } + end + + def test_send_symbol_block_arg + assert_compiles '["1", "2"]', %q{ + def test = [1, 2].map(&:to_s) + test + } + end + + def test_send_variadic_with_block + assert_compiles '[[1, "a"], [2, "b"], [3, "c"]]', %q{ + A = [1, 2, 3] + B = ["a", "b", "c"] + + def test + result = [] + A.zip(B) { |x, y| result << [x, y] } + result + end + + test; test + }, call_threshold: 2 + end + + def test_send_splat + assert_runs '[1, 2]', %q{ + def test(a, b) = [a, b] + def entry(arr) = test(*arr) + entry([1, 2]) + } + end + + def test_send_kwarg + assert_runs '[1, 2]', %q{ + def test(a:, b:) = [a, b] + def entry = test(b: 2, a: 1) # change order + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_optional + assert_compiles '[1, 2]', %q{ + def test(a: 1, b: 2) = [a, b] + def entry = test + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_optional_too_many + assert_compiles '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]', %q{ + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10) = [a, b, c, d, e, f, g, h, i, j] + def entry = test + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_required_and_optional + assert_compiles '[3, 2]', %q{ + def test(a:, b: 2) = [a, b] + def entry = test(a: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_to_hash + assert_compiles '{a: 3}', %q{ + def test(hash) = hash + def entry = test(a: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_to_ccall + assert_compiles '["a", "b", "c"]', %q{ + def test(s) = s.each_line(chomp: true).to_a + def entry = test(%(a\nb\nc)) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_and_block_to_ccall + assert_compiles '["a", "b", "c"]', %q{ + def test(s) + a = [] + s.each_line(chomp: true) { |l| a << l } + a + end + def entry = test(%(a\nb\nc)) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_with_too_many_args_to_c_call + assert_compiles '"a b c d {kwargs: :e}"', %q{ + def test(a:, b:, c:, d:, e:) = sprintf("%s %s %s %s %s", a, b, c, d, kwargs: e) + def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwsplat + assert_compiles '3', %q{ + def test(a:) = a + def entry = test(**{a: 3}) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwrest + assert_compiles '{a: 3}', %q{ + def test(**kwargs) = kwargs + def entry = test(a: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_req_kwreq + assert_compiles '[1, 3]', %q{ + def test(a, c:) = [a, c] + def entry = test(1, c: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_req_opt_kwreq + assert_compiles '[[1, 2, 3], [-1, -2, -3]]', %q{ + def test(a, b = 2, c:) = [a, b, c] + def entry = [test(1, c: 3), test(-1, -2, c: -3)] # specify all, change kw order + entry + entry + }, call_threshold: 2 + end + + def test_send_req_opt_kwreq_kwopt + assert_compiles '[[1, 2, 3, 4], [-1, -2, -3, -4]]', %q{ + def test(a, b = 2, c:, d: 4) = [a, b, c, d] + def entry = [test(1, c: 3), test(-1, -2, d: -4, c: -3)] # specify all, change kw order + entry + entry + }, call_threshold: 2 + end + + def test_send_unexpected_keyword + assert_compiles ':error', %q{ + def test(a: 1) = a*2 + def entry + test(z: 2) + rescue ArgumentError + :error + end + + entry + entry + }, call_threshold: 2 + end + + def test_send_all_arg_types + assert_compiles '[:req, :opt, :post, :kwr, :kwo, true]', %q{ + def test(a, b = :opt, c, d:, e: :kwo) = [a, b, c, d, e, block_given?] + def entry = test(:req, :post, d: :kwr) {} + entry + entry + }, call_threshold: 2 + end + + def test_send_ccall_variadic_with_different_receiver_classes + assert_compiles '[true, true]', %q{ + def test(obj) = obj.start_with?("a") + [test("abc"), test(:abc)] + }, call_threshold: 2 + end + + def test_forwardable_iseq + assert_compiles '1', %q{ + def test(...) = 1 + test + } + end + + def test_sendforward + assert_compiles '[1, 2]', %q{ + def callee(a, b) = [a, b] + def test(...) = callee(...) + test(1, 2) + }, insns: [:sendforward] + end + + def test_iseq_with_optional_arguments + assert_compiles '[[1, 2], [3, 4]]', %q{ + def test(a, b = 2) = [a, b] + [test(1), test(3, 4)] + } + end + + def test_invokesuper + assert_compiles '[6, 60]', %q{ + class Foo + def foo(a) = a + 1 + def bar(a) = a + 10 + end + + class Bar < Foo + def foo(a) = super(a) + 2 + def bar(a) = super + 20 + end + + bar = Bar.new + [bar.foo(3), bar.bar(30)] + } + end + + def test_invokesuper_with_local_written_by_blockiseq + # Using `assert_runs` because we don't compile invokeblock yet + assert_runs '3', %q{ + class Foo + def test + yield + end + end + + class Bar < Foo + def test + a = 1 + super do + a += 2 + end + a + end + end + + Bar.new.test + } + end + + def test_invokebuiltin + # Not using assert_compiles due to register spill + assert_runs '["."]', %q{ + def test = Dir.glob(".") + test + } + end + + def test_invokebuiltin_delegate + assert_compiles '[[], true]', %q{ + def test = [].clone(freeze: true) + r = test + [r, r.frozen?] + } + end + + def test_opt_plus_const + assert_compiles '3', %q{ + def test = 1 + 2 + test # profile opt_plus + test + }, call_threshold: 2 + end + + def test_opt_plus_fixnum + assert_compiles '3', %q{ + def test(a, b) = a + b + test(0, 1) # profile opt_plus + test(1, 2) + }, call_threshold: 2 + end + + def test_opt_plus_chain + assert_compiles '6', %q{ + def test(a, b, c) = a + b + c + test(0, 1, 2) # profile opt_plus + test(1, 2, 3) + }, call_threshold: 2 + end + + def test_opt_plus_left_imm + assert_compiles '3', %q{ + def test(a) = 1 + a + test(1) # profile opt_plus + test(2) + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_exit + assert_compiles '[3, 3.0]', %q{ + def test(a) = 1 + a + test(1) # profile opt_plus + [test(2), test(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_exit_with_locals + assert_compiles '[6, 6.0]', %q{ + def test(a) + local = 3 + 1 + a + local + end + test(1) # profile opt_plus + [test(2), test(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_nested_exit + assert_compiles '[4, 4.0]', %q{ + def side_exit(n) = 1 + n + def jit_frame(n) = 1 + side_exit(n) + def entry(n) = jit_frame(n) + entry(2) # profile send + [entry(2), entry(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_nested_exit_with_locals + assert_compiles '[9, 9.0]', %q{ + def side_exit(n) + local = 2 + 1 + n + local + end + def jit_frame(n) + local = 3 + 1 + side_exit(n) + local + end + def entry(n) = jit_frame(n) + entry(2) # profile send + [entry(2), entry(2.0)] + }, call_threshold: 2 + end + + # Test argument ordering + def test_opt_minus + assert_compiles '2', %q{ + def test(a, b) = a - b + test(2, 1) # profile opt_minus + test(6, 4) + }, call_threshold: 2 + end + + def test_opt_mult + assert_compiles '6', %q{ + def test(a, b) = a * b + test(1, 2) # profile opt_mult + test(2, 3) + }, call_threshold: 2 + end + + def test_opt_mult_overflow + assert_compiles '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{ + def test(a, b) + a * b + end + test(1, 1) # profile opt_mult + + r1 = test(2, 3) + r2 = test(2, -3) + r3 = test(2 << 40, 2 << 41) + r4 = test(2 << 40, -2 << 41) + r5 = test(1 << 62, 1 << 62) + + [r1, r2, r3, r4, r5] + }, call_threshold: 2 + end + + def test_opt_eq + assert_compiles '[true, false]', %q{ + def test(a, b) = a == b + test(0, 2) # profile opt_eq + [test(1, 1), test(0, 1)] + }, insns: [:opt_eq], call_threshold: 2 + end + + def test_opt_eq_with_minus_one + assert_compiles '[false, true]', %q{ + def test(a) = a == -1 + test(1) # profile opt_eq + [test(0), test(-1)] + }, insns: [:opt_eq], call_threshold: 2 + end + + def test_opt_neq_dynamic + # TODO(max): Don't split this test; instead, run all tests with and without + # profiling. + assert_compiles '[false, true]', %q{ + def test(a, b) = a != b + test(0, 2) # profile opt_neq + [test(1, 1), test(0, 1)] + }, insns: [:opt_neq], call_threshold: 1 + end + + def test_opt_neq_fixnum + assert_compiles '[false, true]', %q{ + def test(a, b) = a != b + test(0, 2) # profile opt_neq + [test(1, 1), test(0, 1)] + }, call_threshold: 2 + end + + def test_opt_lt + assert_compiles '[true, false, false]', %q{ + def test(a, b) = a < b + test(2, 3) # profile opt_lt + [test(0, 1), test(0, 0), test(1, 0)] + }, insns: [:opt_lt], call_threshold: 2 + end + + def test_opt_lt_with_literal_lhs + assert_compiles '[false, false, true]', %q{ + def test(n) = 2 < n + test(2) # profile opt_lt + [test(1), test(2), test(3)] + }, insns: [:opt_lt], call_threshold: 2 + end + + def test_opt_le + assert_compiles '[true, true, false]', %q{ + def test(a, b) = a <= b + test(2, 3) # profile opt_le + [test(0, 1), test(0, 0), test(1, 0)] + }, insns: [:opt_le], call_threshold: 2 + end + + def test_opt_gt + assert_compiles '[false, false, true]', %q{ + def test(a, b) = a > b + test(2, 3) # profile opt_gt + [test(0, 1), test(0, 0), test(1, 0)] + }, insns: [:opt_gt], call_threshold: 2 + end + + def test_opt_empty_p + assert_compiles('[false, false, true]', <<~RUBY, insns: [:opt_empty_p]) + def test(x) = x.empty? + return test([1]), test("1"), test({}) + RUBY + end + + def test_opt_succ + assert_compiles('[0, "B"]', <<~RUBY, insns: [:opt_succ]) + def test(obj) = obj.succ + return test(-1), test("A") + RUBY + end + + def test_opt_and + assert_compiles('[1, [3, 2, 1]]', <<~RUBY, insns: [:opt_and]) + def test(x, y) = x & y + return test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3]) + RUBY + end + + def test_opt_or + assert_compiles('[11, [3, 2, 1]]', <<~RUBY, insns: [:opt_or]) + def test(x, y) = x | y + return test(0b1000, 3), test([3, 2, 1], [1, 2, 3]) + RUBY + end + + def test_fixnum_and + assert_compiles '[1, 2, 4]', %q{ + def test(a, b) = a & b + [ + test(5, 3), + test(0b011, 0b110), + test(-0b011, 0b110) + ] + }, call_threshold: 2, insns: [:opt_and] + end + + def test_fixnum_and_side_exit + assert_compiles '[2, 2, false]', %q{ + def test(a, b) = a & b + [ + test(2, 2), + test(0b011, 0b110), + test(true, false) + ] + }, call_threshold: 2, insns: [:opt_and] + end + + def test_fixnum_or + assert_compiles '[7, 3, -3]', %q{ + def test(a, b) = a | b + [ + test(5, 3), + test(1, 2), + test(1, -4) + ] + }, call_threshold: 2, insns: [:opt_or] + end + + def test_fixnum_or_side_exit + assert_compiles '[3, 2, true]', %q{ + def test(a, b) = a | b + [ + test(1, 2), + test(2, 2), + test(true, false) + ] + }, call_threshold: 2, insns: [:opt_or] + end + + def test_fixnum_xor + assert_compiles '[6, -8, 3]', %q{ + def test(a, b) = a ^ b + [ + test(5, 3), + test(-5, 3), + test(1, 2) + ] + }, call_threshold: 2 + end + + def test_fixnum_xor_side_exit + assert_compiles '[6, 6, true]', %q{ + def test(a, b) = a ^ b + [ + test(5, 3), + test(5, 3), + test(true, false) + ] + }, call_threshold: 2 + end + + def test_fixnum_mul + assert_compiles '12', %q{ + C = 3 + def test(n) = C * n + test(4) + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_mult] + end + + def test_fixnum_div + assert_compiles '12', %q{ + C = 48 + def test(n) = C / n + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_fixnum_floor + assert_compiles '0', %q{ + C = 3 + def test(n) = C / n + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_fixnum_div_zero + assert_runs '"divided by 0"', %q{ + def test(n) + n / 0 + rescue ZeroDivisionError => e + e.message + end + + test(0) + test(0) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_opt_not + assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not]) + def test(obj) = !obj + return test(nil), test(false), test(0) + RUBY + end + + def test_opt_regexpmatch2 + assert_compiles('[1, nil]', <<~RUBY, insns: [:opt_regexpmatch2]) + def test(haystack) = /needle/ =~ haystack + return test("kneedle"), test("") + RUBY + end + + def test_opt_ge + assert_compiles '[false, true, true]', %q{ + def test(a, b) = a >= b + test(2, 3) # profile opt_ge + [test(0, 1), test(0, 0), test(1, 0)] + }, insns: [:opt_ge], call_threshold: 2 + end + + def test_opt_new_does_not_push_frame + assert_compiles 'nil', %q{ + class Foo + attr_reader :backtrace + + def initialize + @backtrace = caller + end + end + def test = Foo.new + + foo = test + foo.backtrace.find do |frame| + frame.include?('Class#new') + end + }, insns: [:opt_new] + end + + def test_opt_new_with_redefined + assert_compiles '"foo"', %q{ + class Foo + def self.new = "foo" + + def initialize = raise("unreachable") + end + def test = Foo.new + + test + }, insns: [:opt_new] + end + + def test_opt_new_invalidate_new + assert_compiles '["Foo", "foo"]', %q{ + class Foo; end + def test = Foo.new + test; test + result = [test.class.name] + def Foo.new = "foo" + result << test + result + }, insns: [:opt_new], call_threshold: 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_opt_newarray_send_include_p + assert_compiles '[true, false]', %q{ + def test(x) + [:y, 1, Object.new].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_include_p_redefined + assert_compiles '[:true, :false]', %q{ + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false + end + end + + def test(x) + [:y, 1, Object.new].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_duparray_send_include_p + assert_compiles '[true, false]', %q{ + def test(x) + [:y, 1].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_duparray_send], call_threshold: 1 + end + + def test_opt_duparray_send_include_p_redefined + assert_compiles '[:true, :false]', %q{ + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false + end + end + + def test(x) + [:y, 1].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_duparray_send], call_threshold: 1 + end + + def test_opt_newarray_send_pack_buffer + assert_compiles '["ABC", "ABC", "ABC", "ABC"]', %q{ + def test(num, buffer) + [num].pack('C', buffer:) + end + buf = "" + [test(65, buf), test(66, buf), test(67, buf), buf] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_pack_buffer_redefined + assert_compiles '["b", "A"]', %q{ + class Array + alias_method :old_pack, :pack + def pack(fmt, buffer: nil) + old_pack(fmt, buffer: buffer) + "b" + end + end + + def test(num, buffer) + [num].pack('C', buffer:) + end + buf = "" + [test(65, buf), buf] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_hash + assert_compiles 'Integer', %q{ + def test(x) + [1, 2, x].hash + end + test(20).class + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_hash_redefined + assert_compiles '42', %q{ + Array.class_eval { def hash = 42 } + + def test(x) + [1, 2, x].hash + end + test(20) + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_max + assert_compiles '[20, 40]', %q{ + def test(a,b) = [a,b].max + [test(10, 20), test(40, 30)] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_max_redefined + assert_compiles '[60, 90]', %q{ + class Array + alias_method :old_max, :max + def max + old_max * 2 + end + end + + def test(a,b) = [a,b].max + [test(15, 30), test(45, 35)] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_new_hash_empty + assert_compiles '{}', %q{ + def test = {} + test + }, insns: [:newhash] + end + + def test_new_hash_nonempty + assert_compiles '{"key" => "value", 42 => 100}', %q{ + def test + key = "key" + value = "value" + num = 42 + result = 100 + {key => value, num => result} + end + test + }, insns: [:newhash] + end + + def test_new_hash_single_key_value + assert_compiles '{"key" => "value"}', %q{ + def test = {"key" => "value"} + test + }, insns: [:newhash] + end + + def test_new_hash_with_computation + assert_compiles '{"sum" => 5, "product" => 6}', %q{ + def test(a, b) + {"sum" => a + b, "product" => a * b} + end + test(2, 3) + }, insns: [:newhash] + end + + def test_new_hash_with_user_defined_hash_method + assert_runs 'true', %q{ + class CustomKey + attr_reader :val + + def initialize(val) + @val = val + end + + def hash + @val.hash + end + + def eql?(other) + other.is_a?(CustomKey) && @val == other.val + end + end + + def test + key = CustomKey.new("key") + hash = {key => "value"} + hash[key] == "value" + end + test + } + end + + def test_new_hash_with_user_hash_method_exception + assert_runs 'RuntimeError', %q{ + class BadKey + def hash + raise "Hash method failed!" + end + end + + def test + key = BadKey.new + {key => "value"} + end + + begin + test + rescue => e + e.class + end + } + end + + def test_new_hash_with_user_eql_method_exception + assert_runs 'RuntimeError', %q{ + class BadKey + def hash + 42 + end + + def eql?(other) + raise "Eql method failed!" + end + end + + def test + key1 = BadKey.new + key2 = BadKey.new + {key1 => "value1", key2 => "value2"} + end + + begin + test + rescue => e + e.class + end + } + end + + def test_opt_hash_freeze + assert_compiles "[{}, 5]", %q{ + def test = {}.freeze + result = [test] + class Hash + def freeze = 5 + end + result << test + }, insns: [:opt_hash_freeze], call_threshold: 1 + end + + def test_opt_hash_freeze_rewritten + assert_compiles "5", %q{ + class Hash + def freeze = 5 + end + def test = {}.freeze + test + }, insns: [:opt_hash_freeze], call_threshold: 1 + end + + def test_opt_aset_hash + assert_compiles '42', %q{ + def test(h, k, v) + h[k] = v + end + h = {} + test(h, :key, 42) + test(h, :key, 42) + h[:key] + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_opt_aset_hash_returns_value + assert_compiles '100', %q{ + def test(h, k, v) + h[k] = v + end + test({}, :key, 100) + test({}, :key, 100) + }, call_threshold: 2 + end + + def test_opt_aset_hash_string_key + assert_compiles '"bar"', %q{ + def test(h, k, v) + h[k] = v + end + h = {} + test(h, "foo", "bar") + test(h, "foo", "bar") + h["foo"] + }, call_threshold: 2 + end + + def test_opt_aset_hash_subclass + assert_compiles '42', %q{ + class MyHash < Hash; end + def test(h, k, v) + h[k] = v + end + h = MyHash.new + test(h, :key, 42) + test(h, :key, 42) + h[:key] + }, call_threshold: 2 + end + + def test_opt_aset_hash_too_few_args + assert_compiles '"ArgumentError"', %q{ + def test(h) + h.[]= 123 + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + }, call_threshold: 2 + end + + def test_opt_aset_hash_too_many_args + assert_compiles '"ArgumentError"', %q{ + def test(h) + h[:a, :b] = :c + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + }, call_threshold: 2 + end + + def test_opt_ary_freeze + assert_compiles "[[], 5]", %q{ + def test = [].freeze + result = [test] + class Array + def freeze = 5 + end + result << test + }, insns: [:opt_ary_freeze], call_threshold: 1 + end + + def test_opt_ary_freeze_rewritten + assert_compiles "5", %q{ + class Array + def freeze = 5 + end + def test = [].freeze + test + }, insns: [:opt_ary_freeze], call_threshold: 1 + end + + def test_opt_str_freeze + assert_compiles "[\"\", 5]", %q{ + def test = ''.freeze + result = [test] + class String + def freeze = 5 + end + result << test + }, insns: [:opt_str_freeze], call_threshold: 1 + end + + def test_opt_str_freeze_rewritten + assert_compiles "5", %q{ + class String + def freeze = 5 + end + def test = ''.freeze + test + }, insns: [:opt_str_freeze], call_threshold: 1 + end + + def test_opt_str_uminus + assert_compiles "[\"\", 5]", %q{ + def test = -'' + result = [test] + class String + def -@ = 5 + end + result << test + }, insns: [:opt_str_uminus], call_threshold: 1 + end + + def test_opt_str_uminus_rewritten + assert_compiles "5", %q{ + class String + def -@ = 5 + end + def test = -'' + test + }, insns: [:opt_str_uminus], call_threshold: 1 + end + + def test_new_array_empty + assert_compiles '[]', %q{ + def test = [] + test + }, insns: [:newarray] + end + + def test_new_array_nonempty + assert_compiles '[5]', %q{ + def a = 5 + def test = [a] + test + } + end + + def test_new_array_order + assert_compiles '[3, 2, 1]', %q{ + def a = 3 + def b = 2 + def c = 1 + def test = [a, b, c] + test + } + end + + def test_array_dup + assert_compiles '[1, 2, 3]', %q{ + def test = [1,2,3] + test + } + end + + def test_array_fixnum_aref + assert_compiles '3', %q{ + def test(x) = [1,2,3][x] + test(2) + test(2) + }, call_threshold: 2, insns: [:opt_aref] + end + + def test_empty_array_pop + assert_compiles 'nil', %q{ + def test(arr) = arr.pop + test([]) + test([]) + }, call_threshold: 2 + end + + def test_array_pop_no_arg + assert_compiles '42', %q{ + def test(arr) = arr.pop + test([32, 33, 42]) + test([32, 33, 42]) + }, call_threshold: 2 + end + + def test_array_pop_arg + assert_compiles '[33, 42]', %q{ + def test(arr) = arr.pop(2) + test([32, 33, 42]) + test([32, 33, 42]) + }, call_threshold: 2 + end + + def test_new_range_inclusive + assert_compiles '1..5', %q{ + def test(a, b) = a..b + test(1, 5) + } + end + + def test_new_range_exclusive + assert_compiles '1...5', %q{ + def test(a, b) = a...b + test(1, 5) + } + end + + def test_new_range_with_literal + assert_compiles '3..10', %q{ + def test(n) = n..10 + test(3) + } + end + + def test_new_range_fixnum_both_literals_inclusive + assert_compiles '1..2', %q{ + def test() + a = 2 + (1..a) + end + test; test + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_both_literals_exclusive + assert_compiles '1...2', %q{ + def test() + a = 2 + (1...a) + end + test; test + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_low_literal_inclusive + assert_compiles '1..3', %q{ + def test(a) + (1..a) + end + test(2); test(3) + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_low_literal_exclusive + assert_compiles '1...3', %q{ + def test(a) + (1...a) + end + test(2); test(3) + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_high_literal_inclusive + assert_compiles '3..10', %q{ + def test(a) + (a..10) + end + test(2); test(3) + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_high_literal_exclusive + assert_compiles '3...10', %q{ + def test(a) + (a...10) + end + test(2); test(3) + }, call_threshold: 2, insns: [:newrange] + end + + def test_if + assert_compiles '[0, nil]', %q{ + def test(n) + if n < 5 + 0 + end + end + [test(3), test(7)] + } + end + + def test_if_else + assert_compiles '[0, 1]', %q{ + def test(n) + if n < 5 + 0 + else + 1 + end + end + [test(3), test(7)] + } + end + + def test_if_else_params + assert_compiles '[1, 20]', %q{ + def test(n, a, b) + if n < 5 + a + else + b + end + end + [test(3, 1, 2), test(7, 10, 20)] + } + end + + def test_if_else_nested + assert_compiles '[3, 8, 9, 14]', %q{ + def test(a, b, c, d, e) + if 2 < a + if a < 4 + b + else + c + end + else + if a < 0 + d + else + e + end + end + end + [ + test(-1, 1, 2, 3, 4), + test( 0, 5, 6, 7, 8), + test( 3, 9, 10, 11, 12), + test( 5, 13, 14, 15, 16), + ] + } + end + + def test_if_else_chained + assert_compiles '[12, 11, 21]', %q{ + def test(a) + (if 2 < a then 1 else 2 end) + (if a < 4 then 10 else 20 end) + end + [test(0), test(3), test(5)] + } + end + + def test_if_elsif_else + assert_compiles '[0, 2, 1]', %q{ + def test(n) + if n < 5 + 0 + elsif 8 < n + 1 + else + 2 + end + end + [test(3), test(7), test(9)] + } + end + + def test_ternary_operator + assert_compiles '[1, 20]', %q{ + def test(n, a, b) + n < 5 ? a : b + end + [test(3, 1, 2), test(7, 10, 20)] + } + end + + def test_ternary_operator_nested + assert_compiles '[2, 21]', %q{ + def test(n, a, b) + (n < 5 ? a : b) + 1 + end + [test(3, 1, 2), test(7, 10, 20)] + } + end + + def test_while_loop + assert_compiles '10', %q{ + def test(n) + i = 0 + while i < n + i = i + 1 + end + i + end + test(10) + } + end + + def test_while_loop_chain + assert_compiles '[135, 270]', %q{ + def test(n) + i = 0 + while i < n + i = i + 1 + end + while i < n * 10 + i = i * 3 + end + i + end + [test(5), test(10)] + } + end + + def test_while_loop_nested + assert_compiles '[0, 4, 12]', %q{ + def test(n, m) + i = 0 + while i < n + j = 0 + while j < m + j += 2 + end + i += j + end + i + end + [test(0, 0), test(1, 3), test(10, 5)] + } + end + + def test_while_loop_if_else + assert_compiles '[9, -1]', %q{ + def test(n) + i = 0 + while i < n + if n >= 10 + return -1 + else + i = i + 1 + end + end + i + end + [test(9), test(10)] + } + end + + def test_if_while_loop + assert_compiles '[9, 12]', %q{ + def test(n) + i = 0 + if n < 10 + while i < n + i += 1 + end + else + while i < n + i += 3 + end + end + i + end + [test(9), test(10)] + } + end + + def test_live_reg_past_ccall + assert_compiles '2', %q{ + def callee = 1 + def test = callee + callee + test + } + end + + def test_method_call + assert_compiles '12', %q{ + def callee(a, b) + a - b + end + + def test + callee(4, 2) + 10 + end + + test # profile test + test + }, call_threshold: 2 + end + + def test_recursive_fact + assert_compiles '[1, 6, 720]', %q{ + def fact(n) + if n == 0 + return 1 + end + return n * fact(n-1) + end + [fact(0), fact(3), fact(6)] + } + end + + def test_profiled_fact + assert_compiles '[1, 6, 720]', %q{ + def fact(n) + if n == 0 + return 1 + end + return n * fact(n-1) + end + fact(1) # profile fact + [fact(0), fact(3), fact(6)] + }, call_threshold: 3, num_profiles: 2 + end + + def test_recursive_fib + assert_compiles '[0, 2, 3]', %q{ + def fib(n) + if n < 2 + return n + end + return fib(n-1) + fib(n-2) + end + [fib(0), fib(3), fib(4)] + } + end + + def test_profiled_fib + assert_compiles '[0, 2, 3]', %q{ + def fib(n) + if n < 2 + return n + end + return fib(n-1) + fib(n-2) + end + fib(3) # profile fib + [fib(0), fib(3), fib(4)] + }, call_threshold: 5, num_profiles: 3 + end + + def test_spilled_basic_block_args + assert_compiles '55', %q{ + def test(n1, n2) + n3 = 3 + n4 = 4 + n5 = 5 + n6 = 6 + n7 = 7 + n8 = 8 + n9 = 9 + n10 = 10 + if n1 < n2 + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + end + test(1, 2) + } + end + + def test_spilled_method_args + assert_runs '55', %q{ + def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10) + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + + def test + foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + end + + test + } + + # TODO(Shopify/ruby#716): Support spills and change to assert_compiles + assert_runs '1', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8,n9) = n1+n9 + a(2,0,0,0,0,0,0,0,-1) + } + + # TODO(Shopify/ruby#716): Support spills and change to assert_compiles + assert_runs '0', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8) = n8 + a(1,1,1,1,1,1,1,0) + } + + # TODO(Shopify/ruby#716): Support spills and change to assert_compiles + # self param with spilled param + assert_runs '"main"', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8) = self + a(1,0,0,0,0,0,0,0).to_s + } + end + + def test_spilled_param_new_arary + # TODO(Shopify/ruby#716): Support spills and change to assert_compiles + assert_runs '[:ok]', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8] + a(0,0,0,0,0,0,0, :ok) + } + end + + def test_forty_param_method + # This used to a trigger a miscomp on A64 due + # to a memory displacement larger than 9 bits. + # Using assert_runs again due to register spill. + # TODO: It should be fixed by register spill support. + assert_runs '1', %Q{ + def foo(#{'_,' * 39} n40) = n40 + + foo(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1) + } + end + + def test_putself + assert_compiles '3', %q{ + class Integer + def minus(a) + self - a + end + end + 5.minus(2) + } + end + + def test_getinstancevariable + assert_compiles 'nil', %q{ + def test() = @foo + + test() + } + assert_compiles '3', %q{ + @foo = 3 + def test() = @foo + + test() + } + end + + def test_getinstancevariable_miss + assert_compiles '[1, 1, 4]', %q{ + class C + def foo + @foo + end + + def foo_then_bar + @foo = 1 + @bar = 2 + end + + def bar_then_foo + @bar = 3 + @foo = 4 + end + end + + o1 = C.new + o1.foo_then_bar + result = [] + result << o1.foo + result << o1.foo + o2 = C.new + o2.bar_then_foo + result << o2.foo + result + } + end + + def test_setinstancevariable + assert_compiles '1', %q{ + def test() = @foo = 1 + + test() + @foo + } + end + + def test_getclassvariable + assert_compiles '42', %q{ + class Foo + def self.test = @@x + end + + Foo.class_variable_set(:@@x, 42) + Foo.test() + } + end + + def test_getclassvariable_raises + assert_compiles '"uninitialized class variable @@x in Foo"', %q{ + class Foo + def self.test = @@x + end + + begin + Foo.test + rescue NameError => e + e.message + end + } + end + + def test_setclassvariable + assert_compiles '42', %q{ + class Foo + def self.test = @@x = 42 + end + + Foo.test() + Foo.class_variable_get(:@@x) + } + end + + def test_setclassvariable_raises + assert_compiles '"can\'t modify frozen Class: Foo"', %q{ + class Foo + def self.test = @@x = 42 + freeze + end + + begin + Foo.test + rescue FrozenError => e + e.message + end + } + end + + def test_attr_reader + assert_compiles '[4, 4]', %q{ + class C + attr_reader :foo + + def initialize + @foo = 4 + end + end + + def test(c) = c.foo + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_attr_accessor_getivar + assert_compiles '[4, 4]', %q{ + class C + attr_accessor :foo + + def initialize + @foo = 4 + end + end + + def test(c) = c.foo + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_attr_accessor_setivar + assert_compiles '[5, 5]', %q{ + class C + attr_accessor :foo + + def initialize + @foo = 4 + end + end + + def test(c) + c.foo = 5 + c.foo + end + + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_attr_writer + assert_compiles '[5, 5]', %q{ + class C + attr_writer :foo + + def initialize + @foo = 4 + end + + def get_foo = @foo + end + + def test(c) + c.foo = 5 + c.get_foo + end + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + 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_expandarray_no_splat + assert_compiles '[3, 4]', %q{ + def test(o) + a, b = o + [a, b] + end + test [3, 4] + }, call_threshold: 1, insns: [:expandarray] + end + + def test_expandarray_splat + assert_compiles '[3, [4]]', %q{ + def test(o) + a, *b = o + [a, b] + end + test [3, 4] + }, call_threshold: 1, insns: [:expandarray] + end + + def test_expandarray_splat_post + assert_compiles '[3, [4], 5]', %q{ + def test(o) + a, *b, c = o + [a, b, c] + end + test [3, 4, 5] + }, call_threshold: 1, insns: [:expandarray] + 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_constant_invalidation + assert_compiles '123', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path] + class C; end + def test = C + test + test + + C = 123 + test + RUBY + end + + def test_constant_path_invalidation + assert_compiles '["Foo::C", "Foo::C", "Bar::C"]', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path] + module A + module B; end + end + + module Foo + C = "Foo::C" + end + + module Bar + C = "Bar::C" + end + + A::B = Foo + + def test = A::B::C + + result = [] + + result << test + result << test + + A::B = Bar + + result << test + result + RUBY + end + + def test_single_ractor_mode_invalidation + # Without invalidating the single-ractor mode, the test would crash + assert_compiles '"errored but not crashed"', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path] + C = Object.new + + def test + C + rescue Ractor::IsolationError + "errored but not crashed" + end + + test + test + + Ractor.new { + test + }.value + RUBY + end + + def test_dupn + assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn] + def test(array) = (array[1, 2] ||= :rhs) + + one = [1, 1] + start_empty = [] + [test(one), one, test(start_empty), start_empty] + RUBY + end + + def test_send_backtrace + backtrace = [ + "-e:2:in 'Object#jit_frame1'", + "-e:3:in 'Object#entry'", + "-e:5:in 'block in
'", + "-e:6:in '
'", + ] + 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 + + def test_bop_invalidation + assert_compiles '100', %q{ + def test + eval(<<~RUBY) + class Integer + def +(_) = 100 + end + RUBY + 1 + 2 + end + test + } + end + + def test_defined_with_defined_values + assert_compiles '["constant", "method", "global-variable"]', %q{ + class Foo; end + def bar; end + $ruby = 1 + + def test = return defined?(Foo), defined?(bar), defined?($ruby) + + test + }, insns: [:defined] + end + + def test_defined_with_undefined_values + assert_compiles '[nil, nil, nil]', %q{ + def test = return defined?(Foo), defined?(bar), defined?($ruby) + + test + }, insns: [:defined] + end + + def test_defined_with_method_call + assert_compiles '["method", nil]', %q{ + def test = return defined?("x".reverse(1)), defined?("x".reverse(1).reverse) + + test + }, insns: [:defined] + end + + def test_defined_method_raise + assert_compiles '[nil, nil, nil]', %q{ + class C + def assert_equal expected, actual + if expected != actual + raise "NO" + end + end + + def test_defined_method + assert_equal(nil, defined?("x".reverse(1).reverse)) + end + end + + c = C.new + result = [] + result << c.test_defined_method + result << c.test_defined_method + result << c.test_defined_method + result + } + end + + def test_defined_yield + assert_compiles "nil", "defined?(yield)" + assert_compiles '[nil, nil, "yield"]', %q{ + def test = defined?(yield) + [test, test, test{}] + }, call_threshold: 2, insns: [:defined] + end + + def test_defined_yield_from_block + # This will do some EP hopping to find the local EP, + # so it's slightly different than doing it outside of a block. + + assert_compiles '[nil, nil, "yield"]', %q{ + def test + yield_self { yield_self { defined?(yield) } } + end + + [test, test, test{}] + }, call_threshold: 2 + end + + def test_block_given_p + assert_compiles "false", "block_given?" + assert_compiles '[false, false, true]', %q{ + def test = block_given? + [test, test, test{}] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_block_given_p_from_block + # This will do some EP hopping to find the local EP, + # so it's slightly different than doing it outside of a block. + + assert_compiles '[false, false, true]', %q{ + def test + yield_self { yield_self { block_given? } } + end + + [test, test, test{}] + }, call_threshold: 2 + end + + def test_invokeblock_without_block_after_jit_call + assert_compiles '"no block given (yield)"', %q{ + def test(*arr, &b) + arr.class + yield + end + begin + test + rescue => e + e.message + end + } + end + + def test_putspecialobject_vm_core_and_cbase + assert_compiles '10', %q{ + def test + alias bar test + 10 + end + + test + bar + }, insns: [:putspecialobject] + end + + def test_putspecialobject_const_base + assert_compiles '1', %q{ + Foo = 1 + + def test = Foo + + # First call: populates the constant cache + test + # Second call: triggers ZJIT compilation with warm cache + # RubyVM::ZJIT.assert_compiles will panic if this fails to compile + test + }, call_threshold: 2 + end + + def test_branchnil + assert_compiles '[2, nil]', %q{ + def test(x) + x&.succ + end + [test(1), test(nil)] + }, call_threshold: 1, insns: [:branchnil] + end + + def test_nil_nil + assert_compiles 'true', %q{ + def test = nil.nil? + test + }, insns: [:opt_nil_p] + end + + def test_non_nil_nil + assert_compiles 'false', %q{ + def test = 1.nil? + test + }, insns: [:opt_nil_p] + end + + def test_getspecial_last_match + assert_compiles '"hello"', %q{ + def test(str) + str =~ /hello/ + $& + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_match_pre + assert_compiles '"hello "', %q{ + def test(str) + str =~ /world/ + $` + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_match_post + assert_compiles '" world"', %q{ + def test(str) + str =~ /hello/ + $' + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_match_last_group + assert_compiles '"world"', %q{ + def test(str) + str =~ /(hello) (world)/ + $+ + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_numbered_match_1 + assert_compiles '"hello"', %q{ + def test(str) + str =~ /(hello) (world)/ + $1 + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_numbered_match_2 + assert_compiles '"world"', %q{ + def test(str) + str =~ /(hello) (world)/ + $2 + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_numbered_match_nonexistent + assert_compiles 'nil', %q{ + def test(str) + str =~ /(hello)/ + $2 + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_no_match + assert_compiles 'nil', %q{ + def test(str) + str =~ /xyz/ + $& + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_complex_pattern + assert_compiles '"123"', %q{ + def test(str) + str =~ /(\d+)/ + $1 + end + test("abc123def") + }, insns: [:getspecial] + end + + def test_getspecial_multiple_groups + assert_compiles '"456"', %q{ + def test(str) + str =~ /(\d+)-(\d+)/ + $2 + end + test("123-456") + }, insns: [:getspecial] + 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 '""', %q{ + Array.instance_method(:each).source_location&.first + } + end + + def test_profile_under_nested_jit_call + assert_compiles '[nil, nil, 3]', %q{ + def profile + 1 + 2 + end + + def jit_call(flag) + if flag + profile + end + end + + def entry(flag) + jit_call(flag) + end + + [entry(false), entry(false), entry(true)] + }, call_threshold: 2 + end + + def test_bop_redefined + assert_runs '[3, :+, 100]', %q{ + def test + 1 + 2 + end + + test # profile opt_plus + [test, Integer.class_eval { def +(_) = 100 }, test] + }, call_threshold: 2 + end + + def test_bop_redefined_with_adjacent_patch_points + assert_runs '[15, :+, 100]', %q{ + def test + 1 + 2 + 3 + 4 + 5 + end + + test # profile opt_plus + [test, Integer.class_eval { def +(_) = 100 }, test] + }, call_threshold: 2 + end + + # ZJIT currently only generates a MethodRedefined patch point when the method + # is called on the top-level self. + def test_method_redefined_with_top_self + assert_runs '["original", "redefined"]', %q{ + def foo + "original" + end + + def test = foo + + test; test + + result1 = test + + # Redefine the method + def foo + "redefined" + end + + result2 = test + + [result1, result2] + }, call_threshold: 2 + end + + def test_method_redefined_with_module + assert_runs '["original", "redefined"]', %q{ + module Foo + def self.foo = "original" + end + + def test = Foo.foo + test + result1 = test + + def Foo.foo = "redefined" + result2 = test + + [result1, result2] + }, call_threshold: 2 + end + + def test_module_name_with_guard_passes + assert_compiles '"Integer"', %q{ + def test(mod) + mod.name + end + + test(String) + test(Integer) + }, call_threshold: 2 + end + + def test_module_name_with_guard_side_exit + # This test demonstrates that the guard side exit works correctly + # In this case, when we call with a non-Class object, it should fall back to interpreter + assert_compiles '["String", "Integer", "Bar"]', %q{ + class MyClass + def name = "Bar" + end + + def test(mod) + mod.name + end + + results = [] + results << test(String) + results << test(Integer) + results << test(MyClass.new) + + results + }, call_threshold: 2 + end + + def test_objtostring_calls_to_s_on_non_strings + assert_compiles '["foo", "foo"]', %q{ + results = [] + + class Foo + def to_s + "foo" + end + end + + def test(str) + "#{str}" + end + + results << test(Foo.new) + results << test(Foo.new) + + results + } + end + + def test_objtostring_rewrite_does_not_call_to_s_on_strings + assert_compiles '["foo", "foo"]', %q{ + results = [] + + class String + def to_s + "bad" + end + end + + def test(foo) + "#{foo}" + end + + results << test("foo") + results << test("foo") + + results + } + end + + def test_objtostring_rewrite_does_not_call_to_s_on_string_subclasses + assert_compiles '["foo", "foo"]', %q{ + results = [] + + class StringSubclass < String + def to_s + "bad" + end + end + + foo = StringSubclass.new("foo") + + def test(str) + "#{str}" + end + + results << test(foo) + results << test(foo) + + results + } + end + + def test_objtostring_profiled_string_fastpath + assert_compiles '"foo"', %q{ + def test(str) + "#{str}" + end + test('foo'); test('foo') # profile as string + }, call_threshold: 2 + end + + def test_objtostring_profiled_string_subclass_fastpath + assert_compiles '"foo"', %q{ + class MyString < String; end + + def test(str) + "#{str}" + end + + foo = MyString.new("foo") + test(foo); test(foo) # still profiles as string + }, call_threshold: 2 + end + + def test_objtostring_profiled_string_fastpath_exits_on_nonstring + assert_compiles '"1"', %q{ + def test(str) + "#{str}" + end + + test('foo') # profile as string + test(1) + }, call_threshold: 2 + end + + def test_objtostring_profiled_nonstring_calls_to_s + assert_compiles '"[1, 2, 3]"', %q{ + def test(str) + "#{str}" + end + + test([1,2,3]); # profile as nonstring + test([1,2,3]); + }, call_threshold: 2 + end + + def test_objtostring_profiled_nonstring_guard_exits_when_string + assert_compiles '"foo"', %q{ + def test(str) + "#{str}" + end + + test([1,2,3]); # profiles as nonstring + test('foo'); + }, call_threshold: 2 + end + + def test_string_bytesize_with_guard + assert_compiles '5', %q{ + def test(str) + str.bytesize + end + + test('hello') + test('world') + }, call_threshold: 2 + end + + def test_string_bytesize_multibyte + assert_compiles '4', %q{ + def test(s) + s.bytesize + end + + test("💎") + }, call_threshold: 2 + end + + def test_nil_value_nil_opt_with_guard + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(nil) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_nil_value_nil_opt_with_guard_side_exit + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(nil) + test(nil) + test(1) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_true_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(true) + test(true) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_true_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(true) + test(true) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_false_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(false) + test(false) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_false_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(false) + test(false) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_integer_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(1) + test(2) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_integer_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(1) + test(2) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_float_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(1.0) + test(2.0) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_float_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(1.0) + test(2.0) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_symbol_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(:foo) + test(:bar) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_symbol_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(:foo) + test(:bar) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_class_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(String) + test(Integer) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_class_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(String) + test(Integer) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_module_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(Enumerable) + test(Kernel) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_module_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(Enumerable) + test(Kernel) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_basic_object_guard_works_with_immediate + assert_compiles 'NilClass', %q{ + class Foo; end + + def test(val) = val.class + + test(Foo.new) + test(Foo.new) + test(nil) + }, call_threshold: 2 + end + + def test_basic_object_guard_works_with_false + assert_compiles 'FalseClass', %q{ + class Foo; end + + def test(val) = val.class + + test(Foo.new) + test(Foo.new) + test(false) + }, call_threshold: 2 + end + + def test_string_concat + assert_compiles '"123"', %q{ + def test = "#{1}#{2}#{3}" + + test + }, insns: [:concatstrings] + end + + def test_string_concat_empty + assert_compiles '""', %q{ + def test = "#{}" + + test + }, insns: [:concatstrings] + end + + def test_regexp_interpolation + assert_compiles '/123/', %q{ + def test = /#{1}#{2}#{3}/ + + test + }, insns: [:toregexp] + end + + def test_new_range_non_leaf + assert_compiles '(0/1)..1', %q{ + def jit_entry(v) = make_range_then_exit(v) + + def make_range_then_exit(v) + range = (v..1) + super rescue range # TODO(alan): replace super with side-exit intrinsic + end + + jit_entry(0) # profile + jit_entry(0) # compile + jit_entry(0/1r) # run without stub + }, call_threshold: 2 + end + + def test_raise_in_second_argument + assert_compiles '{ok: true}', %q{ + def write(hash, key) + hash[key] = raise rescue true + hash + end + + write({}, :ok) + } + end + + def test_ivar_attr_reader_optimization_with_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + class << self + attr_accessor :bar + + def get_bar + bar + rescue Ractor::IsolationError + 42 + end + end + end + + Foo.bar = [] # needs to be a ractor unshareable object + + def test + Foo.get_bar + end + + test + test + + Ractor.new { test }.value + }, call_threshold: 2 + end + + def test_ivar_get_with_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + def self.set_bar + @bar = [] # needs to be a ractor unshareable object + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + + def test + Foo.bar + end + + test + test + + Ractor.new { test }.value + }, call_threshold: 2 + end + + def test_ivar_get_with_already_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + def self.set_bar + @bar = [] # needs to be a ractor unshareable object + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + r = Ractor.new { + Ractor.receive + Foo.bar + } + + Foo.bar + Foo.bar + + r << :go + r.value + }, call_threshold: 2 + end + + def test_ivar_set_with_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + def self.bar + _foo = 1 + _bar = 2 + begin + @bar = _foo + _bar + rescue Ractor::IsolationError + 42 + end + end + end + + def test + Foo.bar + end + + test + test + + Ractor.new { test }.value + } + end + + def test_struct_set + assert_compiles '[42, 42, :frozen_error]', %q{ + C = Struct.new(:foo).new(1) + + def test + C.foo = Object.new + 42 + end + + r = [test, test] + C.freeze + r << begin + test + rescue FrozenError + :frozen_error + end + }, call_threshold: 2 + end + + def test_global_tracepoint + assert_compiles 'true', %q{ + def foo = 1 + + foo + foo + + called = false + + tp = TracePoint.new(:return) { |event| + if event.method_id == :foo + called = true + end + } + tp.enable do + foo + end + called + } + end + + def test_local_tracepoint + assert_compiles 'true', %q{ + def foo = 1 + + foo + foo + + called = false + + tp = TracePoint.new(:return) { |_| called = true } + tp.enable(target: method(:foo)) do + foo + end + called + } + 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_opt_case_dispatch + assert_compiles '[true, false]', %q{ + def test(x) + case x + when :foo + true + else + false + end + end + + results = [] + results << test(:foo) + results << test(1) + results + }, insns: [:opt_case_dispatch] + end + + def test_stack_overflow + assert_compiles 'nil', %q{ + def recurse(n) + return if n == 0 + recurse(n-1) + nil # no tail call + end + + recurse(2) + recurse(2) + begin + recurse(20_000) + rescue SystemStackError + # Not asserting an exception is raised here since main + # thread stack size is environment-sensitive. Only + # that we don't crash or infinite loop. + end + }, call_threshold: 2 + end + + def test_invokeblock + assert_compiles '42', %q{ + def test + yield + end + test { 42 } + }, insns: [:invokeblock] + end + + def test_invokeblock_with_args + assert_compiles '3', %q{ + def test(x, y) + yield x, y + end + test(1, 2) { |a, b| a + b } + }, insns: [:invokeblock] + end + + def test_invokeblock_no_block_given + assert_compiles ':error', %q{ + def test + yield rescue :error + end + test + }, insns: [:invokeblock] + end + + def test_invokeblock_multiple_yields + assert_compiles "[1, 2, 3]", %q{ + results = [] + def test + yield 1 + yield 2 + yield 3 + end + test { |x| results << x } + results + }, insns: [:invokeblock] + end + + def test_ccall_variadic_with_multiple_args + assert_compiles "[1, 2, 3]", %q{ + def test + a = [] + a.push(1, 2, 3) + a + end + + test + test + }, insns: [:opt_send_without_block] + end + + def test_ccall_variadic_with_no_args + assert_compiles "[1]", %q{ + def test + a = [1] + a.push + end + + test + test + }, insns: [:opt_send_without_block] + end + + def test_ccall_variadic_with_no_args_causing_argument_error + assert_compiles ":error", %q{ + def test + format + rescue ArgumentError + :error + end + + test + test + }, insns: [:opt_send_without_block] + end + + def test_allocating_in_hir_c_method_is + assert_compiles ":k", %q{ + # Put opt_new in a frame JIT code sets up that doesn't set cfp->pc + def a(f) = test(f) + def test(f) = (f.new if f) + # A parallel couple methods that will set PC at the same stack height + def second = third + def third = nil + + a(nil) + a(nil) + + class Foo + def self.new = :k + end + + second + + a(Foo) + }, call_threshold: 2, insns: [:opt_new] + end + + def test_singleton_class_invalidation_annotated_ccall + assert_compiles '[false, true]', %q{ + def define_singleton(obj, define) + if define + # Wrap in C method frame to avoid exiting JIT on defineclass + [nil].reverse_each do + class << obj + def ==(_) + true + end + end + end + end + false + end + + def test(define) + obj = BasicObject.new + # This == call gets compiled to a CCall + obj == define_singleton(obj, define) + end + + result = [] + result << test(false) # Compiles BasicObject#== + result << test(true) # Should use singleton#== now + result + }, call_threshold: 2 + end + + def test_singleton_class_invalidation_optimized_variadic_ccall + assert_compiles '[1, 1000]', %q{ + def define_singleton(arr, define) + if define + # Wrap in C method frame to avoid exiting JIT on defineclass + [nil].reverse_each do + class << arr + def push(x) + super(x * 1000) + end + end + end + end + 1 + end + + def test(define) + arr = [] + val = define_singleton(arr, define) + arr.push(val) # This CCall should be invalidated if singleton was defined + arr[0] + end + + result = [] + result << test(false) # Compiles Array#push as CCall + result << test(true) # Singleton defined, CCall should be invalidated + result + }, call_threshold: 2 + 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 + + 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, + timeout: 1000, + pipe_fd: nil + ) + args = ["--disable-gems"] + 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/mri/rubygems/helper.rb b/test/mri/rubygems/helper.rb index 35149541031..dc40f4ecb1f 100644 --- a/test/mri/rubygems/helper.rb +++ b/test/mri/rubygems/helper.rb @@ -9,8 +9,6 @@ require "test/unit" -ENV["JARS_SKIP"] = "true" if Gem.java_platform? # avoid unnecessary and noisy `jar-dependencies` post install hook - require "fileutils" require "pathname" require "pp" @@ -62,6 +60,44 @@ def self.specific_extra_args_hash=(value) end end +class Gem::Installer + # Copy from Gem::Installer#install with install_as_default option from old version + def install_default_gem + pre_install_checks + + run_pre_install_hooks + + spec.loaded_from = default_spec_file + + FileUtils.rm_rf gem_dir + FileUtils.rm_rf spec.extension_dir + + dir_mode = options[:dir_mode] + FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755 + + extract_bin + write_default_spec + + generate_bin + generate_plugins + + File.chmod(dir_mode, gem_dir) if dir_mode + + say spec.post_install_message if options[:post_install_message] && !spec.post_install_message.nil? + + Gem::Specification.add_spec(spec) + + load_plugin + + run_post_install_hooks + + spec + rescue Errno::EACCES => e + # Permission denied - /path/to/foo + raise Gem::FilePermissionError, e.message.split(" - ").last + end +end + ## # RubyGemTestCase provides a variety of methods for testing rubygems and # gem-related behavior in a sandbox. Through RubyGemTestCase you can install @@ -297,8 +333,12 @@ def setup ENV["XDG_CONFIG_HOME"] = nil ENV["XDG_DATA_HOME"] = nil ENV["XDG_STATE_HOME"] = nil + ENV["MAKEFLAGS"] = nil ENV["SOURCE_DATE_EPOCH"] = nil ENV["BUNDLER_VERSION"] = nil + ENV["BUNDLE_CONFIG"] = nil + ENV["BUNDLE_USER_CONFIG"] = nil + ENV["BUNDLE_USER_HOME"] = nil ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = "true" @current_dir = Dir.pwd @@ -402,8 +442,9 @@ def setup Gem::RemoteFetcher.fetcher = Gem::FakeFetcher.new @gem_repo = "http://gems.example.com/" + Gem.instance_variable_set :@default_sources, [@gem_repo] + Gem.instance_variable_set :@sources, nil @uri = Gem::URI.parse @gem_repo - Gem.sources.replace [@gem_repo] Gem.searcher = nil Gem::SpecFetcher.fetcher = nil @@ -420,6 +461,9 @@ def setup @orig_hooks[name] = Gem.send(name).dup end + Gem::Platform.const_get(:GENERIC_CACHE).clear + Gem::Platform.const_get(:GENERICS).each {|g| Gem::Platform.const_get(:GENERIC_CACHE)[g] = g } + @marshal_version = "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" @orig_loaded_features = $LOADED_FEATURES.dup end @@ -682,6 +726,14 @@ def write_file(path) 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 @@ -715,7 +767,7 @@ def all_spec_names # # Use this with #write_file to build an installed gem. - def quick_gem(name, version="2") + def quick_gem(name, version = "2") require "rubygems/specification" spec = Gem::Specification.new do |s| @@ -801,8 +853,8 @@ def install_specs(*specs) def install_default_gems(*specs) specs.each do |spec| - installer = Gem::Installer.for_spec(spec, install_as_default: true) - installer.install + installer = Gem::Installer.for_spec(spec) + installer.install_default_gem Gem.register_default_spec(spec) end end @@ -1024,7 +1076,7 @@ def util_set_arch(arch) # 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 @@ -1197,7 +1249,7 @@ def wait_for_child_process_to_exit ## # Allows the proper version of +rake+ to be used for the test. - def build_rake_in(good=true) + def build_rake_in(good = true) gem_ruby = Gem.ruby Gem.ruby = self.class.rubybin env_rake = ENV["rake"] @@ -1569,3 +1621,9 @@ def stub(name, val_or_callable, *block_args) 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/mri/rubygems/installer_test_case.rb b/test/mri/rubygems/installer_test_case.rb index 8a34d28db86..ded205c5f56 100644 --- a/test/mri/rubygems/installer_test_case.rb +++ b/test/mri/rubygems/installer_test_case.rb @@ -215,12 +215,29 @@ def util_setup_gem(ui = @ui, force = true) ## # 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 + 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 + + assert_directory_exists non_existent_parent, "Parent directory should exist now" + assert_directory_exists target_dir, "Target directory should exist now" + end + @@symlink_supported = nil # This is needed for Windows environment without symlink support enabled (the default diff --git a/test/mri/rubygems/mock_gem_ui.rb b/test/mri/rubygems/mock_gem_ui.rb index 218d4b6965d..fb804c5555c 100644 --- a/test/mri/rubygems/mock_gem_ui.rb +++ b/test/mri/rubygems/mock_gem_ui.rb @@ -77,7 +77,7 @@ def terminated? @terminated end - def terminate_interaction(status=0) + def terminate_interaction(status = 0) @terminated = true raise TermError, status if status != 0 diff --git a/test/mri/rubygems/package/tar_test_case.rb b/test/mri/rubygems/package/tar_test_case.rb index e3d812bf3fd..26135cf2967 100644 --- a/test/mri/rubygems/package/tar_test_case.rb +++ b/test/mri/rubygems/package/tar_test_case.rb @@ -6,23 +6,7 @@ ## # 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 @@ def assert_headers_equal(expected, actual) 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/mri/rubygems/test_bundled_ca.rb b/test/mri/rubygems/test_bundled_ca.rb index a737185681e..cc8fa884cac 100644 --- a/test/mri/rubygems/test_bundled_ca.rb +++ b/test/mri/rubygems/test_bundled_ca.rb @@ -12,7 +12,7 @@ # = 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/mri/rubygems/test_config.rb b/test/mri/rubygems/test_config.rb index 657624d5268..822b57b0dcb 100644 --- a/test/mri/rubygems/test_config.rb +++ b/test/mri/rubygems/test_config.rb @@ -5,13 +5,6 @@ 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/mri/rubygems/test_gem.rb b/test/mri/rubygems/test_gem.rb index cdc3479e373..74c8953904f 100644 --- a/test/mri/rubygems/test_gem.rb +++ b/test/mri/rubygems/test_gem.rb @@ -527,35 +527,6 @@ def test_self_configuration 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 +586,7 @@ def test_default_path_vendor_dir end def test_self_default_sources + Gem.remove_instance_variable :@default_sources assert_equal %w[https://rubygems.org/], Gem.default_sources end @@ -1227,6 +1199,8 @@ def test_self_sources 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 diff --git a/test/mri/rubygems/test_gem_bundler_version_finder.rb b/test/mri/rubygems/test_gem_bundler_version_finder.rb index b72670b8022..a773d6249b5 100644 --- a/test/mri/rubygems/test_gem_bundler_version_finder.rb +++ b/test/mri/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 @@ def test_bundler_version_with_env_var 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,75 @@ def test_bundler_version_with_bundle_update_bundler 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_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_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_config_file, f.path) do + assert_equal v("1.1.1.1"), bvf.bundler_version + end + end + end + + def test_bundler_version_with_bundle_config_non_existent_file + bvf.stub(:bundler_config_file, "/non/existent/path") do + assert_nil bvf.bundler_version + 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_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 +157,7 @@ def test_bundler_version 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/mri/rubygems/test_gem_command_manager.rb b/test/mri/rubygems/test_gem_command_manager.rb index f3848e498db..889d5ce9e66 100644 --- a/test/mri/rubygems/test_gem_command_manager.rb +++ b/test/mri/rubygems/test_gem_command_manager.rb @@ -43,7 +43,7 @@ def test_find_login_alias_command 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 @@ def test_find_command_unknown_suggestions 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 @@ def test_process_args_build 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/mri/rubygems/test_gem_commands_build_command.rb b/test/mri/rubygems/test_gem_commands_build_command.rb index d44126d2046..9339f41f7cb 100644 --- a/test/mri/rubygems/test_gem_commands_build_command.rb +++ b/test/mri/rubygems/test_gem_commands_build_command.rb @@ -43,16 +43,6 @@ def test_handle_options 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/mri/rubygems/test_gem_commands_cert_command.rb b/test/mri/rubygems/test_gem_commands_cert_command.rb index c1734679351..39fda73ebac 100644 --- a/test/mri/rubygems/test_gem_commands_cert_command.rb +++ b/test/mri/rubygems/test_gem_commands_cert_command.rb @@ -498,7 +498,7 @@ def test_execute_sign 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 +527,7 @@ def test_execute_sign_encrypted_key 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 +559,7 @@ def test_execute_sign_default 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 +591,7 @@ def test_execute_sign_default_encrypted_key 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 diff --git a/test/mri/rubygems/test_gem_commands_exec_command.rb b/test/mri/rubygems/test_gem_commands_exec_command.rb index b9d5888068f..db738b5e9f5 100644 --- a/test/mri/rubygems/test_gem_commands_exec_command.rb +++ b/test/mri/rubygems/test_gem_commands_exec_command.rb @@ -370,8 +370,11 @@ def test_gem_with_multiple_executables_no_match 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/mri/rubygems/test_gem_commands_help_command.rb b/test/mri/rubygems/test_gem_commands_help_command.rb index 01ab4aab2fe..4ce7285d1f8 100644 --- a/test/mri/rubygems/test_gem_commands_help_command.rb +++ b/test/mri/rubygems/test_gem_commands_help_command.rb @@ -36,7 +36,7 @@ def test_gem_help_platforms def test_gem_help_build util_gem "build" do |out, err| - assert_match(/-C PATH *Run as if gem build was started in /, out) + assert_match(/--platform PLATFORM\s+Specify the platform of gem to build/, out) assert_equal "", err end end diff --git a/test/mri/rubygems/test_gem_commands_info_command.rb b/test/mri/rubygems/test_gem_commands_info_command.rb index f020d380d29..dab7cfb836b 100644 --- a/test/mri/rubygems/test_gem_commands_info_command.rb +++ b/test/mri/rubygems/test_gem_commands_info_command.rb @@ -13,7 +13,7 @@ def setup 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/mri/rubygems/test_gem_commands_install_command.rb b/test/mri/rubygems/test_gem_commands_install_command.rb index c7fbca196be..d2ca933a632 100644 --- a/test/mri/rubygems/test_gem_commands_install_command.rb +++ b/test/mri/rubygems/test_gem_commands_install_command.rb @@ -647,17 +647,10 @@ def test_execute_rdoc @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 @@ -665,7 +658,7 @@ def test_execute_rdoc assert_path_exist File.join(a2.doc_dir, "ri") assert_path_exist File.join(a2.doc_dir, "rdoc") - end unless Gem.rdoc_hooks_defined_via_plugin? + end if defined?(Gem::RDoc) && !Gem.rdoc_hooks_defined_via_plugin? def test_execute_rdoc_with_path specs = spec_fetcher do |fetcher| @@ -684,24 +677,17 @@ def test_execute_rdoc_with_path @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 wait_for_child_process_to_exit assert_path_exist "whatever/doc/a-2", "documentation not installed" - end unless Gem.rdoc_hooks_defined_via_plugin? + end if defined?(Gem::RDoc) && !Gem.rdoc_hooks_defined_via_plugin? def test_execute_saves_build_args specs = spec_fetcher do |fetcher| @@ -720,17 +706,10 @@ def test_execute_saves_build_args @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 @@ -1005,6 +984,38 @@ def test_install_gem_ignore_dependencies_remote_platform_local 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 @@ def test_execute_installs_from_a_gemdeps 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 @@ def test_suggest_update_if_enabled 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/mri/rubygems/test_gem_commands_owner_command.rb b/test/mri/rubygems/test_gem_commands_owner_command.rb index bc4f13ff2a5..80b1497c415 100644 --- a/test/mri/rubygems/test_gem_commands_owner_command.rb +++ b/test/mri/rubygems/test_gem_commands_owner_command.rb @@ -386,9 +386,10 @@ def test_with_webauthn_enabled_success 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 @@ -413,9 +414,10 @@ def test_with_webauthn_enabled_failure 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 +437,10 @@ def test_with_webauthn_enabled_success_with_polling 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 +466,17 @@ def test_with_webauthn_enabled_failure_with_polling 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 +541,7 @@ def test_add_owners_no_api_key_webauthn_enabled_does_not_reuse_otp_codes 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/mri/rubygems/test_gem_commands_pristine_command.rb b/test/mri/rubygems/test_gem_commands_pristine_command.rb index 46c06db014d..e9c4d329450 100644 --- a/test/mri/rubygems/test_gem_commands_pristine_command.rb +++ b/test/mri/rubygems/test_gem_commands_pristine_command.rb @@ -125,8 +125,8 @@ def test_execute_all @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" @@ -537,8 +537,8 @@ def test_execute_only_executables @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 +572,9 @@ def test_execute_only_plugins @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 +606,8 @@ def test_execute_bindir @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 +659,42 @@ def test_execute_default_gem 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/mri/rubygems/test_gem_commands_push_command.rb b/test/mri/rubygems/test_gem_commands_push_command.rb index 2d0190b49fe..1477a749471 100644 --- a/test/mri/rubygems/test_gem_commands_push_command.rb +++ b/test/mri/rubygems/test_gem_commands_push_command.rb @@ -477,9 +477,10 @@ def test_with_webauthn_enabled_success 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 @@ -505,9 +506,10 @@ def test_with_webauthn_enabled_failure 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 +529,10 @@ def test_with_webauthn_enabled_success_with_polling 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 +556,17 @@ def test_with_webauthn_enabled_failure_with_polling 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)" diff --git a/test/mri/rubygems/test_gem_commands_setup_command.rb b/test/mri/rubygems/test_gem_commands_setup_command.rb index c3622c02cda..b33e05ab28d 100644 --- a/test/mri/rubygems/test_gem_commands_setup_command.rb +++ b/test/mri/rubygems/test_gem_commands_setup_command.rb @@ -4,13 +4,6 @@ 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 @@ def setup 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 @@ def test_install_default_bundler_gem 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 @@ def test_show_release_notes 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 @@ def test_show_release_notes 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/mri/rubygems/test_gem_commands_signin_command.rb b/test/mri/rubygems/test_gem_commands_signin_command.rb index 29e5edceb7c..e612288faf9 100644 --- a/test/mri/rubygems/test_gem_commands_signin_command.rb +++ b/test/mri/rubygems/test_gem_commands_signin_command.rb @@ -121,7 +121,7 @@ def test_execute_with_key_name_default_scope 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/mri/rubygems/test_gem_commands_sources_command.rb b/test/mri/rubygems/test_gem_commands_sources_command.rb index 5e675e5c844..00eb9239940 100644 --- a/test/mri/rubygems/test_gem_commands_sources_command.rb +++ b/test/mri/rubygems/test_gem_commands_sources_command.rb @@ -32,7 +32,7 @@ def test_execute end expected = <<-EOF -*** CURRENT SOURCES *** +*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW *** #{@gem_repo} EOF @@ -42,23 +42,28 @@ def test_execute end def test_execute_add - spec_fetcher do |fetcher| - fetcher.spec "a", 1 - end + setup_fake_source(@new_repo) - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end + @cmd.handle_options %W[--add #{@new_repo}] - 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["#{@new_repo}/specs.#{@marshal_version}.gz"] = - specs_dump_gz.string + assert_equal [@gem_repo, @new_repo], Gem.sources + + expected = <<-EOF +#{@new_repo} added to sources + EOF - @cmd.handle_options %W[--add #{@new_repo}] + 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 +82,31 @@ def test_execute_add 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 +126,27 @@ def test_execute_add_allow_typo_squatting_source 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 +162,34 @@ def test_execute_add_allow_typo_squatting_source_forced 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 +234,31 @@ def test_execute_add_nonexistent_source 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 +284,31 @@ def test_execute_add_existent_source_invalid_uri 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 +334,31 @@ def test_execute_add_existent_source_invalid_uri_with_error_by_chance_including_ 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 +378,34 @@ def test_execute_add_redundant_source 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 +414,7 @@ def test_execute_add_redundant_source_trailing_slash 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 +429,46 @@ def test_execute_add_redundant_source_trailing_slash 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 +490,27 @@ def test_execute_add_http_rubygems_org 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,23 +526,33 @@ def test_execute_add_http_rubygems_org_forced def test_execute_add_https_rubygems_org https_rubygems_org = "https://rubygems.org/" - spec_fetcher do |fetcher| - fetcher.spec "a", 1 - end + setup_fake_source(https_rubygems_org) - specs = Gem::Specification.map do |spec| - [spec.name, spec.version, spec.original_platform] - end + @cmd.handle_options %W[--add #{https_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["#{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 = <<-EXPECTED + 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}] ui = Gem::MockGemUi.new "n" @@ -450,6 +590,25 @@ def test_execute_add_bad_uri assert_equal "", @ui.error end + 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 + end + + assert_equal [@gem_repo], Gem.sources + + expected = <<-EOF +beta-gems.example.com is not a URI + EOF + + assert_equal expected, @ui.output + assert_equal "", @ui.error + end + def test_execute_clear_all @cmd.handle_options %w[--clear-all] @@ -476,7 +635,7 @@ def test_execute_list end expected = <<-EOF -*** CURRENT SOURCES *** +*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW *** #{@gem_repo} EOF @@ -486,24 +645,32 @@ def test_execute_list 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 +678,104 @@ def test_execute_remove_no_network @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_update @@ -531,4 +792,102 @@ def test_execute_update 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}/specs.#{@marshal_version}.gz"] = specs_dump_gz.string + end end diff --git a/test/mri/rubygems/test_gem_commands_update_command.rb b/test/mri/rubygems/test_gem_commands_update_command.rb index 24e24f97f64..5ed12ad4814 100644 --- a/test/mri/rubygems/test_gem_commands_update_command.rb +++ b/test/mri/rubygems/test_gem_commands_update_command.rb @@ -506,7 +506,7 @@ def test_execute_rdoc a2 = @specs["a-2"] assert_path_exist File.join(a2.doc_dir, "rdoc") - end unless Gem.rdoc_hooks_defined_via_plugin? + end if defined?(Gem::RDoc) && !Gem.rdoc_hooks_defined_via_plugin? def test_execute_named spec_fetcher do |fetcher| @@ -696,6 +696,38 @@ def test_fetch_remote_gems_prerelease 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/mri/rubygems/test_gem_commands_yank_command.rb b/test/mri/rubygems/test_gem_commands_yank_command.rb index eb78e3a5428..73fd1772432 100644 --- a/test/mri/rubygems/test_gem_commands_yank_command.rb +++ b/test/mri/rubygems/test_gem_commands_yank_command.rb @@ -131,9 +131,10 @@ def test_with_webauthn_enabled_success 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 @@ -163,9 +164,10 @@ def test_with_webauthn_enabled_failure 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 +191,10 @@ def test_with_webauthn_enabled_success_with_polling 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 +222,10 @@ def test_with_webauthn_enabled_failure_with_polling 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 +271,7 @@ def test_execute_host 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/mri/rubygems/test_gem_dependency_installer.rb b/test/mri/rubygems/test_gem_dependency_installer.rb index 56b84160c4d..3e10c0883aa 100644 --- a/test/mri/rubygems/test_gem_dependency_installer.rb +++ b/test/mri/rubygems/test_gem_dependency_installer.rb @@ -382,13 +382,9 @@ def test_install_dependency_existing_extension 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 @@ def test_install_local_subdir 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 + 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 @@ -1066,117 +1114,6 @@ def spec.validate(*args); end 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/mri/rubygems/test_gem_ext_cargo_builder.rb b/test/mri/rubygems/test_gem_ext_cargo_builder.rb index 5faf3e24807..b970e442c25 100644 --- a/test/mri/rubygems/test_gem_ext_cargo_builder.rb +++ b/test/mri/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 @@ def test_custom_name 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 @@ def skip_unsupported_platforms! 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 @@ def assert_ffi_handle(bundle, name) 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/mri/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/mri/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index 310bc3d62fc..6302e9ee37b 100644 --- a/test/mri/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/mri/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" @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.103" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91dbe37ab6ac2fba187480fb6544b92445e41e5c6f553bf0c33743f3c450a1df" +checksum = "f900d1ce4629a2ebffaf5de74bd8f9c1188d4c5ed406df02f97e22f77a006f44" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.103" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d56a49dcb646b70b758789c0d16c055a386a4f2a3346333abb69850fa860ce" +checksum = "ef1e9c857028f631056bcd6d88cec390c751e343ce2223ddb26d23eb4a151d59" dependencies = [ "bindgen", "lazy_static", diff --git a/test/mri/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/mri/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index 29fd66d2a5a..e26fa352a81 100644 --- a/test/mri/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/mri/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.103" +rb-sys = "0.9.117" diff --git a/test/mri/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/mri/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index c42e756fd11..4bbe8932a2b 100644 --- a/test/mri/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/mri/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" @@ -145,18 +145,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.103" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91dbe37ab6ac2fba187480fb6544b92445e41e5c6f553bf0c33743f3c450a1df" +checksum = "f900d1ce4629a2ebffaf5de74bd8f9c1188d4c5ed406df02f97e22f77a006f44" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.103" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d56a49dcb646b70b758789c0d16c055a386a4f2a3346333abb69850fa860ce" +checksum = "ef1e9c857028f631056bcd6d88cec390c751e343ce2223ddb26d23eb4a151d59" dependencies = [ "bindgen", "lazy_static", diff --git a/test/mri/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/mri/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index 1950e0149ea..55d5c5bde7f 100644 --- a/test/mri/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/mri/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.103" +rb-sys = "0.9.117" diff --git a/test/mri/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb b/test/mri/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb index a3fef50d54e..3693f63df6f 100644 --- a/test/mri/rubygems/test_gem_ext_cargo_builder_link_flag_converter.rb +++ b/test/mri/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/mri/rubygems/test_gem_ext_cmake_builder.rb b/test/mri/rubygems/test_gem_ext_cmake_builder.rb index 5f886af05fc..b9b57084d4f 100644 --- a/test/mri/rubygems/test_gem_ext_cmake_builder.rb +++ b/test/mri/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 @@ def setup 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 @@ def test_self_build 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/mri/rubygems/test_gem_ext_ext_conf_builder.rb b/test/mri/rubygems/test_gem_ext_ext_conf_builder.rb index 218c6f3d5e3..bc383e5540a 100644 --- a/test/mri/rubygems/test_gem_ext_ext_conf_builder.rb +++ b/test/mri/rubygems/test_gem_ext_ext_conf_builder.rb @@ -15,15 +15,12 @@ def setup 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 @@ def test_class_build 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 @@ def test_class_build_env_make 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 @@ def test_class_make 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/mri/rubygems/test_gem_ext_rake_builder.rb b/test/mri/rubygems/test_gem_ext_rake_builder.rb index bd72c1aa08c..68ad15b044f 100644 --- a/test/mri/rubygems/test_gem_ext_rake_builder.rb +++ b/test/mri/rubygems/test_gem_ext_rake_builder.rb @@ -29,7 +29,7 @@ def test_class_build 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/mri/rubygems/test_gem_gem_runner.rb b/test/mri/rubygems/test_gem_gem_runner.rb index 4fb205040c3..9cc2fac6191 100644 --- a/test/mri/rubygems/test_gem_gem_runner.rb +++ b/test/mri/rubygems/test_gem_gem_runner.rb @@ -82,17 +82,6 @@ def test_extract_build_args 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/mri/rubygems/test_gem_gemcutter_utilities.rb b/test/mri/rubygems/test_gem_gemcutter_utilities.rb index a3236e62768..9204dc5f20e 100644 --- a/test/mri/rubygems/test_gem_gemcutter_utilities.rb +++ b/test/mri/rubygems/test_gem_gemcutter_utilities.rb @@ -233,9 +233,10 @@ def test_sign_in_with_webauthn_enabled 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 @@ def test_sign_in_with_webauthn_enabled_with_error 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 @@ def test_sign_in_with_webauthn_enabled_with_polling 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 @@ def test_sign_in_with_webauthn_enabled_with_polling_failure 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/mri/rubygems/test_gem_install_update_options.rb b/test/mri/rubygems/test_gem_install_update_options.rb index 8fd5d9c543b..1e451dcb050 100644 --- a/test/mri/rubygems/test_gem_install_update_options.rb +++ b/test/mri/rubygems/test_gem_install_update_options.rb @@ -202,4 +202,16 @@ def test_minimal_deps 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/mri/rubygems/test_gem_installer.rb b/test/mri/rubygems/test_gem_installer.rb index 83e43c135fd..293fe1e823d 100644 --- a/test/mri/rubygems/test_gem_installer.rb +++ b/test/mri/rubygems/test_gem_installer.rb @@ -24,36 +24,35 @@ def test_app_script_text 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 @@ def test_check_executable_overwrite_format_executable 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 @@ -869,11 +868,11 @@ def test_use_plugin_immediately 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 +1246,7 @@ def test_install_does_not_leave_lockfile_for_binstub 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 +1477,7 @@ def test_install_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] @@ -1503,12 +1497,7 @@ def test_install_extension_dir_is_removed_on_reinstall @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 +1528,7 @@ def test_install_extension_dir_is_removed_on_reinstall 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 +1555,20 @@ def test_find_lib_file_after_install @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 void Init_a() { } C @@ -1618,17 +1600,12 @@ def test_install_extension_and_script @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 +1614,7 @@ def test_install_extension_and_script 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 +1640,13 @@ def test_install_extension_flat @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 +1673,13 @@ def test_install_extension_clean_intermediate_files @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 +1913,10 @@ def spec.validate(*args); 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 +2279,6 @@ def test_shebang_custom_with_expands_and_arguments 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 @@ -2450,119 +2412,6 @@ def test_dir 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? - end - - def test_default_gem_without_wrappers - installer = setup_base_installer - - FileUtils.rm_rf File.join(Gem.default_dir, "specifications") - - installer.wrappers = false - installer.options[:install_as_default] = true - installer.gem_dir = @spec.gem_dir - - 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_directory_exists util_inst_bindir - - installed_exec = File.join util_inst_bindir, "executable" - assert_path_exist installed_exec - - wrapper = File.read installed_exec - - 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) - end - end - - def test_default_gem_with_wrappers - installer = setup_base_installer - - installer.wrappers = true - installer.options[:install_as_default] = true - installer.gem_dir = @spec.gem_dir - - use_ui @ui do - installer.install - end - - assert_directory_exists util_inst_bindir - - installed_exec = File.join util_inst_bindir, "executable" - assert_path_exist installed_exec - - wrapper = File.read installed_exec - assert_match(/generated by RubyGems/, wrapper) - end - - def test_default_gem_with_exe_as_bindir - @spec = quick_gem "c" do |spec| - util_make_exec spec, "#!/usr/bin/ruby", "exe" - end - - util_build_gem @spec - - @spec.cache_file - - installer = util_installer @spec, @gemhome - - installer.options[:install_as_default] = true - installer.gem_dir = @spec.gem_dir - - use_ui @ui do - installer.install - end - - assert_directory_exists File.join(@spec.gem_dir, "exe") - installed_exec = File.join @spec.gem_dir, "exe", "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", "c-2.gemspec") - assert_equal Gem::Version.new("2"), default_spec.version - assert_equal ["exe/executable"], default_spec.files - 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 - - use_ui @ui do - installer.install - end - - assert_directory_exists File.join("#{@gemhome}2", "specifications") - assert_directory_exists File.join("#{@gemhome}2", "specifications", "default") - - 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 - def test_package_attribute gem = quick_gem "c" do |spec| util_make_exec spec, "#!/usr/bin/ruby", "exe" diff --git a/test/mri/rubygems/test_gem_name_tuple.rb b/test/mri/rubygems/test_gem_name_tuple.rb index bdb8181ce85..4876737c83d 100644 --- a/test/mri/rubygems/test_gem_name_tuple.rb +++ b/test/mri/rubygems/test_gem_name_tuple.rb @@ -57,4 +57,41 @@ def test_spaceship 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/mri/rubygems/test_gem_package.rb b/test/mri/rubygems/test_gem_package.rb index 8a9cc855802..2ad63acd03b 100644 --- a/test/mri/rubygems/test_gem_package.rb +++ b/test/mri/rubygems/test_gem_package.rb @@ -506,7 +506,7 @@ def test_extract_files 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? @@ -1247,71 +1247,25 @@ def test_verify_truncate # 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 + tar.rewind - package = Gem::Package.new(@gem) - package.instance_variable_set(:@files, []) - def package.load_spec(entry) - $spec_loaded = true - end - - package.verify_entry(entry) - - 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/mri/rubygems/test_gem_package_old.rb b/test/mri/rubygems/test_gem_package_old.rb index 7582dbedd40..e532fa25e1b 100644 --- a/test/mri/rubygems/test_gem_package_old.rb +++ b/test/mri/rubygems/test_gem_package_old.rb @@ -39,7 +39,7 @@ def test_extract_files 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/mri/rubygems/test_gem_package_tar_header_ractor.rb b/test/mri/rubygems/test_gem_package_tar_header_ractor.rb new file mode 100644 index 00000000000..57140648052 --- /dev/null +++ b/test/mri/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/mri/rubygems/test_gem_package_tar_writer.rb b/test/mri/rubygems/test_gem_package_tar_writer.rb index 751ceaca81c..cb9e0d26fab 100644 --- a/test/mri/rubygems/test_gem_package_tar_writer.rb +++ b/test/mri/rubygems/test_gem_package_tar_writer.rb @@ -33,7 +33,7 @@ def test_add_file 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 @@ def test_add_file_source_date_epoch 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 @@ def test_add_file_digest "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 @@ def test_add_file_digest_multiple "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 @@ def test_add_file_signer 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 @@ def test_add_file_signer 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 @@ def test_add_file_signer_empty 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 @@ def test_add_file_simple 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 @@ def test_add_file_simple_padding 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 @@ def test_mkdir 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/mri/rubygems/test_gem_platform.rb b/test/mri/rubygems/test_gem_platform.rb index 070c8007bc0..c1ff36772bd 100644 --- a/test/mri/rubygems/test_gem_platform.rb +++ b/test/mri/rubygems/test_gem_platform.rb @@ -11,15 +11,6 @@ def test_self_local 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 @@ def test_initialize "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 @@ def test_equals3_cpu 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 @@ def test_nil_cpu_arch_is_treated_as_universal 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 @@ def test_equals3_cpu_arm 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 @@ def test_equals3_version 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 @@ def test_inspect 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 @@ def assert_local_match(name) 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/mri/rubygems/test_gem_rdoc.rb b/test/mri/rubygems/test_gem_rdoc.rb index c4282b309c6..9ecbb7d8c39 100644 --- a/test/mri/rubygems/test_gem_rdoc.rb +++ b/test/mri/rubygems/test_gem_rdoc.rb @@ -18,19 +18,7 @@ def setup 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/mri/rubygems/test_gem_remote_fetcher.rb b/test/mri/rubygems/test_gem_remote_fetcher.rb index ca858cfda5d..5c1d89fad69 100644 --- a/test/mri/rubygems/test_gem_remote_fetcher.rb +++ b/test/mri/rubygems/test_gem_remote_fetcher.rb @@ -592,7 +592,7 @@ def test_yaml_error_on_size end end - def assert_error(exception_class=Exception) + def assert_error(exception_class = Exception) got_exception = false begin diff --git a/test/mri/rubygems/test_gem_remote_fetcher_s3.rb b/test/mri/rubygems/test_gem_remote_fetcher_s3.rb index fe7eb7ec01b..4a5acc5a866 100644 --- a/test/mri/rubygems/test_gem_remote_fetcher_s3.rb +++ b/test/mri/rubygems/test_gem_remote_fetcher_s3.rb @@ -8,6 +8,100 @@ 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 @@ def setup @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 @@ def test_fetch_s3_config_creds } 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 @@ def test_fetch_s3_config_creds_with_region } 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 @@ def test_fetch_s3_config_creds_with_token } 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 @@ def test_fetch_s3_env_creds } 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 @@ def test_fetch_s3_env_creds_with_region } 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 @@ def test_fetch_s3_env_creds_with_token } 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 @@ def test_fetch_s3_env_creds_with_token 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 @@ def test_fetch_s3_instance_profile_creds 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 @@ def test_fetch_s3_instance_profile_creds_with_region 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 @@ def test_fetch_s3_instance_profile_creds_with_token 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 @@ def refute_fetch_s3(url, expected_message) 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 @@ def test_fetch_s3_no_host } 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 @@ def test_fetch_s3_no_id 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 @@ def test_fetch_s3_no_secret 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/mri/rubygems/test_gem_request.rb b/test/mri/rubygems/test_gem_request.rb index 5e9b264dacf..cd0a416e79c 100644 --- a/test/mri/rubygems/test_gem_request.rb +++ b/test/mri/rubygems/test_gem_request.rb @@ -2,7 +2,6 @@ require_relative "helper" require "rubygems/request" -require "ostruct" unless Gem::HAVE_OPENSSL warn "Skipping Gem::Request tests. openssl not found." @@ -249,7 +248,7 @@ def test_fetch_basic_oauth_encoded 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 @@ -364,19 +363,19 @@ def test_verify_certificate 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 @@ -501,13 +500,22 @@ def util_save_version def util_stub_net_http(hash) old_client = Gem::Request::ConnectionPools.client - conn = Conn.new OpenStruct.new(hash) + conn = Conn.new Response.new(hash) Gem::Request::ConnectionPools.client = conn yield conn ensure Gem::Request::ConnectionPools.client = old_client end + class Response + attr_reader :code, :body, :message + + def initialize(hash) + @code = hash[:code] + @body = hash[:body] + end + end + class Conn attr_accessor :payload diff --git a/test/mri/rubygems/test_gem_request_connection_pools.rb b/test/mri/rubygems/test_gem_request_connection_pools.rb index 966447bff64..2860deabf71 100644 --- a/test/mri/rubygems/test_gem_request_connection_pools.rb +++ b/test/mri/rubygems/test_gem_request_connection_pools.rb @@ -148,4 +148,16 @@ def test_thread_waits_for_connection 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/mri/rubygems/test_gem_requirement.rb b/test/mri/rubygems/test_gem_requirement.rb index de0d11ec00b..00634dc7f43 100644 --- a/test/mri/rubygems/test_gem_requirement.rb +++ b/test/mri/rubygems/test_gem_requirement.rb @@ -137,11 +137,7 @@ def test_satisfied_by_eh_bang_equal 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 @@ def test_satisfied_by_eh_blank 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 @@ def test_satisfied_by_eh_equal 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 @@ def test_satisfied_by_eh_gt 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/mri/rubygems/test_gem_resolver_best_set.rb b/test/mri/rubygems/test_gem_resolver_best_set.rb index 02f542efc07..ac186884d15 100644 --- a/test/mri/rubygems/test_gem_resolver_best_set.rb +++ b/test/mri/rubygems/test_gem_resolver_best_set.rb @@ -31,6 +31,20 @@ def test_find_all 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/mri/rubygems/test_gem_safe_marshal.rb b/test/mri/rubygems/test_gem_safe_marshal.rb index 3cfa66fb274..bd15f4f796d 100644 --- a/test/mri/rubygems/test_gem_safe_marshal.rb +++ b/test/mri/rubygems/test_gem_safe_marshal.rb @@ -234,8 +234,6 @@ def test_floats 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] @@ -353,7 +351,19 @@ def test_gem_spec_unmarshall_license unmarshalled_spec = Gem::SafeMarshal.safe_load(Marshal.dump(spec)) - assert_equal ["MIT"], unmarshalled_spec.license + assert_equal ["MIT"], unmarshalled_spec.licenses + assert_equal "MIT", unmarshalled_spec.license + + spec = Gem::Specification.new do |s| + s.name = "hi" + s.version = "1.2.3" + s.licenses = ["MIT", "GPL2"] + end + + unmarshalled_spec = Gem::SafeMarshal.safe_load(Marshal.dump(spec)) + + assert_equal ["MIT", "GPL2"], unmarshalled_spec.licenses + assert_equal "MIT", unmarshalled_spec.license end def test_gem_spec_unmarshall_required_ruby_rubygems_version diff --git a/test/mri/rubygems/test_gem_security_trust_dir.rb b/test/mri/rubygems/test_gem_security_trust_dir.rb index cfde8e9d48a..bd3dfb86c23 100644 --- a/test/mri/rubygems/test_gem_security_trust_dir.rb +++ b/test/mri/rubygems/test_gem_security_trust_dir.rb @@ -56,7 +56,7 @@ def test_trust_cert 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 @@ def test_verify 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 @@ def test_verify_wrong_permissions @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/mri/rubygems/test_gem_source_list.rb b/test/mri/rubygems/test_gem_source_list.rb index 64353f8f90d..5327b14db8e 100644 --- a/test/mri/rubygems/test_gem_source_list.rb +++ b/test/mri/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 @@ def test_delete_a_source @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/mri/rubygems/test_gem_specification.rb b/test/mri/rubygems/test_gem_specification.rb index 43b649b9eae..e8c2c0eb470 100644 --- a/test/mri/rubygems/test_gem_specification.rb +++ b/test/mri/rubygems/test_gem_specification.rb @@ -16,7 +16,7 @@ class TestGemSpecification < Gem::TestCase 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 @@ class TestGemSpecification < Gem::TestCase 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 @@ def ext_spec(platform: Gem::Platform::RUBY) def assert_date(date) assert_kind_of Time, date assert_equal [0, 0, 0], [date.hour, date.min, date.sec] - assert_operator (Gem::Specification::TODAY..Time.now), :cover?, date + assert_equal Time.at(Gem::DEFAULT_SOURCE_DATE_EPOCH).utc, date end def setup @@ -564,7 +563,6 @@ def test_self_activate_unrelated # [B] ~> 1.0 # # and should resolve using b-1.0 - # TODO: move these to specification def test_self_activate_over a = util_spec "a", "1.0", "b" => ">= 1.0", "c" => "= 1.0" @@ -653,6 +651,17 @@ def test_self_activate_conflict end end + def test_self_activate_missing_deps_does_not_raise_nested_exceptions + a = util_spec "a", "1.0", "b" => ">= 1.0" + install_specs a + + e = assert_raise Gem::MissingSpecError do + a.activate + end + + refute e.cause + end + def test_self_all_equals a = util_spec "foo", "1", nil, "lib/foo.rb" @@ -1019,7 +1028,7 @@ def test_self_stubs_for_mult_platforms gem = "mingw" v = "1.1.1" - platforms = ["x86-mingw32", "x64-mingw32"] + platforms = ["x86-mingw32", "x64-mingw-ucrt"] # create specs platforms.each do |plat| @@ -1238,12 +1247,37 @@ def test_initialize_prerelease_version_before_name 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 @@ -2206,9 +2240,9 @@ def test_spaceship_name s1 = util_spec "a", "1" s2 = util_spec "b", "1" - assert_equal(-1, (s1 <=> s2)) - assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands - assert_equal(1, (s2 <=> s1)) + assert_equal(-1, s1 <=> s2) + assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + assert_equal(1, s2 <=> s1) end def test_spaceship_platform @@ -2217,18 +2251,18 @@ def test_spaceship_platform s.platform = Gem::Platform.new "x86-my_platform1" end - assert_equal(-1, (s1 <=> s2)) - assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands - assert_equal(1, (s2 <=> s1)) + assert_equal(-1, s1 <=> s2) + assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + assert_equal(1, s2 <=> s1) end def test_spaceship_version s1 = util_spec "a", "1" s2 = util_spec "a", "2" - assert_equal(-1, (s1 <=> s2)) - assert_equal(0, (s1 <=> s1)) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands - assert_equal(1, (s2 <=> s1)) + assert_equal(-1, s1 <=> s2) + assert_equal(0, s1 <=> s1) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + assert_equal(1, s2 <=> s1) end def test_spec_file @@ -2661,27 +2695,7 @@ def test_validate_dependencies @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 @@ -3654,8 +3668,6 @@ def test_version_change_reset_cache_file 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 diff --git a/test/mri/rubygems/test_gem_uri.rb b/test/mri/rubygems/test_gem_uri.rb index 1253ebc6de4..ce633c99b63 100644 --- a/test/mri/rubygems/test_gem_uri.rb +++ b/test/mri/rubygems/test_gem_uri.rb @@ -21,7 +21,7 @@ def test_redacted_with_token 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/mri/rubygems/test_gem_util.rb b/test/mri/rubygems/test_gem_util.rb index 608210a9031..9688d066db2 100644 --- a/test/mri/rubygems/test_gem_util.rb +++ b/test/mri/rubygems/test_gem_util.rb @@ -13,17 +13,6 @@ def test_class_popen 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/mri/rubygems/test_gem_version.rb b/test/mri/rubygems/test_gem_version.rb index cf771bc5a14..3987c620d0a 100644 --- a/test/mri/rubygems/test_gem_version.rb +++ b/test/mri/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 @@ def test_class_create 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 @@ def test_class_create 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,11 +154,14 @@ def test_spaceship 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 diff --git a/test/mri/rubygems/test_webauthn_listener.rb b/test/mri/rubygems/test_webauthn_listener.rb index 08edabceb26..ded41289285 100644 --- a/test/mri/rubygems/test_webauthn_listener.rb +++ b/test/mri/rubygems/test_webauthn_listener.rb @@ -17,7 +17,7 @@ def teardown 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/mri/socket/test_addrinfo.rb b/test/mri/socket/test_addrinfo.rb index c61764d76d6..0c9529090e4 100644 --- a/test/mri/socket/test_addrinfo.rb +++ b/test/mri/socket/test_addrinfo.rb @@ -360,6 +360,12 @@ def test_family_addrinfo 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/mri/socket/test_socket.rb b/test/mri/socket/test_socket.rb index 27e60b33357..c42527f3703 100644 --- a/test/mri/socket/test_socket.rb +++ b/test/mri/socket/test_socket.rb @@ -173,8 +173,11 @@ def random_port 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 @@ -489,7 +492,7 @@ def timestamp_retry_rw(s1, s2, t1, type) end while IO.select([r], nil, nil, 0.1).nil? n end - timeout = (defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? ? 120 : 30) # for --jit-wait + timeout = 30 assert_equal([[s1],[],[]], IO.select([s1], nil, nil, timeout)) msg, _, _, stamp = s1.recvmsg assert_equal("a", msg) @@ -513,7 +516,7 @@ def timestamp_retry_rw(s1, s2, t1, type) 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 @@ -906,7 +909,7 @@ def test_tcp_socket_resolv_timeout 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 +934,38 @@ def test_tcp_socket_resolv_timeout_with_connection_failure 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 +1011,7 @@ def test_tcp_socket_all_hostname_resolution_failed 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/mri/socket/test_tcp.rb b/test/mri/socket/test_tcp.rb index e6a41f56604..d689ab23765 100644 --- a/test/mri/socket/test_tcp.rb +++ b/test/mri/socket/test_tcp.rb @@ -18,6 +18,12 @@ def test_inspect 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 @@ def test_initialize_failure 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 @@ def test_initialize_resolv_timeout 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 @@ def test_initialize_resolv_timeout_with_connection_failure 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/mri/socket/test_unix.rb b/test/mri/socket/test_unix.rb index 3e7d85befcc..e239e3935b8 100644 --- a/test/mri/socket/test_unix.rb +++ b/test/mri/socket/test_unix.rb @@ -146,6 +146,7 @@ def test_fd_passing_n2 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 @@ def bound_unix_socket(klass) 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/mri/stringio/test_ractor.rb b/test/mri/stringio/test_ractor.rb index 4a2033bc1fb..6acf53fb0a0 100644 --- a/test/mri/stringio/test_ractor.rb +++ b/test/mri/stringio/test_ractor.rb @@ -8,6 +8,10 @@ def setup 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 @@ def test_ractor io.puts "def" "\0\0\0\0def\n" == io.string end - puts r.take + puts r.value end; end end diff --git a/test/mri/stringio/test_stringio.rb b/test/mri/stringio/test_stringio.rb index 64bc5f67c3b..024906261ef 100644 --- a/test/mri/stringio/test_stringio.rb +++ b/test/mri/stringio/test_stringio.rb @@ -14,6 +14,24 @@ def open_file(content) 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 @@ def test_null 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 @@ -465,6 +512,11 @@ def test_seek f.close unless f.closed? end + def test_seek_frozen_string + f = StringIO.new(-"1234") + assert_equal(0, f.seek(1)) + end + def test_each_byte f = StringIO.new("1234") a = [] @@ -944,7 +996,7 @@ def test_overflow 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 +1064,20 @@ def test_coderange_after_overwrite 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 +1096,13 @@ def test_chilled_string_string_set 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 private diff --git a/test/mri/strscan/test_ractor.rb b/test/mri/strscan/test_ractor.rb index 9a279d29296..a13fd8fd133 100644 --- a/test/mri/strscan/test_ractor.rb +++ b/test/mri/strscan/test_ractor.rb @@ -8,6 +8,10 @@ def setup 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 @@ def test_ractor s.scan(/\\w+/) ] end - puts r.take.compact + puts r.value.compact end; end end diff --git a/test/mri/strscan/test_stringscanner.rb b/test/mri/strscan/test_stringscanner.rb index 085a9113132..dd3663ea6a2 100644 --- a/test/mri/strscan/test_stringscanner.rb +++ b/test/mri/strscan/test_stringscanner.rb @@ -107,11 +107,6 @@ def test_const_Version 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) @@ -875,7 +870,7 @@ def test_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 diff --git a/test/mri/test_delegate.rb b/test/mri/test_delegate.rb index f7bedf37fbf..903265a8f7c 100644 --- a/test/mri/test_delegate.rb +++ b/test/mri/test_delegate.rb @@ -23,7 +23,7 @@ def test_delegate_class_block 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 @@ def test_override 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,13 @@ def test_DelegateClass_public_instance_method 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 IV < DelegateClass(Integer) attr_accessor :var @@ -181,8 +194,8 @@ def test_copy_frozen 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 +403,20 @@ def test(a, k:) [a, k]; end 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/mri/test_extlibs.rb b/test/mri/test_extlibs.rb index 9b6676416cf..122eca3f5c5 100644 --- a/test/mri/test_extlibs.rb +++ b/test/mri/test_extlibs.rb @@ -10,7 +10,7 @@ def self.check_existence(ext, add_msg = nil) add_msg = ". #{add_msg}" if add_msg log = "#{@extdir}/#{ext}/mkmf.log" define_method("test_existence_of_#{ext}") do - assert_separately([], <<-"end;", ignore_stderr: true) # do + assert_separately([], <<-"end;", ignore_stderr: true, timeout: 60) # do log = #{log.dump} msg = proc { "extension library `#{ext}' is not found#{add_msg}\n" << @@ -53,7 +53,6 @@ def windows? check_existence "etc" check_existence "fcntl" check_existence "fiber" - check_existence "fiddle" check_existence "io/console" check_existence "io/nonblock" check_existence "io/wait" @@ -69,6 +68,5 @@ def windows? check_existence "stringio" check_existence "strscan" check_existence "thread" - check_existence "win32ole" check_existence "zlib", "this may be false positive, but should assert because rubygems requires this" end diff --git a/test/mri/test_ipaddr.rb b/test/mri/test_ipaddr.rb index f2b7ed713f3..005927cd054 100644 --- a/test/mri/test_ipaddr.rb +++ b/test/mri/test_ipaddr.rb @@ -196,6 +196,15 @@ def test_ipv4_compat } 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_mapped @@ -399,6 +408,46 @@ def test_carrot 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::")) @@ -571,4 +620,16 @@ def test_hash 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) + end end diff --git a/test/mri/test_pp.rb b/test/mri/test_pp.rb index 2646846d8b3..4a273e6edd0 100644 --- a/test/mri/test_pp.rb +++ b/test/mri/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 @@ def test_list0123_11 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,20 @@ def a.to_s() "aaa" end 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#\n\z/, PP.pp(a, ''.dup)) + end + + def test_iv_hiding_via_ruby + a = Object.new + def a.instance_variables_to_inspect() [:@b] end + a.instance_eval { @a = "aaa"; @b = "bbb" } + assert_match(/\A#\n\z/, PP.pp(a, ''.dup)) + end + def test_basic_object a = BasicObject.new assert_match(/\A#\n\z/, PP.pp(a, ''.dup)) @@ -150,6 +171,12 @@ def test_hash 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 +185,14 @@ def test_struct 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 +257,6 @@ def test_hash 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 +269,41 @@ def test_hash_symbol_colon_key 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/mri/test_pty.rb b/test/mri/test_pty.rb index 1c0c6fb3e87..81b0d394ff2 100644 --- a/test/mri/test_pty.rb +++ b/test/mri/test_pty.rb @@ -12,7 +12,7 @@ class TestPTY < Test::Unit::TestCase RUBY = EnvUtil.rubybin def test_spawn_without_block - r, w, pid = PTY.spawn(RUBY, '-e', 'puts "a"') + r, w, pid = PTY.spawn(RUBY, '-e', 'puts "a"; sleep 0.1') rescue RuntimeError omit $! else @@ -24,7 +24,7 @@ def test_spawn_without_block end def test_spawn_with_block - PTY.spawn(RUBY, '-e', 'puts "b"') {|r,w,pid| + PTY.spawn(RUBY, '-e', 'puts "b"; sleep 0.1') {|r,w,pid| begin assert_equal("b\r\n", r.gets) ensure @@ -38,7 +38,7 @@ def test_spawn_with_block end def test_commandline - commandline = Shellwords.join([RUBY, '-e', 'puts "foo"']) + commandline = Shellwords.join([RUBY, '-e', 'puts "foo"; sleep 0.1']) PTY.spawn(commandline) {|r,w,pid| begin assert_equal("foo\r\n", r.gets) @@ -53,7 +53,7 @@ def test_commandline end def test_argv0 - PTY.spawn([RUBY, "argv0"], '-e', 'puts "bar"') {|r,w,pid| + PTY.spawn([RUBY, "argv0"], '-e', 'puts "bar"; sleep 0.1') {|r,w,pid| begin assert_equal("bar\r\n", r.gets) ensure diff --git a/test/mri/test_rbconfig.rb b/test/mri/test_rbconfig.rb index 1bbf01b9a68..e01264762da 100644 --- a/test/mri/test_rbconfig.rb +++ b/test/mri/test_rbconfig.rb @@ -51,4 +51,19 @@ def test_vendorarchdirs 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/mri/test_time.rb b/test/mri/test_time.rb index 23e8e104a16..55964d02fc1 100644 --- a/test/mri/test_time.rb +++ b/test/mri/test_time.rb @@ -74,7 +74,7 @@ def test_rfc2822_nonlinear 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/mri/test_timeout.rb b/test/mri/test_timeout.rb index 01156867b05..fead81f5719 100644 --- a/test/mri/test_timeout.rb +++ b/test/mri/test_timeout.rb @@ -4,6 +4,23 @@ 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 @@ -212,6 +229,24 @@ def o.each 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 +257,7 @@ def test_handle_interrupt ok = true Thread.handle_interrupt(Timeout::ExitException => :on_blocking) { sleep 0.2 + raise "unreachable" } } } @@ -229,6 +265,94 @@ def test_handle_interrupt 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 +398,59 @@ def test_handling_enclosed_threadgroup }.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 end diff --git a/test/mri/test_tmpdir.rb b/test/mri/test_tmpdir.rb index adc29183a83..c91fc334ed7 100644 --- a/test/mri/test_tmpdir.rb +++ b/test/mri/test_tmpdir.rb @@ -134,17 +134,32 @@ def assert_mktmpdir_traversal 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/mri/test_unicode_normalize.rb b/test/mri/test_unicode_normalize.rb index 8789ed92d2a..dd06d271310 100644 --- a/test/mri/test_unicode_normalize.rb +++ b/test/mri/test_unicode_normalize.rb @@ -209,4 +209,32 @@ def test_us_ascii 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/mri/uri/test_common.rb b/test/mri/uri/test_common.rb index 6326aec561a..569264005a8 100644 --- a/test/mri/uri/test_common.rb +++ b/test/mri/uri/test_common.rb @@ -31,12 +31,14 @@ def test_fallback_constants 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 @@ def test_parser_switch 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 @@ def test_ractor 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 @@ def test_register_scheme_lowercase 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/mri/uri/test_ftp.rb b/test/mri/uri/test_ftp.rb index f45bb0667c7..3ad7864490e 100644 --- a/test/mri/uri/test_ftp.rb +++ b/test/mri/uri/test_ftp.rb @@ -33,11 +33,11 @@ def test_paths # 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 @@ def test_assemble # 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/mri/uri/test_generic.rb b/test/mri/uri/test_generic.rb index 8209363b82e..94eea71b511 100644 --- a/test/mri/uri/test_generic.rb +++ b/test/mri/uri/test_generic.rb @@ -175,6 +175,17 @@ def test_parse # 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 @@ def test_merge 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 @@ def test_merge 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_rfc3986_examples 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 @@ def test_join 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 @@ def test_set_component 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 @@ def test_hierarchical 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 @@ def test_ipv6 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 @@ def test_build 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/mri/uri/test_http.rb b/test/mri/uri/test_http.rb index e937b1a26b1..8816d201758 100644 --- a/test/mri/uri/test_http.rb +++ b/test/mri/uri/test_http.rb @@ -19,6 +19,10 @@ def test_build 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 @@ def test_normalize 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/mri/uri/test_mailto.rb b/test/mri/uri/test_mailto.rb index e7d31421982..6cd33529784 100644 --- a/test/mri/uri/test_mailto.rb +++ b/test/mri/uri/test_mailto.rb @@ -141,6 +141,21 @@ def test_initializer 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 @@ def test_check_to 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/mri/uri/test_parser.rb b/test/mri/uri/test_parser.rb index f455a5cc9bf..c14824f5e84 100644 --- a/test/mri/uri/test_parser.rb +++ b/test/mri/uri/test_parser.rb @@ -20,17 +20,17 @@ def test_compare 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 @@ def test_rfc3986_port_check 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/mri/uri/test_ws.rb b/test/mri/uri/test_ws.rb index f3918f617c4..d63ebd4a460 100644 --- a/test/mri/uri/test_ws.rb +++ b/test/mri/uri/test_ws.rb @@ -31,19 +31,19 @@ def test_normalize 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/mri/uri/test_wss.rb b/test/mri/uri/test_wss.rb index 13a25830591..cbef327cc65 100644 --- a/test/mri/uri/test_wss.rb +++ b/test/mri/uri/test_wss.rb @@ -31,19 +31,19 @@ def test_normalize 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/mri/yaml/test_store.rb b/test/mri/yaml/test_store.rb index d3895302715..d4eba251fde 100644 --- a/test/mri/yaml/test_store.rb +++ b/test/mri/yaml/test_store.rb @@ -5,8 +5,12 @@ class YAMLStoreTest < Test::Unit::TestCase def setup - @yaml_store_file = File.join(Dir.tmpdir, "yaml_store.tmp.#{Process.pid}") - @yaml_store = YAML::Store.new(@yaml_store_file) + if defined?(::PStore) + @yaml_store_file = File.join(Dir.tmpdir, "yaml_store.tmp.#{Process.pid}") + @yaml_store = YAML::Store.new(@yaml_store_file) + else + omit "PStore is not available" + end end def teardown @@ -133,7 +137,7 @@ def test_nested_transaction_raises_error def test_yaml_store_files_are_accessed_as_binary_files bug5311 = '[ruby-core:39503]' n = 128 - assert_in_out_err(["-Eutf-8:utf-8", "-ryaml/store", "-", @yaml_store_file], <<-SRC, [bug5311], [], bug5311, timeout: 60) + assert_in_out_err(["-Eutf-8:utf-8", "-rrubygems", "-ryaml/store", "-", @yaml_store_file], <<-SRC, [bug5311], [], bug5311, timeout: 60) @yaml_store = YAML::Store.new(ARGV[0]) (1..#{n}).each do |i| @yaml_store.transaction {@yaml_store["Key\#{i}"] = "value \#{i}"} @@ -177,4 +181,4 @@ def test_with_options end assert_equal(indentation_3_yaml, File.read(@yaml_store_file), bug12800) end -end if defined?(::YAML::Store) +end diff --git a/test/mri/zlib/test_zlib.rb b/test/mri/zlib/test_zlib.rb index 5a8463ad8e7..bedf243b3e5 100644 --- a/test/mri/zlib/test_zlib.rb +++ b/test/mri/zlib/test_zlib.rb @@ -9,6 +9,9 @@ begin require 'zlib' rescue LoadError +else + z = "/zlib.#{RbConfig::CONFIG["DLEXT"]}" + LOADED_ZLIB, = $".select {|f| f.end_with?(z)} end if defined? Zlib @@ -1525,7 +1528,7 @@ def test_gunzip_encoding 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)} From 79cf0d45fcb207c4930d32cdc18e2a8bb61ed25e Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 30 Dec 2025 17:20:45 -0600 Subject: [PATCH 041/168] Exclude hanging Fiber test --- test/mri/excludes/TestFiber.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/mri/excludes/TestFiber.rb b/test/mri/excludes/TestFiber.rb index 83b1bbedf72..400f0fde8a1 100644 --- a/test/mri/excludes/TestFiber.rb +++ b/test/mri/excludes/TestFiber.rb @@ -1,4 +1,5 @@ exclude :test_error, "work in progress" +exclude :test_exit_in_fiber, "raises NPE and then hangs" exclude :test_fatal_in_fiber, "work in progress" exclude :test_no_valid_cfp, "raises NPE rather than detecting bad context and raising a Ruby error" exclude :test_prohibit_transfer_to_resuming_fiber, "FiberError expected" From 2ce8be17f059a00e6a49ccc78055b148a0de2d1e Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 30 Dec 2025 18:10:58 -0600 Subject: [PATCH 042/168] Add Array#find and Array#rfind See https://github.com/ruby/ruby/pull/15189 --- core/src/main/java/org/jruby/RubyArray.java | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/src/main/java/org/jruby/RubyArray.java b/core/src/main/java/org/jruby/RubyArray.java index adfeb8f9556..526826e4651 100644 --- a/core/src/main/java/org/jruby/RubyArray.java +++ b/core/src/main/java/org/jruby/RubyArray.java @@ -5260,6 +5260,12 @@ public IRubyObject sumCommon(final ThreadContext context, IRubyObject init, fina return result; } + @JRubyMethod(name = {"detect", "find"}) + public IRubyObject find(ThreadContext context, Block block) { + return find(context, context.nil, block); + } + + @JRubyMethod(name = {"detect", "find"}) public IRubyObject find(ThreadContext context, IRubyObject ifnone, Block block) { CachingCallSite self_each = sites(context).self_each; if (!self_each.isBuiltin(this)) return RubyEnumerable.detectCommon(context, self_each, this, block); @@ -5299,6 +5305,25 @@ public IRubyObject detectCommon(ThreadContext context, IRubyObject ifnone, Block return ifnone != null && !ifnone.isNil() ? sites(context).call.call(context, ifnone, ifnone) : context.nil; } + @JRubyMethod + public IRubyObject rfind(ThreadContext context, Block block) { + return rfind(context, context.nil, block); + } + + @JRubyMethod + public IRubyObject rfind(ThreadContext context, IRubyObject ifnone, Block block) { + CachingCallSite self_each = sites(context).self_each; + if (!self_each.isBuiltin(this)) return RubyEnumerable.detectCommon(context, self_each, this, block); + + for (int i = realLength - 1; i >= 0; i--) { + IRubyObject value = eltOk(i); + + if (block.yield(context, value).isTrue()) return value; + } + + return ifnone != null && !ifnone.isNil() ? sites(context).call.call(context, ifnone, ifnone) : context.nil; + } + @Deprecated(since = "10.0.0.0", forRemoval = true) @SuppressWarnings("removal") public static void marshalTo(RubyArray array, org.jruby.runtime.marshal.MarshalStream output) throws IOException { From a5ed3cfcf8fe7078c84b4ed63d0a4dd6ad518388 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 30 Dec 2025 22:21:58 -0600 Subject: [PATCH 043/168] Implement 128-bit ints and range checks for IO::Buffer Plus a bit of misc cleanup. --- .../src/main/java/org/jruby/RubyIOBuffer.java | 213 ++++++++++++++---- core/src/main/java/org/jruby/api/Convert.java | 15 ++ 2 files changed, 180 insertions(+), 48 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyIOBuffer.java b/core/src/main/java/org/jruby/RubyIOBuffer.java index 43a8b0b5c95..6e86f163252 100644 --- a/core/src/main/java/org/jruby/RubyIOBuffer.java +++ b/core/src/main/java/org/jruby/RubyIOBuffer.java @@ -33,7 +33,8 @@ public class RubyIOBuffer extends RubyObject { - public static final Runtime FFI_RUNTIME = Runtime.getSystemRuntime(); + private static final BigInteger MIN_S128 = BigInteger.ONE.shiftLeft(127).negate(); + private static final BigInteger MAX_S128 = BigInteger.ONE.shiftLeft(127).subtract(BigInteger.ONE); public static RubyClass createIOBufferClass(ThreadContext context, RubyClass Object, RubyModule Comparable, RubyClass IO) { RubyClass IOBuffer = IO.defineClassUnder(context, "Buffer", Object, RubyIOBuffer::new). @@ -745,6 +746,7 @@ public IRubyObject clear(ThreadContext context, IRubyObject value) { public IRubyObject clear(ThreadContext context, IRubyObject _value, IRubyObject _offset) { int value = toInt(context, _value); int offset = toInt(context, _offset); + if (offset > size) throw argumentError(context, "The given offset is bigger than the buffer size!"); return clear(context, value, offset, size - offset); } @@ -759,7 +761,7 @@ public IRubyObject clear(ThreadContext context, IRubyObject _value, IRubyObject // MRI: rb_io_buffer_clear private IRubyObject clear(ThreadContext context, int value, int offset, int length) { ByteBuffer buffer = getBufferForWriting(context); - if (offset + length > size) throw argumentError(context, "The given offset + length out of bounds!"); + if (size - length < offset) throw argumentError(context, "The given offset + length out of bounds!"); if (buffer.hasArray()) Arrays.fill(buffer.array(), offset, offset + length, (byte) value); @@ -821,17 +823,19 @@ private boolean freeInternal(ThreadContext context) { @JRubyMethod(name = "size_of", meta = true) public static IRubyObject size_of(ThreadContext context, IRubyObject self, IRubyObject dataType) { - if (dataType instanceof RubyArray) { - long total = 0; - RubyArray array = (RubyArray) dataType; + long total; + if (dataType instanceof RubyArray array) { + total = 0; int size = array.size(); for (int i = 0; i < size; i++) { IRubyObject elt = array.eltOk(i); - total += getDataType(elt).type.size(); + total += getDataType(elt).size; } + } else { + total = getDataType(dataType).size; } - return asFixnum(context, getDataType(dataType).type.size()); + return asFixnum(context, total); } private boolean isBigEndian() { @@ -946,6 +950,100 @@ private static void writeLong(ThreadContext context, ByteBuffer buffer, int offs buffer.putLong(offset, Long.reverseBytes(value)); } + private static BigInteger readLongLong(ThreadContext context, ByteBuffer buffer, int offset, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + // Read 16 bytes (big-endian) + byte[] bytes = new byte[16]; + for (int i = 0; i < 16; i++) { + bytes[i] = buffer.get(offset + i); + } + + return new BigInteger(bytes); + } + + byte[] bytes = new byte[16]; + for (int i = 0; i < 16; i++) { + bytes[15 - i] = buffer.get(offset + i); + } + + return new BigInteger(bytes); + } + + private static BigInteger readUnsignedLongLong(ThreadContext context, ByteBuffer buffer, int offset, ByteOrder order) { + // Read 16 bytes (big-endian) + byte[] bytes = new byte[16]; + buffer.get(offset, bytes); + + if (order == ByteOrder.LITTLE_ENDIAN) { + reverseBytes(bytes); + } + + return new BigInteger(1, bytes); + } + + private static byte[] reverseBytes(byte[] bytes) { + for (int i = 0; i < bytes.length / 2; i++) { + byte tmp = bytes[i]; + bytes[i] = bytes[bytes.length - i - 1]; + bytes[bytes.length - i - 1] = tmp; + } + return bytes; + } + + private static void writeUnsignedLongLong(ThreadContext context, ByteBuffer buffer, int offset, ByteOrder order, BigInteger value) { + if (value.signum() < 0) { + throw argumentError(context, "Value must be non-negative"); + } + if (value.bitLength() > 128) { + throw argumentError(context, "Value exceeds 128 bits"); + } + + writeLongLongBytes(buffer, offset, order, value.signum(), value.toByteArray()); + } + + private static void writeLongLong(ThreadContext context, ByteBuffer buffer, int offset, ByteOrder order, BigInteger value) { + if (value.compareTo(MIN_S128) < 0 || value.compareTo(MAX_S128) > 0) { + throw argumentError(context, "Value exceeds signed 128-bit range"); + } + + writeLongLongBytes(buffer, offset, order, value.signum(), value.toByteArray()); + } + + private static void writeLongLongBytes(ByteBuffer buffer, int offset, ByteOrder order, int signum, byte[] bytes) { + // Convert to byte array (big-endian, two's complement) + int byteLength = bytes.length; + int paddingNeeded = 16 - byteLength; + + // Sign-extend padding byte if needed + byte paddingByte = (signum < 0) ? (byte) 0xFF : (byte) 0x00; + + if (order == ByteOrder.BIG_ENDIAN) {// Validate the value fits in 128 bits (signed range) + if (byteLength <= 16) { + writePadding(buffer, offset, paddingNeeded, paddingByte); + buffer.put(offset + paddingNeeded, bytes); + } else { + // drop sign byte + buffer.put(offset, bytes, 1, bytes.length - 1); + } + return; + } + + reverseBytes(bytes); + if (byteLength <= 16) { + buffer.put(offset, bytes); + writePadding(buffer, offset + byteLength, paddingNeeded, paddingByte); + } else { + // drop sign byte + buffer.put(offset, bytes, 0, 16); + } + } + + private static void writePadding(ByteBuffer buffer, int offset, int paddingNeeded, byte paddingByte) { + for (int i = 0; i < paddingNeeded; i++) { + buffer.put(offset + i, paddingByte); + } + } + private static void writeUnsignedLong(ThreadContext context, ByteBuffer buffer, int offset, ByteOrder order, long value) { writeLong(context, buffer, offset, order, value); } @@ -1004,6 +1102,10 @@ private static long unwrapUnsignedLong(IRubyObject value) { return RubyNumeric.num2ulong(value); } + private static BigInteger unwrapUnsignedLongLong(ThreadContext context, IRubyObject value) { + return value.convertToInteger().asBigInteger(context); + } + @JRubyMethod(name = "get_value") public IRubyObject get_value(ThreadContext context, IRubyObject type, IRubyObject _offset) { ByteBuffer buffer = getBufferForReading(context); @@ -1016,7 +1118,8 @@ public IRubyObject get_value(ThreadContext context, IRubyObject type, IRubyObjec } private static IRubyObject getValue(ThreadContext context, ByteBuffer buffer, int size, DataType dataType, int offset) { - // TODO: validate size + if (offset < 0) throw argumentError(context, "Offset can't be negative!"); + if (offset == size || size - dataType.size < offset) throw argumentError(context, "Type extends beyond end of buffer! (offset=" + offset + " > size=" + size + ")"); switch (dataType) { case S8: @@ -1047,6 +1150,14 @@ private static IRubyObject getValue(ThreadContext context, ByteBuffer buffer, in return wrap(context, readLong(context, buffer, offset, ByteOrder.LITTLE_ENDIAN)); case S64: return wrap(context, readLong(context, buffer, offset, ByteOrder.BIG_ENDIAN)); + case u128: + return wrap(context, readUnsignedLongLong(context, buffer, offset, ByteOrder.LITTLE_ENDIAN)); + case U128: + return wrap(context, readUnsignedLongLong(context, buffer, offset, ByteOrder.BIG_ENDIAN)); + case s128: + return wrap(context, readLongLong(context, buffer, offset, ByteOrder.LITTLE_ENDIAN)); + case S128: + return wrap(context, readLongLong(context, buffer, offset, ByteOrder.BIG_ENDIAN)); case f32: return wrap(context, readFloat(context, buffer, offset, ByteOrder.LITTLE_ENDIAN)); case F32: @@ -1078,7 +1189,7 @@ public IRubyObject get_values(ThreadContext context, IRubyObject dataTypes, IRub DataType dataType = getDataType(type); IRubyObject value = getValue(context, buffer, size, dataType, offset); - offset += dataType.type.size(); + offset += dataType.size; values.append(context, value); } @@ -1123,7 +1234,7 @@ private IRubyObject each(ThreadContext context, ByteBuffer buffer, DataType data for (int i = 0 ; i < count; i++) { int currentOffset = offset; IRubyObject value = getValue(context, buffer, size, dataType, offset); - offset += dataType.type.size(); + offset += dataType.size; block.yieldSpecific(context, asFixnum(context, currentOffset), value); } @@ -1162,7 +1273,7 @@ private RubyArray values(ThreadContext context, ByteBuffer buffer, DataType data for (int i = 0 ; i < count; i++) { IRubyObject value = getValue(context, buffer, size, dataType, offset); - offset += dataType.type.size(); + offset += dataType.size; values.push(context, value); } @@ -1200,8 +1311,6 @@ public IRubyObject each_byte(ThreadContext context, IRubyObject _offset, IRubyOb } private IRubyObject eachByte(ThreadContext context, ByteBuffer buffer, int offset, int count, Block block) { - Ruby runtime = context.runtime; - for (int i = 0 ; i < count; i++) { IRubyObject value = wrap(context, readByte(context, buffer, offset + i)); block.yieldSpecific(context, value); @@ -1211,7 +1320,9 @@ private IRubyObject eachByte(ThreadContext context, ByteBuffer buffer, int offse } private static void setValue(ThreadContext context, ByteBuffer buffer, int size, DataType dataType, int offset, IRubyObject value) { - // TODO: validate size + if (offset < 0) throw argumentError(context, "Offset can't be negative!"); + if (size == 0) throw argumentError(context, "Size can't be zero!"); + if (offset == size || size - dataType.size < offset) throw argumentError(context, "Specified offset+length is bigger than the buffer size!"); switch (dataType) { case S8: @@ -1256,6 +1367,18 @@ private static void setValue(ThreadContext context, ByteBuffer buffer, int size, case S64: writeLong(context, buffer, offset, ByteOrder.BIG_ENDIAN, toLong(context, value)); return; + case u128: + writeUnsignedLongLong(context, buffer, offset, ByteOrder.LITTLE_ENDIAN, unwrapUnsignedLongLong(context, value)); + return; + case U128: + writeUnsignedLongLong(context, buffer, offset, ByteOrder.BIG_ENDIAN, unwrapUnsignedLongLong(context, value)); + return; + case s128: + writeLongLong(context, buffer, offset, ByteOrder.LITTLE_ENDIAN, toLongLong(context, value)); + return; + case S128: + writeLongLong(context, buffer, offset, ByteOrder.BIG_ENDIAN, toLongLong(context, value)); + return; case f32: writeFloat(context, buffer, offset, ByteOrder.LITTLE_ENDIAN, (float) unwrapDouble(context, value)); return; @@ -1283,7 +1406,7 @@ public IRubyObject set_value(ThreadContext context, IRubyObject _dataType, IRuby setValue(context, buffer, size, dataType, offset, _value); - return asFixnum(context, offset + dataType.type.size()); + return asFixnum(context, offset + dataType.size); } @JRubyMethod(name = "set_values") @@ -1313,7 +1436,7 @@ public IRubyObject set_values(ThreadContext context, IRubyObject _dataTypes, IRu setValue(context, buffer, size, dataType, offset, value); - offset += dataType.type.size(); + offset += dataType.size; } return asFixnum(context, offset); @@ -2067,44 +2190,38 @@ private static IRubyObject pwriteInternal(ThreadContext context, RubyIO io, Byte } enum DataType { - U8(NativeType.UCHAR, BIG_ENDIAN), - S8(NativeType.SCHAR, BIG_ENDIAN), - u16(NativeType.USHORT, LITTLE_ENDIAN), - U16(NativeType.USHORT, BIG_ENDIAN), - s16(NativeType.SSHORT, LITTLE_ENDIAN), - S16(NativeType.SSHORT, BIG_ENDIAN), - u32(NativeType.UINT, LITTLE_ENDIAN), - U32(NativeType.UINT, BIG_ENDIAN), - s32(NativeType.SINT, LITTLE_ENDIAN), - S32(NativeType.SINT, BIG_ENDIAN), - u64(NativeType.ULONG, LITTLE_ENDIAN), - U64(NativeType.ULONG, BIG_ENDIAN), - s64(NativeType.SLONG, LITTLE_ENDIAN), - S64(NativeType.SLONG, BIG_ENDIAN), - f32(NativeType.FLOAT,LITTLE_ENDIAN), - F32(NativeType.FLOAT, BIG_ENDIAN), - f64(NativeType.DOUBLE, LITTLE_ENDIAN), - F64(NativeType.DOUBLE, BIG_ENDIAN); - - DataType(NativeType type, int endian) { - this.type = FFI_RUNTIME.findType(type); + U8(1, BIG_ENDIAN), + S8(1, BIG_ENDIAN), + u16(2, LITTLE_ENDIAN), + U16(2, BIG_ENDIAN), + s16(2, LITTLE_ENDIAN), + S16(2, BIG_ENDIAN), + u32(4, LITTLE_ENDIAN), + U32(4, BIG_ENDIAN), + s32(4, LITTLE_ENDIAN), + S32(4, BIG_ENDIAN), + u64(8, LITTLE_ENDIAN), + U64(8, BIG_ENDIAN), + s64(8, LITTLE_ENDIAN), + S64(8, BIG_ENDIAN), + u128(16, LITTLE_ENDIAN), + U128(16, BIG_ENDIAN), + s128(16, LITTLE_ENDIAN), + S128(16, BIG_ENDIAN), + f32(4, LITTLE_ENDIAN), + F32(4, BIG_ENDIAN), + f64(8, LITTLE_ENDIAN), + F64(8, BIG_ENDIAN); + + DataType(int size, int endian) { + this.size = size; this.endian = endian; } - private final Type type; + private final int size; private final int endian; } - private static long swapAsShort(long l) { - short s = (short) l; - - return (s >>> 8) | (int) (s << 8); - } - - private static short swap(short s) { - return (short) ((s >>> 8) | (s << 8)); - } - private static long swapAsInt(long l) { int s = (int) l; diff --git a/core/src/main/java/org/jruby/api/Convert.java b/core/src/main/java/org/jruby/api/Convert.java index 9e5fb7edd77..9e37bce0f39 100644 --- a/core/src/main/java/org/jruby/api/Convert.java +++ b/core/src/main/java/org/jruby/api/Convert.java @@ -539,6 +539,15 @@ public static long toLong(ThreadContext context, IRubyObject arg) { }; } + public static BigInteger toLongLong(ThreadContext context, IRubyObject arg) { + return switch (arg) { + case RubyFixnum fixnum -> BigInteger.valueOf(fixnum.getValue()); + case RubyFloat flote -> BigInteger.valueOf(toLong(context, flote)); + case RubyBignum bignum -> bignum.getValue(); + default -> toLongLongOther(context, arg); + }; + } + public static long toLong(ThreadContext context, RubyBignum value) { BigInteger big = value.getValue(); @@ -566,6 +575,12 @@ private static long toLongOther(ThreadContext context, IRubyObject arg) { return toLong(context, TypeConverter.convertToType(arg, integerClass(context), "to_int")); } + private static BigInteger toLongLongOther(ThreadContext context, IRubyObject arg) { + if (arg.isNil()) throw typeError(context, "no implicit conversion from nil to integer"); + + return toLongLong(context, TypeConverter.convertToType(arg, integerClass(context), "to_int")); + } + /** * Safely convert a Ruby Numeric into a java long value. Raising if the value will not fit. * @param context the current thread context From d3b889594bd2c49dfc9c50019f14acef562d1c71 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 00:29:33 -0600 Subject: [PATCH 044/168] Infinite Range#to_set should raise RangeError --- core/src/main/java/org/jruby/ext/set/RubySet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index c82347fbd59..e3a8a8fb07d 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -259,7 +259,7 @@ public IRubyObject initialize(final ThreadContext context, IRubyObject enume, fi if (enume.getType().isKindOfModule(context.runtime.getEnumerable()) && enume.respondsTo("size")) { IRubyObject size = enume.callMethod(context, "size"); if (size instanceof RubyFloat flote && flote.isInfinite()) { - throw Error.argumentError(context, "cannot initialize Set from an object with infinite size"); + throw Error.rangeError(context, "cannot initialize Set from an object with infinite size"); } } allocHash(context); From 0f9727e18499f69732256379e3278c922c01e973 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 00:30:03 -0600 Subject: [PATCH 045/168] Implement Range#max with beginless and size Also fixes beginless Range#last for integer end. --- core/src/main/java/org/jruby/RubyRange.java | 81 +++++++++++++++---- .../java/org/jruby/runtime/JavaSites.java | 3 +- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyRange.java b/core/src/main/java/org/jruby/RubyRange.java index e38f35bcdc4..23c98090dd8 100644 --- a/core/src/main/java/org/jruby/RubyRange.java +++ b/core/src/main/java/org/jruby/RubyRange.java @@ -46,10 +46,12 @@ import org.jruby.api.API; import org.jruby.api.JRubyAPI; import org.jruby.exceptions.JumpException; +import org.jruby.exceptions.RuntimeError; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; import org.jruby.runtime.BlockCallback; import org.jruby.runtime.CallBlock; +import org.jruby.runtime.CallBlock19; import org.jruby.runtime.CallSite; import org.jruby.runtime.ClassIndex; import org.jruby.runtime.Helpers; @@ -1154,7 +1156,7 @@ public IRubyObject max(ThreadContext context, Block block) { if (cmp == 0) return context.nil; - if (!(begin instanceof RubyInteger)) throw typeError(context, "cannot exclude end value with non Integer begin value"); + if (!begin.isNil() && !(begin instanceof RubyInteger)) throw typeError(context, "cannot exclude end value with non Integer begin value"); return end instanceof RubyFixnum fixnum ? asFixnum(context, fixnum.getValue() - 1) : @@ -1166,10 +1168,46 @@ public IRubyObject max(ThreadContext context, Block block) { @JRubyMethod(frame = true) public IRubyObject max(ThreadContext context, IRubyObject arg, Block block) { - if (isEndless) throw rangeError(context, "cannot get the maximum element of endless range"); - return Helpers.invokeSuper(context, this, arg, block); + IRubyObject e = end; + boolean nm = e instanceof RubyNumeric; + + if (isEndless) throw rangeError(context, "cannot get the maximum of endless range"); + + IRubyObject b = begin; + + if (block.isGiven() || (isExclusive && !nm)) { + if (b.isNil()) { + throw rangeError(context, "cannot get the maximum of beginless range with custom comparison method"); + } + return Helpers.invokeSuper(context, this, arg, block); + } + + Break[] pBreak = {null}; + int[] data = {toInt(context, arg)}; + RubyArray ary = RubyArray.newArray(context.runtime, toInt(context, arg)); + try { + sites(context).reverse_each.call(context, this, this, CallBlock19.newCallClosure(this, this.getMetaClass(), Signature.TWO_ARGUMENTS, (ctx, args, block1) -> { + int n = data[0]; + + if (n <= 0) { + throw pBreak[0] = new Break(); + } + ary.push(ctx, args[0]); + n--; + data[0] = n; + return ctx.nil; + }, context)); + } catch (Break brk) { + if (pBreak[0] != brk) throw brk; + } + return ary; } + private static class Break extends RuntimeException { + @Override + public Throwable fillInStackTrace() {return this;} + }; + @JRubyMethod public IRubyObject first(ThreadContext context) { return first(context, null); @@ -1255,29 +1293,44 @@ public IRubyObject end(ThreadContext context) { public IRubyObject last(ThreadContext context, IRubyObject arg) { if (isEndless) throw rangeError(context, "cannot get the last element of endless range"); - if (begin instanceof RubyInteger && end instanceof RubyInteger - && getMetaClass().checkMethodBasicDefinition("each")) { - return intRangeLast(context, arg); + if (integerEndOptimizable(context)) { + return intRangeLast(context, arg); } return ((RubyArray) RubyKernel.new_array(context, this, this)).last(context, arg); } + private boolean integerEndOptimizable(ThreadContext context) { + IRubyObject b = begin; + if (!b.isNil() && !(b instanceof RubyInteger)) return false; + IRubyObject e = end; + if (!(e instanceof RubyInteger)) return false; + if (sites(context).each.isBuiltin(this)) return true; + return false; + } + // MRI rb_int_range_last private RubyArray intRangeLast(ThreadContext context, IRubyObject arg) { RubyFixnum one = asFixnum(context, 1); RubyInteger e = (RubyInteger) end; - RubyInteger len; - RubyInteger len1 = (RubyInteger) e.op_minus(context, begin); + RubyInteger len = null; - if (isExclusive) { - e = (RubyInteger) e.op_minus(context, one); - len = len1; + if (!begin.isNil()) { + RubyInteger len1 = (RubyInteger) e.op_minus(context, begin); + + if (isExclusive) { + e = (RubyInteger) e.op_minus(context, one); + len = len1; + } else { + len = (RubyInteger) len1.op_plus(context, one); + } } else { - len = (RubyInteger) len1.op_plus(context, one); + if (isExclusive) { + e = (RubyInteger) e.op_minus(context, one); + } } - if (len.isZero(context) || Numeric.f_negative_p(context, len)) { + if (len != null && (len.isZero(context) || Numeric.f_negative_p(context, len))) { return newEmptyArray(context); } @@ -1285,7 +1338,7 @@ private RubyArray intRangeLast(ThreadContext context, IRubyObject arg) { if (n < 0) throw argumentError(context, "negative array size"); RubyInteger nv = asFixnum(context, n); - if (Numeric.f_gt_p(context, nv, len)) { + if (!begin.isNil() && Numeric.f_gt_p(context, nv, len)) { nv = len; n = toLong(context, nv); } diff --git a/core/src/main/java/org/jruby/runtime/JavaSites.java b/core/src/main/java/org/jruby/runtime/JavaSites.java index 57edbf2346e..3deba0514e0 100644 --- a/core/src/main/java/org/jruby/runtime/JavaSites.java +++ b/core/src/main/java/org/jruby/runtime/JavaSites.java @@ -470,7 +470,8 @@ public static class RangeSites { public final CallSite op_cmp = new FunctionalCachingCallSite("<=>"); public final CallSite op_gt = new FunctionalCachingCallSite(">"); public final CallSite op_lt = new FunctionalCachingCallSite("<"); - public final CallSite each = new FunctionalCachingCallSite("each"); + public final CachingCallSite each = new FunctionalCachingCallSite("each"); + public final CallSite reverse_each = new FunctionalCachingCallSite("reverse_each"); } public static class WarningSites { From fbb7fbb27728182c54ab8d03643de291e22a8492 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 00:59:40 -0600 Subject: [PATCH 046/168] Exclude some MRI-specific tests --- test/mri/excludes/TestGc.rb | 1 + test/mri/excludes/TestRegexp.rb | 1 + test/mri/excludes/TestRubyOptimization.rb | 1 + test/mri/excludes/TestString.rb | 3 +++ test/mri/excludes/TestString2.rb | 3 +++ 5 files changed, 9 insertions(+) diff --git a/test/mri/excludes/TestGc.rb b/test/mri/excludes/TestGc.rb index 0e283126c04..371c24c1c08 100644 --- a/test/mri/excludes/TestGc.rb +++ b/test/mri/excludes/TestGc.rb @@ -13,6 +13,7 @@ exclude :test_gc_internals, "MRI-specific" exclude :test_gc_parameter, "no GC.stat_heap method" exclude :test_gc_parameter_init_slots, "no GC.stat_heap method" +exclude :test_heaps_grow_independently, "MRI-specific" exclude :test_interrupt_in_finalizer, "JRuby handles signals on a separate thread, plus a lot of hinky logic in this test" exclude :test_latest_gc_info, "no GC.latest_gc_info method" exclude :test_latest_gc_info_argument, "no GC.latest_gc_info method" diff --git a/test/mri/excludes/TestRegexp.rb b/test/mri/excludes/TestRegexp.rb index e01e6920ca2..2bbacdd1782 100644 --- a/test/mri/excludes/TestRegexp.rb +++ b/test/mri/excludes/TestRegexp.rb @@ -25,6 +25,7 @@ exclude :test_named_capture_nonascii, "needs investigation" exclude :test_once_multithread, "seems to be flaky, failed in JIT mode on Linux CI on GHA" exclude :test_posix_bracket, "work in progress" +exclude :test_regsub_no_memory_leak, "no working assert_no_memory_leak method" exclude :test_s_timeout_memory_leak, "no working assert_no_memory_leak method" exclude :test_timeout_memory_leak, "no working assert_no_memory_leak method" exclude :test_timeout_nil, "times out without error on JRuby, adds 10s for no value" diff --git a/test/mri/excludes/TestRubyOptimization.rb b/test/mri/excludes/TestRubyOptimization.rb index 5f32071243d..77cc2d55ce5 100644 --- a/test/mri/excludes/TestRubyOptimization.rb +++ b/test/mri/excludes/TestRubyOptimization.rb @@ -36,6 +36,7 @@ exclude :test_objtostring, "may be CRuby-specific, needs investigation" exclude :test_opt_case_dispatch, "depends on RubyVM" exclude :test_opt_duparray_send_include_p, "may be CRuby-specific, needs investigation" +exclude :test_opt_new, "uses RubyVM" exclude :test_opt_newarray_send_include_p, "may be CRuby-specific, needs investigation" exclude :test_optimized_rescue, "may be CRuby-specific, needs investigation" exclude :test_peephole_array_freeze, "may be CRuby-specific, needs investigation" diff --git a/test/mri/excludes/TestString.rb b/test/mri/excludes/TestString.rb index decfbaac8e4..5ab75932268 100644 --- a/test/mri/excludes/TestString.rb +++ b/test/mri/excludes/TestString.rb @@ -7,6 +7,9 @@ exclude :test_delete_prefix_bang_broken_encoding, "needs investigation" exclude :test_delete_prefix_broken_encoding, "needs investigation" exclude :test_each_line_chomp, "needs investigation" +exclude :test_encode_fallback_not_string_memory_leak, "no working assert_no_memory_leak method" +exclude :test_encode_fallback_raise_memory_leak, "no working assert_no_memory_leak method" +exclude :test_encode_fallback_too_big_memory_leak, "no working assert_no_memory_leak method" exclude :test_fs_setter, "needs investigation" exclude :test_grapheme_clusters, "unfinished in initial 2.6 work, #6161" exclude :test_grapheme_clusters_memory_leak, "no working assert_no_memory_leak method" diff --git a/test/mri/excludes/TestString2.rb b/test/mri/excludes/TestString2.rb index decfbaac8e4..5ab75932268 100644 --- a/test/mri/excludes/TestString2.rb +++ b/test/mri/excludes/TestString2.rb @@ -7,6 +7,9 @@ exclude :test_delete_prefix_bang_broken_encoding, "needs investigation" exclude :test_delete_prefix_broken_encoding, "needs investigation" exclude :test_each_line_chomp, "needs investigation" +exclude :test_encode_fallback_not_string_memory_leak, "no working assert_no_memory_leak method" +exclude :test_encode_fallback_raise_memory_leak, "no working assert_no_memory_leak method" +exclude :test_encode_fallback_too_big_memory_leak, "no working assert_no_memory_leak method" exclude :test_fs_setter, "needs investigation" exclude :test_grapheme_clusters, "unfinished in initial 2.6 work, #6161" exclude :test_grapheme_clusters_memory_leak, "no working assert_no_memory_leak method" From acecdae8d6bd21687c4380a327d7c33b1f07e25b Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 11:05:52 -0600 Subject: [PATCH 047/168] Exclude hanging Fiber scheduler unblock test Scheduler.unblock logic needs anoher look. --- test/mri/excludes/TestFiberThread.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/mri/excludes/TestFiberThread.rb b/test/mri/excludes/TestFiberThread.rb index 5a77555996b..fdd5b4233e1 100644 --- a/test/mri/excludes/TestFiberThread.rb +++ b/test/mri/excludes/TestFiberThread.rb @@ -1 +1,2 @@ -exclude :test_broken_unblock, "" +exclude :test_broken_unblock, "unblock needs another look" +exclude :test_spurious_unblock_during_thread_join, "unblock needs another look" From c89e110d075260fee897e7b8385585867c7b4d24 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 12:02:45 -0600 Subject: [PATCH 048/168] Update gems to 4.0.0 release versions --- lib/pom.rb | 60 ++++++++-------- lib/pom.xml | 200 ++++++++++++++++++++++++++-------------------------- 2 files changed, 130 insertions(+), 130 deletions(-) diff --git a/lib/pom.rb b/lib/pom.rb index 0a5eaeacb63..6ac07d539a2 100644 --- a/lib/pom.rb +++ b/lib/pom.rb @@ -21,19 +21,19 @@ def log(message = nil) default_gems = [ # treat RGs update special: # - we do not want bin/update_rubygems or bin/gem overrides - ['rubygems-update', '3.6.9', { bin: false, require_paths: ['lib'] }], - ['bundler', '2.6.9'], + ['rubygems-update', '4.0.3', { bin: false, require_paths: ['lib'] }], + ['bundler', '4.0.3'], ['cgi', '0.4.2'], # Currently using a stub gem for JRuby until we can incorporate our code. # https://github.com/ruby/date/issues/48 - ['date', '3.5.0'], - ['delegate', '0.4.0'], + ['date', '3.5.1'], + ['delegate', '0.6.1'], ['did_you_mean', '2.0.0'], ['digest', '3.2.1'], ['english', '0.8.1'], # Ongoing discussion about the -java gem, since it just omits the ext: https://github.com/ruby/erb/issues/52 - ['erb', '6.0.0'], - ['error_highlight', '0.7.0'], + ['erb', '6.0.1'], + ['error_highlight', '0.7.1'], # https://github.com/ruby/etc/issues/19 # ['etc', '1.4.6'], # https://github.com/ruby/fcntl/issues/9 @@ -41,47 +41,47 @@ def log(message = nil) ['ffi', '1.17.0'], ['fileutils', '1.8.0'], ['find', '0.2.0'], - ['forwardable', '1.3.3'], - ['io-console', '0.8.1'], + ['forwardable', '1.4.0'], + ['io-console', '0.8.2'], # https://github.com/ruby/io-nonblock/issues/4 # ['io-nonblock', '0.3.2'], - ['io-wait', '0.3.3'], - ['ipaddr', '1.2.7'], + ['io-wait', '0.4.0'], + ['ipaddr', '1.2.8'], ['jar-dependencies', '0.5.4'], ['jruby-readline', '1.3.7'], ['jruby-openssl', '0.15.5'], - ['json', '2.16.0'], - ['net-http', '0.7.0'], + ['json', '2.18.0'], + ['net-http', '0.9.1'], ['net-protocol', '0.2.2'], ['open-uri', '0.5.0'], ['open3', '0.2.1'], # https://github.com/ruby/openssl/issues/20#issuecomment-1022872855 - # ['openssl', '4.0.0.pre'], - ['optparse', '0.8.0'], + # ['openssl', '4.0.0'], + ['optparse', '0.8.1'], # https://github.com/ruby/pathname/issues/17 # ['pathname', '0.4.0'], ['pp', '0.6.3'], ['prettyprint', '0.2.0'], # Not ready to ship in the box yet (native dependencies) # ['prism', '1.6.0'], - ['psych', '5.2.6'], + ['psych', '5.3.1'], ['rake-ant', '1.0.6'], # https://github.com/ruby/resolv/issues/19 - # ['resolv', '0.6.3'], + # ['resolv', '0.7.0'], ['ruby2_keywords', '0.0.5'], ['securerandom', '0.4.1'], ['shellwords', '0.2.2'], ['singleton', '0.3.0'], - ['stringio', '3.1.9'], - ['strscan', '3.1.5'], + ['stringio', '3.2.0'], + ['strscan', '3.1.6'], ['subspawn', '0.1.1'], # has 3 transitive deps: ['subspawn-posix', '0.1.1'], ['ffi-binary-libfixposix', '0.5.1.1'], ['ffi-bindings-libfixposix', '0.5.1.0'], ['syntax_suggest', '2.0.2'], ['tempfile', '0.3.1'], - ['time', '0.4.1'], - ['timeout', '0.4.4'], + ['time', '0.4.2'], + ['timeout', '0.6.0'], # https://github.com/ruby/tmpdir/issues/13 # ['tmpdir', '0.3.1'], ['tsort', '0.2.0'], @@ -101,21 +101,21 @@ def log(message = nil) ['base64', '0.3.0'], ['benchmark', '0.5.0'], # Extension still lives in JRuby. See https://github.com/ruby/bigdecimal/issues/268 - ['bigdecimal', '3.3.1'], + ['bigdecimal', '4.0.1'], ['csv', '3.3.5'], # Newer versions require deep control over CRuby internals, needs work to support JRuby. - # ['debug', '1.11.0'], + # ['debug', '1.11.1'], ['debug', '0.2.1'], ['drb', '2.2.3'], ['fiddle', '1.1.8'], ['getoptlong', '0.2.1'], - ['irb', '1.15.3'], + ['irb', '1.16.0'], ['logger', '1.7.0'], ['matrix', '0.4.3'], - ['minitest', '5.26.1'], + ['minitest', '6.0.0'], ['mutex_m', '0.3.0'], ['net-ftp', '0.3.9'], - ['net-imap', '0.5.12'], + ['net-imap', '0.6.2'], ['net-pop', '0.1.2'], ['net-smtp', '0.5.1'], ['nkf', '0.2.0'], @@ -127,8 +127,8 @@ def log(message = nil) ['racc', '1.8.1'], ['rake', '${rake.version}'], # Depends on many CRuby internals - # ['rbs', '3.9.5'], - ['rdoc', '6.15.1'], + # ['rbs', '3.10.0'], + ['rdoc', '7.0.3'], # Ext removed from CRuby in 3.3, equivalent for us would be to remove jruby-readline but unknown implications. # The gem below just attempts to load the extension, and failing that loads reline. Our current readline.rb in # jruby-readline does largely the same, but it finds the extension and does not load reline. @@ -142,12 +142,12 @@ def log(message = nil) ['resolv-replace', '0.1.1'], ['rexml', '3.4.4'], ['rinda', '0.2.0'], - ['rss', '0.3.1'], + ['rss', '0.3.2'], # https://github.com/ruby/syslog/issues/1 # ['syslog', '0.3.0'], - ['test-unit', '3.7.1'] + ['test-unit', '3.7.5'] # Depends on many CRuby internals - # ['typeprof', '0.31.0'], + # ['typeprof', '0.31.1'], ] project 'JRuby Lib Setup' do diff --git a/lib/pom.xml b/lib/pom.xml index a41823098ed..a1037c32387 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -34,7 +34,7 @@ DO NOT MODIFY - GENERATED CODE rubygems rubygems-update - 3.6.9 + 4.0.3 gem provided @@ -47,7 +47,7 @@ DO NOT MODIFY - GENERATED CODE rubygems bundler - 2.6.9 + 4.0.3 gem provided @@ -73,7 +73,7 @@ DO NOT MODIFY - GENERATED CODE rubygems date - 3.5.0 + 3.5.1 gem provided @@ -86,7 +86,7 @@ DO NOT MODIFY - GENERATED CODE rubygems delegate - 0.4.0 + 0.6.1 gem provided @@ -138,7 +138,7 @@ DO NOT MODIFY - GENERATED CODE rubygems erb - 6.0.0 + 6.0.1 gem provided @@ -151,7 +151,7 @@ DO NOT MODIFY - GENERATED CODE rubygems error_highlight - 0.7.0 + 0.7.1 gem provided @@ -203,7 +203,7 @@ DO NOT MODIFY - GENERATED CODE rubygems forwardable - 1.3.3 + 1.4.0 gem provided @@ -216,7 +216,7 @@ DO NOT MODIFY - GENERATED CODE rubygems io-console - 0.8.1 + 0.8.2 gem provided @@ -229,7 +229,7 @@ DO NOT MODIFY - GENERATED CODE rubygems io-wait - 0.3.3 + 0.4.0 gem provided @@ -242,7 +242,7 @@ DO NOT MODIFY - GENERATED CODE rubygems ipaddr - 1.2.7 + 1.2.8 gem provided @@ -294,7 +294,7 @@ DO NOT MODIFY - GENERATED CODE rubygems json - 2.16.0 + 2.18.0 gem provided @@ -307,7 +307,7 @@ DO NOT MODIFY - GENERATED CODE rubygems net-http - 0.7.0 + 0.9.1 gem provided @@ -359,7 +359,7 @@ DO NOT MODIFY - GENERATED CODE rubygems optparse - 0.8.0 + 0.8.1 gem provided @@ -398,7 +398,7 @@ DO NOT MODIFY - GENERATED CODE rubygems psych - 5.2.6 + 5.3.1 gem provided @@ -476,7 +476,7 @@ DO NOT MODIFY - GENERATED CODE rubygems stringio - 3.1.9 + 3.2.0 gem provided @@ -489,7 +489,7 @@ DO NOT MODIFY - GENERATED CODE rubygems strscan - 3.1.5 + 3.1.6 gem provided @@ -580,7 +580,7 @@ DO NOT MODIFY - GENERATED CODE rubygems time - 0.4.1 + 0.4.2 gem provided @@ -593,7 +593,7 @@ DO NOT MODIFY - GENERATED CODE rubygems timeout - 0.4.4 + 0.6.0 gem provided @@ -723,7 +723,7 @@ DO NOT MODIFY - GENERATED CODE rubygems bigdecimal - 3.3.1 + 4.0.1 gem provided @@ -801,7 +801,7 @@ DO NOT MODIFY - GENERATED CODE rubygems irb - 1.15.3 + 1.16.0 gem provided @@ -840,7 +840,7 @@ DO NOT MODIFY - GENERATED CODE rubygems minitest - 5.26.1 + 6.0.0 gem provided @@ -879,7 +879,7 @@ DO NOT MODIFY - GENERATED CODE rubygems net-imap - 0.5.12 + 0.6.2 gem provided @@ -1022,7 +1022,7 @@ DO NOT MODIFY - GENERATED CODE rubygems rdoc - 6.15.1 + 7.0.3 gem provided @@ -1087,7 +1087,7 @@ DO NOT MODIFY - GENERATED CODE rubygems rss - 0.3.1 + 0.3.2 gem provided @@ -1100,7 +1100,7 @@ DO NOT MODIFY - GENERATED CODE rubygems test-unit - 3.7.1 + 3.7.5 gem provided @@ -1131,50 +1131,50 @@ DO NOT MODIFY - GENERATED CODE ${gem.home} specifications/default/* - specifications/rubygems-update-3.6.9* - specifications/bundler-2.6.9* + specifications/rubygems-update-4.0.3* + specifications/bundler-4.0.3* specifications/cgi-0.4.2* - specifications/date-3.5.0* - specifications/delegate-0.4.0* + specifications/date-3.5.1* + specifications/delegate-0.6.1* specifications/did_you_mean-2.0.0* specifications/digest-3.2.1* specifications/english-0.8.1* - specifications/erb-6.0.0* - specifications/error_highlight-0.7.0* + specifications/erb-6.0.1* + specifications/error_highlight-0.7.1* specifications/ffi-1.17.0* specifications/fileutils-1.8.0* specifications/find-0.2.0* - specifications/forwardable-1.3.3* - specifications/io-console-0.8.1* - specifications/io-wait-0.3.3* - specifications/ipaddr-1.2.7* + specifications/forwardable-1.4.0* + specifications/io-console-0.8.2* + specifications/io-wait-0.4.0* + specifications/ipaddr-1.2.8* specifications/jar-dependencies-0.5.4* specifications/jruby-readline-1.3.7* specifications/jruby-openssl-0.15.5* - specifications/json-2.16.0* - specifications/net-http-0.7.0* + specifications/json-2.18.0* + specifications/net-http-0.9.1* specifications/net-protocol-0.2.2* specifications/open-uri-0.5.0* specifications/open3-0.2.1* - specifications/optparse-0.8.0* + specifications/optparse-0.8.1* specifications/pp-0.6.3* specifications/prettyprint-0.2.0* - specifications/psych-5.2.6* + specifications/psych-5.3.1* specifications/rake-ant-1.0.6* specifications/ruby2_keywords-0.0.5* specifications/securerandom-0.4.1* specifications/shellwords-0.2.2* specifications/singleton-0.3.0* - specifications/stringio-3.1.9* - specifications/strscan-3.1.5* + specifications/stringio-3.2.0* + specifications/strscan-3.1.6* specifications/subspawn-0.1.1* specifications/subspawn-posix-0.1.1* specifications/ffi-binary-libfixposix-0.5.1.1* specifications/ffi-bindings-libfixposix-0.5.1.0* specifications/syntax_suggest-2.0.2* specifications/tempfile-0.3.1* - specifications/time-0.4.1* - specifications/timeout-0.4.4* + specifications/time-0.4.2* + specifications/timeout-0.6.0* specifications/tsort-0.2.0* specifications/un-0.3.0* specifications/uri-1.1.1* @@ -1184,19 +1184,19 @@ DO NOT MODIFY - GENERATED CODE specifications/abbrev-0.1.2* specifications/base64-0.3.0* specifications/benchmark-0.5.0* - specifications/bigdecimal-3.3.1* + specifications/bigdecimal-4.0.1* specifications/csv-3.3.5* specifications/debug-0.2.1* specifications/drb-2.2.3* specifications/fiddle-1.1.8* specifications/getoptlong-0.2.1* - specifications/irb-1.15.3* + specifications/irb-1.16.0* specifications/logger-1.7.0* specifications/matrix-0.4.3* - specifications/minitest-5.26.1* + specifications/minitest-6.0.0* specifications/mutex_m-0.3.0* specifications/net-ftp-0.3.9* - specifications/net-imap-0.5.12* + specifications/net-imap-0.6.2* specifications/net-pop-0.1.2* specifications/net-smtp-0.5.1* specifications/nkf-0.2.0* @@ -1207,57 +1207,57 @@ DO NOT MODIFY - GENERATED CODE specifications/pstore-0.2.0* specifications/racc-1.8.1* specifications/rake-${rake.version}* - specifications/rdoc-6.15.1* + specifications/rdoc-7.0.3* specifications/reline-0.6.3* specifications/resolv-replace-0.1.1* specifications/rexml-3.4.4* specifications/rinda-0.2.0* - specifications/rss-0.3.1* - specifications/test-unit-3.7.1* - gems/rubygems-update-3.6.9*/**/* - gems/bundler-2.6.9*/**/* + specifications/rss-0.3.2* + specifications/test-unit-3.7.5* + gems/rubygems-update-4.0.3*/**/* + gems/bundler-4.0.3*/**/* gems/cgi-0.4.2*/**/* - gems/date-3.5.0*/**/* - gems/delegate-0.4.0*/**/* + gems/date-3.5.1*/**/* + gems/delegate-0.6.1*/**/* gems/did_you_mean-2.0.0*/**/* gems/digest-3.2.1*/**/* gems/english-0.8.1*/**/* - gems/erb-6.0.0*/**/* - gems/error_highlight-0.7.0*/**/* + gems/erb-6.0.1*/**/* + gems/error_highlight-0.7.1*/**/* gems/ffi-1.17.0*/**/* gems/fileutils-1.8.0*/**/* gems/find-0.2.0*/**/* - gems/forwardable-1.3.3*/**/* - gems/io-console-0.8.1*/**/* - gems/io-wait-0.3.3*/**/* - gems/ipaddr-1.2.7*/**/* + gems/forwardable-1.4.0*/**/* + gems/io-console-0.8.2*/**/* + gems/io-wait-0.4.0*/**/* + gems/ipaddr-1.2.8*/**/* gems/jar-dependencies-0.5.4*/**/* gems/jruby-readline-1.3.7*/**/* gems/jruby-openssl-0.15.5*/**/* - gems/json-2.16.0*/**/* - gems/net-http-0.7.0*/**/* + gems/json-2.18.0*/**/* + gems/net-http-0.9.1*/**/* gems/net-protocol-0.2.2*/**/* gems/open-uri-0.5.0*/**/* gems/open3-0.2.1*/**/* - gems/optparse-0.8.0*/**/* + gems/optparse-0.8.1*/**/* gems/pp-0.6.3*/**/* gems/prettyprint-0.2.0*/**/* - gems/psych-5.2.6*/**/* + gems/psych-5.3.1*/**/* gems/rake-ant-1.0.6*/**/* gems/ruby2_keywords-0.0.5*/**/* gems/securerandom-0.4.1*/**/* gems/shellwords-0.2.2*/**/* gems/singleton-0.3.0*/**/* - gems/stringio-3.1.9*/**/* - gems/strscan-3.1.5*/**/* + gems/stringio-3.2.0*/**/* + gems/strscan-3.1.6*/**/* gems/subspawn-0.1.1*/**/* gems/subspawn-posix-0.1.1*/**/* gems/ffi-binary-libfixposix-0.5.1.1*/**/* gems/ffi-bindings-libfixposix-0.5.1.0*/**/* gems/syntax_suggest-2.0.2*/**/* gems/tempfile-0.3.1*/**/* - gems/time-0.4.1*/**/* - gems/timeout-0.4.4*/**/* + gems/time-0.4.2*/**/* + gems/timeout-0.6.0*/**/* gems/tsort-0.2.0*/**/* gems/un-0.3.0*/**/* gems/uri-1.1.1*/**/* @@ -1267,19 +1267,19 @@ DO NOT MODIFY - GENERATED CODE gems/abbrev-0.1.2*/**/* gems/base64-0.3.0*/**/* gems/benchmark-0.5.0*/**/* - gems/bigdecimal-3.3.1*/**/* + gems/bigdecimal-4.0.1*/**/* gems/csv-3.3.5*/**/* gems/debug-0.2.1*/**/* gems/drb-2.2.3*/**/* gems/fiddle-1.1.8*/**/* gems/getoptlong-0.2.1*/**/* - gems/irb-1.15.3*/**/* + gems/irb-1.16.0*/**/* gems/logger-1.7.0*/**/* gems/matrix-0.4.3*/**/* - gems/minitest-5.26.1*/**/* + gems/minitest-6.0.0*/**/* gems/mutex_m-0.3.0*/**/* gems/net-ftp-0.3.9*/**/* - gems/net-imap-0.5.12*/**/* + gems/net-imap-0.6.2*/**/* gems/net-pop-0.1.2*/**/* gems/net-smtp-0.5.1*/**/* gems/nkf-0.2.0*/**/* @@ -1290,57 +1290,57 @@ DO NOT MODIFY - GENERATED CODE gems/pstore-0.2.0*/**/* gems/racc-1.8.1*/**/* gems/rake-${rake.version}*/**/* - gems/rdoc-6.15.1*/**/* + gems/rdoc-7.0.3*/**/* gems/reline-0.6.3*/**/* gems/resolv-replace-0.1.1*/**/* gems/rexml-3.4.4*/**/* gems/rinda-0.2.0*/**/* - gems/rss-0.3.1*/**/* - gems/test-unit-3.7.1*/**/* - cache/rubygems-update-3.6.9* - cache/bundler-2.6.9* + gems/rss-0.3.2*/**/* + gems/test-unit-3.7.5*/**/* + cache/rubygems-update-4.0.3* + cache/bundler-4.0.3* cache/cgi-0.4.2* - cache/date-3.5.0* - cache/delegate-0.4.0* + cache/date-3.5.1* + cache/delegate-0.6.1* cache/did_you_mean-2.0.0* cache/digest-3.2.1* cache/english-0.8.1* - cache/erb-6.0.0* - cache/error_highlight-0.7.0* + cache/erb-6.0.1* + cache/error_highlight-0.7.1* cache/ffi-1.17.0* cache/fileutils-1.8.0* cache/find-0.2.0* - cache/forwardable-1.3.3* - cache/io-console-0.8.1* - cache/io-wait-0.3.3* - cache/ipaddr-1.2.7* + cache/forwardable-1.4.0* + cache/io-console-0.8.2* + cache/io-wait-0.4.0* + cache/ipaddr-1.2.8* cache/jar-dependencies-0.5.4* cache/jruby-readline-1.3.7* cache/jruby-openssl-0.15.5* - cache/json-2.16.0* - cache/net-http-0.7.0* + cache/json-2.18.0* + cache/net-http-0.9.1* cache/net-protocol-0.2.2* cache/open-uri-0.5.0* cache/open3-0.2.1* - cache/optparse-0.8.0* + cache/optparse-0.8.1* cache/pp-0.6.3* cache/prettyprint-0.2.0* - cache/psych-5.2.6* + cache/psych-5.3.1* cache/rake-ant-1.0.6* cache/ruby2_keywords-0.0.5* cache/securerandom-0.4.1* cache/shellwords-0.2.2* cache/singleton-0.3.0* - cache/stringio-3.1.9* - cache/strscan-3.1.5* + cache/stringio-3.2.0* + cache/strscan-3.1.6* cache/subspawn-0.1.1* cache/subspawn-posix-0.1.1* cache/ffi-binary-libfixposix-0.5.1.1* cache/ffi-bindings-libfixposix-0.5.1.0* cache/syntax_suggest-2.0.2* cache/tempfile-0.3.1* - cache/time-0.4.1* - cache/timeout-0.4.4* + cache/time-0.4.2* + cache/timeout-0.6.0* cache/tsort-0.2.0* cache/un-0.3.0* cache/uri-1.1.1* @@ -1350,19 +1350,19 @@ DO NOT MODIFY - GENERATED CODE cache/abbrev-0.1.2* cache/base64-0.3.0* cache/benchmark-0.5.0* - cache/bigdecimal-3.3.1* + cache/bigdecimal-4.0.1* cache/csv-3.3.5* cache/debug-0.2.1* cache/drb-2.2.3* cache/fiddle-1.1.8* cache/getoptlong-0.2.1* - cache/irb-1.15.3* + cache/irb-1.16.0* cache/logger-1.7.0* cache/matrix-0.4.3* - cache/minitest-5.26.1* + cache/minitest-6.0.0* cache/mutex_m-0.3.0* cache/net-ftp-0.3.9* - cache/net-imap-0.5.12* + cache/net-imap-0.6.2* cache/net-pop-0.1.2* cache/net-smtp-0.5.1* cache/nkf-0.2.0* @@ -1373,13 +1373,13 @@ DO NOT MODIFY - GENERATED CODE cache/pstore-0.2.0* cache/racc-1.8.1* cache/rake-${rake.version}* - cache/rdoc-6.15.1* + cache/rdoc-7.0.3* cache/reline-0.6.3* cache/resolv-replace-0.1.1* cache/rexml-3.4.4* cache/rinda-0.2.0* - cache/rss-0.3.1* - cache/test-unit-3.7.1* + cache/rss-0.3.2* + cache/test-unit-3.7.5* From b9967890cdbb82d566aaeddbfb4c3c2f570773cf Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 12:02:59 -0600 Subject: [PATCH 049/168] Exclude test that uses RubyVM --- test/mri/excludes/TestFiberScheduler.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/mri/excludes/TestFiberScheduler.rb b/test/mri/excludes/TestFiberScheduler.rb index 6e95b4c664f..7a49ae467c2 100644 --- a/test/mri/excludes/TestFiberScheduler.rb +++ b/test/mri/excludes/TestFiberScheduler.rb @@ -1,2 +1,3 @@ exclude :test_condition_variable, "hangs on macos M1" exclude :test_current_scheduler, "fails intermittently on Linux CI on GHA" +exclude :test_iseq_compile_under_gc_stress_bug_21180, "uses RubyVM" From 76cd35c347075cdac2d796e29f550fd805de32be Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 12:27:36 -0600 Subject: [PATCH 050/168] Implement Enumerator.produce size keyword See https://bugs.ruby-lang.org/issues/21701 --- .../main/java/org/jruby/RubyEnumerator.java | 36 +++++++++++++++++-- .../src/main/java/org/jruby/RubyProducer.java | 24 +++++++++++-- .../java/org/jruby/runtime/JavaSites.java | 5 +++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyEnumerator.java b/core/src/main/java/org/jruby/RubyEnumerator.java index 541c95492be..02a80afa25a 100644 --- a/core/src/main/java/org/jruby/RubyEnumerator.java +++ b/core/src/main/java/org/jruby/RubyEnumerator.java @@ -30,6 +30,7 @@ import org.jruby.anno.JRubyMethod; import org.jruby.anno.JRubyModule; +import org.jruby.ast.util.ArgsUtil; import org.jruby.exceptions.StopIteration; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; @@ -52,6 +53,8 @@ import static org.jruby.runtime.Helpers.arrayOf; import static org.jruby.runtime.ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR; import static org.jruby.runtime.ThreadContext.CALL_KEYWORD; +import static org.jruby.runtime.ThreadContext.hasKeywords; +import static org.jruby.runtime.ThreadContext.resetCallInfo; import static org.jruby.runtime.Visibility.PRIVATE; /** @@ -556,17 +559,44 @@ private static JavaSites.FiberSites sites(ThreadContext context) { /** MRI: enumerator_s_produce * */ - @JRubyMethod(meta = true, optional = 1, checkArity = false) + @JRubyMethod(meta = true, optional = 2, checkArity = false, keywords = true) public static IRubyObject produce(ThreadContext context, IRubyObject recv, IRubyObject[] args, final Block block) { - int argc = Arity.checkArgumentCount(context, args, 0, 1); + IRubyObject size = UNDEF; + int argc = args.length; + + if (hasKeywords(resetCallInfo(context))) { + argc--; + IRubyObject maybeSize = ArgsUtil.extractKeywordArg(context, (RubyHash) args[args.length - 1], "size"); + if (maybeSize != null) { + size = maybeSize; + } + } + + size = size == UNDEF ? + context.runtime.getFloat().getConstant("INFINITY") : + convertToFeasibleSizeValue(context, size); + + Arity.checkArgumentCount(context, argc, 0, 1); if (!block.isGiven()) throw argumentError(context, "no block given"); IRubyObject init = argc == 0 ? null : args[0]; - RubyProducer producer = RubyProducer.newProducer(context, init, block); + RubyProducer producer = RubyProducer.newProducer(context, init, block, size); return enumeratorizeWithSize(context, producer, "each", RubyProducer::size); } + private static IRubyObject convertToFeasibleSizeValue(ThreadContext context, IRubyObject obj) { + if (obj.isNil()) { + return obj; + } else if (obj.respondsTo("call")) { + return obj; + } else if (obj instanceof RubyFloat flote && flote.value == Double.POSITIVE_INFINITY) { + return obj; + } else { + return toInteger(context, obj); + } + } + @Deprecated(since = "9.4.3.0") public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) { IRubyObject size = Arity.checkArgumentCount(context, args, 0, 1) == 1 ? args[0] : null; diff --git a/core/src/main/java/org/jruby/RubyProducer.java b/core/src/main/java/org/jruby/RubyProducer.java index dac67986b9b..821fbc8c28b 100644 --- a/core/src/main/java/org/jruby/RubyProducer.java +++ b/core/src/main/java/org/jruby/RubyProducer.java @@ -31,6 +31,7 @@ import org.jruby.exceptions.StopIteration; import org.jruby.runtime.Block; +import org.jruby.runtime.JavaSites; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -44,6 +45,7 @@ public class RubyProducer extends RubyObject { private IRubyObject init; private Block producerBlock; + private IRubyObject size; public static RubyClass createProducerClass(ThreadContext context, RubyClass Object, RubyClass Enumerator, RubyModule Enumerable) { return Enumerator.defineClassUnder(context, "Producer", Object, RubyProducer::new). @@ -56,19 +58,33 @@ public RubyProducer(Ruby runtime, RubyClass klass) { } public RubyProducer(Ruby runtime, RubyClass klass, IRubyObject init, final Block block) { + this(runtime, klass, init, block, runtime.getNil()); + } + + public RubyProducer(Ruby runtime, RubyClass klass, IRubyObject init, final Block block, IRubyObject size) { super(runtime, klass); this.init = init; this.producerBlock = block; + this.size = size; } public static RubyProducer newProducer(ThreadContext context, IRubyObject init, final Block block) { - return new RubyProducer(context.runtime, context.runtime.getProducer(), init, block); + return newProducer(context, init, block, context.nil); + } + + public static RubyProducer newProducer(ThreadContext context, IRubyObject init, final Block block, IRubyObject size) { + return new RubyProducer(context.runtime, context.runtime.getProducer(), init, block, size); } /** MRI: producer_size */ public static IRubyObject size(ThreadContext context, RubyProducer self, IRubyObject[] args) { - return asFloat(context, Double.POSITIVE_INFINITY); + IRubyObject size = self.size; + + if (size.isNil()) return size; + if (size instanceof RubyInteger || size instanceof RubyFloat) return size; + + return sites(context).call.call(context, self, size); } @JRubyMethod(rest = true) @@ -93,4 +109,8 @@ public IRubyObject each(ThreadContext context, IRubyObject[] args, final Block b return this; } + + private static JavaSites.ProducerSites sites(ThreadContext context) { + return context.sites.Producer; + } } \ No newline at end of file diff --git a/core/src/main/java/org/jruby/runtime/JavaSites.java b/core/src/main/java/org/jruby/runtime/JavaSites.java index 3deba0514e0..a2e1fa28918 100644 --- a/core/src/main/java/org/jruby/runtime/JavaSites.java +++ b/core/src/main/java/org/jruby/runtime/JavaSites.java @@ -58,6 +58,7 @@ public class JavaSites { public final SymbolSites Symbol = new SymbolSites(); public final ProcSites Proc = new ProcSites(); public final ModuleSites Module = new ModuleSites(); + public final ProducerSites Producer = new ProducerSites(); public static class BasicObjectSites { public final CallSite respond_to = new FunctionalCachingCallSite("respond_to?"); @@ -573,6 +574,10 @@ public static class ModuleSites { public final CachingCallSite hash = new FunctionalCachingCallSite("hash"); } + public static class ProducerSites { + public final CachingCallSite call = new FunctionalCachingCallSite("call"); + } + public static class CheckedSites { public final RespondToCallSite respond_to_X; public final CachingCallSite respond_to_missing = new FunctionalCachingCallSite("respond_to_missing?"); From 8a9bf264589dd7b0c44f1e248fe38c333e7906f9 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 12:49:37 -0600 Subject: [PATCH 051/168] Define top-level Ruby module See https://bugs.ruby-lang.org/issues/20884 --- core/src/main/java/org/jruby/RubyGlobal.java | 32 ++++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyGlobal.java b/core/src/main/java/org/jruby/RubyGlobal.java index f489ee741da..e00ac3540eb 100644 --- a/core/src/main/java/org/jruby/RubyGlobal.java +++ b/core/src/main/java/org/jruby/RubyGlobal.java @@ -44,6 +44,7 @@ import org.jcodings.specific.USASCIIEncoding; import org.jruby.anno.JRubyMethod; import org.jruby.api.Create; +import org.jruby.api.Define; import org.jruby.ast.util.ArgsUtil; import org.jruby.common.IRubyWarnings; import org.jruby.exceptions.RaiseException; @@ -155,6 +156,9 @@ public static RubyHash createGlobalsAndENV(ThreadContext context, GlobalVariable // initially set to config's script name, if any defineArgv0Global(context, globals, instanceConfig.displayedFileName()); + // Ruby module contains general Ruby constants + RubyModule Ruby = Define.defineModule(context, "Ruby"); + // Version information: IRubyObject release = RubyString.newFString(runtime, Constants.COMPILE_DATE); IRubyObject platform = RubyString.newFString(runtime, Constants.PLATFORM); @@ -162,27 +166,30 @@ public static RubyHash createGlobalsAndENV(ThreadContext context, GlobalVariable IRubyObject version = RubyString.newFString(runtime, Constants.RUBY_VERSION); IRubyObject patchlevel = asFixnum(context, 0); - Object.defineConstant(context, "RUBY_VERSION", version); - Object.defineConstant(context, "RUBY_PATCHLEVEL", patchlevel); - Object.defineConstant(context, "RUBY_RELEASE_DATE", release); - Object.defineConstant(context, "RUBY_PLATFORM", platform); + defineRubyConstant(context, Object, Ruby, "VERSION", version); + defineRubyConstant(context, Object, Ruby, "PATCHLEVEL", patchlevel); + defineRubyConstant(context, Object, Ruby, "RELEASE_DATE", release); + defineRubyConstant(context, Object, Ruby, "PLATFORM", platform); IRubyObject description = RubyString.newFString(runtime, OutputStrings.getVersionString()); - Object.defineConstant(context, "RUBY_DESCRIPTION", description); + defineRubyConstant(context, Object, Ruby, "DESCRIPTION", description); IRubyObject copyright = RubyString.newFString(runtime, OutputStrings.getCopyrightString()); - Object.defineConstant(context, "RUBY_COPYRIGHT", copyright); + defineRubyConstant(context, Object, Ruby, "COPYRIGHT", copyright); Object.defineConstant(context, "RELEASE_DATE", release); Object.defineConstant(context, "PLATFORM", platform); IRubyObject jrubyVersion = RubyString.newFString(runtime, Constants.VERSION); - IRubyObject jrubyRevision = RubyString.newFString(runtime, Constants.REVISION); + RubyString revision = RubyString.newFString(runtime, Constants.REVISION); + IRubyObject jrubyRevision = revision; + Object.defineConstant(context, "JRUBY_VERSION", jrubyVersion); Object.defineConstant(context, "JRUBY_REVISION", jrubyRevision); - Object.defineConstant(context, "RUBY_REVISION", RubyString.newFString(runtime, Constants.REVISION)); - Object.defineConstant(context, "RUBY_ENGINE", engine); - Object.defineConstant(context, "RUBY_ENGINE_VERSION", jrubyVersion); + + defineRubyConstant(context, Object, Ruby, "REVISION", revision); + defineRubyConstant(context, Object, Ruby, "ENGINE", engine); + defineRubyConstant(context, Object, Ruby, "ENGINE_VERSION", jrubyVersion); RubyInstanceConfig.Verbosity verbosity = instanceConfig.getVerbosity(); runtime.defineVariable(new WarningGlobalVariable(context, "$-W", verbosity), GLOBAL); @@ -299,6 +306,11 @@ public static RubyHash createGlobalsAndENV(ThreadContext context, GlobalVariable return env; } + private static void defineRubyConstant(ThreadContext context, RubyClass Object, RubyModule Ruby, String name, IRubyObject value) { + Object.defineConstant(context, "RUBY_" + name, value); + Ruby.defineConstant(context, name, value); + } + static void defineArgv0Global(ThreadContext context, GlobalVariables globals, String scriptName) { IAccessor d = new IAccessor() { RubyString value = newFrozenString(context, scriptName); From 4cb30dbab06cdac714fc7b98abc55f2c2e5a6aad Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 31 Dec 2025 12:54:20 -0600 Subject: [PATCH 052/168] Add to_set to Enumerable and deprecate passing args See https://bugs.ruby-lang.org/issues/21390 --- core/src/main/java/org/jruby/ext/set/RubySet.java | 3 +++ core/src/main/ruby/jruby/kernel/enumerable.rb | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index e3a8a8fb07d..a19e0175319 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -36,6 +36,7 @@ import org.jruby.api.Access; import org.jruby.api.Create; import org.jruby.api.Error; +import org.jruby.api.Warn; import org.jruby.javasupport.JavaUtil; import org.jruby.runtime.*; import org.jruby.runtime.builtin.IRubyObject; @@ -429,6 +430,8 @@ public RubySet to_set(final ThreadContext context, final Block block) { public RubySet to_set(final ThreadContext context, final IRubyObject[] args, final Block block) { if ( args.length == 0 ) return to_set(context, block); + Warn.warnDeprecated(context, "passing arguments to Set#to_set"); + IRubyObject klass = args[0]; final RubyClass Set = Access.getClass(context, "Set"); diff --git a/core/src/main/ruby/jruby/kernel/enumerable.rb b/core/src/main/ruby/jruby/kernel/enumerable.rb index da475906e1a..358786a4402 100644 --- a/core/src/main/ruby/jruby/kernel/enumerable.rb +++ b/core/src/main/ruby/jruby/kernel/enumerable.rb @@ -114,4 +114,15 @@ def enumerator_size respond_to?(:size) ? size : nil end private :enumerator_size + + # Passing arguments to this method is deprecated. + def to_set(*args, &block) + klass = if args.empty? + Set + else + warn "passing arguments to Enumerable#to_set is deprecated", uplevel: 1 + args.shift + end + klass.new(self, *args, &block) + end end From ef3d5d08518bbeb5b33c099c3272c50d42be48ab Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Fri, 2 Jan 2026 01:28:22 -0600 Subject: [PATCH 053/168] Update warning for ruby2_keywords There's behavior changes missing as well from the linked PR but this gets the existing warnings to match. See https://github.com/ruby/ruby/pull/13475 --- core/src/main/java/org/jruby/RubyModule.java | 4 ++-- core/src/main/java/org/jruby/RubyProc.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyModule.java b/core/src/main/java/org/jruby/RubyModule.java index 2e73fc16a4f..687672231b7 100644 --- a/core/src/main/java/org/jruby/RubyModule.java +++ b/core/src/main/java/org/jruby/RubyModule.java @@ -3499,11 +3499,11 @@ public IRubyObject ruby2_keywords(ThreadContext context, IRubyObject[] args) { if (!method.isNative()) { Signature signature = method.getSignature(); if (!signature.hasRest()) { - warn(context, str(context.runtime, "Skipping set of ruby2_keywords flag for ", ids(context.runtime, name), " (method accepts keywords or method does not accept argument splat)")); + warn(context, str(context.runtime, "Skipping set of ruby2_keywords flag for ", ids(context.runtime, name), " (method accepts keywords or post arguments or method does not accept argument splat)")); } else if (!signature.hasKwargs()) { method.setRuby2Keywords(); } else if (method instanceof AbstractIRMethod && ((AbstractIRMethod) method).getStaticScope().exists("...") == -1) { - warn(context, str(context.runtime, "Skipping set of ruby2_keywords flag for ", ids(context.runtime, name), " (method accepts keywords or method does not accept argument splat)")); + warn(context, str(context.runtime, "Skipping set of ruby2_keywords flag for ", ids(context.runtime, name), " (method accepts keywords or post arguments or method does not accept argument splat)")); } } else { warn(context, str(context.runtime, "Skipping set of ruby2_keywords flag for ", ids(context.runtime, name), " (method not defined in Ruby)")); diff --git a/core/src/main/java/org/jruby/RubyProc.java b/core/src/main/java/org/jruby/RubyProc.java index ef7fdbb777f..9e5af18d56a 100644 --- a/core/src/main/java/org/jruby/RubyProc.java +++ b/core/src/main/java/org/jruby/RubyProc.java @@ -302,7 +302,7 @@ public IRubyObject ruby2_keywords(ThreadContext context) { if (signature.hasRest() && !signature.hasKwargs()) { ((IRBlockBody) body).getScope().setRuby2Keywords(); } else { - warn(context, "Skipping set of ruby2_keywords flag for proc (proc accepts keywords or proc does not accept argument splat)"); + warn(context, "Skipping set of ruby2_keywords flag for proc (proc accepts keywords or post arguments or proc does not accept argument splat)"); } } else { From 9eae064f7fdae5d2e880b6574095362c9f8192fd Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 6 Jan 2026 00:55:00 -0600 Subject: [PATCH 054/168] Mask internal Ruby backtrace elements like native masking When generating backtraces for `caller`, JRuby maintains compat with CRuby by replacing all internal native frames with the most recent external Ruby frame, as illustrated below: ``` $ cx jruby-10.0.2.0 ruby -e "def foo; puts caller(0); end; foo" -e:1:in 'foo' -e:1:in '
' ``` Up until now, we did not treat internal Ruby sources the same way, but Ruby 4.0 now does a similar masking of such sources: JRuby: ``` $ cx jruby-10.0.2.0 ruby -e "def foo; puts caller(0); end; tap { foo }" -e:1:in 'foo' -e:1:in 'block in
' :19:in 'tap' -e:1:in '
' ``` CRuby: ``` $ cx 4.0.0 ruby -e "def foo; puts caller(0); end; tap { foo }" -e:1:in 'Object#foo' -e:1:in 'block in
' -e:1:in 'Kernel#tap' -e:1:in '
' ``` This patch uses the native masking logic in backtrace building to also mask internal Ruby source frames, producing a `caller` trace that matches CRuby: ``` $ ruby -v -e "def foo; puts caller(0); end; tap { foo }" jruby 10.1.0.0-SNAPSHOT (4.0.0) 2026-01-06 ef3d5d0851 OpenJDK 64-Bit Server VM 25+36-LTS on 25+36-LTS +indy +jit [arm64-darwin] -e:1:in 'foo' -e:1:in 'block in
' -e:1:in 'tap' -e:1:in '
' ``` Additionally, this patch expands the omission of internal Ruby frames from `warn` line calculation in the case those methods have been JIT compiled (this logic was missing before). --- .../runtime/backtrace/BacktraceData.java | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/jruby/runtime/backtrace/BacktraceData.java b/core/src/main/java/org/jruby/runtime/backtrace/BacktraceData.java index 91685b8ea28..8f2af94d883 100644 --- a/core/src/main/java/org/jruby/runtime/backtrace/BacktraceData.java +++ b/core/src/main/java/org/jruby/runtime/backtrace/BacktraceData.java @@ -126,8 +126,21 @@ private void eachBacktrace(Map> boundMethods, Predic continue; } - // mask internal file paths - filename = TraceType.maskInternalFiles(filename); + // skip internal sources if requested + if (TraceType.isExcludedInternal(filename)) { + // if excluding internal sources, skip frame altogether + if (excludeInternal) continue; + + // if masking internal sources, replace only the highest frame with most recent Ruby frame + if (maskNative) { + dupFrame = true; + dupFrameName = decodedName; + continue; + } + + // otherwise, mark internal file paths with "internal:" prefix + filename = TraceType.maskInternalFiles(filename); + } // construct Ruby trace element RubyStackTraceElement rubyElement = new RubyStackTraceElement(className, decodedName, filename, line, false, type); @@ -180,28 +193,38 @@ private void eachBacktrace(Map> boundMethods, Predic BacktraceElement rubyFrame = backIter.next(); // construct Ruby trace element - final String newName; + final String translatedName; switch (frameType) { - case METHOD: newName = rubyFrame.method; break; - case BLOCK: newName = rubyFrame.method; break; - case CLASS: newName = "'; break; - case MODULE: newName = "'; break; - case METACLASS: newName = "singleton class"; break; - case ROOT: newName = "
"; break; + case METHOD: translatedName = rubyFrame.method; break; + case BLOCK: translatedName = rubyFrame.method; break; + case CLASS: translatedName = "'; break; + case MODULE: translatedName = "'; break; + case METACLASS: translatedName = "singleton class"; break; + case ROOT: translatedName = "
"; break; case EVAL: - newName = rubyFrame.method == null || rubyFrame.method.isEmpty() ? "
" : rubyFrame.method; + translatedName = rubyFrame.method == null || rubyFrame.method.isEmpty() ? "
" : rubyFrame.method; break; - default: newName = rubyFrame.method; + default: translatedName = rubyFrame.method; } // skip internal sources if requested filename = rubyFrame.filename; - if (excludeInternal && TraceType.isExcludedInternal(filename)) continue; + if (TraceType.isExcludedInternal(filename)) { + // if excluding internal sources, skip frame altogether + if (excludeInternal) continue; + + // if masking internal sources, replace only the highest frame with most recent Ruby frame + if (maskNative) { + dupFrame = true; + dupFrameName = translatedName; + continue; + } - // mask internal file paths - filename = TraceType.maskInternalFiles(filename); + // otherwise, mark internal file paths with "internal:" prefix + filename = TraceType.maskInternalFiles(filename); + } - RubyStackTraceElement rubyElement = new RubyStackTraceElement("RUBY", newName, filename, rubyFrame.line + 1, false, frameType); + RubyStackTraceElement rubyElement = new RubyStackTraceElement("RUBY", translatedName, filename, rubyFrame.line + 1, false, frameType); // dup if masking native and previous frame was native if (maskNative && dupFrame) { From 33c4ae2f7fd8ee4cc087b6e138179d53723ae30f Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 02:32:52 -0600 Subject: [PATCH 055/168] Exclude two more tests that depend on CRuby GC APIs --- test/mri/excludes/TestIOBuffer.rb | 1 + test/mri/excludes/TestVariable.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/mri/excludes/TestIOBuffer.rb b/test/mri/excludes/TestIOBuffer.rb index ea473c2015c..f379c4beec2 100644 --- a/test/mri/excludes/TestIOBuffer.rb +++ b/test/mri/excludes/TestIOBuffer.rb @@ -1,3 +1,4 @@ +exclude :test_bug_21210, "depends on CRuby GC APIs" exclude :test_copy_null_destination, "no support for null buffer" exclude :test_copy_null_source, "no support for null buffer" exclude :test_copy_overlapped_bwd, "needs investigation" diff --git a/test/mri/excludes/TestVariable.rb b/test/mri/excludes/TestVariable.rb index 18481887ad1..cfe2671167f 100644 --- a/test/mri/excludes/TestVariable.rb +++ b/test/mri/excludes/TestVariable.rb @@ -1,7 +1,8 @@ +exclude :test_cloned_allows_setting_cvar, "needs investigation" +exclude :test_exivar_resize_with_compaction_stress, "depends on CRuby GC APIs" exclude :test_global_variables, "requires dynamic listing of backref numbers for available groups" exclude :test_setting_class_variable_on_module_through_inheritance, "work in progress" exclude :test_singleton_class_included_class_variable, "work in progress" exclude :test_special_constant_ivars, "work in progress" exclude :test_svar_with_ifunc, "new isolation of proc frame captures, see https://github.com/jruby/jruby/issues/8726" exclude :test_variable, "needs investigation" -exclude :test_cloned_allows_setting_cvar, "needs investigation" From b8880cf98a7a572b504d5248e6d8a9b215c3ac02 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 02:56:13 -0600 Subject: [PATCH 056/168] Update deprecation for $, and $\ See https://github.com/ruby/ruby/pull/15409 and later change to the warning message in https://github.com/ruby/ruby/commit/29a12297c3594ed24102d62e16dfd6d4e5e328f3 --- core/src/main/java/org/jruby/RubyGlobal.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyGlobal.java b/core/src/main/java/org/jruby/RubyGlobal.java index e00ac3540eb..d2190646987 100644 --- a/core/src/main/java/org/jruby/RubyGlobal.java +++ b/core/src/main/java/org/jruby/RubyGlobal.java @@ -199,7 +199,7 @@ public static RubyHash createGlobalsAndENV(ThreadContext context, GlobalVariable runtime.defineVariable(rs, GLOBAL); runtime.setRecordSeparatorVar(rs); globals.setDefaultSeparator(defaultRS); - runtime.defineVariable(new StringGlobalVariable(runtime, "$\\", context.nil), GLOBAL); + runtime.defineVariable(new DeprecatedStringGlobalVariable(runtime, "$\\", context.nil), GLOBAL); runtime.defineVariable(new DeprecatedStringGlobalVariable(runtime, "$,", context.nil), GLOBAL); runtime.defineVariable(new LineNumberGlobalVariable(runtime, "$."), GLOBAL); @@ -1073,7 +1073,7 @@ public DeprecatedStringGlobalVariable(Ruby runtime, String name, IRubyObject val public IRubyObject set(IRubyObject value) { IRubyObject result = super.set(value); - if (!value.isNil()) warnDeprecatedGlobal(runtime.getCurrentContext(), name); + if (!value.isNil()) warnDeprecatedGlobal(runtime.getCurrentContext(), "non-nil '" + name + "'"); return result; } From 8534401b79a7f59f975d6399559c334fbbc5a0cf Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 03:05:46 -0600 Subject: [PATCH 057/168] Use strict coercion logic to reject non-Proc --- core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java index a40f3e1b0a5..235c4201e46 100644 --- a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java +++ b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java @@ -554,7 +554,7 @@ public static Block getBlockFromObject(ThreadContext context, Object value) { } else if ((value instanceof IRubyObject) && ((IRubyObject)value).isNil()) { block = Block.NULL_BLOCK; } else if (value instanceof IRubyObject) { - block = ((RubyProc) TypeConverter.convertToType((IRubyObject) value, context.runtime.getProc(), "to_proc", true)).getBlock(); + block = ((RubyProc) TypeConverter.convertToType((IRubyObject) value, context.runtime.getProc(), "to_proc")).getBlock(); } else { throw new RuntimeException("Unhandled case in CallInstr:prepareBlock. Got block arg: " + value); } From 4f7d71d3ce9036fe7b35b88a31a8a5066d03abd5 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 03:18:05 -0600 Subject: [PATCH 058/168] Partially fix deprecation for $/ and $; variables --- core/src/main/java/org/jruby/RubyGlobal.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyGlobal.java b/core/src/main/java/org/jruby/RubyGlobal.java index d2190646987..f2549e9b684 100644 --- a/core/src/main/java/org/jruby/RubyGlobal.java +++ b/core/src/main/java/org/jruby/RubyGlobal.java @@ -195,7 +195,7 @@ public static RubyHash createGlobalsAndENV(ThreadContext context, GlobalVariable runtime.defineVariable(new WarningGlobalVariable(context, "$-W", verbosity), GLOBAL); IRubyObject defaultRS = RubyString.newFString(runtime, instanceConfig.getRecordSeparator()); - GlobalVariable rs = new StringGlobalVariable(runtime, "$/", defaultRS); + GlobalVariable rs = new DeprecatedStringGlobalVariable(runtime, "$/", defaultRS); runtime.defineVariable(rs, GLOBAL); runtime.setRecordSeparatorVar(rs); globals.setDefaultSeparator(defaultRS); @@ -1088,7 +1088,7 @@ public DeprecatedStringOrRegexpGlobalVariable(Ruby runtime, String name, IRubyOb public IRubyObject set(IRubyObject value) { IRubyObject result = super.set(value); - if (!result.isNil()) warnDeprecatedGlobal(runtime.getCurrentContext(), name); + if (!value.isNil()) warnDeprecatedGlobal(runtime.getCurrentContext(), "non-nil '" + name + "'"); return result; } From bf93b265650cef1fc08523730573ff5ea6611755 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 12:09:33 -0600 Subject: [PATCH 059/168] Tag in-place dedup test not implementable on JRuby The behavior specified here is that the first instance of a non- interned frozen string to be interned/deduplicated will be stored as-is as the interned string cache (i.e. the `-` method will return the same object). The behavior is impossible to implement predictably without races (some other thread might dedup at the same time, and then you get a copy instead of the original), and doubly impossible on JRuby since all interned strings are of a different internal Java type. This was discussed in ruby/spec#1249 where it was agreed the spec should be moved into CRuby's tests, as it is not implementable on JRuby or TruffleRuby. We exclude this behavior on JRuby for these reasons. --- test/mri/excludes/TestString.rb | 1 + test/mri/excludes/TestString2.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/test/mri/excludes/TestString.rb b/test/mri/excludes/TestString.rb index 5ab75932268..8a70286f2be 100644 --- a/test/mri/excludes/TestString.rb +++ b/test/mri/excludes/TestString.rb @@ -33,6 +33,7 @@ exclude :test_tr, "needs investigation" exclude :test_tr_s!, "needs investigation" exclude :test_tr_s, "needs investigation" +exclude :test_uminus_dedup_in_place, "depends on MRI-specific interning behavior" exclude :test_uminus_frozen, "work in progress" exclude :test_uminus_no_freeze_not_bare, "work in progress" exclude :test_undump, "unfinished in initial 2.6 work, #6161" diff --git a/test/mri/excludes/TestString2.rb b/test/mri/excludes/TestString2.rb index 5ab75932268..8a70286f2be 100644 --- a/test/mri/excludes/TestString2.rb +++ b/test/mri/excludes/TestString2.rb @@ -33,6 +33,7 @@ exclude :test_tr, "needs investigation" exclude :test_tr_s!, "needs investigation" exclude :test_tr_s, "needs investigation" +exclude :test_uminus_dedup_in_place, "depends on MRI-specific interning behavior" exclude :test_uminus_frozen, "work in progress" exclude :test_uminus_no_freeze_not_bare, "work in progress" exclude :test_undump, "unfinished in initial 2.6 work, #6161" From 4e313a7bfbe8b7bd76ed82d49bd0dac2458f7de0 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 12:28:03 -0600 Subject: [PATCH 060/168] Exclude test for use-after-free not possible in JRuby CRuby will segv due to use-after-free of a heap object if a string is modified while being split. This is not possible in JRuby. --- test/mri/excludes/TestString.rb | 1 + test/mri/excludes/TestString2.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/test/mri/excludes/TestString.rb b/test/mri/excludes/TestString.rb index 8a70286f2be..e38a9ca0e98 100644 --- a/test/mri/excludes/TestString.rb +++ b/test/mri/excludes/TestString.rb @@ -24,6 +24,7 @@ exclude :test_rstrip_bang, "needs investigation" exclude :test_scan_gc_compact_stress, "GC is not configurable" exclude :test_scan_segv, "requires ObjectSpace.each_object" +exclude :test_split_with_block, "test for use-after-free in CRuby not possible in JRuby" exclude :test_start_with?, "needs investigation" exclude :test_start_with_timeout_memory_leak, "no working assert_no_memory_leak method" exclude :test_string_interpolations_across_heaps_get_embedded, "uses internal GC constants" diff --git a/test/mri/excludes/TestString2.rb b/test/mri/excludes/TestString2.rb index 8a70286f2be..e38a9ca0e98 100644 --- a/test/mri/excludes/TestString2.rb +++ b/test/mri/excludes/TestString2.rb @@ -24,6 +24,7 @@ exclude :test_rstrip_bang, "needs investigation" exclude :test_scan_gc_compact_stress, "GC is not configurable" exclude :test_scan_segv, "requires ObjectSpace.each_object" +exclude :test_split_with_block, "test for use-after-free in CRuby not possible in JRuby" exclude :test_start_with?, "needs investigation" exclude :test_start_with_timeout_memory_leak, "no working assert_no_memory_leak method" exclude :test_string_interpolations_across_heaps_get_embedded, "uses internal GC constants" From b351ad861bf342e0fc108c9b912e34bc3534994e Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 12:43:40 -0600 Subject: [PATCH 061/168] Verify encoding of path for File.path See https://github.com/ruby/ruby/pull/15429 --- core/src/main/java/org/jruby/RubyFile.java | 30 +++++++++++++++++++-- core/src/main/java/org/jruby/api/Error.java | 5 ++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyFile.java b/core/src/main/java/org/jruby/RubyFile.java index 6b0940c265c..0eff8c6af62 100644 --- a/core/src/main/java/org/jruby/RubyFile.java +++ b/core/src/main/java/org/jruby/RubyFile.java @@ -45,6 +45,7 @@ import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.api.API; +import org.jruby.api.Error; import org.jruby.exceptions.NotImplementedError; import org.jruby.ir.runtime.IRRuntimeHelpers; import org.jruby.runtime.*; @@ -1364,12 +1365,37 @@ public static String getAdjustedPath(ThreadContext context, IRubyObject fileOrPa // mri: FilePathValue/rb_get_path/rb_get_patch_check public static RubyString get_path(ThreadContext context, IRubyObject path) { - if (path instanceof RubyString) return checkEmbeddedNulls(context, path); + return getPathCheckConvert(context, getPathCheckToString(context, path)); + } + + // CRuby rb_get_path_check_convert + private static RubyString getPathCheckConvert(ThreadContext context, RubyString path) { + path = filePathConvert(context, path); + + checkPathEncoding(context, path); + + checkEmbeddedNulls(context, path); + + return path.strDup(context.runtime); + } + + // CRuby: check_path_encoding + private static Encoding checkPathEncoding(ThreadContext context, RubyString str) { + Encoding enc = str.getEncoding(); + if (!enc.isAsciiCompatible()) { + throw Error.encodingCompatibilityError(context, "path name must be ASCII-compatible (" + enc + "): " + str); + } + return enc; + } + + // CRuby: rb_get_path_check_to_string + private static RubyString getPathCheckToString(ThreadContext context, IRubyObject path) { + if (path instanceof RubyString) return (RubyString) path; FileSites sites = sites(context); if (sites.respond_to_to_path.respondsTo(context, path, path, true)) path = sites.to_path.call(context, path, path); - return filePathConvert(context, path.convertToString()); + return path.convertToString(); } // FIXME: MRI skips this logic on windows? Does not make sense to me why so I left it in. diff --git a/core/src/main/java/org/jruby/api/Error.java b/core/src/main/java/org/jruby/api/Error.java index 48cc9483e14..9f9cc849093 100644 --- a/core/src/main/java/org/jruby/api/Error.java +++ b/core/src/main/java/org/jruby/api/Error.java @@ -6,6 +6,7 @@ import org.jruby.RubyModule; import org.jruby.exceptions.ArgumentError; import org.jruby.exceptions.EOFError; +import org.jruby.exceptions.EncodingError; import org.jruby.exceptions.IOError; import org.jruby.exceptions.NotImplementedError; import org.jruby.exceptions.RaiseException; @@ -276,4 +277,8 @@ private static IRubyObject typeFor(Ruby runtime, IRubyObject object) { public static EOFError eofError(ThreadContext context, String message) { return (EOFError) context.runtime.newEOFError(message); } + + public static EncodingError.CompatibilityError encodingCompatibilityError(ThreadContext context, String s) { + return (EncodingError.CompatibilityError) context.runtime.newEncodingCompatibilityError(s); + } } From 74f3afe7632d266410ae9a305de9dab894bc30da Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 13:07:30 -0600 Subject: [PATCH 062/168] Avoid calling #size for to_set on Enumerable and Range See https://github.com/ruby/ruby/commit/61500c6f48135ef018f5e496ff292a86b0043c65 and https://github.com/ruby/ruby/commit/79f36c544a0431d9b76c3c11a5f622383eaca7bd The new native Set impl does not appear to have ever called #size from #initialize, so we delete that from our existing impl. --- core/src/main/java/org/jruby/RubyRange.java | 12 ++++++++++++ core/src/main/java/org/jruby/ext/set/RubySet.java | 6 ------ core/src/main/java/org/jruby/runtime/JavaSites.java | 1 + 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyRange.java b/core/src/main/java/org/jruby/RubyRange.java index 23c98090dd8..0667aa212cd 100644 --- a/core/src/main/java/org/jruby/RubyRange.java +++ b/core/src/main/java/org/jruby/RubyRange.java @@ -69,6 +69,7 @@ import static org.jruby.api.Error.*; import static org.jruby.runtime.Helpers.hashEnd; import static org.jruby.runtime.Helpers.hashStart; +import static org.jruby.runtime.Helpers.invokeSuper; import static org.jruby.runtime.Helpers.invokedynamic; import static org.jruby.runtime.Helpers.murmurCombine; import static org.jruby.runtime.Helpers.safeHashLong; @@ -1370,6 +1371,17 @@ public IRubyObject size(ThreadContext context) { return context.nil; } + @JRubyMethod(name = "to_set", rest = true, frame = true) + public IRubyObject to_set(ThreadContext context, IRubyObject[] args) { + IRubyObject size = sites(context).size.call(context, this, this); + + if (size instanceof RubyFloat flote && flote.isInfinite()) { + throw context.runtime.newRangeError("cannot convert an infinite range to a set"); + } + + return invokeSuper(context, this, context.getFrameKlazz(), context.getFrameName(), args, Block.NULL_BLOCK); + } + private boolean discreteObject(IRubyObject object) { return object.respondsTo("succ"); } diff --git a/core/src/main/java/org/jruby/ext/set/RubySet.java b/core/src/main/java/org/jruby/ext/set/RubySet.java index a19e0175319..845ad557781 100644 --- a/core/src/main/java/org/jruby/ext/set/RubySet.java +++ b/core/src/main/java/org/jruby/ext/set/RubySet.java @@ -257,12 +257,6 @@ public IRubyObject initialize(final ThreadContext context, IRubyObject enume, fi } return set; // done } else { - if (enume.getType().isKindOfModule(context.runtime.getEnumerable()) && enume.respondsTo("size")) { - IRubyObject size = enume.callMethod(context, "size"); - if (size instanceof RubyFloat flote && flote.isInfinite()) { - throw Error.rangeError(context, "cannot initialize Set from an object with infinite size"); - } - } allocHash(context); if (block.isGiven()) { diff --git a/core/src/main/java/org/jruby/runtime/JavaSites.java b/core/src/main/java/org/jruby/runtime/JavaSites.java index a2e1fa28918..6dd889d719c 100644 --- a/core/src/main/java/org/jruby/runtime/JavaSites.java +++ b/core/src/main/java/org/jruby/runtime/JavaSites.java @@ -473,6 +473,7 @@ public static class RangeSites { public final CallSite op_lt = new FunctionalCachingCallSite("<"); public final CachingCallSite each = new FunctionalCachingCallSite("each"); public final CallSite reverse_each = new FunctionalCachingCallSite("reverse_each"); + public final CallSite size = new FunctionalCachingCallSite("size"); } public static class WarningSites { From dce61342424a7f7b69c99507cc59ddfffafa14a8 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 13:35:58 -0600 Subject: [PATCH 063/168] Ensure Module#name is frozen --- core/src/main/java/org/jruby/RubyModule.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyModule.java b/core/src/main/java/org/jruby/RubyModule.java index 687672231b7..aa269716bbc 100644 --- a/core/src/main/java/org/jruby/RubyModule.java +++ b/core/src/main/java/org/jruby/RubyModule.java @@ -827,7 +827,7 @@ private RubyString calculateAnonymousRubyName(ThreadContext context) { anonBase.append(metaClass.getRealClass().rubyName(context)).append(newString(context, ":0x")); anonBase.append(newString(context, Integer.toHexString(System.identityHashCode(this)))).append(newString(context, ">")); - return anonBase; + return (RubyString) anonBase.freeze(context); } private RubyString calculateRubyName(ThreadContext context) { @@ -836,7 +836,7 @@ private RubyString calculateRubyName(ThreadContext context) { if (getBaseName() == null) return calculateAnonymousRubyName(context); // no name...anonymous! if (usingTemporaryName()) { // temporary name - cachedRubyName = asSymbol(context, baseName).toRubyString(context); + cachedRubyName = (RubyString) asSymbol(context, baseName).toRubyString(context).freeze(context); return cachedRubyName; } @@ -863,7 +863,7 @@ private RubyString calculateRubyName(ThreadContext context) { RubyString fullName = buildPathString(context, parents); - if (cache) cachedRubyName = fullName; + if (cache) cachedRubyName = (RubyString) fullName.freeze(context); return fullName; } From 8ca9ae00395c3a81f1b6417a8ae64d31d012c99e Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 13:36:06 -0600 Subject: [PATCH 064/168] Check frozen status setting Module temp name --- core/src/main/java/org/jruby/RubyModule.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/org/jruby/RubyModule.java b/core/src/main/java/org/jruby/RubyModule.java index aa269716bbc..94b4d7973e8 100644 --- a/core/src/main/java/org/jruby/RubyModule.java +++ b/core/src/main/java/org/jruby/RubyModule.java @@ -999,6 +999,8 @@ private String calculateAnonymousName(ThreadContext context) { @JRubyMethod(required = 1) public IRubyObject set_temporary_name(ThreadContext context, IRubyObject arg) { + checkFrozen(); + if (baseName != null && IdUtil.isValidConstantName(baseName) && (parent == null || parent.baseName != null)) { throw runtimeError(context, "can't change permanent name"); } From de7a50855e756e7bdf1d7cabda769d5527f8a462 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 13:42:24 -0600 Subject: [PATCH 065/168] Delegate to Array#fetch from Array#fetch_values --- core/src/main/java/org/jruby/RubyArray.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyArray.java b/core/src/main/java/org/jruby/RubyArray.java index 526826e4651..986e4aa2858 100644 --- a/core/src/main/java/org/jruby/RubyArray.java +++ b/core/src/main/java/org/jruby/RubyArray.java @@ -1032,13 +1032,7 @@ public IRubyObject fetch_values(ThreadContext context, IRubyObject[] args, Block var result = Create.allocArray(context, length); for (int i = 0; i < length; i++) { int index = toInt(context, args[i]); - // FIXME: lookup the bounds part of this in error message?? - if (index >= arraySize) { - if (!block.isGiven()) throw indexError(context, "index " + index + " outside of array bounds: 0...0"); - result.append(context, block.yield(context, asFixnum(context, index))); - } else { - result.append(context, eltOk(index)); - } + result.append(context, fetch(context, args[i], block)); } return result; From e9dff455a46cb117b7ffe02bd2d14b72a1baa37b Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 15:57:38 -0600 Subject: [PATCH 066/168] Specification#has_rdoc was finally removed --- test/jruby/test_marshal_gemspec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/jruby/test_marshal_gemspec.rb b/test/jruby/test_marshal_gemspec.rb index 87059830fda..4e287365d37 100644 --- a/test/jruby/test_marshal_gemspec.rb +++ b/test/jruby/test_marshal_gemspec.rb @@ -107,7 +107,6 @@ def test_dump_and_load_from_source s.date = %q{2007-11-04} s.description = %q{Install this gem to use Derby with JRuby on Rails.} s.email = %q{nick@nicksieger.com, ola.bini@gmail.com} - s.has_rdoc = true s.homepage = %q{http://jruby-extras.rubyforge.org/ActiveRecord-JDBC} s.require_paths = ["lib"] s.required_ruby_version = Gem::Requirement.new("> 0.0.0") From eedab814f83f1ed594bb340fe19b1a0d1001e90f Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 13 Jan 2026 16:18:26 -0600 Subject: [PATCH 067/168] Remove CGI gem and commit remaining pieces CGI gem has been removed and only the cgi/escape extension remains. See https://github.com/ruby/ruby/pull/13275 --- .gitignore | 1 - .../org/jruby/ext/cgi/escape/CGIEscape.java | 574 ++++++++++++++++++ lib/pom.rb | 1 - lib/pom.xml | 16 - lib/ruby/stdlib/cgi.rb | 7 + lib/ruby/stdlib/cgi/escape.rb | 236 +++++++ lib/ruby/stdlib/cgi/util.rb | 7 + 7 files changed, 824 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/org/jruby/ext/cgi/escape/CGIEscape.java create mode 100644 lib/ruby/stdlib/cgi.rb create mode 100644 lib/ruby/stdlib/cgi/escape.rb create mode 100644 lib/ruby/stdlib/cgi/util.rb diff --git a/.gitignore b/.gitignore index 60d12502420..7a6fdac04a7 100644 --- a/.gitignore +++ b/.gitignore @@ -59,7 +59,6 @@ lib/ruby/stdlib/*.jar lib/ruby/stdlib/ant* lib/ruby/stdlib/benchmark* lib/ruby/stdlib/bundler* -lib/ruby/stdlib/cgi* lib/ruby/stdlib/debug* lib/ruby/stdlib/delegate* lib/ruby/stdlib/did_you_mean* diff --git a/core/src/main/java/org/jruby/ext/cgi/escape/CGIEscape.java b/core/src/main/java/org/jruby/ext/cgi/escape/CGIEscape.java new file mode 100644 index 00000000000..63c982b1063 --- /dev/null +++ b/core/src/main/java/org/jruby/ext/cgi/escape/CGIEscape.java @@ -0,0 +1,574 @@ +/* + **** BEGIN LICENSE BLOCK ***** + * BSD 2-Clause License + * + * Copyright (c) 2016, Charles Oliver Nutter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***** END LICENSE BLOCK *****/ + +package org.jruby.ext.cgi.escape; + +import org.jcodings.Encoding; +import org.jcodings.specific.ISO8859_1Encoding; +import org.jcodings.specific.UTF8Encoding; +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyEncoding; +import org.jruby.RubyModule; +import org.jruby.RubyString; +import org.jruby.anno.JRubyMethod; +import org.jruby.runtime.Block; +import org.jruby.runtime.Helpers; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.load.Library; +import org.jruby.util.ByteList; +import org.jruby.util.StringSupport; +import org.jruby.util.io.EncodingUtils; + +public class CGIEscape implements Library { + public static final String ACCEPT_CHARSET = "@@accept_charset"; + public static final byte[] NO39 = "'".getBytes(RubyEncoding.UTF8); + public static final byte[] AMP = "&".getBytes(RubyEncoding.UTF8); + public static final byte[] QUOT = """.getBytes(RubyEncoding.UTF8); + public static final byte[] LT = "<".getBytes(RubyEncoding.UTF8); + public static final byte[] GT = ">".getBytes(RubyEncoding.UTF8); + public static final int UNICODE_MAX = 0x10ffff; + public static final byte[] TSEMI = "t;".getBytes(RubyEncoding.UTF8); + public static final byte[] UOTSEMI = "uot;".getBytes(RubyEncoding.UTF8); + public static final byte[] MPSEMI = "mp;".getBytes(RubyEncoding.UTF8); + public static final byte[] POSSEMI = "pos;".getBytes(RubyEncoding.UTF8); + + static void html_escaped_cat(RubyString str, byte c) { + switch (c) { + case '\'': + str.cat(NO39); + break; + case '&': + str.cat(AMP); + break; + case '"': + str.cat(QUOT); + break; + case '<': + str.cat(LT); + break; + case '>': + str.cat(GT); + break; + } + } + + static void preserve_original_state(RubyString orig, RubyString dest) { + dest.setEncoding(orig.getEncoding()); + + dest.infectBy(orig); + } + + static IRubyObject + optimized_escape_html(Ruby runtime, RubyString str) { + int i, len, beg = 0; + RubyString dest = null; + byte[] cstrBytes; + + len = str.size(); + ByteList byteList = str.getByteList(); + cstrBytes = byteList.unsafeBytes(); + int cstr = byteList.begin(); + + for (i = 0; i < len; i++) { + switch (cstrBytes[cstr + i]) { + case '\'': + case '&': + case '"': + case '<': + case '>': + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + + dest.cat(cstrBytes, cstr + beg, i - beg); + beg = i + 1; + + html_escaped_cat(dest, cstrBytes[cstr + i]); + break; + } + } + + if (dest != null) { + dest.cat(cstrBytes, cstr + beg, len - beg); + preserve_original_state(str, dest); + return dest; + } else { + return str.strDup(runtime); + } + } + + // Set of i has to happen outside this; see MATCH macro in MRI ext/cgi/escape/escape.c + static boolean MATCH(byte[] s, int len, int i, byte[] cstrBytes, int cstr) { + if (len - i >= s.length && ByteList.memcmp(cstrBytes, cstr + i, s, 0, s.length) == 0) { + return true; + } else { + return false; + } + } + + static IRubyObject + optimized_unescape_html(Ruby runtime, RubyString str) { + Encoding enc = str.getEncoding(); + int charlimit = (enc instanceof UTF8Encoding) ? UNICODE_MAX : + (enc instanceof ISO8859_1Encoding) ? 256 : + 128; + int i, j, len, beg = 0; + int clen = 0, plen; + boolean overflow = false; + byte[] cstrBytes; + int cstr; + byte[] buf = new byte[6]; + RubyString dest = null; + + len = str.size(); + ByteList byteList = str.getByteList(); + cstrBytes = byteList.getUnsafeBytes(); + cstr = byteList.begin(); + + for (i = 0; i < len; i++) { + int cc; + int c = cstrBytes[cstr + i]; + if (c != '&') continue; + plen = i - beg; + if (++i >= len) break; + c = cstrBytes[cstr + i] & 0xFF; + j = i; + switch (c) { + case 'a': + ++i; + if (MATCH(POSSEMI, len, i, cstrBytes, cstr)) { + i += POSSEMI.length - 1; + c = '\''; + } else if (MATCH(MPSEMI, len, i, cstrBytes, cstr)) { + i += MPSEMI.length - 1; + c = '&'; + } else { + i = j; + continue; + } + break; + case 'q': + ++i; + if (MATCH(UOTSEMI, len, i, cstrBytes, cstr)) { + i += UOTSEMI.length - 1; + c = '"'; + } else { + i = j; + continue; + } + break; + case 'g': + ++i; + if (MATCH(TSEMI, len, i, cstrBytes, cstr)) { + i += TSEMI.length - 1; + c = '>'; + } else { + i = j; + continue; + } + break; + case 'l': + ++i; + if (MATCH(TSEMI, len, i, cstrBytes, cstr)) { + i += TSEMI.length - 1; + c = '<'; + } else { + i = j; + continue; + } + break; + case '#': + if (len - ++i >= 2 && Character.isDigit(cstrBytes[cstr + i])) { + int[] clenOverflow = {clen, overflow ? 1 : 0}; + cc = ruby_scan_digits(cstrBytes, cstr + i, len - i, 10, clenOverflow); + clen = clenOverflow[0]; + overflow = clenOverflow[1] == 1; + } else if (i < len && (cstrBytes[cstr + i] == 'x' || cstrBytes[cstr + i] == 'X') && len - ++i >= 2 && ISXDIGIT(cstrBytes, cstr + i)) { + int[] clenOverflow = {clen, overflow ? 1 : 0}; + cc = ruby_scan_digits(cstrBytes, cstr + i, len - i, 16, clenOverflow); + clen = clenOverflow[0]; + overflow = clenOverflow[1] == 1; + } else { + i = j; + continue; + } + i += clen; + if (overflow || cc >= charlimit || i >= len || cstrBytes[cstr + i] != ';') { + i = j; + continue; + } + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + dest.cat(cstrBytes, cstr + beg, plen); + if (charlimit > 256) { + dest.cat(buf, 0, enc.codeToMbc(cc, buf, 0)); + } else { + c = cc; + dest.cat(c); + } + beg = i + 1; + continue; + default: + --i; + continue; + } + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + dest.cat(cstrBytes, cstr + beg, plen); + dest.cat(c); + beg = i + 1; + } + + if (dest != null) { + dest.cat(cstrBytes, cstr + beg, len - beg); + preserve_original_state(str, dest); + return dest; + } else { + return str.strDup(runtime); + } + } + + static boolean ISXDIGIT(byte[] cstrBytes, int i) { + byte cstrByte = cstrBytes[i]; + return (cstrByte >= '0' && cstrByte <= '9') || (cstrByte >= 'a' && cstrByte <= 'f') || (cstrByte >= 'A' && cstrByte <= 'F'); + } + + static boolean url_unreserved_char(int c) { + switch (c) { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '-': case '.': case '_': case '~': + return true; + default: + break; + } + return false; + } + + static final byte[] upper_hexdigits = "0123456789ABCDEF".getBytes(RubyEncoding.UTF8); + + static IRubyObject optimized_escape(Ruby runtime, RubyString str, boolean escapePlus) { + int i, len, beg = 0; + RubyString dest = null; + byte[] cstrBytes; + int cstr; + byte[] buf = {'%', 0, 0}; + + len = str.size(); + ByteList byteList = str.getByteList(); + cstrBytes = byteList.unsafeBytes(); + cstr = byteList.begin(); + + for (i = 0; i < len; ++i) { + int c = cstrBytes[cstr + i] & 0xFF; + if (!url_unreserved_char(c)) { + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + + dest.cat(cstrBytes, cstr + beg, i - beg); + beg = i + 1; + + if (escapePlus && c == ' ') { + dest.cat('+'); + } else { + buf[1] = upper_hexdigits[(c >> 4) & 0xf]; + buf[2] = upper_hexdigits[c & 0xf]; + dest.cat(buf, 0, 3); + } + } + } + + if (dest != null) { + dest.cat(cstrBytes, cstr + beg, len - beg); + preserve_original_state(str, dest); + return dest; + } else { + return str.strDup(runtime); + } + } + + static IRubyObject + optimized_unescape(ThreadContext context, RubyString str, IRubyObject encoding, boolean unescapePlus) { + int i, len, beg = 0; + RubyString dest = null; + byte[] cstrBytes; + int cstr; + int cr; + Encoding origenc, encidx = EncodingUtils.rbToEncoding(context, encoding); + + len = str.size(); + ByteList byteList = str.getByteList(); + cstrBytes = byteList.unsafeBytes(); + cstr = byteList.begin(); + + int buf = 0; + Ruby runtime = context.runtime; + + for (i = 0; i < len; ++i) { + int c = cstrBytes[cstr + i] & 0xFF; + int clen = 0; + if (c == '%') { + if (i + 3 > len) break; + if (!ISXDIGIT(cstrBytes, cstr + i + 1)) continue; + if (!ISXDIGIT(cstrBytes, cstr + i + 2)) continue; + buf = ((char_to_number(cstrBytes[cstr + i + 1]) << 4) + | char_to_number(cstrBytes[cstr + i + 2])); + clen = 2; + } else if (unescapePlus && c == '+') { + buf = ' '; + } else { + continue; + } + + if (dest == null) { + dest = RubyString.newStringLight(runtime, len); + } + + dest.cat(cstrBytes, cstr + beg, i - beg); + i += clen; + beg = i + 1; + + dest.cat(buf); + } + + if (dest != null) { + dest.cat(cstrBytes, cstr + beg, len - beg); + preserve_original_state(str, dest); + cr = StringSupport.CR_UNKNOWN; + } else { + dest = str.strDup(runtime); + cr = str.getCodeRange(); + } + origenc = str.getEncoding(); + if (origenc != encidx) { + dest.setEncoding(encidx); + if (StringSupport.encCoderangeClean(dest.getCodeRange()) == 0) { + dest.setEncoding(origenc); + if (cr != StringSupport.CR_UNKNOWN) + dest.setCodeRange(cr); + } + } + return dest; + } + + /* + * call-seq: + * CGI.escapeHTML(string) -> string + * + * Returns HTML-escaped string. + * + */ + @JRubyMethod(name = "escapeHTML", module = true, frame = true) + public static IRubyObject cgiesc_escape_html(ThreadContext context, IRubyObject self, IRubyObject _str) { + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + return optimized_escape_html(context.runtime, str); + } else { + return Helpers.invokeSuper(context, self, _str, Block.NULL_BLOCK); + } + } + + /* + * call-seq: + * CGI.unescapeHTML(string) -> string + * + * Returns HTML-unescaped string. + * + */ + @JRubyMethod(name = "unescapeHTML", module = true, frame = true) + public static IRubyObject cgiesc_unescape_html(ThreadContext context, IRubyObject self, IRubyObject _str) { + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + return optimized_unescape_html(context.runtime, str); + } else { + return Helpers.invokeSuper(context, self, _str, Block.NULL_BLOCK); + } + } + + /* + * call-seq: + * CGI.escape(string) -> string + * + * Returns URL-escaped string. + * + */ + @JRubyMethod(name = "escape", module = true, frame = true) + public static IRubyObject cgiesc_escape(ThreadContext context, IRubyObject self, IRubyObject _str) { + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + return optimized_escape(context.runtime, str, true); + } else { + return Helpers.invokeSuper(context, self, _str, Block.NULL_BLOCK); + } + } + + /* + * call-seq: + * CGI.escapeURIComponent(string) -> string + * + * Returns URL-escaped string following RFC 3986. + * + */ + @JRubyMethod(name = "escapeURIComponent", alias = { "escape_uri_component" }, module = true, frame = true) + public static IRubyObject cgiesc_escape_uri_component(ThreadContext context, IRubyObject self, IRubyObject _str) { + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + return optimized_escape(context.runtime, str, false); + } else { + return Helpers.invokeSuper(context, self, _str, Block.NULL_BLOCK); + } + } + + static IRubyObject accept_charset(IRubyObject[] args, int argc, int argv, IRubyObject self) { + if (argc > 0) + return args[argv]; + return self.getMetaClass().getClassVar(ACCEPT_CHARSET); + } + + /* + * call-seq: + * CGI.unescape(string, encoding=@@accept_charset) -> string + * + * Returns URL-unescaped string. + * + */ + @JRubyMethod(name = "unescape", required = 1, optional = 1, module = true, frame = true) + public static IRubyObject cgiesc_unescape(ThreadContext context, IRubyObject self, IRubyObject[] argv) { + IRubyObject _str = argv[0]; + + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + IRubyObject enc = accept_charset(argv, argv.length - 1, 1, self); + return optimized_unescape(context, str, enc, true); + } else { + return Helpers.invokeSuper(context, self, argv, Block.NULL_BLOCK); + } + } + + /* + * call-seq: + * CGI.unescapeURIComponent(string, encoding=@@accept_charset) -> string + * + * Returns URL-unescaped string following RFC 3986. + * + */ + @JRubyMethod(name = "unescapeURIComponent", alias = { "unescape_uri_component" }, required = 1, optional = 1, module = true, frame = true) + public static IRubyObject cgiesc_unescape_uri_component(ThreadContext context, IRubyObject self, IRubyObject[] argv) { + IRubyObject _str = argv[0]; + + RubyString str = _str.convertToString(); + + if (str.getEncoding().isAsciiCompatible()) { + IRubyObject enc = accept_charset(argv, argv.length - 1, 1, self); + return optimized_unescape(context, str, enc, false); + } else { + return Helpers.invokeSuper(context, self, argv, Block.NULL_BLOCK); + } + } + + public void load(Ruby runtime, boolean wrap) { + RubyClass rb_cCGI = runtime.defineClass("CGI", runtime.getObject(), runtime.getObject().getAllocator()); + RubyModule rb_mEscape = rb_cCGI.defineModuleUnder("Escape"); + RubyModule rb_mUtil = rb_cCGI.defineModuleUnder("Util"); + rb_mEscape.defineAnnotatedMethods(CGIEscape.class); + rb_mUtil.prependModule(rb_mEscape); + rb_mEscape.extend_object(rb_cCGI); + } + + // PORTED FROM OTHER FILES IN MRI + + static int ruby_scan_digits(byte[] strBytes, int str, int len, int base, int[] retlenOverflow) { + int start = str; + int ret = 0, x; + int mul_overflow = Integer.MAX_VALUE / base; + + retlenOverflow[1] = 0; + + if (len == 0) { + retlenOverflow[0] = 0; + return 0; + } + + do { + int d = ruby_digit36_to_number_table[strBytes[str++]]; + if (d == -1 || base <= d) { + --str; + break; + } + if (mul_overflow < ret) + retlenOverflow[1] = 1; + ret *= base; + x = ret; + ret += d; + if (ret < x) + retlenOverflow[1] = 1; + } while (len < 0 || (--len != 0)); + retlenOverflow[0] = str - start; + return ret; + } + + static int char_to_number(int c) { + return ruby_digit36_to_number_table[c]; + } + + private static final int ruby_digit36_to_number_table[] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /*0*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*1*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*2*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*3*/ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + /*4*/ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + /*5*/ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + /*6*/ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + /*7*/ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + /*8*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*9*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*a*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*b*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*c*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*d*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*e*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /*f*/ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; +} diff --git a/lib/pom.rb b/lib/pom.rb index 6ac07d539a2..c0fbb87d113 100644 --- a/lib/pom.rb +++ b/lib/pom.rb @@ -23,7 +23,6 @@ def log(message = nil) # - we do not want bin/update_rubygems or bin/gem overrides ['rubygems-update', '4.0.3', { bin: false, require_paths: ['lib'] }], ['bundler', '4.0.3'], - ['cgi', '0.4.2'], # Currently using a stub gem for JRuby until we can incorporate our code. # https://github.com/ruby/date/issues/48 ['date', '3.5.1'], diff --git a/lib/pom.xml b/lib/pom.xml index a1037c32387..a1745224dc4 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -57,19 +57,6 @@ DO NOT MODIFY - GENERATED CODE - - rubygems - cgi - 0.4.2 - gem - provided - - - rubygems - jar-dependencies - - - rubygems date @@ -1133,7 +1120,6 @@ DO NOT MODIFY - GENERATED CODE specifications/default/* specifications/rubygems-update-4.0.3* specifications/bundler-4.0.3* - specifications/cgi-0.4.2* specifications/date-3.5.1* specifications/delegate-0.6.1* specifications/did_you_mean-2.0.0* @@ -1216,7 +1202,6 @@ DO NOT MODIFY - GENERATED CODE specifications/test-unit-3.7.5* gems/rubygems-update-4.0.3*/**/* gems/bundler-4.0.3*/**/* - gems/cgi-0.4.2*/**/* gems/date-3.5.1*/**/* gems/delegate-0.6.1*/**/* gems/did_you_mean-2.0.0*/**/* @@ -1299,7 +1284,6 @@ DO NOT MODIFY - GENERATED CODE gems/test-unit-3.7.5*/**/* cache/rubygems-update-4.0.3* cache/bundler-4.0.3* - cache/cgi-0.4.2* cache/date-3.5.1* cache/delegate-0.6.1* cache/did_you_mean-2.0.0* diff --git a/lib/ruby/stdlib/cgi.rb b/lib/ruby/stdlib/cgi.rb new file mode 100644 index 00000000000..bb306d2e064 --- /dev/null +++ b/lib/ruby/stdlib/cgi.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "cgi/escape" +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE +CGI library is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features. +If you need to use the full features of CGI library, Please install cgi gem. +WARNING diff --git a/lib/ruby/stdlib/cgi/escape.rb b/lib/ruby/stdlib/cgi/escape.rb new file mode 100644 index 00000000000..92470c81bb3 --- /dev/null +++ b/lib/ruby/stdlib/cgi/escape.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +# Since Ruby 4.0, \CGI is a small holder for various escaping methods, included from CGI::Escape +# +# require 'cgi/escape' +# +# CGI.escape("Ruby programming language") +# #=> "Ruby+programming+language" +# CGI.escapeURIComponent("Ruby programming language") +# #=> "Ruby%20programming%20language" +# +# See CGI::Escape module for methods list and their description. +class CGI + module Escape; end + include Escape + extend Escape + module EscapeExt; end # :nodoc: +end + +# Web-related escape/unescape functionality. +module CGI::Escape + @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset) + + # URL-encode a string into application/x-www-form-urlencoded. + # Space characters (" ") are encoded with plus signs ("+") + # url_encoded_string = CGI.escape("'Stop!' said Fred") + # # => "%27Stop%21%27+said+Fred" + def escape(string) + encoding = string.encoding + buffer = string.b + buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m| + '%' + m.unpack('H2' * m.bytesize).join('%').upcase + end + buffer.tr!(' ', '+') + buffer.force_encoding(encoding) + end + + # URL-decode an application/x-www-form-urlencoded string with encoding(optional). + # string = CGI.unescape("%27Stop%21%27+said+Fred") + # # => "'Stop!' said Fred" + def unescape(string, encoding = @@accept_charset) + str = string.tr('+', ' ') + str = str.b + str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| + [m.delete('%')].pack('H*') + end + str.force_encoding(encoding) + str.valid_encoding? ? str : str.force_encoding(string.encoding) + end + + # URL-encode a string following RFC 3986 + # Space characters (" ") are encoded with ("%20") + # url_encoded_string = CGI.escapeURIComponent("'Stop!' said Fred") + # # => "%27Stop%21%27%20said%20Fred" + def escapeURIComponent(string) + encoding = string.encoding + buffer = string.b + buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m| + '%' + m.unpack('H2' * m.bytesize).join('%').upcase + end + buffer.force_encoding(encoding) + end + alias escape_uri_component escapeURIComponent + + # URL-decode a string following RFC 3986 with encoding(optional). + # string = CGI.unescapeURIComponent("%27Stop%21%27+said%20Fred") + # # => "'Stop!'+said Fred" + def unescapeURIComponent(string, encoding = @@accept_charset) + str = string.b + str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| + [m.delete('%')].pack('H*') + end + str.force_encoding(encoding) + str.valid_encoding? ? str : str.force_encoding(string.encoding) + end + + alias unescape_uri_component unescapeURIComponent + + # The set of special characters and their escaped values + TABLE_FOR_ESCAPE_HTML__ = { # :nodoc: + "'" => ''', + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>', + } + + # \Escape special characters in HTML, namely '&\"<> + # CGI.escapeHTML('Usage: foo "bar" ') + # # => "Usage: foo "bar" <baz>" + def escapeHTML(string) + enc = string.encoding + unless enc.ascii_compatible? + if enc.dummy? + origenc = enc + enc = Encoding::Converter.asciicompat_encoding(enc) + string = enc ? string.encode(enc) : string.b + end + table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}] + string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table) + string.encode!(origenc) if origenc + string + else + string = string.b + string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) + string.force_encoding(enc) + end + end + + # Unescape a string that has been HTML-escaped + # CGI.unescapeHTML("Usage: foo "bar" <baz>") + # # => "Usage: foo \"bar\" " + def unescapeHTML(string) + enc = string.encoding + unless enc.ascii_compatible? + if enc.dummy? + origenc = enc + enc = Encoding::Converter.asciicompat_encoding(enc) + string = enc ? string.encode(enc) : string.b + end + string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do + case $1.encode(Encoding::US_ASCII) + when 'apos' then "'".encode(enc) + when 'amp' then '&'.encode(enc) + when 'quot' then '"'.encode(enc) + when 'gt' then '>'.encode(enc) + when 'lt' then '<'.encode(enc) + when /\A#0*(\d+)\z/ then $1.to_i.chr(enc) + when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc) + end + end + string.encode!(origenc) if origenc + return string + end + return string unless string.include? '&' + charlimit = case enc + when Encoding::UTF_8; 0x10ffff + when Encoding::ISO_8859_1; 256 + else 128 + end + string = string.b + string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do + match = $1.dup + case match + when 'apos' then "'" + when 'amp' then '&' + when 'quot' then '"' + when 'gt' then '>' + when 'lt' then '<' + when /\A#0*(\d+)\z/ + n = $1.to_i + if n < charlimit + n.chr(enc) + else + "&##{$1};" + end + when /\A#x([0-9a-f]+)\z/i + n = $1.hex + if n < charlimit + n.chr(enc) + else + "&#x#{$1};" + end + else + "&#{match};" + end + end + string.force_encoding enc + end + + alias escape_html escapeHTML + alias h escapeHTML + + alias unescape_html unescapeHTML + + case RUBY_ENGINE + when "jruby" + JRuby::Util.load_ext("org.jruby.ext.cgi.escape.CGIEscape") + when "truffleruby" + # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there + else + begin + require 'cgi/escape.so' + rescue LoadError + end + end + + # \Escape only the tags of certain HTML elements in +string+. + # + # Takes an element or elements or array of elements. Each element + # is specified by the name of the element, without angle brackets. + # This matches both the start and the end tag of that element. + # The attribute list of the open tag will also be escaped (for + # instance, the double-quotes surrounding attribute values). + # + # print CGI.escapeElement('
', "A", "IMG") + # # "
<A HREF="url"></A>" + # + # print CGI.escapeElement('
', ["A", "IMG"]) + # # "
<A HREF="url"></A>" + def escapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do + CGI.escapeHTML($&) + end + else + string + end + end + + # Undo escaping such as that done by CGI.escapeElement + # + # print CGI.unescapeElement( + # CGI.escapeHTML('
'), "A", "IMG") + # # "<BR>" + # + # print CGI.unescapeElement( + # CGI.escapeHTML('
'), ["A", "IMG"]) + # # "<BR>" + def unescapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:>)?/im) do + unescapeHTML($&) + end + else + string + end + end + + alias escape_element escapeElement + + alias unescape_element unescapeElement + +end diff --git a/lib/ruby/stdlib/cgi/util.rb b/lib/ruby/stdlib/cgi/util.rb new file mode 100644 index 00000000000..50a2e91665e --- /dev/null +++ b/lib/ruby/stdlib/cgi/util.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "cgi/escape" +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE +CGI::Util is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features. +If you are using CGI.parse, please install and use the cgi gem instead. +WARNING From 1bc93aa0c3c40327235b644e504d9f679f806ddd Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 14 Jan 2026 11:59:58 -0600 Subject: [PATCH 068/168] Implement implicit param logic for Binding See https://bugs.ruby-lang.org/issues/21049 --- core/src/main/java/org/jruby/RubyBinding.java | 38 +++++++++++ .../java/org/jruby/parser/StaticScope.java | 68 +++++++++++++++---- 2 files changed, 94 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyBinding.java b/core/src/main/java/org/jruby/RubyBinding.java index 66465e4d8ec..98e249262c2 100644 --- a/core/src/main/java/org/jruby/RubyBinding.java +++ b/core/src/main/java/org/jruby/RubyBinding.java @@ -177,6 +177,26 @@ public IRubyObject local_variable_set(ThreadContext context, IRubyObject symbol, return evalScope.setValue(slot & 0xffff, value, slot >> 16); } + @JRubyMethod(name = "implicit_parameter_defined?") + public IRubyObject implicit_parameter_defined_p(ThreadContext context, IRubyObject symbol) { + String id = checkImplicitId(context, symbol); + return asBoolean(context, binding.getEvalScope(context.runtime).getStaticScope().isDefined(id) != -1); + } + + @JRubyMethod + public IRubyObject implicit_parameter_get(ThreadContext context, IRubyObject symbol) { + String id = checkImplicitId(context, symbol); + DynamicScope evalScope = binding.getEvalScope(context.runtime); + StaticScope staticScope = evalScope.getStaticScope(); + int slot = staticScope.isDefinedNotImplicit(id); + + if (slot != StaticScope.IMPLICIT) throw undefinedImplicitVariableError(context, symbol); + + slot = staticScope.isDefined(id); + + return evalScope.getValueOrNil(slot & 0xffff, slot >> 16, context.nil); + } + private static boolean isNumberedVariable(String id) { return id.length() == 2 && id.charAt(0) == '_' && Character.isDigit(id.charAt(1)); } @@ -189,6 +209,10 @@ private RaiseException undefinedVariableError(ThreadContext context, IRubyObject return nameError(context, str(context.runtime, "local variable '", symbol, "' not defined for " + inspect(context)), symbol); } + private RaiseException undefinedImplicitVariableError(ThreadContext context, IRubyObject symbol) { + return nameError(context, str(context.runtime, "'", symbol, "' is not an implicit parameter"), symbol); + } + // MRI: check_local_id private String checkLocalId(ThreadContext context, IRubyObject obj) { String id = RubySymbol.idStringFromObject(context, obj); @@ -201,11 +225,25 @@ private String checkLocalId(ThreadContext context, IRubyObject obj) { return id; } + + private String checkImplicitId(ThreadContext context, IRubyObject obj) { + String id = RubySymbol.idStringFromObject(context, obj); + + if (!isNumberedVariable(id) && !id.equals("it")) throw undefinedImplicitVariableError(context, obj); + + return id; + } + @JRubyMethod public IRubyObject local_variables(ThreadContext context) { return binding.getEvalScope(context.runtime).getStaticScope().getLocalVariables(context); } + @JRubyMethod + public IRubyObject implicit_parameters(ThreadContext context) { + return binding.getEvalScope(context.runtime).getStaticScope().getImplicitLocalVariables(context); + } + @JRubyMethod(name = "receiver") public IRubyObject receiver(ThreadContext context) { return binding.getSelf(); diff --git a/core/src/main/java/org/jruby/parser/StaticScope.java b/core/src/main/java/org/jruby/parser/StaticScope.java index 9cd2fc288a5..8cf96735d86 100644 --- a/core/src/main/java/org/jruby/parser/StaticScope.java +++ b/core/src/main/java/org/jruby/parser/StaticScope.java @@ -547,6 +547,25 @@ public T collectVariables(IntFunction collectionFactory, BiConsumer T collectVariables(ThreadContext context, BiFunction collectionFactory, BiConsumer collectionPopulator) { + return collectVariables(context, collectionFactory, collectionPopulator, false); + } + + /** + * Populate a deduplicated collection of implicit variable names("it", "_1", etc) in scope using the given functions. + * + * This may include variables that are not strictly Ruby local variable names, so the consumer should validate + * names as appropriate. + * + * @param collectionFactory used to construct the collection + * @param collectionPopulator used to pass values into the collection + * @param resulting collection type + * @return populated collection + */ + public T collectImplicitVariables(ThreadContext context, BiFunction collectionFactory, BiConsumer collectionPopulator) { + return collectVariables(context, collectionFactory, collectionPopulator, true); + } + + public T collectVariables(ThreadContext context, BiFunction collectionFactory, BiConsumer collectionPopulator, boolean implicit) { StaticScope current = this; T collection = collectionFactory.apply(context, current.variableNamesLength); @@ -554,24 +573,34 @@ public T collectVariables(ThreadContext context, BiFunction dedup = new HashMap<>(); while (current.isBlockOrEval) { - addVariableNamesToCollection(current, collectionPopulator, dedup, collection); + addVariableNamesToCollection(current, collectionPopulator, dedup, collection, implicit); current = current.enclosingScope; } // once more for method scope - addVariableNamesToCollection(current, collectionPopulator, dedup, collection); + addVariableNamesToCollection(current, collectionPopulator, dedup, collection, implicit); return collection; } - private static void addVariableNamesToCollection(StaticScope current, BiConsumer collectionPopulator, HashMap dedup, T collection) { + private static void addVariableNamesToCollection(StaticScope current, BiConsumer collectionPopulator, HashMap dedup, T collection, boolean implicit) { BitSet implicitVariables = current.implicitVariables; - for (int i = 0; i < current.variableNamesLength; i++) { - if (implicitVariables != null && implicitVariables.get(i)) continue; - String name = current.variableNames[i]; + boolean hasImplicits = implicitVariables != null; + if (implicit && !hasImplicits) return; + + int variableNamesLength = current.variableNamesLength; + String[] variableNames = current.variableNames; + + for (int i = 0; i < variableNamesLength; i++) { + if (hasImplicits && implicitVariables.get(i)) { + if (!implicit) continue; + } else if (implicit) continue; + + String name = variableNames[i]; dedup.computeIfAbsent(name, key -> { - collectionPopulator.accept(collection, key); return key;}); + collectionPopulator.accept(collection, key); + return key;}); } } @@ -589,11 +618,26 @@ public RubyArray getLocalVariables(Ruby runtime) { public RubyArray getLocalVariables(ThreadContext context) { return collectVariables( context, - (ctxt, length) -> allocArray(ctxt, length), - (array, id) -> { - RubySymbol symbol = Convert.asSymbol(context, id); - if (symbol.validLocalVariableName()) array.append(context, symbol); - }); + Create::allocArray, + (array, id) -> appendVariableIfValid(context, array, id)); + } + + /** + * Get a Ruby Array of all implicit local variables. + * + * @param context the current context + * @return populated RubyArray + */ + public RubyArray getImplicitLocalVariables(ThreadContext context) { + return collectImplicitVariables( + context, + Create::allocArray, + (array, id) -> appendVariableIfValid(context, array, id)); + } + + private static void appendVariableIfValid(ThreadContext context, RubyArray array, String id) { + RubySymbol symbol = Convert.asSymbol(context, id); + if (symbol.validLocalVariableName()) array.append(context, symbol); } public int isDefined(String name, int depth) { From 2719a900fc48f7f66f7f40518fa965e84a2e8388 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 14 Jan 2026 12:48:54 -0600 Subject: [PATCH 069/168] Update reline tests --- test/mri/reline/helper.rb | 42 +- test/mri/reline/test_config.rb | 17 + test/mri/reline/test_key_actor_emacs.rb | 664 +++++++++--------- test/mri/reline/test_key_actor_vi.rb | 23 +- test/mri/reline/test_reline.rb | 27 +- test/mri/reline/test_unicode.rb | 58 +- test/mri/reline/test_within_pipe.rb | 16 +- test/mri/reline/yamatanooroti/multiline_repl | 4 +- .../reline/yamatanooroti/test_rendering.rb | 105 ++- 9 files changed, 562 insertions(+), 394 deletions(-) diff --git a/test/mri/reline/helper.rb b/test/mri/reline/helper.rb index 37a99bb18a8..6f470a617fe 100644 --- a/test/mri/reline/helper.rb +++ b/test/mri/reline/helper.rb @@ -86,26 +86,12 @@ def test_rubybin end class Reline::TestCase < Test::Unit::TestCase - private def convert_str(input, options = {}, normalized = nil) - return nil if input.nil? - input = input.chars.map { |c| - if Reline::Unicode::EscapedChars.include?(c.ord) - c - else - c.encode(@line_editor.encoding, Encoding::UTF_8, **options) - end - }.join - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError - if unicode?(input.encoding) - input = input.unicode_normalize(:nfc) - if normalized - options[:undef] = :replace - options[:replace] = '?' - end - normalized = true - retry - end - input + private def convert_str(input) + input.encode(@line_editor.encoding, Encoding::UTF_8) + end + + def omit_unless_utf8 + omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 end def input_key_by_symbol(method_symbol, char: nil, csi: false) @@ -113,17 +99,9 @@ def input_key_by_symbol(method_symbol, char: nil, csi: false) @line_editor.input_key(Reline::Key.new(char, method_symbol, false)) end - def input_keys(input, convert = true) - # Reline does not support convert-meta, but test data includes \M-char. It should be converted to ESC+char. - # Note that mixing unicode chars and \M-char is not recommended. "\M-C\M-\C-A" is a single unicode character. - input = input.chars.map do |c| - c.valid_encoding? ? c : "\e#{(c.bytes[0] & 0x7f).chr}" - end.join - input_raw_keys(input, convert) - end + def input_keys(input) + input = convert_str(input) - def input_raw_keys(input, convert = true) - input = convert_str(input) if convert key_stroke = Reline::KeyStroke.new(@config, @encoding) input_bytes = input.bytes until input_bytes.empty? @@ -177,8 +155,4 @@ def assert_key_binding(input, method_symbol, editing_modes = [:emacs, :vi_insert assert_equal(method_symbol, @config.editing_mode.get(input.bytes)) end end - - private def unicode?(encoding) - [Encoding::UTF_8, Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].include?(encoding) - end end diff --git a/test/mri/reline/test_config.rb b/test/mri/reline/test_config.rb index 3c9094eeced..ff3e121ab0f 100644 --- a/test/mri/reline/test_config.rb +++ b/test/mri/reline/test_config.rb @@ -613,4 +613,21 @@ def test_reload @config.reload assert_equal '@', @config.emacs_mode_string end + + def test_invalid_byte_sequence_inputrc + lines = [ + "set vi-cmd-mode-string\n", + "$if Ruby\n", + " \"\C-a\": \"Ruby\"\n", + "$else \"\xFF\"\n".dup.force_encoding(Reline.encoding_system_needs), # Invalid byte sequence + " \"\C-b\": \"NotRuby\"\n", + "$endif\n" + ] + + e = assert_raise(Reline::Config::InvalidInputrc) do + @config.read_lines(lines, "INPUTRC") + end + + assert_equal "INPUTRC:4: can't be converted to the locale #{Reline.encoding_system_needs}", e.message + end end diff --git a/test/mri/reline/test_key_actor_emacs.rb b/test/mri/reline/test_key_actor_emacs.rb index e52c6c2d9ae..aa608641c3c 100644 --- a/test/mri/reline/test_key_actor_emacs.rb +++ b/test/mri/reline/test_key_actor_emacs.rb @@ -38,11 +38,13 @@ def test_ed_insert_mbchar_two end def test_ed_insert_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099") assert_line_around_cursor("か\u3099", '') end def test_ed_insert_for_plural_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099") assert_line_around_cursor("か\u3099き\u3099", '') end @@ -50,11 +52,11 @@ def test_ed_insert_for_plural_mbchar_by_plural_code_points def test_move_next_and_prev input_keys('abd') assert_line_around_cursor('abd', '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('ab', 'd') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('a', 'bd') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('ab', 'd') input_keys('c') assert_line_around_cursor('abc', 'd') @@ -63,24 +65,25 @@ def test_move_next_and_prev def test_move_next_and_prev_for_mbchar input_keys('かきけ') assert_line_around_cursor('かきけ', '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('かき', 'け') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('か', 'きけ') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('かき', 'け') input_keys('く') assert_line_around_cursor('かきく', 'け') end def test_move_next_and_prev_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099け\u3099") assert_line_around_cursor("か\u3099き\u3099け\u3099", '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor("か\u3099き\u3099", "け\u3099") - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor("か\u3099", "き\u3099け\u3099") - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor("か\u3099き\u3099", "け\u3099") input_keys("く\u3099") assert_line_around_cursor("か\u3099き\u3099く\u3099", "け\u3099") @@ -89,11 +92,11 @@ def test_move_next_and_prev_for_mbchar_by_plural_code_points def test_move_to_beg_end input_keys('bcd') assert_line_around_cursor('bcd', '') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'bcd') input_keys('a') assert_line_around_cursor('a', 'bcd') - input_keys("\C-e", false) + input_keys("\C-e") assert_line_around_cursor('abcd', '') input_keys('e') assert_line_around_cursor('abcde', '') @@ -103,7 +106,7 @@ def test_ed_newline_with_cr input_keys('ab') assert_line_around_cursor('ab', '') refute(@line_editor.finished?) - input_keys("\C-m", false) + input_keys("\C-m") assert_line_around_cursor('ab', '') assert(@line_editor.finished?) end @@ -112,7 +115,7 @@ def test_ed_newline_with_lf input_keys('ab') assert_line_around_cursor('ab', '') refute(@line_editor.finished?) - input_keys("\C-j", false) + input_keys("\C-j") assert_line_around_cursor('ab', '') assert(@line_editor.finished?) end @@ -120,21 +123,22 @@ def test_ed_newline_with_lf def test_em_delete_prev_char input_keys('ab') assert_line_around_cursor('ab', '') - input_keys("\C-h", false) + input_keys("\C-h") assert_line_around_cursor('a', '') end def test_em_delete_prev_char_for_mbchar input_keys('かき') assert_line_around_cursor('かき', '') - input_keys("\C-h", false) + input_keys("\C-h") assert_line_around_cursor('か', '') end def test_em_delete_prev_char_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099") assert_line_around_cursor("か\u3099き\u3099", '') - input_keys("\C-h", false) + input_keys("\C-h") assert_line_around_cursor("か\u3099", '') end @@ -160,13 +164,13 @@ def test_ed_quoted_insert_with_vi_arg end def test_ed_kill_line - input_keys("\C-k", false) + input_keys("\C-k") assert_line_around_cursor('', '') input_keys('abc') assert_line_around_cursor('abc', '') - input_keys("\C-k", false) + input_keys("\C-k") assert_line_around_cursor('abc', '') - input_keys("\C-b\C-k", false) + input_keys("\C-b\C-k") assert_line_around_cursor('ab', '') end @@ -177,11 +181,11 @@ def test_em_kill_line input_key_by_symbol(:em_kill_line) assert_line_around_cursor('', '') input_keys('abc') - input_keys("\C-b", false) + input_keys("\C-b") input_key_by_symbol(:em_kill_line) assert_line_around_cursor('', '') input_keys('abc') - input_keys("\C-a", false) + input_keys("\C-a") input_key_by_symbol(:em_kill_line) assert_line_around_cursor('', '') end @@ -189,19 +193,19 @@ def test_em_kill_line def test_ed_move_to_beg input_keys('abd') assert_line_around_cursor('abd', '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('ab', 'd') input_keys('c') assert_line_around_cursor('abc', 'd') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'abcd') input_keys('012') assert_line_around_cursor('012', 'abcd') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', '012abcd') input_keys('ABC') assert_line_around_cursor('ABC', '012abcd') - input_keys("\C-f" * 10 + "\C-a", false) + input_keys("\C-f" * 10 + "\C-a") assert_line_around_cursor('', 'ABC012abcd') input_keys('a') assert_line_around_cursor('a', 'ABC012abcd') @@ -210,26 +214,26 @@ def test_ed_move_to_beg def test_ed_move_to_beg_with_blank input_keys(' abc') assert_line_around_cursor(' abc', '') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', ' abc') end def test_ed_move_to_end input_keys('abd') assert_line_around_cursor('abd', '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('ab', 'd') input_keys('c') assert_line_around_cursor('abc', 'd') - input_keys("\C-e", false) + input_keys("\C-e") assert_line_around_cursor('abcd', '') input_keys('012') assert_line_around_cursor('abcd012', '') - input_keys("\C-e", false) + input_keys("\C-e") assert_line_around_cursor('abcd012', '') input_keys('ABC') assert_line_around_cursor('abcd012ABC', '') - input_keys("\C-b" * 10 + "\C-e", false) + input_keys("\C-b" * 10 + "\C-e") assert_line_around_cursor('abcd012ABC', '') input_keys('a') assert_line_around_cursor('abcd012ABCa', '') @@ -238,27 +242,28 @@ def test_ed_move_to_end def test_em_delete input_keys('ab') assert_line_around_cursor('ab', '') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'ab') - input_keys("\C-d", false) + input_keys("\C-d") assert_line_around_cursor('', 'b') end def test_em_delete_for_mbchar input_keys('かき') assert_line_around_cursor('かき', '') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'かき') - input_keys("\C-d", false) + input_keys("\C-d") assert_line_around_cursor('', 'き') end def test_em_delete_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099") assert_line_around_cursor("か\u3099き\u3099", '') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', "か\u3099き\u3099") - input_keys("\C-d", false) + input_keys("\C-d") assert_line_around_cursor('', "き\u3099") end @@ -270,16 +275,16 @@ def test_em_delete_ends_editing def test_ed_clear_screen @line_editor.instance_variable_get(:@rendered_screen).lines = [[]] - input_keys("\C-l", false) + input_keys("\C-l") assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines) end def test_ed_clear_screen_with_inputted input_keys('abc') - input_keys("\C-b", false) + input_keys("\C-b") @line_editor.instance_variable_get(:@rendered_screen).lines = [[]] assert_line_around_cursor('ab', 'c') - input_keys("\C-l", false) + input_keys("\C-l") assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines) assert_line_around_cursor('ab', 'c') end @@ -299,7 +304,7 @@ def test_key_delete_does_not_end_editing def test_key_delete_preserves_cursor input_keys('abc') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('ab', 'c') input_key_by_symbol(:key_delete) assert_line_around_cursor('ab', '') @@ -308,289 +313,294 @@ def test_key_delete_preserves_cursor def test_em_next_word assert_line_around_cursor('', '') input_keys('abc def{bbb}ccc') - input_keys("\C-a\M-F", false) + input_keys("\C-a\eF") assert_line_around_cursor('abc', ' def{bbb}ccc') - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor('abc def', '{bbb}ccc') - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor('abc def{bbb', '}ccc') - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor('abc def{bbb}ccc', '') - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor('abc def{bbb}ccc', '') end def test_em_next_word_for_mbchar assert_line_around_cursor('', '') input_keys('あいう かきく{さしす}たちつ') - input_keys("\C-a\M-F", false) + input_keys("\C-a\eF") assert_line_around_cursor('あいう', ' かきく{さしす}たちつ') - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor('あいう かきく', '{さしす}たちつ') - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor('あいう かきく{さしす', '}たちつ') - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor('あいう かきく{さしす}たちつ', '') - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor('あいう かきく{さしす}たちつ', '') end def test_em_next_word_for_mbchar_by_plural_code_points + omit_unless_utf8 assert_line_around_cursor("", "") input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\C-a\M-F", false) + input_keys("\C-a\eF") assert_line_around_cursor("あいう", " か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099", "{さしす}たちつ") - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす", "}たちつ") - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") - input_keys("\M-F", false) + input_keys("\eF") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") end def test_em_prev_word input_keys('abc def{bbb}ccc') assert_line_around_cursor('abc def{bbb}ccc', '') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('abc def{bbb}', 'ccc') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('abc def{', 'bbb}ccc') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('abc ', 'def{bbb}ccc') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('', 'abc def{bbb}ccc') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('', 'abc def{bbb}ccc') end def test_em_prev_word_for_mbchar input_keys('あいう かきく{さしす}たちつ') assert_line_around_cursor('あいう かきく{さしす}たちつ', '') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('あいう かきく{さしす}', 'たちつ') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('あいう かきく{', 'さしす}たちつ') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('あいう ', 'かきく{さしす}たちつ') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') end def test_em_prev_word_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", "たちつ") - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", "さしす}たちつ") - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor("あいう ", "か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\M-B", false) + input_keys("\eB") assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ") end def test_em_delete_next_word input_keys('abc def{bbb}ccc') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'abc def{bbb}ccc') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', ' def{bbb}ccc') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '{bbb}ccc') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '}ccc') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '') end def test_em_delete_next_word_for_mbchar input_keys('あいう かきく{さしす}たちつ') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', ' かきく{さしす}たちつ') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '{さしす}たちつ') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '}たちつ') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '') end def test_em_delete_next_word_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', "あいう か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', " か\u3099き\u3099く\u3099{さしす}たちつ") - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '{さしす}たちつ') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '}たちつ') - input_keys("\M-d", false) + input_keys("\ed") assert_line_around_cursor('', '') end def test_ed_delete_prev_word input_keys('abc def{bbb}ccc') assert_line_around_cursor('abc def{bbb}ccc', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('abc def{bbb}', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('abc def{', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('abc ', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('', '') end def test_ed_delete_prev_word_for_mbchar input_keys('あいう かきく{さしす}たちつ') assert_line_around_cursor('あいう かきく{さしす}たちつ', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('あいう かきく{さしす}', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('あいう かきく{', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('あいう ', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('', '') end def test_ed_delete_prev_word_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('あいう ', '') - input_keys("\M-\C-H", false) + input_keys("\e\C-H") assert_line_around_cursor('', '') end def test_ed_transpose_chars input_keys('abc') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'abc') - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor('', 'abc') - input_keys("\C-f\C-t", false) + input_keys("\C-f\C-t") assert_line_around_cursor('ba', 'c') - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor('bca', '') - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor('bac', '') end def test_ed_transpose_chars_for_mbchar input_keys('あかさ') - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'あかさ') - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor('', 'あかさ') - input_keys("\C-f\C-t", false) + input_keys("\C-f\C-t") assert_line_around_cursor('かあ', 'さ') - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor('かさあ', '') - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor('かあさ', '') end def test_ed_transpose_chars_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("あか\u3099さ") - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', "あか\u3099さ") - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor('', "あか\u3099さ") - input_keys("\C-f\C-t", false) + input_keys("\C-f\C-t") assert_line_around_cursor("か\u3099あ", 'さ') - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor("か\u3099さあ", '') - input_keys("\C-t", false) + input_keys("\C-t") assert_line_around_cursor("か\u3099あさ", '') end def test_ed_transpose_words input_keys('abc def') assert_line_around_cursor('abc def', '') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('def abc', '') - input_keys("\C-a\C-k", false) + input_keys("\C-a\C-k") input_keys(' abc def ') - input_keys("\C-b" * 4, false) + input_keys("\C-b" * 4) assert_line_around_cursor(' abc de', 'f ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor(' def abc', ' ') - input_keys("\C-a\C-k", false) + input_keys("\C-a\C-k") input_keys(' abc def ') - input_keys("\C-b" * 6, false) + input_keys("\C-b" * 6) assert_line_around_cursor(' abc ', 'def ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor(' def abc', ' ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor(' abc def', '') end def test_ed_transpose_words_for_mbchar input_keys('あいう かきく') assert_line_around_cursor('あいう かきく', '') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('かきく あいう', '') - input_keys("\C-a\C-k", false) + input_keys("\C-a\C-k") input_keys(' あいう かきく ') - input_keys("\C-b" * 4, false) + input_keys("\C-b" * 4) assert_line_around_cursor(' あいう かき', 'く ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor(' かきく あいう', ' ') - input_keys("\C-a\C-k", false) + input_keys("\C-a\C-k") input_keys(' あいう かきく ') - input_keys("\C-b" * 6, false) + input_keys("\C-b" * 6) assert_line_around_cursor(' あいう ', 'かきく ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor(' かきく あいう', ' ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor(' あいう かきく', '') end def test_ed_transpose_words_with_one_word input_keys('abc ') assert_line_around_cursor('abc ', '') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('abc ', '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('abc ', ' ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('abc ', ' ') - input_keys("\C-b" * 2, false) + input_keys("\C-b" * 2) assert_line_around_cursor('ab', 'c ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('ab', 'c ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('ab', 'c ') end def test_ed_transpose_words_with_one_word_for_mbchar input_keys('あいう ') assert_line_around_cursor('あいう ', '') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('あいう ', '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('あいう ', ' ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('あいう ', ' ') - input_keys("\C-b" * 2, false) + input_keys("\C-b" * 2) assert_line_around_cursor('あい', 'う ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('あい', 'う ') - input_keys("\M-t", false) + input_keys("\et") assert_line_around_cursor('あい', 'う ') end @@ -602,129 +612,130 @@ def test_ed_digit def test_ed_next_and_prev_char input_keys('abc') assert_line_around_cursor('abc', '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('ab', 'c') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('a', 'bc') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('', 'abc') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('', 'abc') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('a', 'bc') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('ab', 'c') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('abc', '') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('abc', '') end def test_ed_next_and_prev_char_for_mbchar input_keys('あいう') assert_line_around_cursor('あいう', '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('あい', 'う') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('あ', 'いう') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('', 'あいう') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('', 'あいう') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('あ', 'いう') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('あい', 'う') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('あいう', '') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor('あいう', '') end def test_ed_next_and_prev_char_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099く\u3099") assert_line_around_cursor("か\u3099き\u3099く\u3099", '') - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor("か\u3099き\u3099", "く\u3099") - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor("か\u3099", "き\u3099く\u3099") - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('', "か\u3099き\u3099く\u3099") - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('', "か\u3099き\u3099く\u3099") - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor("か\u3099", "き\u3099く\u3099") - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor("か\u3099き\u3099", "く\u3099") - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor("か\u3099き\u3099く\u3099", '') - input_keys("\C-f", false) + input_keys("\C-f") assert_line_around_cursor("か\u3099き\u3099く\u3099", '') end def test_em_capitol_case input_keys('abc def{bbb}ccc') - input_keys("\C-a\M-c", false) + input_keys("\C-a\ec") assert_line_around_cursor('Abc', ' def{bbb}ccc') - input_keys("\M-c", false) + input_keys("\ec") assert_line_around_cursor('Abc Def', '{bbb}ccc') - input_keys("\M-c", false) + input_keys("\ec") assert_line_around_cursor('Abc Def{Bbb', '}ccc') - input_keys("\M-c", false) + input_keys("\ec") assert_line_around_cursor('Abc Def{Bbb}Ccc', '') end def test_em_capitol_case_with_complex_example input_keys('{}#* AaA!!!cCc ') - input_keys("\C-a\M-c", false) + input_keys("\C-a\ec") assert_line_around_cursor('{}#* Aaa', '!!!cCc ') - input_keys("\M-c", false) + input_keys("\ec") assert_line_around_cursor('{}#* Aaa!!!Ccc', ' ') - input_keys("\M-c", false) + input_keys("\ec") assert_line_around_cursor('{}#* Aaa!!!Ccc ', '') end def test_em_lower_case input_keys('AbC def{bBb}CCC') - input_keys("\C-a\M-l", false) + input_keys("\C-a\el") assert_line_around_cursor('abc', ' def{bBb}CCC') - input_keys("\M-l", false) + input_keys("\el") assert_line_around_cursor('abc def', '{bBb}CCC') - input_keys("\M-l", false) + input_keys("\el") assert_line_around_cursor('abc def{bbb', '}CCC') - input_keys("\M-l", false) + input_keys("\el") assert_line_around_cursor('abc def{bbb}ccc', '') end def test_em_lower_case_with_complex_example input_keys('{}#* AaA!!!cCc ') - input_keys("\C-a\M-l", false) + input_keys("\C-a\el") assert_line_around_cursor('{}#* aaa', '!!!cCc ') - input_keys("\M-l", false) + input_keys("\el") assert_line_around_cursor('{}#* aaa!!!ccc', ' ') - input_keys("\M-l", false) + input_keys("\el") assert_line_around_cursor('{}#* aaa!!!ccc ', '') end def test_em_upper_case input_keys('AbC def{bBb}CCC') - input_keys("\C-a\M-u", false) + input_keys("\C-a\eu") assert_line_around_cursor('ABC', ' def{bBb}CCC') - input_keys("\M-u", false) + input_keys("\eu") assert_line_around_cursor('ABC DEF', '{bBb}CCC') - input_keys("\M-u", false) + input_keys("\eu") assert_line_around_cursor('ABC DEF{BBB', '}CCC') - input_keys("\M-u", false) + input_keys("\eu") assert_line_around_cursor('ABC DEF{BBB}CCC', '') end def test_em_upper_case_with_complex_example input_keys('{}#* AaA!!!cCc ') - input_keys("\C-a\M-u", false) + input_keys("\C-a\eu") assert_line_around_cursor('{}#* AAA', '!!!cCc ') - input_keys("\M-u", false) + input_keys("\eu") assert_line_around_cursor('{}#* AAA!!!CCC', ' ') - input_keys("\M-u", false) + input_keys("\eu") assert_line_around_cursor('{}#* AAA!!!CCC ', '') end @@ -742,7 +753,7 @@ def test_em_delete_or_list input_keys('fooo') assert_line_around_cursor('fooo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-b", false) + input_keys("\C-b") assert_line_around_cursor('foo', 'o') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_key_by_symbol(:em_delete_or_list) @@ -766,10 +777,10 @@ def test_completion_duplicated_list input_keys('foo_') assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list) end @@ -788,28 +799,28 @@ def test_completion input_keys('fo') assert_line_around_cursor('fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) input_keys('a') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_a', '') - input_keys("\C-h", false) + input_keys("\C-h") input_keys('b') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_ba', '') input_keys("\C-h") input_key_by_symbol(:complete) assert_line_around_cursor('foo_ba', '') - input_keys("\C-h", false) + input_keys("\C-h") input_key_by_symbol(:menu_complete) assert_line_around_cursor('foo_bar', '') input_key_by_symbol(:menu_complete) assert_line_around_cursor('foo_baz', '') - input_keys("\C-h", false) + input_keys("\C-h") input_key_by_symbol(:menu_complete_backward) assert_line_around_cursor('foo_baz', '') input_key_by_symbol(:menu_complete_backward) @@ -829,9 +840,9 @@ def test_autocompletion } input_keys('Re') assert_line_around_cursor('Re', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('Readline', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('Regexp', '') input_key_by_symbol(:completion_journey_up) assert_line_around_cursor('Readline', '') @@ -859,10 +870,10 @@ def test_completion_with_indent input_keys(' fo') assert_line_around_cursor(' fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor(' foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor(' foo_', '') assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) end @@ -884,25 +895,25 @@ def test_completion_with_perfect_match assert_line_around_cursor('fo', '') assert_equal(Reline::LineEditor::CompletionState::NORMAL, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo', '') assert_equal(Reline::LineEditor::CompletionState::MENU_WITH_PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo', '') assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo', '') assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal('foo', matched) matched = nil input_keys('_') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_bar', '') assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_bar', '') assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal('foo_bar', matched) @@ -913,9 +924,9 @@ def test_continuous_completion_with_perfect_match word == 'f' ? ['foo'] : %w[foobar foobaz] } input_keys('f') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('fooba', '') end @@ -925,9 +936,9 @@ def test_continuous_completion_disabled_with_perfect_match } @line_editor.dig_perfect_match_proc = proc {} input_keys('f') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo', '') end @@ -937,13 +948,13 @@ def test_completion_append_character } @line_editor.completion_append_character = 'X' input_keys('f') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') input_keys('f') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_fooX', '') input_keys(' foo_bar') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_fooX foo_barX', '') end @@ -952,22 +963,22 @@ def test_completion_with_quote_append %w[foo bar baz].select { |s| s.start_with? word } } set_line_around_cursor('x = "b', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('x = "ba', '') set_line_around_cursor('x = "f', ' ') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('x = "foo', ' ') set_line_around_cursor("x = 'f", '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor("x = 'foo'", '') set_line_around_cursor('"a "f', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('"a "foo', '') set_line_around_cursor('"a\\" "f', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('"a\\" "foo', '') set_line_around_cursor('"a" "f', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('"a" "foo"', '') end @@ -985,25 +996,25 @@ def test_completion_with_completion_ignore_case input_keys('fo') assert_line_around_cursor('fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list) @config.completion_ignore_case = true - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar Foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) input_keys('a') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_a', '') - input_keys("\C-h", false) + input_keys("\C-h") input_keys('b') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_ba', '') input_keys('Z') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('Foo_baz', '') end @@ -1020,9 +1031,9 @@ def test_completion_in_middle_of_line } input_keys('abcde fo ABCDE') assert_line_around_cursor('abcde fo ABCDE', '') - input_keys("\C-b" * 6 + "\C-i", false) + input_keys("\C-b" * 6 + "\C-i") assert_line_around_cursor('abcde foo_', ' ABCDE') - input_keys("\C-b" * 2 + "\C-i", false) + input_keys("\C-b" * 2 + "\C-i") assert_line_around_cursor('abcde foo_', 'o_ ABCDE') end @@ -1041,42 +1052,42 @@ def test_completion_with_nil_value input_keys('fo') assert_line_around_cursor('fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar Foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) input_keys('a') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_a', '') - input_keys("\C-h", false) + input_keys("\C-h") input_keys('b') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('foo_ba', '') end def test_em_kill_region input_keys('abc def{bbb}ccc ddd ') assert_line_around_cursor('abc def{bbb}ccc ddd ', '') - input_keys("\C-w", false) + input_keys("\C-w") assert_line_around_cursor('abc def{bbb}ccc ', '') - input_keys("\C-w", false) + input_keys("\C-w") assert_line_around_cursor('abc ', '') - input_keys("\C-w", false) + input_keys("\C-w") assert_line_around_cursor('', '') - input_keys("\C-w", false) + input_keys("\C-w") assert_line_around_cursor('', '') end def test_em_kill_region_mbchar input_keys('あ い う{う}う ') assert_line_around_cursor('あ い う{う}う ', '') - input_keys("\C-w", false) + input_keys("\C-w") assert_line_around_cursor('あ い ', '') - input_keys("\C-w", false) + input_keys("\C-w") assert_line_around_cursor('あ ', '') - input_keys("\C-w", false) + input_keys("\C-w") assert_line_around_cursor('', '') end @@ -1232,13 +1243,13 @@ def test_search_history_with_isearch_terminator def test_em_set_mark_and_em_exchange_mark input_keys('aaa bbb ccc ddd') assert_line_around_cursor('aaa bbb ccc ddd', '') - input_keys("\C-a\M-F\M-F", false) + input_keys("\C-a\eF\eF") assert_line_around_cursor('aaa bbb', ' ccc ddd') assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer)) - input_keys("\x00", false) # C-Space + input_keys("\x00") # C-Space assert_line_around_cursor('aaa bbb', ' ccc ddd') assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer)) - input_keys("\C-a", false) + input_keys("\C-a") assert_line_around_cursor('', 'aaa bbb ccc ddd') assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer)) input_key_by_symbol(:em_exchange_mark) @@ -1249,7 +1260,7 @@ def test_em_set_mark_and_em_exchange_mark def test_em_exchange_mark_without_mark input_keys('aaa bbb ccc ddd') assert_line_around_cursor('aaa bbb ccc ddd', '') - input_keys("\C-a\M-f", false) + input_keys("\C-a\ef") assert_line_around_cursor('aaa', ' bbb ccc ddd') assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer)) input_key_by_symbol(:em_exchange_mark) @@ -1405,9 +1416,25 @@ def test_incremental_search_history_saves_and_restores_last_input assert_line_around_cursor('abcd', '') end + def test_beginning_of_history + Reline::HISTORY.concat(['abc', '123']) + # \M-<: move history to beginning + input_key_by_symbol(:beginning_of_history) + assert_line_around_cursor('abc', '') + end + + def test_end_of_history + Reline::HISTORY.concat(['abc', '123']) + input_keys("def\C-p\C-pd") + assert_line_around_cursor('abcd', '') + # \M->: move history to end + input_key_by_symbol(:end_of_history) + assert_line_around_cursor('def', '') + end + # Unicode emoji test def test_ed_insert_for_include_zwj_emoji - omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 + omit_unless_utf8 # U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466 is family: man, woman, girl, boy "👨‍👩‍👧‍👦" input_keys("\u{1F468}") # U+1F468 is man "👨" assert_line_around_cursor('👨', '') @@ -1429,7 +1456,7 @@ def test_ed_insert_for_include_zwj_emoji end def test_ed_insert_for_include_valiation_selector - omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 + omit_unless_utf8 # U+0030 U+FE00 is DIGIT ZERO + VARIATION SELECTOR-1 "0︀" input_keys("\u0030") # U+0030 is DIGIT ZERO assert_line_around_cursor('0', '') @@ -1438,20 +1465,20 @@ def test_ed_insert_for_include_valiation_selector end def test_em_yank_pop - input_keys("def hoge\C-w\C-b\C-f\C-w", false) + input_keys("def hoge\C-w\C-b\C-f\C-w") assert_line_around_cursor('', '') - input_keys("\C-y", false) + input_keys("\C-y") assert_line_around_cursor('def ', '') - input_keys("\M-\C-y", false) + input_keys("\e\C-y") assert_line_around_cursor('hoge', '') end def test_em_kill_region_with_kill_ring - input_keys("def hoge\C-b\C-b\C-b\C-b", false) + input_keys("def hoge\C-b\C-b\C-b\C-b") assert_line_around_cursor('def ', 'hoge') - input_keys("\C-k\C-w", false) + input_keys("\C-k\C-w") assert_line_around_cursor('', '') - input_keys("\C-y", false) + input_keys("\C-y") assert_line_around_cursor('def hoge', '') end @@ -1497,34 +1524,45 @@ def test_ignore_NUL_by_ed_quoted_insert def test_ed_argument_digit_by_meta_num input_keys('abcdef') assert_line_around_cursor('abcdef', '') - input_keys("\M-2", false) - input_keys("\C-h", false) + input_keys("\e2") + input_keys("\C-h") assert_line_around_cursor('abcd', '') end + def test_ed_digit_with_ed_argument_digit + input_keys('1' * 30) + assert_line_around_cursor('1' * 30, '') + input_keys("\e2") + input_keys('3') + input_keys("\C-h") + input_keys('4') + assert_line_around_cursor('1' * 7 + '4', '') + end + def test_halfwidth_kana_width_dakuten - omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 - input_raw_keys('ガギゲゴ') + omit_unless_utf8 + input_keys('ガギゲゴ') assert_line_around_cursor('ガギゲゴ', '') - input_keys("\C-b\C-b", false) + input_keys("\C-b\C-b") assert_line_around_cursor('ガギ', 'ゲゴ') - input_raw_keys('グ', false) + input_keys('グ') assert_line_around_cursor('ガギグ', 'ゲゴ') end def test_input_unknown_char + omit_unless_utf8 input_keys('͸') # U+0378 (unassigned) assert_line_around_cursor('͸', '') end def test_unix_line_discard - input_keys("\C-u", false) + input_keys("\C-u") assert_line_around_cursor('', '') input_keys('abc') assert_line_around_cursor('abc', '') - input_keys("\C-b\C-u", false) + input_keys("\C-b\C-u") assert_line_around_cursor('', 'c') - input_keys("\C-f\C-u", false) + input_keys("\C-f\C-u") assert_line_around_cursor('', '') end @@ -1534,59 +1572,59 @@ def test_vi_editing_mode end def test_undo - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('', '') - input_keys("aあb\C-h\C-h\C-h".encode(@encoding), false) + input_keys("aあb\C-h\C-h\C-h") assert_line_around_cursor('', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('a', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('aあ', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('aあb', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('aあ', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('a', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('', '') end def test_undo_with_cursor_position - input_keys("abc\C-b\C-h", false) + input_keys("abc\C-b\C-h") assert_line_around_cursor('a', 'c') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('ab', 'c') - input_keys("あいう\C-b\C-h".encode(@encoding), false) + input_keys("あいう\C-b\C-h") assert_line_around_cursor('abあ', 'うc') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('abあい', 'うc') end def test_undo_with_multiline @line_editor.multiline_on @line_editor.confirm_multiline_termination_proc = proc {} - input_keys("1\n2\n3", false) + input_keys("1\n2\n3") assert_whole_lines(["1", "2", "3"]) assert_line_index(2) assert_line_around_cursor('3', '') - input_keys("\C-p\C-h\C-h", false) + input_keys("\C-p\C-h\C-h") assert_whole_lines(["1", "3"]) assert_line_index(0) assert_line_around_cursor('1', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines(["1", "", "3"]) assert_line_index(1) assert_line_around_cursor('', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines(["1", "2", "3"]) assert_line_index(1) assert_line_around_cursor('2', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines(["1", "2", ""]) assert_line_index(2) assert_line_around_cursor('', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines(["1", "2"]) assert_line_index(1) assert_line_around_cursor('2', '') @@ -1594,99 +1632,99 @@ def test_undo_with_multiline def test_undo_with_many_times str = "a" + "b" * 99 - input_keys(str, false) - 100.times { input_keys("\C-_", false) } + input_keys(str) + 100.times { input_keys("\C-_") } assert_line_around_cursor('a', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('a', '') end def test_redo - input_keys("aあb".encode(@encoding), false) + input_keys("aあb") assert_line_around_cursor('aあb', '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_line_around_cursor('aあb', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('aあ', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('a', '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_line_around_cursor('aあ', '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_line_around_cursor('aあb', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('aあ', '') - input_keys("c", false) + input_keys("c") assert_line_around_cursor('aあc', '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_line_around_cursor('aあc', '') end def test_redo_with_cursor_position - input_keys("abc\C-b\C-h", false) + input_keys("abc\C-b\C-h") assert_line_around_cursor('a', 'c') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_line_around_cursor('a', 'c') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('ab', 'c') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_line_around_cursor('a', 'c') end def test_redo_with_multiline @line_editor.multiline_on @line_editor.confirm_multiline_termination_proc = proc {} - input_keys("1\n2\n3", false) + input_keys("1\n2\n3") assert_whole_lines(["1", "2", "3"]) assert_line_index(2) assert_line_around_cursor('3', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines(["1", "2", ""]) assert_line_index(2) assert_line_around_cursor('', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines(["1", "2"]) assert_line_index(1) assert_line_around_cursor('2', '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_whole_lines(["1", "2", ""]) assert_line_index(2) assert_line_around_cursor('', '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_whole_lines(["1", "2", "3"]) assert_line_index(2) assert_line_around_cursor('3', '') - input_keys("\C-p\C-h\C-h", false) + input_keys("\C-p\C-h\C-h") assert_whole_lines(["1", "3"]) assert_line_index(0) assert_line_around_cursor('1', '') - input_keys("\C-n", false) + input_keys("\C-n") assert_whole_lines(["1", "3"]) assert_line_index(1) assert_line_around_cursor('3', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines(["1", "", "3"]) assert_line_index(1) assert_line_around_cursor('', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines(["1", "2", "3"]) assert_line_index(1) assert_line_around_cursor('2', '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_whole_lines(["1", "", "3"]) assert_line_index(1) assert_line_around_cursor('', '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_whole_lines(["1", "3"]) assert_line_index(1) assert_line_around_cursor('3', '') @@ -1695,27 +1733,27 @@ def test_redo_with_multiline def test_undo_redo_restores_indentation @line_editor.multiline_on @line_editor.confirm_multiline_termination_proc = proc {} - input_keys(" 1", false) + input_keys(" 1") assert_whole_lines([' 1']) - input_keys("2", false) + input_keys("2") assert_whole_lines([' 12']) @line_editor.auto_indent_proc = proc { 2 } - input_keys("\C-_", false) + input_keys("\C-_") assert_whole_lines([' 1']) - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_whole_lines([' 12']) end def test_redo_with_many_times str = "a" + "b" * 98 + "c" - input_keys(str, false) - 100.times { input_keys("\C-_", false) } + input_keys(str) + 100.times { input_keys("\C-_") } assert_line_around_cursor('a', '') - input_keys("\C-_", false) + input_keys("\C-_") assert_line_around_cursor('a', '') - 100.times { input_keys("\M-\C-_", false) } + 100.times { input_keys("\e\C-_") } assert_line_around_cursor(str, '') - input_keys("\M-\C-_", false) + input_keys("\e\C-_") assert_line_around_cursor(str, '') end end diff --git a/test/mri/reline/test_key_actor_vi.rb b/test/mri/reline/test_key_actor_vi.rb index 928adace2b4..083433f9a80 100644 --- a/test/mri/reline/test_key_actor_vi.rb +++ b/test/mri/reline/test_key_actor_vi.rb @@ -103,11 +103,13 @@ def test_ed_insert_mbchar_two end def test_ed_insert_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099") assert_line_around_cursor("か\u3099", '') end def test_ed_insert_for_plural_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099") assert_line_around_cursor("か\u3099き\u3099", '') end @@ -208,6 +210,7 @@ def test_vi_paste_next_for_mbchar end def test_vi_paste_prev_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h") assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") input_keys('P') @@ -221,6 +224,7 @@ def test_vi_paste_prev_for_mbchar_by_plural_code_points end def test_vi_paste_next_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h") assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") input_keys('p') @@ -438,6 +442,7 @@ def test_vi_delete_next_char_for_mbchar end def test_vi_delete_next_char_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099く\u3099\C-[h") assert_line_around_cursor("か\u3099", "き\u3099く\u3099") input_keys('x') @@ -465,6 +470,7 @@ def test_vi_delete_prev_char_for_mbchar end def test_vi_delete_prev_char_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("か\u3099き\u3099") assert_line_around_cursor("か\u3099き\u3099", '') input_keys("\C-h") @@ -509,6 +515,7 @@ def test_ed_delete_prev_word_for_mbchar end def test_ed_delete_prev_word_for_mbchar_by_plural_code_points + omit_unless_utf8 input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '') input_keys("\C-w") @@ -659,9 +666,9 @@ def test_autocompletion_with_upward_navigation } input_keys('Re') assert_line_around_cursor('Re', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('Readline', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('Regexp', '') input_key_by_symbol(:completion_journey_up) assert_line_around_cursor('Readline', '') @@ -682,9 +689,9 @@ def test_autocompletion_with_upward_navigation_and_menu_complete_backward } input_keys('Re') assert_line_around_cursor('Re', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('Readline', '') - input_keys("\C-i", false) + input_keys("\C-i") assert_line_around_cursor('Regexp', '') input_key_by_symbol(:menu_complete_backward) assert_line_around_cursor('Readline', '') @@ -924,16 +931,16 @@ def test_ed_delete_next_char_at_eol end def test_vi_kill_line_prev - input_keys("\C-u", false) + input_keys("\C-u") assert_line_around_cursor('', '') input_keys('abc') assert_line_around_cursor('abc', '') - input_keys("\C-u", false) + input_keys("\C-u") assert_line_around_cursor('', '') input_keys('abc') - input_keys("\C-[\C-u", false) + input_keys("\C-[\C-u") assert_line_around_cursor('', 'c') - input_keys("\C-u", false) + input_keys("\C-u") assert_line_around_cursor('', 'c') end diff --git a/test/mri/reline/test_reline.rb b/test/mri/reline/test_reline.rb index 0a4f38986b0..aa0fd7d29ad 100644 --- a/test/mri/reline/test_reline.rb +++ b/test/mri/reline/test_reline.rb @@ -382,18 +382,6 @@ def test_dumb_terminal assert_match(/# /, out) - end - def test_read_eof_returns_input pend if win? lib = File.expand_path("../../lib", __dir__) @@ -436,24 +424,25 @@ def win? /mswin|mingw/.match?(RUBY_PLATFORM) end - def test_tty_amibuous_width + def test_tty_ambiguous_width omit unless defined?(PTY) ruby_file = Tempfile.create('rubyfile') ruby_file.write(<<~RUBY) require 'reline' Thread.new { sleep 2; puts 'timeout'; exit } - p [Reline.ambiguous_width, gets.chomp] + line = Reline.readline('>') + p [Reline.ambiguous_width, line] RUBY ruby_file.close lib = File.expand_path('../../lib', __dir__) - cmd = [{ 'TERM' => 'xterm' }, 'ruby', '-I', lib, ruby_file.to_path] + cmd = [{ 'TERM' => 'xterm' }, Reline.test_rubybin, '-I', lib, ruby_file.to_path] # Calculate ambiguous width from cursor position [1, 2].each do |ambiguous_width| PTY.spawn(*cmd) do |r, w, pid| loop { break if r.readpartial(1024).include?("\e[6n") } - w.puts "hello\e[10;#{ambiguous_width + 1}Rworld" - assert_include(r.gets, [ambiguous_width, 'helloworld'].inspect) + w.puts "hello\e[10;#{ambiguous_width + 1}Rworld\n" + assert_include(r.gets + r.gets, [ambiguous_width, 'helloworld'].inspect) ensure r.close w.close @@ -464,8 +453,8 @@ def test_tty_amibuous_width # Ambiguous width = 1 when cursor pos timed out PTY.spawn(*cmd) do |r, w, pid| loop { break if r.readpartial(1024).include?("\e[6n") } - w.puts "hello\e[10;2Sworld" - assert_include(r.gets, [1, "hello\e[10;2Sworld"].inspect) + w.puts "helloworld\n" + assert_include(r.gets + r.gets, [1, "helloworld"].inspect) ensure r.close w.close diff --git a/test/mri/reline/test_unicode.rb b/test/mri/reline/test_unicode.rb index 0778306c323..03e3be7d731 100644 --- a/test/mri/reline/test_unicode.rb +++ b/test/mri/reline/test_unicode.rb @@ -19,7 +19,7 @@ def test_ambiguous_width end def test_csi_regexp - csi_sequences = ["\e[m", "\e[1m", "\e[12;34m", "\e[12;34H"] + csi_sequences = ["\e[m", "\e[1m", "\e[12;34m", "\e[12;34H", "\e[5 q", "\e[?2004h"] assert_equal(csi_sequences, "text#{csi_sequences.join('text')}text".scan(Reline::Unicode::CSI_REGEXP)) end @@ -107,6 +107,21 @@ def test_take_mbchar_range assert_equal ["\e[41m \e[42mい\e[43m ", 1, 4], Reline::Unicode.take_mbchar_range("\e[41mあ\e[42mい\e[43mう", 1, 4, padding: true) end + def test_three_width_characters_take_mbchar_range + halfwidth_dakuten = 0xFF9E.chr('utf-8') + a = 'あ' + halfwidth_dakuten + b = 'い' + halfwidth_dakuten + c = 'う' + halfwidth_dakuten + line = 'x' + a + b + c + 'x' + assert_equal [' ' + b + ' ', 2, 6], Reline::Unicode.take_mbchar_range(line, 2, 6, padding: true) + assert_equal [' ' + b + ' ', 3, 6], Reline::Unicode.take_mbchar_range(line, 3, 6, padding: true) + assert_equal [b + c, 4, 6], Reline::Unicode.take_mbchar_range(line, 4, 6, padding: true) + assert_equal [a + b, 1, 6], Reline::Unicode.take_mbchar_range(line, 2, 6, cover_begin: true) + assert_equal [a + b, 1, 6], Reline::Unicode.take_mbchar_range(line, 3, 6, cover_begin: true) + assert_equal [b + c, 4, 6], Reline::Unicode.take_mbchar_range(line, 2, 6, cover_end: true) + assert_equal [b + c, 4, 6], Reline::Unicode.take_mbchar_range(line, 3, 6, cover_end: true) + end + def test_common_prefix assert_equal('', Reline::Unicode.common_prefix([])) assert_equal('abc', Reline::Unicode.common_prefix(['abc'])) @@ -283,4 +298,45 @@ def test_character_type refute(Reline::Unicode.space_character?('-')) refute(Reline::Unicode.space_character?(nil)) end + + def test_halfwidth_dakuten_handakuten_combinations + assert_equal 1, Reline::Unicode.get_mbchar_width("\uFF9E") + assert_equal 1, Reline::Unicode.get_mbchar_width("\uFF9F") + assert_equal 2, Reline::Unicode.get_mbchar_width("ガ") + assert_equal 2, Reline::Unicode.get_mbchar_width("パ") + assert_equal 2, Reline::Unicode.get_mbchar_width("ザ") + assert_equal 2, Reline::Unicode.get_mbchar_width("a゙") + assert_equal 2, Reline::Unicode.get_mbchar_width("1゚") + assert_equal 3, Reline::Unicode.get_mbchar_width("あ゙") + assert_equal 3, Reline::Unicode.get_mbchar_width("紅゙") + end + + def test_grapheme_cluster_width + # GB6, GB7, GB8: Hangul syllable + assert_equal 2, Reline::Unicode.get_mbchar_width('한'.unicode_normalize(:nfd)) + assert_equal 6, Reline::Unicode.get_mbchar_width('ᄀ' * 3) + + # GB9 + # Char + NonspacingMark + assert_equal 1, Reline::Unicode.get_mbchar_width('ç'.unicode_normalize(:nfd)) + assert_equal 2, Reline::Unicode.get_mbchar_width('ぱ'.unicode_normalize(:nfd)) + assert_equal 1, Reline::Unicode.get_mbchar_width("c\u{301}\u{327}") + # '1' + NonspacingMark + EnclosingMark + assert_equal 1, Reline::Unicode.get_mbchar_width('1️⃣') + # Char + SpacingMark + assert_equal 2, Reline::Unicode.get_mbchar_width('কা') + assert_equal 5, Reline::Unicode.get_mbchar_width('ガ゚゙゙') + # Emoji joined with ZeroWidthJoiner + assert_equal 2, Reline::Unicode.get_mbchar_width('👨‍👩‍👧') + assert_equal 7, Reline::Unicode.get_mbchar_width('👨‍👩‍👧゙゚゚゚゙') + + # GB9a: Char + GraphemeClusterBreak=SpacingMark + assert_equal 2, Reline::Unicode.get_mbchar_width('คำ') + + # GB9c: Consonant + Linker(NonspacingMark) + Consonant + assert_equal 2, Reline::Unicode.get_mbchar_width('क्त') + + # GB12, GB13: RegionalIndicator + assert_equal 2, Reline::Unicode.get_mbchar_width('🇯🇵') + end end diff --git a/test/mri/reline/test_within_pipe.rb b/test/mri/reline/test_within_pipe.rb index 4f05255301b..e5d0c12b9c9 100644 --- a/test/mri/reline/test_within_pipe.rb +++ b/test/mri/reline/test_within_pipe.rb @@ -42,9 +42,9 @@ def test_macro_commands_for_moving @config.add_default_key_binding("\C-x\C-e".bytes, :end_of_line) @config.add_default_key_binding("\C-x\C-f".bytes, :forward_char) @config.add_default_key_binding("\C-x\C-b".bytes, :backward_char) - @config.add_default_key_binding("\C-x\M-f".bytes, :forward_word) - @config.add_default_key_binding("\C-x\M-b".bytes, :backward_word) - @writer.write(" def\C-x\C-aabc\C-x\C-e ghi\C-x\C-a\C-x\C-f\C-x\C-f_\C-x\C-b\C-x\C-b_\C-x\C-f\C-x\C-f\C-x\C-f\C-x\M-f_\C-x\M-b\n") + @config.add_default_key_binding("\C-x\ef".bytes, :forward_word) + @config.add_default_key_binding("\C-x\eb".bytes, :backward_word) + @writer.write(" def\C-x\C-aabc\C-x\C-e ghi\C-x\C-a\C-x\C-f\C-x\C-f_\C-x\C-b\C-x\C-b_\C-x\C-f\C-x\C-f\C-x\C-f\C-x\ef_\C-x\eb\n") assert_equal 'a_b_c def_ ghi', Reline.readmultiline(&proc{ true }) end @@ -54,11 +54,11 @@ def test_macro_commands_for_editing @config.add_default_key_binding("\C-x\C-v".bytes, :quoted_insert) #@config.add_default_key_binding("\C-xa".bytes, :self_insert) @config.add_default_key_binding("\C-x\C-t".bytes, :transpose_chars) - @config.add_default_key_binding("\C-x\M-t".bytes, :transpose_words) - @config.add_default_key_binding("\C-x\M-u".bytes, :upcase_word) - @config.add_default_key_binding("\C-x\M-l".bytes, :downcase_word) - @config.add_default_key_binding("\C-x\M-c".bytes, :capitalize_word) - @writer.write("abcde\C-b\C-b\C-b\C-x\C-d\C-x\C-h\C-x\C-v\C-a\C-f\C-f EF\C-x\C-t gh\C-x\M-t\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-x\M-u\C-x\M-l\C-x\M-c\n") + @config.add_default_key_binding("\C-x\et".bytes, :transpose_words) + @config.add_default_key_binding("\C-x\eu".bytes, :upcase_word) + @config.add_default_key_binding("\C-x\el".bytes, :downcase_word) + @config.add_default_key_binding("\C-x\ec".bytes, :capitalize_word) + @writer.write("abcde\C-b\C-b\C-b\C-x\C-d\C-x\C-h\C-x\C-v\C-a\C-f\C-f EF\C-x\C-t gh\C-x\et\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-b\C-x\eu\C-x\el\C-x\ec\n") assert_equal "a\C-aDE gh Fe", Reline.readmultiline(&proc{ true }) end diff --git a/test/mri/reline/yamatanooroti/multiline_repl b/test/mri/reline/yamatanooroti/multiline_repl index 8b82be60f4b..4930f2e9d86 100755 --- a/test/mri/reline/yamatanooroti/multiline_repl +++ b/test/mri/reline/yamatanooroti/multiline_repl @@ -1,8 +1,6 @@ #!/usr/bin/env ruby - -require 'bundler' -Bundler.require +require 'bundler/setup' require 'reline' require 'optparse' diff --git a/test/mri/reline/yamatanooroti/test_rendering.rb b/test/mri/reline/yamatanooroti/test_rendering.rb index e9f9ee66d2a..008ff4a5e22 100644 --- a/test/mri/reline/yamatanooroti/test_rendering.rb +++ b/test/mri/reline/yamatanooroti/test_rendering.rb @@ -52,6 +52,14 @@ def teardown ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT'] end + # Yamatanooroti::TestCase#write converts 0x80-0xff bytes to "\e"+(byte&0x7f).chr + # This method avoids the conversion and writes the string as is. + def write_without_meta_conversion(str) + sync + @pty_input.write(str) + try_sync + end + def test_history_back start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') write(":a\n") @@ -550,9 +558,38 @@ def test_bracketed_paste prompt> 3 prompt> end EOC + write("\e[200~.tap do\r\t4\r\t5\rend\e[201~") + assert_screen(<<~EOC) + prompt> 3 + prompt> end.tap do + prompt> 4 + prompt> 5 + prompt> end + EOC close end + def test_ctrl_v_escaped_input + omit if Reline.core.io_gate.win? + + start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') + + # When a configuration "Escape non-ASCII Input with Control-V" is enabled + # in macOS Termina.app, all non-ascii characters are escaped with ^V. + write_without_meta_conversion "\C-v\xE3\C-v\x81\C-v\x82" + assert_screen(/prompt> あ/) + + # Bracketed pasted content is also escaped + write_without_meta_conversion "\e[200~\C-a\C-v\xE3\C-v\x81\C-v\x84\C-b\e[201~" + assert_screen(/prompt> あ\^Aい\^B/) + + # Invalid bytes should be ignored with timeout + write_without_meta_conversion "\C-v\x82\C-v\xA4" + sleep 1 + write 'C' + assert_screen(/prompt> あ\^Aい\^BC/) + end + def test_bracketed_paste_with_undo_redo omit if Reline.core.io_gate.win? start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') @@ -563,7 +600,7 @@ def test_bracketed_paste_with_undo_redo Multiline REPL. prompt> abc EOC - write("\M-\C-_") + write("\e\C-_") assert_screen(<<~EOC) Multiline REPL. prompt> abcdef hoge @@ -805,13 +842,30 @@ def test_autowrap_in_the_middle_of_a_line close end - def test_terminate_in_the_middle_of_lines + def test_newline_in_the_middle_of_lines start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') write("def hoge\n 1\n 2\n 3\n 4\nend\n") write("\C-p\C-p\C-p\C-e\n") assert_screen(<<~EOC) + prompt> def hoge + prompt> 1 + prompt> 2 prompt> 3 - prompt> 4 + prompt> + EOC + close + end + + def test_ed_force_submit_in_the_middle_of_lines + write_inputrc <<~LINES + "\\C-a": ed_force_submit + LINES + start_terminal(5, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') + write("def hoge\nend") + write("\C-p\C-a") + assert_screen(<<~EOC) + Multiline REPL. + prompt> def hoge prompt> end => :hoge prompt> @@ -845,7 +899,7 @@ def test_reset_rest_height_when_clear_screen def test_meta_key start_terminal(30, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("def ge\M-bho") + write("def ge\ebho") assert_screen(<<~EOC) Multiline REPL. prompt> def hoge @@ -866,7 +920,7 @@ def test_not_meta_key def test_force_enter start_terminal(30, 120, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') write("def hoge\nend\C-p\C-e") - write("\M-\x0D") + write("\e\x0D") assert_screen(<<~EOC) Multiline REPL. prompt> def hoge @@ -911,7 +965,7 @@ def test_eof_without_newline def test_em_set_mark_and_em_exchange_mark start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') - write("aaa bbb ccc ddd\M-b\M-b\M-\x20\M-b\C-x\C-xX\C-x\C-xY") + write("aaa bbb ccc ddd\eb\eb\e\x20\eb\C-x\C-xX\C-x\C-xY") assert_screen(<<~'EOC') Multiline REPL. prompt> aaa Ybbb Xccc ddd @@ -1511,7 +1565,7 @@ def test_dialog_with_fullwidth_scrollbar def test_rerender_argument_prompt_after_pasting start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') write('abcdef') - write("\M-3\C-h") + write("\e3\C-h") assert_screen(<<~'EOC') Multiline REPL. prompt> abc @@ -1647,7 +1701,7 @@ def test_insert_newline_in_the_middle_of_buffer_just_after_dialog write("class A\n def a\n 3\n end\nend") write("\n") write("\C-p\C-p\C-p\C-p\C-p\C-e\C-hS") - write("\M-\x0D") + write("\e\x0D") write(" 3") assert_screen(<<~'EOC') prompt> 3 @@ -1724,6 +1778,41 @@ def test_exit_with_ctrl_d close end + def test_quoted_insert_intr_keys + omit if Reline.core.io_gate.win? + start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') + write '"' + write "\C-v" + write "\C-c" + write "\C-v" + write "\C-z" + write "\C-v" + write "\C-\\" + write "\".bytes\n" + assert_screen(<<~EOC) + Multiline REPL. + prompt> "^C^Z^\\\".bytes + => [3, 26, 28] + prompt> + EOC + close + end + + def test_quoted_insert_timeout + omit if Reline.core.io_gate.win? + + start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') + write "\C-v" + write "\C-a" + write "\C-v" + # broken bytes should be ignored with timeout + write_without_meta_conversion "\xE3\xE4\xE5" + sleep 1 + write "\C-v" + write "\C-b" + assert_screen(/prompt> \^A\^B/) + end + def test_print_before_readline code = <<~RUBY puts 'Multiline REPL.' From abd6cec6a59e6b165a95985505e3520da0581da2 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 14 Jan 2026 23:01:48 -0600 Subject: [PATCH 070/168] Fix Data subclasses with ivars Data subclasses were set up to copy their parent class's var table manager and accessors, but did not overwrite those copies' realClass to the new class. This resulted in future variable accesses potentially walking back to the parent class, seeing an incorrect var table size, and allocating the wrong size table for the spilled instance variables. The new logic does that copying with the new realClass in hand and properly replaces it in the copied accesses and manager. This fixes all remaining issues marshaling Data with Psych and may fix other issues with Data subclasses that are not currently showing up in tests. --- core/src/main/java/org/jruby/RubyClass.java | 8 ++++---- .../runtime/invokedynamic/VariableSite.java | 8 ++++---- .../runtime/ivars/FieldVariableAccessor.java | 8 ++++++-- .../runtime/ivars/RawFieldVariableAccessor.java | 9 ++++++++- .../runtime/ivars/StampedVariableAccessor.java | 4 ++++ .../jruby/runtime/ivars/VariableAccessor.java | 4 ++++ .../runtime/ivars/VariableTableManager.java | 16 +++++++++------- test/mri/excludes/TestData.rb | 1 - 8 files changed, 39 insertions(+), 19 deletions(-) delete mode 100644 test/mri/excludes/TestData.rb diff --git a/core/src/main/java/org/jruby/RubyClass.java b/core/src/main/java/org/jruby/RubyClass.java index e099445586e..f0b140e69b4 100644 --- a/core/src/main/java/org/jruby/RubyClass.java +++ b/core/src/main/java/org/jruby/RubyClass.java @@ -550,7 +550,7 @@ public static RubyClass newClass(ThreadContext context, RubyClass superClass, St baseName(name); clazz.makeMetaClass(context, superClass.getMetaClass()); if (setParent) clazz.setParent(parent); - clazz.copyVariableTableManager(context, superClass); + clazz.copyVariableTableManager(context, superClass, clazz); parent.setConstant(context, name, clazz, file, line); superClass.invokeInherited(context, superClass, clazz); return clazz; @@ -1073,7 +1073,7 @@ private RubyClass initializeCommon(ThreadContext context, RubyClass superClazz, allocator = superClazz.allocator; makeMetaClass(context, superClazz.getMetaClass()); superClazz.addSubclass(this); - copyVariableTableManager(context, superClazz); + copyVariableTableManager(context, superClazz, this); marshal = superClazz.marshal; @@ -1083,11 +1083,11 @@ private RubyClass initializeCommon(ThreadContext context, RubyClass superClazz, return this; } - private void copyVariableTableManager(ThreadContext context, RubyClass superClazz) { + private void copyVariableTableManager(ThreadContext context, RubyClass realClass, RubyClass superClazz) { VariableTableManager variableTableManager = superClazz.getVariableTableManager(); if (variableTableManager.getRealClass().superClass() == context.runtime.getData()) { // duplicate data's variable table in subclasses - this.variableTableManager = variableTableManager.duplicate(); + this.variableTableManager = variableTableManager.duplicate(realClass); } } diff --git a/core/src/main/java/org/jruby/runtime/invokedynamic/VariableSite.java b/core/src/main/java/org/jruby/runtime/invokedynamic/VariableSite.java index 4d2b188b1d3..f71872d9fb8 100644 --- a/core/src/main/java/org/jruby/runtime/invokedynamic/VariableSite.java +++ b/core/src/main/java/org/jruby/runtime/invokedynamic/VariableSite.java @@ -145,10 +145,10 @@ public IRubyObject ivarGet(IRubyObject self) throws Throwable { MethodHandle test = findStatic(VariableSite.class, "testRealClass", methodType(boolean.class, int.class, IRubyObject.class)); test = insertArguments(test, 0, accessor.getClassId()); - getValue = guardWithTest(test, getValue, fallback); + MethodHandle guarded = guardWithTest(test, getValue, fallback); if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info(name() + "\tget on class " + self.getMetaClass().id + " bound directly" + extractSourceInfo()); - setTarget(getValue); + setTarget(guarded); return (IRubyObject)getValue.invokeExact(self); } @@ -209,10 +209,10 @@ public void ivarSet(IRubyObject self, IRubyObject value) throws Throwable { test = insertArguments(test, 0, accessor.getClassId()); test = dropArguments(test, 1, IRubyObject.class); - setValue = guardWithTest(test, setValue, fallback); + MethodHandle guarded = guardWithTest(test, setValue, fallback); if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info(name() + "\tset on class " + self.getMetaClass().id + " bound directly" + extractSourceInfo()); - setTarget(setValue); + setTarget(guarded); setValue.invokeExact(self, value); } diff --git a/core/src/main/java/org/jruby/runtime/ivars/FieldVariableAccessor.java b/core/src/main/java/org/jruby/runtime/ivars/FieldVariableAccessor.java index 4136b75f605..dbae1062472 100644 --- a/core/src/main/java/org/jruby/runtime/ivars/FieldVariableAccessor.java +++ b/core/src/main/java/org/jruby/runtime/ivars/FieldVariableAccessor.java @@ -43,8 +43,8 @@ * A variable accessor that accesses a field directly; */ public class FieldVariableAccessor extends VariableAccessor { - private final MethodHandle getter; - private final MethodHandle setter; + protected final MethodHandle getter; + protected final MethodHandle setter; /** * Construct a new FieldVariableAccessor for the given "real" class, @@ -64,6 +64,10 @@ public FieldVariableAccessor(RubyClass realClass, String name, int index, int cl this.setter = wrapSetter(setter); } + public FieldVariableAccessor cloneFor(RubyClass newRealClass) { + return new FieldVariableAccessor(newRealClass, name, index, classId, getter, setter); + } + protected MethodHandle wrapSetter(MethodHandle setter) { // mix frozen check into setter return MethodHandles.foldArguments(setter, ENSURE_SETTABLE.asType(setter.type())); diff --git a/core/src/main/java/org/jruby/runtime/ivars/RawFieldVariableAccessor.java b/core/src/main/java/org/jruby/runtime/ivars/RawFieldVariableAccessor.java index 8b62b110f8b..10f5419dd10 100644 --- a/core/src/main/java/org/jruby/runtime/ivars/RawFieldVariableAccessor.java +++ b/core/src/main/java/org/jruby/runtime/ivars/RawFieldVariableAccessor.java @@ -59,8 +59,15 @@ public class RawFieldVariableAccessor extends FieldVariableAccessor { * @param setter the setter handle for the field */ public RawFieldVariableAccessor(RubyClass realClass, boolean unwrapInSet, Class toJava, Class returnType, String name, int index, int classId, MethodHandle getter, MethodHandle setter) { - super(realClass, name, index, classId, wrapGetter(getter, realClass, returnType), wrapSetter(setter, realClass, unwrapInSet, toJava, returnType)); + this(realClass, name, index, classId, wrapGetter(getter, realClass, returnType), wrapSetter(setter, realClass, unwrapInSet, toJava, returnType)); + } + + private RawFieldVariableAccessor(RubyClass realClass, String name, int index, int classId, MethodHandle getter, MethodHandle setter) { + super(realClass, name, index, classId, getter, setter); + } + public RawFieldVariableAccessor cloneFor(RubyClass newRealClass) { + return new RawFieldVariableAccessor(newRealClass, name, index, classId, getter, setter); } @Override diff --git a/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java b/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java index 22519dcae70..2f17b2d34b9 100644 --- a/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java +++ b/core/src/main/java/org/jruby/runtime/ivars/StampedVariableAccessor.java @@ -51,6 +51,10 @@ public StampedVariableAccessor(RubyClass realClass, String name, int index, int super(realClass, name, index, classId); } + public StampedVariableAccessor cloneFor(RubyClass newRealClass) { + return new StampedVariableAccessor(newRealClass, name, index, classId); + } + /** * Set this variable into the given object using Unsafe to ensure * safe updating of the variable table. diff --git a/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java b/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java index 0f1c62efe4c..b458a3887a6 100644 --- a/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java +++ b/core/src/main/java/org/jruby/runtime/ivars/VariableAccessor.java @@ -192,6 +192,10 @@ public String toString() { return "ivar:" + getName() + ":" + index; } + public VariableAccessor cloneFor(RubyClass newRealClass) { + throw new UnsupportedOperationException(); + } + /** a dummy accessor that will always return null */ public static final VariableAccessor DUMMY_ACCESSOR = new VariableAccessor(null, null, -1, -1) {}; diff --git a/core/src/main/java/org/jruby/runtime/ivars/VariableTableManager.java b/core/src/main/java/org/jruby/runtime/ivars/VariableTableManager.java index df163d4fa56..0ac1176017c 100644 --- a/core/src/main/java/org/jruby/runtime/ivars/VariableTableManager.java +++ b/core/src/main/java/org/jruby/runtime/ivars/VariableTableManager.java @@ -111,10 +111,10 @@ public VariableTableManager(RubyClass realClass) { * * @param original VariableTableManager to copy */ - VariableTableManager(VariableTableManager original) { + VariableTableManager(VariableTableManager original, RubyClass realClass) { synchronized (original) { - this.realClass = original.realClass; - this.variableAccessors = copyVariableAccessors(original.variableAccessors); + this.realClass = realClass; + this.variableAccessors = copyVariableAccessors(realClass, original.variableAccessors); this.variableNames = original.variableNames.clone(); this.hasObjectID = original.hasObjectID; this.hasFFI = original.hasFFI; @@ -282,8 +282,10 @@ VariableAccessor getVariableAccessorWithBuilder(String name, Function copyVariableAccessors(Map myVariableAccessors) { - return new LinkedHashMap<>(myVariableAccessors); + private static Map copyVariableAccessors(RubyClass realClass, Map otherVariableAccessors) { + var myVariableAccessors = new LinkedHashMap(otherVariableAccessors.size()); + otherVariableAccessors.forEach((name, accessor) -> myVariableAccessors.put(name, accessor.cloneFor(realClass))); + return myVariableAccessors; } private static Map copyVariableAccessors(Map myVariableAccessors, String name, VariableAccessor ivarAccessor) { @@ -544,8 +546,8 @@ public Object clearVariable(RubyBasicObject object, String name) { } } - public VariableTableManager duplicate() { - return new VariableTableManager(this); + public VariableTableManager duplicate(RubyClass realClass) { + return new VariableTableManager(this, realClass); } /** diff --git a/test/mri/excludes/TestData.rb b/test/mri/excludes/TestData.rb deleted file mode 100644 index 36fbe2ee228..00000000000 --- a/test/mri/excludes/TestData.rb +++ /dev/null @@ -1 +0,0 @@ -exclude :test_inspect, "fails in JIT mode on Linux CI on GHA" From 1559cdaaa9ad3bb2fe0312bab4d20dd0a7a11884 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 22 Jan 2026 16:07:50 -0600 Subject: [PATCH 071/168] Update stdlib tests again from CRuby 4.0 All tests from CRuby 4.0 were copied over, but several older tests, tests for gems no longer bundled with Ruby, or unused fixtures still needed to be deleted. --- test/mri.stdlib.index | 9 - test/mri/benchmark/test_benchmark.rb | 167 - test/mri/cgi/test_cgi_cookie.rb | 211 - test/mri/cgi/test_cgi_core.rb | 307 - test/mri/cgi/test_cgi_header.rb | 192 - test/mri/cgi/test_cgi_modruby.rb | 149 - test/mri/cgi/test_cgi_multipart.rb | 385 -- test/mri/cgi/test_cgi_session.rb | 169 - test/mri/cgi/test_cgi_tag_helper.rb | 355 -- test/mri/cgi/test_cgi_util.rb | 312 -- test/mri/cgi/testdata/file1.html | 10 - test/mri/cgi/testdata/large.png | Bin 156414 -> 0 bytes test/mri/cgi/testdata/small.png | Bin 82 -> 0 bytes test/mri/fiddle/helper.rb | 202 - test/mri/fiddle/test_c_struct_builder.rb | 69 - test/mri/fiddle/test_c_struct_entry.rb | 171 - test/mri/fiddle/test_c_union_entity.rb | 36 - test/mri/fiddle/test_closure.rb | 173 - test/mri/fiddle/test_cparser.rb | 414 -- test/mri/fiddle/test_fiddle.rb | 91 - test/mri/fiddle/test_func.rb | 180 - test/mri/fiddle/test_function.rb | 310 -- test/mri/fiddle/test_handle.rb | 244 - test/mri/fiddle/test_import.rb | 499 -- test/mri/fiddle/test_memory_view.rb | 175 - test/mri/fiddle/test_pack.rb | 37 - test/mri/fiddle/test_pinned.rb | 34 - test/mri/fiddle/test_pointer.rb | 319 -- test/mri/irb/command/test_cd.rb | 84 - test/mri/irb/command/test_custom_command.rb | 194 - test/mri/irb/command/test_disable_irb.rb | 28 - test/mri/irb/command/test_force_exit.rb | 51 - test/mri/irb/command/test_help.rb | 75 - .../irb/command/test_multi_irb_commands.rb | 50 - test/mri/irb/command/test_show_source.rb | 410 -- test/mri/irb/helper.rb | 234 - test/mri/irb/test_color.rb | 274 - test/mri/irb/test_color_printer.rb | 69 - test/mri/irb/test_command.rb | 971 ---- test/mri/irb/test_completion.rb | 317 -- test/mri/irb/test_context.rb | 737 --- test/mri/irb/test_debugger_integration.rb | 513 -- test/mri/irb/test_eval_history.rb | 69 - test/mri/irb/test_evaluation.rb | 44 - test/mri/irb/test_helper_method.rb | 135 - test/mri/irb/test_history.rb | 532 -- test/mri/irb/test_init.rb | 388 -- test/mri/irb/test_input_method.rb | 195 - test/mri/irb/test_irb.rb | 936 ---- test/mri/irb/test_locale.rb | 118 - test/mri/irb/test_nesting_parser.rb | 339 -- test/mri/irb/test_option.rb | 13 - test/mri/irb/test_raise_exception.rb | 74 - test/mri/irb/test_ruby_lex.rb | 242 - test/mri/irb/test_tracer.rb | 90 - test/mri/irb/test_type_completor.rb | 109 - test/mri/irb/test_workspace.rb | 126 - test/mri/irb/yamatanooroti/test_rendering.rb | 478 -- test/mri/json/fixtures/fail4.json | 1 - test/mri/json/fixtures/fail9.json | 1 - test/mri/json/fixtures/pass15.json | 1 - test/mri/json/fixtures/pass16.json | 1 - test/mri/json/fixtures/pass17.json | 1 - test/mri/json/fixtures/pass26.json | 1 - test/mri/logger/test_formatter.rb | 35 - test/mri/logger/test_logdevice.rb | 859 --- test/mri/logger/test_logger.rb | 402 -- test/mri/logger/test_logperiod.rb | 67 - test/mri/logger/test_severity.rb | 94 - .../mri/openssl/fixtures/pkey/certificate.der | Bin 1325 -> 0 bytes test/mri/openssl/fixtures/pkey/dsa1024.pem | 12 - test/mri/openssl/fixtures/pkey/dsa256.pem | 8 - test/mri/openssl/fixtures/pkey/dsa512.pem | 8 - test/mri/openssl/fixtures/pkey/empty.der | 0 test/mri/openssl/fixtures/pkey/empty.pem | 0 test/mri/openssl/fixtures/pkey/fullchain.pem | 56 - test/mri/openssl/fixtures/pkey/garbage.txt | 1 - .../openssl/fixtures/pkey/p256_too_large.pem | 5 - .../openssl/fixtures/pkey/p384_invalid.pem | 6 - test/mri/openssl/fixtures/pkey/rsa1024.pem | 15 - test/mri/ostruct/test_ostruct.rb | 434 -- test/mri/prism/attribute_write_test.rb | 60 - test/mri/prism/command_line_test.rb | 69 - test/mri/prism/comments_test.rb | 138 - test/mri/prism/compiler_test.rb | 31 - test/mri/prism/constant_path_node_test.rb | 91 - test/mri/prism/desugar_compiler_test.rb | 80 - test/mri/prism/dispatcher_test.rb | 46 - test/mri/prism/encoding_test.rb | 572 -- .../errors/block_args_in_array_assignment.txt | 3 - .../dont_allow_return_inside_sclass_body.txt | 3 - .../errors/it_with_ordinary_parameter.txt | 3 - .../keyword_args_in_array_assignment.txt | 3 - .../mri/prism/fixtures/it_indirect_writes.txt | 23 - .../prism/fixtures/seattlerb/block_break.txt | 1 - .../prism/fixtures/seattlerb/block_next.txt | 1 - .../prism/fixtures/seattlerb/dasgn_icky2.txt | 8 - .../prism/fixtures/seattlerb/yield_arg.txt | 1 - .../fixtures/seattlerb/yield_call_assocs.txt | 11 - .../fixtures/seattlerb/yield_empty_parens.txt | 1 - .../unparser/corpus/literal/control.txt | 15 - .../unparser/corpus/literal/yield.txt | 3 - .../prism/fixtures/whitequark/args_assocs.txt | 11 - .../whitequark/args_assocs_legacy.txt | 11 - test/mri/prism/fixtures/whitequark/break.txt | 7 - .../prism/fixtures/whitequark/break_block.txt | 1 - .../class_definition_in_while_cond.txt | 7 - .../if_while_after_class__since_32.txt | 7 - test/mri/prism/fixtures/whitequark/next.txt | 7 - .../prism/fixtures/whitequark/next_block.txt | 1 - test/mri/prism/fixtures/whitequark/redo.txt | 1 - test/mri/prism/fixtures/whitequark/retry.txt | 1 - test/mri/prism/fixtures/whitequark/yield.txt | 7 - test/mri/prism/format_errors_test.rb | 24 - test/mri/prism/integer_parse_test.rb | 45 - test/mri/prism/location_test.rb | 945 ---- test/mri/prism/memsize_test.rb | 17 - test/mri/prism/parameters_signature_test.rb | 91 - test/mri/prism/parse_comments_test.rb | 21 - test/mri/prism/parse_stream_test.rb | 74 - test/mri/prism/parse_test.rb | 374 -- test/mri/prism/parser_test.rb | 185 - test/mri/prism/pattern_test.rb | 132 - test/mri/prism/ripper_test.rb | 85 - test/mri/prism/ruby_api_test.rb | 255 - test/mri/prism/ruby_parser_test.rb | 153 - test/mri/prism/snapshots/alias.txt | 194 - test/mri/prism/snapshots/arithmetic.txt | 255 - test/mri/prism/snapshots/arrays.txt | 2308 -------- test/mri/prism/snapshots/begin_ensure.txt | 251 - test/mri/prism/snapshots/begin_rescue.txt | 699 --- test/mri/prism/snapshots/blocks.txt | 774 --- .../mri/prism/snapshots/boolean_operators.txt | 54 - test/mri/prism/snapshots/booleans.txt | 7 - test/mri/prism/snapshots/break.txt | 239 - test/mri/prism/snapshots/case.txt | 495 -- test/mri/prism/snapshots/classes.txt | 365 -- .../prism/snapshots/command_method_call.txt | 755 --- test/mri/prism/snapshots/comments.txt | 145 - test/mri/prism/snapshots/constants.txt | 1242 ----- test/mri/prism/snapshots/dash_heredocs.txt | 256 - test/mri/prism/snapshots/defined.txt | 88 - test/mri/prism/snapshots/dos_endings.txt | 108 - test/mri/prism/snapshots/dstring.txt | 83 - test/mri/prism/snapshots/dsym_str.txt | 11 - .../snapshots/embdoc_no_newline_at_end.txt | 5 - .../prism/snapshots/emoji_method_calls.txt | 31 - test/mri/prism/snapshots/endless_methods.txt | 107 - .../endless_range_in_conditional.txt | 53 - test/mri/prism/snapshots/for.txt | 188 - test/mri/prism/snapshots/global_variables.txt | 191 - test/mri/prism/snapshots/hashes.txt | 384 -- test/mri/prism/snapshots/heredoc.txt | 11 - .../heredoc_with_carriage_returns.txt | 11 - .../prism/snapshots/heredoc_with_comment.txt | 21 - .../heredoc_with_escaped_newline_at_start.txt | 67 - .../heredoc_with_trailing_newline.txt | 11 - .../snapshots/heredocs_leading_whitespace.txt | 55 - test/mri/prism/snapshots/heredocs_nested.txt | 90 - .../heredocs_with_ignored_newlines.txt | 69 - ...cs_with_ignored_newlines_and_non_empty.txt | 11 - test/mri/prism/snapshots/if.txt | 462 -- .../mri/prism/snapshots/indented_file_end.txt | 18 - .../prism/snapshots/integer_operations.txt | 635 --- .../prism/snapshots/keyword_method_names.txt | 173 - test/mri/prism/snapshots/keywords.txt | 13 - test/mri/prism/snapshots/lambda.txt | 201 - test/mri/prism/snapshots/method_calls.txt | 2456 -------- test/mri/prism/snapshots/methods.txt | 2051 ------- test/mri/prism/snapshots/modules.txt | 183 - test/mri/prism/snapshots/multi_write.txt | 93 - .../prism/snapshots/newline_terminated.txt | 107 - test/mri/prism/snapshots/next.txt | 149 - test/mri/prism/snapshots/nils.txt | 34 - .../snapshots/non_alphanumeric_methods.txt | 503 -- test/mri/prism/snapshots/not.txt | 351 -- test/mri/prism/snapshots/numbers.txt | 142 - test/mri/prism/snapshots/patterns.txt | 4921 ----------------- test/mri/prism/snapshots/procs.txt | 403 -- .../snapshots/range_begin_open_exclusive.txt | 13 - .../snapshots/range_begin_open_inclusive.txt | 13 - test/mri/prism/snapshots/range_beginless.txt | 114 - .../snapshots/range_end_open_exclusive.txt | 13 - .../snapshots/range_end_open_inclusive.txt | 13 - test/mri/prism/snapshots/ranges.txt | 533 -- test/mri/prism/snapshots/regex.txt | 371 -- test/mri/prism/snapshots/regex_char_width.txt | 50 - .../mri/prism/snapshots/repeat_parameters.txt | 473 -- test/mri/prism/snapshots/rescue.txt | 491 -- test/mri/prism/snapshots/rescue_modifier.txt | 230 - test/mri/prism/snapshots/return.txt | 155 - test/mri/prism/snapshots/seattlerb/BEGIN.txt | 15 - .../seattlerb/TestRubyParserShared.txt | 361 -- .../snapshots/seattlerb/__ENCODING__.txt | 6 - .../seattlerb/alias_gvar_backref.txt | 13 - .../snapshots/seattlerb/alias_resword.txt | 21 - .../prism/snapshots/seattlerb/and_multi.txt | 26 - .../snapshots/seattlerb/aref_args_assocs.txt | 23 - .../seattlerb/aref_args_lit_assocs.txt | 26 - .../snapshots/seattlerb/args_kw_block.txt | 39 - .../snapshots/seattlerb/array_line_breaks.txt | 25 - .../seattlerb/array_lits_trailing_calls.txt | 35 - .../prism/snapshots/seattlerb/assoc__bare.txt | 31 - .../prism/snapshots/seattlerb/assoc_label.txt | 34 - .../seattlerb/attr_asgn_colon_id.txt | 23 - .../seattlerb/attrasgn_array_arg.txt | 42 - .../seattlerb/attrasgn_array_lhs.txt | 83 - .../attrasgn_primary_dot_constant.txt | 31 - .../backticks_interpolation_line.txt | 38 - .../mri/prism/snapshots/seattlerb/bang_eq.txt | 24 - test/mri/prism/snapshots/seattlerb/bdot2.txt | 38 - test/mri/prism/snapshots/seattlerb/bdot3.txt | 38 - .../seattlerb/begin_ensure_no_bodies.txt | 16 - .../begin_rescue_else_ensure_bodies.txt | 47 - .../begin_rescue_else_ensure_no_bodies.txt | 27 - .../begin_rescue_ensure_no_bodies.txt | 23 - .../snapshots/seattlerb/block_arg__bare.txt | 31 - .../snapshots/seattlerb/block_arg_kwsplat.txt | 39 - .../seattlerb/block_arg_opt_arg_block.txt | 54 - .../seattlerb/block_arg_opt_splat.txt | 51 - .../block_arg_opt_splat_arg_block_omfg.txt | 59 - .../seattlerb/block_arg_optional.txt | 43 - .../snapshots/seattlerb/block_arg_scope.txt | 40 - .../snapshots/seattlerb/block_arg_scope2.txt | 43 - .../seattlerb/block_arg_splat_arg.txt | 45 - .../snapshots/seattlerb/block_args_kwargs.txt | 44 - .../seattlerb/block_args_no_kwargs.txt | 37 - .../snapshots/seattlerb/block_args_opt1.txt | 59 - .../snapshots/seattlerb/block_args_opt2.txt | 52 - .../snapshots/seattlerb/block_args_opt2_2.txt | 71 - .../snapshots/seattlerb/block_args_opt3.txt | 79 - .../prism/snapshots/seattlerb/block_break.txt | 56 - .../block_call_defn_call_block_call.txt | 80 - .../block_call_dot_op2_brace_block.txt | 100 - .../block_call_dot_op2_cmd_args_do_block.txt | 113 - .../seattlerb/block_call_operation_colon.txt | 54 - .../seattlerb/block_call_operation_dot.txt | 54 - .../block_call_paren_call_block_call.txt | 60 - .../block_command_operation_colon.txt | 49 - .../seattlerb/block_command_operation_dot.txt | 49 - .../seattlerb/block_decomp_anon_splat_arg.txt | 46 - .../seattlerb/block_decomp_arg_splat.txt | 46 - .../seattlerb/block_decomp_arg_splat_arg.txt | 52 - .../seattlerb/block_decomp_splat.txt | 46 - .../prism/snapshots/seattlerb/block_kw.txt | 42 - .../seattlerb/block_kw__required.txt | 38 - .../snapshots/seattlerb/block_kwarg_lvar.txt | 50 - .../seattlerb/block_kwarg_lvar_multiple.txt | 61 - .../prism/snapshots/seattlerb/block_next.txt | 56 - .../snapshots/seattlerb/block_opt_arg.txt | 46 - .../snapshots/seattlerb/block_opt_splat.txt | 48 - .../block_opt_splat_arg_block_omfg.txt | 56 - .../snapshots/seattlerb/block_optarg.txt | 46 - .../snapshots/seattlerb/block_paren_splat.txt | 49 - .../snapshots/seattlerb/block_reg_optarg.txt | 49 - .../snapshots/seattlerb/block_return.txt | 56 - .../prism/snapshots/seattlerb/block_scope.txt | 29 - .../snapshots/seattlerb/block_splat_reg.txt | 42 - test/mri/prism/snapshots/seattlerb/bug169.txt | 28 - test/mri/prism/snapshots/seattlerb/bug179.txt | 28 - test/mri/prism/snapshots/seattlerb/bug190.txt | 11 - test/mri/prism/snapshots/seattlerb/bug191.txt | 87 - test/mri/prism/snapshots/seattlerb/bug202.txt | 22 - test/mri/prism/snapshots/seattlerb/bug236.txt | 70 - test/mri/prism/snapshots/seattlerb/bug290.txt | 24 - .../mri/prism/snapshots/seattlerb/bug_187.txt | 59 - .../mri/prism/snapshots/seattlerb/bug_215.txt | 14 - .../mri/prism/snapshots/seattlerb/bug_249.txt | 86 - .../mri/prism/snapshots/seattlerb/bug_and.txt | 21 - .../snapshots/seattlerb/bug_args__19.txt | 58 - .../snapshots/seattlerb/bug_args_masgn.txt | 49 - .../snapshots/seattlerb/bug_args_masgn2.txt | 58 - .../bug_args_masgn_outer_parens__19.txt | 55 - .../seattlerb/bug_call_arglist_parens.txt | 110 - .../seattlerb/bug_case_when_regexp.txt | 28 - .../prism/snapshots/seattlerb/bug_comma.txt | 41 - .../snapshots/seattlerb/bug_cond_pct.txt | 22 - .../snapshots/seattlerb/bug_hash_args.txt | 38 - .../bug_hash_args_trailing_comma.txt | 38 - .../seattlerb/bug_hash_interp_array.txt | 26 - .../snapshots/seattlerb/bug_masgn_right.txt | 49 - .../snapshots/seattlerb/bug_not_parens.txt | 25 - .../seattlerb/bug_op_asgn_rescue.txt | 26 - .../prism/snapshots/seattlerb/call_and.txt | 24 - .../snapshots/seattlerb/call_arg_assoc.txt | 34 - .../seattlerb/call_arg_assoc_kwsplat.txt | 43 - .../snapshots/seattlerb/call_arg_kwsplat.txt | 37 - .../seattlerb/call_args_assoc_quoted.txt | 106 - .../call_args_assoc_trailing_comma.txt | 34 - .../snapshots/seattlerb/call_args_command.txt | 54 - .../snapshots/seattlerb/call_array_arg.txt | 38 - .../seattlerb/call_array_block_call.txt | 40 - .../call_array_lambda_block_call.txt | 41 - .../seattlerb/call_array_lit_inline_hash.txt | 45 - .../prism/snapshots/seattlerb/call_assoc.txt | 31 - .../snapshots/seattlerb/call_assoc_new.txt | 34 - .../seattlerb/call_assoc_new_if_multiline.txt | 58 - .../seattlerb/call_assoc_trailing_comma.txt | 31 - .../seattlerb/call_bang_command_call.txt | 41 - .../seattlerb/call_bang_squiggle.txt | 24 - .../seattlerb/call_begin_call_block_call.txt | 53 - .../seattlerb/call_block_arg_named.txt | 28 - .../prism/snapshots/seattlerb/call_carat.txt | 24 - .../prism/snapshots/seattlerb/call_colon2.txt | 17 - .../snapshots/seattlerb/call_colon_parens.txt | 18 - .../prism/snapshots/seattlerb/call_div.txt | 24 - .../snapshots/seattlerb/call_dot_parens.txt | 18 - .../prism/snapshots/seattlerb/call_env.txt | 25 - .../prism/snapshots/seattlerb/call_eq3.txt | 24 - .../mri/prism/snapshots/seattlerb/call_gt.txt | 24 - .../snapshots/seattlerb/call_kwsplat.txt | 27 - .../snapshots/seattlerb/call_leading_dots.txt | 35 - .../seattlerb/call_leading_dots_comment.txt | 35 - .../mri/prism/snapshots/seattlerb/call_lt.txt | 24 - .../prism/snapshots/seattlerb/call_lte.txt | 24 - .../prism/snapshots/seattlerb/call_not.txt | 18 - .../prism/snapshots/seattlerb/call_pipe.txt | 24 - .../prism/snapshots/seattlerb/call_rshift.txt | 24 - .../seattlerb/call_self_brackets.txt | 22 - .../snapshots/seattlerb/call_spaceship.txt | 24 - .../call_stabby_do_end_with_block.txt | 41 - .../call_stabby_with_braces_block.txt | 41 - .../prism/snapshots/seattlerb/call_star.txt | 24 - .../prism/snapshots/seattlerb/call_star2.txt | 24 - .../seattlerb/call_trailing_comma.txt | 21 - .../seattlerb/call_trailing_dots.txt | 35 - .../snapshots/seattlerb/call_unary_bang.txt | 18 - .../mri/prism/snapshots/seattlerb/case_in.txt | 976 ---- .../prism/snapshots/seattlerb/case_in_31.txt | 49 - .../prism/snapshots/seattlerb/case_in_37.txt | 58 - .../prism/snapshots/seattlerb/case_in_42.txt | 44 - .../snapshots/seattlerb/case_in_42_2.txt | 40 - .../prism/snapshots/seattlerb/case_in_47.txt | 52 - .../prism/snapshots/seattlerb/case_in_67.txt | 33 - .../prism/snapshots/seattlerb/case_in_86.txt | 52 - .../snapshots/seattlerb/case_in_86_2.txt | 52 - .../seattlerb/case_in_array_pat_const.txt | 42 - .../seattlerb/case_in_array_pat_const2.txt | 48 - .../case_in_array_pat_paren_assign.txt | 48 - .../snapshots/seattlerb/case_in_const.txt | 28 - .../snapshots/seattlerb/case_in_else.txt | 40 - .../snapshots/seattlerb/case_in_find.txt | 47 - .../seattlerb/case_in_find_array.txt | 44 - .../snapshots/seattlerb/case_in_hash_pat.txt | 68 - .../seattlerb/case_in_hash_pat_assign.txt | 86 - .../case_in_hash_pat_paren_assign.txt | 51 - .../seattlerb/case_in_hash_pat_paren_true.txt | 47 - .../seattlerb/case_in_hash_pat_rest.txt | 55 - .../seattlerb/case_in_hash_pat_rest_solo.txt | 42 - .../seattlerb/case_in_if_unless_post_mod.txt | 67 - .../snapshots/seattlerb/case_in_multiple.txt | 59 - .../prism/snapshots/seattlerb/case_in_or.txt | 38 - .../snapshots/seattlerb/class_comments.txt | 31 - .../snapshots/seattlerb/cond_unary_minus.txt | 15 - .../seattlerb/const_2_op_asgn_or2.txt | 24 - .../seattlerb/const_3_op_asgn_or.txt | 18 - .../seattlerb/const_op_asgn_and1.txt | 19 - .../seattlerb/const_op_asgn_and2.txt | 18 - .../snapshots/seattlerb/const_op_asgn_or.txt | 20 - .../prism/snapshots/seattlerb/dasgn_icky2.txt | 61 - .../snapshots/seattlerb/defined_eh_parens.txt | 13 - .../seattlerb/defn_arg_asplat_arg.txt | 37 - .../seattlerb/defn_arg_forward_args.txt | 49 - .../seattlerb/defn_args_forward_args.txt | 61 - .../snapshots/seattlerb/defn_comments.txt | 18 - .../seattlerb/defn_endless_command.txt | 36 - .../seattlerb/defn_endless_command_rescue.txt | 43 - .../snapshots/seattlerb/defn_forward_args.txt | 43 - .../defn_forward_args__no_parens.txt | 43 - .../snapshots/seattlerb/defn_kwarg_env.txt | 55 - .../snapshots/seattlerb/defn_kwarg_kwarg.txt | 45 - .../seattlerb/defn_kwarg_kwsplat.txt | 39 - .../seattlerb/defn_kwarg_kwsplat_anon.txt | 39 - .../snapshots/seattlerb/defn_kwarg_lvar.txt | 42 - .../seattlerb/defn_kwarg_no_parens.txt | 34 - .../snapshots/seattlerb/defn_kwarg_val.txt | 37 - .../snapshots/seattlerb/defn_no_kwargs.txt | 29 - .../snapshots/seattlerb/defn_oneliner.txt | 47 - .../snapshots/seattlerb/defn_oneliner_eq2.txt | 47 - .../seattlerb/defn_oneliner_noargs.txt | 30 - .../defn_oneliner_noargs_parentheses.txt | 30 - .../seattlerb/defn_oneliner_rescue.txt | 158 - .../snapshots/seattlerb/defn_opt_last_arg.txt | 33 - .../snapshots/seattlerb/defn_opt_reg.txt | 36 - .../seattlerb/defn_opt_splat_arg.txt | 43 - .../prism/snapshots/seattlerb/defn_powarg.txt | 31 - .../snapshots/seattlerb/defn_reg_opt_reg.txt | 44 - .../snapshots/seattlerb/defn_splat_arg.txt | 34 - .../snapshots/seattlerb/defn_unary_not.txt | 21 - .../snapshots/seattlerb/defns_reserved.txt | 19 - .../defs_as_arg_with_do_block_inside.txt | 60 - .../snapshots/seattlerb/defs_comments.txt | 19 - .../seattlerb/defs_endless_command.txt | 46 - .../seattlerb/defs_endless_command_rescue.txt | 53 - .../prism/snapshots/seattlerb/defs_kwarg.txt | 35 - .../snapshots/seattlerb/defs_oneliner.txt | 48 - .../snapshots/seattlerb/defs_oneliner_eq2.txt | 48 - .../seattlerb/defs_oneliner_rescue.txt | 161 - .../prism/snapshots/seattlerb/difficult0_.txt | 72 - .../seattlerb/difficult1_line_numbers.txt | 267 - .../seattlerb/difficult1_line_numbers2.txt | 78 - .../prism/snapshots/seattlerb/difficult2_.txt | 74 - .../prism/snapshots/seattlerb/difficult3_.txt | 52 - .../snapshots/seattlerb/difficult3_2.txt | 42 - .../snapshots/seattlerb/difficult3_3.txt | 47 - .../snapshots/seattlerb/difficult3_4.txt | 38 - .../snapshots/seattlerb/difficult3_5.txt | 48 - .../snapshots/seattlerb/difficult3__10.txt | 52 - .../snapshots/seattlerb/difficult3__11.txt | 46 - .../snapshots/seattlerb/difficult3__12.txt | 49 - .../snapshots/seattlerb/difficult3__6.txt | 55 - .../snapshots/seattlerb/difficult3__7.txt | 49 - .../snapshots/seattlerb/difficult3__8.txt | 52 - .../snapshots/seattlerb/difficult3__9.txt | 49 - .../seattlerb/difficult4__leading_dots.txt | 25 - .../seattlerb/difficult4__leading_dots2.txt | 16 - .../prism/snapshots/seattlerb/difficult6_.txt | 61 - .../snapshots/seattlerb/difficult6__7.txt | 55 - .../snapshots/seattlerb/difficult6__8.txt | 55 - .../prism/snapshots/seattlerb/difficult7_.txt | 93 - test/mri/prism/snapshots/seattlerb/do_bug.txt | 63 - .../prism/snapshots/seattlerb/do_lambda.txt | 17 - .../snapshots/seattlerb/dot2_nil__26.txt | 20 - .../snapshots/seattlerb/dot3_nil__26.txt | 20 - .../prism/snapshots/seattlerb/dstr_evstr.txt | 37 - .../seattlerb/dstr_evstr_empty_end.txt | 25 - .../snapshots/seattlerb/dstr_lex_state.txt | 34 - .../prism/snapshots/seattlerb/dstr_str.txt | 27 - .../snapshots/seattlerb/dsym_esc_to_sym.txt | 11 - .../prism/snapshots/seattlerb/dsym_to_sym.txt | 37 - .../seattlerb/eq_begin_line_numbers.txt | 11 - ...gin_why_wont_people_use_their_spacebar.txt | 50 - .../prism/snapshots/seattlerb/evstr_evstr.txt | 41 - .../prism/snapshots/seattlerb/evstr_str.txt | 31 - .../snapshots/seattlerb/expr_not_bang.txt | 38 - test/mri/prism/snapshots/seattlerb/f_kw.txt | 34 - .../snapshots/seattlerb/f_kw__required.txt | 30 - .../snapshots/seattlerb/flip2_env_lvar.txt | 37 - .../seattlerb/float_with_if_modifier.txt | 17 - .../heredoc__backslash_dos_format.txt | 17 - .../seattlerb/heredoc_backslash_nl.txt | 17 - .../seattlerb/heredoc_bad_hex_escape.txt | 17 - .../seattlerb/heredoc_bad_oct_escape.txt | 17 - .../snapshots/seattlerb/heredoc_comma_arg.txt | 27 - .../snapshots/seattlerb/heredoc_lineno.txt | 26 - .../snapshots/seattlerb/heredoc_nested.txt | 41 - .../snapshots/seattlerb/heredoc_squiggly.txt | 33 - ...squiggly_blank_line_plus_interpolation.txt | 66 - .../heredoc_squiggly_blank_lines.txt | 33 - .../seattlerb/heredoc_squiggly_empty.txt | 11 - .../seattlerb/heredoc_squiggly_interp.txt | 48 - .../seattlerb/heredoc_squiggly_no_indent.txt | 11 - .../seattlerb/heredoc_squiggly_tabs.txt | 27 - .../seattlerb/heredoc_squiggly_tabs_extra.txt | 27 - .../heredoc_squiggly_visually_blank_lines.txt | 33 - .../heredoc_trailing_slash_continued_call.txt | 21 - .../snapshots/seattlerb/heredoc_unicode.txt | 11 - .../heredoc_with_carriage_return_escapes.txt | 11 - ...c_with_carriage_return_escapes_windows.txt | 11 - ...redoc_with_extra_carriage_horrible_mix.txt | 11 - .../heredoc_with_extra_carriage_returns.txt | 11 - ...oc_with_extra_carriage_returns_windows.txt | 11 - ...erpolation_and_carriage_return_escapes.txt | 26 - ...on_and_carriage_return_escapes_windows.txt | 26 - .../heredoc_with_not_global_interpolation.txt | 11 - .../heredoc_with_only_carriage_returns.txt | 11 - ...doc_with_only_carriage_returns_windows.txt | 11 - .../prism/snapshots/seattlerb/if_elsif.txt | 25 - .../prism/snapshots/seattlerb/if_symbol.txt | 31 - .../snapshots/seattlerb/in_expr_no_case.txt | 17 - .../mri/prism/snapshots/seattlerb/index_0.txt | 38 - .../snapshots/seattlerb/index_0_opasgn.txt | 36 - .../seattlerb/integer_with_if_modifier.txt | 18 - .../interpolated_symbol_array_line_breaks.txt | 25 - .../interpolated_word_array_line_breaks.txt | 25 - .../prism/snapshots/seattlerb/iter_args_1.txt | 40 - .../snapshots/seattlerb/iter_args_10_1.txt | 51 - .../snapshots/seattlerb/iter_args_10_2.txt | 56 - .../snapshots/seattlerb/iter_args_11_1.txt | 54 - .../snapshots/seattlerb/iter_args_11_2.txt | 59 - .../snapshots/seattlerb/iter_args_2__19.txt | 46 - .../prism/snapshots/seattlerb/iter_args_3.txt | 52 - .../prism/snapshots/seattlerb/iter_args_4.txt | 45 - .../prism/snapshots/seattlerb/iter_args_5.txt | 42 - .../prism/snapshots/seattlerb/iter_args_6.txt | 49 - .../snapshots/seattlerb/iter_args_7_1.txt | 48 - .../snapshots/seattlerb/iter_args_7_2.txt | 53 - .../snapshots/seattlerb/iter_args_8_1.txt | 51 - .../snapshots/seattlerb/iter_args_8_2.txt | 56 - .../snapshots/seattlerb/iter_args_9_1.txt | 46 - .../snapshots/seattlerb/iter_args_9_2.txt | 51 - .../prism/snapshots/seattlerb/iter_kwarg.txt | 42 - .../seattlerb/iter_kwarg_kwsplat.txt | 47 - .../snapshots/seattlerb/label_vs_string.txt | 34 - .../seattlerb/lambda_do_vs_brace.txt | 95 - .../seattlerb/lasgn_arg_rescue_arg.txt | 21 - .../lasgn_call_bracket_rescue_arg.txt | 34 - .../lasgn_call_nobracket_rescue_arg.txt | 34 - .../snapshots/seattlerb/lasgn_command.txt | 37 - .../prism/snapshots/seattlerb/lasgn_env.txt | 14 - .../snapshots/seattlerb/lasgn_ivar_env.txt | 13 - .../seattlerb/lasgn_lasgn_command_call.txt | 33 - .../seattlerb/lasgn_middle_splat.txt | 49 - .../seattlerb/magic_encoding_comment.txt | 46 - .../seattlerb/masgn_anon_splat_arg.txt | 29 - .../seattlerb/masgn_arg_colon_arg.txt | 42 - .../snapshots/seattlerb/masgn_arg_ident.txt | 42 - .../seattlerb/masgn_arg_splat_arg.txt | 35 - .../snapshots/seattlerb/masgn_colon2.txt | 43 - .../snapshots/seattlerb/masgn_colon3.txt | 36 - .../seattlerb/masgn_command_call.txt | 43 - .../seattlerb/masgn_double_paren.txt | 35 - .../snapshots/seattlerb/masgn_lhs_splat.txt | 33 - .../prism/snapshots/seattlerb/masgn_paren.txt | 39 - .../snapshots/seattlerb/masgn_splat_arg.txt | 32 - .../seattlerb/masgn_splat_arg_arg.txt | 35 - .../prism/snapshots/seattlerb/masgn_star.txt | 19 - .../seattlerb/masgn_var_star_var.txt | 32 - .../seattlerb/messy_op_asgn_lineno.txt | 60 - .../method_call_assoc_trailing_comma.txt | 41 - .../seattlerb/method_call_trailing_comma.txt | 31 - .../seattlerb/mlhs_back_anonsplat.txt | 35 - .../snapshots/seattlerb/mlhs_back_splat.txt | 38 - .../seattlerb/mlhs_front_anonsplat.txt | 35 - .../snapshots/seattlerb/mlhs_front_splat.txt | 38 - .../snapshots/seattlerb/mlhs_keyword.txt | 30 - .../seattlerb/mlhs_mid_anonsplat.txt | 44 - .../snapshots/seattlerb/mlhs_mid_splat.txt | 47 - .../prism/snapshots/seattlerb/mlhs_rescue.txt | 36 - .../snapshots/seattlerb/module_comments.txt | 29 - .../seattlerb/multiline_hash_declaration.txt | 95 - ..._interpolated_symbol_array_line_breaks.txt | 25 - ...on_interpolated_word_array_line_breaks.txt | 25 - .../seattlerb/op_asgn_command_call.txt | 37 - .../op_asgn_dot_ident_command_call.txt | 32 - .../seattlerb/op_asgn_index_command_call.txt | 53 - ..._asgn_primary_colon_const_command_call.txt | 41 - .../op_asgn_primary_colon_identifier1.txt | 20 - ..._primary_colon_identifier_command_call.txt | 40 - .../op_asgn_val_dot_ident_command_call.txt | 40 - .../seattlerb/parse_def_special_name.txt | 18 - .../seattlerb/parse_if_not_canonical.txt | 62 - .../seattlerb/parse_if_not_noncanonical.txt | 62 - .../snapshots/seattlerb/parse_line_block.txt | 30 - .../parse_line_block_inline_comment.txt | 35 - ..._block_inline_comment_leading_newlines.txt | 35 - ...se_line_block_inline_multiline_comment.txt | 35 - ...ine_call_ivar_arg_no_parens_line_break.txt | 20 - .../parse_line_call_ivar_line_break_paren.txt | 20 - .../seattlerb/parse_line_call_no_args.txt | 61 - .../seattlerb/parse_line_defn_complex.txt | 66 - .../seattlerb/parse_line_defn_no_parens.txt | 31 - .../parse_line_defn_no_parens_args.txt | 29 - .../snapshots/seattlerb/parse_line_dot2.txt | 51 - .../seattlerb/parse_line_dot2_open.txt | 38 - .../snapshots/seattlerb/parse_line_dot3.txt | 51 - .../seattlerb/parse_line_dot3_open.txt | 38 - .../parse_line_dstr_escaped_newline.txt | 20 - .../parse_line_dstr_soft_newline.txt | 20 - .../parse_line_evstr_after_break.txt | 35 - .../seattlerb/parse_line_hash_lit.txt | 22 - .../seattlerb/parse_line_heredoc.txt | 43 - .../seattlerb/parse_line_heredoc_evstr.txt | 37 - .../parse_line_heredoc_hardnewline.txt | 22 - .../parse_line_heredoc_regexp_chars.txt | 33 - .../parse_line_iter_call_no_parens.txt | 74 - .../seattlerb/parse_line_iter_call_parens.txt | 74 - .../seattlerb/parse_line_multiline_str.txt | 14 - .../parse_line_multiline_str_literal_n.txt | 14 - .../seattlerb/parse_line_newlines.txt | 6 - .../seattlerb/parse_line_op_asgn.txt | 32 - .../seattlerb/parse_line_postexe.txt | 22 - .../snapshots/seattlerb/parse_line_preexe.txt | 22 - .../snapshots/seattlerb/parse_line_rescue.txt | 62 - .../snapshots/seattlerb/parse_line_return.txt | 39 - .../parse_line_str_with_newline_escape.txt | 25 - .../snapshots/seattlerb/parse_line_to_ary.txt | 39 - .../parse_line_trailing_newlines.txt | 25 - .../parse_opt_call_args_assocs_comma.txt | 34 - .../parse_opt_call_args_lit_comma.txt | 24 - .../snapshots/seattlerb/parse_pattern_019.txt | 33 - .../snapshots/seattlerb/parse_pattern_044.txt | 38 - .../snapshots/seattlerb/parse_pattern_051.txt | 47 - .../snapshots/seattlerb/parse_pattern_058.txt | 73 - .../seattlerb/parse_pattern_058_2.txt | 67 - .../snapshots/seattlerb/parse_pattern_069.txt | 48 - .../snapshots/seattlerb/parse_pattern_076.txt | 58 - .../seattlerb/parse_until_not_canonical.txt | 49 - .../parse_until_not_noncanonical.txt | 49 - .../seattlerb/parse_while_not_canonical.txt | 49 - .../parse_while_not_noncanonical.txt | 49 - .../prism/snapshots/seattlerb/pctW_lineno.txt | 52 - .../seattlerb/pct_Q_backslash_nl.txt | 11 - test/mri/prism/snapshots/seattlerb/pct_nl.txt | 17 - .../seattlerb/pct_w_heredoc_interp_nested.txt | 50 - .../snapshots/seattlerb/pipe_semicolon.txt | 39 - .../prism/snapshots/seattlerb/pipe_space.txt | 36 - .../snapshots/seattlerb/qWords_space.txt | 10 - .../prism/snapshots/seattlerb/qsymbols.txt | 28 - .../snapshots/seattlerb/qsymbols_empty.txt | 10 - .../seattlerb/qsymbols_empty_space.txt | 10 - .../snapshots/seattlerb/qsymbols_interp.txt | 57 - .../seattlerb/quoted_symbol_hash_arg.txt | 35 - .../seattlerb/quoted_symbol_keys.txt | 25 - .../prism/snapshots/seattlerb/qw_escape.txt | 11 - .../snapshots/seattlerb/qw_escape_term.txt | 11 - .../snapshots/seattlerb/qwords_empty.txt | 10 - .../seattlerb/read_escape_unicode_curlies.txt | 11 - .../seattlerb/read_escape_unicode_h4.txt | 11 - test/mri/prism/snapshots/seattlerb/regexp.txt | 35 - .../seattlerb/regexp_esc_C_slash.txt | 11 - .../snapshots/seattlerb/regexp_esc_u.txt | 11 - .../seattlerb/regexp_escape_extended.txt | 11 - .../seattlerb/regexp_unicode_curlies.txt | 17 - .../seattlerb/required_kwarg_no_value.txt | 34 - .../seattlerb/rescue_do_end_ensure_result.txt | 58 - .../seattlerb/rescue_do_end_no_raise.txt | 75 - .../seattlerb/rescue_do_end_raised.txt | 52 - .../seattlerb/rescue_do_end_rescued.txt | 79 - .../snapshots/seattlerb/rescue_in_block.txt | 47 - .../snapshots/seattlerb/rescue_parens.txt | 48 - .../seattlerb/return_call_assocs.txt | 212 - .../prism/snapshots/seattlerb/rhs_asgn.txt | 15 - .../snapshots/seattlerb/ruby21_numbers.txt | 27 - .../snapshots/seattlerb/safe_attrasgn.txt | 31 - .../seattlerb/safe_attrasgn_constant.txt | 31 - .../prism/snapshots/seattlerb/safe_call.txt | 25 - .../seattlerb/safe_call_after_newline.txt | 25 - .../seattlerb/safe_call_dot_parens.txt | 25 - .../snapshots/seattlerb/safe_call_newline.txt | 25 - .../seattlerb/safe_call_operator.txt | 31 - .../seattlerb/safe_call_rhs_newline.txt | 31 - .../prism/snapshots/seattlerb/safe_calls.txt | 41 - .../snapshots/seattlerb/safe_op_asgn.txt | 41 - .../snapshots/seattlerb/safe_op_asgn2.txt | 34 - .../slashy_newlines_within_string.txt | 57 - .../seattlerb/stabby_arg_no_paren.txt | 28 - .../stabby_arg_opt_splat_arg_block_omfg.txt | 50 - .../seattlerb/stabby_block_iter_call.txt | 58 - ...bby_block_iter_call_no_target_with_arg.txt | 54 - .../snapshots/seattlerb/stabby_block_kw.txt | 33 - .../seattlerb/stabby_block_kw__required.txt | 29 - .../snapshots/seattlerb/stabby_proc_scope.txt | 31 - .../snapshots/seattlerb/str_backslashes.txt | 24 - .../str_double_double_escaped_newline.txt | 34 - .../seattlerb/str_double_escaped_newline.txt | 34 - .../seattlerb/str_double_newline.txt | 34 - .../prism/snapshots/seattlerb/str_evstr.txt | 31 - .../snapshots/seattlerb/str_evstr_escape.txt | 37 - .../seattlerb/str_heredoc_interp.txt | 31 - .../seattlerb/str_interp_ternary_or_label.txt | 104 - .../str_lit_concat_bad_encodings.txt | 21 - .../str_newline_hash_line_number.txt | 14 - .../snapshots/seattlerb/str_pct_Q_nested.txt | 37 - .../seattlerb/str_pct_nested_nested.txt | 40 - .../prism/snapshots/seattlerb/str_pct_q.txt | 11 - .../str_single_double_escaped_newline.txt | 34 - .../seattlerb/str_single_escaped_newline.txt | 34 - .../seattlerb/str_single_newline.txt | 34 - .../mri/prism/snapshots/seattlerb/str_str.txt | 27 - .../prism/snapshots/seattlerb/str_str_str.txt | 33 - .../prism/snapshots/seattlerb/super_arg.txt | 17 - .../snapshots/seattlerb/symbol_empty.txt | 11 - .../prism/snapshots/seattlerb/symbol_list.txt | 50 - .../mri/prism/snapshots/seattlerb/symbols.txt | 28 - .../snapshots/seattlerb/symbols_empty.txt | 10 - .../seattlerb/symbols_empty_space.txt | 10 - .../snapshots/seattlerb/symbols_interp.txt | 28 - test/mri/prism/snapshots/seattlerb/thingy.txt | 57 - .../snapshots/seattlerb/uminus_float.txt | 7 - .../prism/snapshots/seattlerb/unary_minus.txt | 25 - .../prism/snapshots/seattlerb/unary_plus.txt | 25 - .../seattlerb/unary_plus_on_literal.txt | 21 - .../prism/snapshots/seattlerb/unary_tilde.txt | 25 - .../prism/snapshots/seattlerb/utf8_bom.txt | 21 - .../prism/snapshots/seattlerb/when_splat.txt | 39 - .../snapshots/seattlerb/words_interp.txt | 29 - .../prism/snapshots/seattlerb/yield_arg.txt | 16 - .../snapshots/seattlerb/yield_call_assocs.txt | 224 - .../seattlerb/yield_empty_parens.txt | 10 - .../single_method_call_with_bang.txt | 15 - .../prism/snapshots/single_quote_heredocs.txt | 11 - test/mri/prism/snapshots/spanning_heredoc.txt | 409 -- .../snapshots/spanning_heredoc_newlines.txt | 155 - test/mri/prism/snapshots/strings.txt | 527 -- test/mri/prism/snapshots/super.txt | 132 - test/mri/prism/snapshots/symbols.txt | 464 -- test/mri/prism/snapshots/ternary_operator.txt | 295 - test/mri/prism/snapshots/tilde_heredocs.txt | 388 -- test/mri/prism/snapshots/undef.txt | 117 - test/mri/prism/snapshots/unescaping.txt | 34 - test/mri/prism/snapshots/unless.txt | 136 - .../unparser/corpus/literal/alias.txt | 29 - .../unparser/corpus/literal/assignment.txt | 1075 ---- .../unparser/corpus/literal/block.txt | 1402 ----- .../unparser/corpus/literal/case.txt | 446 -- .../unparser/corpus/literal/class.txt | 233 - .../unparser/corpus/literal/control.txt | 150 - .../snapshots/unparser/corpus/literal/def.txt | 1203 ---- .../unparser/corpus/literal/defined.txt | 55 - .../unparser/corpus/literal/defs.txt | 360 -- .../unparser/corpus/literal/dstr.txt | 341 -- .../unparser/corpus/literal/empty.txt | 5 - .../unparser/corpus/literal/empty_begin.txt | 9 - .../unparser/corpus/literal/flipflop.txt | 193 - .../snapshots/unparser/corpus/literal/for.txt | 171 - .../unparser/corpus/literal/hookexe.txt | 49 - .../snapshots/unparser/corpus/literal/if.txt | 288 - .../unparser/corpus/literal/kwbegin.txt | 491 -- .../unparser/corpus/literal/lambda.txt | 151 - .../unparser/corpus/literal/literal.txt | 1186 ---- .../unparser/corpus/literal/module.txt | 107 - .../unparser/corpus/literal/opasgn.txt | 509 -- .../unparser/corpus/literal/pattern.txt | 446 -- .../unparser/corpus/literal/pragma.txt | 20 - .../unparser/corpus/literal/range.txt | 55 - .../unparser/corpus/literal/rescue.txt | 101 - .../unparser/corpus/literal/send.txt | 2190 -------- .../unparser/corpus/literal/since/27.txt | 49 - .../unparser/corpus/literal/since/30.txt | 88 - .../unparser/corpus/literal/since/31.txt | 90 - .../unparser/corpus/literal/since/32.txt | 108 - .../unparser/corpus/literal/singletons.txt | 9 - .../unparser/corpus/literal/super.txt | 277 - .../unparser/corpus/literal/unary.txt | 248 - .../unparser/corpus/literal/undef.txt | 29 - .../unparser/corpus/literal/variables.txt | 53 - .../unparser/corpus/literal/while.txt | 704 --- .../unparser/corpus/literal/yield.txt | 56 - .../unparser/corpus/semantic/and.txt | 235 - .../unparser/corpus/semantic/block.txt | 191 - .../unparser/corpus/semantic/def.txt | 90 - .../unparser/corpus/semantic/dstr.txt | 570 -- .../unparser/corpus/semantic/kwbegin.txt | 259 - .../unparser/corpus/semantic/literal.txt | 103 - .../unparser/corpus/semantic/send.txt | 163 - .../unparser/corpus/semantic/undef.txt | 29 - .../unparser/corpus/semantic/while.txt | 277 - test/mri/prism/snapshots/until.txt | 143 - test/mri/prism/snapshots/variables.txt | 408 -- test/mri/prism/snapshots/while.txt | 361 -- .../snapshots/whitequark/__ENCODING__.txt | 6 - .../whitequark/__ENCODING___legacy_.txt | 6 - test/mri/prism/snapshots/whitequark/alias.txt | 21 - .../prism/snapshots/whitequark/alias_gvar.txt | 21 - ...iuous_quoted_label_in_ternary_operator.txt | 60 - test/mri/prism/snapshots/whitequark/and.txt | 53 - .../prism/snapshots/whitequark/and_asgn.txt | 59 - .../snapshots/whitequark/and_or_masgn.txt | 93 - .../whitequark/anonymous_blockarg.txt | 46 - test/mri/prism/snapshots/whitequark/arg.txt | 56 - .../whitequark/arg_duplicate_ignored.txt | 59 - .../prism/snapshots/whitequark/arg_label.txt | 115 - .../prism/snapshots/whitequark/arg_scope.txt | 34 - test/mri/prism/snapshots/whitequark/args.txt | 1075 ---- .../snapshots/whitequark/args_args_assocs.txt | 96 - .../whitequark/args_args_assocs_comma.txt | 54 - .../snapshots/whitequark/args_args_comma.txt | 38 - .../snapshots/whitequark/args_args_star.txt | 90 - .../snapshots/whitequark/args_assocs.txt | 195 - .../whitequark/args_assocs_comma.txt | 44 - .../whitequark/args_assocs_legacy.txt | 195 - .../snapshots/whitequark/args_block_pass.txt | 28 - .../prism/snapshots/whitequark/args_cmd.txt | 41 - .../prism/snapshots/whitequark/args_star.txt | 70 - .../snapshots/whitequark/array_assocs.txt | 44 - .../snapshots/whitequark/array_plain.txt | 16 - .../snapshots/whitequark/array_splat.txt | 68 - .../snapshots/whitequark/array_symbols.txt | 22 - .../whitequark/array_symbols_empty.txt | 15 - .../whitequark/array_symbols_interp.txt | 67 - .../snapshots/whitequark/array_words.txt | 22 - .../whitequark/array_words_empty.txt | 15 - .../whitequark/array_words_interp.txt | 78 - .../prism/snapshots/whitequark/asgn_cmd.txt | 55 - .../prism/snapshots/whitequark/asgn_mrhs.txt | 87 - .../prism/snapshots/whitequark/back_ref.txt | 7 - test/mri/prism/snapshots/whitequark/bang.txt | 25 - .../prism/snapshots/whitequark/bang_cmd.txt | 38 - .../snapshots/whitequark/begin_cmdarg.txt | 51 - .../beginless_erange_after_newline.txt | 23 - .../beginless_irange_after_newline.txt | 23 - .../snapshots/whitequark/beginless_range.txt | 21 - .../prism/snapshots/whitequark/blockarg.txt | 31 - .../prism/snapshots/whitequark/blockargs.txt | 1339 ----- test/mri/prism/snapshots/whitequark/break.txt | 56 - .../snapshots/whitequark/break_block.txt | 40 - .../prism/snapshots/whitequark/bug_435.txt | 38 - .../prism/snapshots/whitequark/bug_447.txt | 56 - .../prism/snapshots/whitequark/bug_452.txt | 63 - .../prism/snapshots/whitequark/bug_466.txt | 69 - .../prism/snapshots/whitequark/bug_473.txt | 33 - .../prism/snapshots/whitequark/bug_480.txt | 36 - .../prism/snapshots/whitequark/bug_481.txt | 50 - .../whitequark/bug_ascii_8bit_in_literal.txt | 11 - .../whitequark/bug_cmd_string_lookahead.txt | 30 - .../prism/snapshots/whitequark/bug_cmdarg.txt | 106 - .../whitequark/bug_def_no_paren_eql_begin.txt | 18 - .../whitequark/bug_do_block_in_call_args.txt | 50 - .../whitequark/bug_do_block_in_cmdarg.txt | 40 - .../whitequark/bug_do_block_in_hash_brace.txt | 383 -- .../snapshots/whitequark/bug_heredoc_do.txt | 30 - .../whitequark/bug_interp_single.txt | 36 - .../whitequark/bug_lambda_leakage.txt | 38 - .../whitequark/bug_regex_verification.txt | 11 - .../whitequark/bug_rescue_empty_else.txt | 25 - .../whitequark/bug_while_not_parens_do.txt | 28 - .../prism/snapshots/whitequark/case_cond.txt | 34 - .../snapshots/whitequark/case_cond_else.txt | 46 - .../prism/snapshots/whitequark/case_expr.txt | 44 - .../snapshots/whitequark/case_expr_else.txt | 60 - .../snapshots/whitequark/casgn_scoped.txt | 20 - .../snapshots/whitequark/casgn_toplevel.txt | 18 - .../snapshots/whitequark/casgn_unscoped.txt | 13 - .../prism/snapshots/whitequark/character.txt | 11 - test/mri/prism/snapshots/whitequark/class.txt | 27 - .../class_definition_in_while_cond.txt | 171 - .../snapshots/whitequark/class_super.txt | 18 - .../whitequark/class_super_label.txt | 35 - .../comments_before_leading_dot__27.txt | 85 - .../prism/snapshots/whitequark/complex.txt | 27 - .../prism/snapshots/whitequark/cond_begin.txt | 40 - .../snapshots/whitequark/cond_begin_masgn.txt | 52 - .../snapshots/whitequark/cond_eflipflop.txt | 78 - .../snapshots/whitequark/cond_iflipflop.txt | 78 - .../whitequark/cond_match_current_line.txt | 34 - .../snapshots/whitequark/const_op_asgn.txt | 101 - .../snapshots/whitequark/const_scoped.txt | 13 - .../snapshots/whitequark/const_toplevel.txt | 11 - .../snapshots/whitequark/const_unscoped.txt | 7 - test/mri/prism/snapshots/whitequark/cpath.txt | 33 - test/mri/prism/snapshots/whitequark/cvar.txt | 7 - .../mri/prism/snapshots/whitequark/cvasgn.txt | 13 - .../whitequark/dedenting_heredoc.txt | 485 -- ...olating_heredoc_fake_line_continuation.txt | 21 - ...nterpolating_heredoc_line_continuation.txt | 21 - test/mri/prism/snapshots/whitequark/def.txt | 83 - .../prism/snapshots/whitequark/defined.txt | 42 - test/mri/prism/snapshots/whitequark/defs.txt | 90 - .../prism/snapshots/whitequark/empty_stmt.txt | 5 - .../whitequark/endless_comparison_method.txt | 221 - .../snapshots/whitequark/endless_method.txt | 151 - .../endless_method_command_syntax.txt | 392 -- .../endless_method_forwarded_args_legacy.txt | 43 - .../endless_method_with_rescue_mod.txt | 56 - .../endless_method_without_args.txt | 89 - .../mri/prism/snapshots/whitequark/ensure.txt | 40 - .../snapshots/whitequark/ensure_empty.txt | 16 - test/mri/prism/snapshots/whitequark/false.txt | 6 - test/mri/prism/snapshots/whitequark/float.txt | 9 - test/mri/prism/snapshots/whitequark/for.txt | 83 - .../prism/snapshots/whitequark/for_mlhs.txt | 56 - .../snapshots/whitequark/forward_arg.txt | 43 - .../whitequark/forward_arg_with_open_args.txt | 404 -- .../whitequark/forward_args_legacy.txt | 99 - .../forwarded_argument_with_kwrestarg.txt | 58 - .../forwarded_argument_with_restarg.txt | 55 - .../whitequark/forwarded_kwrestarg.txt | 52 - ...warded_kwrestarg_with_additional_kwarg.txt | 63 - .../whitequark/forwarded_restarg.txt | 49 - test/mri/prism/snapshots/whitequark/gvar.txt | 7 - .../mri/prism/snapshots/whitequark/gvasgn.txt | 13 - .../prism/snapshots/whitequark/hash_empty.txt | 9 - .../snapshots/whitequark/hash_hashrocket.txt | 49 - .../snapshots/whitequark/hash_kwsplat.txt | 35 - .../prism/snapshots/whitequark/hash_label.txt | 22 - .../snapshots/whitequark/hash_label_end.txt | 100 - .../whitequark/hash_pair_value_omission.txt | 97 - .../prism/snapshots/whitequark/heredoc.txt | 23 - test/mri/prism/snapshots/whitequark/if.txt | 63 - .../prism/snapshots/whitequark/if_else.txt | 95 - .../prism/snapshots/whitequark/if_elsif.txt | 65 - .../snapshots/whitequark/if_masgn__24.txt | 42 - .../mri/prism/snapshots/whitequark/if_mod.txt | 34 - .../prism/snapshots/whitequark/if_nl_then.txt | 34 - .../if_while_after_class__since_32.txt | 119 - test/mri/prism/snapshots/whitequark/int.txt | 14 - .../snapshots/whitequark/int___LINE__.txt | 6 - .../snapshots/whitequark/interp_digit_var.txt | 273 - test/mri/prism/snapshots/whitequark/ivar.txt | 7 - .../mri/prism/snapshots/whitequark/ivasgn.txt | 13 - .../whitequark/keyword_argument_omission.txt | 65 - test/mri/prism/snapshots/whitequark/kwarg.txt | 30 - .../snapshots/whitequark/kwbegin_compstmt.txt | 34 - .../prism/snapshots/whitequark/kwnilarg.txt | 84 - .../prism/snapshots/whitequark/kwoptarg.txt | 34 - ...targ_with_kwrestarg_and_forwarded_args.txt | 58 - .../snapshots/whitequark/kwrestarg_named.txt | 31 - .../whitequark/kwrestarg_unnamed.txt | 31 - .../lbrace_arg_after_command_args.txt | 54 - .../lparenarg_after_lvar__since_25.txt | 67 - test/mri/prism/snapshots/whitequark/lvar.txt | 15 - .../whitequark/lvar_injecting_match.txt | 39 - .../mri/prism/snapshots/whitequark/lvasgn.txt | 17 - test/mri/prism/snapshots/whitequark/masgn.txt | 83 - .../prism/snapshots/whitequark/masgn_attr.txt | 82 - .../prism/snapshots/whitequark/masgn_cmd.txt | 35 - .../snapshots/whitequark/masgn_const.txt | 46 - .../snapshots/whitequark/masgn_nested.txt | 66 - .../snapshots/whitequark/masgn_splat.txt | 284 - .../method_definition_in_while_cond.txt | 199 - .../mri/prism/snapshots/whitequark/module.txt | 14 - .../whitequark/multiple_pattern_matches.txt | 173 - .../whitequark/newline_in_hash_argument.txt | 163 - test/mri/prism/snapshots/whitequark/next.txt | 56 - .../prism/snapshots/whitequark/next_block.txt | 40 - test/mri/prism/snapshots/whitequark/nil.txt | 6 - .../snapshots/whitequark/nil_expression.txt | 16 - .../whitequark/non_lvar_injecting_match.txt | 44 - test/mri/prism/snapshots/whitequark/not.txt | 55 - .../prism/snapshots/whitequark/not_cmd.txt | 38 - .../snapshots/whitequark/not_masgn__24.txt | 45 - .../prism/snapshots/whitequark/nth_ref.txt | 7 - .../whitequark/numbered_args_after_27.txt | 143 - .../whitequark/numparam_outside_block.txt | 114 - .../prism/snapshots/whitequark/op_asgn.txt | 74 - .../snapshots/whitequark/op_asgn_cmd.txt | 178 - .../snapshots/whitequark/op_asgn_index.txt | 38 - .../whitequark/op_asgn_index_cmd.txt | 58 - .../mri/prism/snapshots/whitequark/optarg.txt | 74 - test/mri/prism/snapshots/whitequark/or.txt | 53 - .../prism/snapshots/whitequark/or_asgn.txt | 59 - .../snapshots/whitequark/parser_bug_272.txt | 42 - .../snapshots/whitequark/parser_bug_490.txt | 106 - .../snapshots/whitequark/parser_bug_507.txt | 36 - .../snapshots/whitequark/parser_bug_518.txt | 18 - .../snapshots/whitequark/parser_bug_525.txt | 65 - .../snapshots/whitequark/parser_bug_604.txt | 57 - .../snapshots/whitequark/parser_bug_640.txt | 21 - .../snapshots/whitequark/parser_bug_645.txt | 35 - .../snapshots/whitequark/parser_bug_830.txt | 11 - ...ps_truncated_parts_of_squiggly_heredoc.txt | 19 - ...ser_slash_slash_n_escaping_in_literals.txt | 139 - .../pattern_matching__FILE__LINE_literals.txt | 54 - .../pattern_matching_blank_else.txt | 31 - .../whitequark/pattern_matching_else.txt | 36 - .../pattern_matching_single_line.txt | 45 - ...e_line_allowed_omission_of_parentheses.txt | 249 - .../prism/snapshots/whitequark/postexe.txt | 15 - .../mri/prism/snapshots/whitequark/preexe.txt | 15 - .../prism/snapshots/whitequark/procarg0.txt | 78 - .../snapshots/whitequark/range_exclusive.txt | 16 - .../snapshots/whitequark/range_inclusive.txt | 16 - .../prism/snapshots/whitequark/rational.txt | 14 - test/mri/prism/snapshots/whitequark/redo.txt | 6 - .../snapshots/whitequark/regex_interp.txt | 38 - .../snapshots/whitequark/regex_plain.txt | 11 - .../snapshots/whitequark/resbody_list.txt | 45 - .../whitequark/resbody_list_mrhs.txt | 55 - .../snapshots/whitequark/resbody_list_var.txt | 56 - .../snapshots/whitequark/resbody_var.txt | 86 - .../mri/prism/snapshots/whitequark/rescue.txt | 43 - .../snapshots/whitequark/rescue_else.txt | 59 - .../whitequark/rescue_else_ensure.txt | 75 - .../snapshots/whitequark/rescue_ensure.txt | 59 - .../whitequark/rescue_in_lambda_block.txt | 26 - .../prism/snapshots/whitequark/rescue_mod.txt | 29 - .../snapshots/whitequark/rescue_mod_asgn.txt | 35 - .../snapshots/whitequark/rescue_mod_masgn.txt | 44 - .../whitequark/rescue_mod_op_assign.txt | 36 - .../whitequark/rescue_without_begin_end.txt | 59 - .../snapshots/whitequark/restarg_named.txt | 31 - .../snapshots/whitequark/restarg_unnamed.txt | 31 - test/mri/prism/snapshots/whitequark/retry.txt | 6 - .../mri/prism/snapshots/whitequark/return.txt | 56 - .../snapshots/whitequark/return_block.txt | 40 - .../snapshots/whitequark/ruby_bug_10279.txt | 32 - .../snapshots/whitequark/ruby_bug_10653.txt | 173 - .../snapshots/whitequark/ruby_bug_11107.txt | 48 - .../snapshots/whitequark/ruby_bug_11380.txt | 55 - .../snapshots/whitequark/ruby_bug_11873.txt | 767 --- .../snapshots/whitequark/ruby_bug_11873_a.txt | 1231 ----- .../snapshots/whitequark/ruby_bug_11873_b.txt | 98 - .../snapshots/whitequark/ruby_bug_11989.txt | 24 - .../snapshots/whitequark/ruby_bug_11990.txt | 34 - .../snapshots/whitequark/ruby_bug_12073.txt | 96 - .../snapshots/whitequark/ruby_bug_12402.txt | 567 -- .../snapshots/whitequark/ruby_bug_12669.txt | 133 - .../snapshots/whitequark/ruby_bug_12686.txt | 39 - .../snapshots/whitequark/ruby_bug_13547.txt | 31 - .../snapshots/whitequark/ruby_bug_14690.txt | 59 - .../snapshots/whitequark/ruby_bug_15789.txt | 120 - .../snapshots/whitequark/ruby_bug_9669.txt | 58 - .../mri/prism/snapshots/whitequark/sclass.txt | 25 - test/mri/prism/snapshots/whitequark/self.txt | 6 - .../snapshots/whitequark/send_attr_asgn.txt | 106 - .../whitequark/send_attr_asgn_conditional.txt | 31 - .../snapshots/whitequark/send_binary_op.txt | 551 -- .../whitequark/send_block_chain_cmd.txt | 325 -- .../whitequark/send_block_conditional.txt | 31 - .../prism/snapshots/whitequark/send_call.txt | 57 - .../snapshots/whitequark/send_conditional.txt | 25 - .../prism/snapshots/whitequark/send_index.txt | 34 - .../snapshots/whitequark/send_index_asgn.txt | 37 - .../whitequark/send_index_asgn_legacy.txt | 37 - .../snapshots/whitequark/send_index_cmd.txt | 51 - .../whitequark/send_index_legacy.txt | 34 - .../snapshots/whitequark/send_lambda.txt | 44 - .../snapshots/whitequark/send_lambda_args.txt | 51 - .../whitequark/send_lambda_args_noparen.txt | 57 - .../whitequark/send_lambda_args_shadow.txt | 34 - .../whitequark/send_lambda_legacy.txt | 12 - .../whitequark/send_op_asgn_conditional.txt | 27 - .../prism/snapshots/whitequark/send_plain.txt | 65 - .../snapshots/whitequark/send_plain_cmd.txt | 104 - .../prism/snapshots/whitequark/send_self.txt | 41 - .../snapshots/whitequark/send_self_block.txt | 75 - .../snapshots/whitequark/send_unary_op.txt | 65 - .../whitequark/slash_newline_in_heredocs.txt | 33 - .../snapshots/whitequark/space_args_arg.txt | 27 - .../whitequark/space_args_arg_block.txt | 109 - .../whitequark/space_args_arg_call.txt | 37 - .../whitequark/space_args_arg_newline.txt | 27 - .../snapshots/whitequark/space_args_block.txt | 28 - .../snapshots/whitequark/space_args_cmd.txt | 47 - .../snapshots/whitequark/string___FILE__.txt | 8 - .../snapshots/whitequark/string_concat.txt | 30 - .../snapshots/whitequark/string_dvar.txt | 36 - .../snapshots/whitequark/string_interp.txt | 37 - .../snapshots/whitequark/string_plain.txt | 17 - test/mri/prism/snapshots/whitequark/super.txt | 49 - .../snapshots/whitequark/super_block.txt | 48 - .../snapshots/whitequark/symbol_interp.txt | 37 - .../snapshots/whitequark/symbol_plain.txt | 17 - .../prism/snapshots/whitequark/ternary.txt | 36 - .../whitequark/ternary_ambiguous_symbol.txt | 50 - .../whitequark/trailing_forward_arg.txt | 55 - test/mri/prism/snapshots/whitequark/true.txt | 6 - .../whitequark/unary_num_pow_precedence.txt | 80 - test/mri/prism/snapshots/whitequark/undef.txt | 39 - .../mri/prism/snapshots/whitequark/unless.txt | 63 - .../snapshots/whitequark/unless_else.txt | 95 - .../prism/snapshots/whitequark/unless_mod.txt | 34 - test/mri/prism/snapshots/whitequark/until.txt | 61 - .../prism/snapshots/whitequark/until_mod.txt | 33 - .../prism/snapshots/whitequark/until_post.txt | 42 - .../snapshots/whitequark/var_and_asgn.txt | 14 - .../snapshots/whitequark/var_op_asgn.txt | 57 - .../snapshots/whitequark/var_op_asgn_cmd.txt | 28 - .../snapshots/whitequark/var_or_asgn.txt | 14 - .../prism/snapshots/whitequark/when_multi.txt | 50 - .../prism/snapshots/whitequark/when_splat.txt | 72 - .../prism/snapshots/whitequark/when_then.txt | 44 - test/mri/prism/snapshots/whitequark/while.txt | 61 - .../prism/snapshots/whitequark/while_mod.txt | 33 - .../prism/snapshots/whitequark/while_post.txt | 42 - .../snapshots/whitequark/xstring_interp.txt | 37 - .../snapshots/whitequark/xstring_plain.txt | 11 - test/mri/prism/snapshots/whitequark/yield.txt | 51 - .../mri/prism/snapshots/whitequark/zsuper.txt | 7 - test/mri/prism/snapshots/xstring.txt | 67 - .../snapshots/xstring_with_backslash.txt | 11 - test/mri/prism/snapshots/yield.txt | 43 - test/mri/prism/static_inspect_test.rb | 90 - test/mri/prism/static_literals_test.rb | 85 - test/mri/prism/warnings_test.rb | 123 - .../Amps and angle encoding.text | 21 - .../rdoc/MarkdownTest_1.0.3/Auto links.text | 13 - .../MarkdownTest_1.0.3/Backslash escapes.text | 120 - .../Blockquotes with code blocks.text | 11 - .../rdoc/MarkdownTest_1.0.3/Code Blocks.text | 14 - .../rdoc/MarkdownTest_1.0.3/Code Spans.text | 6 - ...apped paragraphs with list-like lines.text | 8 - .../MarkdownTest_1.0.3/Horizontal rules.text | 67 - .../Inline HTML (Advanced).text | 15 - .../Inline HTML (Simple).text | 69 - .../Inline HTML comments.text | 13 - .../Links, inline style.text | 12 - .../Links, reference style.text | 71 - .../Links, shortcut references.text | 20 - .../Literal quotes in titles.text | 7 - .../Markdown Documentation - Basics.text | 306 - .../Markdown Documentation - Syntax.text | 888 --- .../Nested blockquotes.text | 5 - .../Ordered and unordered lists.text | 131 - .../Strong and em together.text | 7 - .../rdoc/rdoc/MarkdownTest_1.0.3/Tabs.text | 21 - .../rdoc/MarkdownTest_1.0.3/Tidiness.text | 5 - test/mri/rdoc/rdoc/README | 1 - test/mri/rdoc/rdoc/binary.dat | Bin 1024 -> 0 bytes test/mri/rdoc/rdoc/helper.rb | 5 - test/mri/rdoc/rdoc/hidden.zip.txt | 1 - test/mri/rdoc/rdoc/rdoc_alias_test.rb | 13 - test/mri/rdoc/rdoc/rdoc_any_method_test.rb | 609 -- test/mri/rdoc/rdoc/rdoc_attr_test.rb | 190 - test/mri/rdoc/rdoc/rdoc_class_module_test.rb | 1706 ------ test/mri/rdoc/rdoc/rdoc_code_object_test.rb | 416 -- test/mri/rdoc/rdoc/rdoc_comment_test.rb | 480 -- test/mri/rdoc/rdoc/rdoc_constant_test.rb | 182 - .../rdoc/rdoc/rdoc_context_section_test.rb | 145 - test/mri/rdoc/rdoc/rdoc_context_test.rb | 958 ---- .../rdoc/rdoc/rdoc_cross_reference_test.rb | 217 - test/mri/rdoc/rdoc/rdoc_encoding_test.rb | 184 - test/mri/rdoc/rdoc/rdoc_extend_test.rb | 94 - .../rdoc/rdoc/rdoc_generator_darkfish_test.rb | 593 -- .../rdoc/rdoc_generator_json_index_test.rb | 363 -- .../rdoc/rdoc/rdoc_generator_markup_test.rb | 59 - .../rdoc/rdoc_generator_pot_po_entry_test.rb | 140 - .../rdoc/rdoc/rdoc_generator_pot_po_test.rb | 52 - test/mri/rdoc/rdoc/rdoc_generator_pot_test.rb | 92 - test/mri/rdoc/rdoc/rdoc_generator_ri_test.rb | 76 - test/mri/rdoc/rdoc/rdoc_i18n_locale_test.rb | 74 - test/mri/rdoc/rdoc/rdoc_i18n_text_test.rb | 124 - test/mri/rdoc/rdoc/rdoc_include_test.rb | 109 - test/mri/rdoc/rdoc/rdoc_markdown_test.rb | 1140 ---- test/mri/rdoc/rdoc/rdoc_markdown_test_test.rb | 1883 ------- .../rdoc_markup_attribute_manager_test.rb | 395 -- .../rdoc/rdoc/rdoc_markup_attributes_test.rb | 39 - .../rdoc/rdoc/rdoc_markup_document_test.rb | 207 - .../rdoc/rdoc/rdoc_markup_formatter_test.rb | 181 - .../rdoc/rdoc/rdoc_markup_hard_break_test.rb | 31 - .../mri/rdoc/rdoc/rdoc_markup_heading_test.rb | 34 - .../mri/rdoc/rdoc/rdoc_markup_include_test.rb | 19 - .../rdoc_markup_indented_paragraph_test.rb | 53 - .../rdoc/rdoc/rdoc_markup_paragraph_test.rb | 32 - test/mri/rdoc/rdoc/rdoc_markup_parser_test.rb | 1684 ------ .../rdoc/rdoc/rdoc_markup_pre_process_test.rb | 481 -- test/mri/rdoc/rdoc/rdoc_markup_raw_test.rb | 22 - test/mri/rdoc/rdoc/rdoc_markup_test.rb | 95 - .../mri/rdoc/rdoc/rdoc_markup_to_ansi_test.rb | 380 -- test/mri/rdoc/rdoc/rdoc_markup_to_bs_test.rb | 363 -- .../rdoc/rdoc_markup_to_html_crossref_test.rb | 325 -- .../rdoc/rdoc_markup_to_html_snippet_test.rb | 709 --- .../mri/rdoc/rdoc/rdoc_markup_to_html_test.rb | 1024 ---- .../rdoc_markup_to_joined_paragraph_test.rb | 32 - .../rdoc/rdoc/rdoc_markup_to_label_test.rb | 112 - .../rdoc/rdoc/rdoc_markup_to_markdown_test.rb | 397 -- .../mri/rdoc/rdoc/rdoc_markup_to_rdoc_test.rb | 388 -- .../rdoc_markup_to_table_of_contents_test.rb | 126 - .../rdoc/rdoc/rdoc_markup_to_tt_only_test.rb | 246 - .../rdoc/rdoc/rdoc_markup_verbatim_test.rb | 29 - test/mri/rdoc/rdoc/rdoc_method_attr_test.rb | 210 - test/mri/rdoc/rdoc/rdoc_normal_class_test.rb | 47 - test/mri/rdoc/rdoc/rdoc_normal_module_test.rb | 42 - test/mri/rdoc/rdoc/rdoc_options_test.rb | 975 ---- test/mri/rdoc/rdoc/rdoc_parser_c_test.rb | 2150 ------- .../rdoc/rdoc/rdoc_parser_changelog_test.rb | 483 -- .../rdoc/rdoc/rdoc_parser_markdown_test.rb | 61 - .../rdoc/rdoc/rdoc_parser_prism_ruby_test.rb | 2165 -------- test/mri/rdoc/rdoc/rdoc_parser_rd_test.rb | 55 - test/mri/rdoc/rdoc/rdoc_parser_ruby_test.rb | 4396 --------------- test/mri/rdoc/rdoc/rdoc_parser_simple_test.rb | 115 - test/mri/rdoc/rdoc/rdoc_parser_test.rb | 334 -- .../rdoc/rdoc/rdoc_rd_block_parser_test.rb | 557 -- .../rdoc/rdoc/rdoc_rd_inline_parser_test.rb | 178 - test/mri/rdoc/rdoc/rdoc_rd_inline_test.rb | 63 - test/mri/rdoc/rdoc/rdoc_rd_test.rb | 30 - test/mri/rdoc/rdoc/rdoc_rdoc_test.rb | 582 -- test/mri/rdoc/rdoc/rdoc_require_test.rb | 25 - test/mri/rdoc/rdoc/rdoc_ri_driver_test.rb | 1610 ------ test/mri/rdoc/rdoc/rdoc_ri_paths_test.rb | 157 - test/mri/rdoc/rdoc/rdoc_rubygems_hook_test.rb | 329 -- test/mri/rdoc/rdoc/rdoc_servlet_test.rb | 553 -- test/mri/rdoc/rdoc/rdoc_single_class_test.rb | 20 - test/mri/rdoc/rdoc/rdoc_stats_test.rb | 720 --- test/mri/rdoc/rdoc/rdoc_store_test.rb | 1057 ---- test/mri/rdoc/rdoc/rdoc_task_test.rb | 182 - test/mri/rdoc/rdoc/rdoc_text_test.rb | 585 -- test/mri/rdoc/rdoc/rdoc_token_stream_test.rb | 109 - test/mri/rdoc/rdoc/rdoc_tom_doc_test.rb | 579 -- test/mri/rdoc/rdoc/rdoc_top_level_test.rb | 289 - .../rdoc/rdoc/support/formatter_test_case.rb | 764 --- test/mri/rdoc/rdoc/support/test_case.rb | 219 - .../rdoc/support/text_formatter_test_case.rb | 131 - test/mri/rdoc/rdoc/test.ja.largedoc | 3 - test/mri/rdoc/rdoc/test.ja.rdoc | 10 - test/mri/rdoc/rdoc/test.ja.txt | 8 - test/mri/rdoc/rdoc/test.txt | 1 - test/mri/rdoc/rdoc/xref_data.rb | 164 - test/mri/rdoc/rdoc/xref_test_case.rb | 86 - test/mri/rdoc/rdoc_alias_test.rb | 13 - test/mri/rdoc/rdoc_any_method_test.rb | 609 -- test/mri/rdoc/rdoc_attr_test.rb | 190 - test/mri/rdoc/rdoc_class_module_test.rb | 1706 ------ test/mri/rdoc/rdoc_code_object_test.rb | 416 -- test/mri/rdoc/rdoc_comment_test.rb | 480 -- test/mri/rdoc/rdoc_constant_test.rb | 182 - test/mri/rdoc/rdoc_context_section_test.rb | 145 - test/mri/rdoc/rdoc_context_test.rb | 958 ---- test/mri/rdoc/rdoc_cross_reference_test.rb | 217 - test/mri/rdoc/rdoc_encoding_test.rb | 184 - test/mri/rdoc/rdoc_extend_test.rb | 94 - test/mri/rdoc/rdoc_generator_darkfish_test.rb | 593 -- .../rdoc/rdoc_generator_json_index_test.rb | 363 -- test/mri/rdoc/rdoc_generator_markup_test.rb | 59 - .../rdoc/rdoc_generator_pot_po_entry_test.rb | 140 - test/mri/rdoc/rdoc_generator_pot_po_test.rb | 52 - test/mri/rdoc/rdoc_generator_pot_test.rb | 92 - test/mri/rdoc/rdoc_generator_ri_test.rb | 76 - test/mri/rdoc/rdoc_i18n_locale_test.rb | 74 - test/mri/rdoc/rdoc_i18n_text_test.rb | 124 - test/mri/rdoc/rdoc_include_test.rb | 109 - test/mri/rdoc/rdoc_markdown_test.rb | 1140 ---- test/mri/rdoc/rdoc_markdown_test_test.rb | 1883 ------- .../rdoc_markup_attribute_manager_test.rb | 395 -- test/mri/rdoc/rdoc_markup_attributes_test.rb | 39 - test/mri/rdoc/rdoc_markup_document_test.rb | 207 - test/mri/rdoc/rdoc_markup_formatter_test.rb | 181 - test/mri/rdoc/rdoc_markup_hard_break_test.rb | 31 - test/mri/rdoc/rdoc_markup_heading_test.rb | 34 - test/mri/rdoc/rdoc_markup_include_test.rb | 19 - .../rdoc_markup_indented_paragraph_test.rb | 53 - test/mri/rdoc/rdoc_markup_paragraph_test.rb | 32 - test/mri/rdoc/rdoc_markup_parser_test.rb | 1684 ------ test/mri/rdoc/rdoc_markup_pre_process_test.rb | 481 -- test/mri/rdoc/rdoc_markup_raw_test.rb | 22 - test/mri/rdoc/rdoc_markup_test.rb | 95 - test/mri/rdoc/rdoc_markup_to_ansi_test.rb | 380 -- test/mri/rdoc/rdoc_markup_to_bs_test.rb | 363 -- .../rdoc/rdoc_markup_to_html_crossref_test.rb | 325 -- .../rdoc/rdoc_markup_to_html_snippet_test.rb | 709 --- test/mri/rdoc/rdoc_markup_to_html_test.rb | 1024 ---- .../rdoc_markup_to_joined_paragraph_test.rb | 32 - test/mri/rdoc/rdoc_markup_to_label_test.rb | 112 - test/mri/rdoc/rdoc_markup_to_markdown_test.rb | 397 -- test/mri/rdoc/rdoc_markup_to_rdoc_test.rb | 388 -- .../rdoc_markup_to_table_of_contents_test.rb | 126 - test/mri/rdoc/rdoc_markup_to_tt_only_test.rb | 246 - test/mri/rdoc/rdoc_markup_verbatim_test.rb | 29 - test/mri/rdoc/rdoc_method_attr_test.rb | 210 - test/mri/rdoc/rdoc_normal_class_test.rb | 47 - test/mri/rdoc/rdoc_normal_module_test.rb | 42 - test/mri/rdoc/rdoc_options_test.rb | 975 ---- test/mri/rdoc/rdoc_parser_c_test.rb | 2150 ------- test/mri/rdoc/rdoc_parser_changelog_test.rb | 483 -- test/mri/rdoc/rdoc_parser_markdown_test.rb | 61 - test/mri/rdoc/rdoc_parser_prism_ruby_test.rb | 2165 -------- test/mri/rdoc/rdoc_parser_rd_test.rb | 55 - test/mri/rdoc/rdoc_parser_ruby_test.rb | 4396 --------------- test/mri/rdoc/rdoc_parser_simple_test.rb | 115 - test/mri/rdoc/rdoc_parser_test.rb | 334 -- test/mri/rdoc/rdoc_rd_block_parser_test.rb | 557 -- test/mri/rdoc/rdoc_rd_inline_parser_test.rb | 178 - test/mri/rdoc/rdoc_rd_inline_test.rb | 63 - test/mri/rdoc/rdoc_rd_test.rb | 30 - test/mri/rdoc/rdoc_rdoc_test.rb | 582 -- test/mri/rdoc/rdoc_require_test.rb | 25 - test/mri/rdoc/rdoc_ri_driver_test.rb | 1610 ------ test/mri/rdoc/rdoc_ri_paths_test.rb | 157 - test/mri/rdoc/rdoc_rubygems_hook_test.rb | 329 -- test/mri/rdoc/rdoc_servlet_test.rb | 553 -- test/mri/rdoc/rdoc_single_class_test.rb | 20 - test/mri/rdoc/rdoc_stats_test.rb | 720 --- test/mri/rdoc/rdoc_store_test.rb | 1057 ---- test/mri/rdoc/rdoc_task_test.rb | 182 - test/mri/rdoc/rdoc_text_test.rb | 585 -- test/mri/rdoc/rdoc_token_stream_test.rb | 109 - test/mri/rdoc/rdoc_tom_doc_test.rb | 579 -- test/mri/rdoc/rdoc_top_level_test.rb | 289 - test/mri/reline/helper.rb | 158 - test/mri/reline/test_ansi.rb | 72 - test/mri/reline/test_config.rb | 633 --- test/mri/reline/test_face.rb | 257 - test/mri/reline/test_history.rb | 317 -- test/mri/reline/test_key_actor_emacs.rb | 1759 ------ test/mri/reline/test_key_actor_vi.rb | 967 ---- test/mri/reline/test_key_stroke.rb | 111 - test/mri/reline/test_kill_ring.rb | 268 - test/mri/reline/test_line_editor.rb | 271 - test/mri/reline/test_macro.rb | 40 - test/mri/reline/test_reline.rb | 476 -- test/mri/reline/test_reline_key.rb | 10 - test/mri/reline/test_string_processing.rb | 46 - test/mri/reline/test_unicode.rb | 342 -- test/mri/reline/test_within_pipe.rb | 77 - .../reline/windows/test_key_event_record.rb | 41 - test/mri/reline/yamatanooroti/multiline_repl | 255 - .../yamatanooroti/termination_checker.rb | 26 - .../reline/yamatanooroti/test_rendering.rb | 1959 ------- test/mri/ruby/rjit/test_assembler.rb | 368 -- .../test_gem_commands_query_command.rb | 830 --- test/mri/test_pstore.rb | 182 - test/mri/win32ole/available_ole.rb | 41 - test/mri/win32ole/err_in_callback.rb | 10 - test/mri/win32ole/orig_data.csv | 5 - test/mri/win32ole/test_err_in_callback.rb | 56 - .../win32ole/test_folderitem2_invokeverb.rb | 66 - test/mri/win32ole/test_nil2vtempty.rb | 37 - test/mri/win32ole/test_ole_methods.rb | 35 - test/mri/win32ole/test_propertyputref.rb | 31 - test/mri/win32ole/test_thread.rb | 34 - test/mri/win32ole/test_win32ole.rb | 536 -- test/mri/win32ole/test_win32ole_event.rb | 407 -- test/mri/win32ole/test_win32ole_method.rb | 134 - .../win32ole/test_win32ole_method_event.rb | 36 - test/mri/win32ole/test_win32ole_param.rb | 98 - .../mri/win32ole/test_win32ole_param_event.rb | 30 - test/mri/win32ole/test_win32ole_record.rb | 212 - test/mri/win32ole/test_win32ole_type.rb | 199 - test/mri/win32ole/test_win32ole_type_event.rb | 44 - test/mri/win32ole/test_win32ole_typelib.rb | 117 - test/mri/win32ole/test_win32ole_variable.rb | 66 - test/mri/win32ole/test_win32ole_variant.rb | 722 --- test/mri/win32ole/test_win32ole_variant_m.rb | 37 - .../win32ole/test_win32ole_variant_outarg.rb | 69 - test/mri/win32ole/test_word.rb | 73 - 1296 files changed, 193611 deletions(-) delete mode 100644 test/mri/benchmark/test_benchmark.rb delete mode 100644 test/mri/cgi/test_cgi_cookie.rb delete mode 100644 test/mri/cgi/test_cgi_core.rb delete mode 100644 test/mri/cgi/test_cgi_header.rb delete mode 100644 test/mri/cgi/test_cgi_modruby.rb delete mode 100644 test/mri/cgi/test_cgi_multipart.rb delete mode 100644 test/mri/cgi/test_cgi_session.rb delete mode 100644 test/mri/cgi/test_cgi_tag_helper.rb delete mode 100644 test/mri/cgi/test_cgi_util.rb delete mode 100644 test/mri/cgi/testdata/file1.html delete mode 100644 test/mri/cgi/testdata/large.png delete mode 100644 test/mri/cgi/testdata/small.png delete mode 100644 test/mri/fiddle/helper.rb delete mode 100644 test/mri/fiddle/test_c_struct_builder.rb delete mode 100644 test/mri/fiddle/test_c_struct_entry.rb delete mode 100644 test/mri/fiddle/test_c_union_entity.rb delete mode 100644 test/mri/fiddle/test_closure.rb delete mode 100644 test/mri/fiddle/test_cparser.rb delete mode 100644 test/mri/fiddle/test_fiddle.rb delete mode 100644 test/mri/fiddle/test_func.rb delete mode 100644 test/mri/fiddle/test_function.rb delete mode 100644 test/mri/fiddle/test_handle.rb delete mode 100644 test/mri/fiddle/test_import.rb delete mode 100644 test/mri/fiddle/test_memory_view.rb delete mode 100644 test/mri/fiddle/test_pack.rb delete mode 100644 test/mri/fiddle/test_pinned.rb delete mode 100644 test/mri/fiddle/test_pointer.rb delete mode 100644 test/mri/irb/command/test_cd.rb delete mode 100644 test/mri/irb/command/test_custom_command.rb delete mode 100644 test/mri/irb/command/test_disable_irb.rb delete mode 100644 test/mri/irb/command/test_force_exit.rb delete mode 100644 test/mri/irb/command/test_help.rb delete mode 100644 test/mri/irb/command/test_multi_irb_commands.rb delete mode 100644 test/mri/irb/command/test_show_source.rb delete mode 100644 test/mri/irb/helper.rb delete mode 100644 test/mri/irb/test_color.rb delete mode 100644 test/mri/irb/test_color_printer.rb delete mode 100644 test/mri/irb/test_command.rb delete mode 100644 test/mri/irb/test_completion.rb delete mode 100644 test/mri/irb/test_context.rb delete mode 100644 test/mri/irb/test_debugger_integration.rb delete mode 100644 test/mri/irb/test_eval_history.rb delete mode 100644 test/mri/irb/test_evaluation.rb delete mode 100644 test/mri/irb/test_helper_method.rb delete mode 100644 test/mri/irb/test_history.rb delete mode 100644 test/mri/irb/test_init.rb delete mode 100644 test/mri/irb/test_input_method.rb delete mode 100644 test/mri/irb/test_irb.rb delete mode 100644 test/mri/irb/test_locale.rb delete mode 100644 test/mri/irb/test_nesting_parser.rb delete mode 100644 test/mri/irb/test_option.rb delete mode 100644 test/mri/irb/test_raise_exception.rb delete mode 100644 test/mri/irb/test_ruby_lex.rb delete mode 100644 test/mri/irb/test_tracer.rb delete mode 100644 test/mri/irb/test_type_completor.rb delete mode 100644 test/mri/irb/test_workspace.rb delete mode 100644 test/mri/irb/yamatanooroti/test_rendering.rb delete mode 100644 test/mri/json/fixtures/fail4.json delete mode 100644 test/mri/json/fixtures/fail9.json delete mode 100644 test/mri/json/fixtures/pass15.json delete mode 100644 test/mri/json/fixtures/pass16.json delete mode 100644 test/mri/json/fixtures/pass17.json delete mode 100644 test/mri/json/fixtures/pass26.json delete mode 100644 test/mri/logger/test_formatter.rb delete mode 100644 test/mri/logger/test_logdevice.rb delete mode 100644 test/mri/logger/test_logger.rb delete mode 100644 test/mri/logger/test_logperiod.rb delete mode 100644 test/mri/logger/test_severity.rb delete mode 100644 test/mri/openssl/fixtures/pkey/certificate.der delete mode 100644 test/mri/openssl/fixtures/pkey/dsa1024.pem delete mode 100644 test/mri/openssl/fixtures/pkey/dsa256.pem delete mode 100644 test/mri/openssl/fixtures/pkey/dsa512.pem delete mode 100644 test/mri/openssl/fixtures/pkey/empty.der delete mode 100644 test/mri/openssl/fixtures/pkey/empty.pem delete mode 100644 test/mri/openssl/fixtures/pkey/fullchain.pem delete mode 100644 test/mri/openssl/fixtures/pkey/garbage.txt delete mode 100644 test/mri/openssl/fixtures/pkey/p256_too_large.pem delete mode 100644 test/mri/openssl/fixtures/pkey/p384_invalid.pem delete mode 100644 test/mri/openssl/fixtures/pkey/rsa1024.pem delete mode 100644 test/mri/ostruct/test_ostruct.rb delete mode 100644 test/mri/prism/attribute_write_test.rb delete mode 100644 test/mri/prism/command_line_test.rb delete mode 100644 test/mri/prism/comments_test.rb delete mode 100644 test/mri/prism/compiler_test.rb delete mode 100644 test/mri/prism/constant_path_node_test.rb delete mode 100644 test/mri/prism/desugar_compiler_test.rb delete mode 100644 test/mri/prism/dispatcher_test.rb delete mode 100644 test/mri/prism/encoding_test.rb delete mode 100644 test/mri/prism/errors/block_args_in_array_assignment.txt delete mode 100644 test/mri/prism/errors/dont_allow_return_inside_sclass_body.txt delete mode 100644 test/mri/prism/errors/it_with_ordinary_parameter.txt delete mode 100644 test/mri/prism/errors/keyword_args_in_array_assignment.txt delete mode 100644 test/mri/prism/fixtures/it_indirect_writes.txt delete mode 100644 test/mri/prism/fixtures/seattlerb/block_break.txt delete mode 100644 test/mri/prism/fixtures/seattlerb/block_next.txt delete mode 100644 test/mri/prism/fixtures/seattlerb/dasgn_icky2.txt delete mode 100644 test/mri/prism/fixtures/seattlerb/yield_arg.txt delete mode 100644 test/mri/prism/fixtures/seattlerb/yield_call_assocs.txt delete mode 100644 test/mri/prism/fixtures/seattlerb/yield_empty_parens.txt delete mode 100644 test/mri/prism/fixtures/unparser/corpus/literal/control.txt delete mode 100644 test/mri/prism/fixtures/unparser/corpus/literal/yield.txt delete mode 100644 test/mri/prism/fixtures/whitequark/args_assocs.txt delete mode 100644 test/mri/prism/fixtures/whitequark/args_assocs_legacy.txt delete mode 100644 test/mri/prism/fixtures/whitequark/break.txt delete mode 100644 test/mri/prism/fixtures/whitequark/break_block.txt delete mode 100644 test/mri/prism/fixtures/whitequark/class_definition_in_while_cond.txt delete mode 100644 test/mri/prism/fixtures/whitequark/if_while_after_class__since_32.txt delete mode 100644 test/mri/prism/fixtures/whitequark/next.txt delete mode 100644 test/mri/prism/fixtures/whitequark/next_block.txt delete mode 100644 test/mri/prism/fixtures/whitequark/redo.txt delete mode 100644 test/mri/prism/fixtures/whitequark/retry.txt delete mode 100644 test/mri/prism/fixtures/whitequark/yield.txt delete mode 100644 test/mri/prism/format_errors_test.rb delete mode 100644 test/mri/prism/integer_parse_test.rb delete mode 100644 test/mri/prism/location_test.rb delete mode 100644 test/mri/prism/memsize_test.rb delete mode 100644 test/mri/prism/parameters_signature_test.rb delete mode 100644 test/mri/prism/parse_comments_test.rb delete mode 100644 test/mri/prism/parse_stream_test.rb delete mode 100644 test/mri/prism/parse_test.rb delete mode 100644 test/mri/prism/parser_test.rb delete mode 100644 test/mri/prism/pattern_test.rb delete mode 100644 test/mri/prism/ripper_test.rb delete mode 100644 test/mri/prism/ruby_api_test.rb delete mode 100644 test/mri/prism/ruby_parser_test.rb delete mode 100644 test/mri/prism/snapshots/alias.txt delete mode 100644 test/mri/prism/snapshots/arithmetic.txt delete mode 100644 test/mri/prism/snapshots/arrays.txt delete mode 100644 test/mri/prism/snapshots/begin_ensure.txt delete mode 100644 test/mri/prism/snapshots/begin_rescue.txt delete mode 100644 test/mri/prism/snapshots/blocks.txt delete mode 100644 test/mri/prism/snapshots/boolean_operators.txt delete mode 100644 test/mri/prism/snapshots/booleans.txt delete mode 100644 test/mri/prism/snapshots/break.txt delete mode 100644 test/mri/prism/snapshots/case.txt delete mode 100644 test/mri/prism/snapshots/classes.txt delete mode 100644 test/mri/prism/snapshots/command_method_call.txt delete mode 100644 test/mri/prism/snapshots/comments.txt delete mode 100644 test/mri/prism/snapshots/constants.txt delete mode 100644 test/mri/prism/snapshots/dash_heredocs.txt delete mode 100644 test/mri/prism/snapshots/defined.txt delete mode 100644 test/mri/prism/snapshots/dos_endings.txt delete mode 100644 test/mri/prism/snapshots/dstring.txt delete mode 100644 test/mri/prism/snapshots/dsym_str.txt delete mode 100644 test/mri/prism/snapshots/embdoc_no_newline_at_end.txt delete mode 100644 test/mri/prism/snapshots/emoji_method_calls.txt delete mode 100644 test/mri/prism/snapshots/endless_methods.txt delete mode 100644 test/mri/prism/snapshots/endless_range_in_conditional.txt delete mode 100644 test/mri/prism/snapshots/for.txt delete mode 100644 test/mri/prism/snapshots/global_variables.txt delete mode 100644 test/mri/prism/snapshots/hashes.txt delete mode 100644 test/mri/prism/snapshots/heredoc.txt delete mode 100644 test/mri/prism/snapshots/heredoc_with_carriage_returns.txt delete mode 100644 test/mri/prism/snapshots/heredoc_with_comment.txt delete mode 100644 test/mri/prism/snapshots/heredoc_with_escaped_newline_at_start.txt delete mode 100644 test/mri/prism/snapshots/heredoc_with_trailing_newline.txt delete mode 100644 test/mri/prism/snapshots/heredocs_leading_whitespace.txt delete mode 100644 test/mri/prism/snapshots/heredocs_nested.txt delete mode 100644 test/mri/prism/snapshots/heredocs_with_ignored_newlines.txt delete mode 100644 test/mri/prism/snapshots/heredocs_with_ignored_newlines_and_non_empty.txt delete mode 100644 test/mri/prism/snapshots/if.txt delete mode 100644 test/mri/prism/snapshots/indented_file_end.txt delete mode 100644 test/mri/prism/snapshots/integer_operations.txt delete mode 100644 test/mri/prism/snapshots/keyword_method_names.txt delete mode 100644 test/mri/prism/snapshots/keywords.txt delete mode 100644 test/mri/prism/snapshots/lambda.txt delete mode 100644 test/mri/prism/snapshots/method_calls.txt delete mode 100644 test/mri/prism/snapshots/methods.txt delete mode 100644 test/mri/prism/snapshots/modules.txt delete mode 100644 test/mri/prism/snapshots/multi_write.txt delete mode 100644 test/mri/prism/snapshots/newline_terminated.txt delete mode 100644 test/mri/prism/snapshots/next.txt delete mode 100644 test/mri/prism/snapshots/nils.txt delete mode 100644 test/mri/prism/snapshots/non_alphanumeric_methods.txt delete mode 100644 test/mri/prism/snapshots/not.txt delete mode 100644 test/mri/prism/snapshots/numbers.txt delete mode 100644 test/mri/prism/snapshots/patterns.txt delete mode 100644 test/mri/prism/snapshots/procs.txt delete mode 100644 test/mri/prism/snapshots/range_begin_open_exclusive.txt delete mode 100644 test/mri/prism/snapshots/range_begin_open_inclusive.txt delete mode 100644 test/mri/prism/snapshots/range_beginless.txt delete mode 100644 test/mri/prism/snapshots/range_end_open_exclusive.txt delete mode 100644 test/mri/prism/snapshots/range_end_open_inclusive.txt delete mode 100644 test/mri/prism/snapshots/ranges.txt delete mode 100644 test/mri/prism/snapshots/regex.txt delete mode 100644 test/mri/prism/snapshots/regex_char_width.txt delete mode 100644 test/mri/prism/snapshots/repeat_parameters.txt delete mode 100644 test/mri/prism/snapshots/rescue.txt delete mode 100644 test/mri/prism/snapshots/rescue_modifier.txt delete mode 100644 test/mri/prism/snapshots/return.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/BEGIN.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/TestRubyParserShared.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/__ENCODING__.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/alias_gvar_backref.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/alias_resword.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/and_multi.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/aref_args_assocs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/aref_args_lit_assocs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/args_kw_block.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/array_line_breaks.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/array_lits_trailing_calls.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/assoc__bare.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/assoc_label.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/attr_asgn_colon_id.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/attrasgn_array_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/attrasgn_array_lhs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/attrasgn_primary_dot_constant.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/backticks_interpolation_line.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bang_eq.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bdot2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bdot3.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/begin_ensure_no_bodies.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/begin_rescue_else_ensure_bodies.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/begin_rescue_else_ensure_no_bodies.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/begin_rescue_ensure_no_bodies.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg__bare.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg_kwsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg_opt_arg_block.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg_opt_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg_opt_splat_arg_block_omfg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg_optional.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg_scope.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg_scope2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_arg_splat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_args_kwargs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_args_no_kwargs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_args_opt1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_args_opt2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_args_opt2_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_args_opt3.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_break.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_call_defn_call_block_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_call_dot_op2_brace_block.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_call_dot_op2_cmd_args_do_block.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_call_operation_colon.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_call_operation_dot.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_call_paren_call_block_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_command_operation_colon.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_command_operation_dot.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_decomp_anon_splat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_decomp_arg_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_decomp_arg_splat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_decomp_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_kw.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_kw__required.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_kwarg_lvar.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_kwarg_lvar_multiple.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_next.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_opt_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_opt_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_opt_splat_arg_block_omfg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_optarg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_paren_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_reg_optarg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_return.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_scope.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/block_splat_reg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug169.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug179.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug190.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug191.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug202.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug236.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug290.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_187.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_215.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_249.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_and.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_args__19.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_args_masgn.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_args_masgn2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_args_masgn_outer_parens__19.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_call_arglist_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_case_when_regexp.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_cond_pct.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_hash_args.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_hash_args_trailing_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_hash_interp_array.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_masgn_right.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_not_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/bug_op_asgn_rescue.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_and.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_arg_assoc.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_arg_assoc_kwsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_arg_kwsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_args_assoc_quoted.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_args_assoc_trailing_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_args_command.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_array_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_array_block_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_array_lambda_block_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_array_lit_inline_hash.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_assoc.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_assoc_new.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_assoc_new_if_multiline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_assoc_trailing_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_bang_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_bang_squiggle.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_begin_call_block_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_block_arg_named.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_carat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_colon2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_colon_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_div.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_dot_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_env.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_eq3.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_gt.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_kwsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_leading_dots.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_leading_dots_comment.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_lt.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_lte.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_not.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_pipe.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_rshift.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_self_brackets.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_spaceship.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_stabby_do_end_with_block.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_stabby_with_braces_block.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_star.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_star2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_trailing_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_trailing_dots.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/call_unary_bang.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_31.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_37.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_42.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_42_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_47.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_67.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_86.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_86_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_array_pat_const.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_array_pat_const2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_array_pat_paren_assign.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_const.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_else.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_find.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_find_array.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_hash_pat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_hash_pat_assign.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_hash_pat_paren_assign.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_hash_pat_paren_true.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_hash_pat_rest.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_hash_pat_rest_solo.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_if_unless_post_mod.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_multiple.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/case_in_or.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/class_comments.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/cond_unary_minus.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/const_2_op_asgn_or2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/const_3_op_asgn_or.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/const_op_asgn_and1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/const_op_asgn_and2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/const_op_asgn_or.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dasgn_icky2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defined_eh_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_arg_asplat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_arg_forward_args.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_args_forward_args.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_comments.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_endless_command.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_endless_command_rescue.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_forward_args.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_forward_args__no_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_kwarg_env.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_kwarg_kwarg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_kwarg_kwsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_kwarg_kwsplat_anon.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_kwarg_lvar.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_kwarg_no_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_kwarg_val.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_no_kwargs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_oneliner.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_oneliner_eq2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_oneliner_noargs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_oneliner_noargs_parentheses.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_oneliner_rescue.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_opt_last_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_opt_reg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_opt_splat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_powarg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_reg_opt_reg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_splat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defn_unary_not.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defns_reserved.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defs_as_arg_with_do_block_inside.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defs_comments.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defs_endless_command.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defs_endless_command_rescue.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defs_kwarg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defs_oneliner.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defs_oneliner_eq2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/defs_oneliner_rescue.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult0_.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult1_line_numbers.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult1_line_numbers2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult2_.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3_.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3_3.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3_4.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3_5.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3__10.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3__11.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3__12.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3__6.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3__7.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3__8.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult3__9.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult4__leading_dots.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult4__leading_dots2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult6_.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult6__7.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult6__8.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/difficult7_.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/do_bug.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/do_lambda.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dot2_nil__26.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dot3_nil__26.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dstr_evstr.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dstr_evstr_empty_end.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dstr_lex_state.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dstr_str.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dsym_esc_to_sym.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/dsym_to_sym.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/eq_begin_line_numbers.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/eq_begin_why_wont_people_use_their_spacebar.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/evstr_evstr.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/evstr_str.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/expr_not_bang.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/f_kw.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/f_kw__required.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/flip2_env_lvar.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/float_with_if_modifier.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc__backslash_dos_format.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_backslash_nl.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_bad_hex_escape.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_bad_oct_escape.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_comma_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_lineno.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_nested.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly_blank_line_plus_interpolation.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly_blank_lines.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly_empty.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly_interp.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly_no_indent.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly_tabs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly_tabs_extra.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_squiggly_visually_blank_lines.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_trailing_slash_continued_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_unicode.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_carriage_return_escapes.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_carriage_return_escapes_windows.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_extra_carriage_horrible_mix.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_extra_carriage_returns.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_extra_carriage_returns_windows.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_interpolation_and_carriage_return_escapes.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_interpolation_and_carriage_return_escapes_windows.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_not_global_interpolation.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_only_carriage_returns.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/heredoc_with_only_carriage_returns_windows.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/if_elsif.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/if_symbol.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/in_expr_no_case.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/index_0.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/index_0_opasgn.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/integer_with_if_modifier.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/interpolated_symbol_array_line_breaks.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/interpolated_word_array_line_breaks.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_10_1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_10_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_11_1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_11_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_2__19.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_3.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_4.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_5.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_6.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_7_1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_7_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_8_1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_8_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_9_1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_args_9_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_kwarg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/iter_kwarg_kwsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/label_vs_string.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lambda_do_vs_brace.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lasgn_arg_rescue_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lasgn_call_bracket_rescue_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lasgn_call_nobracket_rescue_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lasgn_command.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lasgn_env.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lasgn_ivar_env.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lasgn_lasgn_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/lasgn_middle_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/magic_encoding_comment.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_anon_splat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_arg_colon_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_arg_ident.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_arg_splat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_colon2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_colon3.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_double_paren.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_lhs_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_paren.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_splat_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_splat_arg_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_star.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/masgn_var_star_var.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/messy_op_asgn_lineno.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/method_call_assoc_trailing_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/method_call_trailing_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/mlhs_back_anonsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/mlhs_back_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/mlhs_front_anonsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/mlhs_front_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/mlhs_keyword.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/mlhs_mid_anonsplat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/mlhs_mid_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/mlhs_rescue.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/module_comments.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/multiline_hash_declaration.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/non_interpolated_symbol_array_line_breaks.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/non_interpolated_word_array_line_breaks.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/op_asgn_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/op_asgn_dot_ident_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/op_asgn_index_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/op_asgn_primary_colon_const_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/op_asgn_primary_colon_identifier1.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/op_asgn_primary_colon_identifier_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/op_asgn_val_dot_ident_command_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_def_special_name.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_if_not_canonical.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_if_not_noncanonical.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_block.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_block_inline_comment.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_block_inline_comment_leading_newlines.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_block_inline_multiline_comment.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_call_ivar_arg_no_parens_line_break.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_call_ivar_line_break_paren.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_call_no_args.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_defn_complex.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_defn_no_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_defn_no_parens_args.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_dot2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_dot2_open.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_dot3.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_dot3_open.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_dstr_escaped_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_dstr_soft_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_evstr_after_break.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_hash_lit.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_heredoc.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_heredoc_evstr.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_heredoc_hardnewline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_heredoc_regexp_chars.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_iter_call_no_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_iter_call_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_multiline_str.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_multiline_str_literal_n.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_newlines.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_op_asgn.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_postexe.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_preexe.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_rescue.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_return.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_str_with_newline_escape.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_to_ary.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_line_trailing_newlines.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_opt_call_args_assocs_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_opt_call_args_lit_comma.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_pattern_019.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_pattern_044.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_pattern_051.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_pattern_058.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_pattern_058_2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_pattern_069.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_pattern_076.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_until_not_canonical.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_until_not_noncanonical.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_while_not_canonical.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/parse_while_not_noncanonical.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/pctW_lineno.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/pct_Q_backslash_nl.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/pct_nl.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/pct_w_heredoc_interp_nested.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/pipe_semicolon.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/pipe_space.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/qWords_space.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/qsymbols.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/qsymbols_empty.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/qsymbols_empty_space.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/qsymbols_interp.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/quoted_symbol_hash_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/quoted_symbol_keys.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/qw_escape.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/qw_escape_term.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/qwords_empty.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/read_escape_unicode_curlies.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/read_escape_unicode_h4.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/regexp.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/regexp_esc_C_slash.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/regexp_esc_u.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/regexp_escape_extended.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/regexp_unicode_curlies.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/required_kwarg_no_value.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/rescue_do_end_ensure_result.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/rescue_do_end_no_raise.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/rescue_do_end_raised.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/rescue_do_end_rescued.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/rescue_in_block.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/rescue_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/return_call_assocs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/rhs_asgn.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/ruby21_numbers.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_attrasgn.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_attrasgn_constant.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_call_after_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_call_dot_parens.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_call_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_call_operator.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_call_rhs_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_calls.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_op_asgn.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/safe_op_asgn2.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/slashy_newlines_within_string.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/stabby_arg_no_paren.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/stabby_arg_opt_splat_arg_block_omfg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/stabby_block_iter_call.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/stabby_block_iter_call_no_target_with_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/stabby_block_kw.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/stabby_block_kw__required.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/stabby_proc_scope.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_backslashes.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_double_double_escaped_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_double_escaped_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_double_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_evstr.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_evstr_escape.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_heredoc_interp.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_interp_ternary_or_label.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_lit_concat_bad_encodings.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_newline_hash_line_number.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_pct_Q_nested.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_pct_nested_nested.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_pct_q.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_single_double_escaped_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_single_escaped_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_single_newline.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_str.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/str_str_str.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/super_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/symbol_empty.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/symbol_list.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/symbols.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/symbols_empty.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/symbols_empty_space.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/symbols_interp.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/thingy.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/uminus_float.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/unary_minus.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/unary_plus.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/unary_plus_on_literal.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/unary_tilde.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/utf8_bom.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/when_splat.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/words_interp.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/yield_arg.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/yield_call_assocs.txt delete mode 100644 test/mri/prism/snapshots/seattlerb/yield_empty_parens.txt delete mode 100644 test/mri/prism/snapshots/single_method_call_with_bang.txt delete mode 100644 test/mri/prism/snapshots/single_quote_heredocs.txt delete mode 100644 test/mri/prism/snapshots/spanning_heredoc.txt delete mode 100644 test/mri/prism/snapshots/spanning_heredoc_newlines.txt delete mode 100644 test/mri/prism/snapshots/strings.txt delete mode 100644 test/mri/prism/snapshots/super.txt delete mode 100644 test/mri/prism/snapshots/symbols.txt delete mode 100644 test/mri/prism/snapshots/ternary_operator.txt delete mode 100644 test/mri/prism/snapshots/tilde_heredocs.txt delete mode 100644 test/mri/prism/snapshots/undef.txt delete mode 100644 test/mri/prism/snapshots/unescaping.txt delete mode 100644 test/mri/prism/snapshots/unless.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/alias.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/assignment.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/block.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/case.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/class.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/control.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/def.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/defined.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/defs.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/dstr.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/empty.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/empty_begin.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/flipflop.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/for.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/hookexe.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/if.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/kwbegin.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/lambda.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/literal.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/module.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/opasgn.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/pattern.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/pragma.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/range.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/rescue.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/send.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/since/27.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/since/30.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/since/31.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/since/32.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/singletons.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/super.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/unary.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/undef.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/variables.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/while.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/literal/yield.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/and.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/block.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/def.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/dstr.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/kwbegin.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/literal.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/send.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/undef.txt delete mode 100644 test/mri/prism/snapshots/unparser/corpus/semantic/while.txt delete mode 100644 test/mri/prism/snapshots/until.txt delete mode 100644 test/mri/prism/snapshots/variables.txt delete mode 100644 test/mri/prism/snapshots/while.txt delete mode 100644 test/mri/prism/snapshots/whitequark/__ENCODING__.txt delete mode 100644 test/mri/prism/snapshots/whitequark/__ENCODING___legacy_.txt delete mode 100644 test/mri/prism/snapshots/whitequark/alias.txt delete mode 100644 test/mri/prism/snapshots/whitequark/alias_gvar.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ambiuous_quoted_label_in_ternary_operator.txt delete mode 100644 test/mri/prism/snapshots/whitequark/and.txt delete mode 100644 test/mri/prism/snapshots/whitequark/and_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/and_or_masgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/anonymous_blockarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/arg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/arg_duplicate_ignored.txt delete mode 100644 test/mri/prism/snapshots/whitequark/arg_label.txt delete mode 100644 test/mri/prism/snapshots/whitequark/arg_scope.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_args_assocs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_args_assocs_comma.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_args_comma.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_args_star.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_assocs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_assocs_comma.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_assocs_legacy.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_block_pass.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/args_star.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_assocs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_plain.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_splat.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_symbols.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_symbols_empty.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_symbols_interp.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_words.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_words_empty.txt delete mode 100644 test/mri/prism/snapshots/whitequark/array_words_interp.txt delete mode 100644 test/mri/prism/snapshots/whitequark/asgn_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/asgn_mrhs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/back_ref.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bang.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bang_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/begin_cmdarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/beginless_erange_after_newline.txt delete mode 100644 test/mri/prism/snapshots/whitequark/beginless_irange_after_newline.txt delete mode 100644 test/mri/prism/snapshots/whitequark/beginless_range.txt delete mode 100644 test/mri/prism/snapshots/whitequark/blockarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/blockargs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/break.txt delete mode 100644 test/mri/prism/snapshots/whitequark/break_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_435.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_447.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_452.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_466.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_473.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_480.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_481.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_ascii_8bit_in_literal.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_cmd_string_lookahead.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_cmdarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_def_no_paren_eql_begin.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_do_block_in_call_args.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_do_block_in_cmdarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_do_block_in_hash_brace.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_heredoc_do.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_interp_single.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_lambda_leakage.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_regex_verification.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_rescue_empty_else.txt delete mode 100644 test/mri/prism/snapshots/whitequark/bug_while_not_parens_do.txt delete mode 100644 test/mri/prism/snapshots/whitequark/case_cond.txt delete mode 100644 test/mri/prism/snapshots/whitequark/case_cond_else.txt delete mode 100644 test/mri/prism/snapshots/whitequark/case_expr.txt delete mode 100644 test/mri/prism/snapshots/whitequark/case_expr_else.txt delete mode 100644 test/mri/prism/snapshots/whitequark/casgn_scoped.txt delete mode 100644 test/mri/prism/snapshots/whitequark/casgn_toplevel.txt delete mode 100644 test/mri/prism/snapshots/whitequark/casgn_unscoped.txt delete mode 100644 test/mri/prism/snapshots/whitequark/character.txt delete mode 100644 test/mri/prism/snapshots/whitequark/class.txt delete mode 100644 test/mri/prism/snapshots/whitequark/class_definition_in_while_cond.txt delete mode 100644 test/mri/prism/snapshots/whitequark/class_super.txt delete mode 100644 test/mri/prism/snapshots/whitequark/class_super_label.txt delete mode 100644 test/mri/prism/snapshots/whitequark/comments_before_leading_dot__27.txt delete mode 100644 test/mri/prism/snapshots/whitequark/complex.txt delete mode 100644 test/mri/prism/snapshots/whitequark/cond_begin.txt delete mode 100644 test/mri/prism/snapshots/whitequark/cond_begin_masgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/cond_eflipflop.txt delete mode 100644 test/mri/prism/snapshots/whitequark/cond_iflipflop.txt delete mode 100644 test/mri/prism/snapshots/whitequark/cond_match_current_line.txt delete mode 100644 test/mri/prism/snapshots/whitequark/const_op_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/const_scoped.txt delete mode 100644 test/mri/prism/snapshots/whitequark/const_toplevel.txt delete mode 100644 test/mri/prism/snapshots/whitequark/const_unscoped.txt delete mode 100644 test/mri/prism/snapshots/whitequark/cpath.txt delete mode 100644 test/mri/prism/snapshots/whitequark/cvar.txt delete mode 100644 test/mri/prism/snapshots/whitequark/cvasgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/dedenting_heredoc.txt delete mode 100644 test/mri/prism/snapshots/whitequark/dedenting_interpolating_heredoc_fake_line_continuation.txt delete mode 100644 test/mri/prism/snapshots/whitequark/dedenting_non_interpolating_heredoc_line_continuation.txt delete mode 100644 test/mri/prism/snapshots/whitequark/def.txt delete mode 100644 test/mri/prism/snapshots/whitequark/defined.txt delete mode 100644 test/mri/prism/snapshots/whitequark/defs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/empty_stmt.txt delete mode 100644 test/mri/prism/snapshots/whitequark/endless_comparison_method.txt delete mode 100644 test/mri/prism/snapshots/whitequark/endless_method.txt delete mode 100644 test/mri/prism/snapshots/whitequark/endless_method_command_syntax.txt delete mode 100644 test/mri/prism/snapshots/whitequark/endless_method_forwarded_args_legacy.txt delete mode 100644 test/mri/prism/snapshots/whitequark/endless_method_with_rescue_mod.txt delete mode 100644 test/mri/prism/snapshots/whitequark/endless_method_without_args.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ensure.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ensure_empty.txt delete mode 100644 test/mri/prism/snapshots/whitequark/false.txt delete mode 100644 test/mri/prism/snapshots/whitequark/float.txt delete mode 100644 test/mri/prism/snapshots/whitequark/for.txt delete mode 100644 test/mri/prism/snapshots/whitequark/for_mlhs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/forward_arg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/forward_arg_with_open_args.txt delete mode 100644 test/mri/prism/snapshots/whitequark/forward_args_legacy.txt delete mode 100644 test/mri/prism/snapshots/whitequark/forwarded_argument_with_kwrestarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/forwarded_argument_with_restarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/forwarded_kwrestarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/forwarded_kwrestarg_with_additional_kwarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/forwarded_restarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/gvar.txt delete mode 100644 test/mri/prism/snapshots/whitequark/gvasgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/hash_empty.txt delete mode 100644 test/mri/prism/snapshots/whitequark/hash_hashrocket.txt delete mode 100644 test/mri/prism/snapshots/whitequark/hash_kwsplat.txt delete mode 100644 test/mri/prism/snapshots/whitequark/hash_label.txt delete mode 100644 test/mri/prism/snapshots/whitequark/hash_label_end.txt delete mode 100644 test/mri/prism/snapshots/whitequark/hash_pair_value_omission.txt delete mode 100644 test/mri/prism/snapshots/whitequark/heredoc.txt delete mode 100644 test/mri/prism/snapshots/whitequark/if.txt delete mode 100644 test/mri/prism/snapshots/whitequark/if_else.txt delete mode 100644 test/mri/prism/snapshots/whitequark/if_elsif.txt delete mode 100644 test/mri/prism/snapshots/whitequark/if_masgn__24.txt delete mode 100644 test/mri/prism/snapshots/whitequark/if_mod.txt delete mode 100644 test/mri/prism/snapshots/whitequark/if_nl_then.txt delete mode 100644 test/mri/prism/snapshots/whitequark/if_while_after_class__since_32.txt delete mode 100644 test/mri/prism/snapshots/whitequark/int.txt delete mode 100644 test/mri/prism/snapshots/whitequark/int___LINE__.txt delete mode 100644 test/mri/prism/snapshots/whitequark/interp_digit_var.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ivar.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ivasgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/keyword_argument_omission.txt delete mode 100644 test/mri/prism/snapshots/whitequark/kwarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/kwbegin_compstmt.txt delete mode 100644 test/mri/prism/snapshots/whitequark/kwnilarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/kwoptarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/kwoptarg_with_kwrestarg_and_forwarded_args.txt delete mode 100644 test/mri/prism/snapshots/whitequark/kwrestarg_named.txt delete mode 100644 test/mri/prism/snapshots/whitequark/kwrestarg_unnamed.txt delete mode 100644 test/mri/prism/snapshots/whitequark/lbrace_arg_after_command_args.txt delete mode 100644 test/mri/prism/snapshots/whitequark/lparenarg_after_lvar__since_25.txt delete mode 100644 test/mri/prism/snapshots/whitequark/lvar.txt delete mode 100644 test/mri/prism/snapshots/whitequark/lvar_injecting_match.txt delete mode 100644 test/mri/prism/snapshots/whitequark/lvasgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/masgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/masgn_attr.txt delete mode 100644 test/mri/prism/snapshots/whitequark/masgn_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/masgn_const.txt delete mode 100644 test/mri/prism/snapshots/whitequark/masgn_nested.txt delete mode 100644 test/mri/prism/snapshots/whitequark/masgn_splat.txt delete mode 100644 test/mri/prism/snapshots/whitequark/method_definition_in_while_cond.txt delete mode 100644 test/mri/prism/snapshots/whitequark/module.txt delete mode 100644 test/mri/prism/snapshots/whitequark/multiple_pattern_matches.txt delete mode 100644 test/mri/prism/snapshots/whitequark/newline_in_hash_argument.txt delete mode 100644 test/mri/prism/snapshots/whitequark/next.txt delete mode 100644 test/mri/prism/snapshots/whitequark/next_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/nil.txt delete mode 100644 test/mri/prism/snapshots/whitequark/nil_expression.txt delete mode 100644 test/mri/prism/snapshots/whitequark/non_lvar_injecting_match.txt delete mode 100644 test/mri/prism/snapshots/whitequark/not.txt delete mode 100644 test/mri/prism/snapshots/whitequark/not_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/not_masgn__24.txt delete mode 100644 test/mri/prism/snapshots/whitequark/nth_ref.txt delete mode 100644 test/mri/prism/snapshots/whitequark/numbered_args_after_27.txt delete mode 100644 test/mri/prism/snapshots/whitequark/numparam_outside_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/op_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/op_asgn_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/op_asgn_index.txt delete mode 100644 test/mri/prism/snapshots/whitequark/op_asgn_index_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/optarg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/or.txt delete mode 100644 test/mri/prism/snapshots/whitequark/or_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_272.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_490.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_507.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_518.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_525.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_604.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_640.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_645.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_bug_830.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_drops_truncated_parts_of_squiggly_heredoc.txt delete mode 100644 test/mri/prism/snapshots/whitequark/parser_slash_slash_n_escaping_in_literals.txt delete mode 100644 test/mri/prism/snapshots/whitequark/pattern_matching__FILE__LINE_literals.txt delete mode 100644 test/mri/prism/snapshots/whitequark/pattern_matching_blank_else.txt delete mode 100644 test/mri/prism/snapshots/whitequark/pattern_matching_else.txt delete mode 100644 test/mri/prism/snapshots/whitequark/pattern_matching_single_line.txt delete mode 100644 test/mri/prism/snapshots/whitequark/pattern_matching_single_line_allowed_omission_of_parentheses.txt delete mode 100644 test/mri/prism/snapshots/whitequark/postexe.txt delete mode 100644 test/mri/prism/snapshots/whitequark/preexe.txt delete mode 100644 test/mri/prism/snapshots/whitequark/procarg0.txt delete mode 100644 test/mri/prism/snapshots/whitequark/range_exclusive.txt delete mode 100644 test/mri/prism/snapshots/whitequark/range_inclusive.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rational.txt delete mode 100644 test/mri/prism/snapshots/whitequark/redo.txt delete mode 100644 test/mri/prism/snapshots/whitequark/regex_interp.txt delete mode 100644 test/mri/prism/snapshots/whitequark/regex_plain.txt delete mode 100644 test/mri/prism/snapshots/whitequark/resbody_list.txt delete mode 100644 test/mri/prism/snapshots/whitequark/resbody_list_mrhs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/resbody_list_var.txt delete mode 100644 test/mri/prism/snapshots/whitequark/resbody_var.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_else.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_else_ensure.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_ensure.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_in_lambda_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_mod.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_mod_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_mod_masgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_mod_op_assign.txt delete mode 100644 test/mri/prism/snapshots/whitequark/rescue_without_begin_end.txt delete mode 100644 test/mri/prism/snapshots/whitequark/restarg_named.txt delete mode 100644 test/mri/prism/snapshots/whitequark/restarg_unnamed.txt delete mode 100644 test/mri/prism/snapshots/whitequark/retry.txt delete mode 100644 test/mri/prism/snapshots/whitequark/return.txt delete mode 100644 test/mri/prism/snapshots/whitequark/return_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_10279.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_10653.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_11107.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_11380.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_11873.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_11873_a.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_11873_b.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_11989.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_11990.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_12073.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_12402.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_12669.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_12686.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_13547.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_14690.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_15789.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ruby_bug_9669.txt delete mode 100644 test/mri/prism/snapshots/whitequark/sclass.txt delete mode 100644 test/mri/prism/snapshots/whitequark/self.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_attr_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_attr_asgn_conditional.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_binary_op.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_block_chain_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_block_conditional.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_call.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_conditional.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_index.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_index_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_index_asgn_legacy.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_index_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_index_legacy.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_lambda.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_lambda_args.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_lambda_args_noparen.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_lambda_args_shadow.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_lambda_legacy.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_op_asgn_conditional.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_plain.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_plain_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_self.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_self_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/send_unary_op.txt delete mode 100644 test/mri/prism/snapshots/whitequark/slash_newline_in_heredocs.txt delete mode 100644 test/mri/prism/snapshots/whitequark/space_args_arg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/space_args_arg_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/space_args_arg_call.txt delete mode 100644 test/mri/prism/snapshots/whitequark/space_args_arg_newline.txt delete mode 100644 test/mri/prism/snapshots/whitequark/space_args_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/space_args_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/string___FILE__.txt delete mode 100644 test/mri/prism/snapshots/whitequark/string_concat.txt delete mode 100644 test/mri/prism/snapshots/whitequark/string_dvar.txt delete mode 100644 test/mri/prism/snapshots/whitequark/string_interp.txt delete mode 100644 test/mri/prism/snapshots/whitequark/string_plain.txt delete mode 100644 test/mri/prism/snapshots/whitequark/super.txt delete mode 100644 test/mri/prism/snapshots/whitequark/super_block.txt delete mode 100644 test/mri/prism/snapshots/whitequark/symbol_interp.txt delete mode 100644 test/mri/prism/snapshots/whitequark/symbol_plain.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ternary.txt delete mode 100644 test/mri/prism/snapshots/whitequark/ternary_ambiguous_symbol.txt delete mode 100644 test/mri/prism/snapshots/whitequark/trailing_forward_arg.txt delete mode 100644 test/mri/prism/snapshots/whitequark/true.txt delete mode 100644 test/mri/prism/snapshots/whitequark/unary_num_pow_precedence.txt delete mode 100644 test/mri/prism/snapshots/whitequark/undef.txt delete mode 100644 test/mri/prism/snapshots/whitequark/unless.txt delete mode 100644 test/mri/prism/snapshots/whitequark/unless_else.txt delete mode 100644 test/mri/prism/snapshots/whitequark/unless_mod.txt delete mode 100644 test/mri/prism/snapshots/whitequark/until.txt delete mode 100644 test/mri/prism/snapshots/whitequark/until_mod.txt delete mode 100644 test/mri/prism/snapshots/whitequark/until_post.txt delete mode 100644 test/mri/prism/snapshots/whitequark/var_and_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/var_op_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/var_op_asgn_cmd.txt delete mode 100644 test/mri/prism/snapshots/whitequark/var_or_asgn.txt delete mode 100644 test/mri/prism/snapshots/whitequark/when_multi.txt delete mode 100644 test/mri/prism/snapshots/whitequark/when_splat.txt delete mode 100644 test/mri/prism/snapshots/whitequark/when_then.txt delete mode 100644 test/mri/prism/snapshots/whitequark/while.txt delete mode 100644 test/mri/prism/snapshots/whitequark/while_mod.txt delete mode 100644 test/mri/prism/snapshots/whitequark/while_post.txt delete mode 100644 test/mri/prism/snapshots/whitequark/xstring_interp.txt delete mode 100644 test/mri/prism/snapshots/whitequark/xstring_plain.txt delete mode 100644 test/mri/prism/snapshots/whitequark/yield.txt delete mode 100644 test/mri/prism/snapshots/whitequark/zsuper.txt delete mode 100644 test/mri/prism/snapshots/xstring.txt delete mode 100644 test/mri/prism/snapshots/xstring_with_backslash.txt delete mode 100644 test/mri/prism/snapshots/yield.txt delete mode 100644 test/mri/prism/static_inspect_test.rb delete mode 100644 test/mri/prism/static_literals_test.rb delete mode 100644 test/mri/prism/warnings_test.rb delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Amps and angle encoding.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Auto links.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Backslash escapes.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Blockquotes with code blocks.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Code Blocks.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Code Spans.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Hard-wrapped paragraphs with list-like lines.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Horizontal rules.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Inline HTML (Advanced).text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Inline HTML (Simple).text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Inline HTML comments.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Links, inline style.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Links, reference style.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Links, shortcut references.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Literal quotes in titles.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Markdown Documentation - Basics.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Markdown Documentation - Syntax.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Nested blockquotes.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Ordered and unordered lists.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Strong and em together.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Tabs.text delete mode 100644 test/mri/rdoc/rdoc/MarkdownTest_1.0.3/Tidiness.text delete mode 100644 test/mri/rdoc/rdoc/README delete mode 100644 test/mri/rdoc/rdoc/binary.dat delete mode 100644 test/mri/rdoc/rdoc/helper.rb delete mode 100644 test/mri/rdoc/rdoc/hidden.zip.txt delete mode 100644 test/mri/rdoc/rdoc/rdoc_alias_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_any_method_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_attr_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_class_module_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_code_object_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_comment_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_constant_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_context_section_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_context_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_cross_reference_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_encoding_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_extend_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_generator_darkfish_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_generator_json_index_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_generator_markup_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_generator_pot_po_entry_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_generator_pot_po_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_generator_pot_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_generator_ri_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_i18n_locale_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_i18n_text_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_include_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markdown_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markdown_test_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_attribute_manager_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_attributes_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_document_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_formatter_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_hard_break_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_heading_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_include_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_indented_paragraph_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_paragraph_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_parser_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_pre_process_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_raw_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_ansi_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_bs_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_html_crossref_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_html_snippet_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_html_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_joined_paragraph_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_label_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_markdown_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_rdoc_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_table_of_contents_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_to_tt_only_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_markup_verbatim_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_method_attr_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_normal_class_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_normal_module_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_options_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_parser_c_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_parser_changelog_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_parser_markdown_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_parser_prism_ruby_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_parser_rd_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_parser_ruby_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_parser_simple_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_parser_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_rd_block_parser_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_rd_inline_parser_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_rd_inline_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_rd_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_rdoc_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_require_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_ri_driver_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_ri_paths_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_rubygems_hook_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_servlet_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_single_class_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_stats_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_store_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_task_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_text_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_token_stream_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_tom_doc_test.rb delete mode 100644 test/mri/rdoc/rdoc/rdoc_top_level_test.rb delete mode 100644 test/mri/rdoc/rdoc/support/formatter_test_case.rb delete mode 100644 test/mri/rdoc/rdoc/support/test_case.rb delete mode 100644 test/mri/rdoc/rdoc/support/text_formatter_test_case.rb delete mode 100644 test/mri/rdoc/rdoc/test.ja.largedoc delete mode 100644 test/mri/rdoc/rdoc/test.ja.rdoc delete mode 100644 test/mri/rdoc/rdoc/test.ja.txt delete mode 100644 test/mri/rdoc/rdoc/test.txt delete mode 100644 test/mri/rdoc/rdoc/xref_data.rb delete mode 100644 test/mri/rdoc/rdoc/xref_test_case.rb delete mode 100644 test/mri/rdoc/rdoc_alias_test.rb delete mode 100644 test/mri/rdoc/rdoc_any_method_test.rb delete mode 100644 test/mri/rdoc/rdoc_attr_test.rb delete mode 100644 test/mri/rdoc/rdoc_class_module_test.rb delete mode 100644 test/mri/rdoc/rdoc_code_object_test.rb delete mode 100644 test/mri/rdoc/rdoc_comment_test.rb delete mode 100644 test/mri/rdoc/rdoc_constant_test.rb delete mode 100644 test/mri/rdoc/rdoc_context_section_test.rb delete mode 100644 test/mri/rdoc/rdoc_context_test.rb delete mode 100644 test/mri/rdoc/rdoc_cross_reference_test.rb delete mode 100644 test/mri/rdoc/rdoc_encoding_test.rb delete mode 100644 test/mri/rdoc/rdoc_extend_test.rb delete mode 100644 test/mri/rdoc/rdoc_generator_darkfish_test.rb delete mode 100644 test/mri/rdoc/rdoc_generator_json_index_test.rb delete mode 100644 test/mri/rdoc/rdoc_generator_markup_test.rb delete mode 100644 test/mri/rdoc/rdoc_generator_pot_po_entry_test.rb delete mode 100644 test/mri/rdoc/rdoc_generator_pot_po_test.rb delete mode 100644 test/mri/rdoc/rdoc_generator_pot_test.rb delete mode 100644 test/mri/rdoc/rdoc_generator_ri_test.rb delete mode 100644 test/mri/rdoc/rdoc_i18n_locale_test.rb delete mode 100644 test/mri/rdoc/rdoc_i18n_text_test.rb delete mode 100644 test/mri/rdoc/rdoc_include_test.rb delete mode 100644 test/mri/rdoc/rdoc_markdown_test.rb delete mode 100644 test/mri/rdoc/rdoc_markdown_test_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_attribute_manager_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_attributes_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_document_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_formatter_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_hard_break_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_heading_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_include_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_indented_paragraph_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_paragraph_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_parser_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_pre_process_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_raw_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_ansi_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_bs_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_html_crossref_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_html_snippet_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_html_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_joined_paragraph_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_label_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_markdown_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_rdoc_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_table_of_contents_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_to_tt_only_test.rb delete mode 100644 test/mri/rdoc/rdoc_markup_verbatim_test.rb delete mode 100644 test/mri/rdoc/rdoc_method_attr_test.rb delete mode 100644 test/mri/rdoc/rdoc_normal_class_test.rb delete mode 100644 test/mri/rdoc/rdoc_normal_module_test.rb delete mode 100644 test/mri/rdoc/rdoc_options_test.rb delete mode 100644 test/mri/rdoc/rdoc_parser_c_test.rb delete mode 100644 test/mri/rdoc/rdoc_parser_changelog_test.rb delete mode 100644 test/mri/rdoc/rdoc_parser_markdown_test.rb delete mode 100644 test/mri/rdoc/rdoc_parser_prism_ruby_test.rb delete mode 100644 test/mri/rdoc/rdoc_parser_rd_test.rb delete mode 100644 test/mri/rdoc/rdoc_parser_ruby_test.rb delete mode 100644 test/mri/rdoc/rdoc_parser_simple_test.rb delete mode 100644 test/mri/rdoc/rdoc_parser_test.rb delete mode 100644 test/mri/rdoc/rdoc_rd_block_parser_test.rb delete mode 100644 test/mri/rdoc/rdoc_rd_inline_parser_test.rb delete mode 100644 test/mri/rdoc/rdoc_rd_inline_test.rb delete mode 100644 test/mri/rdoc/rdoc_rd_test.rb delete mode 100644 test/mri/rdoc/rdoc_rdoc_test.rb delete mode 100644 test/mri/rdoc/rdoc_require_test.rb delete mode 100644 test/mri/rdoc/rdoc_ri_driver_test.rb delete mode 100644 test/mri/rdoc/rdoc_ri_paths_test.rb delete mode 100644 test/mri/rdoc/rdoc_rubygems_hook_test.rb delete mode 100644 test/mri/rdoc/rdoc_servlet_test.rb delete mode 100644 test/mri/rdoc/rdoc_single_class_test.rb delete mode 100644 test/mri/rdoc/rdoc_stats_test.rb delete mode 100644 test/mri/rdoc/rdoc_store_test.rb delete mode 100644 test/mri/rdoc/rdoc_task_test.rb delete mode 100644 test/mri/rdoc/rdoc_text_test.rb delete mode 100644 test/mri/rdoc/rdoc_token_stream_test.rb delete mode 100644 test/mri/rdoc/rdoc_tom_doc_test.rb delete mode 100644 test/mri/rdoc/rdoc_top_level_test.rb delete mode 100644 test/mri/reline/helper.rb delete mode 100644 test/mri/reline/test_ansi.rb delete mode 100644 test/mri/reline/test_config.rb delete mode 100644 test/mri/reline/test_face.rb delete mode 100644 test/mri/reline/test_history.rb delete mode 100644 test/mri/reline/test_key_actor_emacs.rb delete mode 100644 test/mri/reline/test_key_actor_vi.rb delete mode 100644 test/mri/reline/test_key_stroke.rb delete mode 100644 test/mri/reline/test_kill_ring.rb delete mode 100644 test/mri/reline/test_line_editor.rb delete mode 100644 test/mri/reline/test_macro.rb delete mode 100644 test/mri/reline/test_reline.rb delete mode 100644 test/mri/reline/test_reline_key.rb delete mode 100644 test/mri/reline/test_string_processing.rb delete mode 100644 test/mri/reline/test_unicode.rb delete mode 100644 test/mri/reline/test_within_pipe.rb delete mode 100644 test/mri/reline/windows/test_key_event_record.rb delete mode 100755 test/mri/reline/yamatanooroti/multiline_repl delete mode 100644 test/mri/reline/yamatanooroti/termination_checker.rb delete mode 100644 test/mri/reline/yamatanooroti/test_rendering.rb delete mode 100644 test/mri/ruby/rjit/test_assembler.rb delete mode 100644 test/mri/rubygems/test_gem_commands_query_command.rb delete mode 100644 test/mri/test_pstore.rb delete mode 100644 test/mri/win32ole/available_ole.rb delete mode 100644 test/mri/win32ole/err_in_callback.rb delete mode 100644 test/mri/win32ole/orig_data.csv delete mode 100644 test/mri/win32ole/test_err_in_callback.rb delete mode 100644 test/mri/win32ole/test_folderitem2_invokeverb.rb delete mode 100644 test/mri/win32ole/test_nil2vtempty.rb delete mode 100644 test/mri/win32ole/test_ole_methods.rb delete mode 100644 test/mri/win32ole/test_propertyputref.rb delete mode 100644 test/mri/win32ole/test_thread.rb delete mode 100644 test/mri/win32ole/test_win32ole.rb delete mode 100644 test/mri/win32ole/test_win32ole_event.rb delete mode 100644 test/mri/win32ole/test_win32ole_method.rb delete mode 100644 test/mri/win32ole/test_win32ole_method_event.rb delete mode 100644 test/mri/win32ole/test_win32ole_param.rb delete mode 100644 test/mri/win32ole/test_win32ole_param_event.rb delete mode 100644 test/mri/win32ole/test_win32ole_record.rb delete mode 100644 test/mri/win32ole/test_win32ole_type.rb delete mode 100644 test/mri/win32ole/test_win32ole_type_event.rb delete mode 100644 test/mri/win32ole/test_win32ole_typelib.rb delete mode 100644 test/mri/win32ole/test_win32ole_variable.rb delete mode 100644 test/mri/win32ole/test_win32ole_variant.rb delete mode 100644 test/mri/win32ole/test_win32ole_variant_m.rb delete mode 100644 test/mri/win32ole/test_win32ole_variant_outarg.rb delete mode 100644 test/mri/win32ole/test_word.rb diff --git a/test/mri.stdlib.index b/test/mri.stdlib.index index 0d38dc43262..150e69309e7 100644 --- a/test/mri.stdlib.index +++ b/test/mri.stdlib.index @@ -1,6 +1,5 @@ # MRI stdlib tests -benchmark cgi digest # only runs as root and we don't support most of this anyway @@ -10,19 +9,14 @@ etc fiber fileutils io -irb json -logger monitor net open-uri openssl optparse -ostruct pathname psych -rdoc -reline resolv ripper socket @@ -60,9 +54,6 @@ test_weakref.rb # Looks for did_you_mean in the wrong place (../lib) #did_you_mean -# Various issues setting up test libraries to load -#fiddle - # Unsupported library, tries to use "fire_update!" defined by test harness #mkmf diff --git a/test/mri/benchmark/test_benchmark.rb b/test/mri/benchmark/test_benchmark.rb deleted file mode 100644 index 2e0c47af751..00000000000 --- a/test/mri/benchmark/test_benchmark.rb +++ /dev/null @@ -1,167 +0,0 @@ -# frozen_string_literal: true -require 'test/unit' -require 'benchmark' - -class TestBenchmark < Test::Unit::TestCase - BENCH_FOR_TIMES_UPTO = lambda do |x| - n = 1000 - tf = x.report("for:") { for _ in 1..n; '1'; end } - tt = x.report("times:") { n.times do ; '1'; end } - tu = x.report("upto:") { 1.upto(n) do ; '1'; end } - [tf+tt+tu, (tf+tt+tu)/3] - end - - BENCH_FOR_TIMES_UPTO_NO_LABEL = lambda do |x| - n = 1000 - x.report { for _ in 1..n; '1'; end } - x.report { n.times do ; '1'; end } - x.report { 1.upto(n) do ; '1'; end } - end - - def labels - %w[first second third] - end - - def bench(type = :bm, *args, &block) - if block - Benchmark.send(type, *args, &block) - else - Benchmark.send(type, *args) do |x| - labels.each { |label| - x.report(label) {} - } - end - end - end - - def capture_bench_output(type, *args, &block) - capture_output { bench(type, *args, &block) }.first.gsub(/[ \-]\d\.\d{6}/, ' --time--') - end - - def test_tms_outputs_nicely - assert_equal(" 0.000000 0.000000 0.000000 ( 0.000000)\n", Benchmark::Tms.new.to_s) - assert_equal(" 1.000000 2.000000 10.000000 ( 5.000000)\n", Benchmark::Tms.new(1,2,3,4,5).to_s) - assert_equal("1.000000 2.000000 3.000000 4.000000 10.000000 (5.000000) label", - Benchmark::Tms.new(1,2,3,4,5,'label').format('%u %y %U %Y %t %r %n')) - assert_equal("1.000000 2.000", Benchmark::Tms.new(1).format('%u %.3f', 2)) - assert_equal("100.000000 150.000000 250.000000 (200.000000)\n", - Benchmark::Tms.new(100, 150, 0, 0, 200).to_s) - end - - def test_tms_wont_modify_the_format_String_given - format = "format %u" - Benchmark::Tms.new.format(format) - assert_equal("format %u", format) - end - - BENCHMARK_OUTPUT_WITH_TOTAL_AVG = <total: --time-- --time-- --time-- ( --time--) ->avg: --time-- --time-- --time-- ( --time--) -BENCH - - def test_benchmark_does_not_print_any_space_if_the_given_caption_is_empty - assert_equal(<<-BENCH, capture_bench_output(:benchmark)) -first --time-- --time-- --time-- ( --time--) -second --time-- --time-- --time-- ( --time--) -third --time-- --time-- --time-- ( --time--) -BENCH - end - - def test_benchmark_makes_extra_calculations_with_an_Array_at_the_end_of_the_benchmark_and_show_the_result - assert_equal(BENCHMARK_OUTPUT_WITH_TOTAL_AVG, - capture_bench_output(:benchmark, - Benchmark::CAPTION, 7, - Benchmark::FORMAT, ">total:", ">avg:", - &BENCH_FOR_TIMES_UPTO)) - end - - def test_bm_returns_an_Array_of_the_times_with_the_labels - [:bm, :bmbm].each do |meth| - capture_output do - results = bench(meth) - assert_instance_of(Array, results) - assert_equal(labels.size, results.size) - results.zip(labels).each { |tms, label| - assert_instance_of(Benchmark::Tms, tms) - assert_equal(label, tms.label) - } - end - end - end - - def test_bm_correctly_output_when_the_label_width_is_given - assert_equal(<<-BENCH, capture_bench_output(:bm, 6)) - user system total real -first --time-- --time-- --time-- ( --time--) -second --time-- --time-- --time-- ( --time--) -third --time-- --time-- --time-- ( --time--) -BENCH - end - - def test_bm_correctly_output_when_no_label_is_given - assert_equal(<<-BENCH, capture_bench_output(:bm, &BENCH_FOR_TIMES_UPTO_NO_LABEL)) - user system total real - --time-- --time-- --time-- ( --time--) - --time-- --time-- --time-- ( --time--) - --time-- --time-- --time-- ( --time--) -BENCH - end - - def test_bm_can_make_extra_calcultations_with_an_array_at_the_end_of_the_benchmark - assert_equal(BENCHMARK_OUTPUT_WITH_TOTAL_AVG, - capture_bench_output(:bm, 7, ">total:", ">avg:", - &BENCH_FOR_TIMES_UPTO)) - end - - BMBM_OUTPUT = < '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/mri/cgi/test_cgi_core.rb b/test/mri/cgi/test_cgi_core.rb deleted file mode 100644 index f7adb7e99f1..00000000000 --- a/test/mri/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 = '' - assert_equal(expected, cgi.doctype) - assert_equal("Content-Type: text/html\r\n\r\n",cgi.header) - ## html4 - cgi = CGI.new('html4') - expected = '' - assert_equal(expected, cgi.doctype) - assert_equal("Content-Type: text/html\r\n\r\n",cgi.header) - ## html4 transitional - cgi = CGI.new('html4Tr') - expected = '' - assert_equal(expected, cgi.doctype) - assert_equal("Content-Type: text/html\r\n\r\n",cgi.header) - ## html4 frameset - cgi = CGI.new('html4Fr') - expected = '' - assert_equal(expected, cgi.doctype) - assert_equal("Content-Type: text/html\r\n\r\n",cgi.header) - ## html5 - cgi = CGI.new('html5') - expected = '' - assert_equal(expected, cgi.doctype) - assert_match(/^
<\/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/mri/cgi/test_cgi_header.rb b/test/mri/cgi/test_cgi_header.rb deleted file mode 100644 index ec2f4deb72b..00000000000 --- a/test/mri/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/mri/cgi/test_cgi_modruby.rb b/test/mri/cgi/test_cgi_modruby.rb deleted file mode 100644 index 90132962b5a..00000000000 --- a/test/mri/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/mri/cgi/test_cgi_multipart.rb b/test/mri/cgi/test_cgi_multipart.rb deleted file mode 100644 index 5e8ec253901..00000000000 --- a/test/mri/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" -## # "\n" -## # "

Hello

\n" -## # "\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 - - -

Hello

- - ---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/mri/cgi/test_cgi_session.rb b/test/mri/cgi/test_cgi_session.rb deleted file mode 100644 index b16b69766e9..00000000000 --- a/test/mri/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 - 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/mri/cgi/test_cgi_tag_helper.rb b/test/mri/cgi/test_cgi_tag_helper.rb deleted file mode 100644 index 0b99dfc1bc4..00000000000 --- a/test/mri/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('',cgi.a) - assert_equal('',cgi.a('bar')) - assert_equal('foo',cgi.a{'foo'}) - assert_equal('foo',cgi.a('bar'){'foo'}) - assert_equal('',cgi.tt) - assert_equal('',cgi.tt('bar')) - assert_equal('foo',cgi.tt{'foo'}) - assert_equal('foo',cgi.tt('bar'){'foo'}) - assert_equal('',cgi.i) - assert_equal('',cgi.i('bar')) - assert_equal('foo',cgi.i{'foo'}) - assert_equal('foo',cgi.i('bar'){'foo'}) - assert_equal('',cgi.b) - assert_equal('',cgi.b('bar')) - assert_equal('foo',cgi.b{'foo'}) - assert_equal('foo',cgi.b('bar'){'foo'}) - assert_equal('',cgi.u) - assert_equal('',cgi.u('bar')) - assert_equal('foo',cgi.u{'foo'}) - assert_equal('foo',cgi.u('bar'){'foo'}) - assert_equal('',cgi.strike) - assert_equal('',cgi.strike('bar')) - assert_equal('foo',cgi.strike{'foo'}) - assert_equal('foo',cgi.strike('bar'){'foo'}) - assert_equal('',cgi.big) - assert_equal('',cgi.big('bar')) - assert_equal('foo',cgi.big{'foo'}) - assert_equal('foo',cgi.big('bar'){'foo'}) - assert_equal('',cgi.small) - assert_equal('',cgi.small('bar')) - assert_equal('foo',cgi.small{'foo'}) - assert_equal('foo',cgi.small('bar'){'foo'}) - assert_equal('',cgi.sub) - assert_equal('',cgi.sub('bar')) - assert_equal('foo',cgi.sub{'foo'}) - assert_equal('foo',cgi.sub('bar'){'foo'}) - assert_equal('',cgi.sup) - assert_equal('',cgi.sup('bar')) - assert_equal('foo',cgi.sup{'foo'}) - assert_equal('foo',cgi.sup('bar'){'foo'}) - assert_equal('',cgi.em) - assert_equal('',cgi.em('bar')) - assert_equal('foo',cgi.em{'foo'}) - assert_equal('foo',cgi.em('bar'){'foo'}) - assert_equal('',cgi.strong) - assert_equal('',cgi.strong('bar')) - assert_equal('foo',cgi.strong{'foo'}) - assert_equal('foo',cgi.strong('bar'){'foo'}) - assert_equal('',cgi.dfn) - assert_equal('',cgi.dfn('bar')) - assert_equal('foo',cgi.dfn{'foo'}) - assert_equal('foo',cgi.dfn('bar'){'foo'}) - assert_equal('',cgi.code) - assert_equal('',cgi.code('bar')) - assert_equal('foo',cgi.code{'foo'}) - assert_equal('foo',cgi.code('bar'){'foo'}) - assert_equal('',cgi.samp) - assert_equal('',cgi.samp('bar')) - assert_equal('foo',cgi.samp{'foo'}) - assert_equal('foo',cgi.samp('bar'){'foo'}) - assert_equal('',cgi.kbd) - assert_equal('',cgi.kbd('bar')) - assert_equal('foo',cgi.kbd{'foo'}) - assert_equal('foo',cgi.kbd('bar'){'foo'}) - assert_equal('',cgi.var) - assert_equal('',cgi.var('bar')) - assert_equal('foo',cgi.var{'foo'}) - assert_equal('foo',cgi.var('bar'){'foo'}) - assert_equal('',cgi.cite) - assert_equal('',cgi.cite('bar')) - assert_equal('foo',cgi.cite{'foo'}) - assert_equal('foo',cgi.cite('bar'){'foo'}) - assert_equal('',cgi.font) - assert_equal('',cgi.font('bar')) - assert_equal('foo',cgi.font{'foo'}) - assert_equal('foo',cgi.font('bar'){'foo'}) - assert_equal('
',cgi.address) - assert_equal('
',cgi.address('bar')) - assert_equal('
foo
',cgi.address{'foo'}) - assert_equal('
foo
',cgi.address('bar'){'foo'}) - assert_equal('
',cgi.div) - assert_equal('
',cgi.div('bar')) - assert_equal('
foo
',cgi.div{'foo'}) - assert_equal('
foo
',cgi.div('bar'){'foo'}) - assert_equal('
',cgi.center) - assert_equal('
',cgi.center('bar')) - assert_equal('
foo
',cgi.center{'foo'}) - assert_equal('
foo
',cgi.center('bar'){'foo'}) - assert_equal('',cgi.map) - assert_equal('',cgi.map('bar')) - assert_equal('foo',cgi.map{'foo'}) - assert_equal('foo',cgi.map('bar'){'foo'}) - assert_equal('',cgi.applet) - assert_equal('',cgi.applet('bar')) - assert_equal('foo',cgi.applet{'foo'}) - assert_equal('foo',cgi.applet('bar'){'foo'}) - assert_equal('
',cgi.pre)
-    assert_equal('
',cgi.pre('bar'))
-    assert_equal('
foo
',cgi.pre{'foo'}) - assert_equal('
foo
',cgi.pre('bar'){'foo'}) - assert_equal('',cgi.xmp) - assert_equal('',cgi.xmp('bar')) - assert_equal('foo',cgi.xmp{'foo'}) - assert_equal('foo',cgi.xmp('bar'){'foo'}) - assert_equal('',cgi.listing) - assert_equal('',cgi.listing('bar')) - assert_equal('foo',cgi.listing{'foo'}) - assert_equal('foo',cgi.listing('bar'){'foo'}) - assert_equal('
',cgi.dl) - assert_equal('
',cgi.dl('bar')) - assert_equal('
foo
',cgi.dl{'foo'}) - assert_equal('
foo
',cgi.dl('bar'){'foo'}) - assert_equal('
    ',cgi.ol) - assert_equal('
      ',cgi.ol('bar')) - assert_equal('
        foo
      ',cgi.ol{'foo'}) - assert_equal('
        foo
      ',cgi.ol('bar'){'foo'}) - assert_equal('
        ',cgi.ul) - assert_equal('
          ',cgi.ul('bar')) - assert_equal('
            foo
          ',cgi.ul{'foo'}) - assert_equal('
            foo
          ',cgi.ul('bar'){'foo'}) - assert_equal('',cgi.dir) - assert_equal('',cgi.dir('bar')) - assert_equal('foo',cgi.dir{'foo'}) - assert_equal('foo',cgi.dir('bar'){'foo'}) - assert_equal('',cgi.menu) - assert_equal('',cgi.menu('bar')) - assert_equal('foo',cgi.menu{'foo'}) - assert_equal('foo',cgi.menu('bar'){'foo'}) - assert_equal('',cgi.select) - assert_equal('',cgi.select('bar')) - assert_equal('',cgi.select{'foo'}) - assert_equal('',cgi.select('bar'){'foo'}) - assert_equal('
          ',cgi.table) - assert_equal('
          ',cgi.table('bar')) - assert_equal('foo
          ',cgi.table{'foo'}) - assert_equal('foo
          ',cgi.table('bar'){'foo'}) - assert_equal('',cgi.title) - assert_equal('',cgi.title('bar')) - assert_equal('foo',cgi.title{'foo'}) - assert_equal('foo',cgi.title('bar'){'foo'}) - assert_equal('',cgi.style) - assert_equal('',cgi.style('bar')) - assert_equal('',cgi.style{'foo'}) - assert_equal('',cgi.style('bar'){'foo'}) - assert_equal('',cgi.script) - assert_equal('',cgi.script('bar')) - assert_equal('',cgi.script{'foo'}) - assert_equal('',cgi.script('bar'){'foo'}) - assert_equal('

          ',cgi.h1) - assert_equal('

          ',cgi.h1('bar')) - assert_equal('

          foo

          ',cgi.h1{'foo'}) - assert_equal('

          foo

          ',cgi.h1('bar'){'foo'}) - assert_equal('

          ',cgi.h2) - assert_equal('

          ',cgi.h2('bar')) - assert_equal('

          foo

          ',cgi.h2{'foo'}) - assert_equal('

          foo

          ',cgi.h2('bar'){'foo'}) - assert_equal('

          ',cgi.h3) - assert_equal('

          ',cgi.h3('bar')) - assert_equal('

          foo

          ',cgi.h3{'foo'}) - assert_equal('

          foo

          ',cgi.h3('bar'){'foo'}) - assert_equal('

          ',cgi.h4) - assert_equal('

          ',cgi.h4('bar')) - assert_equal('

          foo

          ',cgi.h4{'foo'}) - assert_equal('

          foo

          ',cgi.h4('bar'){'foo'}) - assert_equal('
          ',cgi.h5) - assert_equal('
          ',cgi.h5('bar')) - assert_equal('
          foo
          ',cgi.h5{'foo'}) - assert_equal('
          foo
          ',cgi.h5('bar'){'foo'}) - assert_equal('
          ',cgi.h6) - assert_equal('
          ',cgi.h6('bar')) - assert_equal('
          foo
          ',cgi.h6{'foo'}) - assert_equal('
          foo
          ',cgi.h6('bar'){'foo'}) - assert_match(/^